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.
/**
* 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);
});
// 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:
npx locklift run -s ./scripts/01-deploy-token.ts -n local
Congratulations, you have deployed your first TIP3 Token Root 🎉
initialSupplyTo
rootOwner
name
symbol
decimals
initialSupply