forked from xssnick/registry-contract
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdeploy.ts
More file actions
161 lines (140 loc) · 8.55 KB
/
deploy.ts
File metadata and controls
161 lines (140 loc) · 8.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// This is a simple generic deploy script in TypeScript that should work for most projects without modification
// Every contract you want to deploy should have a mycontract.deploy.ts script that returns its init data
// The script assumes that it is running from the repo root, and the directories are organized this way:
// ./build/ - directory for build artifacts (mycontract.compiled.json) and deploy init data scripts (mycontract.deploy.ts)
// ./.env - config file with DEPLOYER_MNEMONIC - secret mnemonic of deploying wallet (will be created if not found)
import axios from "axios"
import axiosThrottle from "axios-request-throttle";
axiosThrottle.use(axios, { requestsPerSecond: 0.5 }); // required since toncenter jsonRPC limits to 1 req/sec without API key
import dotenv from "dotenv";
dotenv.config();
import fs from "fs";
import path from "path";
import glob from "fast-glob";
import { Address, Cell, CellMessage, CommonMessageInfo, fromNano, InternalMessage, StateInit, toNano } from "ton";
import { TonClient, WalletContract, WalletV3R2Source, contractAddress, SendMode } from "ton";
import { mnemonicNew, mnemonicToWalletKey } from "ton-crypto";
async function main() {
console.log(`=================================================================`);
console.log(`Deploy script running, let's find some contracts to deploy..`);
const isTestnet = process.env.TESTNET || process.env.npm_lifecycle_event == "deploy:testnet";
// check input arguments (given through environment variables)
if (isTestnet) {
console.log(`\n* We are working with 'testnet' (https://t.me/testgiver_ton_bot will give you test TON)`);
} else {
console.log(`\n* We are working with 'mainnet'`);
}
// initialize globals
const client = new TonClient({ endpoint: `https://${isTestnet ? "testnet." : ""}toncenter.com/api/v2/jsonRPC` });
const deployerWalletType = "org.ton.wallets.v3.r2"; // also see WalletV3R2Source class used below
const newContractFunding = toNano(0.02); // this will be (almost in full) the balance of a new deployed contract and allow it to pay rent
const workchain = 0; // normally 0, only special contracts should be deployed to masterchain (-1)
// make sure we have a wallet mnemonic to deploy from (or create one if not found)
const deployConfigEnv = ".env";
let deployerMnemonic;
if (!fs.existsSync(deployConfigEnv) || !process.env.DEPLOYER_MNEMONIC) {
console.log(`\n* Config file '${deployConfigEnv}' not found, creating a new wallet for deploy..`);
deployerMnemonic = (await mnemonicNew(24)).join(" ");
const deployWalletEnvContent = `DEPLOYER_WALLET=${deployerWalletType}\nDEPLOYER_MNEMONIC="${deployerMnemonic}"\n`;
fs.writeFileSync(deployConfigEnv, deployWalletEnvContent);
console.log(` - Created new wallet in '${deployConfigEnv}' - keep this file secret!`);
} else {
console.log(`\n* Config file '${deployConfigEnv}' found and will be used for deployment!`);
deployerMnemonic = process.env.DEPLOYER_MNEMONIC;
}
// open the wallet and make sure it has enough TON
const walletKey = await mnemonicToWalletKey(deployerMnemonic.split(" "));
const walletContract = WalletContract.create(client, WalletV3R2Source.create({ publicKey: walletKey.publicKey, workchain }));
console.log(` - Wallet address used to deploy from is: ${walletContract.address.toFriendly()}`);
const walletBalance = await client.getBalance(walletContract.address);
if (walletBalance.lt(toNano(0.2))) {
console.log(` - ERROR: Wallet has less than 0.2 TON for gas (${fromNano(walletBalance)} TON), please send some TON for gas first`);
process.exit(1);
} else {
console.log(` - Wallet balance is ${fromNano(walletBalance)} TON, which will be used for gas`);
}
// go over all the contracts we have deploy scripts for
const rootContracts = glob.sync(["build/*.deploy.ts"]);
for (const rootContract of rootContracts) {
// deploy a new root contract
console.log(`\n* Found root contract '${rootContract} - let's deploy it':`);
const contractName = path.parse(path.parse(rootContract).name).name;
// prepare the init data cell
const deployInitScript = require(__dirname +"/"+ rootContract);
if (typeof deployInitScript.initData !== "function") {
console.log(` - ERROR: '${rootContract}' does not have 'initData()' function`);
process.exit(1);
}
const initDataCell = deployInitScript.initData() as Cell;
// prepare the init message
if (typeof deployInitScript.initMessage !== "function") {
console.log(` - ERROR: '${rootContract}' does not have 'initMessage()' function`);
process.exit(1);
}
const initMessageCell = deployInitScript.initMessage() as Cell | null;
// prepare the init code cell
const hexArtifact = `build/${contractName}.compiled.json`;
if (!fs.existsSync(hexArtifact)) {
console.log(` - ERROR: '${hexArtifact}' not found, did you build?`);
process.exit(1);
}
const initCodeCell = Cell.fromBoc(JSON.parse(fs.readFileSync(hexArtifact).toString()).hex)[0];
// make sure the contract was not already deployed
const newContractAddress = contractAddress({ workchain, initialData: initDataCell, initialCode: initCodeCell });
console.log(` - Based on your init code+data, your new contract address is: ${newContractAddress.toFriendly()}`);
if (await client.isContractDeployed(newContractAddress)) {
console.log(` - Looks like the contract is already deployed in this address, skipping deployment`);
await performPostDeploymentTest(rootContract, deployInitScript, walletContract, walletKey.secretKey, newContractAddress);
continue;
}
// deploy by sending an internal message to the deploying wallet
console.log(` - Let's deploy the contract on-chain..`);
const seqno = await walletContract.getSeqNo();
const transfer = walletContract.createTransfer({
secretKey: walletKey.secretKey,
seqno: seqno,
sendMode: SendMode.PAY_GAS_SEPARATLY + SendMode.IGNORE_ERRORS,
order: new InternalMessage({
to: newContractAddress,
value: newContractFunding,
bounce: false,
body: new CommonMessageInfo({
stateInit: new StateInit({ data: initDataCell, code: initCodeCell }),
body: initMessageCell !== null ? new CellMessage(initMessageCell) : null,
}),
}),
});
await client.sendExternalMessage(walletContract, transfer);
console.log(` - Deploy transaction sent successfully`);
// make sure that the contract was deployed
console.log(` - Block explorer link: https://${process.env.TESTNET ? "test." : ""}tonwhales.com/explorer/address/${newContractAddress.toFriendly()}`);
console.log(` - Waiting up to 20 seconds to check if the contract was actually deployed..`);
for (let attempt = 0; attempt < 10; attempt++) {
await sleep(2000);
const seqnoAfter = await walletContract.getSeqNo();
if (seqnoAfter > seqno) break;
}
if (await client.isContractDeployed(newContractAddress)) {
console.log(` - SUCCESS! Contract deployed successfully to address: ${newContractAddress.toFriendly()}`);
const contractBalance = await client.getBalance(newContractAddress);
console.log(` - New contract balance is now ${fromNano(contractBalance)} TON, make sure it has enough to pay rent`);
await performPostDeploymentTest(rootContract, deployInitScript, walletContract, walletKey.secretKey, newContractAddress);
} else {
console.log(` - FAILURE! Contract address still looks uninitialized: ${newContractAddress.toFriendly()}`);
}
}
console.log(``);
}
main();
// helpers
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function performPostDeploymentTest(rootContract: string, deployInitScript: any, walletContract: WalletContract, secretKey: Buffer, newContractAddress: Address) {
if (typeof deployInitScript.postDeployTest !== "function") {
console.log(` - Not running a post deployment test, '${rootContract}' does not have 'postDeployTest()' function`);
return;
}
console.log(` - Running a post deployment test:`);
await deployInitScript.postDeployTest(walletContract, secretKey, newContractAddress);
}