diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index 02a1bea..cbadce8 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/polkagate/polkamask.git" }, "source": { - "shasum": "pfHNpIzWs5bNt6+gN5oqFwg5LqnEljSox9GX8/kM7h4=", + "shasum": "zKNwgefHGQqz8h/tnMRKYyRUwxyvGf12iZVSA28zAYw=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/snap/src/rpc/showConfirmTx.ts b/packages/snap/src/rpc/showConfirmTx.ts index 3c54a3d..b08b81c 100644 --- a/packages/snap/src/rpc/showConfirmTx.ts +++ b/packages/snap/src/rpc/showConfirmTx.ts @@ -1,85 +1,30 @@ /* eslint-disable no-case-declarations */ /* eslint-disable jsdoc/require-jsdoc */ -import { - copyable, - divider, - heading, - image, - panel, - text, -} from '@metamask/snaps-sdk'; +import { divider, heading, image, panel, text } from '@metamask/snaps-sdk'; import { ApiPromise } from '@polkadot/api'; -import { Compact, u128 } from '@polkadot/types'; import { SignerPayloadJSON } from '@polkadot/types/types'; -import { BN, bnToBn } from '@polkadot/util'; +import { bnToBn } from '@polkadot/util'; import { Balance } from '@polkadot/types/interfaces'; import getLogo from '../util/getLogo'; import getChainName from '../util/getChainName'; +import { formatCamelCase } from '../util/formatCamelCase'; +import { getIdentity } from '../util/getIdentity'; +import { txContent } from './txContent'; import { Decoded, getDecoded } from '.'; -const FLOATING_POINT_DIGIT = 4; const EMPTY_LOGO = ` `; -export function fixFloatingPoint( - _number: number | string, - decimalDigit = FLOATING_POINT_DIGIT, - commify?: boolean, -): string { - // make number positive if it is negative - const sNumber = - Number(_number) < 0 ? String(-Number(_number)) : String(_number); - - const dotIndex = sNumber.indexOf('.'); - - if (dotIndex < 0) { - return sNumber; - } - - let integerDigits = sNumber.slice(0, dotIndex); - - integerDigits = commify - ? Number(integerDigits).toLocaleString() - : integerDigits; - const fractionalDigits = sNumber.slice(dotIndex, dotIndex + decimalDigit + 1); - - return integerDigits + fractionalDigits; -} - -function formatCamelCase(input?: string) { - return input - ?.replace(/([a-z])([A-Z])/g, '$1 $2') // Add space between camelCase - ?.replace(/\b(\w)/, (char) => char.toUpperCase()); // Capitalize the first letter -} - -export function amountToHuman( - _amount: string | number | BN | bigint | Compact | undefined, - _decimals: number | undefined, - decimalDigits?: number, - commify?: boolean, -): string { - if (!_amount || !_decimals) { - return ''; - } - - // eslint-disable-next-line no-param-reassign - _amount = String(_amount).replace(/,/g, ''); - - const x = 10 ** _decimals; - - return fixFloatingPoint(Number(_amount) / x, decimalDigits, commify); -} - const transactionContent = ( api: ApiPromise, origin: string, payload: SignerPayloadJSON, partialFee: Balance, decoded: Decoded, + maybeReceiverIdentity: string, ) => { const headingText = `Transaction Approval Request from ${origin}`; - const decodedArgs = decoded?.args; const { args, callIndex } = api.createType('Call', payload.method); const { method, section } = api.registry.findMetaCall(callIndex); @@ -90,155 +35,38 @@ const transactionContent = ( let chainLogoSvg = EMPTY_LOGO; const dataURI = getLogo(payload.genesisHash); const maybeSvgString = atob( - dataURI.replace(/data:image\/svg\+xml;base64,/, ''), + dataURI.replace(/data:image\/svg\+xml;base64,/u, ''), ); const indexOfFirstSvgTag = maybeSvgString.indexOf(' { + let amount; + const isNoArgsMethod = args?.length === 0 && 'noArgsMethods'; + const decodedArgs = decoded?.args; + + const decimal = api.registry.chainDecimals[0]; + const token = api.registry.chainTokens[0]; + + switch (isNoArgsMethod || action) { + case 'balances_transfer': + case 'balances_transferKeepAlive': + case 'balances_transferAll': + const to = `${args[0]}`; + amount = String(args[1]); + + return [ + text(`_To_: **${maybeReceiverIdentity}**`), + copyable(to), + divider(), + text(`_Amount_: **${amountToHuman(amount, decimal)} ${token}**`), + ]; + case 'staking_bond': + amount = `${args[0]}`; + const payee = String(args[1]); + + return [ + text(`_Amount_: **${amountToHuman(amount, decimal)} ${token}**`), + divider(), + text(`_Payee_: ${payee}`), + ]; + case 'staking_nominate': + return [text(`_Validators_: **${args[0]}**`)]; + + case 'nominationPools_unbond': + case 'staking_unbond': + case 'staking_bondExtra': + amount = `${args[action === 'nominationPools_unbond' ? 1 : 0]}`; + + return [text(`_Amount_: **${amountToHuman(amount, decimal)} ${token}**`)]; + + case 'staking_setPayee': + return [text(`_Payee_: **${args[0]}**`)]; + case 'nominationPools_join': + amount = `${args[0]}`; + const poolId = String(args[1]); + + return [ + panel([ + text(`_Amount_: **${amountToHuman(amount, decimal)} ${token}**`), + divider(), + text(`_Pool Id_: **${poolId}**`), + ]), + ]; + case 'nominationPools_bondExtra': + let extra = String(args[0]); + if (extra === 'Rewards') { + extra = 'Rewards'; + } else { + const { freeBalance } = JSON.parse(extra); + extra = `${amountToHuman(freeBalance, decimal)} ${token}`; + } + + return [text(`_Extra_: **${extra}**`)]; + case 'noArgsMethods': + return []; + default: + return [ + text(`_Details:`), + text(JSON.stringify(decodedArgs || args, null, 2)), // decodedArgs show the args' labels as well + ]; + } +}; diff --git a/packages/snap/src/util/amountToHuman.ts b/packages/snap/src/util/amountToHuman.ts new file mode 100644 index 0000000..62b7893 --- /dev/null +++ b/packages/snap/src/util/amountToHuman.ts @@ -0,0 +1,64 @@ +import { Compact, u128 } from '@polkadot/types'; +import { BN } from '@polkadot/util'; + +const FLOATING_POINT_DIGIT = 4; + +/** + * Formats a number or string by fixing the floating-point digits. + * + * @param _number - The number to be formatted. + * @param decimalDigit - The number of decimal digits to include. + * @param commify - If true, adds commas for thousands separator. + * @returns The formatted number. + */ +export function fixFloatingPoint( + _number: number | string, + decimalDigit = FLOATING_POINT_DIGIT, + commify?: boolean, +): string { + // make number positive if it is negative + const sNumber = + Number(_number) < 0 ? String(-Number(_number)) : String(_number); + + const dotIndex = sNumber.indexOf('.'); + + if (dotIndex < 0) { + return sNumber; + } + + let integerDigits = sNumber.slice(0, dotIndex); + + integerDigits = commify + ? Number(integerDigits).toLocaleString() + : integerDigits; + const fractionalDigits = sNumber.slice(dotIndex, dotIndex + decimalDigit + 1); + + return integerDigits + fractionalDigits; +} + +/** + * Converts an amount, which may be a BN, to human-readable format. + * + * @param amount - The amount to be formatted. + * @param decimals - The number of decimal places for the chain. + * @param decimalDigits - The number of decimal digits to include. + * @param commify - If true, adds commas for thousands separator. + * @returns The human-readable amount. + */ +export function amountToHuman( + amount: string | number | BN | bigint | Compact | undefined, + decimals: number | undefined, + decimalDigits?: number, + commify?: boolean, +): string { + if (!amount || !decimals) { + return ''; + } + + // eslint-disable-next-line no-param-reassign + amount = String(amount).replace(/,/gu, ''); + + const x = 10 ** decimals; + + return fixFloatingPoint(Number(amount) / x, decimalDigits, commify); +} diff --git a/packages/snap/src/util/formatCamelCase.ts b/packages/snap/src/util/formatCamelCase.ts new file mode 100644 index 0000000..84c6160 --- /dev/null +++ b/packages/snap/src/util/formatCamelCase.ts @@ -0,0 +1,11 @@ +/** + * Format input string as CamelCase. + * + * @param input - A string. + * @returns A string. + */ +export function formatCamelCase(input?: string) { + return input + ?.replace(/([a-z])([A-Z])/gu, '$1 $2') // Add space between camelCase + ?.replace(/\b(\w)/u, (char) => char.toUpperCase()); // Capitalize the first letter +} diff --git a/packages/snap/src/util/getIdentity.ts b/packages/snap/src/util/getIdentity.ts new file mode 100644 index 0000000..0ced686 --- /dev/null +++ b/packages/snap/src/util/getIdentity.ts @@ -0,0 +1,26 @@ +import { ApiPromise } from '@polkadot/api'; +import type { Registration } from '@polkadot/types/interfaces'; +import type { Option } from '@polkadot/types'; + +import { hexToString } from '@polkadot/util'; + +/** + * To get the display name of an account's on-chain identity. + * + * @param api - The api to connect to a remote node. + * @param formatted - The address to fetch its on-chain identity. + */ +export async function getIdentity( + api: ApiPromise, + formatted: string, +): Promise { + const identity = (await api.query.identity.identityOf( + formatted, + )) as Option; + + const displayName = identity?.isSome + ? hexToString(identity.unwrap().info.display.asRaw.toHex()) + : 'Unknown'; + + return displayName; +}