Referral payouts
Every wallet user gets a shareable referral code, and the chain is user→user: a player’s L1 referrer is whoever’s code they signed up with, L2 is that person’s referrer, and so on. Your game decides who earns and how much — you own the rake. The wallet records those payouts as a dedicated ledger entry and pays them out on demand. This page is the server side: attribution, walking the chain, and the one-call distribute that does the exact split for you.
The attribution model — brief
- First-touch, immutable. A user is attributed to exactly one referrer per slot, written once (at signup, or via an explicit late attach) and never overwritten — so re-attribution cycles cannot form.
- 7-day window. Attribution must land within 7 days of account creation. A code arriving later (a stale deep link, a retroactive campaign) is silently dropped — recruiting credit is for bringing someone, not finding them already inside.
- Platform vs game scope. By default attribution is platform-wide: one referrer per user per app+environment, shared by every game the app hosts. Pass a
gameKeyto run a deliberate per-game program — a game-specific attribution overrides the platform one for that game only.
Capture happens on the client — the provider grabs ?ref=CODE from the URL and attaches it to login. See the capture side for wiring it up. This page assumes attribution already happened.
Distribute a cut across the chain
The workhorse. Give it the bettor, a total amount, and per-level basis points; it walks the chain, credits every level that exists, and reports exactly what happened. Idempotent on idemKey (per-level child keys derive from it), so a retry replays and never double-pays.
import { IAMGameWalletServer } from "@iamgame/wallet-sdk-server";
const wallet = new IAMGameWalletServer({
secretKey: process.env.IAMGAME_WALLET_SECRET_KEY!, // sk_ — BACKEND ONLY
baseUrl: "https://api-wallet.iamgame.com/v1",
});
// At settle time you know the bettor and the round's house rake. Send a slice of
// the rake up the bettor's referral network — ONE call splits it across the chain.
// bettorWalletUserId comes from verifySession, never from the client.
const cut = (rake * 40n) / 100n; // 40% of the rake goes to the network
const r = await wallet.referrals.distribute({
userId: bettorWalletUserId, // chain is walked UP from this player
totalAmount: cut, // bigint base units — YOU own rake/meaning
currency: 3, // your app's currency id
levels: [ // basis points of totalAmount, Σ ≤ 10000
{ level: 1, bps: 6000 }, // direct referrer → 60%
{ level: 2, bps: 3000 }, // their referrer → 30%
{ level: 3, bps: 1000 }, // third degree → 10%
],
sourceRef: `pool:${poolId}`,
idemKey: `refdist:${poolId}:${betId}`, // retry-safe, never double-pays
// gameKey: "flash-football", // omit = platform-wide chain
});
// r = { paid: [{ level, userId, amount, entryId, replayed }], distributed,
// undistributed, chainDepth }
houseKeeps += r.undistributed; // organic bettor / no chain → back to house40% in the sample is just a choice — set it to whatever your economics want) and how that slice splits across levels (the per-level bps, e.g. 60/30/10). Change the numbers per game or per campaign; the wallet just records the credits you tell it to. The one rule: the level bps you pass must sum to ≤ 10000 (100%).The collapse-upward split (exact, no rounding loss)
The chain is a prefix: L2 only exists if L1 does, L3 only if L2 does. So the only levels that can be absent are deeper than the chain runs. The split rule handles that cleanly: every absent deeper level’s share, plus all floor-division dust, folds up into the deepest present referrer. Shallower levels get their exact floor share; the deepest present referrer takes the exact remainder. The result: whenever any referrer exists, Σ credited === totalAmount — nothing is lost, nothing leaks to the house. Only a fully organic bettor (empty chain) leaves the whole cut undistributed.
| Bettor's chain | Credited (cut = 1000, bps 6000/3000/1000) | Why |
|---|---|---|
| L1, L2, L3 | 600, 300, 100 | Full chain — deepest gets its 100 plus any dust. |
| L1, L2 | 600, 400 | L3 absent → its 100 collapses into L2. |
| L1 | 1000 | L2 and L3 absent → everything collapses into L1. |
| (none) | — | Organic bettor → whole cut is undistributed, house keeps it. |
distributed is what actually went out, undistributed is what came back to you (organic bettor or a chain shorter than your top level — add it back to house rake), and chainDepth: 0 means the player is fully organic. Every credit is recorded per app+environment with its level, sourceRef, and meta, and shows on the influencer dashboards.Or orchestrate levels yourself
If you want full control over which levels earn and from what pot, walk the chain with chain and record each credit with payout. This is exactly what distribute does under the hood — reach for it only when you need bespoke per-level logic.
// If you'd rather orchestrate levels yourself, walk the chain and record each
// credit with payout(). distribute() does this for you with the exact split.
const chain = await wallet.referrals.chain({ userId: bettorWalletUserId, levels: 3 });
// [{ level: 1, userId, code, isInfluencer }, { level: 2, … }]
for (const link of chain) {
await wallet.referrals.payout({
beneficiaryUserId: link.userId,
amount: cutFor(link.level, rake), // bigint base units — your rates, your math
currency: 3,
level: link.level,
sourceRef: `pool:${poolId}`,
idemKey: `refpay:${poolId}:${link.userId}`,
});
}Balance & withdrawal
Credits accrue as an off-chain balance per currency. Withdrawal moves the money on-chain to the user’s own wallet through the treasury rail — debit-first and idempotent, so it can never overdraft or double-pay. Influencers can also withdraw from their dashboard on this site.
// Earnings accrue as an off-chain balance: Σ credits − Σ debits, per currency.
const balance = await wallet.referrals.balance({ userId, currency: 3 }); // bigint
// Withdraw moves it on-chain to the user's own wallet via the treasury rail —
// debit-first, can never overdraft or double-pay.
const { railRef } = await wallet.referrals.withdraw({
userId,
currency: 3,
amount: 25_000_000n,
idemKey: crypto.randomUUID(), // retry the SAME key on ambiguity; new intent = new key
});Where to go next
- Referral capture (client) — grabbing
?ref=codes and attaching them to login. - Choose a money model — where the rake you split from actually comes from.