@metamask/transaction-pay-controller 1.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.
Files changed (212) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE +20 -0
  3. package/README.md +19 -0
  4. package/dist/TransactionPayController.cjs +96 -0
  5. package/dist/TransactionPayController.cjs.map +1 -0
  6. package/dist/TransactionPayController.d.cts +9 -0
  7. package/dist/TransactionPayController.d.cts.map +1 -0
  8. package/dist/TransactionPayController.d.mts +9 -0
  9. package/dist/TransactionPayController.d.mts.map +1 -0
  10. package/dist/TransactionPayController.mjs +93 -0
  11. package/dist/TransactionPayController.mjs.map +1 -0
  12. package/dist/actions/update-payment-token.cjs +79 -0
  13. package/dist/actions/update-payment-token.cjs.map +1 -0
  14. package/dist/actions/update-payment-token.d.cts +14 -0
  15. package/dist/actions/update-payment-token.d.cts.map +1 -0
  16. package/dist/actions/update-payment-token.d.mts +14 -0
  17. package/dist/actions/update-payment-token.d.mts.map +1 -0
  18. package/dist/actions/update-payment-token.mjs +75 -0
  19. package/dist/actions/update-payment-token.mjs.map +1 -0
  20. package/dist/constants.cjs +12 -0
  21. package/dist/constants.cjs.map +1 -0
  22. package/dist/constants.d.cts +8 -0
  23. package/dist/constants.d.cts.map +1 -0
  24. package/dist/constants.d.mts +8 -0
  25. package/dist/constants.d.mts.map +1 -0
  26. package/dist/constants.mjs +9 -0
  27. package/dist/constants.mjs.map +1 -0
  28. package/dist/helpers/TransactionPayPublishHook.cjs +61 -0
  29. package/dist/helpers/TransactionPayPublishHook.cjs.map +1 -0
  30. package/dist/helpers/TransactionPayPublishHook.d.cts +12 -0
  31. package/dist/helpers/TransactionPayPublishHook.d.cts.map +1 -0
  32. package/dist/helpers/TransactionPayPublishHook.d.mts +12 -0
  33. package/dist/helpers/TransactionPayPublishHook.d.mts.map +1 -0
  34. package/dist/helpers/TransactionPayPublishHook.mjs +57 -0
  35. package/dist/helpers/TransactionPayPublishHook.mjs.map +1 -0
  36. package/dist/index.cjs +10 -0
  37. package/dist/index.cjs.map +1 -0
  38. package/dist/index.d.cts +6 -0
  39. package/dist/index.d.cts.map +1 -0
  40. package/dist/index.d.mts +6 -0
  41. package/dist/index.d.mts.map +1 -0
  42. package/dist/index.mjs +4 -0
  43. package/dist/index.mjs.map +1 -0
  44. package/dist/logger.cjs +8 -0
  45. package/dist/logger.cjs.map +1 -0
  46. package/dist/logger.d.cts +5 -0
  47. package/dist/logger.d.cts.map +1 -0
  48. package/dist/logger.d.mts +5 -0
  49. package/dist/logger.d.mts.map +1 -0
  50. package/dist/logger.mjs +5 -0
  51. package/dist/logger.mjs.map +1 -0
  52. package/dist/strategy/bridge/BridgeStrategy.cjs +30 -0
  53. package/dist/strategy/bridge/BridgeStrategy.cjs.map +1 -0
  54. package/dist/strategy/bridge/BridgeStrategy.d.cts +11 -0
  55. package/dist/strategy/bridge/BridgeStrategy.d.cts.map +1 -0
  56. package/dist/strategy/bridge/BridgeStrategy.d.mts +11 -0
  57. package/dist/strategy/bridge/BridgeStrategy.d.mts.map +1 -0
  58. package/dist/strategy/bridge/BridgeStrategy.mjs +26 -0
  59. package/dist/strategy/bridge/BridgeStrategy.mjs.map +1 -0
  60. package/dist/strategy/bridge/bridge-quotes.cjs +386 -0
  61. package/dist/strategy/bridge/bridge-quotes.cjs.map +1 -0
  62. package/dist/strategy/bridge/bridge-quotes.d.cts +35 -0
  63. package/dist/strategy/bridge/bridge-quotes.d.cts.map +1 -0
  64. package/dist/strategy/bridge/bridge-quotes.d.mts +35 -0
  65. package/dist/strategy/bridge/bridge-quotes.d.mts.map +1 -0
  66. package/dist/strategy/bridge/bridge-quotes.mjs +380 -0
  67. package/dist/strategy/bridge/bridge-quotes.mjs.map +1 -0
  68. package/dist/strategy/bridge/bridge-submit.cjs +155 -0
  69. package/dist/strategy/bridge/bridge-submit.cjs.map +1 -0
  70. package/dist/strategy/bridge/bridge-submit.d.cts +19 -0
  71. package/dist/strategy/bridge/bridge-submit.d.cts.map +1 -0
  72. package/dist/strategy/bridge/bridge-submit.d.mts +19 -0
  73. package/dist/strategy/bridge/bridge-submit.d.mts.map +1 -0
  74. package/dist/strategy/bridge/bridge-submit.mjs +152 -0
  75. package/dist/strategy/bridge/bridge-submit.mjs.map +1 -0
  76. package/dist/strategy/bridge/types.cjs +3 -0
  77. package/dist/strategy/bridge/types.cjs.map +1 -0
  78. package/dist/strategy/bridge/types.d.cts +27 -0
  79. package/dist/strategy/bridge/types.d.cts.map +1 -0
  80. package/dist/strategy/bridge/types.d.mts +27 -0
  81. package/dist/strategy/bridge/types.d.mts.map +1 -0
  82. package/dist/strategy/bridge/types.mjs +2 -0
  83. package/dist/strategy/bridge/types.mjs.map +1 -0
  84. package/dist/strategy/relay/RelayStrategy.cjs +15 -0
  85. package/dist/strategy/relay/RelayStrategy.cjs.map +1 -0
  86. package/dist/strategy/relay/RelayStrategy.d.cts +9 -0
  87. package/dist/strategy/relay/RelayStrategy.d.cts.map +1 -0
  88. package/dist/strategy/relay/RelayStrategy.d.mts +9 -0
  89. package/dist/strategy/relay/RelayStrategy.d.mts.map +1 -0
  90. package/dist/strategy/relay/RelayStrategy.mjs +11 -0
  91. package/dist/strategy/relay/RelayStrategy.mjs.map +1 -0
  92. package/dist/strategy/relay/constants.cjs +9 -0
  93. package/dist/strategy/relay/constants.cjs.map +1 -0
  94. package/dist/strategy/relay/constants.d.cts +6 -0
  95. package/dist/strategy/relay/constants.d.cts.map +1 -0
  96. package/dist/strategy/relay/constants.d.mts +6 -0
  97. package/dist/strategy/relay/constants.d.mts.map +1 -0
  98. package/dist/strategy/relay/constants.mjs +6 -0
  99. package/dist/strategy/relay/constants.mjs.map +1 -0
  100. package/dist/strategy/relay/relay-quotes.cjs +210 -0
  101. package/dist/strategy/relay/relay-quotes.cjs.map +1 -0
  102. package/dist/strategy/relay/relay-quotes.d.cts +10 -0
  103. package/dist/strategy/relay/relay-quotes.d.cts.map +1 -0
  104. package/dist/strategy/relay/relay-quotes.d.mts +10 -0
  105. package/dist/strategy/relay/relay-quotes.d.mts.map +1 -0
  106. package/dist/strategy/relay/relay-quotes.mjs +206 -0
  107. package/dist/strategy/relay/relay-quotes.mjs.map +1 -0
  108. package/dist/strategy/relay/relay-submit.cjs +137 -0
  109. package/dist/strategy/relay/relay-submit.cjs.map +1 -0
  110. package/dist/strategy/relay/relay-submit.d.cts +13 -0
  111. package/dist/strategy/relay/relay-submit.d.cts.map +1 -0
  112. package/dist/strategy/relay/relay-submit.d.mts +13 -0
  113. package/dist/strategy/relay/relay-submit.d.mts.map +1 -0
  114. package/dist/strategy/relay/relay-submit.mjs +133 -0
  115. package/dist/strategy/relay/relay-submit.mjs.map +1 -0
  116. package/dist/strategy/relay/types.cjs +3 -0
  117. package/dist/strategy/relay/types.cjs.map +1 -0
  118. package/dist/strategy/relay/types.d.cts +52 -0
  119. package/dist/strategy/relay/types.d.cts.map +1 -0
  120. package/dist/strategy/relay/types.d.mts +52 -0
  121. package/dist/strategy/relay/types.d.mts.map +1 -0
  122. package/dist/strategy/relay/types.mjs +2 -0
  123. package/dist/strategy/relay/types.mjs.map +1 -0
  124. package/dist/strategy/test/TestStrategy.cjs +48 -0
  125. package/dist/strategy/test/TestStrategy.cjs.map +1 -0
  126. package/dist/strategy/test/TestStrategy.d.cts +9 -0
  127. package/dist/strategy/test/TestStrategy.d.cts.map +1 -0
  128. package/dist/strategy/test/TestStrategy.d.mts +9 -0
  129. package/dist/strategy/test/TestStrategy.d.mts.map +1 -0
  130. package/dist/strategy/test/TestStrategy.mjs +44 -0
  131. package/dist/strategy/test/TestStrategy.mjs.map +1 -0
  132. package/dist/tests/messenger-mock.cjs +76 -0
  133. package/dist/tests/messenger-mock.cjs.map +1 -0
  134. package/dist/tests/messenger-mock.d.cts +215 -0
  135. package/dist/tests/messenger-mock.d.cts.map +1 -0
  136. package/dist/tests/messenger-mock.d.mts +215 -0
  137. package/dist/tests/messenger-mock.d.mts.map +1 -0
  138. package/dist/tests/messenger-mock.mjs +72 -0
  139. package/dist/tests/messenger-mock.mjs.map +1 -0
  140. package/dist/types.cjs +3 -0
  141. package/dist/types.cjs.map +1 -0
  142. package/dist/types.d.cts +264 -0
  143. package/dist/types.d.cts.map +1 -0
  144. package/dist/types.d.mts +264 -0
  145. package/dist/types.d.mts.map +1 -0
  146. package/dist/types.mjs +2 -0
  147. package/dist/types.mjs.map +1 -0
  148. package/dist/utils/gas.cjs +87 -0
  149. package/dist/utils/gas.cjs.map +1 -0
  150. package/dist/utils/gas.d.cts +33 -0
  151. package/dist/utils/gas.d.cts.map +1 -0
  152. package/dist/utils/gas.d.mts +33 -0
  153. package/dist/utils/gas.d.mts.map +1 -0
  154. package/dist/utils/gas.mjs +82 -0
  155. package/dist/utils/gas.mjs.map +1 -0
  156. package/dist/utils/quotes.cjs +168 -0
  157. package/dist/utils/quotes.cjs.map +1 -0
  158. package/dist/utils/quotes.d.cts +21 -0
  159. package/dist/utils/quotes.d.cts.map +1 -0
  160. package/dist/utils/quotes.d.mts +21 -0
  161. package/dist/utils/quotes.d.mts.map +1 -0
  162. package/dist/utils/quotes.mjs +163 -0
  163. package/dist/utils/quotes.mjs.map +1 -0
  164. package/dist/utils/required-tokens.cjs +179 -0
  165. package/dist/utils/required-tokens.cjs.map +1 -0
  166. package/dist/utils/required-tokens.d.cts +11 -0
  167. package/dist/utils/required-tokens.d.cts.map +1 -0
  168. package/dist/utils/required-tokens.d.mts +11 -0
  169. package/dist/utils/required-tokens.d.mts.map +1 -0
  170. package/dist/utils/required-tokens.mjs +175 -0
  171. package/dist/utils/required-tokens.mjs.map +1 -0
  172. package/dist/utils/source-amounts.cjs +72 -0
  173. package/dist/utils/source-amounts.cjs.map +1 -0
  174. package/dist/utils/source-amounts.d.cts +11 -0
  175. package/dist/utils/source-amounts.d.cts.map +1 -0
  176. package/dist/utils/source-amounts.d.mts +11 -0
  177. package/dist/utils/source-amounts.d.mts.map +1 -0
  178. package/dist/utils/source-amounts.mjs +68 -0
  179. package/dist/utils/source-amounts.mjs.map +1 -0
  180. package/dist/utils/strategy.cjs +39 -0
  181. package/dist/utils/strategy.cjs.map +1 -0
  182. package/dist/utils/strategy.d.cts +19 -0
  183. package/dist/utils/strategy.d.cts.map +1 -0
  184. package/dist/utils/strategy.d.mts +19 -0
  185. package/dist/utils/strategy.d.mts.map +1 -0
  186. package/dist/utils/strategy.mjs +34 -0
  187. package/dist/utils/strategy.mjs.map +1 -0
  188. package/dist/utils/token.cjs +161 -0
  189. package/dist/utils/token.cjs.map +1 -0
  190. package/dist/utils/token.d.cts +53 -0
  191. package/dist/utils/token.d.cts.map +1 -0
  192. package/dist/utils/token.d.mts +53 -0
  193. package/dist/utils/token.d.mts.map +1 -0
  194. package/dist/utils/token.mjs +154 -0
  195. package/dist/utils/token.mjs.map +1 -0
  196. package/dist/utils/totals.cjs +69 -0
  197. package/dist/utils/totals.cjs.map +1 -0
  198. package/dist/utils/totals.d.cts +11 -0
  199. package/dist/utils/totals.d.cts.map +1 -0
  200. package/dist/utils/totals.d.mts +11 -0
  201. package/dist/utils/totals.d.mts.map +1 -0
  202. package/dist/utils/totals.mjs +65 -0
  203. package/dist/utils/totals.mjs.map +1 -0
  204. package/dist/utils/transaction.cjs +132 -0
  205. package/dist/utils/transaction.cjs.map +1 -0
  206. package/dist/utils/transaction.d.cts +42 -0
  207. package/dist/utils/transaction.d.cts.map +1 -0
  208. package/dist/utils/transaction.d.mts +42 -0
  209. package/dist/utils/transaction.d.mts.map +1 -0
  210. package/dist/utils/transaction.mjs +126 -0
  211. package/dist/utils/transaction.mjs.map +1 -0
  212. package/package.json +97 -0
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.queueRefreshQuotes = exports.updateQuotes = void 0;
4
+ const utils_1 = require("@metamask/utils");
5
+ const strategy_1 = require("./strategy.cjs");
6
+ const totals_1 = require("./totals.cjs");
7
+ const transaction_1 = require("./transaction.cjs");
8
+ const logger_1 = require("../logger.cjs");
9
+ const QUOTES_CHECK_INTERVAL = 1 * 1000; // 1 Second
10
+ const DEFAULT_REFRESH_INTERVAL = 30 * 1000; // 30 Seconds
11
+ const log = (0, utils_1.createModuleLogger)(logger_1.projectLogger, 'quotes');
12
+ /**
13
+ * Update the quotes for a specific transaction.
14
+ *
15
+ * @param request - Request parameters.
16
+ */
17
+ async function updateQuotes(request) {
18
+ const { messenger, transactionData, transactionId, updateTransactionData } = request;
19
+ const transaction = (0, transaction_1.getTransaction)(transactionId, messenger);
20
+ log('Updating quotes', { transactionId });
21
+ if (!transaction || !transactionData) {
22
+ throw new Error('Transaction not found');
23
+ }
24
+ const { paymentToken, sourceAmounts, tokens } = transactionData;
25
+ if (!paymentToken) {
26
+ return;
27
+ }
28
+ const requests = (sourceAmounts ?? []).map((sourceAmount, i) => {
29
+ const token = tokens[i];
30
+ return {
31
+ from: transaction.txParams.from,
32
+ sourceBalanceRaw: paymentToken.balanceRaw,
33
+ sourceTokenAmount: sourceAmount.sourceAmountRaw,
34
+ sourceChainId: paymentToken.chainId,
35
+ sourceTokenAddress: paymentToken.address,
36
+ targetAmountMinimum: token.allowUnderMinimum ? '0' : token.amountRaw,
37
+ targetChainId: token.chainId,
38
+ targetTokenAddress: token.address,
39
+ };
40
+ });
41
+ if (!requests?.length) {
42
+ log('No quote requests', { transactionId });
43
+ }
44
+ let quotes = [];
45
+ const strategy = await (0, strategy_1.getStrategy)(messenger, transaction);
46
+ try {
47
+ quotes = requests?.length
48
+ ? (await strategy.getQuotes({
49
+ messenger,
50
+ requests,
51
+ transaction,
52
+ }))
53
+ : [];
54
+ }
55
+ catch (error) {
56
+ log('Error fetching quotes', { error, transactionId });
57
+ }
58
+ log('Updated', { transactionId, quotes });
59
+ const batchTransactions = quotes?.length && strategy.getBatchTransactions
60
+ ? await strategy.getBatchTransactions({
61
+ messenger,
62
+ quotes,
63
+ })
64
+ : [];
65
+ log('Batch transactions', { transactionId, batchTransactions });
66
+ const totals = (0, totals_1.calculateTotals)(quotes, tokens, messenger);
67
+ log('Calculated totals', { transactionId, totals });
68
+ syncTransaction({
69
+ batchTransactions,
70
+ messenger: messenger,
71
+ paymentToken,
72
+ totals,
73
+ transactionId,
74
+ });
75
+ updateTransactionData(transactionId, (data) => {
76
+ data.quotes = quotes;
77
+ data.quotesLastUpdated = Date.now();
78
+ data.totals = totals;
79
+ data.isLoading = false;
80
+ });
81
+ }
82
+ exports.updateQuotes = updateQuotes;
83
+ /**
84
+ * Poll quotes at regular intervals.
85
+ *
86
+ * @param messenger - Messenger instance.
87
+ * @param updateTransactionData - Callback to update transaction data.
88
+ */
89
+ function queueRefreshQuotes(messenger, updateTransactionData) {
90
+ setTimeout(() => {
91
+ refreshQuotes(messenger, updateTransactionData)
92
+ .finally(() => queueRefreshQuotes(messenger, updateTransactionData))
93
+ .catch((error) => {
94
+ log('Error polling quotes', { messenger, error });
95
+ });
96
+ }, QUOTES_CHECK_INTERVAL);
97
+ }
98
+ exports.queueRefreshQuotes = queueRefreshQuotes;
99
+ /**
100
+ * Sync batch transactions to the transaction meta.
101
+ *
102
+ * @param request - Request object.
103
+ * @param request.batchTransactions - Batch transactions to sync.
104
+ * @param request.messenger - Messenger instance.
105
+ * @param request.paymentToken - Payment token used.
106
+ * @param request.totals - Calculated totals.
107
+ * @param request.transactionId - ID of the transaction to sync.
108
+ */
109
+ function syncTransaction({ batchTransactions, messenger, paymentToken, totals, transactionId, }) {
110
+ (0, transaction_1.updateTransaction)({
111
+ transactionId,
112
+ messenger: messenger,
113
+ note: 'Update transaction pay data',
114
+ }, (tx) => {
115
+ tx.batchTransactions = batchTransactions;
116
+ tx.batchTransactionsOptions = {};
117
+ tx.metamaskPay = {
118
+ bridgeFeeFiat: totals.fees.provider.usd,
119
+ chainId: paymentToken.chainId,
120
+ networkFeeFiat: totals.fees.sourceNetwork.usd,
121
+ tokenAddress: paymentToken.address,
122
+ totalFiat: totals.total.usd,
123
+ };
124
+ });
125
+ }
126
+ /**
127
+ * Refresh quotes for all transactions if expired.
128
+ *
129
+ * @param messenger - Messenger instance.
130
+ * @param updateTransactionData - Callback to update transaction data.
131
+ */
132
+ async function refreshQuotes(messenger, updateTransactionData) {
133
+ const state = messenger.call('TransactionPayController:getState');
134
+ const transactionIds = Object.keys(state.transactionData);
135
+ for (const transactionId of transactionIds) {
136
+ const transactionData = state.transactionData[transactionId];
137
+ const { isLoading, quotes, quotesLastUpdated } = transactionData;
138
+ if (isLoading || !quotes?.length) {
139
+ continue;
140
+ }
141
+ const strategyName = quotes[0].strategy;
142
+ const strategy = (0, strategy_1.getStrategyByName)(strategyName);
143
+ const refreshInterval = (await strategy.getRefreshInterval?.({
144
+ chainId: quotes[0].request.sourceChainId,
145
+ messenger,
146
+ })) ?? DEFAULT_REFRESH_INTERVAL;
147
+ const isExpired = Date.now() - (quotesLastUpdated ?? 0) > refreshInterval;
148
+ if (!isExpired) {
149
+ continue;
150
+ }
151
+ log('Refreshing expired quotes', {
152
+ transactionId,
153
+ strategy: strategyName,
154
+ refreshInterval,
155
+ });
156
+ updateTransactionData(transactionId, (data) => {
157
+ data.isLoading = true;
158
+ });
159
+ await updateQuotes({
160
+ messenger,
161
+ transactionData,
162
+ transactionId,
163
+ updateTransactionData,
164
+ });
165
+ log('Refreshed quotes', { transactionId, strategy: strategyName });
166
+ }
167
+ }
168
+ //# sourceMappingURL=quotes.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quotes.cjs","sourceRoot":"","sources":["../../src/utils/quotes.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BatchTransaction } from '@metamask/transaction-controller';\nimport type { TransactionMeta } from '@metamask/transaction-controller';\nimport type { Hex, Json } from '@metamask/utils';\nimport { createModuleLogger } from '@metamask/utils';\n\nimport { getStrategy, getStrategyByName } from './strategy';\nimport { calculateTotals } from './totals';\nimport { getTransaction, updateTransaction } from './transaction';\nimport { projectLogger } from '../logger';\nimport type {\n QuoteRequest,\n TransactionData,\n TransactionPayControllerMessenger,\n TransactionPayQuote,\n TransactionPayTotals,\n TransactionPaymentToken,\n UpdateTransactionDataCallback,\n} from '../types';\n\nconst QUOTES_CHECK_INTERVAL = 1 * 1000; // 1 Second\nconst DEFAULT_REFRESH_INTERVAL = 30 * 1000; // 30 Seconds\n\nconst log = createModuleLogger(projectLogger, 'quotes');\n\nexport type UpdateQuotesRequest = {\n messenger: TransactionPayControllerMessenger;\n transactionData: TransactionData | undefined;\n transactionId: string;\n updateTransactionData: UpdateTransactionDataCallback;\n};\n\n/**\n * Update the quotes for a specific transaction.\n *\n * @param request - Request parameters.\n */\nexport async function updateQuotes(request: UpdateQuotesRequest) {\n const { messenger, transactionData, transactionId, updateTransactionData } =\n request;\n\n const transaction = getTransaction(transactionId, messenger);\n\n log('Updating quotes', { transactionId });\n\n if (!transaction || !transactionData) {\n throw new Error('Transaction not found');\n }\n\n const { paymentToken, sourceAmounts, tokens } = transactionData;\n\n if (!paymentToken) {\n return;\n }\n\n const requests: QuoteRequest[] = (sourceAmounts ?? []).map(\n (sourceAmount, i) => {\n const token = tokens[i];\n\n return {\n from: transaction.txParams.from as Hex,\n sourceBalanceRaw: paymentToken.balanceRaw,\n sourceTokenAmount: sourceAmount.sourceAmountRaw,\n sourceChainId: paymentToken.chainId,\n sourceTokenAddress: paymentToken.address,\n targetAmountMinimum: token.allowUnderMinimum ? '0' : token.amountRaw,\n targetChainId: token.chainId,\n targetTokenAddress: token.address,\n };\n },\n );\n\n if (!requests?.length) {\n log('No quote requests', { transactionId });\n }\n\n let quotes: TransactionPayQuote<Json>[] | undefined = [];\n\n const strategy = await getStrategy(messenger as never, transaction);\n\n try {\n quotes = requests?.length\n ? ((await strategy.getQuotes({\n messenger,\n requests,\n transaction,\n })) as TransactionPayQuote<Json>[])\n : [];\n } catch (error) {\n log('Error fetching quotes', { error, transactionId });\n }\n\n log('Updated', { transactionId, quotes });\n\n const batchTransactions =\n quotes?.length && strategy.getBatchTransactions\n ? await strategy.getBatchTransactions({\n messenger,\n quotes,\n })\n : [];\n\n log('Batch transactions', { transactionId, batchTransactions });\n\n const totals = calculateTotals(quotes as never, tokens, messenger);\n\n log('Calculated totals', { transactionId, totals });\n\n syncTransaction({\n batchTransactions,\n messenger: messenger as never,\n paymentToken,\n totals,\n transactionId,\n });\n\n updateTransactionData(transactionId, (data) => {\n data.quotes = quotes as never;\n data.quotesLastUpdated = Date.now();\n data.totals = totals;\n data.isLoading = false;\n });\n}\n\n/**\n * Poll quotes at regular intervals.\n *\n * @param messenger - Messenger instance.\n * @param updateTransactionData - Callback to update transaction data.\n */\nexport function queueRefreshQuotes(\n messenger: TransactionPayControllerMessenger,\n updateTransactionData: UpdateTransactionDataCallback,\n) {\n setTimeout(() => {\n refreshQuotes(messenger, updateTransactionData)\n .finally(() => queueRefreshQuotes(messenger, updateTransactionData))\n .catch((error) => {\n log('Error polling quotes', { messenger, error });\n });\n }, QUOTES_CHECK_INTERVAL);\n}\n\n/**\n * Sync batch transactions to the transaction meta.\n *\n * @param request - Request object.\n * @param request.batchTransactions - Batch transactions to sync.\n * @param request.messenger - Messenger instance.\n * @param request.paymentToken - Payment token used.\n * @param request.totals - Calculated totals.\n * @param request.transactionId - ID of the transaction to sync.\n */\nfunction syncTransaction({\n batchTransactions,\n messenger,\n paymentToken,\n totals,\n transactionId,\n}: {\n batchTransactions: BatchTransaction[];\n messenger: TransactionPayControllerMessenger;\n paymentToken: TransactionPaymentToken;\n totals: TransactionPayTotals;\n transactionId: string;\n}) {\n updateTransaction(\n {\n transactionId,\n messenger: messenger as never,\n note: 'Update transaction pay data',\n },\n (tx: TransactionMeta) => {\n tx.batchTransactions = batchTransactions;\n tx.batchTransactionsOptions = {};\n\n tx.metamaskPay = {\n bridgeFeeFiat: totals.fees.provider.usd,\n chainId: paymentToken.chainId,\n networkFeeFiat: totals.fees.sourceNetwork.usd,\n tokenAddress: paymentToken.address,\n totalFiat: totals.total.usd,\n };\n },\n );\n}\n\n/**\n * Refresh quotes for all transactions if expired.\n *\n * @param messenger - Messenger instance.\n * @param updateTransactionData - Callback to update transaction data.\n */\nasync function refreshQuotes(\n messenger: TransactionPayControllerMessenger,\n updateTransactionData: UpdateTransactionDataCallback,\n) {\n const state = messenger.call('TransactionPayController:getState');\n const transactionIds = Object.keys(state.transactionData);\n\n for (const transactionId of transactionIds) {\n const transactionData = state.transactionData[transactionId];\n const { isLoading, quotes, quotesLastUpdated } = transactionData;\n\n if (isLoading || !quotes?.length) {\n continue;\n }\n\n const strategyName = quotes[0].strategy;\n const strategy = getStrategyByName(strategyName);\n\n const refreshInterval =\n (await strategy.getRefreshInterval?.({\n chainId: quotes[0].request.sourceChainId,\n messenger,\n })) ?? DEFAULT_REFRESH_INTERVAL;\n\n const isExpired = Date.now() - (quotesLastUpdated ?? 0) > refreshInterval;\n\n if (!isExpired) {\n continue;\n }\n\n log('Refreshing expired quotes', {\n transactionId,\n strategy: strategyName,\n refreshInterval,\n });\n\n updateTransactionData(transactionId, (data) => {\n data.isLoading = true;\n });\n\n await updateQuotes({\n messenger,\n transactionData,\n transactionId,\n updateTransactionData,\n });\n\n log('Refreshed quotes', { transactionId, strategy: strategyName });\n }\n}\n"]}
@@ -0,0 +1,21 @@
1
+ import type { TransactionData, TransactionPayControllerMessenger, UpdateTransactionDataCallback } from "../types.cjs";
2
+ export type UpdateQuotesRequest = {
3
+ messenger: TransactionPayControllerMessenger;
4
+ transactionData: TransactionData | undefined;
5
+ transactionId: string;
6
+ updateTransactionData: UpdateTransactionDataCallback;
7
+ };
8
+ /**
9
+ * Update the quotes for a specific transaction.
10
+ *
11
+ * @param request - Request parameters.
12
+ */
13
+ export declare function updateQuotes(request: UpdateQuotesRequest): Promise<void>;
14
+ /**
15
+ * Poll quotes at regular intervals.
16
+ *
17
+ * @param messenger - Messenger instance.
18
+ * @param updateTransactionData - Callback to update transaction data.
19
+ */
20
+ export declare function queueRefreshQuotes(messenger: TransactionPayControllerMessenger, updateTransactionData: UpdateTransactionDataCallback): void;
21
+ //# sourceMappingURL=quotes.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quotes.d.cts","sourceRoot":"","sources":["../../src/utils/quotes.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAEV,eAAe,EACf,iCAAiC,EAIjC,6BAA6B,EAC9B,qBAAiB;AAOlB,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,iCAAiC,CAAC;IAC7C,eAAe,EAAE,eAAe,GAAG,SAAS,CAAC;IAC7C,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB,EAAE,6BAA6B,CAAC;CACtD,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,mBAAmB,iBAqF9D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,iCAAiC,EAC5C,qBAAqB,EAAE,6BAA6B,QASrD"}
@@ -0,0 +1,21 @@
1
+ import type { TransactionData, TransactionPayControllerMessenger, UpdateTransactionDataCallback } from "../types.mjs";
2
+ export type UpdateQuotesRequest = {
3
+ messenger: TransactionPayControllerMessenger;
4
+ transactionData: TransactionData | undefined;
5
+ transactionId: string;
6
+ updateTransactionData: UpdateTransactionDataCallback;
7
+ };
8
+ /**
9
+ * Update the quotes for a specific transaction.
10
+ *
11
+ * @param request - Request parameters.
12
+ */
13
+ export declare function updateQuotes(request: UpdateQuotesRequest): Promise<void>;
14
+ /**
15
+ * Poll quotes at regular intervals.
16
+ *
17
+ * @param messenger - Messenger instance.
18
+ * @param updateTransactionData - Callback to update transaction data.
19
+ */
20
+ export declare function queueRefreshQuotes(messenger: TransactionPayControllerMessenger, updateTransactionData: UpdateTransactionDataCallback): void;
21
+ //# sourceMappingURL=quotes.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quotes.d.mts","sourceRoot":"","sources":["../../src/utils/quotes.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAEV,eAAe,EACf,iCAAiC,EAIjC,6BAA6B,EAC9B,qBAAiB;AAOlB,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,iCAAiC,CAAC;IAC7C,eAAe,EAAE,eAAe,GAAG,SAAS,CAAC;IAC7C,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB,EAAE,6BAA6B,CAAC;CACtD,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,mBAAmB,iBAqF9D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,iCAAiC,EAC5C,qBAAqB,EAAE,6BAA6B,QASrD"}
@@ -0,0 +1,163 @@
1
+ import { createModuleLogger } from "@metamask/utils";
2
+ import { getStrategy, getStrategyByName } from "./strategy.mjs";
3
+ import { calculateTotals } from "./totals.mjs";
4
+ import { getTransaction, updateTransaction } from "./transaction.mjs";
5
+ import { projectLogger } from "../logger.mjs";
6
+ const QUOTES_CHECK_INTERVAL = 1 * 1000; // 1 Second
7
+ const DEFAULT_REFRESH_INTERVAL = 30 * 1000; // 30 Seconds
8
+ const log = createModuleLogger(projectLogger, 'quotes');
9
+ /**
10
+ * Update the quotes for a specific transaction.
11
+ *
12
+ * @param request - Request parameters.
13
+ */
14
+ export async function updateQuotes(request) {
15
+ const { messenger, transactionData, transactionId, updateTransactionData } = request;
16
+ const transaction = getTransaction(transactionId, messenger);
17
+ log('Updating quotes', { transactionId });
18
+ if (!transaction || !transactionData) {
19
+ throw new Error('Transaction not found');
20
+ }
21
+ const { paymentToken, sourceAmounts, tokens } = transactionData;
22
+ if (!paymentToken) {
23
+ return;
24
+ }
25
+ const requests = (sourceAmounts ?? []).map((sourceAmount, i) => {
26
+ const token = tokens[i];
27
+ return {
28
+ from: transaction.txParams.from,
29
+ sourceBalanceRaw: paymentToken.balanceRaw,
30
+ sourceTokenAmount: sourceAmount.sourceAmountRaw,
31
+ sourceChainId: paymentToken.chainId,
32
+ sourceTokenAddress: paymentToken.address,
33
+ targetAmountMinimum: token.allowUnderMinimum ? '0' : token.amountRaw,
34
+ targetChainId: token.chainId,
35
+ targetTokenAddress: token.address,
36
+ };
37
+ });
38
+ if (!requests?.length) {
39
+ log('No quote requests', { transactionId });
40
+ }
41
+ let quotes = [];
42
+ const strategy = await getStrategy(messenger, transaction);
43
+ try {
44
+ quotes = requests?.length
45
+ ? (await strategy.getQuotes({
46
+ messenger,
47
+ requests,
48
+ transaction,
49
+ }))
50
+ : [];
51
+ }
52
+ catch (error) {
53
+ log('Error fetching quotes', { error, transactionId });
54
+ }
55
+ log('Updated', { transactionId, quotes });
56
+ const batchTransactions = quotes?.length && strategy.getBatchTransactions
57
+ ? await strategy.getBatchTransactions({
58
+ messenger,
59
+ quotes,
60
+ })
61
+ : [];
62
+ log('Batch transactions', { transactionId, batchTransactions });
63
+ const totals = calculateTotals(quotes, tokens, messenger);
64
+ log('Calculated totals', { transactionId, totals });
65
+ syncTransaction({
66
+ batchTransactions,
67
+ messenger: messenger,
68
+ paymentToken,
69
+ totals,
70
+ transactionId,
71
+ });
72
+ updateTransactionData(transactionId, (data) => {
73
+ data.quotes = quotes;
74
+ data.quotesLastUpdated = Date.now();
75
+ data.totals = totals;
76
+ data.isLoading = false;
77
+ });
78
+ }
79
+ /**
80
+ * Poll quotes at regular intervals.
81
+ *
82
+ * @param messenger - Messenger instance.
83
+ * @param updateTransactionData - Callback to update transaction data.
84
+ */
85
+ export function queueRefreshQuotes(messenger, updateTransactionData) {
86
+ setTimeout(() => {
87
+ refreshQuotes(messenger, updateTransactionData)
88
+ .finally(() => queueRefreshQuotes(messenger, updateTransactionData))
89
+ .catch((error) => {
90
+ log('Error polling quotes', { messenger, error });
91
+ });
92
+ }, QUOTES_CHECK_INTERVAL);
93
+ }
94
+ /**
95
+ * Sync batch transactions to the transaction meta.
96
+ *
97
+ * @param request - Request object.
98
+ * @param request.batchTransactions - Batch transactions to sync.
99
+ * @param request.messenger - Messenger instance.
100
+ * @param request.paymentToken - Payment token used.
101
+ * @param request.totals - Calculated totals.
102
+ * @param request.transactionId - ID of the transaction to sync.
103
+ */
104
+ function syncTransaction({ batchTransactions, messenger, paymentToken, totals, transactionId, }) {
105
+ updateTransaction({
106
+ transactionId,
107
+ messenger: messenger,
108
+ note: 'Update transaction pay data',
109
+ }, (tx) => {
110
+ tx.batchTransactions = batchTransactions;
111
+ tx.batchTransactionsOptions = {};
112
+ tx.metamaskPay = {
113
+ bridgeFeeFiat: totals.fees.provider.usd,
114
+ chainId: paymentToken.chainId,
115
+ networkFeeFiat: totals.fees.sourceNetwork.usd,
116
+ tokenAddress: paymentToken.address,
117
+ totalFiat: totals.total.usd,
118
+ };
119
+ });
120
+ }
121
+ /**
122
+ * Refresh quotes for all transactions if expired.
123
+ *
124
+ * @param messenger - Messenger instance.
125
+ * @param updateTransactionData - Callback to update transaction data.
126
+ */
127
+ async function refreshQuotes(messenger, updateTransactionData) {
128
+ const state = messenger.call('TransactionPayController:getState');
129
+ const transactionIds = Object.keys(state.transactionData);
130
+ for (const transactionId of transactionIds) {
131
+ const transactionData = state.transactionData[transactionId];
132
+ const { isLoading, quotes, quotesLastUpdated } = transactionData;
133
+ if (isLoading || !quotes?.length) {
134
+ continue;
135
+ }
136
+ const strategyName = quotes[0].strategy;
137
+ const strategy = getStrategyByName(strategyName);
138
+ const refreshInterval = (await strategy.getRefreshInterval?.({
139
+ chainId: quotes[0].request.sourceChainId,
140
+ messenger,
141
+ })) ?? DEFAULT_REFRESH_INTERVAL;
142
+ const isExpired = Date.now() - (quotesLastUpdated ?? 0) > refreshInterval;
143
+ if (!isExpired) {
144
+ continue;
145
+ }
146
+ log('Refreshing expired quotes', {
147
+ transactionId,
148
+ strategy: strategyName,
149
+ refreshInterval,
150
+ });
151
+ updateTransactionData(transactionId, (data) => {
152
+ data.isLoading = true;
153
+ });
154
+ await updateQuotes({
155
+ messenger,
156
+ transactionData,
157
+ transactionId,
158
+ updateTransactionData,
159
+ });
160
+ log('Refreshed quotes', { transactionId, strategy: strategyName });
161
+ }
162
+ }
163
+ //# sourceMappingURL=quotes.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quotes.mjs","sourceRoot":"","sources":["../../src/utils/quotes.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,wBAAwB;AAErD,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,uBAAmB;AAC5D,OAAO,EAAE,eAAe,EAAE,qBAAiB;AAC3C,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,0BAAsB;AAClE,OAAO,EAAE,aAAa,EAAE,sBAAkB;AAW1C,MAAM,qBAAqB,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,WAAW;AACnD,MAAM,wBAAwB,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,aAAa;AAEzD,MAAM,GAAG,GAAG,kBAAkB,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;AASxD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAA4B;IAC7D,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,aAAa,EAAE,qBAAqB,EAAE,GACxE,OAAO,CAAC;IAEV,MAAM,WAAW,GAAG,cAAc,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IAE7D,GAAG,CAAC,iBAAiB,EAAE,EAAE,aAAa,EAAE,CAAC,CAAC;IAE1C,IAAI,CAAC,WAAW,IAAI,CAAC,eAAe,EAAE;QACpC,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;KAC1C;IAED,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,eAAe,CAAC;IAEhE,IAAI,CAAC,YAAY,EAAE;QACjB,OAAO;KACR;IAED,MAAM,QAAQ,GAAmB,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,GAAG,CACxD,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE;QAClB,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QAExB,OAAO;YACL,IAAI,EAAE,WAAW,CAAC,QAAQ,CAAC,IAAW;YACtC,gBAAgB,EAAE,YAAY,CAAC,UAAU;YACzC,iBAAiB,EAAE,YAAY,CAAC,eAAe;YAC/C,aAAa,EAAE,YAAY,CAAC,OAAO;YACnC,kBAAkB,EAAE,YAAY,CAAC,OAAO;YACxC,mBAAmB,EAAE,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS;YACpE,aAAa,EAAE,KAAK,CAAC,OAAO;YAC5B,kBAAkB,EAAE,KAAK,CAAC,OAAO;SAClC,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE;QACrB,GAAG,CAAC,mBAAmB,EAAE,EAAE,aAAa,EAAE,CAAC,CAAC;KAC7C;IAED,IAAI,MAAM,GAA4C,EAAE,CAAC;IAEzD,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,SAAkB,EAAE,WAAW,CAAC,CAAC;IAEpE,IAAI;QACF,MAAM,GAAG,QAAQ,EAAE,MAAM;YACvB,CAAC,CAAE,CAAC,MAAM,QAAQ,CAAC,SAAS,CAAC;gBACzB,SAAS;gBACT,QAAQ;gBACR,WAAW;aACZ,CAAC,CAAiC;YACrC,CAAC,CAAC,EAAE,CAAC;KACR;IAAC,OAAO,KAAK,EAAE;QACd,GAAG,CAAC,uBAAuB,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC;KACxD;IAED,GAAG,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,CAAC;IAE1C,MAAM,iBAAiB,GACrB,MAAM,EAAE,MAAM,IAAI,QAAQ,CAAC,oBAAoB;QAC7C,CAAC,CAAC,MAAM,QAAQ,CAAC,oBAAoB,CAAC;YAClC,SAAS;YACT,MAAM;SACP,CAAC;QACJ,CAAC,CAAC,EAAE,CAAC;IAET,GAAG,CAAC,oBAAoB,EAAE,EAAE,aAAa,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAEhE,MAAM,MAAM,GAAG,eAAe,CAAC,MAAe,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;IAEnE,GAAG,CAAC,mBAAmB,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,CAAC;IAEpD,eAAe,CAAC;QACd,iBAAiB;QACjB,SAAS,EAAE,SAAkB;QAC7B,YAAY;QACZ,MAAM;QACN,aAAa;KACd,CAAC,CAAC;IAEH,qBAAqB,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE;QAC5C,IAAI,CAAC,MAAM,GAAG,MAAe,CAAC;QAC9B,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACpC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,SAA4C,EAC5C,qBAAoD;IAEpD,UAAU,CAAC,GAAG,EAAE;QACd,aAAa,CAAC,SAAS,EAAE,qBAAqB,CAAC;aAC5C,OAAO,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC,SAAS,EAAE,qBAAqB,CAAC,CAAC;aACnE,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACf,GAAG,CAAC,sBAAsB,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;IACP,CAAC,EAAE,qBAAqB,CAAC,CAAC;AAC5B,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,eAAe,CAAC,EACvB,iBAAiB,EACjB,SAAS,EACT,YAAY,EACZ,MAAM,EACN,aAAa,GAOd;IACC,iBAAiB,CACf;QACE,aAAa;QACb,SAAS,EAAE,SAAkB;QAC7B,IAAI,EAAE,6BAA6B;KACpC,EACD,CAAC,EAAmB,EAAE,EAAE;QACtB,EAAE,CAAC,iBAAiB,GAAG,iBAAiB,CAAC;QACzC,EAAE,CAAC,wBAAwB,GAAG,EAAE,CAAC;QAEjC,EAAE,CAAC,WAAW,GAAG;YACf,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG;YACvC,OAAO,EAAE,YAAY,CAAC,OAAO;YAC7B,cAAc,EAAE,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG;YAC7C,YAAY,EAAE,YAAY,CAAC,OAAO;YAClC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG;SAC5B,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,aAAa,CAC1B,SAA4C,EAC5C,qBAAoD;IAEpD,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;IAClE,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IAE1D,KAAK,MAAM,aAAa,IAAI,cAAc,EAAE;QAC1C,MAAM,eAAe,GAAG,KAAK,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;QAC7D,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,iBAAiB,EAAE,GAAG,eAAe,CAAC;QAEjE,IAAI,SAAS,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE;YAChC,SAAS;SACV;QAED,MAAM,YAAY,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QACxC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAAC;QAEjD,MAAM,eAAe,GACnB,CAAC,MAAM,QAAQ,CAAC,kBAAkB,EAAE,CAAC;YACnC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa;YACxC,SAAS;SACV,CAAC,CAAC,IAAI,wBAAwB,CAAC;QAElC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,iBAAiB,IAAI,CAAC,CAAC,GAAG,eAAe,CAAC;QAE1E,IAAI,CAAC,SAAS,EAAE;YACd,SAAS;SACV;QAED,GAAG,CAAC,2BAA2B,EAAE;YAC/B,aAAa;YACb,QAAQ,EAAE,YAAY;YACtB,eAAe;SAChB,CAAC,CAAC;QAEH,qBAAqB,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE;YAC5C,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACxB,CAAC,CAAC,CAAC;QAEH,MAAM,YAAY,CAAC;YACjB,SAAS;YACT,eAAe;YACf,aAAa;YACb,qBAAqB;SACtB,CAAC,CAAC;QAEH,GAAG,CAAC,kBAAkB,EAAE,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC;KACpE;AACH,CAAC","sourcesContent":["import type { BatchTransaction } from '@metamask/transaction-controller';\nimport type { TransactionMeta } from '@metamask/transaction-controller';\nimport type { Hex, Json } from '@metamask/utils';\nimport { createModuleLogger } from '@metamask/utils';\n\nimport { getStrategy, getStrategyByName } from './strategy';\nimport { calculateTotals } from './totals';\nimport { getTransaction, updateTransaction } from './transaction';\nimport { projectLogger } from '../logger';\nimport type {\n QuoteRequest,\n TransactionData,\n TransactionPayControllerMessenger,\n TransactionPayQuote,\n TransactionPayTotals,\n TransactionPaymentToken,\n UpdateTransactionDataCallback,\n} from '../types';\n\nconst QUOTES_CHECK_INTERVAL = 1 * 1000; // 1 Second\nconst DEFAULT_REFRESH_INTERVAL = 30 * 1000; // 30 Seconds\n\nconst log = createModuleLogger(projectLogger, 'quotes');\n\nexport type UpdateQuotesRequest = {\n messenger: TransactionPayControllerMessenger;\n transactionData: TransactionData | undefined;\n transactionId: string;\n updateTransactionData: UpdateTransactionDataCallback;\n};\n\n/**\n * Update the quotes for a specific transaction.\n *\n * @param request - Request parameters.\n */\nexport async function updateQuotes(request: UpdateQuotesRequest) {\n const { messenger, transactionData, transactionId, updateTransactionData } =\n request;\n\n const transaction = getTransaction(transactionId, messenger);\n\n log('Updating quotes', { transactionId });\n\n if (!transaction || !transactionData) {\n throw new Error('Transaction not found');\n }\n\n const { paymentToken, sourceAmounts, tokens } = transactionData;\n\n if (!paymentToken) {\n return;\n }\n\n const requests: QuoteRequest[] = (sourceAmounts ?? []).map(\n (sourceAmount, i) => {\n const token = tokens[i];\n\n return {\n from: transaction.txParams.from as Hex,\n sourceBalanceRaw: paymentToken.balanceRaw,\n sourceTokenAmount: sourceAmount.sourceAmountRaw,\n sourceChainId: paymentToken.chainId,\n sourceTokenAddress: paymentToken.address,\n targetAmountMinimum: token.allowUnderMinimum ? '0' : token.amountRaw,\n targetChainId: token.chainId,\n targetTokenAddress: token.address,\n };\n },\n );\n\n if (!requests?.length) {\n log('No quote requests', { transactionId });\n }\n\n let quotes: TransactionPayQuote<Json>[] | undefined = [];\n\n const strategy = await getStrategy(messenger as never, transaction);\n\n try {\n quotes = requests?.length\n ? ((await strategy.getQuotes({\n messenger,\n requests,\n transaction,\n })) as TransactionPayQuote<Json>[])\n : [];\n } catch (error) {\n log('Error fetching quotes', { error, transactionId });\n }\n\n log('Updated', { transactionId, quotes });\n\n const batchTransactions =\n quotes?.length && strategy.getBatchTransactions\n ? await strategy.getBatchTransactions({\n messenger,\n quotes,\n })\n : [];\n\n log('Batch transactions', { transactionId, batchTransactions });\n\n const totals = calculateTotals(quotes as never, tokens, messenger);\n\n log('Calculated totals', { transactionId, totals });\n\n syncTransaction({\n batchTransactions,\n messenger: messenger as never,\n paymentToken,\n totals,\n transactionId,\n });\n\n updateTransactionData(transactionId, (data) => {\n data.quotes = quotes as never;\n data.quotesLastUpdated = Date.now();\n data.totals = totals;\n data.isLoading = false;\n });\n}\n\n/**\n * Poll quotes at regular intervals.\n *\n * @param messenger - Messenger instance.\n * @param updateTransactionData - Callback to update transaction data.\n */\nexport function queueRefreshQuotes(\n messenger: TransactionPayControllerMessenger,\n updateTransactionData: UpdateTransactionDataCallback,\n) {\n setTimeout(() => {\n refreshQuotes(messenger, updateTransactionData)\n .finally(() => queueRefreshQuotes(messenger, updateTransactionData))\n .catch((error) => {\n log('Error polling quotes', { messenger, error });\n });\n }, QUOTES_CHECK_INTERVAL);\n}\n\n/**\n * Sync batch transactions to the transaction meta.\n *\n * @param request - Request object.\n * @param request.batchTransactions - Batch transactions to sync.\n * @param request.messenger - Messenger instance.\n * @param request.paymentToken - Payment token used.\n * @param request.totals - Calculated totals.\n * @param request.transactionId - ID of the transaction to sync.\n */\nfunction syncTransaction({\n batchTransactions,\n messenger,\n paymentToken,\n totals,\n transactionId,\n}: {\n batchTransactions: BatchTransaction[];\n messenger: TransactionPayControllerMessenger;\n paymentToken: TransactionPaymentToken;\n totals: TransactionPayTotals;\n transactionId: string;\n}) {\n updateTransaction(\n {\n transactionId,\n messenger: messenger as never,\n note: 'Update transaction pay data',\n },\n (tx: TransactionMeta) => {\n tx.batchTransactions = batchTransactions;\n tx.batchTransactionsOptions = {};\n\n tx.metamaskPay = {\n bridgeFeeFiat: totals.fees.provider.usd,\n chainId: paymentToken.chainId,\n networkFeeFiat: totals.fees.sourceNetwork.usd,\n tokenAddress: paymentToken.address,\n totalFiat: totals.total.usd,\n };\n },\n );\n}\n\n/**\n * Refresh quotes for all transactions if expired.\n *\n * @param messenger - Messenger instance.\n * @param updateTransactionData - Callback to update transaction data.\n */\nasync function refreshQuotes(\n messenger: TransactionPayControllerMessenger,\n updateTransactionData: UpdateTransactionDataCallback,\n) {\n const state = messenger.call('TransactionPayController:getState');\n const transactionIds = Object.keys(state.transactionData);\n\n for (const transactionId of transactionIds) {\n const transactionData = state.transactionData[transactionId];\n const { isLoading, quotes, quotesLastUpdated } = transactionData;\n\n if (isLoading || !quotes?.length) {\n continue;\n }\n\n const strategyName = quotes[0].strategy;\n const strategy = getStrategyByName(strategyName);\n\n const refreshInterval =\n (await strategy.getRefreshInterval?.({\n chainId: quotes[0].request.sourceChainId,\n messenger,\n })) ?? DEFAULT_REFRESH_INTERVAL;\n\n const isExpired = Date.now() - (quotesLastUpdated ?? 0) > refreshInterval;\n\n if (!isExpired) {\n continue;\n }\n\n log('Refreshing expired quotes', {\n transactionId,\n strategy: strategyName,\n refreshInterval,\n });\n\n updateTransactionData(transactionId, (data) => {\n data.isLoading = true;\n });\n\n await updateQuotes({\n messenger,\n transactionData,\n transactionId,\n updateTransactionData,\n });\n\n log('Refreshed quotes', { transactionId, strategy: strategyName });\n }\n}\n"]}
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseRequiredTokens = void 0;
4
+ const abi_1 = require("@ethersproject/abi");
5
+ const controller_utils_1 = require("@metamask/controller-utils");
6
+ const metamask_eth_abis_1 = require("@metamask/metamask-eth-abis");
7
+ const utils_1 = require("@metamask/utils");
8
+ const bignumber_js_1 = require("bignumber.js");
9
+ const token_1 = require("./token.cjs");
10
+ const FOUR_BYTE_TOKEN_TRANSFER = '0xa9059cbb';
11
+ /**
12
+ * Parse required tokens from a transaction.
13
+ *
14
+ * @param transaction - Transaction metadata.
15
+ * @param messenger - Controller messenger.
16
+ * @returns An array of required tokens.
17
+ */
18
+ function parseRequiredTokens(transaction, messenger) {
19
+ return [
20
+ getTokenTransferToken(transaction, messenger),
21
+ getGasFeeToken(transaction, messenger),
22
+ ].filter(Boolean);
23
+ }
24
+ exports.parseRequiredTokens = parseRequiredTokens;
25
+ /**
26
+ * Parse a required token from a token transfer.
27
+ *
28
+ * @param transaction - Transaction metadata.
29
+ * @param messenger - Controller messenger.
30
+ * @returns The required token or undefined if the transaction is not a token transfer.
31
+ */
32
+ function getTokenTransferToken(transaction, messenger) {
33
+ const { data, to } = getTokenTransferData(transaction) ?? {};
34
+ if (!to || !data) {
35
+ return undefined;
36
+ }
37
+ let transferAmount;
38
+ try {
39
+ const result = new abi_1.Interface(metamask_eth_abis_1.abiERC20).decodeFunctionData('transfer', data);
40
+ transferAmount = (0, controller_utils_1.toHex)(result._value);
41
+ }
42
+ catch {
43
+ // Intentionally empty
44
+ }
45
+ if (transferAmount === undefined) {
46
+ return undefined;
47
+ }
48
+ return buildRequiredToken(transaction, to, transferAmount, messenger);
49
+ }
50
+ /**
51
+ * Get the gas fee token required for a transaction.
52
+ *
53
+ * @param transaction - Transaction metadata.
54
+ * @param messenger - Controller messenger.
55
+ * @returns The gas fee token or undefined if it could not be determined.
56
+ */
57
+ function getGasFeeToken(transaction, messenger) {
58
+ const { chainId, txParams } = transaction;
59
+ const { gas, maxFeePerGas } = txParams;
60
+ const nativeTokenAddress = (0, token_1.getNativeToken)(chainId);
61
+ const maxGasCostRawHex = (0, utils_1.add0x)(new bignumber_js_1.BigNumber(gas ?? '0x0')
62
+ .multipliedBy(new bignumber_js_1.BigNumber(maxFeePerGas ?? '0x0'))
63
+ .toString(16));
64
+ const token = buildRequiredToken(transaction, nativeTokenAddress, maxGasCostRawHex, messenger);
65
+ if (!token) {
66
+ return undefined;
67
+ }
68
+ const amountUsdValue = new bignumber_js_1.BigNumber(token.amountUsd);
69
+ const hasBalance = new bignumber_js_1.BigNumber(token.balanceRaw).isGreaterThanOrEqualTo(token.amountRaw);
70
+ if (hasBalance || amountUsdValue.isGreaterThanOrEqualTo(1)) {
71
+ return {
72
+ ...token,
73
+ allowUnderMinimum: true,
74
+ skipIfBalance: true,
75
+ };
76
+ }
77
+ const fiatRates = (0, token_1.getTokenFiatRate)(messenger, nativeTokenAddress, chainId);
78
+ const oneDollarRawHex = (0, utils_1.add0x)(new bignumber_js_1.BigNumber(1).dividedBy(fiatRates.usdRate).shiftedBy(18).toString(16));
79
+ const oneDollarToken = buildRequiredToken(transaction, nativeTokenAddress, oneDollarRawHex, messenger);
80
+ /* istanbul ignore next */
81
+ if (!oneDollarToken) {
82
+ return undefined;
83
+ }
84
+ return {
85
+ ...oneDollarToken,
86
+ allowUnderMinimum: true,
87
+ skipIfBalance: true,
88
+ };
89
+ }
90
+ /**
91
+ * Get the full token properties for a specific token and amount.
92
+ *
93
+ * @param transaction - Transaction metadata.
94
+ * @param tokenAddress - Token address.
95
+ * @param amountRawHex - Raw token amount in hexadecimal format.
96
+ * @param messenger - Controller messenger.
97
+ * @returns The full token properties or undefined if the token data could not be retrieved.
98
+ */
99
+ function buildRequiredToken(transaction, tokenAddress, amountRawHex, messenger) {
100
+ const { chainId, txParams } = transaction;
101
+ const from = txParams.from;
102
+ const { decimals: tokenDecimals, symbol } = (0, token_1.getTokenInfo)(messenger, tokenAddress, chainId) ?? {};
103
+ const fiatRates = (0, token_1.getTokenFiatRate)(messenger, tokenAddress, chainId);
104
+ const tokenBalance = (0, token_1.getTokenBalance)(messenger, from, chainId, tokenAddress);
105
+ if (tokenDecimals === undefined || !symbol || fiatRates === undefined) {
106
+ return undefined;
107
+ }
108
+ const { amountHuman: balanceHuman, amountRaw: balanceRaw, amountFiat: balanceFiat, amountUsd: balanceUsd, } = calculateAmounts(tokenBalance, tokenDecimals, fiatRates);
109
+ const { amountHuman, amountRaw, amountFiat, amountUsd } = calculateAmounts(amountRawHex, tokenDecimals, fiatRates);
110
+ return {
111
+ address: tokenAddress,
112
+ allowUnderMinimum: false,
113
+ amountFiat,
114
+ amountHuman,
115
+ amountRaw,
116
+ amountUsd,
117
+ balanceFiat,
118
+ balanceHuman,
119
+ balanceRaw,
120
+ balanceUsd,
121
+ chainId,
122
+ decimals: tokenDecimals,
123
+ skipIfBalance: false,
124
+ symbol,
125
+ };
126
+ }
127
+ /**
128
+ * Calculates the various amount representations for a token value.
129
+ *
130
+ * @param amountRawInput - Raw amount.
131
+ * @param decimals - Number of decimals for the token.
132
+ * @param fiatRates - Fiat rates for the token.
133
+ * @returns Object containing amount in fiat, human-readable, raw, and USD formats.
134
+ */
135
+ function calculateAmounts(amountRawInput, decimals, fiatRates) {
136
+ const amountRawValue = new bignumber_js_1.BigNumber(amountRawInput);
137
+ const amountHumanValue = amountRawValue.shiftedBy(-decimals);
138
+ const amountFiat = amountHumanValue
139
+ .multipliedBy(fiatRates.fiatRate)
140
+ .toString(10);
141
+ const amountUsd = amountHumanValue
142
+ .multipliedBy(fiatRates.usdRate)
143
+ .toString(10);
144
+ const amountRaw = amountRawValue.toFixed(0);
145
+ const amountHuman = amountHumanValue.toString(10);
146
+ return {
147
+ amountFiat,
148
+ amountHuman,
149
+ amountRaw,
150
+ amountUsd,
151
+ };
152
+ }
153
+ /**
154
+ * Find token transfer data in a transaction.
155
+ *
156
+ * @param transactionMeta - Transaction metadata.
157
+ * @returns - Token transfer data or undefined if not found.
158
+ */
159
+ function getTokenTransferData(transactionMeta) {
160
+ const { nestedTransactions, txParams } = transactionMeta;
161
+ const { data: singleData } = txParams;
162
+ const singleTo = txParams?.to;
163
+ if (singleData?.startsWith(FOUR_BYTE_TOKEN_TRANSFER) && singleTo) {
164
+ return { data: singleData, to: singleTo, index: undefined };
165
+ }
166
+ const nestedCallIndex = nestedTransactions?.findIndex((call) => call.data?.startsWith(FOUR_BYTE_TOKEN_TRANSFER));
167
+ const nestedCall = nestedCallIndex !== undefined
168
+ ? nestedTransactions?.[nestedCallIndex]
169
+ : undefined;
170
+ if (nestedCall?.data && nestedCall.to) {
171
+ return {
172
+ data: nestedCall.data,
173
+ to: nestedCall.to,
174
+ index: nestedCallIndex,
175
+ };
176
+ }
177
+ return undefined;
178
+ }
179
+ //# sourceMappingURL=required-tokens.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"required-tokens.cjs","sourceRoot":"","sources":["../../src/utils/required-tokens.ts"],"names":[],"mappings":"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","sourcesContent":["import { Interface } from '@ethersproject/abi';\nimport { toHex } from '@metamask/controller-utils';\nimport { abiERC20 } from '@metamask/metamask-eth-abis';\nimport type { TransactionMeta } from '@metamask/transaction-controller';\nimport { add0x, type Hex } from '@metamask/utils';\nimport { BigNumber } from 'bignumber.js';\n\nimport {\n getNativeToken,\n getTokenBalance,\n getTokenFiatRate,\n getTokenInfo,\n} from './token';\nimport type {\n FiatRates,\n TransactionPayControllerMessenger,\n TransactionPayRequiredToken,\n} from '../types';\n\nconst FOUR_BYTE_TOKEN_TRANSFER = '0xa9059cbb';\n\n/**\n * Parse required tokens from a transaction.\n *\n * @param transaction - Transaction metadata.\n * @param messenger - Controller messenger.\n * @returns An array of required tokens.\n */\nexport function parseRequiredTokens(\n transaction: TransactionMeta,\n messenger: TransactionPayControllerMessenger,\n): TransactionPayRequiredToken[] {\n return [\n getTokenTransferToken(transaction, messenger),\n getGasFeeToken(transaction, messenger),\n ].filter(Boolean) as TransactionPayRequiredToken[];\n}\n\n/**\n * Parse a required token from a token transfer.\n *\n * @param transaction - Transaction metadata.\n * @param messenger - Controller messenger.\n * @returns The required token or undefined if the transaction is not a token transfer.\n */\nfunction getTokenTransferToken(\n transaction: TransactionMeta,\n messenger: TransactionPayControllerMessenger,\n): TransactionPayRequiredToken | undefined {\n const { data, to } = getTokenTransferData(transaction) ?? {};\n\n if (!to || !data) {\n return undefined;\n }\n\n let transferAmount: Hex | undefined;\n\n try {\n const result = new Interface(abiERC20).decodeFunctionData('transfer', data);\n transferAmount = toHex(result._value);\n } catch {\n // Intentionally empty\n }\n\n if (transferAmount === undefined) {\n return undefined;\n }\n\n return buildRequiredToken(transaction, to, transferAmount, messenger);\n}\n\n/**\n * Get the gas fee token required for a transaction.\n *\n * @param transaction - Transaction metadata.\n * @param messenger - Controller messenger.\n * @returns The gas fee token or undefined if it could not be determined.\n */\nfunction getGasFeeToken(\n transaction: TransactionMeta,\n messenger: TransactionPayControllerMessenger,\n): TransactionPayRequiredToken | undefined {\n const { chainId, txParams } = transaction;\n const { gas, maxFeePerGas } = txParams;\n const nativeTokenAddress = getNativeToken(chainId);\n\n const maxGasCostRawHex = add0x(\n new BigNumber(gas ?? '0x0')\n .multipliedBy(new BigNumber(maxFeePerGas ?? '0x0'))\n .toString(16),\n );\n\n const token = buildRequiredToken(\n transaction,\n nativeTokenAddress,\n maxGasCostRawHex,\n messenger,\n );\n\n if (!token) {\n return undefined;\n }\n\n const amountUsdValue = new BigNumber(token.amountUsd);\n\n const hasBalance = new BigNumber(token.balanceRaw).isGreaterThanOrEqualTo(\n token.amountRaw,\n );\n\n if (hasBalance || amountUsdValue.isGreaterThanOrEqualTo(1)) {\n return {\n ...token,\n allowUnderMinimum: true,\n skipIfBalance: true,\n };\n }\n\n const fiatRates = getTokenFiatRate(\n messenger,\n nativeTokenAddress,\n chainId,\n ) as FiatRates;\n\n const oneDollarRawHex = add0x(\n new BigNumber(1).dividedBy(fiatRates.usdRate).shiftedBy(18).toString(16),\n );\n\n const oneDollarToken = buildRequiredToken(\n transaction,\n nativeTokenAddress,\n oneDollarRawHex,\n messenger,\n );\n\n /* istanbul ignore next */\n if (!oneDollarToken) {\n return undefined;\n }\n\n return {\n ...oneDollarToken,\n allowUnderMinimum: true,\n skipIfBalance: true,\n };\n}\n\n/**\n * Get the full token properties for a specific token and amount.\n *\n * @param transaction - Transaction metadata.\n * @param tokenAddress - Token address.\n * @param amountRawHex - Raw token amount in hexadecimal format.\n * @param messenger - Controller messenger.\n * @returns The full token properties or undefined if the token data could not be retrieved.\n */\nfunction buildRequiredToken(\n transaction: TransactionMeta,\n tokenAddress: Hex,\n amountRawHex: Hex,\n messenger: TransactionPayControllerMessenger,\n): TransactionPayRequiredToken | undefined {\n const { chainId, txParams } = transaction;\n const from = txParams.from as Hex;\n\n const { decimals: tokenDecimals, symbol } =\n getTokenInfo(messenger, tokenAddress, chainId) ?? {};\n\n const fiatRates = getTokenFiatRate(messenger, tokenAddress, chainId);\n const tokenBalance = getTokenBalance(messenger, from, chainId, tokenAddress);\n\n if (tokenDecimals === undefined || !symbol || fiatRates === undefined) {\n return undefined;\n }\n\n const {\n amountHuman: balanceHuman,\n amountRaw: balanceRaw,\n amountFiat: balanceFiat,\n amountUsd: balanceUsd,\n } = calculateAmounts(tokenBalance, tokenDecimals, fiatRates);\n\n const { amountHuman, amountRaw, amountFiat, amountUsd } = calculateAmounts(\n amountRawHex,\n tokenDecimals,\n fiatRates,\n );\n\n return {\n address: tokenAddress,\n allowUnderMinimum: false,\n amountFiat,\n amountHuman,\n amountRaw,\n amountUsd,\n balanceFiat,\n balanceHuman,\n balanceRaw,\n balanceUsd,\n chainId,\n decimals: tokenDecimals,\n skipIfBalance: false,\n symbol,\n };\n}\n\n/**\n * Calculates the various amount representations for a token value.\n *\n * @param amountRawInput - Raw amount.\n * @param decimals - Number of decimals for the token.\n * @param fiatRates - Fiat rates for the token.\n * @returns Object containing amount in fiat, human-readable, raw, and USD formats.\n */\nfunction calculateAmounts(\n amountRawInput: BigNumber.Value,\n decimals: number,\n fiatRates: FiatRates,\n) {\n const amountRawValue = new BigNumber(amountRawInput);\n const amountHumanValue = amountRawValue.shiftedBy(-decimals);\n\n const amountFiat = amountHumanValue\n .multipliedBy(fiatRates.fiatRate)\n .toString(10);\n\n const amountUsd = amountHumanValue\n .multipliedBy(fiatRates.usdRate)\n .toString(10);\n\n const amountRaw = amountRawValue.toFixed(0);\n const amountHuman = amountHumanValue.toString(10);\n\n return {\n amountFiat,\n amountHuman,\n amountRaw,\n amountUsd,\n };\n}\n\n/**\n * Find token transfer data in a transaction.\n *\n * @param transactionMeta - Transaction metadata.\n * @returns - Token transfer data or undefined if not found.\n */\nfunction getTokenTransferData(transactionMeta: TransactionMeta):\n | {\n data: Hex;\n to: Hex;\n index?: number;\n }\n | undefined {\n const { nestedTransactions, txParams } = transactionMeta;\n const { data: singleData } = txParams;\n const singleTo = txParams?.to as Hex | undefined;\n\n if (singleData?.startsWith(FOUR_BYTE_TOKEN_TRANSFER) && singleTo) {\n return { data: singleData as Hex, to: singleTo, index: undefined };\n }\n\n const nestedCallIndex = nestedTransactions?.findIndex((call) =>\n call.data?.startsWith(FOUR_BYTE_TOKEN_TRANSFER),\n );\n\n const nestedCall =\n nestedCallIndex !== undefined\n ? nestedTransactions?.[nestedCallIndex]\n : undefined;\n\n if (nestedCall?.data && nestedCall.to) {\n return {\n data: nestedCall.data,\n to: nestedCall.to,\n index: nestedCallIndex,\n };\n }\n\n return undefined;\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import type { TransactionMeta } from "@metamask/transaction-controller";
2
+ import type { TransactionPayControllerMessenger, TransactionPayRequiredToken } from "../types.cjs";
3
+ /**
4
+ * Parse required tokens from a transaction.
5
+ *
6
+ * @param transaction - Transaction metadata.
7
+ * @param messenger - Controller messenger.
8
+ * @returns An array of required tokens.
9
+ */
10
+ export declare function parseRequiredTokens(transaction: TransactionMeta, messenger: TransactionPayControllerMessenger): TransactionPayRequiredToken[];
11
+ //# sourceMappingURL=required-tokens.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"required-tokens.d.cts","sourceRoot":"","sources":["../../src/utils/required-tokens.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,yCAAyC;AAUxE,OAAO,KAAK,EAEV,iCAAiC,EACjC,2BAA2B,EAC5B,qBAAiB;AAIlB;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,eAAe,EAC5B,SAAS,EAAE,iCAAiC,GAC3C,2BAA2B,EAAE,CAK/B"}