From 094e8761f089996ad5b1be388ac4973704ac8002 Mon Sep 17 00:00:00 2001 From: pacoyang Date: Wed, 13 Dec 2023 13:15:50 +0800 Subject: [PATCH 1/7] chore: update copywriting --- src/lib/PhatBaseCommand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/PhatBaseCommand.ts b/src/lib/PhatBaseCommand.ts index af41590..48cf563 100644 --- a/src/lib/PhatBaseCommand.ts +++ b/src/lib/PhatBaseCommand.ts @@ -381,7 +381,7 @@ export default abstract class PhatBaseCommand extends BaseCommand { } async promptRpc( - message = 'Please enter your client RPC URL' + message = 'Please enter your EVM RPC URL' ): Promise { const { rpc } = await inquirer.prompt([ { From 813ebe0768bf5e797ed65e5dbfabc4a5ba90a76f Mon Sep 17 00:00:00 2001 From: pacoyang Date: Fri, 15 Dec 2023 11:23:45 +0800 Subject: [PATCH 2/7] feat: add command to list evm accounts --- src/commands/list-evm-accounts.ts | 87 +++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/commands/list-evm-accounts.ts diff --git a/src/commands/list-evm-accounts.ts b/src/commands/list-evm-accounts.ts new file mode 100644 index 0000000..878c3e8 --- /dev/null +++ b/src/commands/list-evm-accounts.ts @@ -0,0 +1,87 @@ +import { + PinkContractPromise, +} from '@phala/sdk' +import chalk from 'chalk' + +import PhatBaseCommand from '../lib/PhatBaseCommand' +import type { BrickProfileContract } from '../lib/PhatBaseCommand' + +export default class ListEvmAccounts extends PhatBaseCommand { + static description = 'List EVM accounts' + + static args = { + ...PhatBaseCommand.args + } + + static flags = { + ...PhatBaseCommand.flags + } + + public async run(): Promise { + const pair = await this.getDecodedPair({ + suri: this.parsedFlags.suri || process.env.POLKADOT_WALLET_SURI, + accountFilePath: this.parsedFlags.accountFilePath || process.env.POLKADOT_WALLET_ACCOUNT_FILE, + accountPassword: this.parsedFlags.accountPassword || process.env.POLKADOT_WALLET_ACCOUNT_PASSWORD, + }) + + // Step 1: Connect to the endpoint. + const endpoint = this.getEndpoint() + const [apiPromise, registry, cert] = await this.connect({ + endpoint, + pair, + }) + + // Step 2: Query the brick profile contract id. + this.action.start('Querying your Brick Profile contract ID') + const brickProfileContractId = await this.getBrickProfileContractId({ + endpoint, + registry, + apiPromise, + pair, + cert, + }) + this.action.succeed(`Your Brick Profile contract ID: ${brickProfileContractId}`) + + // Step 3: Querying your external accounts + try { + this.action.start('Querying your external accounts') + const brickProfileAbi = await this.loadAbiByContractId( + registry, + brickProfileContractId + ) + const brickProfileContractKey = await registry.getContractKeyOrFail( + brickProfileContractId + ) + const brickProfile: BrickProfileContract = new PinkContractPromise( + apiPromise, + registry, + brickProfileAbi, + brickProfileContractId, + brickProfileContractKey + ) + const { output } = await brickProfile.query.getAllEvmAccounts(cert.address, { + cert, + }) + if (output.isErr) { + throw new Error(output.asErr.toString()) + } + if (output.asOk.isErr) { + throw new Error(output.asOk.asErr.toString()) + } + this.action.stop() + const accounts = output.asOk.asOk.map((i) => { + const obj = i.toJSON() + return { + id: obj.id, + address: obj.address, + rpcEndpoint: obj.rpc, + } + }) + accounts.map(account => this.log(`[${account.id}] ${account.address}. ${chalk.dim(account.rpcEndpoint)}`)) + process.exit(0) + } catch (error) { + this.action.fail('Failed to query your external accounts.') + return this.error(error as Error) + } + } +} From d9105d5bd5849fb20cbd0c19676a3f5e0485ead2 Mon Sep 17 00:00:00 2001 From: pacoyang Date: Fri, 15 Dec 2023 12:54:16 +0800 Subject: [PATCH 3/7] feat: add command to add evm account --- src/commands/add-evm-account.ts | 104 ++++++++++++++++++++++++++++++++ src/commands/update.ts | 4 +- src/lib/PhatBaseCommand.ts | 11 ++++ src/lib/types.ts | 3 + src/lib/utils.ts | 35 +++++++++++ 5 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 src/commands/add-evm-account.ts create mode 100644 src/lib/types.ts diff --git a/src/commands/add-evm-account.ts b/src/commands/add-evm-account.ts new file mode 100644 index 0000000..eeb4d47 --- /dev/null +++ b/src/commands/add-evm-account.ts @@ -0,0 +1,104 @@ +import { Flags } from '@oclif/core' +import { PinkContractPromise } from '@phala/sdk' + +import PhatBaseCommand, { type ParsedFlags, type BrickProfileContract } from '../lib/PhatBaseCommand' +import { bindWaitPRuntimeFinalized } from '../lib/utils' + +export default class AddEvmAccount extends PhatBaseCommand { + static description = 'Add EVM accounts' + + static args = { + ...PhatBaseCommand.args + } + + static flags = { + ...PhatBaseCommand.flags, + evmRpcEndpoint: Flags.string({ + description: 'EVM RPC endpoint', + required: true, + }), + } + + public async run(): Promise { + const { evmRpcEndpoint } = this.parsedFlags as ParsedFlags & { + evmRpcEndpoint: string + } + console.info(evmRpcEndpoint) + const pair = await this.getDecodedPair({ + suri: this.parsedFlags.suri || process.env.POLKADOT_WALLET_SURI, + accountFilePath: this.parsedFlags.accountFilePath || process.env.POLKADOT_WALLET_ACCOUNT_FILE, + accountPassword: this.parsedFlags.accountPassword || process.env.POLKADOT_WALLET_ACCOUNT_PASSWORD, + }) + + // Step 1: Connect to the endpoint. + const endpoint = this.getEndpoint() + const [apiPromise, registry, cert] = await this.connect({ + endpoint, + pair, + }) + + // Step 2: Query the brick profile contract id. + this.action.start('Querying your Brick Profile contract ID') + const brickProfileContractId = await this.getBrickProfileContractId({ + endpoint, + registry, + apiPromise, + pair, + cert, + }) + this.action.succeed(`Your Brick Profile contract ID: ${brickProfileContractId}`) + + // Step 3: generate evm account + try { + this.action.start('Adding evm account') + const brickProfileAbi = await this.loadAbiByContractId( + registry, + brickProfileContractId + ) + const brickProfileContractKey = await registry.getContractKeyOrFail( + brickProfileContractId + ) + const brickProfile: BrickProfileContract = new PinkContractPromise( + apiPromise, + registry, + brickProfileAbi, + brickProfileContractId, + brickProfileContractKey + ) + const { output } = await brickProfile.query.externalAccountCount(cert.address, { + cert, + }) + if (output.isErr) { + throw new Error(output.asErr.toString()) + } + const externalAccountCount = output.asOk.toNumber() + const waitForPRuntimeFinalized = bindWaitPRuntimeFinalized(registry) + await waitForPRuntimeFinalized( + brickProfile.send.generateEvmAccount( + { cert, address: pair.address, pair }, + evmRpcEndpoint + ), + async function () { + const { output } = await brickProfile.query.externalAccountCount(cert.address, { + cert, + }) + return output.isOk && output.asOk.toNumber() === externalAccountCount + 1 + } + ) + const { output: evmAccountAddressOutput } = await brickProfile.query.getEvmAccountAddress( + cert.address, + { cert }, + externalAccountCount + ) + if (evmAccountAddressOutput.isErr) { + throw new Error(evmAccountAddressOutput.asErr.toString()) + } + const evmAddress = evmAccountAddressOutput.asOk.asOk.toHex() + this.action.succeed(`Added successfully, your evm address is: ${evmAddress}`) + process.exit(0) + } catch (error) { + this.action.fail('Failed to add evm account.') + return this.error(error as Error) + } + } +} diff --git a/src/commands/update.ts b/src/commands/update.ts index 70d466b..5d78f74 100644 --- a/src/commands/update.ts +++ b/src/commands/update.ts @@ -1,9 +1,7 @@ import fs from 'node:fs' import { Flags } from '@oclif/core' import type { Result, Struct, u16, Text, Bool } from '@polkadot/types' -import { - PinkContractPromise, -} from '@phala/sdk' +import { PinkContractPromise } from '@phala/sdk' import inquirer from 'inquirer' import PhatBaseCommand, { type ParsedFlags } from '../lib/PhatBaseCommand' diff --git a/src/lib/PhatBaseCommand.ts b/src/lib/PhatBaseCommand.ts index 48cf563..b1ca090 100644 --- a/src/lib/PhatBaseCommand.ts +++ b/src/lib/PhatBaseCommand.ts @@ -13,6 +13,7 @@ import { PinkContractPromise, PinkContractQuery, type CertificateData, + type PinkContractTx, } from '@phala/sdk' import { ApiPromise } from '@polkadot/api' import { Abi } from '@polkadot/api-contract' @@ -61,6 +62,16 @@ export type BrickProfileContract = PinkContractPromise< [], Result, any> > + workflowCount: PinkContractQuery<[], u64> + externalAccountCount: PinkContractQuery<[], u64> + getEvmAccountAddress: PinkContractQuery< + [number | u64], + Result + > + + }, + { + generateEvmAccount: PinkContractTx<[string | Text]> } > diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..9e0d17a --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,3 @@ +export interface WaitPRuntimeFinalized { + (awaitable: Promise, predicate?: () => Promise): Promise +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 62d3e78..eb982b2 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,7 +1,42 @@ import os from 'node:os' import upath from 'upath' +import type { OnChainRegistry } from '@phala/sdk' + +import { WaitPRuntimeFinalized } from './types' export function resolveToAbsolutePath(inputPath: string): string { const regex = /^~(?=$|[/\\])/ return upath.resolve(inputPath.replace(regex, os.homedir())) } + +export function bindWaitPRuntimeFinalized( + phatRegistry: OnChainRegistry, + confirmations = 10, + pollingIntervalMs = 1000 +): WaitPRuntimeFinalized { + return async function waitPRuntimeFinalized( + awaitable: Promise, + predicate?: () => Promise + ) { + const { blocknum: initBlockNum } = await phatRegistry.phactory.getInfo({}) + const result = await awaitable + while (true) { + const { blocknum } = await phatRegistry.phactory.getInfo({}) + if (blocknum > initBlockNum + confirmations) { + if (!predicate) { + return result + } + throw new Error( + `Wait for transaction finalized in PRuntime but timeout after ${confirmations} blocks.` + ) + } + if (predicate) { + const predicateResult = await predicate() + if (predicateResult) { + return result + } + } + await new Promise((resolve) => setTimeout(resolve, pollingIntervalMs)) + } + } +} From 2ad006e4409872eaa87abc208bf2fbe94a3c2494 Mon Sep 17 00:00:00 2001 From: pacoyang Date: Mon, 18 Dec 2023 16:23:16 +0800 Subject: [PATCH 4/7] feat: add command to create brick profile --- src/commands/add-evm-account.ts | 1 - src/commands/create-brick-profile.ts | 202 +++++++++++++++++++++++++++ src/lib/PhatBaseCommand.ts | 58 +++++++- 3 files changed, 255 insertions(+), 6 deletions(-) create mode 100644 src/commands/create-brick-profile.ts diff --git a/src/commands/add-evm-account.ts b/src/commands/add-evm-account.ts index eeb4d47..4a62e06 100644 --- a/src/commands/add-evm-account.ts +++ b/src/commands/add-evm-account.ts @@ -23,7 +23,6 @@ export default class AddEvmAccount extends PhatBaseCommand { const { evmRpcEndpoint } = this.parsedFlags as ParsedFlags & { evmRpcEndpoint: string } - console.info(evmRpcEndpoint) const pair = await this.getDecodedPair({ suri: this.parsedFlags.suri || process.env.POLKADOT_WALLET_SURI, accountFilePath: this.parsedFlags.accountFilePath || process.env.POLKADOT_WALLET_ACCOUNT_FILE, diff --git a/src/commands/create-brick-profile.ts b/src/commands/create-brick-profile.ts new file mode 100644 index 0000000..cdfd17f --- /dev/null +++ b/src/commands/create-brick-profile.ts @@ -0,0 +1,202 @@ +import type { Struct, u128 } from '@polkadot/types' +import { PinkContractPromise, OnChainRegistry, type CertificateData } from '@phala/sdk' +import { type KeyringPair } from '@polkadot/keyring/types' + +import PhatBaseCommand, { type BrickProfileFactoryContract, type BrickProfileContract } from '../lib/PhatBaseCommand' +import { bindWaitPRuntimeFinalized } from '../lib/utils' + +interface PartialAccountQueryResult extends Struct { + data: { + free: u128 + } +} + +export default class CreateBrickProfile extends PhatBaseCommand { + static description = 'Create brick profile' + + static args = { + ...PhatBaseCommand.args + } + + static flags = { + ...PhatBaseCommand.flags + } + + public async run(): Promise { + const pair = await this.getDecodedPair({ + suri: this.parsedFlags.suri || process.env.POLKADOT_WALLET_SURI, + accountFilePath: this.parsedFlags.accountFilePath || process.env.POLKADOT_WALLET_ACCOUNT_FILE, + accountPassword: this.parsedFlags.accountPassword || process.env.POLKADOT_WALLET_ACCOUNT_PASSWORD, + }) + + // Step 1: Connect to the endpoint. + const endpoint = this.getEndpoint() + const [apiPromise, registry, cert, type] = await this.connect({ + endpoint, + pair, + }) + + // Step 2: Check balance + const account = await apiPromise.query.system.account(cert.address) + const balance = Number(account.data.free.toBigInt() / BigInt(1e12)) + if (balance < 50) { + this.action.fail(`Insufficient on-chain balance, please go to ${type.isDevelopment || type.isLocal ? 'https://phala.network/faucet' : 'https://docs.phala.network/introduction/basic-guidance/get-pha-and-transfer'} to get more than 50 PHA before continuing the process.`) + this.exit(0) + } + try { + this.action.start('Creating your brick profile') + const brickProfileFactoryContractId = await this.getBrickProfileFactoryContractId(endpoint) + const brickProfileFactoryAbi = await this.loadAbiByContractId( + registry, + brickProfileFactoryContractId + ) + const brickProfileFactoryContractKey = await registry.getContractKeyOrFail( + brickProfileFactoryContractId + ) + const brickProfileFactory: BrickProfileFactoryContract = new PinkContractPromise( + apiPromise, + registry, + brickProfileFactoryAbi, + brickProfileFactoryContractId, + brickProfileFactoryContractKey + ) + const waitForPRuntimeFinalized = bindWaitPRuntimeFinalized(registry) + // Step 3: create user profile + await waitForPRuntimeFinalized( + brickProfileFactory.send.createUserProfile( + { cert, address: pair.address, pair }, + ), + async function () { + const { output } = await brickProfileFactory.query.getUserProfileAddress(cert.address, { + cert, + }) + const created = output && output.isOk && output.asOk.isOk + if (!created) { + return false + } + const result = await registry.getContractKey( + output.asOk.asOk.toHex() + ) + if (result) { + return true + } + return false + } + ) + // Step 4: query profile + const { output } = await brickProfileFactory.query.getUserProfileAddress(cert.address, { + cert, + }) + if (output.isErr) { + throw new Error(output.asErr.toString()) + } + const brickProfileContractId = output.asOk.asOk.toHex() + const brickProfileAbi = await this.loadAbiByContractId( + registry, + brickProfileContractId + ) + const brickProfileContractKey = await registry.getContractKeyOrFail( + brickProfileContractId + ) + const brickProfile: BrickProfileContract = new PinkContractPromise( + apiPromise, + registry, + brickProfileAbi, + brickProfileContractId, + brickProfileContractKey + ) + // Step 5: unsafeConfigureJsRunner + const jsRunnerContractId = await this.getJsRunnerContractId(endpoint) + await this.unsafeConfigureJsRunner({ + registry, + contract: brickProfile, + jsRunnerContractId, + pair, + cert, + }) + // Step 6: unsafeGenerateEtherAccount + const { output: queryCount } = await brickProfile.query.externalAccountCount(cert.address, { + cert, + }) + if (queryCount.isErr) { + throw new Error(queryCount.asErr.toString()) + } + const externalAccountCount = output.asOk.toNumber() + if (externalAccountCount === 0) { + await this.unsafeGenerateEtherAccount({ + registry, + contract: brickProfile, + externalAccountCount, + evmRpcEndpoint: type.isDevelopment || type.isLocal ? 'https://polygon-mumbai.g.alchemy.com/v2/YWlujLKt0nSn5GrgEpGCUA0C_wKV1sVQ' : 'https://polygon-mainnet.g.alchemy.com/v2/W1kyx17tiFQFT2b19mGOqppx90BLHp0a', + pair, + cert + }) + } + this.action.succeed(`Created successfully.`) + process.exit(0) + } catch (error) { + this.action.fail('Failed to create brick profile.') + return this.error(error as Error) + } + } + + async unsafeGenerateEtherAccount({ + registry, + contract, + externalAccountCount, + evmRpcEndpoint, + pair, + cert, + }: { + registry: OnChainRegistry + contract: BrickProfileContract + externalAccountCount: number + evmRpcEndpoint: string + pair: KeyringPair + cert: CertificateData + }) { + const waitForPRuntimeFinalized = bindWaitPRuntimeFinalized(registry) + await waitForPRuntimeFinalized( + contract.send.generateEvmAccount( + { cert, address: pair.address, pair }, + evmRpcEndpoint + ), + async function () { + const { output } = await contract.query.externalAccountCount(cert.address, { + cert, + }) + return output.isOk && output.asOk.toNumber() === externalAccountCount + 1 + } + ) + } + + async unsafeConfigureJsRunner({ + registry, + contract, + jsRunnerContractId, + pair, + cert, + }: { + registry: OnChainRegistry + contract: BrickProfileContract + jsRunnerContractId: string + pair: KeyringPair + cert: CertificateData + }) { + const waitForPRuntimeFinalized = bindWaitPRuntimeFinalized(registry) + await waitForPRuntimeFinalized( + contract.send.config( + { cert, address: pair.address, pair }, + jsRunnerContractId + ), + async function () { + const { output } = await contract.query.getJsRunner(cert.address, { cert }) + return ( + output.isOk && + output.asOk.isOk && + output.asOk.asOk.toHex() === jsRunnerContractId + ) + } + ) + } +} diff --git a/src/lib/PhatBaseCommand.ts b/src/lib/PhatBaseCommand.ts index b1ca090..d048d26 100644 --- a/src/lib/PhatBaseCommand.ts +++ b/src/lib/PhatBaseCommand.ts @@ -21,7 +21,7 @@ import { waitReady } from '@polkadot/wasm-crypto' import { Keyring } from '@polkadot/keyring' import { type KeyringPair } from '@polkadot/keyring/types' import type { Result, Vec, u64, u8, Text, Struct } from '@polkadot/types' -import type { AccountId } from '@polkadot/types/interfaces' +import type { AccountId, ChainType, Hash } from '@polkadot/types/interfaces' import { MAX_BUILD_SIZE, @@ -44,6 +44,7 @@ export interface ParsedFlags { readonly coreSettings: string readonly pruntimeUrl: string readonly externalAccountId: string + readonly jsRunner: string } interface ParsedArgs { @@ -56,8 +57,24 @@ export interface ExternalAccountCodec extends Struct { rpc: Text } +export type BrickProfileFactoryContract = PinkContractPromise< + { + version: PinkContractQuery<[], u64[]> + owner: PinkContractQuery<[], AccountId> + userCount: PinkContractQuery<[], u64> + profileCodeHash: PinkContractQuery<[], Hash> + getUserProfileAddress: PinkContractQuery<[], Result> + }, + { + setProfileCodeHash: PinkContractTx<[string]> + createUserProfile: PinkContractTx<[]> + } +> + + export type BrickProfileContract = PinkContractPromise< { + getJsRunner: PinkContractQuery<[], Result> getAllEvmAccounts: PinkContractQuery< [], Result, any> @@ -68,7 +85,6 @@ export type BrickProfileContract = PinkContractPromise< [number | u64], Result > - }, { generateEvmAccount: PinkContractTx<[string | Text]> @@ -116,7 +132,7 @@ export default abstract class PhatBaseCommand extends BaseCommand { required: false, }), brickProfileFactory: Flags.string({ - description: 'Brick profile factory contract address', + description: 'Brick profile factory contract id', required: false, default: '', }), @@ -144,6 +160,11 @@ export default abstract class PhatBaseCommand extends BaseCommand { char: 'b', default: true, }), + jsRunner: Flags.string({ + description: 'JS runner contract id', + required: false, + default: '', + }), } public parsedFlags!: ParsedFlags @@ -204,6 +225,20 @@ export default abstract class PhatBaseCommand extends BaseCommand { return brickProfileFactoryContractId } + async getJsRunnerContractId(endpoint: string) { + let jsRunnerContractId = this.parsedFlags.jsRunner + if (!jsRunnerContractId) { + if (endpoint === 'wss://poc6.phala.network/ws') { + jsRunnerContractId = '0x15fd4cc6e96b1637d46bd896f586e5de7c6835d8922d9d43f3c1dd5b84883d79' + } else if (endpoint === 'wss://api.phala.network/ws') { + jsRunnerContractId = '0xd0b2ee3ac67b363734c5105a275b5de964ecc4a304d98c2cc49a8d417331ade2' + } else { + jsRunnerContractId = await this.promptJsRunner() + } + } + return jsRunnerContractId + } + async getBrickProfileContractId({ endpoint, registry, @@ -250,7 +285,7 @@ export default abstract class PhatBaseCommand extends BaseCommand { }: { endpoint: string pair: KeyringPair - }): Promise<[ApiPromise, OnChainRegistry, CertificateData]> { + }): Promise<[ApiPromise, OnChainRegistry, CertificateData, ChainType]> { this.action.start(`Connecting to the endpoint: ${endpoint}`) const registry = await getClient({ transport: endpoint, @@ -262,7 +297,7 @@ export default abstract class PhatBaseCommand extends BaseCommand { if (type.isDevelopment || type.isLocal) { this.log(chalk.yellow(`\nYou are connecting to a testnet.\n`)) } - return [registry.api, registry, cert] + return [registry.api, registry, cert, type] } async getRollupAbi() { @@ -430,6 +465,19 @@ export default abstract class PhatBaseCommand extends BaseCommand { return brickProfileFactory } + async promptJsRunner( + message = 'Please enter the js runner contract ID' + ): Promise { + const { jsRunner } = await inquirer.prompt([ + { + name: 'jsRunner', + type: 'input', + message, + }, + ]) + return jsRunner + } + async getDecodedPair({ suri, accountFilePath, accountPassword }: { suri?: string, accountFilePath?: string, accountPassword?: string }): Promise { await waitReady() const keyring = new Keyring({ type: 'sr25519' }) From f1c540d49a53b1658764fdb3fd617ba65237da05 Mon Sep 17 00:00:00 2001 From: pacoyang Date: Fri, 22 Dec 2023 13:25:42 +0800 Subject: [PATCH 5/7] refactor: use getContract and waitFinalized --- src/commands/add-evm-account.ts | 41 ++++++++++++++------------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/commands/add-evm-account.ts b/src/commands/add-evm-account.ts index 4a62e06..09b3a0f 100644 --- a/src/commands/add-evm-account.ts +++ b/src/commands/add-evm-account.ts @@ -1,8 +1,7 @@ import { Flags } from '@oclif/core' -import { PinkContractPromise } from '@phala/sdk' +import { getContract } from '@phala/sdk' import PhatBaseCommand, { type ParsedFlags, type BrickProfileContract } from '../lib/PhatBaseCommand' -import { bindWaitPRuntimeFinalized } from '../lib/utils' export default class AddEvmAccount extends PhatBaseCommand { static description = 'Add EVM accounts' @@ -54,16 +53,11 @@ export default class AddEvmAccount extends PhatBaseCommand { registry, brickProfileContractId ) - const brickProfileContractKey = await registry.getContractKeyOrFail( - brickProfileContractId - ) - const brickProfile: BrickProfileContract = new PinkContractPromise( - apiPromise, - registry, - brickProfileAbi, - brickProfileContractId, - brickProfileContractKey - ) + const brickProfile = await getContract({ + client: registry, + contractId: brickProfileContractId, + abi: brickProfileAbi, + }) as BrickProfileContract const { output } = await brickProfile.query.externalAccountCount(cert.address, { cert, }) @@ -71,19 +65,18 @@ export default class AddEvmAccount extends PhatBaseCommand { throw new Error(output.asErr.toString()) } const externalAccountCount = output.asOk.toNumber() - const waitForPRuntimeFinalized = bindWaitPRuntimeFinalized(registry) - await waitForPRuntimeFinalized( - brickProfile.send.generateEvmAccount( - { cert, address: pair.address, pair }, - evmRpcEndpoint - ), - async function () { - const { output } = await brickProfile.query.externalAccountCount(cert.address, { - cert, - }) - return output.isOk && output.asOk.toNumber() === externalAccountCount + 1 - } + + const result = await brickProfile.send.generateEvmAccount( + { cert, address: pair.address, pair }, + evmRpcEndpoint ) + await result.waitFinalized(async () => { + const { output } = await brickProfile.query.externalAccountCount(cert.address, { + cert, + }) + return output.isOk && output.asOk.toNumber() === externalAccountCount + 1 + }) + const { output: evmAccountAddressOutput } = await brickProfile.query.getEvmAccountAddress( cert.address, { cert }, From 623ac3f68782ca6701c18a760a612e9ae51efba8 Mon Sep 17 00:00:00 2001 From: pacoyang Date: Fri, 22 Dec 2023 13:41:01 +0800 Subject: [PATCH 6/7] feat: verify the EVM RPC endpoint --- package.json | 1 + src/commands/add-evm-account.ts | 15 +++++++++++++++ yarn.lock | 14 ++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/package.json b/package.json index 79e87e4..8882158 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "typescript": "^5.2.2", "undici": "^5.27.0", "upath": "^2.0.1", + "viem": "^1.20.3", "webpack": "^5.88.2", "webpack-merge": "^5.9.0", "webpack-virtual-modules": "^0.5.0" diff --git a/src/commands/add-evm-account.ts b/src/commands/add-evm-account.ts index 09b3a0f..4d540cc 100644 --- a/src/commands/add-evm-account.ts +++ b/src/commands/add-evm-account.ts @@ -1,5 +1,6 @@ import { Flags } from '@oclif/core' import { getContract } from '@phala/sdk' +import { createPublicClient, http } from 'viem' import PhatBaseCommand, { type ParsedFlags, type BrickProfileContract } from '../lib/PhatBaseCommand' @@ -22,6 +23,20 @@ export default class AddEvmAccount extends PhatBaseCommand { const { evmRpcEndpoint } = this.parsedFlags as ParsedFlags & { evmRpcEndpoint: string } + + // Verify the RPC endpoint + try { + this.action.start(`Verifying the RPC endpoint: ${evmRpcEndpoint}`) + const client = createPublicClient({ + transport: http(evmRpcEndpoint) + }) + await client.getChainId() + this.action.succeed() + } catch (error) { + this.action.fail('Failed to verify the RPC endpoint.') + return this.error(error as Error) + } + const pair = await this.getDecodedPair({ suri: this.parsedFlags.suri || process.env.POLKADOT_WALLET_SURI, accountFilePath: this.parsedFlags.accountFilePath || process.env.POLKADOT_WALLET_ACCOUNT_FILE, diff --git a/yarn.lock b/yarn.lock index 6c3f25c..7dc6d2a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6050,6 +6050,20 @@ validate-npm-package-name@^5.0.0: dependencies: builtins "^5.0.0" +viem@^1.20.3: + version "1.20.3" + resolved "https://registry.yarnpkg.com/viem/-/viem-1.20.3.tgz#8b8360daee622295f5385949c02c86d943d14e0f" + integrity sha512-7CrmeCb2KYkeCgUmUyb1hsf+IX/PLwi+Np+Vm4YUTPeG82y3HRSgGHSaCOp3d0YtR2kXD3nv9y5kE7LBFE+wWw== + dependencies: + "@adraffy/ens-normalize" "1.10.0" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@scure/bip32" "1.3.2" + "@scure/bip39" "1.2.1" + abitype "0.9.8" + isows "1.0.3" + ws "8.13.0" + viem@^1.5.0: version "1.19.9" resolved "https://registry.yarnpkg.com/viem/-/viem-1.19.9.tgz#a11f3ad4a3323994ebd2010dbc659d1a2b12e583" From 376c06a7bd0ec66ee4d5a9417875f4ac71efc8ce Mon Sep 17 00:00:00 2001 From: pacoyang Date: Fri, 22 Dec 2023 14:13:49 +0800 Subject: [PATCH 7/7] feat: support for specifying evm rpc endpoint --- src/commands/add-evm-account.ts | 13 +------------ src/commands/create-brick-profile.ts | 19 ++++++++++++++++--- src/lib/PhatBaseCommand.ts | 15 +++++++++++++++ 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/commands/add-evm-account.ts b/src/commands/add-evm-account.ts index 4d540cc..d12a0b9 100644 --- a/src/commands/add-evm-account.ts +++ b/src/commands/add-evm-account.ts @@ -1,6 +1,5 @@ import { Flags } from '@oclif/core' import { getContract } from '@phala/sdk' -import { createPublicClient, http } from 'viem' import PhatBaseCommand, { type ParsedFlags, type BrickProfileContract } from '../lib/PhatBaseCommand' @@ -25,17 +24,7 @@ export default class AddEvmAccount extends PhatBaseCommand { } // Verify the RPC endpoint - try { - this.action.start(`Verifying the RPC endpoint: ${evmRpcEndpoint}`) - const client = createPublicClient({ - transport: http(evmRpcEndpoint) - }) - await client.getChainId() - this.action.succeed() - } catch (error) { - this.action.fail('Failed to verify the RPC endpoint.') - return this.error(error as Error) - } + await this.verifyRpcEndpoint(evmRpcEndpoint) const pair = await this.getDecodedPair({ suri: this.parsedFlags.suri || process.env.POLKADOT_WALLET_SURI, diff --git a/src/commands/create-brick-profile.ts b/src/commands/create-brick-profile.ts index cdfd17f..6cff1d0 100644 --- a/src/commands/create-brick-profile.ts +++ b/src/commands/create-brick-profile.ts @@ -1,8 +1,9 @@ +import { Flags } from '@oclif/core' import type { Struct, u128 } from '@polkadot/types' import { PinkContractPromise, OnChainRegistry, type CertificateData } from '@phala/sdk' import { type KeyringPair } from '@polkadot/keyring/types' -import PhatBaseCommand, { type BrickProfileFactoryContract, type BrickProfileContract } from '../lib/PhatBaseCommand' +import PhatBaseCommand, { type ParsedFlags, type BrickProfileFactoryContract, type BrickProfileContract } from '../lib/PhatBaseCommand' import { bindWaitPRuntimeFinalized } from '../lib/utils' interface PartialAccountQueryResult extends Struct { @@ -19,10 +20,22 @@ export default class CreateBrickProfile extends PhatBaseCommand { } static flags = { - ...PhatBaseCommand.flags + ...PhatBaseCommand.flags, + evmRpcEndpoint: Flags.string({ + description: 'EVM RPC endpoint', + required: false, + }), } public async run(): Promise { + const { evmRpcEndpoint } = this.parsedFlags as ParsedFlags & { + evmRpcEndpoint: string + } + + if (evmRpcEndpoint) { + await this.verifyRpcEndpoint(evmRpcEndpoint) + } + const pair = await this.getDecodedPair({ suri: this.parsedFlags.suri || process.env.POLKADOT_WALLET_SURI, accountFilePath: this.parsedFlags.accountFilePath || process.env.POLKADOT_WALLET_ACCOUNT_FILE, @@ -127,7 +140,7 @@ export default class CreateBrickProfile extends PhatBaseCommand { registry, contract: brickProfile, externalAccountCount, - evmRpcEndpoint: type.isDevelopment || type.isLocal ? 'https://polygon-mumbai.g.alchemy.com/v2/YWlujLKt0nSn5GrgEpGCUA0C_wKV1sVQ' : 'https://polygon-mainnet.g.alchemy.com/v2/W1kyx17tiFQFT2b19mGOqppx90BLHp0a', + evmRpcEndpoint: evmRpcEndpoint || (type.isDevelopment || type.isLocal) ? 'https://polygon-mumbai.g.alchemy.com/v2/YWlujLKt0nSn5GrgEpGCUA0C_wKV1sVQ' : 'https://polygon-mainnet.g.alchemy.com/v2/W1kyx17tiFQFT2b19mGOqppx90BLHp0a', pair, cert }) diff --git a/src/lib/PhatBaseCommand.ts b/src/lib/PhatBaseCommand.ts index d048d26..6e8cf72 100644 --- a/src/lib/PhatBaseCommand.ts +++ b/src/lib/PhatBaseCommand.ts @@ -22,6 +22,7 @@ import { Keyring } from '@polkadot/keyring' import { type KeyringPair } from '@polkadot/keyring/types' import type { Result, Vec, u64, u8, Text, Struct } from '@polkadot/types' import type { AccountId, ChainType, Hash } from '@polkadot/types/interfaces' +import { createPublicClient, http } from 'viem' import { MAX_BUILD_SIZE, @@ -616,4 +617,18 @@ export default abstract class PhatBaseCommand extends BaseCommand { return abi } + async verifyRpcEndpoint(endpoint: string) { + try { + this.action.start(`Verifying the RPC endpoint: ${endpoint}`) + const client = createPublicClient({ + transport: http(endpoint) + }) + await client.getChainId() + this.action.succeed() + } catch (error) { + this.action.fail('Failed to verify the RPC endpoint.') + return this.error(error as Error) + } + } + }