Skip to content

Tournaments ​

Beta

Tournaments are in beta. The API may change before they leave it.

A tournament is a scheduled event on your token page, run by one of your page's Crown Apps. Players register, your game server reports who placed where, and once the event has closed and a review hold has passed, Chain Daddy pays the prize table in your token from a prize pool you fund from your own wallet. The winners sign nothing and pay no gas.

A tournament adds three things to a prize pool: a time window, the entry options you choose, and one payout of the whole table after a review hold.

You're the sponsor. Prizes must be won by skill or by taking part, never by chance, and entry is never paid for. The rules you publish are the event's rules. See the terms.

How it works ​

  1. You create an event on your token page: the app it runs in, the prize pool that pays it, the prize table, the window, the rules and who may enter. It starts as a draft.
  2. You schedule it. Players can register from the token page.
  3. It goes live at its start. Your game server posts the results, as often as it likes, until the payout.
  4. It closes at its end. The review hold starts: 24 hours unless you set another length. During the hold you can disqualify an entry, with a reason.
  5. The prize table is paid when the hold ends, to the best ranked entries that were not disqualified, through your prize pool's own awards.

The token page shows the event, its entry count and, once it is paid, the winners with their transactions. It never shows the full standings: your app shows its own standings to its players.

Before you start ​

  • A prize pool on your token, in your wallet, with enough budget left for the whole prize table. Its Max per award must cover your biggest prize.
  • Your token's store, which the prizes are paid through (who can open a store).
  • The app installed on your token page.

The event ​

Field
appSlugThe app the event runs in, installed on the page
titleUp to 120 characters
rulesRequired, up to 5000 characters. Published with the event
startsAt, endsAtThe window, in RFC 3339. At most 90 days
holdHoursThe review hold after the end, 0 to 168. Default 24
minPlayersThe fewest ranked players for the prizes to be paid. Default 4
prizeTable[{"place": 1, "amount": "500000000000000000000"}, …]: places 1 to N, each once, N at most 20. Amounts are base units of your token
campaignIdYour prize pool
eligibilityWho may enter, below
resultsKeyIdThe store key your game server posts results with, below. Without one, only you can post them

Who may enter ​

json
{"holdersOnly": {"minBalance": "1000000000000000000000"}, "oneEntryPerWallet": true, "verifiedIdentity": false}
Option
holdersOnlyOnly wallets holding at least minBalance base units of your token may register. null for an event open to everyone
oneEntryPerWalletOn by default. The platform always keeps one registration per wallet; this tells your app whether a wallet may also play only once
verifiedIdentityA player must have an X, Discord, GitHub, Twitch, YouTube or Kick account proven on their wallet, and one proven account enters once: a second wallet with the same account is refused

The wallet that owns the prize pool can't enter its own event.

Posting results ​

Your game server posts the results with a store key that has the tournaments:results scope, and which you pinned on the event as its resultsKeyId. That scope opens this one route and nothing else: not an award, not a purchase, not the store's reads. Create a separate key for it.

bash
curl -X PUT https://api.chaindaddy.io/api/v2/tournaments/$TOURNAMENT_ID/results \
  -H "Authorization: Bearer $CHAINDADDY_RESULTS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"results": [
        {"wallet": "0x5a1c…", "rank": 1, "metric": "1:02.381"},
        {"wallet": "0x7d20…", "rank": 2, "metric": "1:03.004"}
      ]}'
#   → {"tournamentId": "…", "accepted": 2}
Field
walletA wallet that registered for the event
rank1 or more. Lower is better; ties are allowed
metricOptional, up to 64 characters: what your game measured (a time, a score). Only you see it

Every post replaces the one before, so post the whole standings each time. A wallet you leave out is unranked. You can post while the event is live and during the review hold, until the payout starts. Your own account (a session or your account key) can post too.

Take each wallet from a sign-in you have verified, never from a value a game client sends you. In a Crown App, that is the app's session token.

Errors ​

Errors are {"error": "…", "code": "…"}.

StatuscodeWhat to do
400INVALID_REQUESTFix the body: a wallet twice, a rank under 1, a metric too long, an unknown field
400WALLET_NOT_REGISTEREDwallets lists the ones that did not register. Nothing was written
401INVALID_STORE_KEYThe key is wrong or revoked
403TEST_KEY_NOT_ALLOWEDA test store key (cd_iap_test_…) never drives a real payout. Use the live store's key
403SCOPE_REQUIREDThe key lacks tournaments:results
403KEY_NOT_PINNEDThe event's resultsKeyId is another key
403IAP_PLAN_REQUIREDThe wallet that opened the store no longer has the plan its keys need
404NOT_FOUNDNo such event
409TOURNAMENT_STATEThe event is not live or in its hold: not started, paying, paid or cancelled

The payout ​

When the hold ends:

  • Fewer than minPlayers ranked players (disqualified entries don't count): the event ends paid with no winners, and payoutNote says why.
  • Otherwise place 1 goes to the best rank, place 2 to the next, and so on, skipping disqualified entries. Two entries on the same rank are ordered by who registered first. If fewer players ranked than there are places, the lower places are not paid.

The places are paid as your prize pool's awards, all together or not at all: every limit of the pool is checked for the whole table at once. If the pool can't cover it (its budget, its max per award, its awards left, its per-wallet or 24-hour limits, or it is paused), no place is paid, the event waits in needs_funding, and payoutNote says what to fix. Fix it, then retry the payout from the Events tab, or with POST /api/v2/tournaments/{id}/retry-payout.

Each place's award has the Idempotency-Key

<tournamentId>:<wallet>:place-<n>

so a payout that runs twice pays nothing twice. Your store's webhook hears each prize as iap.airdrop.award.submitted, …confirmed or …failed, with that key in data.payload.idempotencyKey, exactly like a prize your server pays itself (what happens next).

Statuses ​

StatusMeans
draftOnly you and your page's managers see it. You can edit it
scheduledPublished. Players can register
liveBetween its start and its end. Players can register, and results are accepted
closedThe review hold. Results are accepted, and you can disqualify
payingThe payout is running. Results and disqualifications are closed
needs_fundingThe prize pool couldn't cover the table, and nothing was paid. Fix it and retry, or cancel
paidDone. The winners are public
cancelledStopped before its payout. Nothing was paid

The API ​

All under https://api.chaindaddy.io/api/v2. "The creator" is the wallet that owns the event's prize pool, while it manages the token page.

RouteWhoDoes
GET /tournaments?crown=&chain=AnyoneThe page's events, newest first. includeDrafts=true adds drafts, for the page's managers
GET /tournaments/{id}AnyoneOne event. winners (place, wallet, amount, txHash, status) only once it is paid. With a sign-in, registered says whether you entered
GET /tournaments/{id}/entriesThe page's managersEvery entry with its rank, metric and disqualification
POST /tournamentsThe creatorCreate a draft: crownId, chain and the fields above
PUT /tournaments/{id}The creatorEdit a draft
POST /tournaments/{id}/scheduleThe creatorPublish a draft. The prize pool is checked again
POST /tournaments/{id}/cancelThe creator, or the page's ownerAny status before the payout, or needs_funding
POST /tournaments/{id}/registerA signed-in playerRegister a wallet on the event's chain ({"wallet"} to pick one). Registering again returns the entry
PUT /tournaments/{id}/resultsThe pinned key, or the creatorPosting results
POST /tournaments/{id}/disqualifyThe creator{"wallet", "reason"}, during the hold
POST /tournaments/{id}/retry-payoutThe creatorAfter needs_funding

Creating or scheduling an event is refused when the prize table can't be paid: PRIZE_POOL_INVALID (not a prize pool of this token), PRIZE_POOL_NOT_YOURS, PRIZE_OVER_MAX_AWARD, PRIZE_TABLE_OVER_BUDGET, STORE_REQUIRED, APP_NOT_INSTALLED or RESULTS_KEY_INVALID. Registering is refused with HOLDERS_ONLY, VERIFIED_IDENTITY_REQUIRED, IDENTITY_ALREADY_ENTERED or SPONSOR_CANNOT_ENTER.