@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.
- package/lib/commonjs/index.js +24 -0
- package/lib/commonjs/index.test.js +53 -0
- package/lib/commonjs/mpc/index.js +3 -4
- package/lib/commonjs/provider/index.js +19 -0
- package/lib/commonjs/provider/index.test.js +91 -0
- package/lib/esm/index.js +24 -0
- package/lib/esm/index.test.js +54 -1
- package/lib/esm/mpc/index.js +3 -4
- package/lib/esm/provider/index.js +19 -0
- package/lib/esm/provider/index.test.js +91 -0
- package/package.json +1 -1
- package/src/__mocks/constants.ts +28 -0
- package/src/index.test.ts +89 -0
- package/src/index.ts +24 -0
- package/src/mpc/index.ts +3 -4
- package/src/provider/index.test.ts +116 -0
- package/src/provider/index.ts +31 -0
- package/src/shared/types/common.ts +15 -1
package/lib/commonjs/index.js
CHANGED
|
@@ -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.
|
|
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;
|
package/lib/esm/index.test.js
CHANGED
|
@@ -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');
|
package/lib/esm/mpc/index.js
CHANGED
|
@@ -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.
|
|
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
package/src/__mocks/constants.ts
CHANGED
|
@@ -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.
|
|
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', () => {
|
package/src/provider/index.ts
CHANGED
|
@@ -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
|