Skip to content

Instantly share code, notes, and snippets.

@danielsimao
Created August 11, 2026 12:48
Show Gist options
  • Select an option

  • Save danielsimao/f4ce1b8d03153739b5a0ee2f3bc6447e to your computer and use it in GitHub Desktop.

Select an option

Save danielsimao/f4ce1b8d03153739b5a0ee2f3bc6447e to your computer and use it in GitHub Desktop.
Minimal repro: @reown/appkit-adapter-bitcoin WalletStandardConnector mishandles publicKey / drops signInput params
// Minimal reproduction against @reown/appkit-adapter-bitcoin@1.8.21's WalletStandardConnector.
// No app, no dependencies beyond the package itself. Run with: node reown-repro.mjs
//
// Simulates exactly what a real MetaMask BTC connection looks like on the page (verified via a
// live wallet-standard `register-wallet` probe against a real MetaMask account), then calls the
// connector's own public API the way a consuming app is expected to.
// WalletStandardConnector isn't in the package's public `exports` map, so a bare subpath import
// is rejected — resolving through the package root and stepping in by relative path instead.
import { createRequire } from 'node:module';
import { dirname, join } from 'node:path';
const require = createRequire(import.meta.url);
const pkgRoot = dirname(require.resolve('@reown/appkit-adapter-bitcoin'));
const { WalletStandardConnector } = await import(
join(pkgRoot, '..', 'src', 'connectors', 'WalletStandardConnector.js')
);
const ADDRESS = 'bc1qxj43gq5jgx843h3s4nmk2lqme825jsps3nl824';
// This is MetaMask's real registered wallet-standard account for a p2wpkh (bc1q) address.
// `publicKey` should be a 33-byte compressed secp256k1 key. It is not.
const fakeMetaMaskWallet = {
name: 'MetaMask',
icon: 'data:image/png;base64,',
chains: ['bitcoin:mainnet'],
accounts: [
{
address: ADDRESS,
publicKey: new TextEncoder().encode(ADDRESS), // <- 42 bytes: the address string itself
chains: ['bitcoin:mainnet'],
features: ['bitcoin:signTransaction'],
},
],
features: {
'bitcoin:connect': {
connect: async () => ({ accounts: [{ address: ADDRESS }] }),
},
'bitcoin:signTransaction': {
signTransaction: async (params) => {
console.log('\n--- What MetaMask\'s wallet-standard feature actually receives ---');
console.log(JSON.stringify(params.inputsToSign, null, 2));
return [{ signedPsbt: params.psbt }]; // stand-in signature, irrelevant to the bug
},
},
},
};
const connector = new WalletStandardConnector({
wallet: fakeMetaMaskWallet,
requestedChains: [{ caipNetworkId: 'bitcoin:mainnet' }],
});
console.log('--- Bug 1: getAccountAddresses() exposes the malformed publicKey verbatim ---');
const accounts = await connector.getAccountAddresses();
console.log(accounts);
const decoded = Buffer.from(accounts[0].publicKey, 'hex').toString('utf8');
console.log(`Decoded "publicKey" hex back to UTF-8: "${decoded}"`);
console.log(`Byte length: ${accounts[0].publicKey.length / 2} (not 33/65/32 — not a valid key of any kind)\n`);
console.log('--- Bug 2: signPSBT() silently drops publicKey, sighashTypes, disableTweakSigner ---');
// A minimal valid PSBT (single input, no signature) — its content is irrelevant to this bug.
const emptyPsbtBase64 = 'cHNidP8BAAoAAAAAAAAAAAAAAA==';
await connector.signPSBT({
psbt: emptyPsbtBase64,
broadcast: false,
signInputs: [
{
index: 0,
address: ADDRESS,
sighashTypes: [0x01, 0x03], // SIGHASH_ALL, SIGHASH_SINGLE — caller's actual request
publicKey: accounts[0].publicKey, // caller's public-key hint, whatever it is
disableTweakSigner: true,
},
],
});
console.log('Notice: no `publicKey`, `sighashTypes`, or `disableTweakSigner` field above.');
console.log('The connector rebuilt the request from `{ account, signingIndexes, sigHash: undefined }`,');
console.log('discarding everything the caller supplied.');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment