Root Deployer
In this section we will learn what is the root deployer contract, its code and how to deploy it using tools like locklift
or everscale-inpage-provider
.
What is the Root Deployer ?
We have developed a smart contract written in t-solidity that facilitates the deployment of the TokenRoot
contract and enables us to obtain the address of an already deployed TokenRoot
contract.
Deploying a token root contract using this smart contract is easier than deploying it through an Account. Contributors can also customize the contract to suit different needs, such as keeping track of all deployed token roots or implementing other functionalities.
Please note that while the RootDeployer
contract acts as the deployer of the TokenRoot
contract, the ownership of the TokenRoot
can be specified by the user.
WARNING
To retrieve the address of your deployed token root via the root deployer's getExpectedTokenRootAddress
function, it is crucial to remember what was your token root randomNonce_
value!
Contract Code
show code
pragma ever-solidity >= 0.61.2;
pragma AbiHeader expire;
pragma AbiHeader pubkey;
import "@broxus/contracts/contracts/libraries/MsgFlag.tsol";
import "@broxus/tip3/contracts/TokenRoot.tsol";
contract RootDeployer {
uint32 static randomNonce_;
address owner_;
TvmCell rootCode_;
TvmCell walletCode_;
constructor(
TvmCell _rootCode,
TvmCell _walletCode
) public {
tvm.accept();
owner_ = msg.sender;
rootCode_ = _rootCode;
walletCode_ = _walletCode;
}
function getExpectedTokenRootAddress(
string name,
string symbol,
uint8 decimals,
address rootOwner,
uint32 randomNonce
) public view returns(address){
return address(tvm.hash(tvm.buildStateInit({
contr: TokenRoot,
varInit: {
randomNonce_: randomNonce,
deployer_: address(this),
rootOwner_: rootOwner,
name_: name,
symbol_: symbol,
decimals_: decimals,
walletCode_: walletCode_
},
pubkey: 0,
code: rootCode_
})));
}
function DeployRootDeployer(
string name,
string symbol,
uint8 decimals,
address initialSupplyTo,
uint128 initialSupply,
uint128 deployWalletValue,
address rootOwner,
uint32 randomNonce,
bool mintDisabled,
bool burnByRootDisabled,
bool burnPaused,
address remainingGasTo
) public view{
tvm.accept();
TvmCell initData = tvm.buildStateInit({
contr: TokenRoot,
varInit: {
randomNonce_: randomNonce,
deployer_: address(this),
rootOwner_: rootOwner,
name_: name,
symbol_: symbol,
decimals_: decimals,
walletCode_: walletCode_
},
pubkey: 0,
code: rootCode_
});
new TokenRoot {
stateInit: initData,
value: 2 ever,
flag: MsgFlag.SENDER_PAYS_FEES
}(
initialSupplyTo,
initialSupply,
deployWalletValue,
mintDisabled,
burnByRootDisabled,
burnPaused,
remainingGasTo
);
}
}
TIP
It is important to understand that smart contracts in Everscale have a life of their own
live in separate mini-blockchains and can only communicate by messages.
For this reason, we pass callbacks so that the contract returns something.
Step 1: Write Deployment Script
Now we write scripts to deploy the Root Deployer contract :
We have already covered how to deploy a contract using locklift, so deploying RootDeployer
should be a straightforward process:
INFO
Before we start to write our scripts we need to make sure that there is file named 01-deploy-root-deployer.ts
in the script
folder in the project root.
We have already learned how to deploy a contract using everscale-inpage-provider
as well, so the process remains the same as before.
/**
* locklift is a globally declared object
*/
import {
Address,
Contract,
Signer,
WalletTypes,
zeroAddress,
} from 'locklift';
import { FactorySource } from '../build/factorySource';
async function getWalletData(
MWContract: Contract<FactorySource['MultiWalletTIP3']>,
tokenRootAddress: Address
): Promise<{ tokenWallet: Address; balance: number }> {
// Returned value of the wallets mapping on the multi wallet tip-3 contract
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 = zeroAddress;
if (walletData.length != 0) {
balance = Number(walletData[0]!.balance);
tokenWallet = walletData[0]!.tokenWallet;
}
return { tokenWallet: tokenWallet, balance: balance };
}
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,
});
// Deploying the Root deployer
const { contract: rootDeployer } =
await locklift.factory.deployContract({
contract: 'RootDeployer',
publicKey: signerAlice.publicKey,
initParams: {
randomNonce_: locklift.utils.getRandomNonce(),
},
constructorParams: {
_rootCode:
locklift.factory.getContractArtifacts('TokenRoot').code,
_walletCode:
locklift.factory.getContractArtifacts('TokenWallet').code,
},
value: locklift.utils.toNano('5'),
});
console.log(`Root Deployer: ${rootDeployer.address.toString()}`);
}
main()
.then(() => process.exit(0))
.catch(e => {
console.log(e);
process.exit(1);
});
import {
ProviderRpcClient as PRC,
Address,
GetExpectedAddressParams,
Contract,
Transaction,
FullContractState,
} from 'everscale-inpage-provider';
import * as tip3Artifacts from 'tip3-docs-artifacts';
import { provider, providerAddress } from './useProvider';
async function main() {
// Root Deployer contract abi
const rootDeployerAbi: tip3Artifacts.FactorySource['RootDeployer'] =
tip3Artifacts.factorySource['RootDeployer'];
// required contracts code and tvc
const tokenRootArtifacts: typeof tip3Artifacts.artifacts.TokenRoot =
tip3Artifacts.artifacts.TokenRoot;
const tokenWalletArtifacts: typeof tip3Artifacts.artifacts.TokenWallet =
tip3Artifacts.artifacts.TokenWallet;
const rootDeployerArtifacts: typeof tip3Artifacts.artifacts.RootDeployer =
tip3Artifacts.artifacts.RootDeployer;
// 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 the deployment params
const deployParams: DeployParams<
tip3Artifacts.FactorySource['RootDeployer']
> = {
tvc: rootDeployerArtifacts.tvc,
workchain: 0,
publicKey: senderPublicKey,
initParams: {
randomNonce_: (Math.random() * 6400) | 0,
},
};
// Get the expected address of the root deployer contract
const expectedAddress = await provider.getExpectedAddress(
rootDeployerAbi,
deployParams
);
// Get the state init
const stateInit = await provider.getStateInit(
rootDeployerAbi,
deployParams
);
// Send the coins to the address
await provider.sendMessage({
sender: providerAddress,
recipient: expectedAddress,
amount: String(3 * 10 ** 9),
bounce: false, // It is important to set 'bounce' to false
// to ensure funds remain in the contract.
stateInit: stateInit.stateInit,
});
console.log('Fund sent to the Calculated address !');
// Create a instance of the root deployer contract
const userRootDeployer: Contract<
tip3Artifacts.FactorySource['RootDeployer']
> = new provider.Contract(rootDeployerAbi, expectedAddress);
console.log('Sending stateInit to the Calculated address ...');
// Call the contract constructor
const { transaction: deployRes } = await userRootDeployer.methods
.constructor({
_rootCode: tokenRootArtifacts.code,
_walletCode: tokenWalletArtifacts.code,
})
.sendExternal({
stateInit: stateInit.stateInit,
publicKey: deployParams.publicKey!,
});
// checking if the token root is deployed successfully by calling one of its methods
if (
(
await provider.getFullContractState({
address: expectedAddress,
})
).state?.isDeployed
) {
console.log(`Root Deployer deployed successfully`);
return `Root Deployer deployed to ${expectedAddress.toString()}`;
} else {
throw new Error(
`Root Deployer deployment failed !${
(deployRes.exitCode, deployRes.resultCode)
}`
);
}
}
Step 2: Deploy Root Deployer
Let's run our script using locklift
npx locklift run -s ./scripts/01-deploy-root-deployer.ts -n local
Congratulations, you have deployed a Root Deployer contract 🎉