-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathcontractWrites.ts
267 lines (245 loc) · 8.15 KB
/
contractWrites.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
import { RenNetworkDetails } from "@renproject/contracts";
import BigNumber from "bignumber.js";
import Web3 from "web3";
import { PromiEvent, TransactionReceipt } from "web3-core";
import { catchInteractionException, noCapture } from "../react/errors";
import { retryNTimes } from "../retryNTimes";
import {
getDarknodePayment,
getDarknodeRegistry,
getRenToken,
} from "./contract";
/**
* Top-up the ETH balance of a darknode.
*
* @param web3 Web3 provider with `address` unlocked.
* @param address Ethereum address to send ETH from.
* @param darknodeID Hexadecimal ID of the darknode to fund.
* @param ethAmountStr Amount as a string.
*/
// eslint-disable-next-line @typescript-eslint/promise-function-async
export const fundNode = (
web3: Web3,
address: string,
darknodeID: string,
ethAmountStr: string,
): PromiEvent<TransactionReceipt> => {
// Convert eth to wei
const weiAmount = new BigNumber(ethAmountStr)
.times(new BigNumber(10).exponentiatedBy(18))
.decimalPlaces(0);
if (weiAmount.isNaN()) {
throw new Error(
`Invalid ETH amount '${ethAmountStr}' - please try again`,
);
}
// Simple ETH transaction with no data.
return web3.eth.sendTransaction({
to: darknodeID,
value: weiAmount.toFixed(),
from: address,
});
};
// ////////////////////////// //
// Darknode Registry contract //
// ////////////////////////// //
/**
* Approve REN to the DarknodeRegistry contract for registering a node.
*
* @param web3 Web3 provider with `address` unlocked.
* @param renNetwork The details of the selected Ren network.
* @param address Ethereum address to send Ethereum transactions from.
* @param bond The bond amount in REN's smallest unit (1e-18 REN).
*/
export const approveNode = async (
web3: Web3,
renNetwork: RenNetworkDetails,
address: string,
bond: BigNumber,
): Promise<{ promiEvent: PromiEvent<TransactionReceipt> | null }> => {
const ercContract = getRenToken(web3, renNetwork);
// Check that the user has sufficient REN for bond
let ercBalance;
try {
ercBalance = new BigNumber(
await retryNTimes(
async () => await ercContract.methods.balanceOf(address).call(),
2,
),
);
} catch (error) {
ercBalance = bond;
catchInteractionException(
error,
"Error in contractWrites.ts: approveNode > balanceOf",
);
}
if (ercBalance.lt(bond)) {
throw noCapture(
new Error("You have insufficient REN to register a darknode."),
);
}
// Check if they've already approved REN
let ercAllowance;
try {
ercAllowance = new BigNumber(
await retryNTimes(
async () =>
await ercContract.methods
.allowance(
address,
renNetwork.addresses.ren.DarknodeRegistry.address,
)
.call(),
2,
),
);
} catch (error) {
catchInteractionException(
error,
"Error in contractWrites.ts: approveNode > allowance",
);
ercAllowance = new BigNumber(0);
}
if (ercAllowance.gte(bond)) {
// Already approved
return { promiEvent: null };
}
return {
promiEvent: ercContract.methods
.approve(
renNetwork.addresses.ren.DarknodeRegistry.address,
bond.toFixed(),
)
.send({ from: address }),
};
};
/**
* Register node in the DarknodeRegistry contract. Must have called
* [[approveNode]] first. The darknode will then have the status
* "Registration Pending" until the next Epoch.
*
* @param web3 Web3 provider with `address` unlocked.
* @param renNetwork The details of the selected Ren network.
* @param darknodeID Hexadecimal ID of the darknode to register.
* @param bond The bond amount in REN's smallest unit (1e-18 REN).
*/
export const registerNode = async (
web3: Web3,
renNetwork: RenNetworkDetails,
address: string,
darknodeID: string,
bond: BigNumber,
): Promise<{ promiEvent: PromiEvent<TransactionReceipt> }> => {
const hardCodedGas = 500000;
const ercContract = getRenToken(web3, renNetwork);
let ercAllowance;
try {
ercAllowance = new BigNumber(
await retryNTimes(
async () =>
await ercContract.methods
.allowance(
address,
renNetwork.addresses.ren.DarknodeRegistry.address,
)
.call(),
2,
),
);
} catch (error) {
ercAllowance = new BigNumber(0);
catchInteractionException(
error,
"Error in contractWrites.ts: registerNode > allowance",
);
}
let gas: number | undefined = hardCodedGas;
if (ercAllowance.gte(bond)) {
gas = undefined;
}
const darknodeRegistry = getDarknodeRegistry(web3, renNetwork);
return {
promiEvent: darknodeRegistry.methods
.register(darknodeID, [])
.send({ from: address, gas }),
};
};
/**
* Deregister a node in the DarknodeRegistry contract. The node will then have
* the status "Pending Deregistration" until the next Epoch. The bond won't
* be returned yet.
*
* @param web3 Web3 provider with `address` unlocked.
* @param renNetwork The details of the selected Ren network.
* @param address Ethereum address to send Ethereum transactions from.
* @param darknodeID Hexadecimal ID of the darknode to deregister.
*/
// eslint-disable-next-line @typescript-eslint/promise-function-async
export const deregisterNode = (
web3: Web3,
renNetwork: RenNetworkDetails,
address: string,
darknodeID: string,
): PromiEvent<TransactionReceipt> => {
// The node has been registered and can be deregistered.
const darknodeRegistry = new web3.eth.Contract(
renNetwork.addresses.ren.DarknodeRegistry.abi,
renNetwork.addresses.ren.DarknodeRegistry.address,
);
return darknodeRegistry.methods
.deregister(darknodeID)
.send({ from: address });
};
/**
* Return the REN bond of the darknode. This is the last step in deregistering
* a darknode.
*
* @param web3 Web3 provider with `address` unlocked.
* @param renNetwork The details of the selected Ren network.
* @param address Ethereum address to send Ethereum transactions from.
* @param darknodeID Hexadecimal ID of the darknode to refund.
*/
// eslint-disable-next-line @typescript-eslint/promise-function-async
export const refundNode = (
web3: Web3,
renNetwork: RenNetworkDetails,
address: string,
darknodeID: string,
): PromiEvent<TransactionReceipt> => {
// The node is awaiting refund.
const darknodeRegistry = getDarknodeRegistry(web3, renNetwork);
return darknodeRegistry.methods.refund(darknodeID).send({ from: address });
};
/**
* Withdraw a darknode's fees for a single token from the DarknodePayment
* contract.
*
* @param web3 Web3 provider with `address` unlocked.
* @param darknodeID Hexadecimal ID of the darknode to register.
* @param address Ethereum address to send Ethereum transactions from.
* @param darknodeID Hexadecimal ID of the darknode to refund.
* @param token The token to withdraw fees for.
*/
// eslint-disable-next-line @typescript-eslint/promise-function-async
export const withdrawToken = (
web3: Web3,
renNetwork: RenNetworkDetails,
address: string | null,
darknodeIDs: string[],
tokenAddress: string,
): PromiEvent<TransactionReceipt> => {
if (!address) {
throw new Error(`Unable to retrieve account address.`);
}
const darknodePayment = getDarknodePayment(web3, renNetwork);
if (darknodeIDs.length === 1) {
return darknodePayment.methods
.withdraw(darknodeIDs[0], tokenAddress)
.send({ from: address });
} else {
return darknodePayment.methods
.withdrawMultiple(darknodeIDs, [tokenAddress])
.send({ from: address });
}
};