diff --git a/package.json b/package.json index b032bc5..6e5fb1a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@poppyseed/lastic-sdk", "author": "Lastic (https://lastic.xyz)", - "version": "0.2.17", + "version": "0.2.20", "description": "Typesafe React Hooks abstracting functionality of polkadot.js, created for Lastic website", "homepage": "https://lastic.xyz", "license": "GPL-3.0", @@ -113,6 +113,7 @@ "@changesets/cli": "^2.27.5", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", + "@mimirdev/apps-inject": "^0.3.0", "@mui/material": "^5.15.20", "@polkadot/api": "^11.3.1", "@polkadot/api-contract": "^11.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f0d4f2..2a31d79 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: '@emotion/styled': specifier: ^11.11.5 version: 11.11.5(@emotion/react@11.11.4)(@types/react@18.3.3)(react@18.3.1) + '@mimirdev/apps-inject': + specifier: ^0.3.0 + version: 0.3.0(@polkadot/api@11.3.1)(@polkadot/types@11.3.1)(@polkadot/util@12.6.2) '@mui/material': specifier: ^5.15.20 version: 5.15.20(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) @@ -1263,6 +1266,29 @@ packages: read-yaml-file: 1.1.0 dev: false + /@mimirdev/apps-inject@0.3.0(@polkadot/api@11.3.1)(@polkadot/types@11.3.1)(@polkadot/util@12.6.2): + resolution: {integrity: sha512-dKq715ZCvZm0ckUgvB69Uh90h1O1Fxm8YR2tyCRJvI44WHfXSUf8JkRPFs22ifZlcKGRjmaYvtnZbK+u/PcJpw==} + dependencies: + '@mimirdev/apps-sdk': 0.2.0(@polkadot/api@11.3.1)(@polkadot/types@11.3.1)(@polkadot/util@12.6.2) + transitivePeerDependencies: + - '@polkadot/api' + - '@polkadot/types' + - '@polkadot/util' + dev: false + + /@mimirdev/apps-sdk@0.2.0(@polkadot/api@11.3.1)(@polkadot/types@11.3.1)(@polkadot/util@12.6.2): + resolution: {integrity: sha512-jTAT0t0uUF87dDnrTfg1WQnbG+k8qgvQhr1MX8tZNfJhAtNqua//5R/3467f/YdPXp5gSQMX4yBTGpI+oE6Vdg==} + peerDependencies: + '@polkadot/api': '*' + '@polkadot/types': '*' + '@polkadot/util': '*' + dependencies: + '@polkadot/api': 11.3.1 + '@polkadot/types': 11.3.1 + '@polkadot/util': 12.6.2 + eventemitter3: 5.0.1 + dev: false + /@mui/base@5.0.0-beta.40(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==} engines: {node: '>=12.0.0'} @@ -2513,6 +2539,7 @@ packages: /@substrate/connect@0.8.10: resolution: {integrity: sha512-DIyQ13DDlXqVFnLV+S6/JDgiGowVRRrh18kahieJxhgvzcWicw5eLc6jpfQ0moVVLBYkO7rctB5Wreldwpva8w==} + deprecated: versions below 1.x are no longer maintained requiresBuild: true dependencies: '@substrate/connect-extension-protocol': 2.0.0 @@ -2527,6 +2554,7 @@ packages: /@substrate/connect@0.8.8: resolution: {integrity: sha512-zwaxuNEVI9bGt0rT8PEJiXOyebLIo6QN1SyiAHRPBOl6g3Sy0KKdSN8Jmyn++oXhVRD8aIe75/V8ZkS81T+BPQ==} + deprecated: versions below 1.x are no longer maintained requiresBuild: true dependencies: '@substrate/connect-extension-protocol': 2.0.0 diff --git a/src/hooks/broker/txButton.ts b/src/hooks/broker/txButton.ts index bf25a7f..c94b201 100644 --- a/src/hooks/broker/txButton.ts +++ b/src/hooks/broker/txButton.ts @@ -1,8 +1,7 @@ import { Toast } from '@/types' import { signedTx, unsignedTx } from '@/utils' import { ApiPromise } from '@polkadot/api' -import { InjectedAccount } from '@polkadot/extension-inject/types' -import { Signer } from '@polkadot/types/types' +import { InjectedAccount, InjectedExtension } from '@polkadot/extension-inject/types' import { useState } from 'react' export interface TxButtonProps { @@ -17,7 +16,7 @@ export interface TxButtonProps { } type: 'SIGNED-TX' | 'UNSIGNED-TX' activeAccount: InjectedAccount | undefined - activeSigner: Signer | undefined + activeExtension: InjectedExtension | undefined } interface UseTxButtonResult { @@ -32,7 +31,7 @@ export const useTxButton = ({ attrs, type, activeAccount, - activeSigner, + activeExtension, }: TxButtonProps): UseTxButtonResult => { const [status, setStatus] = useState(null) const [unsub, setUnsub] = useState<(() => void) | null>(null) @@ -51,7 +50,7 @@ export const useTxButton = ({ setStatus('Sending...') // Check if API, account, and signer are present - if (!api || !activeAccount || !activeSigner) { + if (!api || !activeAccount || !activeExtension) { setStatus('Error: API, account or signer not present') return } @@ -59,7 +58,7 @@ export const useTxButton = ({ // Call the appropriate transaction function based on the type try { if (isSigned()) { - await signedTx(api, attrs, setStatus, addToast, setUnsub, activeAccount, activeSigner) + await signedTx(api, attrs, setStatus, addToast, setUnsub, activeAccount, activeExtension) } else if (isUnsigned()) { await unsignedTx(api, attrs, setStatus, addToast, setUnsub) } diff --git a/src/provider.tsx b/src/provider.tsx index a7e05cf..13ce527 100644 --- a/src/provider.tsx +++ b/src/provider.tsx @@ -16,14 +16,15 @@ import { getSubstrateWallet, isWalletInstalled } from '@/wallets' +import { inject, isMimirReady, MIMIR_REGEXP } from '@mimirdev/apps-inject' import { ApiPromise, HttpProvider, WsProvider } from '@polkadot/api' import { ApiOptions } from '@polkadot/api/types' import { InjectedAccount, InjectedExtension, Unsubcall } from '@polkadot/extension-inject/types' import { Signer } from '@polkadot/types/types' import { + createContext, FC, PropsWithChildren, - createContext, useContext, useEffect, useRef, @@ -136,6 +137,16 @@ export const UseInkathonProvider: FC = ({ ...apiOptions, })) + ;(async () => { + // special, apps will working in iframe with mimir wallet + // so, check if the environment in ifram + const origin = await isMimirReady(); + if (origin && MIMIR_REGEXP.test(origin)) { + // inject window.injectedWeb3.mimir + inject() + } + })() + api?.disconnect() setApi(_api) relayApi?.disconnect() @@ -236,6 +247,10 @@ export const UseInkathonProvider: FC = ({ activeExtension.current = extension activeSigner.current = extension?.signer as Signer + extension?.accounts.get().then(accounts => { + updateAccounts(accounts, lastActiveAccountAddress) + }) + // Query & keep listening to injected accounts unsubscribeAccounts.current?.() const unsubscribe = extension?.accounts.subscribe((accounts) => { diff --git a/src/utils/broker_tx.ts b/src/utils/broker_tx.ts index 86fa3a5..019aca1 100644 --- a/src/utils/broker_tx.ts +++ b/src/utils/broker_tx.ts @@ -1,7 +1,7 @@ import { Toast } from '@/types' +import { checkCall } from '@/utils/check' import { ApiPromise, SubmittableResult } from '@polkadot/api' -import { InjectedAccount } from '@polkadot/extension-inject/types' -import { Signer } from '@polkadot/types/types' +import { InjectedAccount, InjectedExtension } from '@polkadot/extension-inject/types' import { Dispatch, SetStateAction } from 'react' import { transformParams } from './broker' import { txErrHandler, txResHandler } from './broker_handler' @@ -28,21 +28,39 @@ const signedTx = async ( addToast: (toast: Omit) => void, setUnsub: Dispatch>, activeAccount: InjectedAccount, - activeSigner: Signer, + activeExtension: InjectedExtension ) => { const address = activeAccount?.address const transformed = transformParams(paramFields, inputParams) - const txExecute = transformed + let txExecute = transformed ? api.tx[palletRpc][callable](...transformed) : api.tx[palletRpc][callable]() - const signerOptions = { - signer: activeSigner, + const isMimir = activeExtension.name === 'mimir' + + if (isMimir) { + const result: any = await activeExtension.signer.signPayload?.({ + address, + genesisHash: api.genesisHash.toHex(), + method: txExecute.method.toHex() + } as unknown as any) + + const method = api.registry.createType('Call', result.payload.method) + + if (!checkCall(api, method, txExecute.method)) { + throw new Error('not safe tx') + } + + txExecute = api.tx[method.section][method.method](...method.args) + + txExecute.addSignature(result.signer, result.signature, result.payload) + } else { + await txExecute.signAsync(address, { signer: activeExtension.signer }) } const unsub = await txExecute - .signAndSend(address, signerOptions, (result: SubmittableResult) => + .send((result: SubmittableResult) => txResHandler(setStatus, api, addToast, result), ) .catch((err: Error) => txErrHandler(setStatus, addToast, err)) diff --git a/src/utils/check.ts b/src/utils/check.ts new file mode 100644 index 0000000..99eee99 --- /dev/null +++ b/src/utils/check.ts @@ -0,0 +1,36 @@ +// Copyright 2023-2024 dev.mimir authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { ApiPromise } from '@polkadot/api' + +import type { Call } from '@polkadot/types/interfaces' +import type { IMethod } from '@polkadot/types/types' + +function findFinalCall (api: ApiPromise, call: Call | IMethod): Call | IMethod { + if (api.tx.multisig?.asMulti?.is(call)) { + const data = call.args[3].toU8a(true) + + return findFinalCall(api, api.registry.createType('Call', data)) + } else if (api.tx.proxy?.proxy?.is(call)) { + const data = call.args[2].toU8a(true) + + return findFinalCall(api, api.registry.createType('Call', data)) + } else { + return call + } +} + +export function checkCall (api: ApiPromise, multisigCall: Call | IMethod, expectCall: Call | IMethod): boolean { + const finalCall = findFinalCall(api, multisigCall) + + if (!finalCall.hash.eq(expectCall.hash)) { + const meta = api.registry.findMetaCall(finalCall.callIndex) + const expectMeta = api.registry.findMetaCall(finalCall.callIndex) + + console.warn(`the final call found in multisigCall is (${meta.section}.${meta.method}), which does not match the expected call(${expectMeta.section}.${expectMeta.method}).`) + + return false + } else { + return true + } +} diff --git a/src/wallets.ts b/src/wallets.ts index a9bdc6d..9d3a785 100644 --- a/src/wallets.ts +++ b/src/wallets.ts @@ -90,10 +90,25 @@ export const nightlyConnect: SubstrateWallet = { 'https://github.com/scio-labs/use-inkathon/raw/main/assets/wallet-logos/nightlyConnect@512w.png', ], } + +export const mimir: SubstrateWallet = { + id: 'mimir', + name: 'Mimir Wallet', + platforms: [ + SubstrateWalletPlatform.Browser + ], + urls: { + website: 'https://mimir.global', + }, + logoUrls: [ + 'https://app.mimir.global/icons/icon@128.png', + 'https://app.mimir.global/icons/icon@512.png' + ], +} /** * Exporting all wallets separately */ -export const allSubstrateWallets: SubstrateWallet[] = [subwallet, talisman, polkadotjs, nova] +export const allSubstrateWallets: SubstrateWallet[] = [subwallet, talisman, polkadotjs, nova, mimir] /** * Returns wallet (if existent) for given identifier (`id` field).