Skip to content

Commit

Permalink
Merge pull request #26 from Phala-Network/imp-user-journey
Browse files Browse the repository at this point in the history
Imp user journey
  • Loading branch information
Leechael authored Dec 23, 2023
2 parents 4a31034 + 376c06a commit fb0419d
Show file tree
Hide file tree
Showing 9 changed files with 535 additions and 8 deletions.
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
}

// 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

0 comments on commit fb0419d

Please sign in to comment.