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.

MyCardGame.sol
// 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.

install
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 step
deal-hand.ts
import { 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.

CardEntropy0x86Ceb3234F879D45C90fc24DB0fB25F995820F54
CardToken ($CARD)0x37B78eB93F40C4966c944d889F8F24C306F66eEC
CardStaking0x916cee186793D19Ed00C466Bb4Cd373cf30e8E8E
DemoCardGame0xe54fF9060b59AD062B02bDb07D99255E86788A3e
Default dealer0x79ef83b4094d39b0eaD43081f112f119E58926Bb

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)