feat: show addresses, not account ids, and default the prefix to 189
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
This commit is contained in:
@@ -34,7 +34,7 @@
|
||||
"@polkadot/ui-settings": "^3.16.7",
|
||||
"@polkadot/util": "^14.0.3",
|
||||
"@polkadot/util-crypto": "^14.0.3",
|
||||
"@quantus/codec": "^0.4.0",
|
||||
"@quantus/codec": "^0.5.0",
|
||||
"@quantus/crypto": "^0.1.1",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"@polkadot/networks": "^14.0.3",
|
||||
"@polkadot/util": "^14.0.3",
|
||||
"@polkadot/util-crypto": "^14.0.3",
|
||||
"@quantus/codec": "^0.4.0",
|
||||
"@quantus/codec": "^0.5.0",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -29,9 +29,16 @@ const expanded = new Map<string, Chain>();
|
||||
* for — see `RequestExtrinsicSign` — which is a better outcome than an exception
|
||||
* thrown from a metadata lookup on an unrelated screen.
|
||||
*/
|
||||
function fromMetadata (bytes: Uint8Array, genesisHash: string): Runtime | null {
|
||||
function fromMetadata (bytes: Uint8Array, genesisHash: string, ss58Format: number): Runtime | null {
|
||||
try {
|
||||
return Runtime.fromMetadata(bytes);
|
||||
const runtime = Runtime.fromMetadata(bytes);
|
||||
|
||||
// So a decoded call names its recipient as an address rather than as 32
|
||||
// bytes of hex. That matters on exactly one screen — the one asking somebody
|
||||
// to approve a transfer — and hex is the form nobody checks.
|
||||
runtime.setSs58Format(ss58Format);
|
||||
|
||||
return runtime;
|
||||
} catch (error) {
|
||||
console.error(`Unable to read the metadata for ${genesisHash}: ${(error as Error).message}`);
|
||||
|
||||
@@ -76,7 +83,7 @@ export function metadataExpand (definition: MetadataDef, isPartial = false): Cha
|
||||
// replacement, and it is the runtime describing itself rather than a decoder
|
||||
// written against a version of it.
|
||||
const runtime = metadataBytes && !isPartial
|
||||
? fromMetadata(metadataBytes, genesisHash)
|
||||
? fromMetadata(metadataBytes, genesisHash, ss58Format)
|
||||
: null;
|
||||
const hasMetadata = !!runtime;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import details from '../assets/details.svg';
|
||||
import { useMetadata, useOutsideClick, useToast, useTranslation } from '../hooks/index.js';
|
||||
import { showAccount } from '../messaging.js';
|
||||
import { styled } from '../styled.js';
|
||||
import { DEFAULT_PREFIX } from '../util/defaultPrefix.js';
|
||||
import { DEFAULT_TYPE } from '../util/defaultType.js';
|
||||
import getParentNameSuri from '../util/getParentNameSuri.js';
|
||||
import { AccountContext, SettingsContext } from './contexts.js';
|
||||
@@ -75,7 +76,7 @@ function recodeAddress (address: string, accounts: AccountWithChildren[], chain:
|
||||
const publicKey = isHex(address) ? hexToU8a(address) : decodeAddress(address);
|
||||
// find our account using the actual publicKey, and then find the associated chain
|
||||
const account = findSubstrateAccount(accounts, publicKey);
|
||||
const prefix = chain ? chain.ss58Format : (settings.prefix === -1 ? 42 : settings.prefix);
|
||||
const prefix = chain ? chain.ss58Format : (settings.prefix === -1 ? DEFAULT_PREFIX : settings.prefix);
|
||||
|
||||
// always allow the actual settings to override the display
|
||||
return {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ActionContext, ActionText, Checkbox, chooseTheme, Dropdown, Menu, MenuD
|
||||
import { useIsPopup, useTranslation } from '../hooks/index.js';
|
||||
import { setNotification, windowOpen } from '../messaging.js';
|
||||
import { styled } from '../styled.js';
|
||||
import { DEFAULT_PREFIX } from '../util/defaultPrefix.js';
|
||||
import getLanguageOptions from '../util/getLanguageOptions.js';
|
||||
|
||||
interface Option {
|
||||
@@ -32,7 +33,7 @@ const prefixOptions = settings.availablePrefixes
|
||||
function MenuSettings ({ className, reference }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const [camera, setCamera] = useState(settings.camera === 'on');
|
||||
const [prefix, setPrefix] = useState(`${settings.prefix === -1 ? 42 : settings.prefix}`);
|
||||
const [prefix, setPrefix] = useState(`${settings.prefix === -1 ? DEFAULT_PREFIX : settings.prefix}`);
|
||||
const [notification, updateNotification] = useState(settings.notification);
|
||||
const [theme, setTheme] = useState(chooseTheme());
|
||||
const setThemeContext = useContext(ThemeSwitchContext);
|
||||
|
||||
@@ -19,8 +19,12 @@ describe('decoding a call for the approval screen', (): void => {
|
||||
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: `0x${'11'.repeat(32)}` },
|
||||
dest: { Id: BOB_ID },
|
||||
value: '1000000000'
|
||||
}));
|
||||
|
||||
@@ -33,7 +37,9 @@ describe('decoding a call for the approval screen', (): void => {
|
||||
|
||||
expect(name).toEqual('Balances.transfer_keep_alive');
|
||||
expect(args?.['value']).toEqual('1000000000');
|
||||
expect(args?.['dest']).toEqual({ Id: `0x${'11'.repeat(32)}` });
|
||||
// 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
|
||||
|
||||
18
packages/extension-ui/src/util/defaultPrefix.ts
Normal file
18
packages/extension-ui/src/util/defaultPrefix.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2019-2026 @polkadot/extension-ui authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* The SS58 prefix to display an address at when nothing else has decided.
|
||||
*
|
||||
* Upstream falls back to 42, 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 with
|
||||
* `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; this is only the
|
||||
* fallback, and the user can still override it in settings.
|
||||
*/
|
||||
export const DEFAULT_PREFIX = 189;
|
||||
12
yarn.lock
12
yarn.lock
@@ -982,7 +982,7 @@ __metadata:
|
||||
"@polkadot/ui-settings": "npm:^3.16.7"
|
||||
"@polkadot/util": "npm:^14.0.3"
|
||||
"@polkadot/util-crypto": "npm:^14.0.3"
|
||||
"@quantus/codec": "npm:^0.4.0"
|
||||
"@quantus/codec": "npm:^0.5.0"
|
||||
"@quantus/crypto": "npm:^0.1.1"
|
||||
eventemitter3: "npm:^5.0.1"
|
||||
rxjs: "npm:^7.8.1"
|
||||
@@ -998,7 +998,7 @@ __metadata:
|
||||
"@polkadot/networks": "npm:^14.0.3"
|
||||
"@polkadot/util": "npm:^14.0.3"
|
||||
"@polkadot/util-crypto": "npm:^14.0.3"
|
||||
"@quantus/codec": "npm:^0.4.0"
|
||||
"@quantus/codec": "npm:^0.5.0"
|
||||
tslib: "npm:^2.8.1"
|
||||
peerDependencies:
|
||||
"@polkadot/api": "*"
|
||||
@@ -1604,13 +1604,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@quantus/codec@npm:^0.4.0":
|
||||
version: 0.4.0
|
||||
resolution: "@quantus/codec@npm:0.4.0::__archiveUrl=https%3A%2F%2Fgit.lair.cafe%2Fapi%2Fpackages%2Fquantus%2Fnpm%2F%2540quantus%252Fcodec%2F-%2F0.4.0%2Fcodec-0.4.0.tgz"
|
||||
"@quantus/codec@npm:^0.5.0":
|
||||
version: 0.5.0
|
||||
resolution: "@quantus/codec@npm:0.5.0::__archiveUrl=https%3A%2F%2Fgit.lair.cafe%2Fapi%2Fpackages%2Fquantus%2Fnpm%2F%2540quantus%252Fcodec%2F-%2F0.5.0%2Fcodec-0.5.0.tgz"
|
||||
dependencies:
|
||||
fflate: "npm:^0.8.2"
|
||||
tslib: "npm:^2.7.0"
|
||||
checksum: 10/9327551cf97a5bf0a54d5cf1ef9703efc94e85138f24346d597fe4860570d451a3301db0dc1f7a382bc760491ff3ea823cecf384d07e0559f766bafb9ec19b46
|
||||
checksum: 10/248a2ae9650b1521d56aa3bf81c01084ae37474365df1f2dbece928b59647004c719bfb396ba1fccd33ec8e6e67c7e5e82660f9fbbcc2fcda41b5dbe6e3a38fb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user