@exodus/ethereum-api 6.0.4 → 6.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "6.0.4",
3
+ "version": "6.1.0",
4
4
  "description": "Ethereum Api",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -36,5 +36,5 @@
36
36
  "devDependencies": {
37
37
  "@exodus/models": "^8.10.4"
38
38
  },
39
- "gitHead": "39949d6e7fbda630eaa97143d441f226344fa7e2"
39
+ "gitHead": "2de4be14ec048af2830b2a2c21ddd8dbc5d06d4d"
40
40
  }
@@ -0,0 +1,14 @@
1
+ // assets that require setting allowance to zero by calling `approve(_spender, 0)`
2
+ // before setting a non-zero allowance
3
+ export const ZERO_ALLOWANCE_ASSETS = [
4
+ 'tetherusd',
5
+ 'aragon',
6
+ 'bancor',
7
+ 'status',
8
+ 'decentraland',
9
+ 'dent',
10
+ 'lidodao',
11
+ ]
12
+
13
+ export const APPROVAL_GAS_LIMIT = 60000
14
+ export const ALLOWANCE_TX_TIMEOUT = 60000 * 5
@@ -0,0 +1,121 @@
1
+ import { fetchGasLimit, getServer } from '../'
2
+ import { ALLOWANCE_TX_TIMEOUT, APPROVAL_GAS_LIMIT, ZERO_ALLOWANCE_ASSETS } from './constants'
3
+
4
+ export { APPROVAL_GAS_LIMIT, ZERO_ALLOWANCE_ASSETS }
5
+
6
+ export const isZeroAllowanceAsset = (assetName) => ZERO_ALLOWANCE_ASSETS.includes(assetName)
7
+
8
+ export async function getSpendingAllowance({ asset, fromAddress, spenderAddress }) {
9
+ const data = asset.contract.allowance.build(fromAddress, spenderAddress)
10
+
11
+ const res = await getServer(asset)
12
+ .ethCall({
13
+ data: '0x' + data.toString('hex'),
14
+ to: asset.contract.address,
15
+ })
16
+ .catch(() => 0)
17
+
18
+ return asset.currency.baseUnit(res)
19
+ }
20
+
21
+ export async function isSpendingApprovalRequired({
22
+ asset,
23
+ fromAddress,
24
+ spenderAddress,
25
+ tokenAmount,
26
+ }) {
27
+ const allowance = await getSpendingAllowance({ asset, fromAddress, spenderAddress })
28
+ return tokenAmount.gt(allowance)
29
+ }
30
+
31
+ export const createApprove = ({ sendTx }) => async ({
32
+ walletAccount,
33
+ fromAddress,
34
+ spenderAddress,
35
+ asset,
36
+ feeData,
37
+ feeAmount,
38
+ approveAmount,
39
+ gasLimit,
40
+ txInput: preTxInput,
41
+ }) => {
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
+ fromAddress,
51
+ toAddress,
52
+ txInput,
53
+ feeData,
54
+ isContract: true,
55
+ }
56
+
57
+ gasLimit = gasLimit || (await fetchGasLimit(gasLimitOptions))
58
+
59
+ const txData = await sendTx({
60
+ asset: baseAsset.name,
61
+ walletAccount,
62
+ receiver: {
63
+ address: toAddress,
64
+ amount: baseAsset.currency.ZERO,
65
+ },
66
+ txInput,
67
+ gasPrice: feeData.gasPrice,
68
+ gasLimit,
69
+ feeAmount,
70
+ silent: true,
71
+ })
72
+
73
+ if (!txData || !txData.txId) {
74
+ throw new Error(`Failed to approve ${asset.displayTicker} - ${spenderAddress}`)
75
+ }
76
+
77
+ return txData
78
+ }
79
+
80
+ const createSendApprovalAndWatchConfirmation = ({ sendTx, watchTxConfirmation }) => async (
81
+ data
82
+ ) => {
83
+ // To change the approve amount you first have to reduce the addresses`
84
+ // allowance to zero by calling `approve(_spender, 0)` if it is not
85
+ // already 0 to mitigate the race condition described here:
86
+ // see: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
87
+ // see: https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#code
88
+ const approve = createApprove({ sendTx })
89
+ const { txId } = await approve(data)
90
+ await watchTxConfirmation(
91
+ { asset: data.asset.baseAsset.name, walletAccount: data.walletAccount },
92
+ txId,
93
+ ALLOWANCE_TX_TIMEOUT
94
+ )
95
+
96
+ return txId
97
+ }
98
+
99
+ export const createApproveSpendingTokens = ({ sendTx, watchTxConfirmation }) => async (data) => {
100
+ const txIds = []
101
+ const sendApprovalAndWatchConfirmation = createSendApprovalAndWatchConfirmation({
102
+ sendTx,
103
+ watchTxConfirmation,
104
+ })
105
+ if (ZERO_ALLOWANCE_ASSETS.includes(data.asset.name) && !data.approveAmount.isZero) {
106
+ const revokeTokenApprovalTxId = await sendApprovalAndWatchConfirmation({
107
+ ...data,
108
+ txInput: undefined,
109
+ approveAmount: data.asset.currency.ZERO,
110
+ })
111
+
112
+ txIds.push(revokeTokenApprovalTxId)
113
+ // Hardcoded min gas limit. Otherwise `fetchGasLimit` will fail since `approve(_spender, 0)` is not yet confirmed
114
+ data.gasLimit = Math.max(data.gasLimit || 0, APPROVAL_GAS_LIMIT)
115
+ }
116
+
117
+ const tokenApprovalTxId = await sendApprovalAndWatchConfirmation({ ...data, sendTx })
118
+ txIds.push(tokenApprovalTxId)
119
+
120
+ return txIds
121
+ }
package/src/index.js CHANGED
@@ -9,3 +9,4 @@ export * from './tx-log'
9
9
  export * from './get-balances'
10
10
  export * from './staking'
11
11
  export * from './simulate-tx'
12
+ export * from './allowance'