Tal Kol
3 years ago
7 changed files with 281 additions and 122 deletions
@ -0,0 +1,110 @@
|
||||
// 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.cell) and deploy init data scripts (mycontract.deploy.ts)
|
||||
// ./build/deploy.config.json - JSON config file with secret mnemonic of deploying wallet (will be created if not found)
|
||||
|
||||
import fs from "fs"; |
||||
import path from "path"; |
||||
import glob from "fast-glob"; |
||||
import { Address, Cell, CommonMessageInfo, contractAddress, fromNano, InternalMessage, SendMode, StateInit, toNano, TonClient, WalletContract, WalletV3R2Source } from "ton"; |
||||
import { mnemonicNew, mnemonicToWalletKey } from "ton-crypto"; |
||||
import { BN } from "bn.js"; |
||||
|
||||
async function main() { |
||||
console.log(`=================================================================`); |
||||
console.log(`Deploy script running, let's find some contracts to deploy..`); |
||||
|
||||
// make sure we have a wallet mnemonic to deploy from (or create one if not found)
|
||||
const deployConfigJson = `build/deploy.config.json`; |
||||
let deployerMnemonic; |
||||
if (!fs.existsSync(deployConfigJson)) { |
||||
console.log(`\n* Config file '${deployConfigJson}' not found, creating a new wallet for deploy..`); |
||||
deployerMnemonic = (await mnemonicNew(24)).join(" "); |
||||
const deployWalletJsonContent = { created: new Date().toISOString(), deployerMnemonic }; |
||||
fs.writeFileSync(deployConfigJson, JSON.stringify(deployWalletJsonContent, null, 2)); |
||||
console.log(` - Created new wallet in '${deployConfigJson}' - keep this file secret!`); |
||||
} else { |
||||
console.log(`\n* Config file '${deployConfigJson}' found and will be used for deployment!`); |
||||
const deployConfigJsonContent = require(__dirname + "/../" + deployConfigJson); |
||||
if (!deployConfigJsonContent.deployerMnemonic) { |
||||
console.log(` - ERROR: '${deployConfigJson}' does not have the key 'deployerMnemonic'`); |
||||
process.exit(1); |
||||
} |
||||
deployerMnemonic = deployConfigJsonContent.deployerMnemonic; |
||||
} |
||||
|
||||
// open the wallet and make sure it has enough TON
|
||||
const client = new TonClient({ endpoint: "https://toncenter.com/api/v2/jsonRPC" }); |
||||
const walletKey = await mnemonicToWalletKey(deployerMnemonic.split(" ")); |
||||
const walletContract = WalletContract.create(client, WalletV3R2Source.create({ publicKey: walletKey.publicKey, workchain: 0 })); |
||||
console.log(` - Wallet address used for deployment is: ${walletContract.address.toFriendly()}`); |
||||
const walletBalance = await client.getBalance(walletContract.address); |
||||
if (walletBalance.lt(toNano(1))) { |
||||
console.log(` - ERROR: Wallet has less than 1 TON for gas (${fromNano(walletBalance)} TON), please send some TON for gas first`); |
||||
process.exit(1); |
||||
} |
||||
|
||||
// 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 to deploy '${rootContract}':`); |
||||
const contractName = path.parse(path.parse(rootContract).name).name; |
||||
|
||||
// prepare the init data cell
|
||||
const deployInit = require(__dirname + "/../" + rootContract); |
||||
if (typeof deployInit.initData !== "function") { |
||||
console.log(` - ERROR: '${rootContract}' does not have 'initData()' function`); |
||||
process.exit(1); |
||||
} |
||||
const initDataCell = deployInit.initData() as Cell; |
||||
|
||||
// prepare the init code cell
|
||||
const cellArtifact = `build/${contractName}.cell`; |
||||
if (!fs.existsSync(cellArtifact)) { |
||||
console.log(` - ERROR: '${cellArtifact}' not found, did you build?`); |
||||
process.exit(1); |
||||
} |
||||
const initCodeCell = Cell.fromBoc(fs.readFileSync(cellArtifact))[0]; |
||||
|
||||
// deploy by sending an internal message to the deploying wallet
|
||||
sleep(1000); // to make sure we don't fail due to throttling
|
||||
const newContractAddress = contractAddress({ workchain: 0, initialData: initDataCell, initialCode: initCodeCell }); |
||||
console.log(` - About to deploy contract to new address: ${newContractAddress.toFriendly()}`); |
||||
const seqno = await getSeqNo(client, walletContract.address); |
||||
sleep(1000); // to make sure we don't fail due to throttling
|
||||
const transfer = await walletContract.createTransfer({ |
||||
secretKey: walletKey.secretKey, |
||||
seqno: seqno, |
||||
sendMode: SendMode.PAY_GAS_SEPARATLY + SendMode.IGNORE_ERRORS, |
||||
order: new InternalMessage({ |
||||
to: newContractAddress, |
||||
value: new BN(0.5), |
||||
bounce: false, |
||||
body: new CommonMessageInfo({ stateInit: new StateInit({ data: initDataCell, code: initCodeCell }) }), |
||||
}), |
||||
}); |
||||
await client.sendExternalMessage(walletContract, transfer); |
||||
console.log(` - Contract deployed successfully!`); |
||||
} |
||||
|
||||
console.log(``); |
||||
} |
||||
|
||||
main(); |
||||
|
||||
// helpers
|
||||
|
||||
function sleep(ms: number) { |
||||
return new Promise((resolve) => setTimeout(resolve, ms)); |
||||
} |
||||
|
||||
async function getSeqNo(client: TonClient, walletAddress: Address) { |
||||
if (await client.isContractDeployed(walletAddress)) { |
||||
let res = await client.callGetMethod(walletAddress, "seqno"); |
||||
return parseInt(res.stack[0][1], 16); |
||||
} else { |
||||
return 0; |
||||
} |
||||
} |
@ -0,0 +1,10 @@
|
||||
import * as main from "../contracts/main"; |
||||
import { Address } from "ton"; |
||||
|
||||
// return the init Cell of the contract storage (according to load_data() contract method)
|
||||
export function initData() { |
||||
return main.data({ |
||||
ownerAddress: Address.parseFriendly("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N").address, |
||||
counter: 0, |
||||
}); |
||||
} |
Loading…
Reference in new issue