Skip to content

Commit 36391d9

Browse files
feat(keyring-api): add TRON account support (#308)
1 parent 103b1fd commit 36391d9

8 files changed

Lines changed: 122 additions & 3 deletions

File tree

packages/keyring-api/src/api/account.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AccountIdStruct, object } from '@metamask/keyring-utils';
22
import type { Infer } from '@metamask/superstruct';
3-
import { nonempty, array, enums, record, string } from '@metamask/superstruct';
3+
import { array, enums, nonempty, record, string } from '@metamask/superstruct';
44
import { JsonStruct } from '@metamask/utils';
55

66
import { CaipChainIdStruct } from './caip';
@@ -30,6 +30,13 @@ export enum SolAccountType {
3030
DataAccount = 'solana:data-account',
3131
}
3232

33+
/**
34+
* Supported TRON account types.
35+
*/
36+
export enum TrxAccountType {
37+
Eoa = 'tron:eoa',
38+
}
39+
3340
/**
3441
* Supported account types.
3542
*/
@@ -40,7 +47,8 @@ export type KeyringAccountType =
4047
| `${BtcAccountType.P2sh}`
4148
| `${BtcAccountType.P2wpkh}`
4249
| `${BtcAccountType.P2tr}`
43-
| `${SolAccountType.DataAccount}`;
50+
| `${SolAccountType.DataAccount}`
51+
| `${TrxAccountType.Eoa}`;
4452

4553
/**
4654
* A struct which represents a Keyring account object. It is abstract enough to
@@ -66,6 +74,7 @@ export const KeyringAccountStruct = object({
6674
`${BtcAccountType.P2wpkh}`,
6775
`${BtcAccountType.P2tr}`,
6876
`${SolAccountType.DataAccount}`,
77+
`${TrxAccountType.Eoa}`,
6978
]),
7079

7180
/**

packages/keyring-api/src/api/address.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@ import type { CaipAccountId } from './caip';
66
import { BtcScope } from '../btc';
77
import { EthScope } from '../eth';
88
import { SolScope } from '../sol';
9+
import { TrxScope } from '../trx';
910

1011
const MOCK_ETH_ADDRESS = '0x6431726EEE67570BF6f0Cf892aE0a3988F03903F';
1112
const MOCK_BTC_ADDRESS = 'bc1qwl8399fz829uqvqly9tcatgrgtwp3udnhxfq4k';
1213
const MOCK_SOL_ADDRESS = '7EcDhSYGxXyscszYEp35KHN8vvw3svAuLKTzXwCFLtV';
14+
const MOCK_TRX_ADDRESS = 'TT2T17KZhoDu47i2E4FWxfG79zdkEWkU9N';
1315

1416
describe('ResolveAccountAddress', () => {
1517
it.each([
1618
`${EthScope.Eoa}:${MOCK_ETH_ADDRESS}`,
1719
`${BtcScope.Mainnet}:${MOCK_BTC_ADDRESS}`,
1820
`${SolScope.Mainnet}:${MOCK_SOL_ADDRESS}`,
21+
`${TrxScope.Mainnet}:${MOCK_TRX_ADDRESS}`,
1922
] as CaipAccountId[])(
2023
'allows CAIP-10 account ID: %s',
2124
(address: CaipAccountId) => {
@@ -28,7 +31,12 @@ describe('ResolveAccountAddress', () => {
2831
},
2932
);
3033

31-
it.each([MOCK_ETH_ADDRESS, MOCK_BTC_ADDRESS, MOCK_SOL_ADDRESS])(
34+
it.each([
35+
MOCK_ETH_ADDRESS,
36+
MOCK_BTC_ADDRESS,
37+
MOCK_SOL_ADDRESS,
38+
MOCK_TRX_ADDRESS,
39+
])(
3240
'throws an error if address is not a CAIP-10 account ID: %s',
3341
(address: string) => {
3442
const resolvedAddress: ResolvedAccountAddress = {

packages/keyring-api/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export * from './api';
22
export * from './btc';
33
export * from './sol';
44
export * from './eth';
5+
export * from './trx';
56
export type * from './contexts';
67
export * from './rpc';
78
export * from './events';
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// istanbul ignore file
2+
3+
/**
4+
* Scopes for TRON account type. See {@link KeyringAccount.scopes}.
5+
*/
6+
export enum TrxScope {
7+
Mainnet = 'tron:728126428',
8+
Nile = 'tron:3448148188',
9+
Shasta = 'tron:2494104990',
10+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './constants';
2+
export * from './types';
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Extends } from '@metamask/keyring-utils';
2+
import { expectTrue } from '@metamask/keyring-utils';
3+
4+
import type { TrxEoaAccount } from './types';
5+
import type { KeyringAccount } from '../api';
6+
7+
// `TrxEoaAccount` extends `KeyringAccount`
8+
expectTrue<Extends<TrxEoaAccount, KeyringAccount>>();
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { TrxAddressStruct } from './types';
2+
3+
describe('types', () => {
4+
describe('TrxAddressStruct', () => {
5+
it.each([
6+
'TT2T17KZhoDu47i2E4FWxfG79zdkEWkU9N',
7+
'TE2RzoSV3wFK99w6J9UnnZ4vLfXYoxvRwP',
8+
])('is valid address: %s', (address) => {
9+
expect(() => TrxAddressStruct.assert(address)).not.toThrow();
10+
});
11+
12+
it.each([
13+
// Invalid lengths, too long (45 chars)
14+
'TT2T17KZhoDu47i2E4FWxfG79zdkEWkU9NTT2T17KZhoD',
15+
// Too short (24 chars)
16+
'TT2T17KZhoDu47i2E4FWxfG7',
17+
// Empty or invalid input
18+
'',
19+
// Eth style address
20+
'0x1234',
21+
'not-an-address',
22+
])('rejects invalid address: %s', (address) => {
23+
expect(() => TrxAddressStruct.assert(address)).toThrow(
24+
`Expected a value of type \`TrxAddress\`, but received: \`"${address}"\``,
25+
);
26+
});
27+
});
28+
});
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { object } from '@metamask/keyring-utils';
2+
import type { Infer } from '@metamask/superstruct';
3+
import { array, enums, literal, nonempty } from '@metamask/superstruct';
4+
import { definePattern } from '@metamask/utils';
5+
6+
import {
7+
CaipChainIdStruct,
8+
KeyringAccountStruct,
9+
TrxAccountType,
10+
} from '../api';
11+
12+
/**
13+
* TRON addresses are Base58-encoded strings that are exactly 34 characters long
14+
* and start with the letter 'T'.
15+
*/
16+
export const TrxAddressStruct = definePattern(
17+
'TrxAddress',
18+
/^T[1-9A-HJ-NP-Za-km-z]{33}$/u,
19+
);
20+
21+
/**
22+
* Supported TRON methods.
23+
*/
24+
export enum TrxMethod {
25+
SignMessageV2 = 'signMessageV2',
26+
VerifyMessageV2 = 'verifyMessageV2',
27+
}
28+
29+
export const TrxEoaAccountStruct = object({
30+
...KeyringAccountStruct.schema,
31+
32+
/**
33+
* Account address.
34+
*/
35+
address: TrxAddressStruct,
36+
37+
/**
38+
* Account type.
39+
*/
40+
type: literal(`${TrxAccountType.Eoa}`),
41+
42+
/**
43+
* Account supported scopes (CAIP-2 chain IDs).
44+
*/
45+
scopes: nonempty(array(CaipChainIdStruct)),
46+
47+
/**
48+
* Account supported methods.
49+
*/
50+
methods: array(enums(Object.values(TrxMethod))),
51+
});
52+
53+
export type TrxEoaAccount = Infer<typeof TrxEoaAccountStruct>;

0 commit comments

Comments
 (0)