|
| 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