diff --git a/package-lock.json b/package-lock.json index d126b3ed0..4064c8333 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15861,9 +15861,10 @@ "dependencies": { "@ckb-js/kuai-common": "^0.0.1", "@ckb-js/kuai-docker-node": "0.0.1", - "@ckb-lumos/base": "^0.19.0", + "@ckb-lumos/base": "0.19.0", "@ckb-lumos/config-manager": "0.19.0", "@ckb-lumos/hd": "0.19.0", + "@ckb-lumos/helpers": "0.20.0-alpha.2", "@iarna/toml": "2.2.5", "chalk": "4.1.2", "enquirer": "2.3.6", @@ -15874,6 +15875,92 @@ "@types/lodash": "4.14.194" } }, + "packages/core/node_modules/@ckb-lumos/bi": { + "version": "0.20.0-alpha.2", + "resolved": "https://registry.npmjs.org/@ckb-lumos/bi/-/bi-0.20.0-alpha.2.tgz", + "integrity": "sha512-LqB0qlHIU0Q7ccBac/O5opW9/kfd4pak0MTyO5YhS5qKFAJ7Qq0eTPLXwMvhI+h4pVIc4SnoxXjP0x+9ciMTBQ==", + "dependencies": { + "jsbi": "^4.1.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "packages/core/node_modules/@ckb-lumos/codec": { + "version": "0.20.0-alpha.2", + "resolved": "https://registry.npmjs.org/@ckb-lumos/codec/-/codec-0.20.0-alpha.2.tgz", + "integrity": "sha512-1wUV3SAy3d1z5E4IZWMxb8YmL7eCRvD9PbzQ54eFnFiqYR2U3GUeoDes3YE+uLiIkLd9gGl3h2659E5Djk28JA==", + "dependencies": { + "@ckb-lumos/bi": "0.20.0-alpha.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "packages/core/node_modules/@ckb-lumos/helpers": { + "version": "0.20.0-alpha.2", + "resolved": "https://registry.npmjs.org/@ckb-lumos/helpers/-/helpers-0.20.0-alpha.2.tgz", + "integrity": "sha512-8v5xYxh8PKbDqO0B/wvbv62oZSUZjTuCOHsTCy9AsGFjP7vW8UCg7S7RHJg9b0nc2a77nl8WLLtzzym/YvFIGA==", + "dependencies": { + "@ckb-lumos/base": "0.20.0-alpha.2", + "@ckb-lumos/bi": "0.20.0-alpha.2", + "@ckb-lumos/config-manager": "0.20.0-alpha.2", + "@ckb-lumos/toolkit": "0.20.0-alpha.2", + "bech32": "^2.0.0", + "immutable": "^4.0.0-rc.12" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "packages/core/node_modules/@ckb-lumos/helpers/node_modules/@ckb-lumos/base": { + "version": "0.20.0-alpha.2", + "resolved": "https://registry.npmjs.org/@ckb-lumos/base/-/base-0.20.0-alpha.2.tgz", + "integrity": "sha512-1J4icB0soMMjYrJ++nL2aZ4tQFhpKdTBbBe0DhfSpFv6rAeMNGKG/sc6RO/2fr8e5TUygK23oirzwGcqlZ9Oag==", + "dependencies": { + "@ckb-lumos/bi": "0.20.0-alpha.2", + "@ckb-lumos/codec": "0.20.0-alpha.2", + "@ckb-lumos/toolkit": "0.20.0-alpha.2", + "@types/blake2b": "^2.1.0", + "@types/lodash.isequal": "^4.5.5", + "blake2b": "^2.1.3", + "js-xxhash": "^1.0.4", + "lodash.isequal": "^4.5.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "jsbi": "^4.1.0" + } + }, + "packages/core/node_modules/@ckb-lumos/helpers/node_modules/@ckb-lumos/config-manager": { + "version": "0.20.0-alpha.2", + "resolved": "https://registry.npmjs.org/@ckb-lumos/config-manager/-/config-manager-0.20.0-alpha.2.tgz", + "integrity": "sha512-MCwHa0JsJyV9l+IMQ3kro0RWReZTttSmUARo0lY6UPWZkcNxdMHvI3RKoO1nZtxMRkZ0tmV+C3O0Fv3J/3kpsg==", + "dependencies": { + "@ckb-lumos/base": "0.20.0-alpha.2", + "@ckb-lumos/bi": "0.20.0-alpha.2", + "@ckb-lumos/codec": "0.20.0-alpha.2", + "@types/deep-freeze-strict": "^1.1.0", + "deep-freeze-strict": "^1.1.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "packages/core/node_modules/@ckb-lumos/toolkit": { + "version": "0.20.0-alpha.2", + "resolved": "https://registry.npmjs.org/@ckb-lumos/toolkit/-/toolkit-0.20.0-alpha.2.tgz", + "integrity": "sha512-nhiA81/51D3qO3vZieKs9vRJ643hnm67PImkTTeHXNxj95CgIyoxQH+E2yKUKt3Htew8IXOlbNUpJ/QsahPiqg==", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "cross-fetch": "^3.1.4", + "jsbi": "^4.1.0" + } + }, "packages/docker-node": { "name": "@ckb-js/kuai-docker-node", "version": "0.0.1", @@ -16458,15 +16545,82 @@ "requires": { "@ckb-js/kuai-common": "^0.0.1", "@ckb-js/kuai-docker-node": "0.0.1", - "@ckb-lumos/base": "^0.19.0", + "@ckb-lumos/base": "0.19.0", "@ckb-lumos/config-manager": "0.19.0", "@ckb-lumos/hd": "0.19.0", + "@ckb-lumos/helpers": "0.20.0-alpha.2", "@iarna/toml": "2.2.5", "@types/lodash": "4.14.194", "chalk": "4.1.2", "enquirer": "2.3.6", "find-up": "5.0.0", "lodash": "4.17.21" + }, + "dependencies": { + "@ckb-lumos/bi": { + "version": "0.20.0-alpha.2", + "resolved": "https://registry.npmjs.org/@ckb-lumos/bi/-/bi-0.20.0-alpha.2.tgz", + "integrity": "sha512-LqB0qlHIU0Q7ccBac/O5opW9/kfd4pak0MTyO5YhS5qKFAJ7Qq0eTPLXwMvhI+h4pVIc4SnoxXjP0x+9ciMTBQ==", + "requires": { + "jsbi": "^4.1.0" + } + }, + "@ckb-lumos/codec": { + "version": "0.20.0-alpha.2", + "resolved": "https://registry.npmjs.org/@ckb-lumos/codec/-/codec-0.20.0-alpha.2.tgz", + "integrity": "sha512-1wUV3SAy3d1z5E4IZWMxb8YmL7eCRvD9PbzQ54eFnFiqYR2U3GUeoDes3YE+uLiIkLd9gGl3h2659E5Djk28JA==", + "requires": { + "@ckb-lumos/bi": "0.20.0-alpha.2" + } + }, + "@ckb-lumos/helpers": { + "version": "0.20.0-alpha.2", + "resolved": "https://registry.npmjs.org/@ckb-lumos/helpers/-/helpers-0.20.0-alpha.2.tgz", + "integrity": "sha512-8v5xYxh8PKbDqO0B/wvbv62oZSUZjTuCOHsTCy9AsGFjP7vW8UCg7S7RHJg9b0nc2a77nl8WLLtzzym/YvFIGA==", + "requires": { + "@ckb-lumos/base": "0.20.0-alpha.2", + "@ckb-lumos/bi": "0.20.0-alpha.2", + "@ckb-lumos/config-manager": "0.20.0-alpha.2", + "@ckb-lumos/toolkit": "0.20.0-alpha.2", + "bech32": "^2.0.0", + "immutable": "^4.0.0-rc.12" + }, + "dependencies": { + "@ckb-lumos/base": { + "version": "0.20.0-alpha.2", + "resolved": "https://registry.npmjs.org/@ckb-lumos/base/-/base-0.20.0-alpha.2.tgz", + "integrity": "sha512-1J4icB0soMMjYrJ++nL2aZ4tQFhpKdTBbBe0DhfSpFv6rAeMNGKG/sc6RO/2fr8e5TUygK23oirzwGcqlZ9Oag==", + "requires": { + "@ckb-lumos/bi": "0.20.0-alpha.2", + "@ckb-lumos/codec": "0.20.0-alpha.2", + "@ckb-lumos/toolkit": "0.20.0-alpha.2", + "@types/blake2b": "^2.1.0", + "@types/lodash.isequal": "^4.5.5", + "blake2b": "^2.1.3", + "js-xxhash": "^1.0.4", + "lodash.isequal": "^4.5.0" + } + }, + "@ckb-lumos/config-manager": { + "version": "0.20.0-alpha.2", + "resolved": "https://registry.npmjs.org/@ckb-lumos/config-manager/-/config-manager-0.20.0-alpha.2.tgz", + "integrity": "sha512-MCwHa0JsJyV9l+IMQ3kro0RWReZTttSmUARo0lY6UPWZkcNxdMHvI3RKoO1nZtxMRkZ0tmV+C3O0Fv3J/3kpsg==", + "requires": { + "@ckb-lumos/base": "0.20.0-alpha.2", + "@ckb-lumos/bi": "0.20.0-alpha.2", + "@ckb-lumos/codec": "0.20.0-alpha.2", + "@types/deep-freeze-strict": "^1.1.0", + "deep-freeze-strict": "^1.1.1" + } + } + } + }, + "@ckb-lumos/toolkit": { + "version": "0.20.0-alpha.2", + "resolved": "https://registry.npmjs.org/@ckb-lumos/toolkit/-/toolkit-0.20.0-alpha.2.tgz", + "integrity": "sha512-nhiA81/51D3qO3vZieKs9vRJ643hnm67PImkTTeHXNxj95CgIyoxQH+E2yKUKt3Htew8IXOlbNUpJ/QsahPiqg==", + "requires": {} + } } }, "@ckb-js/kuai-docker-node": { diff --git a/packages/core/package.json b/packages/core/package.json index 402a74d4a..2e1f7d245 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -10,9 +10,10 @@ "dependencies": { "@ckb-js/kuai-common": "^0.0.1", "@ckb-js/kuai-docker-node": "0.0.1", - "@ckb-lumos/base": "^0.19.0", + "@ckb-lumos/base": "0.19.0", "@ckb-lumos/config-manager": "0.19.0", "@ckb-lumos/hd": "0.19.0", + "@ckb-lumos/helpers": "0.20.0-alpha.2", "@iarna/toml": "2.2.5", "chalk": "4.1.2", "enquirer": "2.3.6", diff --git a/packages/core/src/builtin-tasks/contract.ts b/packages/core/src/builtin-tasks/contract.ts index 96287872d..8c06bea28 100644 --- a/packages/core/src/builtin-tasks/contract.ts +++ b/packages/core/src/builtin-tasks/contract.ts @@ -1,19 +1,94 @@ import { execSync } from 'node:child_process' +import readline from 'node:readline/promises' +import { stdin, stdout } from 'node:process' +import { existsSync } from 'node:fs' import path from 'node:path' -import fs from 'node:fs' -import TOML from '@iarna/toml' -import { key } from '@ckb-lumos/hd' -import { generateAddress } from '@ckb-lumos/helpers' -import { predefined } from '@ckb-lumos/config-manager' -import { Script } from '@ckb-lumos/base' +import { KuaiError } from '@ckb-js/kuai-common' +import { ERRORS } from '../errors-list' +import { parseFromInfo } from '@ckb-lumos/common-scripts' +import { encodeToAddress } from '@ckb-lumos/helpers' +import { Config } from '@ckb-lumos/config-manager' +import { ContractDeployer } from '../contract' +import type { MessageSigner } from '../contract' +import { signMessageByCkbCli } from '../ckbcli' import { task, subtask } from '../config/config-env' import { paramTypes } from '../params' import { getUserConfigPath } from '../project-structure' +import { getGenesisScriptsConfig } from '../util/chain' task('contract').setAction(async () => { execSync('kuai contract --help', { stdio: 'inherit' }) }) +subtask('contract:deploy') + .addParam('name', 'name of the contract to be deployed', '', paramTypes.string, false) + .addParam('address', 'address of the contract deployer', '', paramTypes.string, false) + .addParam( + 'feeRate', + "Per transaction's fee, deployment may involve more than one transaction. default: [1000] shannon/Byte", + 1000, + paramTypes.number, + true, + ) + .setAction(async ({ name, address, feeRate }, { config, run }) => { + const { ckbChain } = config + const workspace = (await run('contract:get-workspace')) as string + + if (!name) { + throw new Error('Please specify the name of the contract, not support deploying all contracts for now.') + } + + if (!address) { + throw new Error('please specify address of deployer') + } + + const conrtactBinPath = path.join(workspace, `build/release/${name}`) + + if (!existsSync(conrtactBinPath)) { + throw new KuaiError(ERRORS.BUILTIN_TASKS.UNSUPPORTED_SIGNER, { + var: name, + }) + } + + const lumosConfig: Config = { + PREFIX: ckbChain.prefix, + SCRIPTS: ckbChain.scripts || { + ...(await getGenesisScriptsConfig(ckbChain.rpcUrl)), + }, + } + + const signer: MessageSigner = (message, fromInfo) => + run('contract:sign-message', { + message, + address: encodeToAddress(parseFromInfo(fromInfo, { config: lumosConfig }).fromScript, { config: lumosConfig }), + }) as Promise + + const deployer = new ContractDeployer(signer, { + rpcUrl: config.ckbChain.rpcUrl, + config: lumosConfig, + }) + + const result = await deployer.deploy(conrtactBinPath, address, { feeRate }) + console.info('deploy success, txHash: ', result.txHash) + }) + +subtask('contract:sign-message') + .addParam('message', 'message to be signed', '', paramTypes.string, false) + .addParam('address', 'the address of message signer', '', paramTypes.string, false) + .addParam('signer', 'the sign method of signer, default: ckb-cli', 'ckb-cli', paramTypes.string, true) + .setAction(async ({ message, address, signer }): Promise => { + if (signer === 'ckb-cli') { + const rl = readline.createInterface({ input: stdin, output: stdout }) + const password = await rl.question(`Input ${address}'s password for sign messge by ckb-cli:`) + rl.close() + return signMessageByCkbCli(message, address, password) + } + + throw new KuaiError(ERRORS.BUILTIN_TASKS.UNSUPPORTED_SIGNER, { + var: signer, + }) + }) + subtask('contract:get-workspace').setAction(async (_, { config }) => { if (config.contract?.workspace) { return config.contract?.workspace @@ -31,30 +106,6 @@ subtask('contract:set-environment').setAction(async () => { // todo: download ckb-cli & capsule etc... }) -interface ImportArgs { - privateKey: string -} - -subtask('contract:import-private-key') - .addParam('privateKey', '', '', paramTypes.string, false) - .setAction(async ({ privateKey }: ImportArgs) => { - const lockArg = key.privateKeyToBlake160(privateKey) - - const res = execSync('ckb-cli account list --output-format json').toString() - const accountList: Array<{ lock_arg: string }> = JSON.parse(res) - - if (accountList.some((account) => account.lock_arg === lockArg)) { - return - } - - fs.writeFileSync('./.tmp_pk', privateKey) - - console.log('add the user to ckb-cli, enter a password for the signature please') - execSync('ckb-cli account import --privkey-path ./.tmp_pk', { stdio: 'inherit' }) - - fs.rmSync('./.tmp_pk') - }) - interface BuildArgs { name?: string release?: boolean @@ -86,124 +137,5 @@ subtask('contract:new') ) .setAction(async ({ name, template }: NewArgs, { run }) => { const workspace = await run('contract:get-workspace') - console.log('workspace', workspace) execSync(`cd ${workspace} && capsule new-contract ${name} --template ${template}`, { stdio: 'inherit' }) }) - -interface DeployArgs { - name?: string - chain?: string - isMainnet?: boolean - fee: number - env: string - migrate: string -} - -subtask('contract:deploy') - .addParam('name', 'name of the contract to be deployed', '', paramTypes.string, true) - .addParam( - 'chain', - 'ckb rpc url or name [default: dev] [possible values: dev, test, mainnet, http://localhost:8114 etc...]', - 'dev', - paramTypes.string, - true, - ) - .addParam('isMainnet', '', false, paramTypes.boolean, true) - .addParam( - 'fee', - "Per transaction's fee, deployment may involve more than one transaction. default: [1]", - 1, - paramTypes.number, - true, - ) - .addParam( - 'env', - 'Deployment environment. [default: dev] [possible values: dev, production]', - 'dev', - paramTypes.string, - true, - ) - .addParam( - 'migrate', - 'Use previously deployed cells as inputs. [default: on] [possible values: on, off]', - 'on', - paramTypes.string, - true, - ) - .setAction(async ({ name, chain, fee, env, migrate, ...args }: DeployArgs, { config, run }) => { - const isMainnet = chain === 'mainnet' ? true : args.isMainnet - - const workspace = (await run('contract:get-workspace')) as string - const { type = 'ckb-cli', deployerPrivateKey } = config.contract?.deployment || {} - - if (type !== 'ckb-cli') { - throw new Error('Other deployment options are not supported temporary') - } - - if (!deployerPrivateKey) { - throw new Error('please set deployerPrivateKey in kuai.config.js') - } - - await run('contract:import-private-key', { privateKey: deployerPrivateKey }) - - const deployerLock: Script = { - codeHash: '0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8', - args: key.privateKeyToBlake160(deployerPrivateKey), - hashType: 'type', - } - const { lock = deployerLock } = config.contract?.deployment || {} - - // setting capsule config - const capsuleConfigFile = fs.readFileSync(path.join(workspace, 'capsule.toml'), 'utf-8') - const deploymentConfigFile = fs.readFileSync(path.join(workspace, 'deployment.toml'), 'utf-8') - - const capsuleConfig = TOML.parse(capsuleConfigFile) - const deploymentConfig = TOML.parse(deploymentConfigFile) - - const contractNames = (capsuleConfig['contracts'] as Array<{ name: string }>).map((contract) => contract.name) - const deployedContractName = name || contractNames[0] - - const generateContractDeployConfig = (name: string, enableTypeId = true) => { - return { - name, - enable_type_id: enableTypeId, - location: { file: `build/release/${name}` }, - } - } - - deploymentConfig['cells'] = [generateContractDeployConfig(deployedContractName)] - deploymentConfig['lock'] = { - code_hash: lock.codeHash, - args: lock.args, - hash_type: lock.hashType, - } - - fs.writeFileSync(path.join(workspace, 'deployment.toml'), TOML.stringify(deploymentConfig)) - - const ckbRpcUrl = (() => { - if (chain === 'dev') { - return 'http://localhost:8114' - } - - if (chain === 'test') { - return 'https://testnet.ckb.dev/rpc' - } - - if (chain === 'mainnet') { - return 'https://mainnet.ckb.dev/rpc' - } - - return chain - })() - - const deployerAddress = generateAddress(deployerLock, { config: isMainnet ? predefined.LINA : predefined.AGGRON4 }) - - console.log('deployed', deployedContractName, 'to', ckbRpcUrl, ', deployer', deployerAddress) - - execSync( - `cd ${workspace} && capsule deploy --address ${deployerAddress} --api ${ckbRpcUrl} --fee ${fee} --env ${env} --migrate ${migrate}`, - { - stdio: 'inherit', - }, - ) - }) diff --git a/packages/core/src/builtin-tasks/node.ts b/packages/core/src/builtin-tasks/node.ts index 5be270701..4095591a2 100644 --- a/packages/core/src/builtin-tasks/node.ts +++ b/packages/core/src/builtin-tasks/node.ts @@ -30,10 +30,6 @@ subtask('node:start', 'start a ckb node') detached, genesisAccountArgs: genesisArgs, }) - - env.config.network = { - url: 'http://localhost:' + port, - } }) subtask('node:stop', 'stop ckb node').setAction(async (_, env) => { diff --git a/packages/core/src/ckbcli.ts b/packages/core/src/ckbcli.ts new file mode 100644 index 000000000..49bb186f8 --- /dev/null +++ b/packages/core/src/ckbcli.ts @@ -0,0 +1,39 @@ +import { execSync } from 'node:child_process' + +interface CkbCliAccount { + address: { + mainnet: string + testnet: string + } + 'address(deprecated)': { + mainnet: string + testnet: string + } + has_ckb_pubkey_derivation_root_path: boolean + lock_arg: string + lock_hash: string + source: string +} + +export function getCkbCliAccounts(): Array { + const output = execSync(`ckb-cli account list --output-format json`, { + stdio: 'inherit', + }) + + return JSON.parse(output.toString()) +} + +export function signMessageByCkbCli(message: string, account: string, password: string): string { + const output = execSync( + `ckb-cli util sign-message --message ${message} --from-account ${account} --recoverable --output-format json`, + { + input: password, + encoding: 'utf-8', + stdio: 'pipe', + }, + ) + + const json = output.substring(output.indexOf('{'), output.lastIndexOf('}') + 1) + + return JSON.parse(json).signature +} diff --git a/packages/core/src/config/config-loading.ts b/packages/core/src/config/config-loading.ts index 5a941ce98..0dec6056d 100644 --- a/packages/core/src/config/config-loading.ts +++ b/packages/core/src/config/config-loading.ts @@ -1,7 +1,9 @@ import path from 'node:path' +import { KuaiError } from '@ckb-js/kuai-common' import { KuaiConfig, KuaiArguments } from '../type' import { getUserConfigPath } from '../project-structure' -import { DEFAULT_KUAI_ARGUMENTS } from '../constants' +import { ERRORS } from '../errors-list' +import { DEFAULT_KUAI_ARGUMENTS, DEFAULT_NETWORKDS } from '../constants' // eslint-disable-next-line @typescript-eslint/no-explicit-any function importCsjOrEsModule(filePath: string): any { @@ -34,7 +36,8 @@ export async function loadConfigAndTasks(args: KuaiArguments = {}): Promise Promise + +export class ContractDeployer { + #rpc: RPC + #index: Indexer + #config: config.Config + + constructor( + private readonly signer: MessageSigner, + private readonly network: { + rpcUrl: string + config: config.Config + }, + ) { + this.#rpc = new RPC(network.rpcUrl) + this.#index = new Indexer(network.rpcUrl) + this.#config = network.config + } + + async deploy( + contractBinPath: string, + fromInfo: FromInfo, + options?: { + feeRate?: number + enableTypeId?: boolean + }, + ): Promise<{ + txHash: Hash + index: number + dataHash: string + typeId?: string + }> { + const { feeRate = 1000 } = options || {} + const contractBin = readFileSync(contractBinPath) + + const result = await generateDeployWithTypeIdTx({ + cellProvider: this.#index, + fromInfo, + scriptBinary: contractBin, + config: this.#config, + }) + + let { txSkeleton } = result + const { scriptConfig, typeId } = result + + txSkeleton = await commons.common.payFee(txSkeleton, [fromInfo], feeRate, undefined, { config: this.#config }) + txSkeleton = commons.common.prepareSigningEntries(txSkeleton) + const signatures = await Promise.all(txSkeleton.signingEntries.map(({ message }) => this.signer(message, fromInfo))) + const signedTx = sealTransaction(txSkeleton, signatures) + const txHash = await this.#rpc.sendTransaction(signedTx) + + return { + txHash, + index: parseInt(scriptConfig.INDEX), + dataHash: scriptConfig.CODE_HASH, + typeId: utils.ckbHash(blockchain.Script.pack(typeId)), + } + } +} diff --git a/packages/core/src/errors-list.ts b/packages/core/src/errors-list.ts index d98b83234..37c151320 100644 --- a/packages/core/src/errors-list.ts +++ b/packages/core/src/errors-list.ts @@ -42,11 +42,23 @@ Please run: npm install --save-dev ts-node`, Please run: npm install --save-dev typescript`, }, + NETWORK_NOT_FOUND: { + code: 'NETWORK_NOT_FOUND', + message: `The specified network is not defined, please configure it first`, + }, }, BUILTIN_TASKS: { UNSUPPORTED_NETWORK: { code: 'UNSUPPORTED_NETWORK', message: 'Unsupported network %var%.', }, + UNSUPPORTED_SIGNER: { + code: 'UNSUPPORTED_SIGNER', + message: 'Unsupported signer %var%.', + }, + CONTRACT_RELEASE_FILE_NOT_FOUND: { + code: 'CONTRACT_RELEASE_FILE_NOT_FOUND', + message: '%var% contract release file not found, please check conrtact is exists or build it first', + }, }, } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index b731440e0..c30991e1d 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -6,3 +6,4 @@ export * from './config' export { KuaiContext } from './context' export * from './typescript-support' export * from './helper' +export * from './util' diff --git a/packages/core/src/type/runtime.ts b/packages/core/src/type/runtime.ts index 2f6fe256b..6f730072b 100644 --- a/packages/core/src/type/runtime.ts +++ b/packages/core/src/type/runtime.ts @@ -1,4 +1,5 @@ import type { Config as JestConfig } from 'jest' +import type { ScriptConfig } from '@ckb-lumos/config-manager' // eslint-disable-next-line @typescript-eslint/no-explicit-any export type TaskArguments = any @@ -10,26 +11,25 @@ export interface KuaiArguments { export interface KuaiConfig { kuaiArguments?: KuaiArguments - network?: HttpNetworkConfig + network?: string + ckbChain: NetworkConfig jest?: JestConfig contract?: ContractConfig + networks?: { + [name: string]: NetworkConfig + } } - export type ContractConfig = { workspace?: string - deployment?: { - type?: string - deployerPrivateKey: string - lock?: { - codeHash: string - args: string - hashType: string - } - } } export type HttpNetworkConfig = { - url: string + rpcUrl: string +} + +export type NetworkConfig = HttpNetworkConfig & { + prefix: string + scripts?: Record } export type RunTaskFunction = (name: string, taskArguments?: TaskArguments) => Promise diff --git a/packages/core/src/util/chain.ts b/packages/core/src/util/chain.ts new file mode 100644 index 000000000..0cb0f689f --- /dev/null +++ b/packages/core/src/util/chain.ts @@ -0,0 +1,49 @@ +import { RPC, config, utils } from '@ckb-lumos/lumos' + +// Special cells in genesis transactions: (transaction-index, output-index) +const SpecialCellLocation: { + [name: string]: [number, number] +} = { + SIGHASH: [0, 1], + MULTISIG: [0, 4], + DAO: [0, 2], + SIGHASH_GROUP: [1, 0], + MULTISIG_GROUP: [1, 1], +} + +export async function getGenesisScriptsConfig(rpcUrl: string): Promise> { + const rpc = new RPC(rpcUrl) + const genesisBlock = await rpc.getBlockByNumber('0x0') + + const generateGenesisScriptConfig = (transactionIndex: number, outputIndex: number) => { + const cellTypeScript = genesisBlock.transactions[transactionIndex].outputs[outputIndex].type + + return { + CODE_HASH: cellTypeScript ? utils.computeScriptHash(cellTypeScript) : '', + TX_HASH: genesisBlock.transactions[transactionIndex].hash!, + INDEX: '0x' + outputIndex.toString(16), + } + } + + return { + DAO: { + HASH_TYPE: 'type', + ...generateGenesisScriptConfig(...SpecialCellLocation['DAO']), + DEP_TYPE: 'code', + }, + SECP256K1_BLAKE160: { + HASH_TYPE: 'type', + ...generateGenesisScriptConfig(...SpecialCellLocation['SIGHASH_GROUP']), + CODE_HASH: generateGenesisScriptConfig(...SpecialCellLocation['SIGHASH']).CODE_HASH, + DEP_TYPE: 'depGroup', + SHORT_ID: 0, + }, + SECP256K1_BLAKE160_MULTISIG: { + HASH_TYPE: 'type', + ...generateGenesisScriptConfig(...SpecialCellLocation['MULTISIG_GROUP']), + CODE_HASH: generateGenesisScriptConfig(...SpecialCellLocation['MULTISIG']).CODE_HASH, + DEP_TYPE: 'depGroup', + SHORT_ID: 1, + }, + } +} diff --git a/packages/core/src/util/index.ts b/packages/core/src/util/index.ts new file mode 100644 index 000000000..bbfb40bd0 --- /dev/null +++ b/packages/core/src/util/index.ts @@ -0,0 +1 @@ +export * from './chain' diff --git a/packages/samples/mvp-dapp/kuai.config.js b/packages/samples/mvp-dapp/kuai.config.js index bd0e4fb81..eaecd7c87 100644 --- a/packages/samples/mvp-dapp/kuai.config.js +++ b/packages/samples/mvp-dapp/kuai.config.js @@ -3,8 +3,7 @@ require('dotenv').config() module.exports = { host: process.env.HOST, port: process.env.PORT, - rpcUrl: process.env.CKB_RPC_URL || 'https://testnet.ckb.dev/rpc', - lumosConfig: process.env.LUMOS_CONFIG || 'aggron4', + network: process.env.NETWORK || 'devnet', redisPort: process.env.REDIS_PORT, redisHost: process.env.REDIS_HOST, jest: { @@ -17,11 +16,4 @@ module.exports = { }, }, }, - contract: { - deployment: { - type: 'ckb-cli', - // just for example, please use env variables to pass the private key - deployerPrivateKey: '0xd6013cd867d286ef84cc300ac6546013837df2b06c9f53c83b4c33c2417f6a07', - }, - }, } diff --git a/packages/samples/mvp-dapp/src/main.ts b/packages/samples/mvp-dapp/src/main.ts index 234acceb7..4acf1d1f4 100644 --- a/packages/samples/mvp-dapp/src/main.ts +++ b/packages/samples/mvp-dapp/src/main.ts @@ -1,6 +1,6 @@ import Koa from 'koa' import { koaBody } from 'koa-body' -import { initialKuai } from '@ckb-js/kuai-core' +import { initialKuai, getGenesisScriptsConfig } from '@ckb-js/kuai-core' import { config } from '@ckb-lumos/lumos' import { KoaRouterAdapter, CoR, TipHeaderListener } from '@ckb-js/kuai-io' import cors from '@koa/cors' @@ -28,30 +28,22 @@ async function bootstrap() { mqContainer.bind(REDIS_HOST_SYMBOL).toConstantValue(kuaiEnv.config.redisHost) } - const lumosConfig: config.Config = (() => { - if (kuaiEnv.config.lumosConfig === 'aggron4') { - return config.predefined.AGGRON4 - } + config.initializeConfig( + config.createConfig({ + PREFIX: kuaiEnv.config.ckbChain.prefix, + SCRIPTS: kuaiEnv.config.ckbChain.scripts || { + ...(await getGenesisScriptsConfig(kuaiEnv.config.ckbChain.rpcUrl)), + }, + }), + ) - if (kuaiEnv.config.lumosConfig === 'lina') { - return config.predefined.LINA - } - - if (kuaiEnv.config.lumosConfig) { - return kuaiEnv.config.lumosConfig - } - - return { PREFIX: 'ckt', SCRIPTS: {} } - })() - - config.initializeConfig(lumosConfig) // eslint-disable-next-line @typescript-eslint/no-explicit-any const port = kuaiEnv.config.port || 3000 const host = kuaiEnv.config.host || '127.0.0.1' Reflect.defineMetadata(ProviderKey.Actor, { ref: new ActorReference('resource', '/').json }, Manager) - const dataSource = new NervosChainSource(kuaiEnv.config.rpcUrl) + const dataSource = new NervosChainSource(kuaiEnv.config.ckbChain.rpcUrl) const listener = new TipHeaderListener(dataSource) const manager = new Manager(listener, dataSource) manager.listen()