Skip to content
This repository was archived by the owner on Jul 22, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
886300b
feat: support Taproot account in KeyringHandler.ts
Battambang Jan 20, 2026
447f0e5
test: fix Taproot compatibility current KeyringHandler.ts unit tests
Battambang Jan 21, 2026
3764391
test: add Taproot KeyringHandler unit tests
Battambang Jan 21, 2026
0a69195
test: remove obsolete skipped tests and unused imports
Battambang Jan 21, 2026
4984a50
test: fix current integration tests compatibility
Battambang Jan 21, 2026
38afe7a
test: add Taproot integration tests
Battambang Jan 21, 2026
598ccf8
Merge remote-tracking branch 'origin/main' into feat/taproot
Battambang Feb 6, 2026
773784a
chore: update unit test and fix linter warning
Battambang Feb 6, 2026
501da4f
chore: separate assert from act for better readability
Battambang Feb 9, 2026
81a37f3
chore: add TEMPLATE_PSBT as a shared exported constant for integratio…
Battambang Feb 9, 2026
40f6243
fix: remove KeyringRequest typecast from integration tests
Battambang Feb 9, 2026
0381062
refactor: extract trace helpers from createAccount
Battambang Feb 10, 2026
8dd7895
refactor: extract to #assertSupportedAddressType to deduplicate valid…
Battambang Feb 10, 2026
278273f
refactor: extract to #resolveAccountIndex method from createAccount
Battambang Feb 10, 2026
9f86cf1
refactor: extract to #resolveAddressType method to flatten createAcco…
Battambang Feb 10, 2026
1336726
fix: address the invalid Bech32m checksum in regtest P2TR recipient a…
Battambang Feb 10, 2026
4066f32
fix: revert createAccount() refactoring, move it to another PR
Battambang Feb 10, 2026
13c3bcf
Merge branch 'main' into feat/taproot
Battambang Feb 10, 2026
7372244
refactor: reapply the previous refactoring of createAccount()
Battambang Feb 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/snap/integration-test/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ export const TEST_ADDRESS_REGTEST =
'bcrt1qjtgffm20l9vu6a7gacxvpu2ej4kdcsgcgnly6t';
export const TEST_ADDRESS_MAINNET =
'bc1q832zlt4tgnqy88vd20mazw77dlt0j0wf2naw8q';

// P2TR (Taproot) addresses
export const TEST_ADDRESS_P2TR_MAINNET =
'bc1p4rue37y0v9snd4z3fvw43d29u97qxf9j3fva72xy2t7hekg24dzsaz40mz';
export const TEST_ADDRESS_P2TR_TESTNET =
'tb1pwwjax3vpq6h69965hcr22vkpm4qdvyu2pz67wyj8eagp9vxkcz0q0ya20h';

export const ORIGIN = 'metamask';
export const FUNDING_TX = {
account: expect.any(Number),
Expand Down
157 changes: 81 additions & 76 deletions packages/snap/integration-test/keyring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
ORIGIN,
TEST_ADDRESS_REGTEST,
TEST_ADDRESS_MAINNET,
TEST_ADDRESS_P2TR_MAINNET,
TEST_ADDRESS_P2TR_TESTNET,
scopeToCoinType,
accountTypeToPurpose,
} from './constants';
Expand Down Expand Up @@ -64,7 +66,8 @@ describe('Keyring', () => {
},
});

// We should get 1 account, the p2wpkh one of Regtest
// Discovery now checks both P2WPKH and P2TR, but only returns accounts with history.
// We should get 1 account (P2WPKH) since only that one has funded history on Regtest.
expect(response).toRespondWith([
{
type: 'bip44',
Expand Down Expand Up @@ -165,76 +168,52 @@ describe('Keyring', () => {
},
);

// skip non-P2WPKH address types as we are not supporting them for v1
it.skip.each([
{
addressType: BtcAccountType.P2pkh,
scope: BtcScope.Mainnet,
index: 0,
expectedAddress: '15feVv7kK3z7jxA4RZZzY7Fwdu3yqFwzcT',
},
{
addressType: BtcAccountType.P2pkh,
scope: BtcScope.Testnet,
index: 0,
expectedAddress: 'mjPQaLkhZN3MxsYN8Nebzwevuz8vdTaRCq',
},
{
addressType: BtcAccountType.P2sh,
scope: BtcScope.Mainnet,
index: 0,
expectedAddress: '3QVSaDYjxEh4L3K24eorrQjfVxPAKJMys2',
},
{
addressType: BtcAccountType.P2sh,
scope: BtcScope.Testnet,
index: 0,
expectedAddress: '2NBG623WvXp1zxKB6gK2mnMe2mSDCur5qRU',
},
it.each([
{
addressType: BtcAccountType.P2tr,
scope: BtcScope.Mainnet,
index: 0,
expectedAddress:
'bc1p4rue37y0v9snd4z3fvw43d29u97qxf9j3fva72xy2t7hekg24dzsaz40mz',
expectedAddress: TEST_ADDRESS_P2TR_MAINNET,
},
{
addressType: BtcAccountType.P2tr,
scope: BtcScope.Testnet,
index: 0,
expectedAddress:
'tb1pwwjax3vpq6h69965hcr22vkpm4qdvyu2pz67wyj8eagp9vxkcz0q0ya20h',
expectedAddress: TEST_ADDRESS_P2TR_TESTNET,
},
])('creates an account: %s', async ({ expectedAddress, ...requestOpts }) => {
const response = await snap.onKeyringRequest({
origin: ORIGIN,
method: 'keyring_createAccount',
params: { options: { ...requestOpts, synchronize: false } },
});
])(
'creates a P2TR account: %s',
async ({ expectedAddress, ...requestOpts }) => {
const response = await snap.onKeyringRequest({
origin: ORIGIN,
method: 'keyring_createAccount',
params: { options: { ...requestOpts, synchronize: false } },
});

expect(response).toRespondWith({
type: requestOpts.addressType,
id: expect.anything(),
address: expectedAddress,
options: {
entropySource: 'm',
entropy: {
type: 'mnemonic',
id: 'm',
groupIndex: requestOpts.index,
derivationPath: `m/${accountTypeToPurpose[requestOpts.addressType]}/${scopeToCoinType[requestOpts.scope]}/${requestOpts.index}'`,
expect(response).toRespondWith({
type: requestOpts.addressType,
id: expect.anything(),
address: expectedAddress,
options: {
entropySource: 'm',
entropy: {
type: 'mnemonic',
id: 'm',
groupIndex: requestOpts.index,
derivationPath: `m/${accountTypeToPurpose[requestOpts.addressType]}/${scopeToCoinType[requestOpts.scope]}/${requestOpts.index}'`,
},
exportable: false,
},
exportable: false,
},
scopes: [requestOpts.scope],
methods: Object.values(AccountCapability),
});
scopes: [requestOpts.scope],
methods: Object.values(AccountCapability),
});

// eslint-disable-next-line jest/no-conditional-in-test
if ('result' in response.response) {
accounts[expectedAddress] = response.response.result as KeyringAccount;
}
});
// eslint-disable-next-line jest/no-conditional-in-test
if ('result' in response.response) {
accounts[expectedAddress] = response.response.result as KeyringAccount;
}
},
);

it('returns the same account if already exists by derivationPath', async () => {
// Account already exists so we should get the same account
Expand Down Expand Up @@ -273,20 +252,17 @@ describe('Keyring', () => {
{
addressType: BtcAccountType.P2pkh,
scope: BtcScope.Mainnet,
expectedError: 'Only native segwit (P2WPKH) addresses are supported',
expectedError:
'Only native segwit (P2WPKH) and taproot (P2TR) addresses are supported',
},
{
addressType: BtcAccountType.P2sh,
scope: BtcScope.Testnet,
expectedError: 'Only native segwit (P2WPKH) addresses are supported',
},
{
addressType: BtcAccountType.P2tr,
scope: BtcScope.Mainnet,
expectedError: 'Only native segwit (P2WPKH) addresses are supported',
expectedError:
'Only native segwit (P2WPKH) and taproot (P2TR) addresses are supported',
},
])(
'rejects creation of non-P2WPKH account: $addressType',
'rejects creation of non-P2WPKH/P2TR account: $addressType',
async ({ addressType, scope, expectedError }) => {
const response = await snap.onKeyringRequest({
origin: ORIGIN,
Expand Down Expand Up @@ -314,20 +290,15 @@ describe('Keyring', () => {
{
derivationPath: "m/44'/0'/0'", // (P2PKH)
expectedError:
'Only native segwit (BIP-84) derivation paths are supported',
'Only native segwit (BIP-84) and taproot (BIP-86) derivation paths are supported',
},
{
derivationPath: "m/49'/0'/0'", // (P2SH)
expectedError:
'Only native segwit (BIP-84) derivation paths are supported',
},
{
derivationPath: "m/86'/0'/0'", // (P2TR)
expectedError:
'Only native segwit (BIP-84) derivation paths are supported',
'Only native segwit (BIP-84) and taproot (BIP-86) derivation paths are supported',
},
])(
'rejects creation with non-BIP84 derivation path: $derivationPath',
'rejects creation with non-BIP84/BIP86 derivation path: $derivationPath',
async ({ derivationPath, expectedError }) => {
const response = await snap.onKeyringRequest({
origin: ORIGIN,
Expand Down Expand Up @@ -368,7 +339,7 @@ describe('Keyring', () => {
error: {
code: -32000,
message:
'Invalid format: Only native segwit (BIP-84) derivation paths are supported',
'Invalid format: Only native segwit (BIP-84) and taproot (BIP-86) derivation paths are supported',
},
});
});
Expand All @@ -392,7 +363,41 @@ describe('Keyring', () => {
const account: KeyringAccount = (
response.response as { result: KeyringAccount }
).result;
expect(account.address).toMatch(/^bcrt1/u); // Native segwit address
expect(account.address).toMatch(/^bcrt1q/u); // Native segwit address
expect((account.options.entropy as { groupIndex: number }).groupIndex).toBe(
10,
);

// remove to avoid interfering with other tests
await snap.onKeyringRequest({
origin: ORIGIN,
method: 'keyring_deleteAccount',
params: {
id: account.id,
},
});
});

it('accepts creation when addressType and derivationPath both indicate P2TR', async () => {
const response = await snap.onKeyringRequest({
origin: ORIGIN,
method: 'keyring_createAccount',
params: {
options: {
scope: BtcScope.Regtest,
addressType: BtcAccountType.P2tr,
derivationPath: "m/86'/1'/10'", // Taproot path matching P2TR
synchronize: false,
},
},
});

expect(response.response).toHaveProperty('result');

const account: KeyringAccount = (
response.response as { result: KeyringAccount }
).result;
expect(account.address).toMatch(/^bcrt1p/u); // Taproot address
expect((account.options.entropy as { groupIndex: number }).groupIndex).toBe(
10,
);
Expand Down
Loading
Loading