From 6107210308983ff0f45abf5395d6d7780ec72682 Mon Sep 17 00:00:00 2001 From: Kami Date: Sat, 22 Jun 2024 01:44:21 +0330 Subject: [PATCH] add export account exported json --- packages/snap/{note => .note} | 0 packages/snap/package.json | 2 +- packages/snap/snap.manifest.json | 8 +- packages/snap/src/index.ts | 67 ++++++++---- packages/snap/src/rpc/metadata.ts | 7 +- packages/snap/src/ui/accountDemo.ts | 17 ++- packages/snap/src/ui/accountInfo.tsx | 27 +++-- packages/snap/src/ui/dappList.ts | 12 +- packages/snap/src/ui/exportAccount.ts | 86 +++++++++++++++ packages/snap/src/ui/index.ts | 6 + packages/snap/src/ui/transfer.ts | 127 ++++++++++++---------- packages/snap/src/util/amountToHuman.ts | 4 +- packages/snap/src/util/amountToMachine.ts | 8 +- packages/snap/src/util/getCurrentChain.ts | 7 +- packages/snap/src/util/getJsonKeyPair.ts | 6 + packages/snap/src/util/getKeyPair.ts | 7 +- packages/snap/src/util/index.ts | 4 + 17 files changed, 275 insertions(+), 120 deletions(-) rename packages/snap/{note => .note} (100%) create mode 100644 packages/snap/src/ui/exportAccount.ts create mode 100644 packages/snap/src/ui/index.ts create mode 100644 packages/snap/src/util/getJsonKeyPair.ts create mode 100644 packages/snap/src/util/index.ts diff --git a/packages/snap/note b/packages/snap/.note similarity index 100% rename from packages/snap/note rename to packages/snap/.note diff --git a/packages/snap/package.json b/packages/snap/package.json index 52f6a01..e62ac26 100644 --- a/packages/snap/package.json +++ b/packages/snap/package.json @@ -1,6 +1,6 @@ { "name": "@polkagate/snap", - "version": "0.2.3", + "version": "0.3.0", "description": "A MetaMask Snap to interact with Polkadot ecosystem, a platform for cross-chain communication and scalability. Use your MetaMask wallet to access Polkadot dApps and tokens. No extra extension needed.", "repository": { "type": "git", diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index d499d8f..1286eec 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "0.2.3", + "version": "0.3.0", "description": "Explore Polkadot decentralized applications and manage your tokens using your MetaMask wallet. Start your journey at apps.polkagate.xyz.", "proposedName": "PolkaGate", "repository": { @@ -7,7 +7,7 @@ "url": "https://github.com/polkagate/snap.git" }, "source": { - "shasum": "QJbEjZQBTNNCpfJ/dXrflv8lBho8Z1ZteFOIhJOD5js=", + "shasum": "MCRX17YL353EhPXMmobvxgBNDRStBIA0ATAin1JvPxM=", "location": { "npm": { "filePath": "dist/bundle.js", @@ -17,6 +17,10 @@ } } }, + "initialConnections": { + "https://apps.polkagate.xyz": {}, + "https://staking.polkadot.network": {} + }, "initialPermissions": { "endowment:network-access": {}, "endowment:page-home": {}, diff --git a/packages/snap/src/index.ts b/packages/snap/src/index.ts index c263076..f08d461 100644 --- a/packages/snap/src/index.ts +++ b/packages/snap/src/index.ts @@ -14,15 +14,25 @@ import type { import { getGenesisHash } from './chains'; import { DEFAULT_CHAIN_NAME } from './defaults'; -import { getAddress, signJSON, signRaw } from './rpc'; -import { getMetadataList, setMetadata, updateState } from './rpc/metadata'; -import { accountDemo } from './ui/accountDemo'; -import { accountInfo } from './ui/accountInfo'; -import { showDappList } from './ui/dappList'; -import { showSpinner, showTransferInputs, transfer } from './ui/transfer'; -import { getBalances2 } from './util/getBalance'; -import { getCurrentChain } from './util/getCurrentChain'; -import { getKeyPair } from './util/getKeyPair'; +import { + getMetadataList, + setMetadata, + updateState, + getAddress, + signJSON, + signRaw, +} from './rpc'; +import { + showSpinner, + accountDemo, + accountInfo, + showDappList, + transferReview, + exportAccount, + showJsonContent, + getNextChain, +} from './ui'; +import { getBalances2, getCurrentChain, getKeyPair } from './util'; export const onRpcRequest: OnRpcRequestHandler = async ({ origin, @@ -81,7 +91,7 @@ export const onInstall: OnInstallHandler = async () => { heading('🏠 Your account is now created 🚀'), divider(), text( - "To access your account's information, navigate to Menu → Snaps and click on the Polkagate icon.", + "To access your account's information, navigate to **Menu → Snaps** and click on the Polkagate icon.", ), text( 'To manage your account, please visit: **[https://apps.polkagate.xyz](https://apps.polkagate.xyz)**', @@ -94,38 +104,49 @@ export const onInstall: OnInstallHandler = async () => { export const onUserInput: OnUserInputHandler = async ({ id, event }) => { if (event.type === UserInputEventType.ButtonClickEvent) { switch (event.name) { - case 'switchChain': + case 'switchChain': { await showSpinner(id, 'Switching chain ...'); - await accountInfo(id); - break; - - case 'transfer': - await showTransferInputs(id); + const nextChainName = await getNextChain(); + await accountInfo(id, nextChainName); break; + } case 'dapp': await showDappList(id); break; + case 'showExportAccount': + await exportAccount(id); + break; + + case 'backToHome': + await showSpinner(id, 'Loading ...'); + await accountInfo(id); + break; + default: break; } } if (event.type === UserInputEventType.FormSubmitEvent) { - const chainName = await getCurrentChain(); - - const value = { ...(event?.value || {}), chainName } as unknown as Record< - string, - string - >; + const { value } = event; switch (event.name) { case 'transferInput': + if (!event?.value?.amount) { + break; + } await showSpinner(id); - await transfer(id, value); + await transferReview(id, value); break; + case 'saveExportedAccount': { + await showSpinner(id, 'Exporting the account ...'); + + await showJsonContent(id, value?.password); + break; + } default: break; } diff --git a/packages/snap/src/rpc/metadata.ts b/packages/snap/src/rpc/metadata.ts index 9f9557a..961efbc 100644 --- a/packages/snap/src/rpc/metadata.ts +++ b/packages/snap/src/rpc/metadata.ts @@ -1,9 +1,10 @@ +import { divider, heading, panel, text } from '@metamask/snaps-sdk'; +import type { ApiPromise } from '@polkadot/api'; import type { InjectedMetadataKnown, MetadataDef, } from '@polkadot/extension-inject/types'; -import { divider, heading, panel, text } from '@metamask/snaps-sdk'; -import type { ApiPromise } from '@polkadot/api'; + import getChainInfo from '../util/getChainInfo'; import { rand } from '../util/rand'; @@ -112,6 +113,6 @@ export const checkAndUpdateMetaData = async (api: ApiPromise) => { const metaData = await getChainInfo(api); if (metaData) { selfOrigin = `Polkagate-${rand()}`; - setMetadata(selfOrigin, metaData); + await setMetadata(selfOrigin, metaData); } }; diff --git a/packages/snap/src/ui/accountDemo.ts b/packages/snap/src/ui/accountDemo.ts index e4d5d30..2d326a9 100644 --- a/packages/snap/src/ui/accountDemo.ts +++ b/packages/snap/src/ui/accountDemo.ts @@ -31,13 +31,13 @@ export const accountDemo = ( row('Transferable', text(`**${transferable.toHuman()}**`)), row('Locked', text(`**${locked.toHuman()}**`)), divider(), + // button({ + // value: 'Transfer fund', + // name: 'transfer', + // }), button({ - value: 'Transfer fund', - name: 'transfer', - }), - button({ - variant: 'secondary', - value: 'View App list', + variant: 'primary', + value: 'View dApp list', name: 'dapp', }), button({ @@ -45,6 +45,11 @@ export const accountDemo = ( value: 'Click to switch chain', name: 'switchChain', }), + button({ + variant: 'secondary', + value: 'Export account', + name: 'showExportAccount', + }), ]), ]); }; diff --git a/packages/snap/src/ui/accountInfo.tsx b/packages/snap/src/ui/accountInfo.tsx index e75f7e5..8663b5b 100644 --- a/packages/snap/src/ui/accountInfo.tsx +++ b/packages/snap/src/ui/accountInfo.tsx @@ -1,18 +1,19 @@ -/* eslint-disable no-case-declarations */ -/* eslint-disable jsdoc/require-jsdoc */ - +import { accountDemo } from './accountDemo'; import { getGenesisHash } from '../chains'; -import { getKeyPair } from '../util/getKeyPair'; -import { getBalances2 } from '../util/getBalance'; -import { getState, updateState } from '../rpc'; import { DEFAULT_CHAIN_NAME, CHAIN_NAMES } from '../defaults'; -import { accountDemo } from './accountDemo'; +import { getState, updateState } from '../rpc'; +import { getCurrentChain } from '../util'; +import { getBalances2 } from '../util/getBalance'; +import { getKeyPair } from '../util/getKeyPair'; +/** + * Returns the next chain in a circular way. + */ export async function getNextChain() { const state = await getState(); console.log('state in getNextChain', state); - const currentChainName = (state?.currentChain || + const currentChainName = (state?.currentChain ?? DEFAULT_CHAIN_NAME) as string; const index = CHAIN_NAMES.findIndex((name) => name === currentChainName); @@ -25,7 +26,7 @@ export async function getNextChain() { } // eslint-disable-next-line @typescript-eslint/prefer-optional-chain - (state || {}).currentChain = nextChainName; + (state ?? {}).currentChain = nextChainName; console.log('update state in getNextChain', state); await updateState(state); @@ -33,8 +34,14 @@ export async function getNextChain() { return nextChainName; } +/** + * Show account info on the current chain. + * + * @param id - The id of current UI interface. + * @param chainName - Current chain name. + */ export async function accountInfo(id: string, chainName?: string) { - const _chainName = chainName || (await getNextChain()); + const _chainName = chainName ?? (await getCurrentChain()); const { address } = await getKeyPair(_chainName); diff --git a/packages/snap/src/ui/dappList.ts b/packages/snap/src/ui/dappList.ts index 9877dc7..d3c2175 100644 --- a/packages/snap/src/ui/dappList.ts +++ b/packages/snap/src/ui/dappList.ts @@ -1,4 +1,4 @@ -import { heading, panel, text, divider } from '@metamask/snaps-sdk'; +import { button, heading, panel, text, divider } from '@metamask/snaps-sdk'; /** * This shows a dapp list to users. @@ -11,16 +11,22 @@ export async function showDappList(id: string) { params: { id, ui: panel([ - heading('App List'), + heading('dApp List'), divider(), text( - 'Explore these Apps to streamline your daily tasks and engage with the Polkadot ecosystem', + 'Explore these dApps to streamline your daily tasks and engage with the Polkadot ecosystem.', ), text('General : **[apps.polkagate.xyz](https://apps.polkagate.xyz)**'), text( 'Staking : **[staking.polkadot.network](https://staking.polkadot.network/)**', ), text('Governance : **Coming Soon ...**'), + divider(), + button({ + variant: 'primary', + value: 'Back', + name: 'backToHome', + }), ]), }, }); diff --git a/packages/snap/src/ui/exportAccount.ts b/packages/snap/src/ui/exportAccount.ts new file mode 100644 index 0000000..fdf3398 --- /dev/null +++ b/packages/snap/src/ui/exportAccount.ts @@ -0,0 +1,86 @@ +import { + button, + heading, + panel, + text, + divider, + input, + form, + copyable, +} from '@metamask/snaps-sdk'; + +import { getJsonKeyPair } from '../util'; + +/** + * This will show the alert to get password to export account as JSON file. + * + * @param id - The id of UI interface to be updated. + */ +export async function exportAccount(id: string) { + await snap.request({ + method: 'snap_updateInterface', + params: { + id, + ui: panel([ + heading('Export Account'), + divider(), + text( + 'Here, you can export your account as a JSON file, which can be used to import your account in another extension or wallet.'), + form({ + name: 'saveExportedAccount', + children: [ + input({ + inputType: 'password', + label: 'Enter a password to encrypt your export data:', + name: 'password', + placeholder: 'password ...', + }), + button({ + variant: 'primary', + value: 'Export', + name: 'exportAccountBtn', + }), + button({ + variant: 'secondary', + value: 'Back', + name: 'backToHome', + }), + ], + }), + ]), + }, + }); +} + +/** + * This will show the exported account content that can be copied in a file. + * + * @param id - The id of UI interface to be updated. + * @param password - The password to encode the content. + */ +export async function showJsonContent(id: string, password: string | null) { + if (!password) { + return; + } + const json = await getJsonKeyPair(password); + + await snap.request({ + method: 'snap_updateInterface', + params: { + id, + ui: panel([ + heading('Export Account'), + divider(), + text( + 'Copy and save the following content in a (.json) file. This file can be imported later in extensions and wallets.', + ), + copyable(json), + button({ + variant: 'secondary', + value: 'Back', + name: 'backToHome', + }), + ]), + }, + }); +} diff --git a/packages/snap/src/ui/index.ts b/packages/snap/src/ui/index.ts new file mode 100644 index 0000000..8f31c99 --- /dev/null +++ b/packages/snap/src/ui/index.ts @@ -0,0 +1,6 @@ +export * from './accountDemo'; +export * from './accountInfo'; +export * from './dappList'; +export * from './exportAccount'; +export * from './showConfirmTx'; +export * from './transfer'; diff --git a/packages/snap/src/ui/transfer.ts b/packages/snap/src/ui/transfer.ts index 9833130..8f67ca7 100644 --- a/packages/snap/src/ui/transfer.ts +++ b/packages/snap/src/ui/transfer.ts @@ -1,5 +1,3 @@ -/* eslint-disable no-case-declarations */ -/* eslint-disable jsdoc/require-jsdoc */ import { ButtonType, button, @@ -14,35 +12,25 @@ import { copyable, } from '@metamask/snaps-sdk'; -import { Balance } from '@polkadot/types/interfaces'; -import { ISubmittableResult } from '@polkadot/types/types'; -import { SubmittableExtrinsic } from '@polkadot/api/types'; -import { KeyringPair } from '@polkadot/keyring/types'; import { getGenesisHash } from '../chains'; -import { getApi } from '../util/getApi'; -import { getKeyPair } from '../util/getKeyPair'; import { amountToMachine } from '../util/amountToMachine'; -import { getCurrentChain } from '../util/getCurrentChain'; import { formatCamelCase } from '../util/formatCamelCase'; +import { getApi } from '../util/getApi'; +import { getCurrentChain } from '../util/getCurrentChain'; +import { getKeyPair } from '../util/getKeyPair'; -type Inputs = { - partialFee: Balance; - call: SubmittableExtrinsic<'promise', ISubmittableResult>; - amount: string; - recipient: string; - chainName: string; - token: string; - keyPair: KeyringPair; -}; - +/** + * Run the transfer extrinsics and then show the result page. + * + * @param id - The id of interface. + * @param values - The parameters of the transaction. + */ +// TODO: can not send params from review page to transfer since review page is not a form, this will be resolved when new snap JSX components will be available export async function transfer(id: string, values: Record) { - console.log('transferring inputs ?'); - const { amount, recipient, chainName } = values; const genesisHash = getGenesisHash(chainName); const api = await getApi(genesisHash); const decimal = api.registry.chainDecimals[0]; - const token = api.registry.chainTokens[0]; const amountAsBN = amountToMachine(amount, decimal); @@ -52,18 +40,6 @@ export async function transfer(id: string, values: Record) { const call = api.tx.balances.transferKeepAlive(...params); - const { partialFee } = await call.paymentInfo(keyPair.address); - - // const inputs: Inputs = { - // partialFee, - // call, - // ...values, - // token, - // keyPair, - // }; - - // showConfirm(id, inputs); - const txHash = await call.signAndSend(keyPair); const result = { @@ -72,25 +48,14 @@ export async function transfer(id: string, values: Record) { txHash: String(txHash), }; - showResult(id, result); + await showResult(id, result); } -// export async function transfer(id: string, values: Record) { -// console.log('transfer fund ...', values); - -// // const { amount, recipient, call } = values; - -// // const txHash = await call.signAndSend(keyPair); - -// // const result = { -// // address: keyPair.address, -// // chainName: values.chainName, -// // txHash: String(txHash), -// // }; - -// // showResult(id, result); -// } - +/** + * Show amount and recipient input boxes. + * + * @param id - The id of interface. + */ export async function showTransferInputs(id: string) { const currentChainName = await getCurrentChain(); @@ -120,7 +85,11 @@ export async function showTransferInputs(id: string) { variant: 'primary', value: 'Confirm', name: 'transferReview', - buttonType: 'submit', + }), + button({ + variant: 'secondary', + value: 'Back', + name: 'backToHome', }), ], }), @@ -129,8 +98,31 @@ export async function showTransferInputs(id: string) { }); } -export async function showConfirm(id: string, inputs: Inputs) { - const { amount, recipient, chainName, partialFee, call, token } = inputs; +/** + * Show the details of the transaction before submitting. + * @param id - The id of interface. + * @param values - The transaction parameters. + */ +export async function transferReview( + id: string, + values: Record, +) { + const { amount, recipient } = values; + const chainName = await getCurrentChain(); + const genesisHash = getGenesisHash(chainName); + const api = await getApi(genesisHash); + const decimal = api.registry.chainDecimals[0]; + const token = api.registry.chainTokens[0]; + + const amountAsBN = amountToMachine(amount, decimal); + + const params = [recipient, amountAsBN]; + + const keyPair = await getKeyPair(genesisHash); + + const call = api.tx.balances.transferKeepAlive(...params); + + const { partialFee } = await call.paymentInfo(keyPair.address); await snap.request({ method: 'snap_updateInterface', @@ -139,21 +131,32 @@ export async function showConfirm(id: string, inputs: Inputs) { ui: panel([ heading('Transaction Review!'), divider(), - row('Chain Name:', text(`**${formatCamelCase(chainName)}**`)), + row('Chain Name:', text(`**${formatCamelCase(chainName) ?? ''}**`)), row('Amount:', text(`**${amount} ${token}** `)), text('Recipient'), copyable(recipient), row('Estimated Fee:', text(`**${partialFee.toHuman()}**`)), divider(), - form({ - name: 'transferConfirm', - children: [button('Confirm', ButtonType.Submit, 'submit')], + button({ + variant: 'primary', + value: 'Confirm', + name: 'transferReview', + }), + button({ + variant: 'secondary', + value: 'Back', + name: 'backToHome', }), ]), }, }); } +/** + * + * @param id + * @param result + */ export async function showResult(id: string, result: Record) { const { address, txHash, chainName } = result; @@ -185,6 +188,12 @@ export async function showResult(id: string, result: Record) { }); } +/** + * Show an spinner while processing. + * + * @param id - The id of interface. + * @param title - The title to show while spinning. + */ export async function showSpinner(id: string, title?: string) { await snap.request({ method: 'snap_updateInterface', @@ -193,7 +202,7 @@ export async function showSpinner(id: string, title?: string) { ui: panel([ heading('Processing'), divider(), - text(title || 'We are working on your transaction, Please wait ...'), + text(title ?? 'We are working on your transaction, Please wait ...'), spinner(), ]), }, diff --git a/packages/snap/src/util/amountToHuman.ts b/packages/snap/src/util/amountToHuman.ts index 62b7893..6f218b9 100644 --- a/packages/snap/src/util/amountToHuman.ts +++ b/packages/snap/src/util/amountToHuman.ts @@ -1,5 +1,5 @@ -import { Compact, u128 } from '@polkadot/types'; -import { BN } from '@polkadot/util'; +import type { Compact, u128 } from '@polkadot/types'; +import type { BN } from '@polkadot/util'; const FLOATING_POINT_DIGIT = 4; diff --git a/packages/snap/src/util/amountToMachine.ts b/packages/snap/src/util/amountToMachine.ts index 1fa0075..880aadf 100644 --- a/packages/snap/src/util/amountToMachine.ts +++ b/packages/snap/src/util/amountToMachine.ts @@ -1,12 +1,14 @@ import { BN, BN_TEN, BN_ZERO } from '@polkadot/util'; /** + * Convert an amount to BN. * - * @param amount - * @param decimal + * @param amount - The amount to be converted to BN. + * @param decimal - The chain decimal. + * @returns The Bn equivalent of the amount. */ export function amountToMachine( - amount: string | undefined, + amount: string | undefined | null, decimal: number | undefined, ): BN { if (!amount || !Number(amount) || !decimal) { diff --git a/packages/snap/src/util/getCurrentChain.ts b/packages/snap/src/util/getCurrentChain.ts index c5d1ae6..fdff226 100644 --- a/packages/snap/src/util/getCurrentChain.ts +++ b/packages/snap/src/util/getCurrentChain.ts @@ -1,16 +1,13 @@ -import { getState } from '../rpc'; import { DEFAULT_CHAIN_NAME } from '../defaults'; +import { getState } from '../rpc'; /** * To get the current chain name saved in snap state. */ export async function getCurrentChain(): Promise { - console.log('getCurrentChain '); - const state = await getState(); - console.log('state: ', state); - const currentChainName = (state?.currentChain || + const currentChainName = (state?.currentChain ?? DEFAULT_CHAIN_NAME) as string; return currentChainName; diff --git a/packages/snap/src/util/getJsonKeyPair.ts b/packages/snap/src/util/getJsonKeyPair.ts new file mode 100644 index 0000000..5b3be54 --- /dev/null +++ b/packages/snap/src/util/getJsonKeyPair.ts @@ -0,0 +1,6 @@ +import { getKeyPair } from '.'; + +export const getJsonKeyPair = async (password: string): Promise => { + const keyPair = await getKeyPair(); + return JSON.stringify(keyPair.toJson(password)); +}; diff --git a/packages/snap/src/util/getKeyPair.ts b/packages/snap/src/util/getKeyPair.ts index 59e520a..0c6f609 100644 --- a/packages/snap/src/util/getKeyPair.ts +++ b/packages/snap/src/util/getKeyPair.ts @@ -1,7 +1,8 @@ -import { JsonBIP44CoinTypeNode } from '@metamask/key-tree'; +import type { JsonBIP44CoinTypeNode } from '@metamask/key-tree'; import { Keyring } from '@polkadot/keyring'; +import type { KeyringPair } from '@polkadot/keyring/types'; import { stringToU8a } from '@polkadot/util'; -import { KeyringPair } from '@polkadot/keyring/types'; + import { getChain } from '../chains'; import { DEFAULT_CHAIN_NAME, DEFAULT_COIN_TYPE } from '../defaults'; @@ -9,7 +10,7 @@ export const getKeyPair = async ( chainName: string = DEFAULT_CHAIN_NAME, genesisHash?: string, ): Promise => { - const { prefix } = getChain((genesisHash || chainName) as string); + const { prefix } = getChain(genesisHash ?? chainName); const BIP44CoinNode = (await snap.request({ method: 'snap_getBip44Entropy', diff --git a/packages/snap/src/util/index.ts b/packages/snap/src/util/index.ts new file mode 100644 index 0000000..9801ead --- /dev/null +++ b/packages/snap/src/util/index.ts @@ -0,0 +1,4 @@ +export * from './getBalance'; +export * from './getCurrentChain'; +export * from './getKeyPair'; +export * from './getJsonKeyPair';