Skip to content

Commit 682da9c

Browse files
committed
refactor: unify submitTx and submitIntent
rename wip
1 parent 8029245 commit 682da9c

9 files changed

Lines changed: 658 additions & 503 deletions

File tree

packages/bridge-status-controller/src/bridge-status-controller.ts

Lines changed: 107 additions & 483 deletions
Large diffs are not rendered by default.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { isEvmTxData } from '@metamask/bridge-controller';
2+
3+
import { ExecuteParams, SubmitResult } from './types';
4+
import {
5+
addTransactionBatch,
6+
getAddTransactionBatchParams,
7+
} from '../utils/transaction';
8+
9+
/**
10+
* Submits batched EVM transactions to the TransactionController
11+
*
12+
* @param args - The parameters for the transaction
13+
* @param args.isBridgeTx - Whether the transaction is a bridge transaction
14+
* @param args.quoteResponse - The quote response
15+
* @param args.messenger - The messenger
16+
* @param args.requireApproval - Whether to require approval for the transaction
17+
* @param args.addTransactionBatchFn - The function to add the transaction batch
18+
* @yields The approvalMeta and tradeMeta for the batched transaction
19+
*/
20+
export async function* submitBatchHandler({
21+
requireApproval,
22+
quoteResponse,
23+
messenger,
24+
isBridgeTx,
25+
addTransactionBatchFn,
26+
}: ExecuteParams): AsyncGenerator<SubmitResult, void, void> {
27+
if (!isEvmTxData(quoteResponse.trade)) {
28+
throw new Error(
29+
'Failed to submit cross-chain swap transaction: trade is not an EVM transaction',
30+
);
31+
}
32+
const transactionParams = await getAddTransactionBatchParams({
33+
messenger,
34+
isBridgeTx,
35+
resetApproval: quoteResponse.resetApproval,
36+
approval:
37+
quoteResponse.approval && isEvmTxData(quoteResponse.approval)
38+
? quoteResponse.approval
39+
: undefined,
40+
trade: quoteResponse.trade,
41+
quoteResponse,
42+
requireApproval,
43+
});
44+
45+
const { approvalMeta, tradeMeta } = await addTransactionBatch(
46+
messenger,
47+
addTransactionBatchFn,
48+
transactionParams,
49+
);
50+
51+
yield {
52+
tradeMeta,
53+
};
54+
55+
yield {
56+
historyItem: {
57+
approvalTxId: approvalMeta?.id,
58+
txMetaId: tradeMeta?.id,
59+
},
60+
};
61+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
2+
import { isEvmTxData } from '@metamask/bridge-controller';
3+
import { TransactionType } from '@metamask/transaction-controller';
4+
5+
import { ExecuteParams } from './types';
6+
import { getApprovalTraceParams } from '../utils/trace';
7+
import {
8+
handleApprovalDelay,
9+
handleMobileHardwareWalletDelay,
10+
submitEvmTransaction,
11+
} from '../utils/transaction';
12+
13+
export const handleApprovalTx = async ({
14+
quoteResponse,
15+
messenger,
16+
isBridgeTx,
17+
requireApproval,
18+
}: ExecuteParams) => {
19+
const {
20+
approval,
21+
resetApproval,
22+
quote: { srcChainId },
23+
} = quoteResponse;
24+
if (!approval || !isEvmTxData(approval)) {
25+
return {};
26+
}
27+
if (resetApproval) {
28+
await submitEvmTransaction({
29+
messenger,
30+
transactionType: TransactionType.bridgeApproval,
31+
trade: resetApproval,
32+
});
33+
}
34+
35+
const approvalTxMeta = await submitEvmTransaction({
36+
messenger,
37+
transactionType: isBridgeTx
38+
? TransactionType.bridgeApproval
39+
: TransactionType.swapApproval,
40+
trade: approval,
41+
requireApproval,
42+
});
43+
44+
await handleApprovalDelay(srcChainId);
45+
await handleMobileHardwareWalletDelay(Boolean(requireApproval));
46+
return { approvalTxId: approvalTxMeta?.id };
47+
};
48+
49+
export async function* submitEvmHandler(args: ExecuteParams) {
50+
const {
51+
quoteResponse,
52+
messenger,
53+
traceFn,
54+
isBridgeTx,
55+
requireApproval,
56+
actionId,
57+
isStxEnabledOnClient,
58+
} = args;
59+
if (!isEvmTxData(quoteResponse.trade)) {
60+
throw new Error(
61+
'Failed to submit cross-chain swap transaction: trade is not an EVM transaction',
62+
);
63+
}
64+
65+
// Set approval time and id if an approval tx is needed
66+
const { approvalTxId } = await traceFn(
67+
getApprovalTraceParams(quoteResponse, isStxEnabledOnClient),
68+
async () => {
69+
return await handleApprovalTx(args);
70+
},
71+
);
72+
73+
// Add pre-submission history keyed by actionId
74+
// This ensures we have quote data available if transaction fails during submission
75+
// Set approval time and id if an approval tx is needed
76+
yield {
77+
historyItem: {
78+
approvalTxId,
79+
},
80+
};
81+
82+
// Pass txFee when gasIncluded is true to use the quote's gas fees
83+
// instead of re-estimating (which would fail for max native token swaps)
84+
const tradeMeta = await submitEvmTransaction({
85+
messenger,
86+
transactionType: isBridgeTx ? TransactionType.bridge : TransactionType.swap,
87+
trade: quoteResponse.trade,
88+
requireApproval,
89+
txFee: quoteResponse.quote.gasIncluded
90+
? quoteResponse.quote.feeData.txFee
91+
: undefined,
92+
actionId,
93+
});
94+
95+
yield { approvalTxId, tradeMeta };
96+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
2+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
3+
import { submitBatchHandler } from './batch';
4+
import { submitEvmHandler as defaultSubmitHandler } from './evm';
5+
import { submitIntentHandler } from './intent';
6+
import { submitNonEvmHandler } from './non-evm';
7+
import type { ExecuteParams, SubmitStrategy } from './types';
8+
import { isNonEvmChainId } from '../../../bridge-controller/src';
9+
10+
// const batchFlow = ['submitBatch', 'addToHistory'];
11+
12+
// const regularFlow = [
13+
// 'submitReset',
14+
// 'submitApproval',
15+
// 'submitIntent',
16+
// 'addToHistory',
17+
// 'submitTrade',
18+
// 'addToHistory',
19+
// 'startPolling',
20+
// ];
21+
22+
// const tronFlow = [
23+
// 'submitApproval',
24+
// 'addToHistory',
25+
// 'submitTrade',
26+
// 'addToHistory',
27+
// 'startPolling',
28+
// ];
29+
30+
const SUBMIT_STRATEGY_REGISTRY = [
31+
{
32+
matchesFlow: (params: ExecuteParams) => {
33+
const { quoteResponse } = params;
34+
return isNonEvmChainId(quoteResponse.quote.srcChainId);
35+
},
36+
execute: submitNonEvmHandler,
37+
},
38+
{
39+
matchesFlow: (params: ExecuteParams) => {
40+
const { quoteResponse, isStxEnabledOnClient, isDelegatedAccount } =
41+
params;
42+
return (
43+
isStxEnabledOnClient ||
44+
quoteResponse.quote.gasIncluded7702 ||
45+
isDelegatedAccount
46+
);
47+
},
48+
execute: submitBatchHandler,
49+
},
50+
{
51+
matchesFlow: (params: ExecuteParams) => {
52+
const { quoteResponse } = params;
53+
return Boolean(quoteResponse.quote.intent);
54+
},
55+
execute: submitIntentHandler,
56+
},
57+
];
58+
59+
const executeSubmitFlow = (
60+
params: ExecuteParams,
61+
): ReturnType<SubmitStrategy['execute']> => {
62+
return (
63+
SUBMIT_STRATEGY_REGISTRY.find((strategy) => strategy.matchesFlow(params))
64+
?.execute ?? defaultSubmitHandler
65+
)(params);
66+
};
67+
68+
export default executeSubmitFlow;
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
2+
import { formatChainIdToHex, isEvmTxData } from '@metamask/bridge-controller';
3+
import { TransactionType } from '@metamask/transaction-controller';
4+
5+
import { handleApprovalTx } from './evm';
6+
import { ExecuteParams, SubmitStrategy } from './types';
7+
import { getJwt } from '../utils/authentication';
8+
import {
9+
getIntentFromQuote,
10+
mapIntentOrderStatusToTransactionStatus,
11+
postSubmitOrder,
12+
} from '../utils/intent-api';
13+
import { signTypedMessage } from '../utils/keyring';
14+
import { getNetworkClientIdByChainId } from '../utils/network';
15+
import { addSyntheticTransaction } from '../utils/transaction';
16+
17+
const handleSyntheticTx = async ({
18+
quoteResponse,
19+
messenger,
20+
isBridgeTx,
21+
selectedAccount,
22+
orderUid,
23+
}: ExecuteParams) => {
24+
const {
25+
quote: { srcChainId },
26+
} = quoteResponse;
27+
28+
// Determine transaction type: swap for same-chain, bridge for cross-chain
29+
const transactionType = isBridgeTx
30+
? /* c8 ignore start */
31+
TransactionType.bridge
32+
: /* c8 ignore end */
33+
TransactionType.swap;
34+
35+
// Create actual transaction in Transaction Controller first
36+
const networkClientId = getNetworkClientIdByChainId(messenger, srcChainId);
37+
38+
// This is a synthetic transaction whose purpose is to be able
39+
// to track the order status via the history
40+
if (!isEvmTxData(quoteResponse.trade)) {
41+
throw new Error('Failed to submit intent: trade is not an EVM transaction');
42+
}
43+
const intent = getIntentFromQuote(quoteResponse);
44+
// This is a synthetic transaction whose purpose is to be able
45+
// to track the order status via the history
46+
/**
47+
* @deprecated use trade data from quote response instead
48+
*/
49+
const intentTransactionParams = {
50+
chainId: formatChainIdToHex(srcChainId),
51+
from: selectedAccount.address,
52+
to:
53+
intent.settlementContract ?? '0x9008D19f58AAbd9eD0D60971565AA8510560ab41', // Default settlement contract
54+
data: `0x${orderUid?.slice(-8)}`, // Use last 8 chars of orderUid to make each transaction unique
55+
value: '0x0',
56+
gas: '0x5208', // Minimal gas for display purposes
57+
gasPrice: '0x3b9aca00', // 1 Gwei - will be converted to EIP-1559 fees if network supports it
58+
};
59+
60+
const initialTxMeta = await addSyntheticTransaction(
61+
messenger,
62+
intentTransactionParams,
63+
{
64+
requireApproval: false,
65+
networkClientId,
66+
type: transactionType,
67+
},
68+
);
69+
return initialTxMeta;
70+
};
71+
72+
/**
73+
* Submits batched EVM transactions to the TransactionController
74+
*
75+
* @param args - The parameters for the transaction
76+
* @param args.quoteResponse - The quote response
77+
* @param args.messenger - The messenger
78+
* @param args.selectedAccount - The selected account
79+
* @param args.traceFn - The trace function
80+
* @param args.isBridgeTx - Whether the transaction is a bridge transaction
81+
* @returns The approvalTxId and tradeMeta for the non-EVM transaction
82+
*/
83+
const handleSubmitIntent = async (args: ExecuteParams) => {
84+
const {
85+
quoteResponse,
86+
messenger,
87+
selectedAccount,
88+
clientId,
89+
fetchFn,
90+
bridgeApiBaseUrl,
91+
} = args;
92+
const { srcChainId, requestId } = quoteResponse.quote;
93+
94+
const intent = getIntentFromQuote(quoteResponse);
95+
const signature = await signTypedMessage({
96+
messenger,
97+
accountAddress: selectedAccount.address,
98+
typedData: intent.typedData,
99+
});
100+
101+
const { id: orderUid, status } = await postSubmitOrder({
102+
params: {
103+
srcChainId,
104+
quoteId: requestId,
105+
signature,
106+
order: intent.order,
107+
userAddress: selectedAccount.address,
108+
aggregatorId: intent.protocol,
109+
},
110+
clientId,
111+
jwt: await getJwt(messenger),
112+
fetchFn,
113+
bridgeApiBaseUrl,
114+
});
115+
116+
return {
117+
orderUid,
118+
orderStatus: status,
119+
};
120+
};
121+
122+
export async function* submitIntentHandler(
123+
args: ExecuteParams,
124+
): ReturnType<SubmitStrategy['execute']> {
125+
// TODO approval handler should handle batch transactions as well
126+
const approvalResult = await handleApprovalTx(args);
127+
// TODO add to history
128+
129+
const { orderUid, orderStatus } = await handleSubmitIntent(args);
130+
131+
const tradeMeta = await handleSyntheticTx({
132+
...args,
133+
requireApproval: false,
134+
isStxEnabledOnClient: false,
135+
orderUid,
136+
});
137+
138+
if (tradeMeta && orderStatus) {
139+
yield {
140+
tradeMeta: {
141+
...tradeMeta,
142+
// TODO remove this
143+
// Update txHistory with actual transaction metadata
144+
// Map intent order status to TransactionController status
145+
status: mapIntentOrderStatusToTransactionStatus(orderStatus),
146+
},
147+
};
148+
149+
// Record in bridge history with actual transaction metadata
150+
// Use orderId as the history key for intent transactions
151+
// Create a bridge transaction metadata that includes the original txId
152+
153+
yield {
154+
historyItem: {
155+
txMetaId: orderUid,
156+
approvalTxId: approvalResult.approvalTxId,
157+
// TODO deprecate originalTransactionId
158+
originalTransactionId: tradeMeta?.id, // Keep original txId for TransactionController updates
159+
},
160+
};
161+
}
162+
163+
// Start polling using the orderId key to route to intent manager
164+
yield {
165+
pollingToken: orderUid,
166+
};
167+
}

0 commit comments

Comments
 (0)