@exodus/ethereum-api 8.41.0 → 8.42.1

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 CHANGED
@@ -3,6 +3,24 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [8.42.1](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.42.0...@exodus/ethereum-api@8.42.1) (2025-07-11)
7
+
8
+ **Note:** Version bump only for package @exodus/ethereum-api
9
+
10
+
11
+
12
+
13
+
14
+ ## [8.42.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.41.0...@exodus/ethereum-api@8.42.0) (2025-07-10)
15
+
16
+
17
+ ### Features
18
+
19
+
20
+ * feat: rig `getNonce` to evm asset api (#6047)
21
+
22
+
23
+
6
24
  ## [8.41.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.40.1...@exodus/ethereum-api@8.41.0) (2025-07-01)
7
25
 
8
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "8.41.0",
3
+ "version": "8.42.1",
4
4
  "description": "Transaction monitors, fee monitors, RPC with the blockchain node, and other networking code for Ethereum and EVM-based blockchains",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -63,5 +63,5 @@
63
63
  "type": "git",
64
64
  "url": "git+https://github.com/ExodusMovement/assets.git"
65
65
  },
66
- "gitHead": "1077a752b60e0b25961613fd007596ab3368188e"
66
+ "gitHead": "439848922f4e7a525c4e2373c99daefd89da402b"
67
67
  }
@@ -1,3 +1,8 @@
1
+ import { isNumberUnit } from '@exodus/currency'
2
+ import { buffer2currency, createUnsignedTxFactory } from '@exodus/ethereum-lib'
3
+ import { bufferToInt } from '@exodus/ethereumjs/util'
4
+ import assert from 'minimalistic-assert'
5
+
1
6
  import { getServer } from '../exodus-eth-server/index.js'
2
7
  import { fetchGasLimit } from '../gas-estimation.js'
3
8
  import { ALLOWANCE_TX_TIMEOUT, APPROVAL_GAS_LIMIT, ZERO_ALLOWANCE_ASSETS } from './constants.js'
@@ -27,8 +32,80 @@ export async function isSpendingApprovalRequired({
27
32
  return tokenAmount.gt(allowance)
28
33
  }
29
34
 
35
+ export const buildApproveTx = async ({
36
+ spenderAddress,
37
+ asset,
38
+ feeData,
39
+ fromAddress,
40
+ approveAmount = asset.currency.ZERO,
41
+ gasLimit,
42
+ txInput,
43
+ nonce,
44
+ walletAccount,
45
+ }) => {
46
+ assert(asset, 'expected asset')
47
+ assert(feeData, 'expected feeData')
48
+ assert(isNumberUnit(approveAmount), 'expected approveAmount')
49
+ assert(typeof spenderAddress === 'string', 'expected spenderAddress')
50
+ assert(walletAccount, 'expected walletAccount')
51
+
52
+ const { baseAsset } = asset
53
+ const chainId = baseAsset.chainId
54
+ assert(typeof chainId === 'number', 'expected chainId')
55
+
56
+ const createUnsignedTx = createUnsignedTxFactory({ chainId })
57
+
58
+ const toAddress = asset.contract.address
59
+ assert(typeof toAddress === 'string', 'expected contract address')
60
+
61
+ if (!txInput) txInput = asset.contract.approve.build(spenderAddress, approveAmount.toBaseString())
62
+
63
+ if (typeof nonce !== 'number')
64
+ nonce = await baseAsset.getNonce({ asset, fromAddress, walletAccount })
65
+
66
+ if (!gasLimit)
67
+ gasLimit = await fetchGasLimit({
68
+ asset: baseAsset,
69
+ amount: baseAsset.currency.ZERO,
70
+ toAddress,
71
+ txInput,
72
+ feeData,
73
+ isContract: true,
74
+ fromAddress,
75
+ })
76
+
77
+ return createUnsignedTx({
78
+ asset,
79
+ address: toAddress,
80
+ amount: baseAsset.currency.ZERO,
81
+ nonce,
82
+ txInput,
83
+ gasLimit,
84
+ gasPrice: feeData.gasPrice,
85
+ tipGasPrice: feeData.tipGasPrice,
86
+ fromAddress,
87
+ eip1559Enabled: feeData.eip1559Enabled,
88
+ })
89
+ }
90
+
91
+ const getLegacyApproveTxFromUnsignedTx = ({ baseAsset, unsignedTx }) => ({
92
+ asset: baseAsset.name,
93
+ receiver: {
94
+ address: unsignedTx.txData.to,
95
+ amount: buffer2currency({ asset: baseAsset, value: unsignedTx.txData.value }),
96
+ },
97
+ txInput: unsignedTx.txData.data,
98
+ gasPrice: buffer2currency({ asset: baseAsset, value: unsignedTx.txData.gasPrice }),
99
+ tipGasPrice: unsignedTx.txData.tipGasPrice
100
+ ? buffer2currency({ asset: baseAsset, value: unsignedTx.txData.tipGasPrice })
101
+ : undefined,
102
+ gasLimit: bufferToInt(unsignedTx.txData.gasLimit),
103
+ fromAddress: unsignedTx.txMeta.fromAddress,
104
+ })
105
+
106
+ // @deprecated use buildApproveTx instead
30
107
  export const createApprove =
31
- ({ sendTx }) =>
108
+ ({ assetClientInterface, sendTx }) =>
32
109
  async ({
33
110
  spenderAddress,
34
111
  asset,
@@ -36,45 +113,41 @@ export const createApprove =
36
113
  fromAddress,
37
114
  approveAmount,
38
115
  gasLimit,
39
- txInput: preTxInput,
40
- ...rest
116
+ txInput,
117
+ nonce,
118
+ walletAccount,
119
+ ...extras
41
120
  }) => {
42
- const baseAsset = asset.baseAsset
43
- const txInput =
44
- preTxInput ?? asset.contract.approve.build(spenderAddress, approveAmount.toBaseString())
45
-
46
- const toAddress = asset.contract.address
47
- const gasLimitOptions = {
48
- asset: baseAsset,
49
- amount: baseAsset.currency.ZERO,
50
- toAddress,
51
- txInput,
121
+ const unsignedTx = await buildApproveTx({
122
+ spenderAddress,
123
+ asset,
52
124
  feeData,
53
- isContract: true,
54
125
  fromAddress,
55
- }
56
-
57
- gasLimit = gasLimit || (await fetchGasLimit(gasLimitOptions))
58
-
59
- const txData = await sendTx({
60
- asset: baseAsset.name,
61
- receiver: {
62
- address: toAddress,
63
- amount: baseAsset.currency.ZERO,
64
- },
65
- txInput,
66
- gasPrice: feeData.gasPrice,
126
+ approveAmount,
67
127
  gasLimit,
68
- silent: true,
69
- fromAddress,
70
- ...rest,
128
+ txInput,
129
+ nonce,
130
+ walletAccount,
71
131
  })
72
132
 
73
- if (!txData || !txData.txId) {
74
- throw new Error(`Failed to approve ${asset.displayTicker} - ${spenderAddress}`)
133
+ if (!assetClientInterface) {
134
+ const approveTx = getLegacyApproveTxFromUnsignedTx({ baseAsset: asset.baseAsset, unsignedTx })
135
+ const txData = await sendTx({ walletAccount, ...approveTx, silent: true, ...extras })
136
+
137
+ if (!txData || !txData.txId)
138
+ throw new Error(`Failed to approve ${asset.displayTicker} - ${spenderAddress}`)
139
+
140
+ return txData
75
141
  }
76
142
 
77
- return txData
143
+ const { txId, rawTx } = await assetClientInterface.signTransaction({
144
+ assetName: asset.baseAsset.name,
145
+ unsignedTx,
146
+ walletAccount,
147
+ })
148
+
149
+ await asset.baseAsset.api.broadcastTx(rawTx.toString('hex'))
150
+ return { txId }
78
151
  }
79
152
 
80
153
  const createSendApprovalAndWatchConfirmation =
@@ -6,6 +6,7 @@ import { createEthereumHooks } from './hooks/index.js'
6
6
  import { ClarityMonitor } from './tx-log/clarity-monitor.js'
7
7
  import { EthereumMonitor } from './tx-log/ethereum-monitor.js'
8
8
  import { EthereumNoHistoryMonitor } from './tx-log/ethereum-no-history-monitor.js'
9
+ import { resolveNonce } from './tx-send/nonce-utils.js'
9
10
 
10
11
  // Determines the appropriate `monitorType`, `serverUrl` and `monitorInterval`
11
12
  // to use for a given config.
@@ -151,3 +152,33 @@ export const createHistoryMonitorFactory = ({
151
152
  return monitor
152
153
  }
153
154
  }
155
+
156
+ export const getNonceFactory = ({ assetClientInterface, useAbsoluteBalanceAndNonce }) => {
157
+ assert(assetClientInterface, 'expected assetClientInterface')
158
+ assert(typeof useAbsoluteBalanceAndNonce === 'boolean', 'expected useAbsoluteBalanceAndNonce')
159
+
160
+ const getNonce = async ({ asset, fromAddress, walletAccount }) => {
161
+ assert(asset, 'expected asset')
162
+ assert(typeof fromAddress === 'string', 'expected fromAddress')
163
+ assert(walletAccount, 'expected walletAccount')
164
+
165
+ const txLog = await assetClientInterface.getTxLog({
166
+ assetName: asset.baseAsset.name,
167
+ walletAccount,
168
+ })
169
+
170
+ return resolveNonce({
171
+ asset,
172
+ fromAddress,
173
+ txLog,
174
+ // For assets where we'll fall back to querying the coin node, we
175
+ // search for pending transactions. For base assets with history,
176
+ // we'll fall back to the `TxLog` since this also has a knowledge
177
+ // of which transactions are currently in pending.
178
+ tag: 'pending',
179
+ useAbsoluteNonce: useAbsoluteBalanceAndNonce,
180
+ })
181
+ }
182
+
183
+ return { getNonce }
184
+ }
@@ -1,5 +1,4 @@
1
1
  import { connectAssetsList } from '@exodus/assets'
2
- import { pick } from '@exodus/basic-utils'
3
2
  import bip44Constants from '@exodus/bip44-constants/by-ticker.js'
4
3
  import {
5
4
  createEthereumLikeAccountState,
@@ -24,6 +23,7 @@ import { addressHasHistoryFactory } from './address-has-history.js'
24
23
  import {
25
24
  createHistoryMonitorFactory,
26
25
  createTransactionPrivacyFactory,
26
+ getNonceFactory,
27
27
  resolveMonitorSettings,
28
28
  } from './create-asset-utils.js'
29
29
  import { createTokenFactory } from './create-token-factory.js'
@@ -237,6 +237,8 @@ export const createAssetFactory = ({
237
237
  ? getL1GetFeeFactory({ asset, originalGetFee })
238
238
  : originalGetFee
239
239
 
240
+ const { getNonce } = getNonceFactory({ assetClientInterface, useAbsoluteBalanceAndNonce })
241
+
240
242
  const api = {
241
243
  addressHasHistory,
242
244
  broadcastTx: (...args) => server.sendRawTransaction(...args),
@@ -280,7 +282,6 @@ export const createAssetFactory = ({
280
282
 
281
283
  const fullAsset = {
282
284
  ...asset,
283
- ...pick(config, ['accountReserve']),
284
285
  gasLimit,
285
286
  contractGasLimit,
286
287
  bip44,
@@ -293,6 +294,7 @@ export const createAssetFactory = ({
293
294
  broadcastPrivateBundle,
294
295
  broadcastPrivateTx,
295
296
  forceGasLimitEstimation,
297
+ getNonce,
296
298
  privacyServer,
297
299
  server,
298
300
  ...(erc20FuelBuffer && { erc20FuelBuffer }),
package/src/index.js CHANGED
@@ -61,6 +61,7 @@ export {
61
61
  isZeroAllowanceAsset,
62
62
  getSpendingAllowance,
63
63
  isSpendingApprovalRequired,
64
+ buildApproveTx,
64
65
  createApprove,
65
66
  createApproveSpendingTokens,
66
67
  APPROVAL_GAS_LIMIT,
@@ -204,17 +204,7 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx, useAbsoluteBala
204
204
  const resolvedNonce =
205
205
  providedNonce ??
206
206
  bumpNonce ??
207
- (await resolveNonce({
208
- asset,
209
- fromAddress,
210
- txLog: baseAssetTxLog,
211
- // For assets where we'll fall back to querying the coin node, we
212
- // search for pending transactions. For base assets with history,
213
- // we'll fall back to the `TxLog` since this also has a knowledge
214
- // of which transactions are currently in pending.
215
- tag: 'pending',
216
- useAbsoluteNonce: useAbsoluteBalanceAndNonce,
217
- }))
207
+ (await asset.baseAsset.getNonce({ asset, fromAddress, walletAccount }))
218
208
 
219
209
  const createTxParams = {
220
210
  assetClientInterface,