@metamask/transaction-pay-controller 24.0.3 → 25.0.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/CHANGELOG.md +28 -1
- package/dist/constants.cjs +8 -2
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +7 -1
- package/dist/constants.d.cts.map +1 -1
- package/dist/constants.d.mts +7 -1
- package/dist/constants.d.mts.map +1 -1
- package/dist/constants.mjs +8 -2
- package/dist/constants.mjs.map +1 -1
- package/dist/helpers/QuoteRefresher.cjs +2 -1
- package/dist/helpers/QuoteRefresher.cjs.map +1 -1
- package/dist/helpers/QuoteRefresher.d.cts.map +1 -1
- package/dist/helpers/QuoteRefresher.d.mts.map +1 -1
- package/dist/helpers/QuoteRefresher.mjs +2 -1
- package/dist/helpers/QuoteRefresher.mjs.map +1 -1
- package/dist/helpers/TransactionPayPublishHook.cjs +5 -3
- package/dist/helpers/TransactionPayPublishHook.cjs.map +1 -1
- package/dist/helpers/TransactionPayPublishHook.d.cts.map +1 -1
- package/dist/helpers/TransactionPayPublishHook.d.mts.map +1 -1
- package/dist/helpers/TransactionPayPublishHook.mjs +5 -3
- package/dist/helpers/TransactionPayPublishHook.mjs.map +1 -1
- package/dist/strategy/relay/relay-quotes.cjs +6 -6
- package/dist/strategy/relay/relay-quotes.cjs.map +1 -1
- package/dist/strategy/relay/relay-quotes.d.cts.map +1 -1
- package/dist/strategy/relay/relay-quotes.d.mts.map +1 -1
- package/dist/strategy/relay/relay-quotes.mjs +8 -8
- package/dist/strategy/relay/relay-quotes.mjs.map +1 -1
- package/dist/strategy/server/perps.cjs +18 -1
- package/dist/strategy/server/perps.cjs.map +1 -1
- package/dist/strategy/server/perps.d.cts +5 -1
- package/dist/strategy/server/perps.d.cts.map +1 -1
- package/dist/strategy/server/perps.d.mts +5 -1
- package/dist/strategy/server/perps.d.mts.map +1 -1
- package/dist/strategy/server/perps.mjs +18 -1
- package/dist/strategy/server/perps.mjs.map +1 -1
- package/dist/strategy/server/server-quotes.cjs +68 -19
- package/dist/strategy/server/server-quotes.cjs.map +1 -1
- package/dist/strategy/server/server-quotes.d.cts.map +1 -1
- package/dist/strategy/server/server-quotes.d.mts.map +1 -1
- package/dist/strategy/server/server-quotes.mjs +69 -20
- package/dist/strategy/server/server-quotes.mjs.map +1 -1
- package/dist/strategy/server/server-submit.cjs +389 -61
- package/dist/strategy/server/server-submit.cjs.map +1 -1
- package/dist/strategy/server/server-submit.d.cts.map +1 -1
- package/dist/strategy/server/server-submit.d.mts.map +1 -1
- package/dist/strategy/server/server-submit.mjs +390 -62
- package/dist/strategy/server/server-submit.mjs.map +1 -1
- package/dist/strategy/server/types.cjs.map +1 -1
- package/dist/strategy/server/types.d.cts +20 -4
- package/dist/strategy/server/types.d.cts.map +1 -1
- package/dist/strategy/server/types.d.mts +20 -4
- package/dist/strategy/server/types.d.mts.map +1 -1
- package/dist/strategy/server/types.mjs.map +1 -1
- package/dist/utils/feature-flags.cjs +24 -1
- package/dist/utils/feature-flags.cjs.map +1 -1
- package/dist/utils/feature-flags.d.cts +9 -0
- package/dist/utils/feature-flags.d.cts.map +1 -1
- package/dist/utils/feature-flags.d.mts +9 -0
- package/dist/utils/feature-flags.d.mts.map +1 -1
- package/dist/utils/feature-flags.mjs +23 -1
- package/dist/utils/feature-flags.mjs.map +1 -1
- package/dist/utils/no-op-quote.cjs +55 -0
- package/dist/utils/no-op-quote.cjs.map +1 -0
- package/dist/utils/no-op-quote.d.cts +18 -0
- package/dist/utils/no-op-quote.d.cts.map +1 -0
- package/dist/utils/no-op-quote.d.mts +18 -0
- package/dist/utils/no-op-quote.d.mts.map +1 -0
- package/dist/utils/no-op-quote.mjs +51 -0
- package/dist/utils/no-op-quote.mjs.map +1 -0
- package/dist/utils/quotes.cjs +29 -6
- package/dist/utils/quotes.cjs.map +1 -1
- package/dist/utils/quotes.d.cts.map +1 -1
- package/dist/utils/quotes.d.mts.map +1 -1
- package/dist/utils/quotes.mjs +29 -6
- package/dist/utils/quotes.mjs.map +1 -1
- package/dist/utils/token.cjs +1 -1
- package/dist/utils/token.cjs.map +1 -1
- package/dist/utils/token.d.cts.map +1 -1
- package/dist/utils/token.d.mts.map +1 -1
- package/dist/utils/token.mjs +3 -3
- package/dist/utils/token.mjs.map +1 -1
- package/dist/utils/transaction.cjs +4 -1
- package/dist/utils/transaction.cjs.map +1 -1
- package/dist/utils/transaction.d.cts.map +1 -1
- package/dist/utils/transaction.d.mts.map +1 -1
- package/dist/utils/transaction.mjs +4 -1
- package/dist/utils/transaction.mjs.map +1 -1
- 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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
await
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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 {
|
|
103
|
-
|
|
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 (
|
|
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:
|
|
443
|
+
params: allParams[0],
|
|
143
444
|
options: addTransactionOptions,
|
|
144
445
|
});
|
|
145
|
-
await messenger.call('TransactionController:addTransaction',
|
|
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 =
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
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);
|