In our example, we simply queried maxBet from the dice contract. But in reality, the dice can be played by several players at the same time, and this value can change often. Therefore, after getting the value, we need to subscribe transactions on the account and update this value:
export const TokenDiceContractAddress = new Address("0:19128985f2d034a0a7b8dad5b23946aff3e63fe68c13243feb58124cef9acbb6"); const tokenDiceContract = new provider.Contract(TokenDiceAbi, TokenDiceContractAddress); const {value0: maxBet} = await tokenDiceContract.methods.maxBet().call(); setMaxBet(maxBet); // some logic const subscriber = new provider.Subscriber(); subscriber.states(tokenDiceContract.address).on(async (update) => { // Account state was changed! // Fetch max bet again const {value0: maxBet} = await tokenDiceContract.methods.maxBet().call(); setMaxBet(maxBet); // some logic // you need to add some concurency controll there, in case tokenDiceContract have a lot // transactions in parallel. // You can use update.state.lastTransactionId.lt - monotonic counter of account // transaction time. });
You should do the same with the user's token balance to update it dynamically when the state of the TokenWallet changes.
Every time you make a request to the smart contract, the inpage-provider goes online to query the complete state of the account and executes this request locally by running TVM. However, if you only need to make static queries or don't care about the latest status, you can cache the state once and run the queries very quickly.
For example, we make three queries to the TokenRoot contract to get the user's decimal, symbol, and TokenWallet address. Let's rewrite these queries using the cache:
const tokenRootContract = new provider.Contract(TokenRootAbi, TokenRootContractAddress); const {state: tokenRootFullState} = await tokenRootContract.getFullState(); const { value0: userTokenWalletAddress } = await tokenRootContract.methods.walletOf({answerId: 0, walletOwner: userAddress}).call({cachedState: tokenRootFullState}); const { value0: decimals } = await tokenRootContract.methods.decimals({answerId: 0}).call({cachedState: tokenRootFullState}); const { value0: symbol } = await tokenRootContract.methods.symbol({answerId: 0}).call({cachedState: tokenRootFullState});
When we rolled a die, we waited until the entire chain of calls was complete before displaying the result. But really, the only thing the user cares about is the result of the transaction in which the die was rolled. He does not have to wait until the contract returns him the change in Venom (+ 1 transaction), or transfer tokens and then return the change (+ 2 transactions). So let us just wait until this transaction happens:
// before // let playTx; // const subscriber = new provider.Subscriber(); // await subscriber.trace(firstTx).tap(tx_in_tree => { // if (tx_in_tree.account.equals(TokenDiceContractAddress)) { // playTx = tx_in_tree; // } // }).finished(); // let events = await tokenDiceContract.decodeTransactionEvents({transaction: playTx}); // after const subscriber = new provider.Subscriber(); const playTx = await subscriber.trace(firstTx).filter(tx_in_tree => { return tx_in_tree.account.equals(TokenDiceContractAddress); }).first(); let events = await tokenDiceContract.decodeTransactionEvents({transaction: playTx}); // ...
When we transfer tokens in our application, we always set the deployWalletValue
parameter to 0.1. This means that
TokenWallet creates a message to deploy the destination wallet before creating an outgoing message to transfer tokens.
This is a common practice when you don't know if the recipient's wallet has been deployed, but it creates an
additional transaction.
In our case, we know for sure that both wallets are deployed at the time of the game (the player's wallet and
the dice wallet), so we can set deployWalletValue
to 0 and not have to spend extra money on fees.
// before // await userTokenWalletContract.methods.transfer({ // amount: '1000000000', // recipient: tokenDiceContract.address, // deployWalletValue: '100000000', //0.1 Venom // remainingGasTo: userAddress, // notify: true, // payload: payload // }).send({ // amount: "1500000000", // bounce: true, // from: userAddress // }) // after await userTokenWalletContract.methods.transfer({ amount: '1000000000', recipient: tokenDiceContract.address, deployWalletValue: '0', //already deployed remainingGasTo: userAddress, notify: true, payload: payload }).send({ amount: "1500000000", bounce: true, from: userAddress })