Skip to content
On this page

Burn TIP-3 Tokens

Now that we have gained knowledge on deploying custom contracts and minting/transferring tokens using them, we can proceed to explore the burning of TIP-3 tokens through the multi wallet TIP-3 contract. As discussed earlier in the Burn Tokens Using an Account section, the TIP-3 standard offers two implementations for the burn functionality. It is important to note that the multi wallet contract only supports transactions utilizing the burn function, as the burnByRoot function can only be called on the token root contract. In order to understand how the multi wallet contract updates its state, we will cover both implementations.

Step 1: Write Burn Script

The code sample below utilizes both burn functions with the help of the locklift but checks the balance on the multi wallet TIP-3 contract.

We use the previously written script stats from the transfer tip3 tokens section for the following script.

INFO

Before we start to write our scripts we need to make sure that there is a file named 07-burn-tip3.ts in the script folder in the project root.

The code sample below follows the same approach but makes the transactions using everscale-inpage-provider:


typescript
/*
  Burning tip-3 tokens using burn method on the Multi Wallet TIP-3 contract
  */

// Preparing the burning amount
const burnAmount: number =
  10 * 10 ** deployRootFromDeployerParams.decimals;

// Fetching the old balance of the token wallet using multi wallet tpi-3 contract
let oldBal: number = (
  await getWalletData(
    aliceMultiWalletContract,
    tokenRootContract.address
  )
).balance;

// Burning tip-3 tokens from Bob's wallet(bob's Multi Wallet contract)
const { transaction: burnRes } =
  await aliceMultiWalletContract.methods
    .burn({
      _amount: burnAmount,
      _tokenRoot: tokenRootContract.address,
    })
    .sendExternal({ publicKey: signerAlice.publicKey });

// Confirming tokens are burnt
let newBal: number = (
  await getWalletData(
    aliceMultiWalletContract,
    tokenRootContract.address
  )
).balance;

if (newBal < oldBal) {
  console.log(
    `${burnAmount / 10 ** deployRootFromDeployerParams.decimals} ${
      deployRootFromDeployerParams.symbol
    }'s burnt successfully \n
    Alice balance before burn: ${
      oldBal / 10 ** deployRootFromDeployerParams.decimals
    } \n
    Alice balance after burn: ${
      newBal / 10 ** deployRootFromDeployerParams.decimals
    }`
  );
} else {
  console.log(
    `Burning token failed ${(burnRes.exitCode, burnRes.resultCode)}`
  );
}

/*
    Burning tip-3 tokens using burnByRoot function on the token root contract
  */

// Defining the burn amount to be used in the burnByRoot function
const burnByRootAmount: number =
  5 * 10 ** deployRootFromDeployerParams.decimals;

// Defining the balance of the token wallet before burning
oldBal = (
  await getWalletData(
    aliceMultiWalletContract,
    tokenRootContract.address
  )
).balance;

// Burning tip-3 tokens using burnByRoot
await tokenRootContract.methods
  .burnTokens({
    amount: burnByRootAmount,
    remainingGasTo: aliceAccount.address,
    walletOwner: aliceMultiWalletContract.address,
    callbackTo: aliceMultiWalletContract.address, // important to update the MW state
    payload: '',
  })
  .send({
    from: aliceAccount.address,
    amount: locklift.utils.toNano('2'),
  });

// Fetching the new balance after the burn is done
newBal = (
  await getWalletData(
    aliceMultiWalletContract,
    tokenRootContract.address
  )
).balance;

// Checking if the token are burnt successfully
if (newBal < oldBal) {
  console.log(
    `${
      burnByRootAmount / 10 ** deployRootFromDeployerParams.decimals
    } ${
      deployRootFromDeployerParams.symbol
    }'s burnt by root successfully \n
    Alice balance before burnByRoot: ${
      oldBal / 10 ** deployRootFromDeployerParams.decimals
    } \n
    Alice balance after burnByRoot: ${
      newBal / 10 ** deployRootFromDeployerParams.decimals
    }`
  );
} else {
  console.log(
    `Burning token failed ${(burnRes.exitCode, burnRes.resultCode)}`
  );
}
typescript
import {
  ProviderRpcClient,
  Address,
  Transaction,
  Contract,
  FullContractState,
} from 'everscale-inpage-provider';
import * as tip3Artifacts from 'tip3-docs-artifacts';
import { provider, providerAddress } from './useProvider';

/**
 * We develop two more methods in order to reduce the mass of the script
 */

// This function is utilized to extract the public key of the sender wallet
async function extractPubkey(
  provider: ProviderRpcClient,
  providerAddress: Address
): Promise<string> {
  // Fetching the user public key
  const accountFullState: FullContractState = (
    await provider.getFullContractState({ address: providerAddress })
  ).state!;

  const senderPublicKey: string = await provider.extractPublicKey(
    accountFullState.boc
  );

  return senderPublicKey;
}
interface walletData {
  tokenWallet: Address;
  balance: number;
}

// THis function is utilized to fetch token wallet data from the multi wallet tip-3 contract
async function getWalletData(
  MWContract: Contract<
    tip3Artifacts.FactorySource['MultiWalletTIP3']
  >,
  tokenRootAddress: Address
): Promise<walletData> {
  // returned value of the "wallets" mapping from multi wallet tip-3
  const walletData = (
    await MWContract.methods.wallets().call()
  ).wallets.map(item => {
    if (item[0].toString() == tokenRootAddress.toString()) {
      return item[1];
    }
  });
  let balance: number = 0;
  let tokenWallet: Address = tip3Artifacts.zeroAddress;
  if (walletData.length != 0) {
    balance = Number(walletData[0]!.balance);
    tokenWallet = walletData[0]!.tokenWallet;
  }
  return { tokenWallet: tokenWallet, balance: balance };
}

async function main() {
  try {
    // Preparing the required contracts addresses
    const tokenRootAddress: Address = new Address(
      '<YOUR_TOKEN_ROOT_ADDRESS>'
    );
    const multiWalletAddress: Address = new Address(
      '<YOUR_MULTI_WALLET_ADDRESS>'
    );

    // creating an instance of the required contracts
    const tokenRootContract: Contract<
      tip3Artifacts.FactorySource['TokenRoot']
    > = new provider.Contract(
      tip3Artifacts.factorySource['TokenRoot'],
      tokenRootAddress
    );

    const multiWalletContract: Contract<
      tip3Artifacts.FactorySource['MultiWalletTIP3']
    > = new provider.Contract(
      tip3Artifacts.factorySource['MultiWalletTIP3'],
      multiWalletAddress
    );

    // Fetching the decimals and symbol
    const [decimals, symbol] = await Promise.all([
      Number(
        (
          await tokenRootContract.methods
            .decimals({ answerId: 0 })
            .call()
        ).value0
      ),
      (await tokenRootContract.methods.symbol({ answerId: 0 }).call())
        .value0,
    ]);

    // Defining the burn amount
    const burnAmount: number = 10 * 10 ** decimals;

    // Fetching the balance before burning tokens
    let oldBal: number = (
      await getWalletData(
        multiWalletContract,
        tokenRootContract.address
      )
    ).balance;

    // Get the senders public key
    const senderPubkey: string = await extractPubkey(
      provider,
      providerAddress
    );
    if (
      senderPubkey !=
      (await multiWalletContract.methods.owner({}).call()).owner
    ) {
      throw new Error(
        'You are not the owner of the sender multi wallet contract !'
      );
    }

    // checking if the user has enough tokens to burn
    if (oldBal < burnAmount) {
      throw new Error('Low balance !');
    }

    // burning tokens from a token wallet by calling the burn method
    const { transaction: burnRes } = await multiWalletContract.methods
      .burn({
        _amount: burnAmount,
        _tokenRoot: tokenRootContract.address,
      })
      .sendExternal({
        publicKey: await extractPubkey(provider, providerAddress),
      });

    // Throwing an Error if the transaction was aborted
    if (burnRes.aborted) {
      throw new Error(`Transaction aborted ! ${burnRes.exitCode}`);
    }

    // Getting the balance after burning the tokens
    let newBal = (
      await getWalletData(
        multiWalletContract,
        tokenRootContract.address
      )
    ).balance;

    // Checking if the tokens are successfully burnt
    if (newBal < oldBal) {
      console.log(
        `${
          burnAmount / 10 ** decimals
        } ${symbol}'s successfully burnt !`
      );

      return `Hash: ${burnRes.id.hash} \n
      Balance before burn:  ${oldBal / 10 ** decimals} \n
      Balance after burn: ${newBal / 10 ** decimals}`;
    } else {
      console.error(
        `Burning tokens failed !  ${
          (burnRes.exitCode, burnRes.resultCode)
        }`
      );
    }

    /*
      Using burnByRoot function
    */

    // Fetching the balance before utilizing the burnByRoot function
    oldBal = (
      await getWalletData(
        multiWalletContract,
        tokenRootContract.address
      )
    ).balance;

    // Defining the burn amount to be used when calling the burnByRoot function
    const burnByRootAmount: number = 5 * 10 ** decimals;

    // Checking iof the user has enough tokens to burn
    if (oldBal < burnByRootAmount) {
      throw new Error('Low balance !');
    }

    // burning tokens from a token wallet by calling the burn method
    const { transaction: burnByRootRes } =
      await tokenRootContract.methods
        .burnTokens({
          amount: burnByRootAmount,
          walletOwner: multiWalletContract.address,
          remainingGasTo: multiWalletContract.address,
          callbackTo: multiWalletContract.address,
          payload: '',
        })
        .sendExternal({
          publicKey: await extractPubkey(provider, providerAddress),
        });

    // Throwing an error if the transaction was aborted
    if (burnByRootRes.aborted) {
      throw new Error(
        `Transaction aborted ! ${burnByRootRes.exitCode}`
      );
    }

    // Getting the balance after burning tokens using token root function
    newBal = (
      await getWalletData(
        multiWalletContract,
        tokenRootContract.address
      )
    ).balance;

    // Checking if the tokens are burnt successfully
    if (newBal < oldBal) {
      console.log(
        `${
          burnByRootAmount / 10 ** decimals
        } ${symbol}'s successfully burnt By Root!`
      );

      return `Hash: ${burnByRootRes.id.hash} \n
      Balance before burnByRoot:  ${oldBal / 10 ** decimals} \n
      Balance after burnByRoot:  ${newBal / 10 ** decimals}`;
    } else {
      throw new Error(`Burning tokens failed !
      ${(burnByRootRes.exitCode, burnByRootRes.resultCode)}`);
    }
  } catch (e: any) {
    throw new Error(`Failed ${e.message}`);
  }
}

Step 2: Burn TIP-3 Tokens

Use this command to burn TIP-3 tokens:

shell
npx locklift run -s ./scripts/07-burn-tip3.ts -n local
buildStructure

Congratulations, you have successfully burned TIP-3 tokens using a custom contract 🎉

burn TIP-3 tokens

Token Root address

Multi wallet address

Amount

GIF

burn TIP-3 tokens By Root

Token Root address

Multi wallet address

Amount

GIF