Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
9 changes: 9 additions & 0 deletions packages/keyring-api/src/api/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ export enum SolAccountType {
DataAccount = 'solana:data-account',
}

/**
* Supported Tron account types.
*/
export enum TrxAccountType {
Eoa = 'tron:eoa',
}

/**
* A generic account type. It can be used to represent any account type that is
* not covered by the other account types. It only applies to non-EVM chains.
Expand All @@ -49,6 +56,7 @@ export type KeyringAccountType =
| `${BtcAccountType.P2wpkh}`
| `${BtcAccountType.P2tr}`
| `${SolAccountType.DataAccount}`
| `${TrxAccountType.Eoa}`
| `${AnyAccountType.Account}`;

/**
Expand All @@ -75,6 +83,7 @@ export const KeyringAccountStruct = object({
`${BtcAccountType.P2wpkh}`,
`${BtcAccountType.P2tr}`,
`${SolAccountType.DataAccount}`,
`${TrxAccountType.Eoa}`,
`${AnyAccountType.Account}`,
]),

Expand Down
1 change: 1 addition & 0 deletions packages/keyring-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './api';
export * from './btc';
export * from './sol';
export * from './eth';
export * from './trx';
export type * from './contexts';
export * from './rpc';
export * from './events';
10 changes: 10 additions & 0 deletions packages/keyring-api/src/trx/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// istanbul ignore file

/**
* Scopes for TRON account type. See {@link KeyringAccount.scopes}.
*/
export enum TrxScope {
Mainnet = 'tron:728126428',
Nile = 'tron:3448148188',
Shasta = 'tron:2494104990',
}
2 changes: 2 additions & 0 deletions packages/keyring-api/src/trx/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './constants';
export * from './types';
8 changes: 8 additions & 0 deletions packages/keyring-api/src/trx/types.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { Extends } from '@metamask/keyring-utils';
import { expectTrue } from '@metamask/keyring-utils';

import type { TrxEoaAccount } from './types';
import type { KeyringAccount } from '../api';

// `TrxEoaAccount` extends `KeyringAccount`
expectTrue<Extends<TrxEoaAccount, KeyringAccount>>();
28 changes: 28 additions & 0 deletions packages/keyring-api/src/trx/types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { TrxAddressStruct } from './types';

describe('types', () => {
describe('TrxAddressStruct', () => {
it.each([
'TRjE1H8dxypKM1NZRdysbs9wo7huR4bdNz',
'TGFCAXqwaBofiLZPWEp5rnin5LKDTKYpct',
])('is valid address: %s', (address) => {
expect(() => TrxAddressStruct.assert(address)).not.toThrow();
});

it.each([
// Invalid lengths, too long (45 chars)
'TRjE1H8dxypKM1NZRdysbs9wo7huR4bdNz1',
// Too short (31 chars)
'TRjE1H8dxypKM1NZRdywo7huR4bdNz',
// Empty or invalid input
'',
// Eth style address
'0x1234',
'not-an-address',
])('rejects invalid address: %s', (address) => {
expect(() => TrxAddressStruct.assert(address)).toThrow(
`Expected a value of type \`TrxAddress\`, but received: \`"${address}"\``,
);
});
});
});
49 changes: 49 additions & 0 deletions packages/keyring-api/src/trx/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { object } from '@metamask/keyring-utils';
import type { Infer } from '@metamask/superstruct';
import { array, enums, literal, nonempty } from '@metamask/superstruct';
import { definePattern } from '@metamask/utils';

import {
CaipChainIdStruct,
KeyringAccountStruct,
TrxAccountType,
} from '../api';

/**
* TRON addresses are Base58-encoded strings that are exactly 34 characters long
* and start with the letter 'T'.
*/
export const TrxAddressStruct = definePattern(
'TrxAddress',
/^T[0-9A-HJ-NP-Za-km-z]{33}$/iu,
);
Comment thread
zone-live marked this conversation as resolved.

/**
* Supported TRON methods.
*/
export enum TrxMethod {
SignMessageV2 = 'signMessageV2',
VerifyMessageV2 = 'verifyMessageV2',
}

export const TrxEoaAccountStruct = object({
...KeyringAccountStruct.schema,
/**
* Account address.
*/
address: TrxAddressStruct,
/**
* Account type.
*/
type: literal(`${TrxAccountType.Eoa}`),
/**
* Account supported scopes (CAIP-2 chain IDs).
*/
scopes: nonempty(array(CaipChainIdStruct)),
/**
* Account supported methods.
*/
methods: array(enums(Object.values(TrxMethod))),
});

export type TrxEoaAccount = Infer<typeof TrxEoaAccountStruct>;
21 changes: 21 additions & 0 deletions packages/keyring-snap-bridge/src/SnapKeyring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import {
KeyringRpcMethod,
CreateAccountRequestStruct,
AnyAccountType,
TrxScope,
TrxMethod,
TrxAccountType,
} from '@metamask/keyring-api';
import type { JsonRpcRequest } from '@metamask/keyring-utils';
import type { HandleSnapRequest } from '@metamask/snaps-controllers';
Expand Down Expand Up @@ -196,6 +199,15 @@ describe('SnapKeyring', () => {
type: SolAccountType.DataAccount,
};

const trxEoaAccount = {
id: '890ee179-5ab5-449d-9c25-34e12c1ada67',
address: 'TJRabPrwbZy45sbavfcjinPJC18kjpRTv8',
options: {},
methods: [...Object.values(TrxMethod)],
scopes: [TrxScope.Mainnet, TrxScope.Nile, TrxScope.Shasta],
type: TrxAccountType.Eoa,
};

// This is an example generic chain, its scope and address are bogus.
const anyGenericAccount: KeyringAccount = {
id: '6131c205-26b5-40a8-9626-4761423bb1d5',
Expand Down Expand Up @@ -241,6 +253,7 @@ describe('SnapKeyring', () => {
btcAccountP2sh,
btcAccountP2tr,
solDataAccount,
trxEoaAccount,
anyGenericAccount,
] as const;

Expand Down Expand Up @@ -1167,6 +1180,7 @@ describe('SnapKeyring', () => {
btcAccountP2sh.address,
btcAccountP2tr.address,
solDataAccount.address,
trxEoaAccount.address,
anyGenericAccount.address,
]);
});
Expand All @@ -1186,6 +1200,7 @@ describe('SnapKeyring', () => {
btcAccountP2sh.address,
btcAccountP2tr.address,
solDataAccount.address,
trxEoaAccount.address,
anyGenericAccount.address,
]);
});
Expand Down Expand Up @@ -1366,6 +1381,7 @@ describe('SnapKeyring', () => {
btcAccountP2sh.address,
btcAccountP2tr.address,
solDataAccount.address,
trxEoaAccount.address,
anyGenericAccount.address,
]);
});
Expand All @@ -1384,6 +1400,7 @@ describe('SnapKeyring', () => {
[btcAccountP2sh.id]: { account: btcAccountP2sh, snapId },
[btcAccountP2tr.id]: { account: btcAccountP2tr, snapId },
[solDataAccount.id]: { account: solDataAccount, snapId },
[trxEoaAccount.id]: { account: trxEoaAccount, snapId },
[anyGenericAccount.id]: { account: anyGenericAccount, snapId },
},
};
Expand Down Expand Up @@ -1437,6 +1454,7 @@ describe('SnapKeyring', () => {
btcAccountP2tr,
btcTestnetAccount,
solDataAccount,
trxEoaAccount,
])('migrates accounts v1: %s', async (expectedAccount: KeyringAccount) => {
// A v1 account has no scopes, so remove it.
const state = {
Expand All @@ -1459,6 +1477,7 @@ describe('SnapKeyring', () => {
btcAccount,
btcTestnetAccount,
solDataAccount,
trxEoaAccount,
anyGenericAccount,
])(
'migrates v2 accounts to v1 accounts is noop: %s',
Expand Down Expand Up @@ -2118,6 +2137,7 @@ describe('SnapKeyring', () => {
accounts[7].address,
accounts[8].address,
accounts[9].address,
accounts[10].address,
]);
});

Expand All @@ -2135,6 +2155,7 @@ describe('SnapKeyring', () => {
accounts[7].address,
accounts[8].address,
accounts[9].address,
accounts[10].address,
]);
expect(console.error).toHaveBeenCalledWith(
"Account '0xc728514df8a7f9271f4b7a4dd2aa6d2d723d3ee3' may not have been removed from snap 'local:snap.mock':",
Expand Down
8 changes: 7 additions & 1 deletion packages/keyring-snap-bridge/src/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
KeyringAccountStruct,
SolAccountType,
SolDataAccountStruct,
TrxAccountType,
TrxEoaAccountStruct,
} from '@metamask/keyring-api';
import { assert, omit, type Infer } from '@metamask/superstruct';

Expand Down Expand Up @@ -66,6 +68,10 @@ export function assertKeyringAccount<
assert(account, EthEoaAccountStruct);
return account;
}
case TrxAccountType.Eoa: {
assert(account, TrxEoaAccountStruct);
return account;
}
case AnyAccountType.Account: {
assert(account, KeyringAccountStruct);
return account;
Expand Down Expand Up @@ -95,5 +101,5 @@ export function transformAccount(
: accountToTransform;

// We still assert that the converted account is valid according to their account's type.
return assertKeyringAccount(account);
return assertKeyringAccount(account as { type: KeyringAccountType });
Comment thread
zone-live marked this conversation as resolved.
Outdated
}
14 changes: 12 additions & 2 deletions packages/keyring-snap-bridge/src/migrations/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ import {
BtcScope,
EthScope,
SolScope,
TrxScope,
TrxAccountType,
} from '@metamask/keyring-api';
import type {
CaipChainId,
KeyringAccount,
KeyringAccountType,
} from '@metamask/keyring-api';
import type { CaipChainId, KeyringAccount } from '@metamask/keyring-api';
import { isBtcMainnetAddress } from '@metamask/keyring-utils';
import { is } from '@metamask/superstruct';

Expand Down Expand Up @@ -69,6 +75,10 @@ export function getScopesForAccountV1(
// Solana account supports multiple chains.
return [SolScope.Mainnet, SolScope.Testnet, SolScope.Devnet];
}
case TrxAccountType.Eoa: {
// TRON account supports multiple chains.
return [TrxScope.Mainnet, TrxScope.Nile, TrxScope.Shasta];
}
default:
// We re-use EOA scopes if we don't know what to do for now.
return [EthScope.Eoa];
Expand Down Expand Up @@ -102,7 +112,7 @@ export function transformAccountV1(

// For all other non-EVM and ERC4337 Snap accounts, the `scopes` is required, and
// each `*AccountStruct` should assert that automatically.
return assertKeyringAccount(accountV1);
return assertKeyringAccount(accountV1 as { type: KeyringAccountType });
Comment thread
zone-live marked this conversation as resolved.
Outdated
}

/**
Expand Down
Loading