@portal-hq/web 3.15.0-alpha.2 → 3.16.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -429,6 +429,14 @@ class Portal {
429
429
  return solAddress;
430
430
  });
431
431
  }
432
+ getTronAddress() {
433
+ var _a, _b, _c, _d;
434
+ return __awaiter(this, void 0, void 0, function* () {
435
+ const client = yield ((_a = this.mpc) === null || _a === void 0 ? void 0 : _a.getClient());
436
+ const tronAddress = ((_d = (_c = (_b = client === null || client === void 0 ? void 0 : client.metadata) === null || _b === void 0 ? void 0 : _b.namespaces) === null || _c === void 0 ? void 0 : _c.tron) === null || _d === void 0 ? void 0 : _d.address) || '';
437
+ return tronAddress;
438
+ });
439
+ }
432
440
  doesWalletExist(chainId) {
433
441
  var _a, _b, _c, _d, _e;
434
442
  return __awaiter(this, void 0, void 0, function* () {
@@ -551,6 +559,18 @@ class Portal {
551
559
  });
552
560
  break;
553
561
  }
562
+ case 'tron': {
563
+ const tronTx = buildTxResponse;
564
+ response = yield this.request({
565
+ chainId,
566
+ method: 'tron_sendTransaction',
567
+ params: [tronTx.transaction.id],
568
+ sponsorGas: params.sponsorGas,
569
+ signatureApprovalMemo: params.signatureApprovalMemo,
570
+ traceId,
571
+ });
572
+ break;
573
+ }
554
574
  default: {
555
575
  throw new Error(`Unsupported chain namespace: ${chainNamespace}`);
556
576
  }
@@ -570,6 +590,9 @@ class Portal {
570
590
  * - **Solana (`solana:*`):** Polls `getSignatureStatuses` until the
571
591
  * commitment level from `options.commitment`
572
592
  * is met (default `confirmed`).
593
+ * - **TRON (`tron:*`):** Confirmation polling is not supported.
594
+ * Always returns `false`. Transaction status must be verified
595
+ * directly via TRON RPC using the transaction ID returned by `sendAsset`.
573
596
  * - **Other networks:** Returns `false` (unsupported for polling).
574
597
  *
575
598
  * Optional `options` tune poll/timeout for all polled chains; EVM also accepts
@@ -1164,5 +1187,6 @@ var ChainNamespace;
1164
1187
  (function (ChainNamespace) {
1165
1188
  ChainNamespace["EIP155"] = "eip155";
1166
1189
  ChainNamespace["SOLANA"] = "solana";
1190
+ ChainNamespace["TRON"] = "tron";
1167
1191
  })(ChainNamespace = exports.ChainNamespace || (exports.ChainNamespace = {}));
1168
1192
  exports.default = Portal;
@@ -444,6 +444,12 @@ describe('Portal', () => {
444
444
  expect(res).toBe(constants_1.mockSolanaAddress);
445
445
  }));
446
446
  });
447
+ describe('getTronAddress', () => {
448
+ it("should return the wallet's TRON address correctly", () => __awaiter(void 0, void 0, void 0, function* () {
449
+ const res = yield portal.getTronAddress();
450
+ expect(res).toBe(constants_1.mockTronAddress);
451
+ }));
452
+ });
447
453
  describe('doesWalletExist', () => {
448
454
  it('should successfully return if wallets exist', () => __awaiter(void 0, void 0, void 0, function* () {
449
455
  const res = yield portal.doesWalletExist();
@@ -617,6 +623,53 @@ describe('Portal', () => {
617
623
  });
618
624
  }));
619
625
  });
626
+ describe('sendAsset (TRON)', () => {
627
+ const tronChainId = 'tron:nile';
628
+ const tronRpcUrl = 'https://test-tron-rpc';
629
+ beforeEach(() => {
630
+ portal = new _1.default({
631
+ rpcConfig: Object.assign(Object.assign({}, constants_1.mockRpcConfig), { [tronChainId]: tronRpcUrl }),
632
+ });
633
+ portal.mpc = mpc_1.default;
634
+ portal.provider = provider_1.default;
635
+ });
636
+ it('should build the transaction and sign it via tron_sendTransaction', () => __awaiter(void 0, void 0, void 0, function* () {
637
+ ;
638
+ portal.mpc.buildTransaction.mockResolvedValueOnce(constants_1.mockBuiltTronTransaction);
639
+ portal.provider.request.mockResolvedValueOnce(constants_1.mockSignedHash);
640
+ const result = yield portal.sendAsset(tronChainId, {
641
+ to: constants_1.mockBuiltTronTransaction.metadata.toAddress,
642
+ token: 'NATIVE',
643
+ amount: '1',
644
+ });
645
+ expect(result).toBe(constants_1.mockSignedHash);
646
+ expect(portal.mpc.buildTransaction).toHaveBeenCalledWith(tronChainId, constants_1.mockBuiltTronTransaction.metadata.toAddress, 'NATIVE', '1', expect.any(String));
647
+ expect(portal.provider.request).toHaveBeenCalledWith(expect.objectContaining({
648
+ chainId: tronChainId,
649
+ method: 'tron_sendTransaction',
650
+ params: [constants_1.mockBuiltTronTransaction.transaction.id],
651
+ }));
652
+ }));
653
+ it('should propagate build errors', () => __awaiter(void 0, void 0, void 0, function* () {
654
+ ;
655
+ portal.mpc.buildTransaction.mockRejectedValueOnce(new Error('Build failed'));
656
+ yield expect(portal.sendAsset(tronChainId, {
657
+ to: constants_1.mockBuiltTronTransaction.metadata.toAddress,
658
+ token: 'NATIVE',
659
+ amount: '1',
660
+ })).rejects.toThrow('Failed to send asset: Build failed');
661
+ }));
662
+ it('should propagate signing errors', () => __awaiter(void 0, void 0, void 0, function* () {
663
+ ;
664
+ portal.mpc.buildTransaction.mockResolvedValueOnce(constants_1.mockBuiltTronTransaction);
665
+ portal.provider.request.mockRejectedValueOnce(new Error('Signing failed'));
666
+ yield expect(portal.sendAsset(tronChainId, {
667
+ to: constants_1.mockBuiltTronTransaction.metadata.toAddress,
668
+ token: 'NATIVE',
669
+ amount: '1',
670
+ })).rejects.toThrow('Failed to send asset: Signing failed');
671
+ }));
672
+ });
620
673
  describe('getBalances', () => {
621
674
  it('should correctly call mpc.getBalances', () => __awaiter(void 0, void 0, void 0, function* () {
622
675
  yield portal.getBalances('eip155:1');
@@ -14,7 +14,7 @@ const errors_1 = require("./errors");
14
14
  const logger_1 = require("../logger");
15
15
  const index_1 = require("../index");
16
16
  const trace_1 = require("../shared/trace");
17
- const WEB_SDK_VERSION = '3.15.0-alpha.2';
17
+ const WEB_SDK_VERSION = '3.16.0-alpha.0';
18
18
  class Mpc {
19
19
  get ready() {
20
20
  return this._ready;
@@ -42,15 +42,14 @@ class Mpc {
42
42
  if (!requestId) {
43
43
  return;
44
44
  }
45
+ const forceRefresh = (data === null || data === void 0 ? void 0 : data.forceRefresh) === true;
45
46
  const getter = this.firebaseGetToken;
46
47
  void (() => __awaiter(this, void 0, void 0, function* () {
47
48
  try {
48
49
  if (!getter) {
49
50
  throw new Error('Firebase storage is not configured (getToken missing)');
50
51
  }
51
- const token = yield getter({
52
- forceRefresh: (data === null || data === void 0 ? void 0 : data.forceRefresh) === true,
53
- });
52
+ const token = yield getter({ forceRefresh });
54
53
  this.postMessage({
55
54
  type: 'portal:firebase:requestTokenResult',
56
55
  data: { requestId, token },
@@ -132,6 +132,8 @@ var RequestMethod;
132
132
  RequestMethod["sol_signAndSendTransaction"] = "sol_signAndSendTransaction";
133
133
  RequestMethod["sol_signMessage"] = "sol_signMessage";
134
134
  RequestMethod["sol_signTransaction"] = "sol_signTransaction";
135
+ // TRON Wallet Methods
136
+ RequestMethod["tron_sendTransaction"] = "tron_sendTransaction";
135
137
  })(RequestMethod = exports.RequestMethod || (exports.RequestMethod = {}));
136
138
  const passiveSignerMethods = [
137
139
  RequestMethod.eth_accounts,
@@ -153,6 +155,7 @@ const signerMethods = [
153
155
  RequestMethod.sol_signAndSendTransaction,
154
156
  RequestMethod.sol_signMessage,
155
157
  RequestMethod.sol_signTransaction,
158
+ RequestMethod.tron_sendTransaction,
156
159
  ];
157
160
  const iframeProxiedMethodStrings = [
158
161
  'eth_getTransactionReceipt',
@@ -177,6 +180,14 @@ class Provider {
177
180
  throw new Error(`[PortalProvider] Chain ID must be prefixed with "solana:" for the operation, got ${chainId}`);
178
181
  }
179
182
  };
183
+ this.enforceTronChainId = (chainId) => {
184
+ if (!chainId) {
185
+ throw new Error('[PortalProvider] Chain ID is required for the operation');
186
+ }
187
+ if (!chainId.startsWith('tron:')) {
188
+ throw new Error(`[PortalProvider] Chain ID must be prefixed with "tron:" for the operation, got ${chainId}`);
189
+ }
190
+ };
180
191
  this.buildParams = (method, txParams) => {
181
192
  let params = txParams;
182
193
  switch (method) {
@@ -470,6 +481,14 @@ class Provider {
470
481
  })));
471
482
  return result;
472
483
  }
484
+ case RequestMethod.tron_sendTransaction: {
485
+ this.enforceTronChainId(chainId);
486
+ const result = yield this.portal.mpc.sign(Object.assign({ chainId: chainId, method, params: this.buildParams(method, params), rpcUrl: this.portal.getRpcUrl(chainId), sponsorGas,
487
+ traceId }, (signatureApprovalMemo !== undefined && {
488
+ signatureApprovalMemo,
489
+ })));
490
+ return result;
491
+ }
473
492
  default:
474
493
  throw new Error('[PortalProvider] Method "' + method + '" not supported');
475
494
  }
@@ -549,6 +549,44 @@ describe('Provider', () => {
549
549
  });
550
550
  }));
551
551
  });
552
+ describe('tron_sendTransaction', () => {
553
+ it('should throw an error if no chainId is provided alongside tron_sendTransaction', () => __awaiter(void 0, void 0, void 0, function* () {
554
+ yield expect(provider.request({
555
+ method: __1.RequestMethod.tron_sendTransaction,
556
+ params: ['test'],
557
+ })).rejects.toThrow(new Error(`[PortalProvider] Chain ID is required for the operation`));
558
+ }));
559
+ it('should throw an error if malformed chainId is provided alongside tron_sendTransaction', () => __awaiter(void 0, void 0, void 0, function* () {
560
+ const chainId = 'unsupported:chain';
561
+ yield expect(provider.request({
562
+ chainId,
563
+ method: __1.RequestMethod.tron_sendTransaction,
564
+ params: ['test'],
565
+ })).rejects.toThrow(new Error(`[PortalProvider] Chain ID must be prefixed with "tron:" for the operation, got ${chainId}`));
566
+ }));
567
+ it('should successfully handle a tron_sendTransaction request', () => __awaiter(void 0, void 0, void 0, function* () {
568
+ const result = yield provider.request({
569
+ chainId: 'tron:nile',
570
+ method: __1.RequestMethod.tron_sendTransaction,
571
+ params: ['test'],
572
+ });
573
+ expect(result).toEqual(constants_1.mockSignedHash);
574
+ expect(portal_1.default.mpc.sign).toHaveBeenCalledWith({
575
+ chainId: 'tron:nile',
576
+ method: __1.RequestMethod.tron_sendTransaction,
577
+ params: 'test',
578
+ rpcUrl: constants_1.mockRpcUrl,
579
+ sponsorGas: undefined,
580
+ traceId: 'mock-trace-id-12345',
581
+ });
582
+ expect(provider.emit).toHaveBeenCalledWith('portal_signatureReceived', {
583
+ chainId: 'tron:nile',
584
+ method: __1.RequestMethod.tron_sendTransaction,
585
+ params: ['test'],
586
+ signature: result,
587
+ });
588
+ }));
589
+ });
552
590
  });
553
591
  describe('Signer methods without auto-approval', () => {
554
592
  const mockSigningRequestedHandler = jest.fn();
@@ -1200,6 +1238,59 @@ describe('Provider', () => {
1200
1238
  expect(mockConsoleWarn).toHaveBeenCalledWith("[PortalProvider] Request for signing method 'sol_signTransaction' could not be completed because it was not approved by the user.");
1201
1239
  }));
1202
1240
  });
1241
+ describe('tron_sendTransaction', () => {
1242
+ it('should successfully handle an approved tron_sendTransaction request', () => __awaiter(void 0, void 0, void 0, function* () {
1243
+ provider.on('portal_signingRequested', () => {
1244
+ provider.emit('portal_signingApproved', {
1245
+ method: __1.RequestMethod.tron_sendTransaction,
1246
+ params: ['test'],
1247
+ });
1248
+ });
1249
+ const result = yield provider.request({
1250
+ chainId: 'tron:nile',
1251
+ method: __1.RequestMethod.tron_sendTransaction,
1252
+ params: ['test'],
1253
+ });
1254
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1255
+ method: __1.RequestMethod.tron_sendTransaction,
1256
+ params: ['test'],
1257
+ });
1258
+ expect(result).toEqual(constants_1.mockSignedHash);
1259
+ expect(portal_1.default.mpc.sign).toHaveBeenCalledWith({
1260
+ chainId: 'tron:nile',
1261
+ method: __1.RequestMethod.tron_sendTransaction,
1262
+ params: 'test',
1263
+ rpcUrl: constants_1.mockRpcUrl,
1264
+ sponsorGas: undefined,
1265
+ traceId: 'mock-trace-id-12345',
1266
+ });
1267
+ expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({
1268
+ chainId: 'tron:nile',
1269
+ method: __1.RequestMethod.tron_sendTransaction,
1270
+ params: ['test'],
1271
+ signature: result,
1272
+ });
1273
+ }));
1274
+ it('should successfully handle a rejected tron_sendTransaction request', () => __awaiter(void 0, void 0, void 0, function* () {
1275
+ provider.on('portal_signingRequested', () => {
1276
+ provider.emit('portal_signingRejected', {
1277
+ method: __1.RequestMethod.tron_sendTransaction,
1278
+ params: ['test'],
1279
+ });
1280
+ });
1281
+ const result = yield provider.request({
1282
+ chainId: 'tron:nile',
1283
+ method: __1.RequestMethod.tron_sendTransaction,
1284
+ params: ['test'],
1285
+ });
1286
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1287
+ method: __1.RequestMethod.tron_sendTransaction,
1288
+ params: ['test'],
1289
+ });
1290
+ expect(result).toEqual(undefined);
1291
+ expect(mockConsoleWarn).toHaveBeenCalledWith("[PortalProvider] Request for signing method 'tron_sendTransaction' could not be completed because it was not approved by the user.");
1292
+ }));
1293
+ });
1203
1294
  });
1204
1295
  describe('Non-signer methods', () => {
1205
1296
  beforeAll(() => {
package/lib/esm/index.js CHANGED
@@ -400,6 +400,14 @@ class Portal {
400
400
  return solAddress;
401
401
  });
402
402
  }
403
+ getTronAddress() {
404
+ var _a, _b, _c, _d;
405
+ return __awaiter(this, void 0, void 0, function* () {
406
+ const client = yield ((_a = this.mpc) === null || _a === void 0 ? void 0 : _a.getClient());
407
+ const tronAddress = ((_d = (_c = (_b = client === null || client === void 0 ? void 0 : client.metadata) === null || _b === void 0 ? void 0 : _b.namespaces) === null || _c === void 0 ? void 0 : _c.tron) === null || _d === void 0 ? void 0 : _d.address) || '';
408
+ return tronAddress;
409
+ });
410
+ }
403
411
  doesWalletExist(chainId) {
404
412
  var _a, _b, _c, _d, _e;
405
413
  return __awaiter(this, void 0, void 0, function* () {
@@ -522,6 +530,18 @@ class Portal {
522
530
  });
523
531
  break;
524
532
  }
533
+ case 'tron': {
534
+ const tronTx = buildTxResponse;
535
+ response = yield this.request({
536
+ chainId,
537
+ method: 'tron_sendTransaction',
538
+ params: [tronTx.transaction.id],
539
+ sponsorGas: params.sponsorGas,
540
+ signatureApprovalMemo: params.signatureApprovalMemo,
541
+ traceId,
542
+ });
543
+ break;
544
+ }
525
545
  default: {
526
546
  throw new Error(`Unsupported chain namespace: ${chainNamespace}`);
527
547
  }
@@ -541,6 +561,9 @@ class Portal {
541
561
  * - **Solana (`solana:*`):** Polls `getSignatureStatuses` until the
542
562
  * commitment level from `options.commitment`
543
563
  * is met (default `confirmed`).
564
+ * - **TRON (`tron:*`):** Confirmation polling is not supported.
565
+ * Always returns `false`. Transaction status must be verified
566
+ * directly via TRON RPC using the transaction ID returned by `sendAsset`.
544
567
  * - **Other networks:** Returns `false` (unsupported for polling).
545
568
  *
546
569
  * Optional `options` tune poll/timeout for all polled chains; EVM also accepts
@@ -1131,5 +1154,6 @@ export var ChainNamespace;
1131
1154
  (function (ChainNamespace) {
1132
1155
  ChainNamespace["EIP155"] = "eip155";
1133
1156
  ChainNamespace["SOLANA"] = "solana";
1157
+ ChainNamespace["TRON"] = "tron";
1134
1158
  })(ChainNamespace || (ChainNamespace = {}));
1135
1159
  export default Portal;
@@ -11,7 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  });
12
12
  };
13
13
  import Portal, { BackupMethods, GetTransactionsOrder } from '.';
14
- import { mockAddress, mockBackupConfig, mockBlockHashResponse, mockCipherText, mockClientResponse, mockEip155Address, mockEjectResult, mockEjectPrivateKeysResult, mockEthRpcUrl, mockEthTransaction, mockMpcBackupResponse, mockOrgBackupShare, mockOrgBackupShares, mockQuoteArgs, mockRpcConfig, mockSharesOnDevice, mockSignedHash, mockSolanaAddress, mockSolRpcUrl, } from './__mocks/constants';
14
+ import { mockAddress, mockBackupConfig, mockBlockHashResponse, mockBuiltTronTransaction, mockCipherText, mockClientResponse, mockEip155Address, mockEjectResult, mockEjectPrivateKeysResult, mockEthRpcUrl, mockEthTransaction, mockMpcBackupResponse, mockOrgBackupShare, mockOrgBackupShares, mockQuoteArgs, mockRpcConfig, mockSharesOnDevice, mockSignedHash, mockSolanaAddress, mockSolRpcUrl, mockTronAddress, } from './__mocks/constants';
15
15
  import mpcMock from './__mocks/portal/mpc';
16
16
  import providerMock from './__mocks/portal/provider';
17
17
  /**
@@ -416,6 +416,12 @@ describe('Portal', () => {
416
416
  expect(res).toBe(mockSolanaAddress);
417
417
  }));
418
418
  });
419
+ describe('getTronAddress', () => {
420
+ it("should return the wallet's TRON address correctly", () => __awaiter(void 0, void 0, void 0, function* () {
421
+ const res = yield portal.getTronAddress();
422
+ expect(res).toBe(mockTronAddress);
423
+ }));
424
+ });
419
425
  describe('doesWalletExist', () => {
420
426
  it('should successfully return if wallets exist', () => __awaiter(void 0, void 0, void 0, function* () {
421
427
  const res = yield portal.doesWalletExist();
@@ -589,6 +595,53 @@ describe('Portal', () => {
589
595
  });
590
596
  }));
591
597
  });
598
+ describe('sendAsset (TRON)', () => {
599
+ const tronChainId = 'tron:nile';
600
+ const tronRpcUrl = 'https://test-tron-rpc';
601
+ beforeEach(() => {
602
+ portal = new Portal({
603
+ rpcConfig: Object.assign(Object.assign({}, mockRpcConfig), { [tronChainId]: tronRpcUrl }),
604
+ });
605
+ portal.mpc = mpcMock;
606
+ portal.provider = providerMock;
607
+ });
608
+ it('should build the transaction and sign it via tron_sendTransaction', () => __awaiter(void 0, void 0, void 0, function* () {
609
+ ;
610
+ portal.mpc.buildTransaction.mockResolvedValueOnce(mockBuiltTronTransaction);
611
+ portal.provider.request.mockResolvedValueOnce(mockSignedHash);
612
+ const result = yield portal.sendAsset(tronChainId, {
613
+ to: mockBuiltTronTransaction.metadata.toAddress,
614
+ token: 'NATIVE',
615
+ amount: '1',
616
+ });
617
+ expect(result).toBe(mockSignedHash);
618
+ expect(portal.mpc.buildTransaction).toHaveBeenCalledWith(tronChainId, mockBuiltTronTransaction.metadata.toAddress, 'NATIVE', '1', expect.any(String));
619
+ expect(portal.provider.request).toHaveBeenCalledWith(expect.objectContaining({
620
+ chainId: tronChainId,
621
+ method: 'tron_sendTransaction',
622
+ params: [mockBuiltTronTransaction.transaction.id],
623
+ }));
624
+ }));
625
+ it('should propagate build errors', () => __awaiter(void 0, void 0, void 0, function* () {
626
+ ;
627
+ portal.mpc.buildTransaction.mockRejectedValueOnce(new Error('Build failed'));
628
+ yield expect(portal.sendAsset(tronChainId, {
629
+ to: mockBuiltTronTransaction.metadata.toAddress,
630
+ token: 'NATIVE',
631
+ amount: '1',
632
+ })).rejects.toThrow('Failed to send asset: Build failed');
633
+ }));
634
+ it('should propagate signing errors', () => __awaiter(void 0, void 0, void 0, function* () {
635
+ ;
636
+ portal.mpc.buildTransaction.mockResolvedValueOnce(mockBuiltTronTransaction);
637
+ portal.provider.request.mockRejectedValueOnce(new Error('Signing failed'));
638
+ yield expect(portal.sendAsset(tronChainId, {
639
+ to: mockBuiltTronTransaction.metadata.toAddress,
640
+ token: 'NATIVE',
641
+ amount: '1',
642
+ })).rejects.toThrow('Failed to send asset: Signing failed');
643
+ }));
644
+ });
592
645
  describe('getBalances', () => {
593
646
  it('should correctly call mpc.getBalances', () => __awaiter(void 0, void 0, void 0, function* () {
594
647
  yield portal.getBalances('eip155:1');
@@ -11,7 +11,7 @@ import { PortalMpcError } from './errors';
11
11
  import { sdkLogger } from '../logger';
12
12
  import { BackupMethods, } from '../index';
13
13
  import { generateTraceId } from '../shared/trace';
14
- const WEB_SDK_VERSION = '3.15.0-alpha.2';
14
+ const WEB_SDK_VERSION = '3.16.0-alpha.0';
15
15
  class Mpc {
16
16
  get ready() {
17
17
  return this._ready;
@@ -39,15 +39,14 @@ class Mpc {
39
39
  if (!requestId) {
40
40
  return;
41
41
  }
42
+ const forceRefresh = (data === null || data === void 0 ? void 0 : data.forceRefresh) === true;
42
43
  const getter = this.firebaseGetToken;
43
44
  void (() => __awaiter(this, void 0, void 0, function* () {
44
45
  try {
45
46
  if (!getter) {
46
47
  throw new Error('Firebase storage is not configured (getToken missing)');
47
48
  }
48
- const token = yield getter({
49
- forceRefresh: (data === null || data === void 0 ? void 0 : data.forceRefresh) === true,
50
- });
49
+ const token = yield getter({ forceRefresh });
51
50
  this.postMessage({
52
51
  type: 'portal:firebase:requestTokenResult',
53
52
  data: { requestId, token },
@@ -129,6 +129,8 @@ export var RequestMethod;
129
129
  RequestMethod["sol_signAndSendTransaction"] = "sol_signAndSendTransaction";
130
130
  RequestMethod["sol_signMessage"] = "sol_signMessage";
131
131
  RequestMethod["sol_signTransaction"] = "sol_signTransaction";
132
+ // TRON Wallet Methods
133
+ RequestMethod["tron_sendTransaction"] = "tron_sendTransaction";
132
134
  })(RequestMethod || (RequestMethod = {}));
133
135
  const passiveSignerMethods = [
134
136
  RequestMethod.eth_accounts,
@@ -150,6 +152,7 @@ const signerMethods = [
150
152
  RequestMethod.sol_signAndSendTransaction,
151
153
  RequestMethod.sol_signMessage,
152
154
  RequestMethod.sol_signTransaction,
155
+ RequestMethod.tron_sendTransaction,
153
156
  ];
154
157
  const iframeProxiedMethodStrings = [
155
158
  'eth_getTransactionReceipt',
@@ -174,6 +177,14 @@ class Provider {
174
177
  throw new Error(`[PortalProvider] Chain ID must be prefixed with "solana:" for the operation, got ${chainId}`);
175
178
  }
176
179
  };
180
+ this.enforceTronChainId = (chainId) => {
181
+ if (!chainId) {
182
+ throw new Error('[PortalProvider] Chain ID is required for the operation');
183
+ }
184
+ if (!chainId.startsWith('tron:')) {
185
+ throw new Error(`[PortalProvider] Chain ID must be prefixed with "tron:" for the operation, got ${chainId}`);
186
+ }
187
+ };
177
188
  this.buildParams = (method, txParams) => {
178
189
  let params = txParams;
179
190
  switch (method) {
@@ -467,6 +478,14 @@ class Provider {
467
478
  })));
468
479
  return result;
469
480
  }
481
+ case RequestMethod.tron_sendTransaction: {
482
+ this.enforceTronChainId(chainId);
483
+ const result = yield this.portal.mpc.sign(Object.assign({ chainId: chainId, method, params: this.buildParams(method, params), rpcUrl: this.portal.getRpcUrl(chainId), sponsorGas,
484
+ traceId }, (signatureApprovalMemo !== undefined && {
485
+ signatureApprovalMemo,
486
+ })));
487
+ return result;
488
+ }
470
489
  default:
471
490
  throw new Error('[PortalProvider] Method "' + method + '" not supported');
472
491
  }
@@ -544,6 +544,44 @@ describe('Provider', () => {
544
544
  });
545
545
  }));
546
546
  });
547
+ describe('tron_sendTransaction', () => {
548
+ it('should throw an error if no chainId is provided alongside tron_sendTransaction', () => __awaiter(void 0, void 0, void 0, function* () {
549
+ yield expect(provider.request({
550
+ method: RequestMethod.tron_sendTransaction,
551
+ params: ['test'],
552
+ })).rejects.toThrow(new Error(`[PortalProvider] Chain ID is required for the operation`));
553
+ }));
554
+ it('should throw an error if malformed chainId is provided alongside tron_sendTransaction', () => __awaiter(void 0, void 0, void 0, function* () {
555
+ const chainId = 'unsupported:chain';
556
+ yield expect(provider.request({
557
+ chainId,
558
+ method: RequestMethod.tron_sendTransaction,
559
+ params: ['test'],
560
+ })).rejects.toThrow(new Error(`[PortalProvider] Chain ID must be prefixed with "tron:" for the operation, got ${chainId}`));
561
+ }));
562
+ it('should successfully handle a tron_sendTransaction request', () => __awaiter(void 0, void 0, void 0, function* () {
563
+ const result = yield provider.request({
564
+ chainId: 'tron:nile',
565
+ method: RequestMethod.tron_sendTransaction,
566
+ params: ['test'],
567
+ });
568
+ expect(result).toEqual(mockSignedHash);
569
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
570
+ chainId: 'tron:nile',
571
+ method: RequestMethod.tron_sendTransaction,
572
+ params: 'test',
573
+ rpcUrl: mockRpcUrl,
574
+ sponsorGas: undefined,
575
+ traceId: 'mock-trace-id-12345',
576
+ });
577
+ expect(provider.emit).toHaveBeenCalledWith('portal_signatureReceived', {
578
+ chainId: 'tron:nile',
579
+ method: RequestMethod.tron_sendTransaction,
580
+ params: ['test'],
581
+ signature: result,
582
+ });
583
+ }));
584
+ });
547
585
  });
548
586
  describe('Signer methods without auto-approval', () => {
549
587
  const mockSigningRequestedHandler = jest.fn();
@@ -1195,6 +1233,59 @@ describe('Provider', () => {
1195
1233
  expect(mockConsoleWarn).toHaveBeenCalledWith("[PortalProvider] Request for signing method 'sol_signTransaction' could not be completed because it was not approved by the user.");
1196
1234
  }));
1197
1235
  });
1236
+ describe('tron_sendTransaction', () => {
1237
+ it('should successfully handle an approved tron_sendTransaction request', () => __awaiter(void 0, void 0, void 0, function* () {
1238
+ provider.on('portal_signingRequested', () => {
1239
+ provider.emit('portal_signingApproved', {
1240
+ method: RequestMethod.tron_sendTransaction,
1241
+ params: ['test'],
1242
+ });
1243
+ });
1244
+ const result = yield provider.request({
1245
+ chainId: 'tron:nile',
1246
+ method: RequestMethod.tron_sendTransaction,
1247
+ params: ['test'],
1248
+ });
1249
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1250
+ method: RequestMethod.tron_sendTransaction,
1251
+ params: ['test'],
1252
+ });
1253
+ expect(result).toEqual(mockSignedHash);
1254
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
1255
+ chainId: 'tron:nile',
1256
+ method: RequestMethod.tron_sendTransaction,
1257
+ params: 'test',
1258
+ rpcUrl: mockRpcUrl,
1259
+ sponsorGas: undefined,
1260
+ traceId: 'mock-trace-id-12345',
1261
+ });
1262
+ expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({
1263
+ chainId: 'tron:nile',
1264
+ method: RequestMethod.tron_sendTransaction,
1265
+ params: ['test'],
1266
+ signature: result,
1267
+ });
1268
+ }));
1269
+ it('should successfully handle a rejected tron_sendTransaction request', () => __awaiter(void 0, void 0, void 0, function* () {
1270
+ provider.on('portal_signingRequested', () => {
1271
+ provider.emit('portal_signingRejected', {
1272
+ method: RequestMethod.tron_sendTransaction,
1273
+ params: ['test'],
1274
+ });
1275
+ });
1276
+ const result = yield provider.request({
1277
+ chainId: 'tron:nile',
1278
+ method: RequestMethod.tron_sendTransaction,
1279
+ params: ['test'],
1280
+ });
1281
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1282
+ method: RequestMethod.tron_sendTransaction,
1283
+ params: ['test'],
1284
+ });
1285
+ expect(result).toEqual(undefined);
1286
+ expect(mockConsoleWarn).toHaveBeenCalledWith("[PortalProvider] Request for signing method 'tron_sendTransaction' could not be completed because it was not approved by the user.");
1287
+ }));
1288
+ });
1198
1289
  });
1199
1290
  describe('Non-signer methods', () => {
1200
1291
  beforeAll(() => {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Portal MPC Support for Web",
4
4
  "author": "Portal Labs, Inc.",
5
5
  "homepage": "https://portalhq.io/",
6
- "version": "3.15.0-alpha.2",
6
+ "version": "3.16.0-alpha.0",
7
7
  "license": "MIT",
8
8
  "main": "lib/commonjs/index",
9
9
  "module": "lib/esm/index",
@@ -46,6 +46,7 @@ export const mockMpcBackupResponse = {
46
46
 
47
47
  export const mockEip155Address = 'test-eip155-address'
48
48
  export const mockSolanaAddress = 'CqQMFUCEMbK9Cp9yMonmthKqzc59Ta48JpZMZsh1bDqf'
49
+ export const mockTronAddress = 'TQvGkCH57KBBgGRiMpKNbZnqMHRb1x9p9X'
49
50
  export const mockClientResponse = {
50
51
  createdAt: '2023-02-20T00:00:00.000Z',
51
52
  custodian: {
@@ -71,6 +72,10 @@ export const mockClientResponse = {
71
72
  address: mockSolanaAddress,
72
73
  curve: 'ED25519',
73
74
  },
75
+ tron: {
76
+ address: mockTronAddress,
77
+ curve: 'SECP256K1',
78
+ },
74
79
  },
75
80
  },
76
81
  wallets: [
@@ -212,6 +217,10 @@ export const mockClient: ClientResponse = {
212
217
  curve: PortalCurve.ED25519,
213
218
  address: mockAddress,
214
219
  },
220
+ [ChainNamespace.TRON]: {
221
+ curve: PortalCurve.SECP256K1,
222
+ address: mockAddress,
223
+ },
215
224
  },
216
225
  },
217
226
  wallets: [
@@ -436,6 +445,20 @@ export const mockBuiltTransaction = {
436
445
  },
437
446
  }
438
447
 
448
+ export const mockBuiltTronTransaction = {
449
+ transaction: {
450
+ id: 'mock-tron-tx-id-abc123',
451
+ network: 'nile',
452
+ },
453
+ metadata: {
454
+ amount: '1',
455
+ fromAddress: mockTronAddress,
456
+ toAddress: 'TAKAgsfLknBjAupezWMPosnjruGnjAnhin',
457
+ tokenSymbol: 'TRX',
458
+ contractAddress: null,
459
+ },
460
+ }
461
+
439
462
  export const mockNFTs = [
440
463
  {
441
464
  contract: {
@@ -803,6 +826,7 @@ export const mockClientMetadata = {
803
826
  addresses: {
804
827
  [ChainNamespace.EIP155]: 'test',
805
828
  [ChainNamespace.SOLANA]: 'test',
829
+ [ChainNamespace.TRON]: 'test',
806
830
  },
807
831
  custodian: {
808
832
  id: 'test',
@@ -817,6 +841,10 @@ export const mockClientMetadata = {
817
841
  ...mockClient.wallets[1],
818
842
  publicKey: { X: 'test', Y: 'test' },
819
843
  },
844
+ [ChainNamespace.TRON]: {
845
+ ...mockClient.wallets[0],
846
+ publicKey: { X: 'test', Y: 'test' },
847
+ },
820
848
  },
821
849
  }
822
850
 
package/src/index.test.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  mockAddress,
8
8
  mockBackupConfig,
9
9
  mockBlockHashResponse,
10
+ mockBuiltTronTransaction,
10
11
  mockCipherText,
11
12
  mockClientResponse,
12
13
  mockEip155Address,
@@ -23,6 +24,7 @@ import {
23
24
  mockSignedHash,
24
25
  mockSolanaAddress,
25
26
  mockSolRpcUrl,
27
+ mockTronAddress,
26
28
  } from './__mocks/constants'
27
29
  import mpcMock from './__mocks/portal/mpc'
28
30
  import providerMock from './__mocks/portal/provider'
@@ -708,6 +710,13 @@ describe('Portal', () => {
708
710
  })
709
711
  })
710
712
 
713
+ describe('getTronAddress', () => {
714
+ it("should return the wallet's TRON address correctly", async () => {
715
+ const res = await portal.getTronAddress()
716
+ expect(res).toBe(mockTronAddress)
717
+ })
718
+ })
719
+
711
720
  describe('doesWalletExist', () => {
712
721
  it('should successfully return if wallets exist', async () => {
713
722
  const res = await portal.doesWalletExist()
@@ -953,6 +962,86 @@ describe('Portal', () => {
953
962
  })
954
963
  })
955
964
 
965
+ describe('sendAsset (TRON)', () => {
966
+ const tronChainId = 'tron:nile'
967
+ const tronRpcUrl = 'https://test-tron-rpc'
968
+
969
+ beforeEach(() => {
970
+ portal = new Portal({
971
+ rpcConfig: {
972
+ ...mockRpcConfig,
973
+ [tronChainId]: tronRpcUrl,
974
+ },
975
+ })
976
+ portal.mpc = mpcMock
977
+ portal.provider = providerMock
978
+ })
979
+
980
+ it('should build the transaction and sign it via tron_sendTransaction', async () => {
981
+ ;(portal.mpc.buildTransaction as jest.Mock).mockResolvedValueOnce(
982
+ mockBuiltTronTransaction,
983
+ )
984
+ ;(portal.provider.request as jest.Mock).mockResolvedValueOnce(
985
+ mockSignedHash,
986
+ )
987
+
988
+ const result = await portal.sendAsset(tronChainId, {
989
+ to: mockBuiltTronTransaction.metadata.toAddress,
990
+ token: 'NATIVE',
991
+ amount: '1',
992
+ })
993
+
994
+ expect(result).toBe(mockSignedHash)
995
+
996
+ expect(portal.mpc.buildTransaction).toHaveBeenCalledWith(
997
+ tronChainId,
998
+ mockBuiltTronTransaction.metadata.toAddress,
999
+ 'NATIVE',
1000
+ '1',
1001
+ expect.any(String),
1002
+ )
1003
+
1004
+ expect(portal.provider.request).toHaveBeenCalledWith(
1005
+ expect.objectContaining({
1006
+ chainId: tronChainId,
1007
+ method: 'tron_sendTransaction',
1008
+ params: [mockBuiltTronTransaction.transaction.id],
1009
+ }),
1010
+ )
1011
+ })
1012
+
1013
+ it('should propagate build errors', async () => {
1014
+ ;(portal.mpc.buildTransaction as jest.Mock).mockRejectedValueOnce(
1015
+ new Error('Build failed'),
1016
+ )
1017
+
1018
+ await expect(
1019
+ portal.sendAsset(tronChainId, {
1020
+ to: mockBuiltTronTransaction.metadata.toAddress,
1021
+ token: 'NATIVE',
1022
+ amount: '1',
1023
+ }),
1024
+ ).rejects.toThrow('Failed to send asset: Build failed')
1025
+ })
1026
+
1027
+ it('should propagate signing errors', async () => {
1028
+ ;(portal.mpc.buildTransaction as jest.Mock).mockResolvedValueOnce(
1029
+ mockBuiltTronTransaction,
1030
+ )
1031
+ ;(portal.provider.request as jest.Mock).mockRejectedValueOnce(
1032
+ new Error('Signing failed'),
1033
+ )
1034
+
1035
+ await expect(
1036
+ portal.sendAsset(tronChainId, {
1037
+ to: mockBuiltTronTransaction.metadata.toAddress,
1038
+ token: 'NATIVE',
1039
+ amount: '1',
1040
+ }),
1041
+ ).rejects.toThrow('Failed to send asset: Signing failed')
1042
+ })
1043
+ })
1044
+
956
1045
  describe('getBalances', () => {
957
1046
  it('should correctly call mpc.getBalances', async () => {
958
1047
  await portal.getBalances('eip155:1')
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  import {
9
9
  GetAssetsResponse,
10
10
  BuiltTransaction,
11
+ BuiltTronTransaction,
11
12
  EvaluateTransactionOperationType,
12
13
  EvaluateTransactionParam,
13
14
  EvaluatedTransaction,
@@ -581,6 +582,12 @@ class Portal {
581
582
  return solAddress
582
583
  }
583
584
 
585
+ public async getTronAddress(): Promise<string> {
586
+ const client = await this.mpc?.getClient()
587
+ const tronAddress = client?.metadata?.namespaces?.tron?.address || ''
588
+ return tronAddress
589
+ }
590
+
584
591
  public async doesWalletExist(chainId?: string): Promise<boolean> {
585
592
  const client = await this.mpc?.getClient()
586
593
 
@@ -738,6 +745,19 @@ class Portal {
738
745
  })
739
746
  break
740
747
  }
748
+ case 'tron': {
749
+ const tronTx = buildTxResponse as BuiltTronTransaction
750
+ response = await this.request({
751
+ chainId,
752
+ method: 'tron_sendTransaction',
753
+ params: [tronTx.transaction.id],
754
+ sponsorGas: params.sponsorGas,
755
+ signatureApprovalMemo: params.signatureApprovalMemo,
756
+ traceId,
757
+ })
758
+ break
759
+ }
760
+
741
761
  default: {
742
762
  throw new Error(`Unsupported chain namespace: ${chainNamespace}`)
743
763
  }
@@ -757,6 +777,9 @@ class Portal {
757
777
  * - **Solana (`solana:*`):** Polls `getSignatureStatuses` until the
758
778
  * commitment level from `options.commitment`
759
779
  * is met (default `confirmed`).
780
+ * - **TRON (`tron:*`):** Confirmation polling is not supported.
781
+ * Always returns `false`. Transaction status must be verified
782
+ * directly via TRON RPC using the transaction ID returned by `sendAsset`.
760
783
  * - **Other networks:** Returns `false` (unsupported for polling).
761
784
  *
762
785
  * Optional `options` tune poll/timeout for all polled chains; EVM also accepts
@@ -1617,6 +1640,7 @@ export enum PortalCurve {
1617
1640
  export enum ChainNamespace {
1618
1641
  EIP155 = 'eip155',
1619
1642
  SOLANA = 'solana',
1643
+ TRON = 'tron',
1620
1644
  }
1621
1645
 
1622
1646
  export default Portal
package/src/mpc/index.ts CHANGED
@@ -134,7 +134,7 @@ import {
134
134
  } from '../../hypernative'
135
135
  import { generateTraceId } from '../shared/trace'
136
136
 
137
- const WEB_SDK_VERSION = '3.15.0-alpha.2'
137
+ const WEB_SDK_VERSION = '3.16.0-alpha.0'
138
138
 
139
139
  class Mpc {
140
140
  public iframe?: HTMLIFrameElement
@@ -162,6 +162,7 @@ class Mpc {
162
162
  if (!requestId) {
163
163
  return
164
164
  }
165
+ const forceRefresh = data?.forceRefresh === true
165
166
  const getter = this.firebaseGetToken
166
167
  void (async () => {
167
168
  try {
@@ -170,9 +171,7 @@ class Mpc {
170
171
  'Firebase storage is not configured (getToken missing)',
171
172
  )
172
173
  }
173
- const token = await getter({
174
- forceRefresh: data?.forceRefresh === true,
175
- })
174
+ const token = await getter({ forceRefresh })
176
175
  this.postMessage({
177
176
  type: 'portal:firebase:requestTokenResult',
178
177
  data: { requestId, token },
@@ -812,6 +812,63 @@ describe('Provider', () => {
812
812
  )
813
813
  })
814
814
  })
815
+
816
+ describe('tron_sendTransaction', () => {
817
+ it('should throw an error if no chainId is provided alongside tron_sendTransaction', async () => {
818
+ await expect(
819
+ provider.request({
820
+ method: RequestMethod.tron_sendTransaction,
821
+ params: ['test'],
822
+ }),
823
+ ).rejects.toThrow(
824
+ new Error(
825
+ `[PortalProvider] Chain ID is required for the operation`,
826
+ ),
827
+ )
828
+ })
829
+
830
+ it('should throw an error if malformed chainId is provided alongside tron_sendTransaction', async () => {
831
+ const chainId = 'unsupported:chain'
832
+ await expect(
833
+ provider.request({
834
+ chainId,
835
+ method: RequestMethod.tron_sendTransaction,
836
+ params: ['test'],
837
+ }),
838
+ ).rejects.toThrow(
839
+ new Error(
840
+ `[PortalProvider] Chain ID must be prefixed with "tron:" for the operation, got ${chainId}`,
841
+ ),
842
+ )
843
+ })
844
+
845
+ it('should successfully handle a tron_sendTransaction request', async () => {
846
+ const result = await provider.request({
847
+ chainId: 'tron:nile',
848
+ method: RequestMethod.tron_sendTransaction,
849
+ params: ['test'],
850
+ })
851
+
852
+ expect(result).toEqual(mockSignedHash)
853
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
854
+ chainId: 'tron:nile',
855
+ method: RequestMethod.tron_sendTransaction,
856
+ params: 'test',
857
+ rpcUrl: mockRpcUrl,
858
+ sponsorGas: undefined,
859
+ traceId: 'mock-trace-id-12345',
860
+ })
861
+ expect(provider.emit).toHaveBeenCalledWith(
862
+ 'portal_signatureReceived',
863
+ {
864
+ chainId: 'tron:nile',
865
+ method: RequestMethod.tron_sendTransaction,
866
+ params: ['test'],
867
+ signature: result,
868
+ },
869
+ )
870
+ })
871
+ })
815
872
  })
816
873
 
817
874
  describe('Signer methods without auto-approval', () => {
@@ -1539,6 +1596,65 @@ describe('Provider', () => {
1539
1596
  )
1540
1597
  })
1541
1598
  })
1599
+
1600
+ describe('tron_sendTransaction', () => {
1601
+ it('should successfully handle an approved tron_sendTransaction request', async () => {
1602
+ provider.on('portal_signingRequested', () => {
1603
+ provider.emit('portal_signingApproved', {
1604
+ method: RequestMethod.tron_sendTransaction,
1605
+ params: ['test'],
1606
+ })
1607
+ })
1608
+ const result = await provider.request({
1609
+ chainId: 'tron:nile',
1610
+ method: RequestMethod.tron_sendTransaction,
1611
+ params: ['test'],
1612
+ })
1613
+
1614
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1615
+ method: RequestMethod.tron_sendTransaction,
1616
+ params: ['test'],
1617
+ })
1618
+ expect(result).toEqual(mockSignedHash)
1619
+ expect(portal.mpc.sign).toHaveBeenCalledWith({
1620
+ chainId: 'tron:nile',
1621
+ method: RequestMethod.tron_sendTransaction,
1622
+ params: 'test',
1623
+ rpcUrl: mockRpcUrl,
1624
+ sponsorGas: undefined,
1625
+ traceId: 'mock-trace-id-12345',
1626
+ })
1627
+ expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({
1628
+ chainId: 'tron:nile',
1629
+ method: RequestMethod.tron_sendTransaction,
1630
+ params: ['test'],
1631
+ signature: result,
1632
+ })
1633
+ })
1634
+
1635
+ it('should successfully handle a rejected tron_sendTransaction request', async () => {
1636
+ provider.on('portal_signingRequested', () => {
1637
+ provider.emit('portal_signingRejected', {
1638
+ method: RequestMethod.tron_sendTransaction,
1639
+ params: ['test'],
1640
+ })
1641
+ })
1642
+
1643
+ const result = await provider.request({
1644
+ chainId: 'tron:nile',
1645
+ method: RequestMethod.tron_sendTransaction,
1646
+ params: ['test'],
1647
+ })
1648
+ expect(mockSigningRequestedHandler).toHaveBeenCalledWith({
1649
+ method: RequestMethod.tron_sendTransaction,
1650
+ params: ['test'],
1651
+ })
1652
+ expect(result).toEqual(undefined)
1653
+ expect(mockConsoleWarn).toHaveBeenCalledWith(
1654
+ "[PortalProvider] Request for signing method 'tron_sendTransaction' could not be completed because it was not approved by the user.",
1655
+ )
1656
+ })
1657
+ })
1542
1658
  })
1543
1659
 
1544
1660
  describe('Non-signer methods', () => {
@@ -134,6 +134,9 @@ export enum RequestMethod {
134
134
  sol_signAndSendTransaction = 'sol_signAndSendTransaction',
135
135
  sol_signMessage = 'sol_signMessage',
136
136
  sol_signTransaction = 'sol_signTransaction',
137
+
138
+ // TRON Wallet Methods
139
+ tron_sendTransaction = 'tron_sendTransaction',
137
140
  }
138
141
 
139
142
  const passiveSignerMethods = [
@@ -157,6 +160,7 @@ const signerMethods = [
157
160
  RequestMethod.sol_signAndSendTransaction,
158
161
  RequestMethod.sol_signMessage,
159
162
  RequestMethod.sol_signTransaction,
163
+ RequestMethod.tron_sendTransaction,
160
164
  ]
161
165
 
162
166
  const iframeProxiedMethodStrings: string[] = [
@@ -540,6 +544,21 @@ class Provider {
540
544
  })
541
545
  return result
542
546
  }
547
+ case RequestMethod.tron_sendTransaction: {
548
+ this.enforceTronChainId(chainId)
549
+ const result = await this.portal.mpc.sign({
550
+ chainId: chainId as string,
551
+ method,
552
+ params: this.buildParams(method, params),
553
+ rpcUrl: this.portal.getRpcUrl(chainId),
554
+ sponsorGas,
555
+ traceId,
556
+ ...(signatureApprovalMemo !== undefined && {
557
+ signatureApprovalMemo,
558
+ }),
559
+ })
560
+ return result
561
+ }
543
562
  default:
544
563
  throw new Error(
545
564
  '[PortalProvider] Method "' + method + '" not supported',
@@ -571,6 +590,18 @@ class Provider {
571
590
  }
572
591
  }
573
592
 
593
+ private enforceTronChainId = (chainId?: string) => {
594
+ if (!chainId) {
595
+ throw new Error('[PortalProvider] Chain ID is required for the operation')
596
+ }
597
+
598
+ if (!chainId.startsWith('tron:')) {
599
+ throw new Error(
600
+ `[PortalProvider] Chain ID must be prefixed with "tron:" for the operation, got ${chainId}`,
601
+ )
602
+ }
603
+ }
604
+
574
605
  private buildParams = (method: string, txParams: any) => {
575
606
  let params = txParams
576
607
 
@@ -334,7 +334,7 @@ export type GetTransactionHistoryResponse =
334
334
  | UnifiedTransactionsResponse
335
335
 
336
336
  // Built transaction types
337
- export type BuiltTransaction = BuiltEip155Transaction | BuiltSolanaTransaction
337
+ export type BuiltTransaction = BuiltEip155Transaction | BuiltSolanaTransaction | BuiltTronTransaction
338
338
 
339
339
  export interface BuiltEip155Transaction {
340
340
  transaction: {
@@ -402,6 +402,20 @@ export interface BuiltSolanaTransaction {
402
402
  }
403
403
  }
404
404
 
405
+ export interface BuiltTronTransaction {
406
+ transaction: {
407
+ id: string
408
+ network: string
409
+ }
410
+ metadata: {
411
+ amount: string
412
+ fromAddress: string
413
+ toAddress: string
414
+ tokenSymbol: string
415
+ contractAddress: string | null
416
+ }
417
+ }
418
+
405
419
  // Simulation types
406
420
  export interface SimulateTransactionParam {
407
421
  to: string