Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Imp user journey #26

Merged
merged 7 commits into from
Dec 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
100 changes: 100 additions & 0 deletions src/commands/add-evm-account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { Flags } from '@oclif/core'
import { getContract } from '@phala/sdk'

import PhatBaseCommand, { type ParsedFlags, type BrickProfileContract } from '../lib/PhatBaseCommand'

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<void> {
const { evmRpcEndpoint } = this.parsedFlags as ParsedFlags & {
evmRpcEndpoint: string
}
pacoyang marked this conversation as resolved.
Show resolved Hide resolved

// Verify the RPC endpoint
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,
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 brickProfile = await getContract({
client: registry,
contractId: brickProfileContractId,
abi: brickProfileAbi,
}) as BrickProfileContract
const { output } = await brickProfile.query.externalAccountCount(cert.address, {
cert,
})
if (output.isErr) {
throw new Error(output.asErr.toString())
}
const externalAccountCount = output.asOk.toNumber()

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 },
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)
}
}
}
215 changes: 215 additions & 0 deletions src/commands/create-brick-profile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
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 ParsedFlags, 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,
evmRpcEndpoint: Flags.string({
description: 'EVM RPC endpoint',
required: false,
}),
}

public async run(): Promise<void> {
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,
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<PartialAccountQueryResult>(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: 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
)
}
)
}
}
Loading
Loading