@metamask/transaction-pay-controller 24.0.3 → 24.1.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 (54) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/dist/strategy/relay/relay-quotes.cjs +6 -6
  3. package/dist/strategy/relay/relay-quotes.cjs.map +1 -1
  4. package/dist/strategy/relay/relay-quotes.d.cts.map +1 -1
  5. package/dist/strategy/relay/relay-quotes.d.mts.map +1 -1
  6. package/dist/strategy/relay/relay-quotes.mjs +8 -8
  7. package/dist/strategy/relay/relay-quotes.mjs.map +1 -1
  8. package/dist/strategy/server/perps.cjs +18 -1
  9. package/dist/strategy/server/perps.cjs.map +1 -1
  10. package/dist/strategy/server/perps.d.cts +5 -1
  11. package/dist/strategy/server/perps.d.cts.map +1 -1
  12. package/dist/strategy/server/perps.d.mts +5 -1
  13. package/dist/strategy/server/perps.d.mts.map +1 -1
  14. package/dist/strategy/server/perps.mjs +18 -1
  15. package/dist/strategy/server/perps.mjs.map +1 -1
  16. package/dist/strategy/server/server-quotes.cjs +68 -19
  17. package/dist/strategy/server/server-quotes.cjs.map +1 -1
  18. package/dist/strategy/server/server-quotes.d.cts.map +1 -1
  19. package/dist/strategy/server/server-quotes.d.mts.map +1 -1
  20. package/dist/strategy/server/server-quotes.mjs +69 -20
  21. package/dist/strategy/server/server-quotes.mjs.map +1 -1
  22. package/dist/strategy/server/server-submit.cjs +389 -61
  23. package/dist/strategy/server/server-submit.cjs.map +1 -1
  24. package/dist/strategy/server/server-submit.d.cts.map +1 -1
  25. package/dist/strategy/server/server-submit.d.mts.map +1 -1
  26. package/dist/strategy/server/server-submit.mjs +390 -62
  27. package/dist/strategy/server/server-submit.mjs.map +1 -1
  28. package/dist/strategy/server/types.cjs.map +1 -1
  29. package/dist/strategy/server/types.d.cts +20 -4
  30. package/dist/strategy/server/types.d.cts.map +1 -1
  31. package/dist/strategy/server/types.d.mts +20 -4
  32. package/dist/strategy/server/types.d.mts.map +1 -1
  33. package/dist/strategy/server/types.mjs.map +1 -1
  34. package/dist/utils/feature-flags.cjs +24 -1
  35. package/dist/utils/feature-flags.cjs.map +1 -1
  36. package/dist/utils/feature-flags.d.cts +9 -0
  37. package/dist/utils/feature-flags.d.cts.map +1 -1
  38. package/dist/utils/feature-flags.d.mts +9 -0
  39. package/dist/utils/feature-flags.d.mts.map +1 -1
  40. package/dist/utils/feature-flags.mjs +23 -1
  41. package/dist/utils/feature-flags.mjs.map +1 -1
  42. package/dist/utils/token.cjs +1 -1
  43. package/dist/utils/token.cjs.map +1 -1
  44. package/dist/utils/token.d.cts.map +1 -1
  45. package/dist/utils/token.d.mts.map +1 -1
  46. package/dist/utils/token.mjs +3 -3
  47. package/dist/utils/token.mjs.map +1 -1
  48. package/dist/utils/transaction.cjs +4 -1
  49. package/dist/utils/transaction.cjs.map +1 -1
  50. package/dist/utils/transaction.d.cts.map +1 -1
  51. package/dist/utils/transaction.d.mts.map +1 -1
  52. package/dist/utils/transaction.mjs +4 -1
  53. package/dist/utils/transaction.mjs.map +1 -1
  54. package/package.json +3 -3
@@ -2,14 +2,31 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.submitServerQuotes = void 0;
4
4
  const controller_utils_1 = require("@metamask/controller-utils");
5
+ const keyring_controller_1 = require("@metamask/keyring-controller");
5
6
  const utils_1 = require("@metamask/utils");
6
7
  const bignumber_js_1 = require("bignumber.js");
7
8
  const logger_1 = require("../../logger.cjs");
8
9
  const feature_flags_1 = require("../../utils/feature-flags.cjs");
10
+ const provider_1 = require("../../utils/provider.cjs");
11
+ const token_1 = require("../../utils/token.cjs");
9
12
  const transaction_1 = require("../../utils/transaction.cjs");
13
+ const constants_1 = require("../relay/constants.cjs");
10
14
  const server_api_1 = require("./server-api.cjs");
11
15
  const types_1 = require("./types.cjs");
12
16
  const log = (0, utils_1.createModuleLogger)(logger_1.projectLogger, 'server-strategy');
17
+ const DOMAIN_FIELD_MAP = {
18
+ name: { name: 'name', type: 'string' },
19
+ version: { name: 'version', type: 'string' },
20
+ chainId: { name: 'chainId', type: 'uint256' },
21
+ verifyingContract: { name: 'verifyingContract', type: 'address' },
22
+ salt: { name: 'salt', type: 'bytes32' },
23
+ };
24
+ function isSignatureStep(step) {
25
+ return step.type === 'signature';
26
+ }
27
+ function isTransactionStep(step) {
28
+ return step.type === 'transaction';
29
+ }
13
30
  /**
14
31
  * Submits server intent quotes.
15
32
  *
@@ -35,12 +52,15 @@ async function executeSingleServerQuote(quote, messenger, transaction) {
35
52
  }, (tx) => {
36
53
  tx.txParams.nonce = undefined;
37
54
  });
38
- if (quote.original.gasless) {
39
- await submitViaServerExecute(quote, messenger, transaction);
40
- }
41
- else {
42
- await submitViaTransactionController(quote, messenger, transaction);
55
+ // Phase 1: off-chain signature steps (e.g. Relay authorize, HyperLiquid deposit).
56
+ // Use quote.request.from (resolved accountOverride) not transaction.txParams.from.
57
+ const signatureSteps = quote.original.steps.filter(isSignatureStep);
58
+ for (const step of signatureSteps) {
59
+ await submitSignatureStep(step, quote.request.from, messenger);
43
60
  }
61
+ // Phase 2: on-chain transaction steps (if any).
62
+ await submitTransactionSteps(quote, messenger, transaction);
63
+ // Phase 3: poll until the intent is confirmed on the target chain.
44
64
  const targetHash = await waitForServerCompletion(quote.original, messenger, transaction.id);
45
65
  log('Server request completed', targetHash);
46
66
  (0, transaction_1.updateTransaction)({
@@ -52,13 +72,299 @@ async function executeSingleServerQuote(quote, messenger, transaction) {
52
72
  });
53
73
  return { transactionHash: targetHash };
54
74
  }
55
- async function submitViaServerExecute(quote, messenger, transaction) {
56
- const { from, sourceChainId } = quote.request;
57
- const networkClientId = messenger.call('NetworkController:findNetworkClientIdByChainId', sourceChainId);
58
- const nestedTransactions = quote.original.steps.map((step) => ({
75
+ /**
76
+ * Submit the on-chain transaction steps for a server quote.
77
+ *
78
+ * Validates the source balance, builds the complete set of params (including
79
+ * any post-quote or payment-override prepends), then dispatches to the
80
+ * gasless execute path or TransactionController depending on the quote.
81
+ *
82
+ * No-ops when the quote has no transaction steps (signature-only flows).
83
+ *
84
+ * @param quote - Server quote.
85
+ * @param messenger - Controller messenger.
86
+ * @param transaction - Original transaction meta.
87
+ */
88
+ async function submitTransactionSteps(quote, messenger, transaction) {
89
+ const transactionSteps = quote.original.steps.filter(isTransactionStep);
90
+ if (transactionSteps.length === 0) {
91
+ // Signature-only quotes (all steps are signature steps, no transaction
92
+ // steps) have nothing to submit on-chain even when gasless is false.
93
+ const hasSignatureSteps = quote.original.steps.some(isSignatureStep);
94
+ if (!quote.original.gasless && !hasSignatureSteps) {
95
+ throw new Error('Server quote has no steps to submit');
96
+ }
97
+ return;
98
+ }
99
+ const { isHyperliquidSource, isPostQuote, paymentOverride } = quote.request;
100
+ // Skip balance check for HyperLiquid source flows (no on-chain debit),
101
+ // post-quote flows (funds come from the Safe after the original tx executes),
102
+ // and payment-override flows (funds are supplied by the override account).
103
+ if (!isHyperliquidSource && !isPostQuote && !paymentOverride) {
104
+ await validateSourceBalance(quote, messenger);
105
+ }
106
+ const allParams = await buildTransactionParams(quote, transactionSteps, transaction, messenger);
107
+ if (quote.original.gasless) {
108
+ await submitViaServerExecute(quote, allParams, messenger, transaction);
109
+ }
110
+ else {
111
+ await submitViaTransactionController(quote, allParams, messenger, transaction);
112
+ }
113
+ }
114
+ /**
115
+ * Build the complete flat array of TransactionParams for on-chain submission.
116
+ *
117
+ * Converts server transaction steps to params, then prepends any additional
118
+ * calls required by the payment override or post-quote flow. The returned
119
+ * array is ready to pass directly to submitViaServerExecute or
120
+ * submitViaTransactionController.
121
+ *
122
+ * @param quote - Server quote.
123
+ * @param transactionSteps - Transaction steps from the quote (pre-filtered).
124
+ * @param transaction - Original transaction meta.
125
+ * @param messenger - Controller messenger.
126
+ * @returns Complete ordered array of TransactionParams for submission.
127
+ */
128
+ async function buildTransactionParams(quote, transactionSteps, transaction, messenger) {
129
+ const { from, isPostQuote, paymentOverride } = quote.request;
130
+ const { gasLimits, maxFeePerGas, maxPriorityFeePerGas } = quote.original.client;
131
+ const originalType = getEffectiveTransactionType(transaction);
132
+ const relayParams = transactionSteps.map((step, i) => transactionStepToParams(step, i, transactionSteps.length, from, gasLimits, maxFeePerGas, maxPriorityFeePerGas, originalType));
133
+ if (paymentOverride) {
134
+ return prependPaymentOverrideParams(relayParams, quote, transaction, messenger);
135
+ }
136
+ if (isPostQuote && transaction.txParams.to) {
137
+ return prependPostQuoteParams(relayParams, quote, transaction, messenger);
138
+ }
139
+ return relayParams;
140
+ }
141
+ /**
142
+ * Converts a single server transaction step to TransactionParams.
143
+ *
144
+ * @param step - The transaction step.
145
+ * @param index - Zero-based position within the transaction steps array.
146
+ * @param totalSteps - Total number of transaction steps (for type mapping).
147
+ * @param from - Sender address.
148
+ * @param gasLimits - Per-step gas limits from client-side estimation.
149
+ * @param clientMaxFeePerGas - Client-side max fee per gas fallback.
150
+ * @param clientMaxPriorityFeePerGas - Client-side max priority fee per gas fallback.
151
+ * @param originalType - Effective type of the parent transaction.
152
+ * @returns Normalized TransactionParams for this step.
153
+ */
154
+ function transactionStepToParams(step, index, totalSteps, from, gasLimits, clientMaxFeePerGas, clientMaxPriorityFeePerGas, originalType) {
155
+ let gas;
156
+ const gasLimit = gasLimits[index];
157
+ if (gasLimit) {
158
+ gas = (0, controller_utils_1.toHex)(gasLimit);
159
+ }
160
+ else if (step.gasLimit) {
161
+ gas = (0, controller_utils_1.toHex)(step.gasLimit);
162
+ }
163
+ const resolvedMaxFeePerGas = step.maxFeePerGas ?? clientMaxFeePerGas;
164
+ const resolvedMaxPriorityFeePerGas = step.maxPriorityFeePerGas ?? clientMaxPriorityFeePerGas;
165
+ const params = {
59
166
  data: step.data,
167
+ from,
168
+ gas,
169
+ maxFeePerGas: resolvedMaxFeePerGas
170
+ ? (0, controller_utils_1.toHex)(resolvedMaxFeePerGas)
171
+ : undefined,
172
+ maxPriorityFeePerGas: resolvedMaxPriorityFeePerGas
173
+ ? (0, controller_utils_1.toHex)(resolvedMaxPriorityFeePerGas)
174
+ : undefined,
60
175
  to: step.to,
61
- value: step.value,
176
+ type: getTransactionType(index, totalSteps, originalType),
177
+ value: (0, controller_utils_1.toHex)(step.value),
178
+ };
179
+ log('Built transaction params for step', {
180
+ index,
181
+ step: {
182
+ gasLimit: step.gasLimit,
183
+ maxFeePerGas: step.maxFeePerGas,
184
+ maxPriorityFeePerGas: step.maxPriorityFeePerGas,
185
+ value: step.value,
186
+ },
187
+ params: {
188
+ gas: params.gas,
189
+ maxFeePerGas: params.maxFeePerGas,
190
+ maxPriorityFeePerGas: params.maxPriorityFeePerGas,
191
+ type: params.type,
192
+ value: params.value,
193
+ },
194
+ });
195
+ return params;
196
+ }
197
+ /**
198
+ * Determine the relay deposit transaction type for a step at the given index.
199
+ *
200
+ * Single-step quotes always use the deposit type. In multi-step quotes the
201
+ * first step is an approval and subsequent steps are deposits — matching the
202
+ * Relay strategy's convention.
203
+ *
204
+ * @param index - Zero-based index of the step within the transaction step array.
205
+ * @param totalSteps - Total number of transaction steps.
206
+ * @param originalType - Effective type of the parent transaction.
207
+ * @returns The mapped TransactionType for this step.
208
+ */
209
+ function getTransactionType(index, totalSteps, originalType) {
210
+ const depositType = getRelayDepositType(originalType);
211
+ if (totalSteps === 1) {
212
+ return depositType;
213
+ }
214
+ return index === 0 ? 'tokenMethodApprove' : depositType;
215
+ }
216
+ /**
217
+ * Get the relay deposit transaction type based on the parent transaction type.
218
+ *
219
+ * @param originalType - Type of the parent transaction.
220
+ * @returns The mapped relay deposit type, or `relayDeposit` as a fallback.
221
+ */
222
+ function getRelayDepositType(originalType) {
223
+ return ((originalType && constants_1.RELAY_DEPOSIT_TYPES[originalType]) ??
224
+ 'relayDeposit');
225
+ }
226
+ /**
227
+ * Get the effective transaction type, resolving through batch-type parent
228
+ * transactions to find the nested perps/predict type.
229
+ *
230
+ * @param transaction - The transaction metadata.
231
+ * @returns The resolved type from nested transactions, or the top-level type.
232
+ */
233
+ function getEffectiveTransactionType(transaction) {
234
+ if (transaction.type !== 'batch') {
235
+ return transaction.type;
236
+ }
237
+ const nestedType = transaction.nestedTransactions?.find((tx) => tx.type && constants_1.RELAY_DEPOSIT_TYPES[tx.type] !== undefined)?.type;
238
+ return nestedType ?? transaction.type;
239
+ }
240
+ /**
241
+ * Prepend payment override calls before the relay transaction steps.
242
+ *
243
+ * For money-account payment override flows the override account supplies
244
+ * the source funds. The override transactions must be batched ahead of the
245
+ * relay deposit steps.
246
+ *
247
+ * @param relayParams - Already-built relay step params.
248
+ * @param quote - Server quote.
249
+ * @param transaction - Original transaction meta.
250
+ * @param messenger - Controller messenger.
251
+ * @returns Combined params with override calls prepended.
252
+ */
253
+ async function prependPaymentOverrideParams(relayParams, quote, transaction, messenger) {
254
+ const { transactionData } = messenger.call('TransactionPayController:getState');
255
+ const { calls: overrideCalls } = await messenger.call('TransactionPayController:getPaymentOverrideData', {
256
+ amount: quote.sourceAmount.human,
257
+ transaction,
258
+ transactionData: transactionData[transaction.id],
259
+ });
260
+ if (!overrideCalls.length) {
261
+ log('No payment override calls to prepend');
262
+ return relayParams;
263
+ }
264
+ log('Prepending payment override calls', { count: overrideCalls.length });
265
+ return [...overrideCalls, ...relayParams];
266
+ }
267
+ /**
268
+ * Prepend the original transaction (or a delegation-wrapped version) before
269
+ * the relay deposit steps for post-quote flows.
270
+ *
271
+ * In post-quote flows the source tokens are held in the Safe and only become
272
+ * available after the original transaction executes as part of the batch.
273
+ * When an accountOverride is active the override account cannot directly
274
+ * execute the original call, so it is wrapped in a delegation transaction.
275
+ *
276
+ * @param relayParams - Already-built relay step params.
277
+ * @param quote - Server quote.
278
+ * @param transaction - Original transaction meta.
279
+ * @param messenger - Controller messenger.
280
+ * @returns Combined params with the original tx prepended.
281
+ */
282
+ async function prependPostQuoteParams(relayParams, quote, transaction, messenger) {
283
+ const hasAccountOverride = quote.request.from.toLowerCase() !==
284
+ transaction.txParams.from.toLowerCase();
285
+ const { maxFeePerGas, maxPriorityFeePerGas } = quote.original.client;
286
+ let prependedParams;
287
+ if (hasAccountOverride) {
288
+ prependedParams = await buildDelegatedOriginalParams(transaction, messenger);
289
+ }
290
+ else {
291
+ prependedParams = {
292
+ data: transaction.txParams.data,
293
+ from: transaction.txParams.from,
294
+ to: transaction.txParams.to,
295
+ value: transaction.txParams.value,
296
+ };
297
+ }
298
+ // Ensure the prepended tx carries the same fee caps as the relay steps so
299
+ // it isn't submitted with undefined maxFeePerGas in a non-7702 batch.
300
+ prependedParams.maxFeePerGas = maxFeePerGas ? (0, controller_utils_1.toHex)(maxFeePerGas) : undefined;
301
+ prependedParams.maxPriorityFeePerGas = maxPriorityFeePerGas
302
+ ? (0, controller_utils_1.toHex)(maxPriorityFeePerGas)
303
+ : undefined;
304
+ log('Prepending post-quote original tx', { hasAccountOverride });
305
+ return [prependedParams, ...relayParams];
306
+ }
307
+ /**
308
+ * Build TransactionParams for a delegation that redeems the original
309
+ * post-quote transaction on behalf of the override account.
310
+ *
311
+ * @param transaction - Original transaction meta to be redeemed.
312
+ * @param messenger - Controller messenger.
313
+ * @returns Transaction params for the delegation tx.
314
+ */
315
+ async function buildDelegatedOriginalParams(transaction, messenger) {
316
+ const delegation = await messenger.call('TransactionPayController:getDelegationTransaction', { transaction });
317
+ log('Delegation result for post-quote original tx', delegation);
318
+ return {
319
+ data: delegation.data,
320
+ from: transaction.txParams.from,
321
+ to: delegation.to,
322
+ value: delegation.value,
323
+ };
324
+ }
325
+ /**
326
+ * Validate that the user's source token balance covers the quote's required
327
+ * source amount before submitting on-chain.
328
+ *
329
+ * Reads the live balance via RPC rather than the cached state so it reflects
330
+ * any concurrent spends. Throws fast with a clear error instead of letting
331
+ * the on-chain transaction revert.
332
+ *
333
+ * @param quote - Server quote containing the required source amount.
334
+ * @param messenger - Controller messenger.
335
+ */
336
+ async function validateSourceBalance(quote, messenger) {
337
+ const { from, sourceChainId, sourceTokenAddress } = quote.request;
338
+ const normalizedSourceTokenAddress = (0, token_1.normalizeTokenAddress)(sourceTokenAddress, sourceChainId, token_1.TokenAddressTarget.MetaMask);
339
+ let currentBalance;
340
+ try {
341
+ currentBalance = await (0, token_1.getLiveTokenBalance)(messenger, from, sourceChainId, normalizedSourceTokenAddress);
342
+ }
343
+ catch (error) {
344
+ throw new Error(`Cannot validate payment token balance - ${error.message}`);
345
+ }
346
+ const requiredAmount = new bignumber_js_1.BigNumber(quote.sourceAmount.raw);
347
+ const balance = new bignumber_js_1.BigNumber(currentBalance);
348
+ log('Validating source balance', {
349
+ from,
350
+ sourceChainId,
351
+ sourceTokenAddress,
352
+ currentBalance,
353
+ requiredAmount: requiredAmount.toString(10),
354
+ });
355
+ if (balance.isLessThan(requiredAmount)) {
356
+ throw new Error(`Insufficient source token balance for server deposit. ` +
357
+ `Required: ${requiredAmount.toString(10)}, ` +
358
+ `Available: ${balance.toString(10)}`);
359
+ }
360
+ }
361
+ async function submitViaServerExecute(quote, allParams, messenger, transaction) {
362
+ const { from, sourceChainId } = quote.request;
363
+ const networkClientId = (0, provider_1.getNetworkClientId)(messenger, sourceChainId);
364
+ const nestedTransactions = allParams.map((params) => ({
365
+ data: (params.data ?? '0x'),
366
+ to: params.to,
367
+ value: (params.value ?? '0x0'),
62
368
  }));
63
369
  const sourceCallTransaction = {
64
370
  ...transaction,
@@ -97,15 +403,10 @@ async function submitViaServerExecute(quote, messenger, transaction) {
97
403
  throw new Error(`Server submit failed: ${submitResponse.error ?? 'unknown'}`);
98
404
  }
99
405
  }
100
- async function submitViaTransactionController(quote, messenger, transaction) {
406
+ async function submitViaTransactionController(quote, allParams, messenger, transaction) {
101
407
  const { from, sourceChainId, sourceTokenAddress } = quote.request;
102
- const { steps } = quote.original;
103
- if (steps.length === 0) {
104
- throw new Error('Server quote has no steps to submit');
105
- }
106
- const networkClientId = messenger.call('NetworkController:findNetworkClientIdByChainId', sourceChainId);
107
- const { gasLimits, is7702, maxFeePerGas, maxPriorityFeePerGas } = quote.original.client;
108
- const transactionParams = steps.map((step, i) => stepToParams(step, from, gasLimits[i], maxFeePerGas, maxPriorityFeePerGas));
408
+ const { gasLimits, is7702 } = quote.original.client;
409
+ const networkClientId = (0, provider_1.getNetworkClientId)(messenger, sourceChainId);
109
410
  const gasFeeToken = quote.fees.isSourceGasFeeToken
110
411
  ? sourceTokenAddress
111
412
  : undefined;
@@ -114,8 +415,8 @@ async function submitViaTransactionController(quote, messenger, transaction) {
114
415
  gasFeeToken,
115
416
  is7702,
116
417
  networkClientId,
418
+ paramCount: allParams.length,
117
419
  sourceChainId,
118
- stepCount: steps.length,
119
420
  });
120
421
  const transactionIds = [];
121
422
  const { end } = (0, transaction_1.collectTransactionIds)(sourceChainId, from, messenger, (transactionId) => {
@@ -130,7 +431,7 @@ async function submitViaTransactionController(quote, messenger, transaction) {
130
431
  });
131
432
  });
132
433
  try {
133
- if (transactionParams.length === 1) {
434
+ if (allParams.length === 1) {
134
435
  const addTransactionOptions = {
135
436
  gasFeeToken,
136
437
  isInternal: true,
@@ -139,16 +440,19 @@ async function submitViaTransactionController(quote, messenger, transaction) {
139
440
  requireApproval: false,
140
441
  };
141
442
  log('Calling addTransaction', {
142
- params: transactionParams[0],
443
+ params: allParams[0],
143
444
  options: addTransactionOptions,
144
445
  });
145
- await messenger.call('TransactionController:addTransaction', transactionParams[0], addTransactionOptions);
446
+ await messenger.call('TransactionController:addTransaction', allParams[0], addTransactionOptions);
146
447
  }
147
448
  else {
148
449
  const gasLimit7702 = is7702 ? (0, controller_utils_1.toHex)(gasLimits[0]) : undefined;
149
- const batchTransactions = transactionParams.map((params, i) => {
150
- const gas = (gasLimit7702 ??
151
- (gasLimits[i] === undefined ? undefined : params.gas));
450
+ const batchTransactions = allParams.map((params) => {
451
+ // params.gas was already resolved correctly by transactionStepToParams
452
+ // (indexed by relay-step position), so use it directly. Indexing
453
+ // allParams position into gasLimits would be wrong when payment-
454
+ // override or post-quote params are prepended.
455
+ const gas = (gasLimit7702 ?? params.gas);
152
456
  return {
153
457
  params: {
154
458
  data: params.data,
@@ -158,6 +462,7 @@ async function submitViaTransactionController(quote, messenger, transaction) {
158
462
  to: params.to,
159
463
  value: params.value,
160
464
  },
465
+ type: params.type,
161
466
  };
162
467
  });
163
468
  const addTransactionBatchOptions = {
@@ -203,44 +508,67 @@ async function submitViaTransactionController(quote, messenger, transaction) {
203
508
  });
204
509
  }
205
510
  }
206
- function stepToParams(step, from, gasLimit, clientMaxFeePerGas, clientMaxPriorityFeePerGas) {
207
- let gas;
208
- if (gasLimit) {
209
- gas = (0, controller_utils_1.toHex)(gasLimit);
210
- }
211
- else if (step.gasLimit) {
212
- gas = (0, controller_utils_1.toHex)(step.gasLimit);
213
- }
214
- const resolvedMaxFeePerGas = step.maxFeePerGas ?? clientMaxFeePerGas;
215
- const resolvedMaxPriorityFeePerGas = step.maxPriorityFeePerGas ?? clientMaxPriorityFeePerGas;
216
- const params = {
217
- data: step.data,
218
- from,
219
- gas,
220
- maxFeePerGas: resolvedMaxFeePerGas
221
- ? (0, controller_utils_1.toHex)(resolvedMaxFeePerGas)
222
- : undefined,
223
- maxPriorityFeePerGas: resolvedMaxPriorityFeePerGas
224
- ? (0, controller_utils_1.toHex)(resolvedMaxPriorityFeePerGas)
225
- : undefined,
226
- to: step.to,
227
- value: (0, controller_utils_1.toHex)(step.value),
228
- };
229
- log('Step to params', {
230
- step: {
231
- gasLimit: step.gasLimit,
232
- maxFeePerGas: step.maxFeePerGas,
233
- maxPriorityFeePerGas: step.maxPriorityFeePerGas,
234
- value: step.value,
235
- },
236
- params: {
237
- gas: params.gas,
238
- maxFeePerGas: params.maxFeePerGas,
239
- maxPriorityFeePerGas: params.maxPriorityFeePerGas,
240
- value: params.value,
511
+ async function submitSignatureStep(step, from, messenger) {
512
+ const { sign, post } = step;
513
+ const typedData = {
514
+ domain: sign.domain,
515
+ types: {
516
+ ...sign.types,
517
+ EIP712Domain: deriveEIP712DomainType(sign.domain),
241
518
  },
519
+ primaryType: sign.primaryType,
520
+ message: sign.value,
521
+ };
522
+ log('Signing typed data for signature step', {
523
+ primaryType: sign.primaryType,
242
524
  });
243
- return params;
525
+ const signature = await messenger.call('KeyringController:signTypedMessage', { from, data: JSON.stringify(typedData) }, keyring_controller_1.SignTypedDataVersion.V4);
526
+ await postSignatureStepResult(post.endpoint, post.method, post.body, signature, post.signatureFormat);
527
+ }
528
+ async function postSignatureStepResult(endpoint, method, body, signature, signatureFormat) {
529
+ let url = endpoint;
530
+ let postBody;
531
+ if (signatureFormat === 'queryParam') {
532
+ url = `${endpoint}?signature=${signature}`;
533
+ postBody = body;
534
+ }
535
+ else {
536
+ // eslint-disable-next-line id-length
537
+ const r = signature.slice(0, 66);
538
+ // eslint-disable-next-line id-length
539
+ const s = `0x${signature.slice(66, 130)}`;
540
+ // eslint-disable-next-line id-length
541
+ const v = parseInt(signature.slice(130, 132), 16);
542
+ postBody = { ...body, signature: { r, s, v } };
543
+ }
544
+ log('Posting signature step result', { url, signatureFormat });
545
+ let result;
546
+ try {
547
+ const response = await (0, controller_utils_1.successfulFetch)(url, {
548
+ method,
549
+ headers: { 'Content-Type': 'application/json' },
550
+ body: JSON.stringify(postBody),
551
+ });
552
+ result = await response.json();
553
+ }
554
+ catch (error) {
555
+ throw new Error(`Signature step POST failed: ${error.message}`);
556
+ }
557
+ log('Signature step POST response', result);
558
+ // For rsv-format steps (HyperLiquid exchange endpoint) the response body
559
+ // carries an explicit status field. Validate it here so a failed deposit
560
+ // throws immediately rather than silently proceeding to the polling phase.
561
+ if (signatureFormat === 'rsv') {
562
+ const status = result?.status;
563
+ if (status !== 'ok') {
564
+ throw new Error(`Signature step rejected by server: ${JSON.stringify(result)}`);
565
+ }
566
+ }
567
+ }
568
+ function deriveEIP712DomainType(domain) {
569
+ return Object.keys(DOMAIN_FIELD_MAP)
570
+ .filter((key) => Object.prototype.hasOwnProperty.call(domain, key))
571
+ .map((key) => DOMAIN_FIELD_MAP[key]);
244
572
  }
245
573
  async function waitForServerCompletion(quote, messenger, transactionId) {
246
574
  const pollingInterval = (0, feature_flags_1.getServerPollingInterval)(messenger);