Skip to main content

Friend challenges

Host ≥ 1.2.0. Players can challenge each other in your game — high score, fastest time, or win streak — wagering Gamma Coins in escrow. The winner takes the pot; ties and expiries refund. When your game is launched in challenge mode the Gamma Games app injects the context; your job is to read it, report progress, and submit one final result.

gamma.challenges lives under the main gamma object and is promise-first like everything else:

import {gamma} from '@swiftware/gamma-sdk';
// drop-in: global `gamma`

Branch once at startup

Call getActive() after ready(). It returns null for a normal play session — and on hosts older than 1.2.0 — so the same build works everywhere.

await gamma.ready();
const challenge = await gamma.challenges.getActive();

if (challenge) {
// {
// challengeId, type: 'high_score' | 'fastest_time' | 'level_streak',
// mode: 'live' | 'async', param, wagerCoins, deadline,
// myRole: 'challenger' | 'opponent',
// opponent: { displayName, avatar } | null
// }
startCompetitiveRun(challenge);
} else {
startNormalGame();
}

Report progress while playing

submitScore(value) reports the current progressive value — score, elapsed milliseconds, or streak, matching the challenge type. It powers the live opponent overlay and, for higher-wins types, the disconnect fallback. Call it as often as you like; the host throttles.

function onScoreChanged(currentScore) {
gamma.challenges.submitScore(currentScore); // fire-and-forget
}

Submit the final result

Call complete(result) exactly once, at game over. Set the key that matches challenge.type:

Challenge typeResult keyExample
high_score{ score }{ score: 1240 }
fastest_time{ timeMs }{ timeMs: 53120 }
level_streak{ streak }{ streak: 9 }
const res = await gamma.challenges.complete({score: finalScore});
// res.outcome: 'won' | 'lost' | 'tie' | 'pending_opponent'
// res.settled: true once both players have finished
// res.potCoins: coins won (winner only)
if (res.ok && res.settled) {
showOutcome(res.outcome, res.potCoins);
}

Rules that matter

  • getActive() returns null for a normal session and on hosts < 1.2.0 — always handle the non-challenge path so one build runs everywhere.
  • complete() is accepted once per attempt; later calls return already_completed. Results are validated and settled server-side — never grant a payout client-side.
  • In live mode, opponents race in real time: don't pause the run on your own ads or interstitials.
  • wagerCoins is held in escrow by the host. Your game doesn't move coins for the wager — just play and report. (Use spendCoins/earnCoins only for your normal in-game economy.)

Test outside the app

Seed the dev mock to exercise the flow in a plain browser:

gamma.configure({mock: {challenge: {type: 'high_score', wagerCoins: 100}}});
await gamma.ready();
const challenge = await gamma.challenges.getActive(); // mocked context

The mock pretends the opponent already finished and you won, so complete() resolves with {settled: true, outcome: 'won'}. Don't ship mock enabled.

See also the full API reference, error codes, and App-only & offline.

Ask:View .md