@exodus/ethereum-api 6.0.3 → 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 +3 -3
- package/src/allowance/constants.js +14 -0
- package/src/allowance/index.js +121 -0
- package/src/index.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-api",
|
|
3
|
-
"version": "6.0
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"description": "Ethereum Api",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@exodus/asset-lib": "^3.7.1",
|
|
18
18
|
"@exodus/crypto": "^1.0.0-rc.0",
|
|
19
|
-
"@exodus/ethereum-lib": "^3.0.
|
|
19
|
+
"@exodus/ethereum-lib": "^3.0.3",
|
|
20
20
|
"@exodus/ethereumjs-util": "^7.1.0-exodus.6",
|
|
21
21
|
"@exodus/fetch": "^1.2.1",
|
|
22
22
|
"@exodus/simple-retry": "^0.0.6",
|
|
@@ -36,5 +36,5 @@
|
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@exodus/models": "^8.10.4"
|
|
38
38
|
},
|
|
39
|
-
"gitHead": "
|
|
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