Skip to content
On this page

4. New game โ€‹

A game is a means to change the state of a channel. Each games has a current state. This state evolves according to the rules defined by its model. A game is attached to a channel.

Structures of a game โ€‹

Outcomes โ€‹

The outcome of a game. Partially controlled by the apply_ lambda of the model and partially controlled by the gamePlatform.

t_outcome_content = sp.TVariant(
t_outcome = sp.TOption(sp.TVariant(t_outcome_content

If outcome is none or the outcome is pending, the game is still running. Otherwise, (i.e. the outcome is variant "final") the game ended with one of the following results:

game_finishedThe game ended properly(string) Set by the model apply_ lambda or by calling game_set_outcome.
game_abortedThe game was aborted(unit) Set by calling game_set_outcome.
player_inactiveOne player timed out and the other called starved.(nat) Id of the player who timed out
player_double_playedOne player double played and the other called double_signed.(nat) Id of the player who double-played

Constants โ€‹

The constants structure contains everything that was agreed by the players at the beginning of the game.

t_constants = sp.TRecord(
    model_id     = sp.TBytes,
    channel_id   = sp.TBytes,
    # Incremental player id starting at 1 associated to address
    players_addr = sp.TMap(sp.TInt, sp.TAddress),
    # A nonce that players gave to this game.
    game_nonce   = sp.TString,
    # Number of seconds a player has to play on-chain
    # after the other player called starving.
    play_delay   = sp.TNat,
    settlements  = t_settlements,
    bonds        = sp.TMap(sp.TInt, sp.TMap(sp.TInt, sp.TInt)),
constants = sp.record(
    model_id     = model_id,
    channel_id   = channel_id,
    players_addr = {1:player1.address, 2:player2.address},
    game_nonce   = "game1",
    play_delay   = 3600 * 24,
    settlements  = settlements,
    bonds        = {1:{0:20}, 2:{0:20}}

See channel id calculation, model id calculation and settlements.

Transfers โ€‹

A transfer describes a combination of tokens that is sent from a sender to a receiver.

You can use the player id 0 to set the platform as a sender.

In this case the game or the model must have special permissions from the platform admin to perform the transfer. :::

t_transfer = sp.TRecord(
    # Player ID of the sender. Use 0 to send from the platform.
    sender     = sp.TInt,
    # Player ID of the receiver.
    receiver   = sp.TInt,
    # Bonds (token id: amount) that will be transferred.
    bonds = sp.TMap(sp.TNat, sp.TNat),
transfer = sp.record(
    sender = 1,
    receiver = 2,
    bonds ={0: 10})

Settlements โ€‹

t_settlements = sp.TMap(t_outcome, sp.TList(t_transfer))
settlements ={
    # (Outcome, Outcome Value)                 : # List of transfers
    sp.variant("game_finished", "player_1_won"): [sp.record(sender = 2, receiver = 1, bonds = {0: 10})],
    sp.variant("game_finished", "player_2_won"): [sp.record(sender = 1, receiver = 2, bonds = {0: 10})],
    sp.variant("game_finished", "draw")        : [],
    sp.variant("player_inactive",            1): [sp.record(sender = 1, receiver = 2, bonds = {0: 15})],
    sp.variant("player_inactive",            2): [sp.record(sender = 2, receiver = 1, bonds = {0: 15})],
    sp.variant("player_double_played",       1): [sp.record(sender = 1, receiver = 2, bonds = {0: 20})],
    sp.variant("player_double_played",       2): [sp.record(sender = 2, receiver = 1, bonds = {0: 20})],
    sp.variant("game_aborted",         sp.unit): [],

# In this example "player_1_won", "player_2_won" and "draw" are managed by the model: tictactoe

Settlements associates an outcome to a list of transfers.

Each outcome defined by the model and each standard outcome (player_inactive, player_double_play, game_aborted) MUST appear in the settlement.

Keep player_double_played > player_inactive >

game_finished. :::

Current โ€‹

The current structure is managed by the game platform.

t_current = sp.TRecord(
    move_nb=sp.TNat,  # Incremental id of the move.
    player=sp.TInt,  # Id of the current player

See outcome.

Game โ€‹

The game structure is managed by the game platform.

t_game = sp.TRecord(
    addr_players=sp.TMap(sp.TAddress, sp.TInt),
    timeouts=sp.TMap(sp.TInt, sp.TTimestamp),

game_bonds โ€‹

game_bonds defines what each player agreed to post as bond until the game is settled.
The index corresponds to the player id (1 for player 1...). The value is a map of tokens and amount.

For each player, the bonds corresponds to **the maximum

that a player could be asked to pay** by a settlement for each token. :::



settlements ={
    # (Outcome, Outcome Value)                 : # List of transfers
    sp.variant("game_finished", "player_1_won"): [sp.record(sender = 1, receiver = 2, bonds = {0: 10})],
    sp.variant("player_inactive",            1): [sp.record(sender = 1, receiver = 2, bonds = {0: 15}),
                                                  sp.record(sender = 1, receiver = 2, bonds = {0:  5})],
    sp.variant("player_double_played",       1): [sp.record(sender = 1, receiver = 2, bonds = {1: 42})],

bonds = {
    1: {
        0: 20, # For the outcome ("player_inactive", 1)
        1: 42  # For the outcome ("player_double_played", 1)
    2: {}
t_bonds = sp.TMap(sp.TInt, sp.TMap(sp.TInt, sp.TInt))
bonds = {1:{0:20}, 2:{0:20}}

Start a new game โ€‹

Each player is responsible for verifying the other

players' bonds before signing a game (see game bonds). If this step is neglected, a player may not receive their settlements.

If the other player has a running withdraw_request: make sure he has sufficient channel bonds to cover the withdraw + all the non-settled game bonds.
Otherwise, ask him to push new bonds or cancel the withdraw_request. :::

All players need to sign a pack of a pair of "New Game", <constants>, <initParams>.

<constants> game constants agreed by the players.
<initParams> params for the init lambda of the model.

The pair is of type string $constants bytes.

In SmartPy this corresponds to a call to the action_new_game method of

def action_new_game(constants, params):
    constants = sp.set_type_expr(constants, types.t_constants)
    params    = sp.set_type_expr(params, sp.TBytes)
    return sp.pack(("New Game", constants, params))

Compute the game structure off-chain โ€‹

More info about offchain views and signatures can be found in

offchain views and signatures

def offchain_new_game(self, params):
            constants=t_constants,  # Constants of the game
            params=sp.TBytes,  # Init params for the `init` lambda of the model.
            signatures=sp.TMap(sp.TKey, sp.Signature),  # Signatures.
import smartpy as sp

class TestView(sp.Contract):
    def __init__(self, c, f):
        self.c = c
        self.f = f.f
        self.init(result = sp.none)

    def compute(self, data, params): = data
        b = sp.bind_block()
        with b:
            self.f(self.c, params) = sp.some(b.value)

def make_signatures(p1, p2, x):
    # This function is illustrative.
    # In real life examples, other player's signature are received from them
    # You shouldn't know their secret_key'
    sig1 = sp.make_signature(p1.secret_key, x)
    sig2 = sp.make_signature(p2.secret_key, x)
    return{p1.public_key: sig1, p2.public_key: sig2})

@sp.add_test(name="New Game")
def test():
    sc = sp.test_scenario()

    # ... (scenario + platform origination + constants creation)

    def new_game_sigs(constants, params = sp.unit):
        new_game = gp.action_new_game(constants, sp.pack(params))
        return make_signatures(player1, player2, new_game)

    offchain_new_game = TestView(platform, platform.offchain_new_game)
    sc += offchain_new_game

        data   =,
        params = sp.record(
            constants  = constants,
            params     = sp.pack(sp.unit),
            signatures = new_game_signatures
    )).run(sender = player1)
    game = sc.compute(

See constants, model class.

Push a new game on the platform โ€‹

Pushing a new game on-chain is not necessary if there is no

conflict between players :::

import smartpy as sp

# ... Build constants, gather other's signature and sign the new_game structure

    constants  = constants,
    params     = sp.pack(sp.unit),
    signatures = new_game_signatures
).run(sender = player2.address)

Compute the game_id โ€‹

The game id is the BLAKE2B hash of the sp.pair(channel_id, sp.pair(model_id, game_nonce)).

import smartpy as sp
gp ="state_channel_games/")

game_id = gp.compute_game_id(constants)

The constants contains the id of the channel which depends

on the platform address. :::