Skip to content

Commit

Permalink
Fix/icrc hook (#170)
Browse files Browse the repository at this point in the history
  • Loading branch information
rustin01 authored Sep 29, 2024
1 parent a78c473 commit 75b3d6d
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 43 deletions.
1 change: 1 addition & 0 deletions apps/wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"ahooks": "^3.8.0",
"borc": "^3.0.0",
"buffer": "^6.0.3",
"cborg": "^4.2.4",
"class-transformer": "^0.5.1",
"dayjs": "^1.11.11",
"ethers": "^6.13.1",
Expand Down
12 changes: 8 additions & 4 deletions apps/wallet/src/utils/chain/chain-wallets/dfinity/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { IcrcLedgerCanister } from "@dfinity/ledger-icrc";
import { Principal } from "@dfinity/principal";
import { Icrc49CallCanisterRequest, Icrc49CallCanisterResult, IcrcErrorCode, JsonRpcResponseError, JsonRpcResponseSuccess } from "./types";
import { buildJsonRpcError, buildJsonRpcResponse } from "./utils";
// @ts-ignore
import cbor from 'borc';
import * as cbor from 'cborg';
import { RUNTIME_ICRC_DEV, RUNTIME_ICRC_HOST } from "../../../runtime";

const ICP_LEDGER_CANISTER_ID = Principal.fromText('ryjl3-tyaaa-aaaaa-aaaba-cai');

Expand Down Expand Up @@ -147,8 +147,11 @@ export class DfinityChainWallet extends BaseChainWallet {
callSync: true,
}
)
if (response.response.status > 202) {
throw new Error(`ICRC49 call failed with http status ${response.response.status}`)
}
return buildJsonRpcResponse(request.id, {
contentMap: cbor.encode(response.requestDetails),
contentMap: Buffer.from(cbor.encode(response.requestDetails)).toString('base64'),
certificate: Buffer.from(response.response.body?.certificate!).toString('base64'),
})
} catch (e: any) {
Expand All @@ -160,7 +163,8 @@ export class DfinityChainWallet extends BaseChainWallet {
this.identity = Secp256k1KeyIdentity.fromSeedPhrase(phrase)
this.agent = await createAgent({
identity: this.identity,
host: 'https://ic0.app',
host: RUNTIME_ICRC_HOST || 'https://ic0.app',
fetchRootKey: RUNTIME_ICRC_DEV,
})
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { useEffect, useRef } from "react"
import { useCallback, useEffect, useRef } from "react"
import { Icrc25PermissionsResult, Icrc25RequestPermissionsRequest, Icrc25RequestPermissionsResult, Icrc25SupportedStandardsResult, Icrc27AccountsResult, Icrc29StatusResult, Icrc32SignChallengeRequest, Icrc32SignChallengeResult, Icrc49CallCanisterRequest, IcrcErrorCode, IcrcErrorCodeMessages, IcrcMethods, IcrcPermissionState, JsonRpcRequest } from "./types"
import { buildJsonRpcError, buildJsonRpcResponse, getIcrc29Session, NEED_PERMISSION_METHODS, parseJsonRpcRequest, setIcrc29Session, SUPPORTED_STANDARDS } from "./utils"
import hibitIdSession from "../../../../stores/session"
import { Dfinity } from "../../chain-list"
import { DfinityChainWallet } from "."
import { RUNTIME_ENV } from "../../../runtime"
import { RuntimeEnv } from "../../../basicEnums"
import { BridgePromise } from "@delandlabs/hibit-id-sdk"
import { ChainWalletPool } from ".."

const ICRC_CHAIN_ID = Dfinity.chainId

export const useDfinityIcrcPostMessageTransport = (isReady: boolean) => {
const handleMessageRef = useRef<(event: MessageEvent) => Promise<void>>(async (event) => {
const walletPoolRef = useRef<BridgePromise<ChainWalletPool>>(new BridgePromise())

const handleMessage = useCallback(async (event: MessageEvent) => {
let request: JsonRpcRequest | null = null
try {
request = parseJsonRpcRequest(event.data)
Expand All @@ -25,9 +29,9 @@ export const useDfinityIcrcPostMessageTransport = (isReady: boolean) => {
console.debug('ICRC29 not ready yet, ignored')
return
}
window.postMessage(
event.source?.postMessage(
buildJsonRpcResponse(request.id, 'ready' as Icrc29StatusResult),
event.origin,
{ targetOrigin: event.origin },
)
setIcrc29Session(event.origin, {})
return
Expand All @@ -47,7 +51,7 @@ export const useDfinityIcrcPostMessageTransport = (isReady: boolean) => {
currentPermissions[scope.method] = IcrcPermissionState.GRANTED
}
})
window.postMessage(
event.source?.postMessage(
buildJsonRpcResponse<Icrc25RequestPermissionsResult>(request.id, {
scopes: Object.keys(currentPermissions).map((method) => ({
scope: {
Expand All @@ -56,15 +60,15 @@ export const useDfinityIcrcPostMessageTransport = (isReady: boolean) => {
state: currentPermissions[method as IcrcMethods] ?? IcrcPermissionState.DENIED,
}))
}),
event.origin,
{ targetOrigin: event.origin },
)
setIcrc29Session(event.origin, currentPermissions)
return
}

if (request.method === IcrcMethods.ICRC25_PERMISSIONS) {
const currentPermissions = { ...session.permissions }
window.postMessage(
event.source?.postMessage(
buildJsonRpcResponse<Icrc25PermissionsResult>(request.id, {
scopes: Object.keys(currentPermissions).map((method) => ({
scope: {
Expand All @@ -73,131 +77,139 @@ export const useDfinityIcrcPostMessageTransport = (isReady: boolean) => {
state: currentPermissions[method as IcrcMethods] ?? IcrcPermissionState.DENIED,
}))
}),
event.origin,
{ targetOrigin: event.origin },
)
return
}

if (request.method === IcrcMethods.ICRC25_SUPPORTED_STANDARDS) {
window.postMessage(
event.source?.postMessage(
buildJsonRpcResponse<Icrc25SupportedStandardsResult>(request.id, {
supportedStandards: SUPPORTED_STANDARDS
}),
event.origin,
{ targetOrigin: event.origin },
)
return
}

if (request.method === IcrcMethods.ICRC27_ACCOUNTS) {
if (!hibitIdSession.walletPool || session.permissions[IcrcMethods.ICRC27_ACCOUNTS] !== IcrcPermissionState.GRANTED) {
window.postMessage(
if (session.permissions[IcrcMethods.ICRC27_ACCOUNTS] !== IcrcPermissionState.GRANTED) {
event.source?.postMessage(
buildJsonRpcError(request.id, IcrcErrorCode.PermissionNotGranted, IcrcErrorCodeMessages[IcrcErrorCode.PermissionNotGranted]),
event.origin,
{ targetOrigin: event.origin },
)
return
}
try {
const account = await hibitIdSession.walletPool.getAccount(ICRC_CHAIN_ID)
window.postMessage(
const walletPool = await walletPoolRef.current.promise
const account = await walletPool.getAccount(ICRC_CHAIN_ID)
event.source?.postMessage(
buildJsonRpcResponse<Icrc27AccountsResult>(request.id, {
accounts: [
{
owner: account.address,
}
],
}),
event.origin,
{ targetOrigin: event.origin },
)
} catch (e: any) {
window.postMessage(
event.source?.postMessage(
buildJsonRpcError(
request.id,
IcrcErrorCode.GenericError,
IcrcErrorCodeMessages[IcrcErrorCode.GenericError],
e.message ?? JSON.stringify(e),
),
event.origin,
{ targetOrigin: event.origin },
)
}
return
}

if (request.method === IcrcMethods.ICRC32_SIGN_CHALLENGE) {
if (!hibitIdSession.walletPool || session.permissions[IcrcMethods.ICRC32_SIGN_CHALLENGE] !== IcrcPermissionState.GRANTED) {
window.postMessage(
if (session.permissions[IcrcMethods.ICRC32_SIGN_CHALLENGE] !== IcrcPermissionState.GRANTED) {
event.source?.postMessage(
buildJsonRpcError(request.id, IcrcErrorCode.PermissionNotGranted, IcrcErrorCodeMessages[IcrcErrorCode.PermissionNotGranted]),
event.origin,
{ targetOrigin: event.origin },
)
return
}
try {
const account = await hibitIdSession.walletPool.getAccount(ICRC_CHAIN_ID)
const walletPool = await walletPoolRef.current.promise
const account = await walletPool.getAccount(ICRC_CHAIN_ID)
const req = request as Icrc32SignChallengeRequest
if (account.address !== req.params.principal) {
window.postMessage(
event.source?.postMessage(
buildJsonRpcError(request.id, IcrcErrorCode.GenericError, IcrcErrorCodeMessages[IcrcErrorCode.GenericError], 'Principal does not match account'),
event.origin,
{ targetOrigin: event.origin },
)
return
}
const signature = await hibitIdSession.walletPool.signMessage(req.params.challenge, ICRC_CHAIN_ID)
window.postMessage(
const signature = await walletPool.signMessage(req.params.challenge, ICRC_CHAIN_ID)
event.source?.postMessage(
buildJsonRpcResponse<Icrc32SignChallengeResult>(request.id, {
publicKey: account.publicKey!,
signature,
}),
event.origin,
{ targetOrigin: event.origin },
)
} catch (e: any) {
window.postMessage(
event.source?.postMessage(
buildJsonRpcError(
request.id,
IcrcErrorCode.GenericError,
IcrcErrorCodeMessages[IcrcErrorCode.GenericError],
e.message ?? JSON.stringify(e),
),
event.origin,
{ targetOrigin: event.origin },
)
}
return
}

if (request.method === IcrcMethods.ICRC49_CALL_CANISTER) {
if (!hibitIdSession.walletPool || session.permissions[IcrcMethods.ICRC49_CALL_CANISTER] !== IcrcPermissionState.GRANTED) {
window.postMessage(
if (session.permissions[IcrcMethods.ICRC49_CALL_CANISTER] !== IcrcPermissionState.GRANTED) {
event.source?.postMessage(
buildJsonRpcError(request.id, IcrcErrorCode.PermissionNotGranted, IcrcErrorCodeMessages[IcrcErrorCode.PermissionNotGranted]),
event.origin,
{ targetOrigin: event.origin },
)
return
}
try {
const wallet = hibitIdSession.walletPool.get(ICRC_CHAIN_ID) as DfinityChainWallet
const walletPool = await walletPoolRef.current.promise
const wallet = walletPool.get(ICRC_CHAIN_ID) as DfinityChainWallet
const req = request as Icrc49CallCanisterRequest
const res = await wallet.Icrc49CallCanister(req)
window.postMessage(res, event.origin)
event.source?.postMessage(res, { targetOrigin: event.origin })
} catch (e: any) {
window.postMessage(
event.source?.postMessage(
buildJsonRpcError(
request.id,
IcrcErrorCode.GenericError,
IcrcErrorCodeMessages[IcrcErrorCode.GenericError],
e.message ?? JSON.stringify(e),
),
event.origin,
{ targetOrigin: event.origin },
)
}
return
}
})
}, [isReady])

useEffect(() => {
if (hibitIdSession.walletPool) {
walletPoolRef.current.resolve(hibitIdSession.walletPool)
}
}, [hibitIdSession.walletPool])

useEffect(() => {
if (RUNTIME_ENV !== RuntimeEnv.ICRC_POSTMESSAGE) {
return
}
const handleMessage = handleMessageRef.current
window.addEventListener("message", handleMessage)
return () => {
window.removeEventListener("message", handleMessage)
}
}, [])
}, [handleMessage])
}
1 change: 1 addition & 0 deletions apps/wallet/src/utils/chain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const SupportedChainsForTestnet = [
TonTestnet,
// TronNile,
// TODO: Dfinity testnet
Dfinity,
];

export function getChainByChainId(chainId: ChainId | null, devMode?: boolean): ChainInfo | null {
Expand Down
19 changes: 19 additions & 0 deletions apps/wallet/src/utils/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { Dfinity } from "./chain/chain-list";
export const IS_IN_IFRAME = window.top !== window.self;

let runtimeEnv: RuntimeEnv = RuntimeEnv.SDK;
let runtimeIcrcHost: string | undefined = undefined;
let runtimeIcrcDev: boolean = false;
let runtimeParamsRaw: string | undefined = undefined;
let runtimeParams: unknown = undefined;
let runtimeSupportedChainIds: ChainId[] = [];
Expand All @@ -28,10 +30,23 @@ try {
}
} catch (e) { /* empty */ }

// Dfinity ICRC PostMessage
if (urlParams.has('is_icrc') || sessionStorage.getItem('is_icrc')) {
runtimeEnv = RuntimeEnv.ICRC_POSTMESSAGE
runtimeSupportedChainIds = [Dfinity.chainId]
sessionStorage.setItem('is_icrc', '1')
if (urlParams.get('icrc_host') || sessionStorage.getItem('icrc_host')) {
runtimeIcrcHost = urlParams.get('icrc_host') || sessionStorage.getItem('icrc_host') || undefined
if (runtimeIcrcHost) {
sessionStorage.setItem('icrc_host', runtimeIcrcHost)
}
}
if (urlParams.get('icrc_dev') || sessionStorage.getItem('icrc_dev')) {
runtimeIcrcDev = !!(urlParams.get('icrc_dev') || sessionStorage.getItem('icrc_dev'))
if (runtimeIcrcDev) {
sessionStorage.setItem('icrc_dev', '1')
}
}
}

urlParams.get('chains')?.split(',').forEach((idStr) => {
Expand All @@ -44,12 +59,16 @@ runtimeLang = urlParams.get('lang') as Language || undefined

export const IS_TELEGRAM_MINI_APP = isTelegramMiniApp;
export const RUNTIME_ENV = runtimeEnv
export const RUNTIME_ICRC_HOST = runtimeIcrcHost
export const RUNTIME_ICRC_DEV = runtimeIcrcDev
export const RUNTIME_PARAMS_RAW = runtimeParamsRaw
export const RUNTIME_PARAMS = runtimeParams
export const RUNTIME_SUPPORTED_CHAIN_IDS = runtimeSupportedChainIds
export const RUNTIME_LANG = runtimeLang

console.debug('[runtime env]', RUNTIME_ENV)
console.debug('[runtime icrc host]', RUNTIME_ICRC_HOST)
console.debug('[runtime icrc dev]', RUNTIME_ICRC_DEV)
console.debug('[runtime params]', RUNTIME_PARAMS)
console.debug('[runtime supported chains]', RUNTIME_SUPPORTED_CHAIN_IDS)
console.debug('[runtime lang]', RUNTIME_LANG)
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1904,6 +1904,11 @@ caniuse-lite@^1.0.30001646:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001659.tgz#f370c311ffbc19c4965d8ec0064a3625c8aaa7af"
integrity sha512-Qxxyfv3RdHAfJcXelgf0hU4DFUVXBGTjqrBUZLUh8AtlGnsDo+CnncYtTd95+ZKfnANUOzxyIQCuU/UeBZBYoA==

cborg@^4.2.4:
version "4.2.4"
resolved "https://registry.npmmirror.com/cborg/-/cborg-4.2.4.tgz#33f5c18bda7cae33fb0c7e84d329bce2e51e1789"
integrity sha512-ns2xY95zViHIVy4lq+qdLmfXTpnT3XjmKradz4RJxxbr5jc/A5gS5FiFLcPGhSdHVlSeeoizT1fuKdI1Kcd6oA==

chai@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.1.tgz#f035d9792a22b481ead1c65908d14bb62ec1c82c"
Expand Down

0 comments on commit 75b3d6d

Please sign in to comment.