Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d8ca5c4
chore: move tron files into packages
taran-a Jul 27, 2026
7acd5f9
chore: yarn constraints fixes
taran-a Jul 27, 2026
dcfa6b5
chore: fix eslint errors, yarn constraints
taran-a Jul 27, 2026
7514358
chore: upgrade keyring packages and snaps tooling for v2 support
hmalik88 Jul 28, 2026
ded2225
feat: configure moduleResolution bundler for v2 subpath imports
hmalik88 Jul 28, 2026
90994ef
feat: add ExportAccountRequestStruct and PrivateKeyHexStruct
hmalik88 Jul 28, 2026
2cab39e
feat: register Keyring API v2 method permissions
hmalik88 Jul 28, 2026
e093671
feat: implement KeyringSnapRpc v2 interface on KeyringHandler
hmalik88 Jul 28, 2026
b43d316
test: update getAccounts test for v2 rename
hmalik88 Jul 28, 2026
359d7b6
feat: add endowment:keyring capabilities for v2
hmalik88 Jul 28, 2026
d18e874
chore: fix formatting, bump monorepo dep versions, add changelog entry
hmalik88 Jul 28, 2026
581017e
chore: update deps
hmalik88 Jul 29, 2026
b46edee
Merge remote-tracking branch 'origin/main' into hm/tron-v2
hmalik88 Jul 30, 2026
24149a0
fix: yarn.lock
hmalik88 Jul 30, 2026
5ae23e0
fix: bitcoin shasum
hmalik88 Jul 30, 2026
8c843b5
feat: align deps
hmalik88 Jul 30, 2026
931808e
fix: tron shasum
hmalik88 Jul 30, 2026
785af81
fix: remove unused var
hmalik88 Jul 30, 2026
4151202
fix: undo changelog change
hmalik88 Jul 30, 2026
f8fa113
chore: add changelog entries
hmalik88 Jul 30, 2026
ff3263e
fix: changelogs
hmalik88 Jul 30, 2026
ee3a925
feat(tron-snap): fold discoverAccounts into createAccounts and remove…
hmalik88 Jul 30, 2026
8d0452c
fix: update tron snap shasum to match production build
hmalik88 Jul 30, 2026
35faa47
Merge remote-tracking branch 'origin/main' into hm/tron-v2
hmalik88 Jul 30, 2026
00d3327
fix: bitcoin shasum
hmalik88 Jul 30, 2026
37628fa
fix: add changelog PR links and restore coverage above thresholds
hmalik88 Jul 30, 2026
04d8ff3
fix: resolve lint and formatting issues in test files
hmalik88 Jul 30, 2026
d2e6e21
fix: format eslint-suppressions.json
hmalik88 Jul 30, 2026
d6c0fd3
fix: remove any types in keyring handler and test file
hmalik88 Jul 30, 2026
e2e37da
fix: tron shasum
hmalik88 Jul 30, 2026
e04998f
fix: add missing blank line between import groups in keyring test
hmalik88 Jul 30, 2026
aa92f48
refactor: throw instead of assert
hmalik88 Jul 30, 2026
1063a9c
Merge remote-tracking branch 'origin/main' into hm/tron-v2
hmalik88 Jul 30, 2026
e3b3f34
fix: tron shasum
hmalik88 Jul 30, 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
8 changes: 0 additions & 8 deletions eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -265,14 +265,6 @@
"count": 4
}
},
"packages/tron-wallet-snap/src/handlers/keyring.test.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 10
},
"import-x/no-extraneous-dependencies": {
"count": 1
}
},
"packages/tron-wallet-snap/src/handlers/keyring.ts": {
"import-x/no-extraneous-dependencies": {
"count": 1
Expand Down
4 changes: 4 additions & 0 deletions packages/bitcoin-wallet-snap/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Migrate `KeyringHandler` to implement `KeyringSnapRpc` from `@metamask/keyring-api/v2` ([#43](https://github.com/MetaMask/internal-snaps/pull/43))
- Update `snap.manifest.json` to declare `derivePath` capability in the `bip44` keyring block ([#43](https://github.com/MetaMask/internal-snaps/pull/43))
- Mark accounts as exportable in the keyring account mapping ([#43](https://github.com/MetaMask/internal-snaps/pull/43))
- Bump `@metamask/snaps-cli` from `^8.3.0` to `^8.4.1` ([#56](https://github.com/MetaMask/internal-snaps/pull/56))
- Bump `@metamask/keyring-api` from `^23.2.0` to `^23.7.0` ([#56](https://github.com/MetaMask/internal-snaps/pull/56))
- Bump `@metamask/keyring-snap-sdk` from `^8.0.0` to `^9.2.1` ([#56](https://github.com/MetaMask/internal-snaps/pull/56))
- Bump `@metamask/snaps-sdk` from `11.1.1` to `11.2.0` ([#56](https://github.com/MetaMask/internal-snaps/pull/56))

## [1.15.2]

Expand Down
5 changes: 5 additions & 0 deletions packages/sample-snap/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Add package to `internal-snaps` monorepo ([#8](https://github.com/MetaMask/internal-snaps/pull/8))

### Changed

- Bump `@metamask/snaps-cli` from `^8.3.0` to `^8.4.1` ([#56](https://github.com/MetaMask/internal-snaps/pull/56))
- Bump `@metamask/snaps-sdk` from `11.1.1` to `11.2.0` ([#56](https://github.com/MetaMask/internal-snaps/pull/56))

[Unreleased]: https://github.com/MetaMask/internal-snaps/
6 changes: 6 additions & 0 deletions packages/tron-wallet-snap/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **BREAKING** Implement Keyring API v2 (`KeyringSnapRpc` interface): rename `listAccounts` → `getAccounts`, `listAccountAssets` → `getAccountAssets`, `listAccountTransactions` → `getAccountTransactions`; `getAccount` now throws instead of returning `undefined`; add `exportAccount` with hexadecimal private key export using `sensitive()` for redaction; remove v1-only methods `createAccount`, `discoverAccounts`, `filterAccountChains`, and `updateAccount`. ([#56](https://github.com/MetaMask/internal-snaps/pull/56))
- Add `bip44:discover` support to `createAccounts`: checks on-chain activity across all Tron networks before persisting; returns `[]` if no activity to signal end-of-discovery to the client. ([#56](https://github.com/MetaMask/internal-snaps/pull/56))
- Add `endowment:keyring` capabilities to manifest declaring the `tron:728126428` scope, hexadecimal private key export, and BIP-44 derivation strategies. ([#56](https://github.com/MetaMask/internal-snaps/pull/56))

### Changed

- Bump `@metamask/keyring-api` from `^23.2.0` to `^23.7.0` ([#43](https://github.com/MetaMask/internal-snaps/pull/43))
Expand Down
19 changes: 17 additions & 2 deletions packages/tron-wallet-snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/internal-snaps.git"
},
"source": {
"shasum": "83y66B4a9awX0MoxVkfv7HV+Jt5S04octhhc5dUELM4=",
"shasum": "dp+m4eGZFWjHhiJ3lVXiQ2lyprExWYyvCTQkx/myroU=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand All @@ -23,7 +23,22 @@
},
"initialPermissions": {
"endowment:keyring": {
"allowedOrigins": ["https://portfolio.metamask.io"]
"allowedOrigins": ["https://portfolio.metamask.io"],
"capabilities": {
"scopes": ["tron:728126428"],
"privateKey": {
"exportFormats": [
{
"encoding": "hexadecimal"
}
]
},
"bip44": {
"deriveIndex": true,
"deriveIndexRange": true,
"discover": true
}
}
},
"snap_getBip32Entropy": [
{
Expand Down
229 changes: 91 additions & 138 deletions packages/tron-wallet-snap/src/handlers/keyring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
InvalidParamsError,
UserRejectedRequestError,
} from '@metamask/snaps-sdk';
import { bytesToBase64, bytesToHex, stringToBytes } from '@metamask/utils';

import type { SnapClient } from '../clients/snap/SnapClient';
import { Network } from '../constants';
Expand All @@ -28,7 +27,7 @@ import { TronMultichainMethod } from './keyring-types';
* @returns Base64 encoded string.
*/
function toBase64(str: string): string {
return bytesToBase64(stringToBytes(str));
return Buffer.from(str).toString('base64');
}

/**
Expand All @@ -38,7 +37,7 @@ function toBase64(str: string): string {
* @returns Hex encoded string (without 0x prefix).
*/
function toHex(str: string): string {
return bytesToHex(stringToBytes(str)).slice(2);
return Buffer.from(str).toString('hex');
}

describe('KeyringHandler', () => {
Expand All @@ -52,7 +51,7 @@ describe('KeyringHandler', () => {
],
type: 'tron:eoa',
scopes: [Network.Mainnet, Network.Shasta],
entropySource: 'entropy-source-1' as any,
entropySource: 'entropy-source-1',
derivationPath: "m/44'/195'/0'/0/0",
index: 0,
};
Expand All @@ -69,27 +68,30 @@ describe('KeyringHandler', () => {
mockSnapClient = {
scheduleBackgroundEvent: jest.fn().mockResolvedValue(undefined),
trackError: jest.fn(),
} as any;
} as unknown as jest.Mocked<SnapClient>;
mockAccountsService = {
findById: jest.fn().mockResolvedValue(mockAccount),
findByIdOrThrow: jest.fn().mockResolvedValue(mockAccount),
deriveAccount: jest.fn(),
create: jest.fn(),
createAccounts: jest.fn(),
getAll: jest.fn().mockResolvedValue([mockAccount]),
} as any;
mockAssetsService = {} as any;
} as unknown as jest.Mocked<AccountsService>;
mockAssetsService = {
getByKeyringAccountId: jest.fn().mockResolvedValue([]),
} as unknown as jest.Mocked<AssetsService>;
mockTransactionsService = {
checkAddressActivity: jest.fn(),
} as any;
findByAccounts: jest.fn().mockResolvedValue([]),
} as unknown as jest.Mocked<TransactionsService>;
mockWalletService = {
handleKeyringRequest: jest
.fn()
.mockResolvedValue({ signature: '0xsignature123' }),
} as any;
} as unknown as jest.Mocked<WalletService>;
mockConfirmationHandler = {
handleKeyringRequest: jest.fn().mockResolvedValue(true),
} as any;
} as unknown as jest.Mocked<ConfirmationHandler>;

keyringHandler = new KeyringHandler({
logger: mockLogger,
Expand Down Expand Up @@ -338,7 +340,7 @@ describe('KeyringHandler', () => {
method: TronMultichainMethod.SignMessage,
params: {},
},
} as any;
} as unknown as KeyringRequest;

await expect(
keyringHandler.submitRequest(invalidRequest),
Expand All @@ -355,7 +357,7 @@ describe('KeyringHandler', () => {
method: TronMultichainMethod.SignMessage,
// Missing params
},
} as any;
} as unknown as KeyringRequest;

await expect(keyringHandler.submitRequest(request)).rejects.toThrow(
'satisfy a union',
Expand Down Expand Up @@ -513,114 +515,6 @@ describe('KeyringHandler', () => {
});
});

describe('discoverAccounts', () => {
const mockDerivedAccount: TronKeyringAccount = {
id: '123e4567-e89b-42d3-a456-426614174001',
address: 'TDerivedAddress12345678901234567',
options: {},
methods: [
TronMultichainMethod.SignMessage,
TronMultichainMethod.SignTransaction,
],
type: 'tron:eoa',
scopes: [Network.Mainnet, Network.Shasta],
entropySource: 'test-entropy-source' as any,
derivationPath: "m/44'/195'/0'/0/0",
index: 0,
};

beforeEach(() => {
jest
.spyOn(mockAccountsService, 'deriveAccount')
.mockImplementation()
.mockResolvedValue(mockDerivedAccount);
jest
.spyOn(mockTransactionsService, 'checkAddressActivity')
.mockImplementation();
});

it('returns empty array if there is no activity on any of the scopes', async () => {
mockTransactionsService.checkAddressActivity
.mockResolvedValueOnce(false)
.mockResolvedValueOnce(false);

const result = await keyringHandler.discoverAccounts?.(
[Network.Mainnet, Network.Shasta],
'test-entropy-source',
0,
);

expect(result).toStrictEqual([]);
expect(mockAccountsService.deriveAccount).toHaveBeenCalledWith({
entropySource: 'test-entropy-source',
index: 0,
});
});

it('returns discovered accounts when there is activity on any scope', async () => {
mockTransactionsService.checkAddressActivity
.mockResolvedValueOnce(false)
.mockResolvedValueOnce(true);

const result = await keyringHandler.discoverAccounts?.(
[Network.Mainnet, Network.Shasta],
'test-entropy-source',
3,
);

expect(result).toStrictEqual([
{
type: 'bip44',
scopes: [Network.Mainnet, Network.Shasta],
derivationPath: mockDerivedAccount.derivationPath,
},
]);
});

it('throws error if there is an error fetching transactions', async () => {
mockTransactionsService.checkAddressActivity.mockRejectedValue(
new Error('Network error'),
);

await expect(
keyringHandler.discoverAccounts?.([Network.Mainnet], 'test', 0),
).rejects.toThrow('Network error');
});

it('throws error if no scopes are provided', async () => {
await expect(
keyringHandler.discoverAccounts?.([], 'test', 0),
).rejects.toThrow('Expected a nonempty array but received an empty one');
expect(
mockTransactionsService.checkAddressActivity,
).not.toHaveBeenCalled();
});

it('throws error if scope is not a valid Tron network', async () => {
await expect(
keyringHandler.discoverAccounts?.(
['invalid:network' as Network],
'test',
0,
),
).rejects.toThrow(/Expected one of/u);
expect(
mockTransactionsService.checkAddressActivity,
).not.toHaveBeenCalled();
});

it('throws error if groupIndex is negative', async () => {
await expect(
keyringHandler.discoverAccounts?.([Network.Mainnet], 'test', -1),
).rejects.toThrow(
'Expected a number greater than or equal to 0 but received `-1`',
);
expect(
mockTransactionsService.checkAddressActivity,
).not.toHaveBeenCalled();
});
});

describe('setSelectedAccounts', () => {
const NON_EXISTENT_ACCOUNT_ID = '123e4567-e89b-42d3-a456-426614174999';

Expand Down Expand Up @@ -654,29 +548,15 @@ describe('KeyringHandler', () => {
});
});

describe('listAccounts', () => {
describe('getAccounts', () => {
it('fails with cause', async () => {
const causeError = new Error('Account error');

mockAccountsService.getAll.mockRejectedValue(causeError);

await expect(keyringHandler.listAccounts()).rejects.toMatchObject({
message: 'Error listing accounts',
cause: causeError,
});
});
});

describe('createAccount', () => {
it('fails with cause', async () => {
const causeError = new Error('Account error');

mockAccountsService.create.mockRejectedValue(causeError);

await expect(keyringHandler.createAccount()).rejects.toMatchObject({
message: `Error creating account: ${causeError.message}`,
cause: causeError,
});
await expect(keyringHandler.getAccounts()).rejects.toThrow(
'Error listing accounts',
);
});
});

Expand Down Expand Up @@ -721,5 +601,78 @@ describe('KeyringHandler', () => {
'Key derivation failed. Please check your connection and try again.',
);
});

it('delegates bip44:discover to accountsService.createAccounts and returns the account', async () => {
const createdAccounts = [
{
id: mockAccount.id,
address: mockAccount.address,
type: mockAccount.type,
options: mockAccount.options,
methods: mockAccount.methods,
scopes: mockAccount.scopes,
},
];
const options: KeyringBatchCreateAccountOptions = {
type: AccountCreationType.Bip44Discover,
entropySource: 'entropy-source-1',
groupIndex: 0,
};

mockAccountsService.createAccounts.mockResolvedValue(createdAccounts);

const result = await keyringHandler.createAccounts(options);

expect(mockAccountsService.createAccounts).toHaveBeenCalledWith(options);
expect(result).toStrictEqual(createdAccounts);
});

it('delegates bip44:discover to accountsService.createAccounts and returns empty array when no activity', async () => {
const options: KeyringBatchCreateAccountOptions = {
type: AccountCreationType.Bip44Discover,
entropySource: 'entropy-source-1',
groupIndex: 5,
};

mockAccountsService.createAccounts.mockResolvedValue([]);

const result = await keyringHandler.createAccounts(options);

expect(mockAccountsService.createAccounts).toHaveBeenCalledWith(options);
expect(result).toStrictEqual([]);
});
});

describe('getAccountAssets', () => {
it('returns asset types for an account', async () => {
const result = await keyringHandler.getAccountAssets(mockAccount.id);

expect(result).toStrictEqual([]);
expect(mockAssetsService.getByKeyringAccountId).toHaveBeenCalledWith(
mockAccount.id,
);
});

it('throws when the account is not found', async () => {
mockAccountsService.findById.mockResolvedValue(null);

await expect(
keyringHandler.getAccountAssets(mockAccount.id),
).rejects.toThrow('not found');
});
});

describe('getAccountTransactions', () => {
it('returns paginated transactions for an account', async () => {
const result = await keyringHandler.getAccountTransactions(
mockAccount.id,
{ limit: 10, next: null },
);

expect(result).toStrictEqual({ data: [], next: null });
expect(mockTransactionsService.findByAccounts).toHaveBeenCalledWith([
mockAccount,
]);
});
});
});
Loading
Loading