@portal-hq/web 3.13.2-alpha.1 → 3.14.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.
Files changed (96) hide show
  1. package/lib/commonjs/index.js +127 -9
  2. package/lib/commonjs/index.test.js +13 -0
  3. package/lib/commonjs/integrations/delegations/index.js +109 -2
  4. package/lib/commonjs/integrations/delegations/index.test.js +171 -0
  5. package/lib/commonjs/integrations/ramps/noah/index.test.js +18 -5
  6. package/lib/commonjs/integrations/trading/index.js +16 -5
  7. package/lib/commonjs/integrations/trading/lifi/index.js +297 -25
  8. package/lib/commonjs/integrations/trading/lifi/lifi.tradeAsset.test.js +360 -0
  9. package/lib/commonjs/integrations/trading/lifi/lifiStatusPoll.js +118 -0
  10. package/lib/commonjs/integrations/trading/lifi/lifiStatusPoll.test.js +66 -0
  11. package/lib/commonjs/integrations/trading/zero-x/index.js +129 -26
  12. package/lib/commonjs/integrations/trading/zero-x/index.test.js +163 -1
  13. package/lib/commonjs/integrations/yield/index.js +18 -4
  14. package/lib/commonjs/integrations/yield/yieldxyz.getValidators.test.js +71 -0
  15. package/lib/commonjs/integrations/yield/yieldxyz.highLevel.test.js +330 -0
  16. package/lib/commonjs/integrations/yield/yieldxyz.js +517 -1
  17. package/lib/commonjs/internal/pollLoop.js +64 -0
  18. package/lib/commonjs/internal/pollLoop.test.js +100 -0
  19. package/lib/commonjs/internal/stripStalePlanningNonce.js +65 -0
  20. package/lib/commonjs/internal/stripStalePlanningNonce.test.js +35 -0
  21. package/lib/commonjs/internal/waitForEvmOrUserOpConfirmation.js +155 -0
  22. package/lib/commonjs/internal/waitForEvmOrUserOpConfirmation.test.js +33 -0
  23. package/lib/commonjs/internal/waitForEvmTxConfirmation.js +104 -0
  24. package/lib/commonjs/internal/waitForSolanaTxConfirmation.js +106 -0
  25. package/lib/commonjs/internal/yieldEvmNetwork.js +60 -0
  26. package/lib/commonjs/mpc/index.js +116 -1
  27. package/lib/commonjs/provider/index.js +17 -0
  28. package/lib/commonjs/shared/trace/index.js +0 -1
  29. package/lib/esm/index.js +127 -9
  30. package/lib/esm/index.test.js +13 -0
  31. package/lib/esm/integrations/delegations/index.js +109 -2
  32. package/lib/esm/integrations/delegations/index.test.js +171 -0
  33. package/lib/esm/integrations/ramps/noah/index.test.js +18 -5
  34. package/lib/esm/integrations/trading/index.js +16 -5
  35. package/lib/esm/integrations/trading/lifi/index.js +292 -25
  36. package/lib/esm/integrations/trading/lifi/lifi.tradeAsset.test.js +332 -0
  37. package/lib/esm/integrations/trading/lifi/lifiStatusPoll.js +113 -0
  38. package/lib/esm/integrations/trading/lifi/lifiStatusPoll.test.js +64 -0
  39. package/lib/esm/integrations/trading/zero-x/index.js +129 -26
  40. package/lib/esm/integrations/trading/zero-x/index.test.js +141 -2
  41. package/lib/esm/integrations/yield/index.js +18 -4
  42. package/lib/esm/integrations/yield/yieldxyz.getValidators.test.js +66 -0
  43. package/lib/esm/integrations/yield/yieldxyz.highLevel.test.js +325 -0
  44. package/lib/esm/integrations/yield/yieldxyz.js +517 -1
  45. package/lib/esm/internal/pollLoop.js +59 -0
  46. package/lib/esm/internal/pollLoop.test.js +98 -0
  47. package/lib/esm/internal/stripStalePlanningNonce.js +61 -0
  48. package/lib/esm/internal/stripStalePlanningNonce.test.js +33 -0
  49. package/lib/esm/internal/waitForEvmOrUserOpConfirmation.js +151 -0
  50. package/lib/esm/internal/waitForEvmOrUserOpConfirmation.test.js +31 -0
  51. package/lib/esm/internal/waitForEvmTxConfirmation.js +100 -0
  52. package/lib/esm/internal/waitForSolanaTxConfirmation.js +102 -0
  53. package/lib/esm/internal/yieldEvmNetwork.js +55 -0
  54. package/lib/esm/mpc/index.js +116 -1
  55. package/lib/esm/provider/index.js +17 -0
  56. package/lib/esm/shared/trace/index.js +0 -1
  57. package/noah-types.d.ts +16 -2
  58. package/package.json +1 -1
  59. package/src/index.test.ts +15 -0
  60. package/src/index.ts +203 -14
  61. package/src/integrations/delegations/index.test.ts +251 -0
  62. package/src/integrations/delegations/index.ts +202 -4
  63. package/src/integrations/ramps/noah/index.test.ts +18 -5
  64. package/src/integrations/trading/index.ts +10 -7
  65. package/src/integrations/trading/lifi/index.ts +388 -28
  66. package/src/integrations/trading/lifi/lifi.tradeAsset.test.ts +436 -0
  67. package/src/integrations/trading/lifi/lifiStatusPoll.test.ts +74 -0
  68. package/src/integrations/trading/lifi/lifiStatusPoll.ts +158 -0
  69. package/src/integrations/trading/zero-x/index.test.ts +297 -1
  70. package/src/integrations/trading/zero-x/index.ts +181 -27
  71. package/src/integrations/yield/index.ts +24 -4
  72. package/src/integrations/yield/yieldxyz.getValidators.test.ts +70 -0
  73. package/src/integrations/yield/yieldxyz.highLevel.test.ts +403 -0
  74. package/src/integrations/yield/yieldxyz.ts +740 -8
  75. package/src/internal/pollLoop.test.ts +109 -0
  76. package/src/internal/pollLoop.ts +87 -0
  77. package/src/internal/stripStalePlanningNonce.test.ts +38 -0
  78. package/src/internal/stripStalePlanningNonce.ts +66 -0
  79. package/src/internal/waitForEvmOrUserOpConfirmation.test.ts +31 -0
  80. package/src/internal/waitForEvmOrUserOpConfirmation.ts +194 -0
  81. package/src/internal/waitForEvmTxConfirmation.ts +155 -0
  82. package/src/internal/waitForSolanaTxConfirmation.ts +135 -0
  83. package/src/internal/yieldEvmNetwork.ts +57 -0
  84. package/src/mpc/index.ts +142 -1
  85. package/src/provider/index.ts +25 -0
  86. package/src/shared/trace/index.ts +0 -1
  87. package/src/shared/types/README.md +6 -0
  88. package/src/shared/types/api.ts +12 -1
  89. package/src/shared/types/common.ts +332 -20
  90. package/src/shared/types/delegations.ts +10 -0
  91. package/src/shared/types/index.ts +1 -0
  92. package/src/shared/types/lifi.ts +82 -0
  93. package/src/shared/types/noah.ts +124 -33
  94. package/src/shared/types/yieldxyz.ts +186 -0
  95. package/src/shared/types/zero-x.ts +66 -0
  96. package/types.d.ts +6 -0
@@ -48,15 +48,23 @@ const passkeys_1 = __importDefault(require("./passkeys"));
48
48
  const evmAccountType_1 = require("./namespaces/evmAccountType");
49
49
  const logger_1 = require("./logger");
50
50
  const trace_1 = require("./shared/trace");
51
+ const waitForEvmOrUserOpConfirmation_1 = require("./internal/waitForEvmOrUserOpConfirmation");
52
+ const waitForSolanaTxConfirmation_1 = require("./internal/waitForSolanaTxConfirmation");
51
53
  class Portal {
52
54
  get ready() {
53
55
  return this.mpc.ready;
54
56
  }
57
+ get rpcConfig() {
58
+ return this._rpcConfig;
59
+ }
60
+ get iframeRpcConfig() {
61
+ return this._iframeRpcConfig;
62
+ }
55
63
  constructor({
56
64
  // Required
57
65
  rpcConfig,
58
66
  // Optional
59
- apiKey, authToken, authUrl, autoApprove = false, gdrive, passkey, host = 'web.portalhq.io', mpcVersion = 'v6', mpcHost = 'mpc-client.portalhq.io', featureFlags = {}, chainId, logLevel = 'none', logger = console, }) {
67
+ iframeRpcConfig, apiKey, authToken, authUrl, autoApprove = false, gdrive, passkey, host = 'web.portalhq.io', mpcVersion = 'v6', mpcHost = 'mpc-client.portalhq.io', featureFlags = {}, chainId, logLevel = 'none', logger = console, }) {
60
68
  this.errorCallbacks = [];
61
69
  this.readyCallbacks = [];
62
70
  this.logger = console;
@@ -77,7 +85,8 @@ class Portal {
77
85
  this.authToken = authToken;
78
86
  this.authUrl = authUrl;
79
87
  this.autoApprove = autoApprove;
80
- this.rpcConfig = rpcConfig;
88
+ this._rpcConfig = rpcConfig;
89
+ this._iframeRpcConfig = iframeRpcConfig;
81
90
  this.host = host;
82
91
  this.mpcHost = mpcHost;
83
92
  this.mpcVersion = mpcVersion;
@@ -95,15 +104,43 @@ class Portal {
95
104
  this.mpc = new mpc_1.default({
96
105
  portal: this,
97
106
  });
98
- this.yield = new yield_1.default({ mpc: this.mpc });
99
107
  this.ramps = new ramps_1.default({ mpc: this.mpc });
100
- this.trading = new trading_1.default({ mpc: this.mpc });
101
108
  this.security = new security_1.default({ mpc: this.mpc });
102
- this.delegations = new delegations_1.default({ mpc: this.mpc });
103
109
  this.provider = new provider_1.default({
104
110
  portal: this,
105
111
  chainId: chainId ? Number(chainId) : undefined,
106
112
  });
113
+ const signAndSendTransaction = (transaction, network) => __awaiter(this, void 0, void 0, function* () {
114
+ const method = network.startsWith('solana')
115
+ ? provider_1.RequestMethod.sol_signAndSendTransaction
116
+ : provider_1.RequestMethod.eth_sendTransaction;
117
+ const hash = yield this.provider.request({
118
+ chainId: network,
119
+ method,
120
+ params: [transaction],
121
+ });
122
+ if (typeof hash !== 'string' || hash.length === 0) {
123
+ throw new Error('[Portal] Signing request did not return a transaction hash. The user may have rejected the request, or the provider did not complete signing.');
124
+ }
125
+ return hash;
126
+ });
127
+ const evmRequestFn = (method, params, network) => this.provider.request({ chainId: network, method, params });
128
+ this.yield = new yield_1.default({
129
+ mpc: this.mpc,
130
+ waitForConfirmation: this.waitForConfirmation.bind(this),
131
+ evmRequestFn,
132
+ });
133
+ this.trading = new trading_1.default({
134
+ mpc: this.mpc,
135
+ signAndSendTransaction,
136
+ waitForConfirmation: this.waitForConfirmation.bind(this),
137
+ evmRequestFn,
138
+ });
139
+ this.yield.yieldXyz.setSignAndSendTransaction(signAndSendTransaction);
140
+ this.delegations = new delegations_1.default({
141
+ mpc: this.mpc,
142
+ signAndSendTransaction,
143
+ });
107
144
  this.evmAccountType = new evmAccountType_1.EvmAccountType({ mpc: this.mpc });
108
145
  }
109
146
  /***********************************
@@ -522,6 +559,44 @@ class Portal {
522
559
  }
523
560
  });
524
561
  }
562
+ /**
563
+ * Wait until a transaction is confirmed on-chain, or until timeout.
564
+ *
565
+ * - **EVM (`eip155:*`):** Polls both `eth_getTransactionReceipt` and
566
+ * `eth_getUserOperationReceipt` (auto-detect, locks after first hit).
567
+ * - **Solana (`solana:*`):** Polls `getSignatureStatuses` until the
568
+ * commitment level from `options.commitment`
569
+ * is met (default `confirmed`).
570
+ * - **Other networks:** Returns `false` (unsupported for polling).
571
+ *
572
+ * Optional `options` tune poll/timeout for all polled chains; EVM also accepts
573
+ * `onTimeout` (default `resolve_false`) and `lockModeAfterDetection` (default `true`) for dual-receipt polling and timeout handling.
574
+ *
575
+ * All RPC calls are routed through the iframe proxy to avoid CORS issues.
576
+ */
577
+ waitForConfirmation(txHash, network, options) {
578
+ var _a, _b, _c, _d, _e, _f, _g;
579
+ return __awaiter(this, void 0, void 0, function* () {
580
+ const requestFn = (method, params, chainId) => this.provider.request({ chainId, method, params });
581
+ if (network.startsWith('eip155:')) {
582
+ return (0, waitForEvmOrUserOpConfirmation_1.waitForEvmOrUserOpConfirmation)(txHash, network, requestFn, {
583
+ pollIntervalMs: (_a = options === null || options === void 0 ? void 0 : options.pollIntervalMs) !== null && _a !== void 0 ? _a : 4000,
584
+ timeoutMs: (_b = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _b !== void 0 ? _b : 900000,
585
+ onTimeout: (_c = options === null || options === void 0 ? void 0 : options.onTimeout) !== null && _c !== void 0 ? _c : 'resolve_false',
586
+ lockModeAfterDetection: (_d = options === null || options === void 0 ? void 0 : options.lockModeAfterDetection) !== null && _d !== void 0 ? _d : true,
587
+ });
588
+ }
589
+ if (network.toLowerCase().startsWith('solana:')) {
590
+ return (0, waitForSolanaTxConfirmation_1.waitForSolanaTxConfirmation)(txHash, network, requestFn, {
591
+ pollIntervalMs: (_e = options === null || options === void 0 ? void 0 : options.pollIntervalMs) !== null && _e !== void 0 ? _e : 4000,
592
+ timeoutMs: (_f = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _f !== void 0 ? _f : 900000,
593
+ commitment: (_g = options === null || options === void 0 ? void 0 : options.commitment) !== null && _g !== void 0 ? _g : 'confirmed',
594
+ });
595
+ }
596
+ logger_1.sdkLogger.warn(`[Portal.waitForConfirmation] Unsupported network: "${network}". Returning false (cannot verify confirmation).`, { txHash, network });
597
+ return false;
598
+ });
599
+ }
525
600
  /****************************
526
601
  * Provider Methods
527
602
  ****************************/
@@ -811,12 +886,57 @@ class Portal {
811
886
  return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.getNFTs(chainId);
812
887
  });
813
888
  }
889
+ /**
890
+ * @deprecated This method is deprecated and will be removed in a future version.
891
+ * Please use `getTransactionHistory()` instead, which uses the new Portal v3 API
892
+ * endpoint and returns the unified transaction format across all chains.
893
+ *
894
+ * Legacy endpoint: /api/v3/clients/me/transactions?chainId={chainId}
895
+ * New endpoint: /api/v3/clients/me/chains/{chain}/transactions
896
+ */
814
897
  getTransactions(chainId, limit, offset, order) {
815
898
  var _a;
816
899
  return __awaiter(this, void 0, void 0, function* () {
817
900
  return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.getTransactions(chainId, limit, offset, order);
818
901
  });
819
902
  }
903
+ /**
904
+ * Retrieves transaction history for the client's wallet on the specified chain.
905
+ *
906
+ * This method uses the new Portal v3 API endpoint and returns the unified
907
+ * transaction format. Supports EVM (EIP-155), Solana, Bitcoin, Tron, and Stellar chains.
908
+ *
909
+ * Response format varies by chain:
910
+ * - Solana returns the legacy format (will be migrated in a future release)
911
+ * - All other chains return the unified TransactionHistoryItem format
912
+ *
913
+ * @param params - Request parameters
914
+ * @param params.chainId - Chain ID in CAIP-2 format (e.g., 'eip155:1', 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp')
915
+ * @param params.limit - Maximum number of transactions to return (default: 50, max: 1000 for EVM, 15 for Solana)
916
+ * @param params.offset - Number of transactions to skip (default: 0)
917
+ * @param params.order - Sort order ('asc' or 'desc')
918
+ * @param params.address - Override wallet address (EVM only, must match client's known addresses)
919
+ * @param params.userOperations - Filter for ERC-4337 UserOperations (EVM only): 'include', 'only', or 'exclude'
920
+ * @returns Promise resolving to transaction history response
921
+ *
922
+ * @example
923
+ * ```typescript
924
+ * // Fetch latest 100 transactions for Ethereum mainnet
925
+ * const response = await portal.getTransactionHistory({
926
+ * chainId: 'eip155:1',
927
+ * limit: 100,
928
+ * order: 'desc'
929
+ * });
930
+ *
931
+ * console.log(response.data.transactions); // Array of TransactionHistoryItem
932
+ * ```
933
+ */
934
+ getTransactionHistory(params) {
935
+ var _a;
936
+ return __awaiter(this, void 0, void 0, function* () {
937
+ return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.getTransactionHistory(params);
938
+ });
939
+ }
820
940
  /**
821
941
  * @deprecated This method is deprecated. Use `portal.evaluateTransaction` instead.
822
942
  */
@@ -888,13 +1008,11 @@ class Portal {
888
1008
  if (!chainId) {
889
1009
  throw new Error('[Portal] No chainId provided. Please provide a chainId to get the RPC endpoint');
890
1010
  }
891
- // Ensure the chainId is configured in the rpcConfig.
892
1011
  // eslint-disable-next-line no-prototype-builtins
893
- if (!this.rpcConfig.hasOwnProperty(chainId)) {
1012
+ if (!this._rpcConfig.hasOwnProperty(chainId)) {
894
1013
  throw new Error(`[Portal] No RPC endpoint configured for chainId: ${chainId}`);
895
1014
  }
896
- // Derive the RPC endpoint from the rpcConfig.
897
- const gatewayUrl = this.rpcConfig[chainId];
1015
+ const gatewayUrl = this._rpcConfig[chainId];
898
1016
  // If the RPC endpoint is a string, return it as-is.
899
1017
  if (typeof gatewayUrl === 'string') {
900
1018
  return gatewayUrl;
@@ -731,5 +731,18 @@ describe('Portal', () => {
731
731
  it('should throw an error if the chainId is not present in the rpc config', () => {
732
732
  expect(() => portal.getRpcUrl('incorrect-config')).toThrow(new Error('[Portal] Could not find a valid rpcConfig entry for chainId: incorrect-config'));
733
733
  });
734
+ it('should use rpcConfig for getRpcUrl when iframeRpcConfig is also set', () => {
735
+ const iframeRpcConfig = {
736
+ 'eip155:1': 'http://localhost:9999/rpc/v1/eip155/1',
737
+ };
738
+ portal = new _1.default({
739
+ rpcConfig: constants_1.mockRpcConfig,
740
+ iframeRpcConfig,
741
+ });
742
+ portal.mpc = mpc_1.default;
743
+ portal.provider = provider_1.default;
744
+ expect(portal.getRpcUrl('eip155:1')).toEqual(constants_1.mockEthRpcUrl);
745
+ expect(portal.iframeRpcConfig).toEqual(iframeRpcConfig);
746
+ });
734
747
  });
735
748
  });
@@ -9,9 +9,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ const DELEGATIONS_SUBMIT_CONFIG_ERROR = '[Delegations] No signer configured. Call setSignAndSendTransaction() on the instance or pass signAndSendTransaction in options.';
12
13
  class Delegations {
13
- constructor({ mpc }) {
14
- this.mpc = mpc;
14
+ constructor(options) {
15
+ this.mpc = options.mpc;
16
+ this.signAndSendTransaction = options.signAndSendTransaction;
17
+ }
18
+ setSignAndSendTransaction(fn) {
19
+ this.signAndSendTransaction = fn;
15
20
  }
16
21
  /**
17
22
  * Approves a delegation for a specified token on a given chain.
@@ -61,5 +66,107 @@ class Delegations {
61
66
  return (_a = this.mpc) === null || _a === void 0 ? void 0 : _a.delegationsTransferFrom(params);
62
67
  });
63
68
  }
69
+ /**
70
+ * Approves a delegation, then signs and broadcasts each returned transaction via
71
+ * {@link DelegationsOptions.signAndSendTransaction}.
72
+ *
73
+ * Requires `signAndSendTransaction` (provided by default on `Portal.delegations`).
74
+ * Confirming transactions on-chain remains the application's responsibility.
75
+ *
76
+ * @param params - Same as {@link Delegations.approve}
77
+ * @param options - Optional progress callbacks
78
+ * @returns Transaction hashes in submission order
79
+ */
80
+ approveAndSubmit(params, options) {
81
+ var _a;
82
+ return __awaiter(this, void 0, void 0, function* () {
83
+ const signAndSend = (_a = options === null || options === void 0 ? void 0 : options.signAndSendTransaction) !== null && _a !== void 0 ? _a : this.signAndSendTransaction;
84
+ if (!signAndSend) {
85
+ throw new Error(DELEGATIONS_SUBMIT_CONFIG_ERROR);
86
+ }
87
+ const response = yield this.approve(params);
88
+ return this.executeAndTrack(response, params.chain, signAndSend, options === null || options === void 0 ? void 0 : options.onProgress);
89
+ });
90
+ }
91
+ /**
92
+ * Revokes a delegation, then signs and broadcasts each returned transaction.
93
+ *
94
+ * Requires `signAndSendTransaction` (provided by default on `Portal.delegations`).
95
+ *
96
+ * @param params - Same as {@link Delegations.revoke}
97
+ * @param options - Optional progress callbacks
98
+ * @returns Transaction hashes in submission order
99
+ */
100
+ revokeAndSubmit(params, options) {
101
+ var _a;
102
+ return __awaiter(this, void 0, void 0, function* () {
103
+ const signAndSend = (_a = options === null || options === void 0 ? void 0 : options.signAndSendTransaction) !== null && _a !== void 0 ? _a : this.signAndSendTransaction;
104
+ if (!signAndSend) {
105
+ throw new Error(DELEGATIONS_SUBMIT_CONFIG_ERROR);
106
+ }
107
+ const response = yield this.revoke(params);
108
+ return this.executeAndTrack(response, params.chain, signAndSend, options === null || options === void 0 ? void 0 : options.onProgress);
109
+ });
110
+ }
111
+ /**
112
+ * Executes a delegated transfer, then signs and broadcasts each returned transaction.
113
+ *
114
+ * Requires `signAndSendTransaction` (provided by default on `Portal.delegations`).
115
+ *
116
+ * @param params - Same as {@link Delegations.transferFrom}
117
+ * @param options - Optional progress callbacks
118
+ * @returns Transaction hashes in submission order
119
+ */
120
+ transferAndSubmit(params, options) {
121
+ var _a;
122
+ return __awaiter(this, void 0, void 0, function* () {
123
+ const signAndSend = (_a = options === null || options === void 0 ? void 0 : options.signAndSendTransaction) !== null && _a !== void 0 ? _a : this.signAndSendTransaction;
124
+ if (!signAndSend) {
125
+ throw new Error(DELEGATIONS_SUBMIT_CONFIG_ERROR);
126
+ }
127
+ const response = yield this.transferFrom(params);
128
+ return this.executeAndTrack(response, params.chain, signAndSend, options === null || options === void 0 ? void 0 : options.onProgress);
129
+ });
130
+ }
131
+ normalizeToTransactionList(response) {
132
+ const transactions = Array.isArray(response.transactions)
133
+ ? response.transactions
134
+ : [];
135
+ const encodedTransactions = Array.isArray(response.encodedTransactions)
136
+ ? response.encodedTransactions
137
+ : [];
138
+ if (transactions.length > 0) {
139
+ return transactions;
140
+ }
141
+ if (encodedTransactions.length > 0) {
142
+ return encodedTransactions;
143
+ }
144
+ return [];
145
+ }
146
+ executeAndTrack(response, chainId, signAndSend, onProgress) {
147
+ return __awaiter(this, void 0, void 0, function* () {
148
+ const transactions = this.normalizeToTransactionList(response);
149
+ if (transactions.length === 0) {
150
+ throw new Error('No transactions in delegation response.');
151
+ }
152
+ const total = transactions.length;
153
+ const hashes = [];
154
+ for (let index = 0; index < transactions.length; index++) {
155
+ const tx = transactions[index];
156
+ // Validate transaction exists (defensive check for sparse arrays)
157
+ if (!tx) {
158
+ throw new Error(`Transaction at index ${index} is undefined or null. This indicates a malformed API response.`);
159
+ }
160
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress({ step: 'signing', index, total });
161
+ const hash = yield signAndSend(tx, chainId);
162
+ if (typeof hash !== 'string' || hash.trim().length === 0) {
163
+ throw new Error(`Invalid transaction hash returned from signAndSendTransaction at index ${index} for chain ${chainId}.`);
164
+ }
165
+ hashes.push(hash);
166
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress({ step: 'submitted', index, total, hash });
167
+ }
168
+ return { hashes };
169
+ });
170
+ }
64
171
  }
65
172
  exports.default = Delegations;
@@ -90,4 +90,175 @@ describe('Delegations', () => {
90
90
  yield expect(delegations.transferFrom(delegations_1.mockTransferFromRequest)).rejects.toThrow('Test error');
91
91
  }));
92
92
  });
93
+ describe('approveAndSubmit, revokeAndSubmit, transferAndSubmit', () => {
94
+ const mockSignAndSend = jest.fn();
95
+ beforeEach(() => {
96
+ mockSignAndSend.mockResolvedValue('0xtxhash');
97
+ });
98
+ it('throws when signAndSendTransaction is not configured', () => __awaiter(void 0, void 0, void 0, function* () {
99
+ const d = new _1.default({ mpc });
100
+ yield expect(d.approveAndSubmit(delegations_1.mockEVMApproveRequest)).rejects.toThrow('[Delegations] No signer configured. Call setSignAndSendTransaction() on the instance or pass signAndSendTransaction in options.');
101
+ yield expect(d.revokeAndSubmit(delegations_1.mockEVMRevokeRequest)).rejects.toThrow('[Delegations] No signer configured. Call setSignAndSendTransaction() on the instance or pass signAndSendTransaction in options.');
102
+ yield expect(d.transferAndSubmit(delegations_1.mockTransferFromRequest)).rejects.toThrow('[Delegations] No signer configured. Call setSignAndSendTransaction() on the instance or pass signAndSendTransaction in options.');
103
+ }));
104
+ it('approveAndSubmit calls MPC then signAndSend per transaction', () => __awaiter(void 0, void 0, void 0, function* () {
105
+ jest.spyOn(mpc, 'delegationsApprove').mockResolvedValue(delegations_1.mockEVMApproveResponse);
106
+ const d = new _1.default({
107
+ mpc,
108
+ signAndSendTransaction: mockSignAndSend,
109
+ });
110
+ const result = yield d.approveAndSubmit(delegations_1.mockEVMApproveRequest);
111
+ expect(mpc.delegationsApprove).toHaveBeenCalledWith(delegations_1.mockEVMApproveRequest);
112
+ expect(mockSignAndSend).toHaveBeenCalledTimes(1);
113
+ expect(mockSignAndSend).toHaveBeenCalledWith(delegations_1.mockEVMApproveResponse.transactions[0], delegations_1.mockEVMApproveRequest.chain);
114
+ expect(result).toEqual({ hashes: ['0xtxhash'] });
115
+ }));
116
+ it('revokeAndSubmit calls delegationsRevoke then signAndSend per transaction', () => __awaiter(void 0, void 0, void 0, function* () {
117
+ jest.spyOn(mpc, 'delegationsRevoke').mockResolvedValue(delegations_1.mockEVMRevokeResponse);
118
+ const d = new _1.default({
119
+ mpc,
120
+ signAndSendTransaction: mockSignAndSend,
121
+ });
122
+ const result = yield d.revokeAndSubmit(delegations_1.mockEVMRevokeRequest);
123
+ expect(mpc.delegationsRevoke).toHaveBeenCalledWith(delegations_1.mockEVMRevokeRequest);
124
+ expect(mockSignAndSend).toHaveBeenCalledTimes(1);
125
+ expect(mockSignAndSend).toHaveBeenCalledWith(delegations_1.mockEVMRevokeResponse.transactions[0], delegations_1.mockEVMRevokeRequest.chain);
126
+ expect(result).toEqual({ hashes: ['0xtxhash'] });
127
+ }));
128
+ it('transferAndSubmit calls delegationsTransferFrom then signAndSend per transaction', () => __awaiter(void 0, void 0, void 0, function* () {
129
+ jest
130
+ .spyOn(mpc, 'delegationsTransferFrom')
131
+ .mockResolvedValue(delegations_1.mockTransferFromResponse);
132
+ const d = new _1.default({
133
+ mpc,
134
+ signAndSendTransaction: mockSignAndSend,
135
+ });
136
+ const result = yield d.transferAndSubmit(delegations_1.mockTransferFromRequest);
137
+ expect(mpc.delegationsTransferFrom).toHaveBeenCalledWith(delegations_1.mockTransferFromRequest);
138
+ expect(mockSignAndSend).toHaveBeenCalledTimes(1);
139
+ expect(mockSignAndSend).toHaveBeenCalledWith(delegations_1.mockTransferFromResponse.transactions[0], delegations_1.mockTransferFromRequest.chain);
140
+ expect(result).toEqual({ hashes: ['0xtxhash'] });
141
+ }));
142
+ it('uses encodedTransactions when transactions is empty', () => __awaiter(void 0, void 0, void 0, function* () {
143
+ const response = {
144
+ transactions: [],
145
+ encodedTransactions: ['solTxPayload'],
146
+ metadata: delegations_1.mockEVMApproveResponse.metadata,
147
+ };
148
+ jest.spyOn(mpc, 'delegationsApprove').mockResolvedValue(response);
149
+ const d = new _1.default({
150
+ mpc,
151
+ signAndSendTransaction: mockSignAndSend,
152
+ });
153
+ yield d.approveAndSubmit(delegations_1.mockEVMApproveRequest);
154
+ expect(mockSignAndSend).toHaveBeenCalledWith('solTxPayload', delegations_1.mockEVMApproveRequest.chain);
155
+ }));
156
+ it('invokes onProgress for each transaction', () => __awaiter(void 0, void 0, void 0, function* () {
157
+ jest.spyOn(mpc, 'delegationsApprove').mockResolvedValue(delegations_1.mockEVMApproveResponse);
158
+ const onProgress = jest.fn();
159
+ const d = new _1.default({
160
+ mpc,
161
+ signAndSendTransaction: mockSignAndSend,
162
+ });
163
+ yield d.approveAndSubmit(delegations_1.mockEVMApproveRequest, { onProgress });
164
+ expect(onProgress).toHaveBeenCalledTimes(2);
165
+ expect(onProgress).toHaveBeenNthCalledWith(1, {
166
+ step: 'signing',
167
+ index: 0,
168
+ total: 1,
169
+ });
170
+ expect(onProgress).toHaveBeenNthCalledWith(2, {
171
+ step: 'submitted',
172
+ index: 0,
173
+ total: 1,
174
+ hash: '0xtxhash',
175
+ });
176
+ }));
177
+ it('throws when response has no transactions', () => __awaiter(void 0, void 0, void 0, function* () {
178
+ jest.spyOn(mpc, 'delegationsApprove').mockResolvedValue({
179
+ metadata: delegations_1.mockEVMApproveResponse.metadata,
180
+ });
181
+ const d = new _1.default({
182
+ mpc,
183
+ signAndSendTransaction: mockSignAndSend,
184
+ });
185
+ yield expect(d.approveAndSubmit(delegations_1.mockEVMApproveRequest)).rejects.toThrow('No transactions in delegation response.');
186
+ expect(mockSignAndSend).not.toHaveBeenCalled();
187
+ }));
188
+ it('submits multiple EVM transactions in order', () => __awaiter(void 0, void 0, void 0, function* () {
189
+ const txA = Object.assign({}, delegations_1.mockEVMApproveResponse.transactions[0]);
190
+ const txB = Object.assign(Object.assign({}, txA), { data: '0xbbbb' });
191
+ jest.spyOn(mpc, 'delegationsApprove').mockResolvedValue(Object.assign(Object.assign({}, delegations_1.mockEVMApproveResponse), { transactions: [txA, txB] }));
192
+ const d = new _1.default({
193
+ mpc,
194
+ signAndSendTransaction: mockSignAndSend,
195
+ });
196
+ mockSignAndSend.mockResolvedValueOnce('0xh1').mockResolvedValueOnce('0xh2');
197
+ const result = yield d.approveAndSubmit(delegations_1.mockEVMApproveRequest);
198
+ expect(mockSignAndSend).toHaveBeenCalledTimes(2);
199
+ expect(mockSignAndSend).toHaveBeenNthCalledWith(1, txA, delegations_1.mockEVMApproveRequest.chain);
200
+ expect(mockSignAndSend).toHaveBeenNthCalledWith(2, txB, delegations_1.mockEVMApproveRequest.chain);
201
+ expect(result.hashes).toEqual(['0xh1', '0xh2']);
202
+ }));
203
+ it('propagates error after first tx succeeds (partial submit)', () => __awaiter(void 0, void 0, void 0, function* () {
204
+ const txA = Object.assign({}, delegations_1.mockEVMApproveResponse.transactions[0]);
205
+ const txB = Object.assign(Object.assign({}, txA), { data: '0xbbbb' });
206
+ jest.spyOn(mpc, 'delegationsApprove').mockResolvedValue(Object.assign(Object.assign({}, delegations_1.mockEVMApproveResponse), { transactions: [txA, txB] }));
207
+ const d = new _1.default({
208
+ mpc,
209
+ signAndSendTransaction: mockSignAndSend,
210
+ });
211
+ mockSignAndSend
212
+ .mockResolvedValueOnce('0xh1')
213
+ .mockRejectedValueOnce(new Error('second tx failed'));
214
+ yield expect(d.approveAndSubmit(delegations_1.mockEVMApproveRequest)).rejects.toThrow('second tx failed');
215
+ expect(mockSignAndSend).toHaveBeenCalledTimes(2);
216
+ }));
217
+ it('per-call signAndSendTransaction overrides constructor signer', () => __awaiter(void 0, void 0, void 0, function* () {
218
+ jest.spyOn(mpc, 'delegationsApprove').mockResolvedValue(delegations_1.mockEVMApproveResponse);
219
+ const instanceSigner = jest.fn().mockResolvedValue('0xinstance');
220
+ const perCallSigner = jest.fn().mockResolvedValue('0xpercall');
221
+ const d = new _1.default({
222
+ mpc,
223
+ signAndSendTransaction: instanceSigner,
224
+ });
225
+ yield d.approveAndSubmit(delegations_1.mockEVMApproveRequest, {
226
+ signAndSendTransaction: perCallSigner,
227
+ });
228
+ expect(instanceSigner).not.toHaveBeenCalled();
229
+ expect(perCallSigner).toHaveBeenCalledTimes(1);
230
+ }));
231
+ it('throws on whitespace-only hash from signer', () => __awaiter(void 0, void 0, void 0, function* () {
232
+ jest.spyOn(mpc, 'delegationsApprove').mockResolvedValue(delegations_1.mockEVMApproveResponse);
233
+ mockSignAndSend.mockResolvedValue(' ');
234
+ const d = new _1.default({
235
+ mpc,
236
+ signAndSendTransaction: mockSignAndSend,
237
+ });
238
+ yield expect(d.approveAndSubmit(delegations_1.mockEVMApproveRequest)).rejects.toThrow('Invalid transaction hash');
239
+ }));
240
+ it('prefers transactions over encodedTransactions when both non-empty', () => __awaiter(void 0, void 0, void 0, function* () {
241
+ const evmTx = delegations_1.mockEVMApproveResponse.transactions[0];
242
+ jest.spyOn(mpc, 'delegationsApprove').mockResolvedValue({
243
+ transactions: [evmTx],
244
+ encodedTransactions: ['should-not-use'],
245
+ metadata: delegations_1.mockEVMApproveResponse.metadata,
246
+ });
247
+ const d = new _1.default({
248
+ mpc,
249
+ signAndSendTransaction: mockSignAndSend,
250
+ });
251
+ yield d.approveAndSubmit(delegations_1.mockEVMApproveRequest);
252
+ expect(mockSignAndSend).toHaveBeenCalledWith(evmTx, delegations_1.mockEVMApproveRequest.chain);
253
+ }));
254
+ it('setSignAndSendTransaction enables submit after construction', () => __awaiter(void 0, void 0, void 0, function* () {
255
+ jest.spyOn(mpc, 'delegationsApprove').mockResolvedValue(delegations_1.mockEVMApproveResponse);
256
+ const d = new _1.default({ mpc });
257
+ const late = jest.fn().mockResolvedValue('0xlate');
258
+ d.setSignAndSendTransaction(late);
259
+ const result = yield d.approveAndSubmit(delegations_1.mockEVMApproveRequest);
260
+ expect(late).toHaveBeenCalledTimes(1);
261
+ expect(result).toEqual({ hashes: ['0xlate'] });
262
+ }));
263
+ });
93
264
  });
@@ -67,7 +67,15 @@ describe('Noah', () => {
67
67
  destinationAddress: 'SoLAddr1111111111111111111111111111111111111',
68
68
  };
69
69
  const spy = jest.spyOn(mpc, 'initiatePayin').mockResolvedValue({
70
- data: { payinId: 'p1', bankDetails: {} },
70
+ data: {
71
+ payinId: 'p1',
72
+ bankDetails: {
73
+ paymentMethodId: 'pm-123',
74
+ paymentMethodType: 'bank_account',
75
+ accountNumber: '1234567890',
76
+ network: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
77
+ },
78
+ },
71
79
  });
72
80
  yield noah.initiatePayin(req);
73
81
  expect(spy).toHaveBeenCalledWith(req);
@@ -79,14 +87,14 @@ describe('Noah', () => {
79
87
  fiatCurrency: 'USD',
80
88
  };
81
89
  const spy = jest.spyOn(mpc, 'simulatePayin').mockResolvedValue({
82
- data: {},
90
+ data: { fiatDepositId: 'deposit-123' },
83
91
  });
84
92
  yield noah.simulatePayin(sim);
85
93
  expect(spy).toHaveBeenCalledWith(sim);
86
94
  }));
87
95
  it('should call mpc.getPayoutCountries', () => __awaiter(void 0, void 0, void 0, function* () {
88
96
  const spy = jest.spyOn(mpc, 'getPayoutCountries').mockResolvedValue({
89
- data: { countries: [] },
97
+ data: { countries: { US: ['USD'], MX: ['MXN', 'USD'] } },
90
98
  });
91
99
  yield noah.getPayoutCountries();
92
100
  expect(spy).toHaveBeenCalled();
@@ -99,7 +107,7 @@ describe('Noah', () => {
99
107
  fiatAmount: '10',
100
108
  };
101
109
  const spy = jest.spyOn(mpc, 'getPayoutChannels').mockResolvedValue({
102
- data: [],
110
+ data: { items: [] },
103
111
  });
104
112
  yield noah.getPayoutChannels(req);
105
113
  expect(spy).toHaveBeenCalledWith(req);
@@ -107,7 +115,12 @@ describe('Noah', () => {
107
115
  it('should call mpc.getPayoutChannelForm', () => __awaiter(void 0, void 0, void 0, function* () {
108
116
  const spy = jest
109
117
  .spyOn(mpc, 'getPayoutChannelForm')
110
- .mockResolvedValue({ data: {} });
118
+ .mockResolvedValue({
119
+ data: {
120
+ formSchema: { type: 'object', properties: {} },
121
+ formMetadata: { contentHash: 'abc123' },
122
+ },
123
+ });
111
124
  yield noah.getPayoutChannelForm('ch-1');
112
125
  expect(spy).toHaveBeenCalledWith('ch-1');
113
126
  }));
@@ -1,4 +1,15 @@
1
1
  "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
2
13
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
14
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
15
  };
@@ -6,13 +17,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
17
  const zero_x_1 = __importDefault(require("./zero-x"));
7
18
  const lifi_1 = __importDefault(require("./lifi"));
8
19
  /**
9
- * This class is a container for the LiFi class.
10
- * In the future, Trading domain logic should be here.
20
+ * Trading integrations (LiFi, 0x).
11
21
  */
12
22
  class Trading {
13
- constructor({ mpc }) {
14
- this.lifi = new lifi_1.default({ mpc });
15
- this.zeroX = new zero_x_1.default({ mpc });
23
+ constructor(_a) {
24
+ var { mpc } = _a, defaults = __rest(_a, ["mpc"]);
25
+ this.lifi = new lifi_1.default(Object.assign({ mpc }, defaults));
26
+ this.zeroX = new zero_x_1.default(Object.assign({ mpc }, defaults));
16
27
  }
17
28
  }
18
29
  exports.default = Trading;