Documentation
Wiring up CardEntropy
What a consumer contract or a frontend actually needs to ask for cards, get them, and check the result.
The short version
CardEntropy is one immutable contract. A dealer registers by committing to the tip of a hash chain it built off-chain; each reveal steps one link down that chain, checked with keccak256 right there in the contract. Asking for all 52 cards costs the same gas as asking for one - the full deck comes out of a single combined seed via Fisher-Yates.
1. Implement ICardConsumer
One callback is all it takes. CardEntropy calls it once a request has been revealed and checked - see contracts/examples/DemoCardGame.sol for the working version this snippet is trimmed down from.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ICardConsumer} from "./interfaces/ICardConsumer.sol";
interface ICardEntropyMinimal {
function requestDraw(address provider, bytes32 userRandom, uint32 gasLimit, uint8 numCards)
external payable returns (uint64);
function getFee(address provider) external view returns (uint128);
}
contract MyCardGame is ICardConsumer {
ICardEntropyMinimal public immutable cardEntropy;
address public immutable dealer;
constructor(address entropy, address dealerAddress) {
cardEntropy = ICardEntropyMinimal(entropy);
dealer = dealerAddress;
}
function dealHand(bytes32 userRandom) external payable returns (uint64) {
uint128 fee = cardEntropy.getFee(dealer);
require(msg.value == fee, "wrong fee");
return cardEntropy.requestDraw{value: msg.value}(dealer, userRandom, 200_000, 5);
}
function cardCallback(uint64 sequenceNumber, address, uint8[] calldata cards) external override {
require(msg.sender == address(cardEntropy), "only CardEntropy");
// cards[i] in 0..51 - rank = id % 13, suit = id / 13
}
}2. Or request a draw straight from a frontend
The TypeScript SDK handles the read/write calls and turns the resulting card ids into ranks and suits.
npm install viem
# then copy packages/sdk/src into your project, or
# publish it to your own npm registry - it's a plain
# workspace package with no Card-Protocol-specific build stepimport { createPublicClient, createWalletClient, http, custom } from "viem";
import { CardProtocolClient, decodeCards } from "@card-protocol/sdk";
const publicClient = createPublicClient({ transport: http(RPC_URL) });
const walletClient = createWalletClient({ transport: custom(window.ethereum) });
const client = new CardProtocolClient({
cardEntropyAddress: CARD_ENTROPY_ADDRESS,
publicClient,
walletClient,
});
const { sequenceNumber } = await client.requestDraw({
provider: DEALER_ADDRESS,
numCards: 5,
account: userAddress,
});
const { cardIds } = await client.waitForReveal(sequenceNumber);
console.log(decodeCards(cardIds).map((c) => c.label));Fees & refunds
- Each provider sets its own fee via
setFee; the default dealer charges 0.000025 ETH. - Payment must match the fee exactly - both over- and under-payment revert.
- If a request sits unrevealed for 90 seconds, the original requester can call
refundRequest(sequenceNumber)to reclaim the fee.
Deployed addresses
Populated from this deployment's environment variables - see the root README for how to fill these in.
Events
RequestedDraw(sequenceNumber, provider, requester, numCards, userRandom, gasLimit)Revealed(sequenceNumber, provider, requester, cards, combinedSeed)CallbackFailed(sequenceNumber, requester, reason)- the reveal still succeeded on-chain even if your callback reverted.Refunded(sequenceNumber, requester, amount)
