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

Fix/icrc hook #170

Merged
merged 4 commits into from
Sep 29, 2024
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 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
Loading