Two halves of #6. **The approval screen names its recipient.** Moving call decoding onto @quantus/codec left `dest` rendering as 32 bytes of hex, because the codec has no business guessing an SS58 prefix. `metadataExpand` now tells it the one the chain's own metadata definition carries, so a transfer shows `qzkYEQv8tQsmniZYdame3Cku18RL5g9bGK9Pdydq5TMPdpE3y` rather than `0x300bb607…`. Somebody approving a transfer has to be able to check the recipient against what they meant to send to, and hex is the form nobody checks. Account ids are identified by registry path, not by length, so a 32-byte block hash still renders as hex — quantus/wasm has the test. **The fallback prefix is 189, not 42.** Upstream falls back to the generic Substrate prefix because it is a wallet for every Substrate chain and has no reason to prefer one. This one does: every account it can hold is a Quantus account, so 42 would show a correct address in a form no Quantus tool displays — the same account id, the same funds, an unfamiliar string. Somebody comparing the extension against quantus-cli or the mobile wallet would reasonably conclude they had created the wrong account. A chain's own ss58Format still wins where one is known, and the setting still overrides. Refs #6, quantus/wasm#3 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
65 lines
3.0 KiB
TypeScript
65 lines
3.0 KiB
TypeScript
// Copyright 2019-2026 @polkadot/extension-ui authors & contributors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
|
|
|
import type { Chain } from '@polkadot/extension-chains/types';
|
|
|
|
import { HEISENBERG_SPEC, heisenbergMetadataDef } from '@polkadot/extension-base/test/metadata';
|
|
import { metadataExpand } from '@polkadot/extension-chains';
|
|
import { bnToBn, u8aToHex } from '@polkadot/util';
|
|
|
|
import { decodeMethod } from './decodeMethod.js';
|
|
|
|
describe('decoding a call for the approval screen', (): void => {
|
|
const chain: Chain = metadataExpand(heisenbergMetadataDef(), false);
|
|
const runtime = chain.runtime;
|
|
|
|
if (!runtime) {
|
|
throw new Error('the fixture metadata did not produce a runtime');
|
|
}
|
|
|
|
// crystal_bob on Heisenberg, as an account id and as the address it renders to.
|
|
const BOB_ID = '0x300bb607ba60e89461d2f9005668231ceb30237b33db53a614164b8590965519';
|
|
const BOB = 'qzkYEQv8tQsmniZYdame3Cku18RL5g9bGK9Pdydq5TMPdpE3y';
|
|
|
|
const transfer = u8aToHex(runtime.encodeCall('Balances', 'transfer_keep_alive', {
|
|
dest: { Id: BOB_ID },
|
|
value: '1000000000'
|
|
}));
|
|
|
|
// What the screen exists to do. Before this it went through
|
|
// registry.createType('Call', …), which cannot work on a chain @polkadot/types
|
|
// cannot describe — so every transaction rendered as raw hex, which is the
|
|
// failure mode where somebody approves bytes nobody read to them.
|
|
it('names the call and its arguments as the runtime does', (): void => {
|
|
const { args, name } = decodeMethod(transfer, chain, bnToBn(HEISENBERG_SPEC));
|
|
|
|
expect(name).toEqual('Balances.transfer_keep_alive');
|
|
expect(args?.['value']).toEqual('1000000000');
|
|
// An address, not 32 bytes of hex — somebody approving a transfer has to be
|
|
// able to check the recipient against what they meant to send to.
|
|
expect(args?.['dest']).toEqual({ Id: BOB });
|
|
});
|
|
|
|
// Metadata from a different runtime decodes a call into something plausible and
|
|
// wrong. Showing hex is worse than showing a decoded call; showing the *wrong*
|
|
// decoded call is worse than both.
|
|
it('refuses to decode against a different spec version', (): void => {
|
|
expect(decodeMethod(transfer, chain, bnToBn(HEISENBERG_SPEC + 1))).toEqual({ args: null, name: null });
|
|
});
|
|
|
|
// A chain the extension holds no metadata for. It will not sign one of these
|
|
// either — see RequestExtrinsicSign — but the screen still has to render, and
|
|
// rendering hex is the honest thing to show.
|
|
it('returns nothing readable when there is no runtime', (): void => {
|
|
expect(decodeMethod(transfer, { ...chain, runtime: null }, bnToBn(HEISENBERG_SPEC))).toEqual({ args: null, name: null });
|
|
});
|
|
|
|
// Bytes that are not a call at all. The screen must not throw: an exception
|
|
// here lands between somebody and their funds.
|
|
it('returns nothing readable for bytes that are not a call', (): void => {
|
|
expect(decodeMethod('0xdeadbeef', chain, bnToBn(HEISENBERG_SPEC))).toEqual({ args: null, name: null });
|
|
});
|
|
});
|