Skip to content
On this page

Deploy Token Root

In this section, we will provide a simple, step-by-step guide on deploying the token root contract.

Step 1: Write Deployment Script

To deploy the token root using the locklift tool, which provides a straightforward approach. The following code sample demonstrates the deployment process:

INFO

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

Deploying a contract using the everscale-inpage-provider can be a bit challenging. To ensure a successful contract deployment using this tool, please follow the steps outlined below

WARNING

The parameter initialSupply must be set to zero if the initialSupplyTo is zero address.


typescript
/**
 * locklift is a globally declared object
 */

import {
  Address,
  zeroAddress,
  Signer,
  WalletTypes,
  Contract,
} from 'locklift';
import { ContractData } from 'locklift/internal/factory';
import { FactorySource, factorySource } from '../build/factorySource';

async function main() {
  // Fetching the signer key pair from locklift.config.ts
  const signerAlice: Signer =
    (await locklift.keystore.getSigner('0'))!;
  const signerBob: Signer = (await locklift.keystore.getSigner('1'))!;

  // uncomment if deploying a new account
  // const { contract: account } = await locklift.factory.deployContract({
  //   contract: "Account",
  //   publicKey: signer.publicKey,
  //   constructorParams: {},
  //   initParams: { _randomNonce: locklift.utils.getRandomNonce() },
  //   value: locklift.utils.toNano(20),
  // });

  // Adding an existing SafeMultiSig Account using its address
  const aliceAccount =
    await locklift.factory.accounts.addExistingAccount({
      type: WalletTypes.MsigAccount,
      address: new Address('<ALICE_ACCOUNT_ADDRESS>'),
      mSigType: 'SafeMultisig',
      publicKey: signerAlice.publicKey,
    });

  // uncomment if deploying a new account
  // const { contract: account } = await locklift.factory.deployContract({
  //   contract: "Account",
  //   publicKey: signer.publicKey,
  //   constructorParams: {},
  //   initParams: { _randomNonce: locklift.utils.getRandomNonce() },
  //   value: locklift.utils.toNano(20),
  // });

  // Adding an existing SafeMultiSig Account using its address
  const bobAccount =
    await locklift.factory.accounts.addExistingAccount({
      type: WalletTypes.MsigAccount,
      address: new Address('<BOB_ACCOUNT_ADDRESS>'),
      mSigType: 'SafeMultisig',
      publicKey: signerBob.publicKey,
    });

  // Preparing test params
  const initialSupplyTo: Address = zeroAddress;
  const rootOwner: Address = aliceAccount.address;
  const name: string = 'Tip3OnboardingToken';
  const symbol: string = 'TOT';
  const decimals: number = 6;
  const disableMint: boolean = false;
  const disableBurnByRoot: boolean = false;
  const pauseBurn: boolean = false;
  const initialSupply: number = 0;

  /*
    Returns compilation artifacts based on the .sol file name
      or name from value config.externalContracts[pathToLib].
  */
  const tokenWallet: ContractData<FactorySource['TokenWallet']> =
    locklift.factory.getContractArtifacts('TokenWallet');

  /**
    * Deploy the TIP-3 Token Root contract.
    * @param deployer_ Its important to set this param to zero address when deploying the token root contract whiteout using an smart contract.
    * @param initialSupplyTo The token wallet that receives the initial supply.
    * @param initialSupply The amount of the tokens to be sent to "initialSupplyTo".
    * @param deployWalletValue: Along with the deployment of the root token,
      the wallet will be automatically deployed to the owner.
      This is the amount of EVERs that will be sent to the wallet.
      This parameter should be zero if the "initialSupplyTo" is zero address.
    * @param burnDisabledByRoot Root can not burn tokens of a token wallet.
    * @param remainingGasTo Address to send the change back.
  */
  const { contract: tokenRootContract } =
    await locklift.factory.deployContract({
      contract: 'TokenRoot',
      publicKey: signerAlice.publicKey,
      initParams: {
        deployer_: zeroAddress,
        randomNonce_: locklift.utils.getRandomNonce(),
        rootOwner_: rootOwner,
        name_: name,
        symbol_: symbol,
        decimals_: decimals,
        walletCode_: tokenWallet.code,
      },
      constructorParams: {
        initialSupplyTo: initialSupplyTo,
        initialSupply: initialSupply * 10 ** decimals,
        deployWalletValue: 0,
        mintDisabled: disableMint,
        burnByRootDisabled: disableBurnByRoot,
        burnPaused: pauseBurn,
        remainingGasTo: aliceAccount.address,
      },
      value: locklift.utils.toNano(5),
    });

  console.log(
    `${name} deployed to: ${tokenRootContract.address.toString()}`
  );
}

main()
  .then(() => process.exit(0))
  .catch(e => {
    console.log(e);
    process.exit(1);
  });
typescript
// Import the following libraries
import {
  Address,
  GetExpectedAddressParams,
  Contract,
  ProviderApiResponse,
  FullContractState,
} from 'everscale-inpage-provider';
import * as tip3Artifacts from 'tip3-docs-artifacts';
import { provider, providerAddress } from './useProvider';

async function main() {
  // Defining an interface for token root deployment parameters
  interface deployRootParams {
    initialSupplyTo: Address;
    rootOwner: Address;
    name: string;
    symbol: string;
    decimals: number;
    disableMint: boolean;
    disableBurnByRoot: boolean;
    pauseBurn: boolean;
    initialSupply: number;
  }

  // Token root abi
  const tokenRootAbi: tip3Artifacts.FactorySource['TokenRoot'] =
    tip3Artifacts.factorySource['TokenRoot'];

  // Token root and wallet's code and tvc
  const tokenRootArtifacts: typeof tip3Artifacts.artifacts.TokenRoot =
    tip3Artifacts.artifacts.TokenRoot;
  const tokenWalletArtifacts: typeof tip3Artifacts.artifacts.TokenWallet =
    tip3Artifacts.artifacts.TokenWallet;

  // Preparing deployments params
  const params: deployRootParams = {
    initialSupplyTo: tip3Artifacts.zeroAddress,
    rootOwner: tip3Artifacts.zeroAddress,
    name: 'Tip3OnboardingToken',
    symbol: 'TOT',
    decimals: 6,
    disableMint: false,
    disableBurnByRoot: false,
    pauseBurn: false,
    initialSupply: 0,
  };

  // Setting the deployWalletValue based on the initialSupply
  const deployWalletValue: number =
    params.initialSupplyTo == tip3Artifacts.zeroAddress
      ? 2 * 10 ** params.decimals
      : 0;

  // Amount to attach to the tx
  const amount: number =
    params.initialSupplyTo == tip3Artifacts.zeroAddress ? 2 : 4;

  // Define the deployParams type
  type DeployParams<Abi> = GetExpectedAddressParams<Abi> & {
    publicKey: string | undefined;
  };

  // Fetching the user public key
  const accountFullState: FullContractState = (
    await provider.getFullContractState({ address: providerAddress })
  ).state!;
  const senderPublicKey: string = await provider.extractPublicKey(
    accountFullState.boc!
  );

  /**
   * Preparing deploy params to build the state init with the contract abi
   * @param deployer_ Its important to set this param to zero address when deploying the token root contract whiteout using an smart contract.
   */
  const deployParams: DeployParams<
    tip3Artifacts.FactorySource['TokenRoot']
  > = {
    tvc: tokenRootArtifacts.tvc,
    workchain: 0,
    publicKey: senderPublicKey,
    initParams: {
      deployer_: tip3Artifacts.zeroAddress,
      randomNonce_: (Math.random() * 6400) | 0,
      rootOwner_: params.rootOwner,
      name_: params.name,
      symbol_: params.symbol,
      decimals_: params.decimals,
      walletCode_: tokenWalletArtifacts.code,
    },
  };

  // Get the expected contract address
  const expectedAddress: Address = await provider.getExpectedAddress(
    tokenRootAbi,
    deployParams
  );

  // Get the state init
  const stateInit: ProviderApiResponse<'getExpectedAddress'> =
    await provider.getStateInit(tokenRootAbi, deployParams);

  // Send the coins to the calculated address
  await provider.sendMessage({
    sender: providerAddress,
    recipient: expectedAddress,
    amount: String(amount * 10 ** 9),
    bounce: false, // it's important to set this param to keep the evers in the contract
    stateInit: stateInit.stateInit,
  });

  // Create a contract instance
  const tokenRootContract: Contract<
    tip3Artifacts.FactorySource['TokenRoot']
  > = new provider.Contract(tokenRootAbi, expectedAddress);

  // Call the contract constructor
  const { transaction: deployRes } = await tokenRootContract.methods
    .constructor({
      initialSupplyTo: params.initialSupplyTo,
      initialSupply: params.initialSupply,
      deployWalletValue: deployWalletValue,
      mintDisabled: params.disableMint,
      burnByRootDisabled: params.disableBurnByRoot,
      burnPaused: params.pauseBurn,
      remainingGasTo: providerAddress,
    })
    .sendExternal({
      stateInit: stateInit.stateInit,
      publicKey: deployParams.publicKey!,
    });

  // checking if the token root is deployed successfully by calling its name method
  const tokenName: string = (
    await tokenRootContract.methods.name({ answerId: 0 }).call({})
  ).value0;
  if (tokenName == params.name) {
    console.log(
      `${
        params.symbol
      } Token deployed to ${expectedAddress.toString()}`
    );
    return true;
  } else {
    console.log(
      `${params.symbol} Token deployment failed ! ${
        (deployRes.exitCode, deployRes.resultCode)
      }`
    );
    return false;
  }
}

Step 2: Deploy Token Root

Let's run our script using locklift:

shell
npx locklift run -s ./scripts/01-deploy-token.ts -n local
deployTokenRootOutput

Congratulations, you have deployed your first TIP3 Token Root 🎉

initialSupplyTo

rootOwner

name

symbol

decimals

initialSupply

GIF