Skip to content

Instantly share code, notes, and snippets.

@barinbritva
Last active February 7, 2024 08:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save barinbritva/b3db1605f2667b7562b53a23877c0e73 to your computer and use it in GitHub Desktop.
Save barinbritva/b3db1605f2667b7562b53a23877c0e73 to your computer and use it in GitHub Desktop.
flappy bird article

TON blockchain for games

What’s in the tutorial

In this tutorial we will consider how to add TON blockchain to a game. For our example we will use a Flappy Bird clone written in Phaser and will add GameFi features step by step. In the tutorial we will use short code pieces and pseudocode to make it more readable. Also, we will provide links to real code blocks to help you understand better. The whole implementation can be found in the demo repo.

Flappy Bird game without GameFi features

We are going to implement the following:

  • Achievements. Let’s reward our users with SBTs. Achievement system is a great tool to increase user engagement.
  • Game currency. In TON blockchain it’s easy to launch your own token (jetton). The token can be used to create an in-game economy. Our users will be able to earn the game coins to spend them later.
  • Game shop. We will provide users with a possibility to purchase in-game items using either in-game currency or TON coin itself.

Preparations

Install GameFi SDK

First, we will set up the game environment. To do that we need to install gamefi-sdk. The package is designed to prepare everything developers need to integrate the blockchain into games. The lib can be used either from CLI or from Node.js scripts. In this tutorial we stick with the CLI approach.

// install globally
npm install -g @ton-community/gamefi-sdk

Create a master wallet

Next, we need to create a master wallet. Master wallet is a wallet we will use to mint the jetton, collections, NFTs, SBTs and receive payments.

gamefi-sdk setup-env

You will be asked a few questions:

Field Hint
Network Select testnet as far it’s a test game.
Type Select highload-v2type of wallet as far it’s the best, performant option to use as a master wallet.
Storage The storage will be used to store NFT/SBT files. Amazon S3 (centralized) or Pinata (decentralized). For the tutorial let’s use Pinata as far as decentralized storage will be more illustrative for the Web3 game.

The script outputs the link you can open to see the created wallet state.

New wallet in Nonexist status

As you can see the wallet is not actually created yet. To the wallet be really created we need to deposit some funds into it. In the real world scenario, you can deposit the wallet any way you prefer using the wallet address. In our case we will use Testgiver TON Bot. Please open it to claim 5 test TON coins.

A bit later you could see 5 TON on the wallet and its status became Uninit. The wallet is ready . After the first usage it changes status to Active.

Wallet status after top up

Mint in-game currency

We are going to create in-game currency to reward users with it:

gamefi-sdk deploy-jetton

You will be asked a few questions:

Field Hint
Name Token name, for example Flappy Jetton.
Description Token description, for instance: A vibrant digital token from the Flappy Bird universe.
Image Download prepared jetton logo and specify file path. Of course, you can use any image.
Symbol FLAP or enter any abbreviation you want to use.
Decimals How many zeros after the dot your currency will have. Let’ it be 0 in our case.

The script outputs the link you can open to see the created jetton state. It will have Active status. The wallet status will change the status from Uninit to Active.

In-game currency / jetton

Create collections for SBTs

Just for example, in the demo game we will reward users for the first and the fifth games. So, we will mint two collections to put SBTs into them when users achieve related conditions – play first and fifth time:

gamefi-sdk deploy-nft-collection
Field First game Fifth game
Name Flappy First Flight Flappy High Fiver
Description Commemorating your inaugural journey in the Flappy Bird game! Celebrate your persistent play with the Flappy High Fiver NFT!
Image You can download the image here You can download the image here
Type sbt sbt

We are fully prepared. So, let’s go to the logic implementation.

Connecting wallet

Everything starts from a user connects its wallet. So, let’s add wallet connect integration. To work with the blockchain from the client side we need to install GameFi SDK for Phaser:

npm install –save @ton/phaser-sdk

Now let’s setup GameFi SDK and create an instance of it:

import { GameFi } from '@ton/phaser-sdk'

const gameFi = await GameFi.create({
    network: 'testnet'
    connector: {
        // if tonconnect-manifest.json is placed in the root you can skip this option
        manifestUrl: '/assets/tonconnect-manifest.json',
        actionsConfiguration: {
            // address of your Telegram Mini App to return to after the wallet is connected
            // url you provided to BothFather during the app creation process
            // to read more please read https://github.com/ton-community/flappy-bird#telegram-bot--telegram-web-app
            twaReturnUrl: URL_YOU_ASSIGNED_TO_YOUR_APP
        },
        contentResolver: {
            // some NFT marketplaces don't support CORS, so we need to use a proxy
            // you are able to use any format of the URL, %URL% will be replaced with the actual URL
            urlProxy: `${YOUR_BACKEND_URL}/${PROXY_URL}?url=%URL%`
        },
        // where in-game purchases come to
        merchant: {
            // in-game jetton purchases (FLAP)
            // use address you got running `gamefi-sdk deploy-jetton`
            jettonAddress: FLAP_ADDRESS,
            // in-game TON purchases
            // use master wallet address you got running `gamefi-sdk setup-env`
            tonAddress: config.TOKEN_RECIPIENT
        }
    },

})

To learn more about initialization options please read the library documentation.

To learn what tonconnect-manifest.json is please check ton-connect manifest description.

Now we are ready to create a wallet connect button. Let’s create an UI scene in Phaser which will contain the connect button:

class UiScene extends Phaser.Scene {
    // receive gameFi instance via constructor
    private gameFi: GameFi;

    create() {
        this.button = this.gameFi.createConnectButton({
            scene: this,
            // you can calculate the position for the button in your UI scene
            x: 0,
            y: 0,
            button: {
                onError: (error) => {
                    console.error(error)
                }
                // other options, read the docs
            }
        })
    }
}

Read how to create connect button and the UI scene.

To watch when a user connects or disconnects its wallet let’s use the following piece of code:

function onWalletChange(wallet: Wallet | null) {
    if (wallet) {
        // wallet is ready to use
    } else {
        // wallet is disconnected
    }
}
const unsubscribe = gameFi.onWalletChange(onWalletChange)

To learn about more complex scenarios please check out the full implementation of wallet connect flow.

Read how game UI managing might be implemented.

Now we have user wallet connected and we can move forward.

Connect wallet button Confirm wallet connection Wallet is connected

Implementing achievements & rewards

To implement achievements and reward system we need to prepare an endpoint which will be requested per user try.

/played endpoint

We need to create an endpoint /played which must do the following:

  • receive a body with user wallet address and Telegram initial data passed to Mini app during app launch. The initial data needs to be parsed to extract authentication data and ensure a user sends the request only on its behalf.
  • the endpoint must calculate and store the number of games a user played.
  • the endpoint must check if it’s the first or fifth game for a user and if so, reward a user with related SBT.
  • the endpoint must reward a user with 1 FLAP for each game.

Read /played endpoint code.

Request /played endpoint

Every time the bird hits a pipe or falling down the client code must call /played endpoint passing the correct body:

async function submitPlayed(endpoint: string, walletAddress: string) {
    return await (await fetch(endpoint + '/played', {
        body: JSON.stringify({
            tg_data: (window as any).Telegram.WebApp.initData,
            wallet: walletAddress
        }),
        headers: {
            'content-type': 'application/json'
        },
        method: 'POST'
    })).json()
}

const playedInfo = await submitPlayed('http://localhost:3001', wallet.account.address);

Read submitPlayer function code.

Let’s play the first time and ensure we will be rewarded with a FLAP token and SBT. Click the Play button, fly through a pipe or two and then hit into a tube. Alright, everything works!

Rewarded with token and SBT

Play 4 more times to get the second SBT, then open your Wallet, TON Space. Here your collectibles are:

Achievements as SBT in Wallet

Implementing game shop

To have an in-game shop we need to have two components. The first is an endpoint which provides info about users purchases. The second is a global loop to watch user transactions and assign game properties to its owners.

/purchases endpoint

The endpoint does the following:

  • receive auth get param with Telegram Mini Apps initial data.
  • the endpoint gets items user purchased and respond with the items list.

Read /purchases endpoint code.

Purchases loop

To know when users make payments, we need to watch the master wallet transactions. Every transaction must contain message userId:itemId. We will remember the last processed transaction, get only new ones, assign users properties they bought using userId and itemId, rewrite the last transaction hash. This will work in infinite loop.

Read purchase loop code.

Client side for the shop

On the client side we have the Shop button.

Enter shop button

Opening the Shop will trigger purchased items loading and updating it every 10 seconds:

// inside of fetchPurchases function
await fetch('http://localhost:3000/purchases?auth=' + encodeURIComponent((window as any).Telegram.WebApp.initData))
// watch for purchases
setTimeout(() => { fetchPurchases() }, 10000)

Read showShop function code.

Now we need to implement the purchase itself. To do that, we will create GameFi SDK instance first and then use pay method:

gameFi.buy({
    // pay with jetton set up in the gameFi instance as `merchant.jettonAddress`
    jetton: true,
    amount: BigInt(price),
    // message which will be used in the Purchase Loop to assign the property to the user
    forwardMessage: (window as any).Telegram.WebApp.initDataUnsafe.user.id + ':' + itemId,
    // fee prepay to deliver the `forwardMessage` - extra amount will be refunded
    forwardFee: BigInt(1),
})

Game prop to purchase Purchase confirmation Property is ready to use

It's also possible to pay with TON coin:

import { Convertor } from '@ton/phaser-sdk'

gameFi.buy({
    amount: Convertor.toNano(0.5),
    comment: (window as any).Telegram.WebApp.initDataUnsafe.user.id + ':' + itemId
})

Afterword

That’s it for this tutorial! We considered the basic GameFi features, but the SDK delivers more functionality like transfers between players, utils to work NFTs and collections, etc. We will deliver even more features in the future.

To learn about all the GameFi features you can use read the docs of ton-org/game-engines-sdk and @ton-community/gamefi-sdk.

So, let us know what you think in the Discussions!

The complete implementation is available in flappy-bird repo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment