Skip to main content

Integrate into your game

Here we share all the steps to integrate the wallet delegation system into a game

For this example, we will be delegating a locally generated keypair (using the thirdweb wrapper for ethers) to a user's EVM wallet (ex: Rabby)

In your middleware

  1. First implement a Local Wallet

  2. Create a function to sign messages with your local wallet.

import { getActiveAddress, WalletConnectHelper } from '@paima/sdk/mw-core';

const signMessageWithLocalWallet = async (message: string) => {
// wraps the message with the correct Paima Concise notation
const toSign = new WalletConnectHelper().buildMessageToSign(message);

return {
message: toSign,
// assumes you've called `userWalletLogin` on the local Ethers wallet from step (1)
walletAddress: getActiveAddress(WalletMode.EvmEthers).toLocaleLowerCase(),
/* localWallet is an instance of LocalWallet from thirdweb from step (1) */
signedMessage: await localWallet.signMessage(message),
};
};
  1. Now create a function to get User Wallets
import { allInjectedWallets } from '@paima/sdk/providers';

const getWallets = async () => {
/* "wallets" is a map of connected wallets */
const wallets = await allInjectedWallets({
gameName: 'GameName', // replace with your game's name
gameChainId: undefined,
});
return wallets;
}
  1. Create a final function to sign messages with an external wallet.
import { AddressType } from '@paima/sdk/utils';
import { WalletConnectHelper } from '@paima/sdk/mw-core';

const connectAndSignExternalWallet = async (
walletType: AddressType,
walletName: string, /* Provided in wallets[WalletMode].metadata.name */
otherAddress: string
) => {
return await new WalletConnectHelper().connectExternalWalletAndSign(walletType, walletName, message);
};
  1. Put it all together.
import { walletConnect } from '@paima/sdk/mw-core';
import { WalletMode } from '@paima/sdk/providers';

const localWalletMessage = await signMessageWithLocalWallet();
const wallets = getWallets();

// This is to use only the first EVM wallet.
// You should check the contents of "wallets" and display the necessary options.
const injectedWalletMessage = await connectAndSignExternalWallet(
AddressType.EVM,
wallets[WalletMode.EvmInjected].metadata.name,
localWalletMessage.walletAddress
);

// Send message to blockchain.
await walletConnect(
injectedWalletMessage.walletAddress,
null, // localWalletMessage.walletAddress,
injectedWalletMessage.signedMessage,
localWalletMessage.signedMessage
);

In your backend

  1. In the default API index: /backend/api/index.ts Add the following code:
// Clear wallet-connection cache.
clearDelegateWalletCacheOnChanges(requirePersistentConnection());

This allows to clear the API cache when the user changes the wallet connection.

  1. STF Changes

As user wallet might change over time, as they can delegate, migrate and cancel delegations, we need to use userId instead of userAddress or realAddress for user identification in our STF.

// main entry point for your game's state machine
export default async function (
inputData: STFSubmittedData,
blockHeader: PreExecutionBlockHeader,
randomnessGenerator: Prando,
dbConn: Pool
): Promise<{ stateTransitions: SQLUpdate[]; events: Events }>
/* use this user to identify the player instead of userAddress or realAddress */
const user = String(inputData.userId);
...
}
  • userAddress: contains the main wallet address.
  • realAddress: contains the real wallet address that sent the transaction.
  • userId: contains the user id, which is the same for all wallets of the same user.
  1. API Controllers In your API, if receiving user wallet address, convert them into the USER ID.
import { getMainAddress } from '@paima/node-sdk/db';

// example query for a game that gets items for a user
@Route('items')
export class ItemController extends Controller {
@Get('/user')
public async getItemsForUser(
@Query() wallet: string
): Promise<{ items: IGetAllItemsForUserResult[] }> {
const pool = requirePool();

// this is the main line (everything else is just an example context)
const address = await getMainAddress(wallet, pool);

/* now use address (instead of wallet) */
const items = await getAllItemsForUser.run(
{ wallet: address },
pool
);
return { items };
}
}