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 gameKey to 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.

TS
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 house
The rates are yours to set
Nothing here is fixed by the wallet. You decide what slice of the rake feeds the referral network (the 40% 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 chainCredited (cut = 1000, bps 6000/3000/1000)Why
L1, L2, L3600, 300, 100Full chain — deepest gets its 100 plus any dust.
L1, L2600, 400L3 absent → its 100 collapses into L2.
L11000L2 and L3 absent → everything collapses into L1.
(none)Organic bettor → whole cut is undistributed, house keeps it.
Read the result
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.

TS
// 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.

TS
// 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