@exodus/ethereum-lib 0.0.9 → 0.2.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 -6
- package/src/abi/cdai.js +609 -0
- package/src/abi/dai.js +348 -0
- package/src/abi/index.js +7 -0
- package/src/constants.js +1 -0
- package/src/create-contract/erc20.js +77 -0
- package/src/create-contract/index.js +8 -0
- package/src/encode.js +36 -0
- package/src/fee-data/ethereum.js +11 -0
- package/src/fee-data/ethereumclassic.js +11 -0
- package/src/fee-data/index.js +2 -0
- package/src/index.js +8 -0
- package/src/unsigned-tx/create-and-sign-tx.js +13 -0
- package/src/unsigned-tx/create-unsigned-tx.js +62 -0
- package/src/unsigned-tx/index.js +4 -0
- package/src/unsigned-tx/parse-unsigned-tx.js +44 -0
- package/src/unsigned-tx/sign-unsigned-tx.js +19 -0
- package/src/utils.js +28 -0
- package/lib/abi/cdai.js +0 -900
- package/lib/abi/dai.js +0 -451
- package/lib/abi/index.js +0 -18
- package/lib/constants.js +0 -11
- package/lib/create-contract/erc20.js +0 -72
- package/lib/create-contract/index.js +0 -17
- package/lib/encode.js +0 -50
- package/lib/fee-data/ethereum.js +0 -16
- package/lib/fee-data/ethereumclassic.js +0 -16
- package/lib/fee-data/index.js +0 -23
- package/lib/index.js +0 -79
- package/lib/unsigned-tx/create-and-sign-tx.js +0 -17
- package/lib/unsigned-tx/create-unsigned-tx.js +0 -70
- package/lib/unsigned-tx/index.js +0 -39
- package/lib/unsigned-tx/parse-unsigned-tx.js +0 -69
- package/lib/unsigned-tx/sign-unsigned-tx.js +0 -25
- package/lib/utils.js +0 -47
package/src/abi/dai.js
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
export default [
|
|
2
|
+
{
|
|
3
|
+
inputs: [{ internalType: 'uint256', name: 'chainId_', type: 'uint256' }],
|
|
4
|
+
payable: false,
|
|
5
|
+
stateMutability: 'nonpayable',
|
|
6
|
+
type: 'constructor',
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
anonymous: false,
|
|
10
|
+
inputs: [
|
|
11
|
+
{ indexed: true, internalType: 'address', name: 'src', type: 'address' },
|
|
12
|
+
{
|
|
13
|
+
indexed: true,
|
|
14
|
+
internalType: 'address',
|
|
15
|
+
name: 'guy',
|
|
16
|
+
type: 'address',
|
|
17
|
+
},
|
|
18
|
+
{ indexed: false, internalType: 'uint256', name: 'wad', type: 'uint256' },
|
|
19
|
+
],
|
|
20
|
+
name: 'Approval',
|
|
21
|
+
type: 'event',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
anonymous: true,
|
|
25
|
+
inputs: [
|
|
26
|
+
{ indexed: true, internalType: 'bytes4', name: 'sig', type: 'bytes4' },
|
|
27
|
+
{
|
|
28
|
+
indexed: true,
|
|
29
|
+
internalType: 'address',
|
|
30
|
+
name: 'usr',
|
|
31
|
+
type: 'address',
|
|
32
|
+
},
|
|
33
|
+
{ indexed: true, internalType: 'bytes32', name: 'arg1', type: 'bytes32' },
|
|
34
|
+
{
|
|
35
|
+
indexed: true,
|
|
36
|
+
internalType: 'bytes32',
|
|
37
|
+
name: 'arg2',
|
|
38
|
+
type: 'bytes32',
|
|
39
|
+
},
|
|
40
|
+
{ indexed: false, internalType: 'bytes', name: 'data', type: 'bytes' },
|
|
41
|
+
],
|
|
42
|
+
name: 'LogNote',
|
|
43
|
+
type: 'event',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
anonymous: false,
|
|
47
|
+
inputs: [
|
|
48
|
+
{ indexed: true, internalType: 'address', name: 'src', type: 'address' },
|
|
49
|
+
{
|
|
50
|
+
indexed: true,
|
|
51
|
+
internalType: 'address',
|
|
52
|
+
name: 'dst',
|
|
53
|
+
type: 'address',
|
|
54
|
+
},
|
|
55
|
+
{ indexed: false, internalType: 'uint256', name: 'wad', type: 'uint256' },
|
|
56
|
+
],
|
|
57
|
+
name: 'Transfer',
|
|
58
|
+
type: 'event',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
constant: true,
|
|
62
|
+
inputs: [],
|
|
63
|
+
name: 'DOMAIN_SEPARATOR',
|
|
64
|
+
outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
|
|
65
|
+
payable: false,
|
|
66
|
+
stateMutability: 'view',
|
|
67
|
+
type: 'function',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
constant: true,
|
|
71
|
+
inputs: [],
|
|
72
|
+
name: 'PERMIT_TYPEHASH',
|
|
73
|
+
outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
|
|
74
|
+
payable: false,
|
|
75
|
+
stateMutability: 'view',
|
|
76
|
+
type: 'function',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
constant: true,
|
|
80
|
+
inputs: [
|
|
81
|
+
{ internalType: 'address', name: '', type: 'address' },
|
|
82
|
+
{
|
|
83
|
+
internalType: 'address',
|
|
84
|
+
name: '',
|
|
85
|
+
type: 'address',
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
name: 'allowance',
|
|
89
|
+
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
|
90
|
+
payable: false,
|
|
91
|
+
stateMutability: 'view',
|
|
92
|
+
type: 'function',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
constant: false,
|
|
96
|
+
inputs: [
|
|
97
|
+
{ internalType: 'address', name: 'usr', type: 'address' },
|
|
98
|
+
{
|
|
99
|
+
internalType: 'uint256',
|
|
100
|
+
name: 'wad',
|
|
101
|
+
type: 'uint256',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
name: 'approve',
|
|
105
|
+
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
|
|
106
|
+
payable: false,
|
|
107
|
+
stateMutability: 'nonpayable',
|
|
108
|
+
type: 'function',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
constant: true,
|
|
112
|
+
inputs: [{ internalType: 'address', name: '', type: 'address' }],
|
|
113
|
+
name: 'balanceOf',
|
|
114
|
+
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
|
115
|
+
payable: false,
|
|
116
|
+
stateMutability: 'view',
|
|
117
|
+
type: 'function',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
constant: false,
|
|
121
|
+
inputs: [
|
|
122
|
+
{ internalType: 'address', name: 'usr', type: 'address' },
|
|
123
|
+
{
|
|
124
|
+
internalType: 'uint256',
|
|
125
|
+
name: 'wad',
|
|
126
|
+
type: 'uint256',
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
name: 'burn',
|
|
130
|
+
outputs: [],
|
|
131
|
+
payable: false,
|
|
132
|
+
stateMutability: 'nonpayable',
|
|
133
|
+
type: 'function',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
constant: true,
|
|
137
|
+
inputs: [],
|
|
138
|
+
name: 'decimals',
|
|
139
|
+
outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }],
|
|
140
|
+
payable: false,
|
|
141
|
+
stateMutability: 'view',
|
|
142
|
+
type: 'function',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
constant: false,
|
|
146
|
+
inputs: [{ internalType: 'address', name: 'guy', type: 'address' }],
|
|
147
|
+
name: 'deny',
|
|
148
|
+
outputs: [],
|
|
149
|
+
payable: false,
|
|
150
|
+
stateMutability: 'nonpayable',
|
|
151
|
+
type: 'function',
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
constant: false,
|
|
155
|
+
inputs: [
|
|
156
|
+
{ internalType: 'address', name: 'usr', type: 'address' },
|
|
157
|
+
{
|
|
158
|
+
internalType: 'uint256',
|
|
159
|
+
name: 'wad',
|
|
160
|
+
type: 'uint256',
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
name: 'mint',
|
|
164
|
+
outputs: [],
|
|
165
|
+
payable: false,
|
|
166
|
+
stateMutability: 'nonpayable',
|
|
167
|
+
type: 'function',
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
constant: false,
|
|
171
|
+
inputs: [
|
|
172
|
+
{ internalType: 'address', name: 'src', type: 'address' },
|
|
173
|
+
{
|
|
174
|
+
internalType: 'address',
|
|
175
|
+
name: 'dst',
|
|
176
|
+
type: 'address',
|
|
177
|
+
},
|
|
178
|
+
{ internalType: 'uint256', name: 'wad', type: 'uint256' },
|
|
179
|
+
],
|
|
180
|
+
name: 'move',
|
|
181
|
+
outputs: [],
|
|
182
|
+
payable: false,
|
|
183
|
+
stateMutability: 'nonpayable',
|
|
184
|
+
type: 'function',
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
constant: true,
|
|
188
|
+
inputs: [],
|
|
189
|
+
name: 'name',
|
|
190
|
+
outputs: [{ internalType: 'string', name: '', type: 'string' }],
|
|
191
|
+
payable: false,
|
|
192
|
+
stateMutability: 'view',
|
|
193
|
+
type: 'function',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
constant: true,
|
|
197
|
+
inputs: [{ internalType: 'address', name: '', type: 'address' }],
|
|
198
|
+
name: 'nonces',
|
|
199
|
+
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
|
200
|
+
payable: false,
|
|
201
|
+
stateMutability: 'view',
|
|
202
|
+
type: 'function',
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
constant: false,
|
|
206
|
+
inputs: [
|
|
207
|
+
{ internalType: 'address', name: 'holder', type: 'address' },
|
|
208
|
+
{
|
|
209
|
+
internalType: 'address',
|
|
210
|
+
name: 'spender',
|
|
211
|
+
type: 'address',
|
|
212
|
+
},
|
|
213
|
+
{ internalType: 'uint256', name: 'nonce', type: 'uint256' },
|
|
214
|
+
{
|
|
215
|
+
internalType: 'uint256',
|
|
216
|
+
name: 'expiry',
|
|
217
|
+
type: 'uint256',
|
|
218
|
+
},
|
|
219
|
+
{ internalType: 'bool', name: 'allowed', type: 'bool' },
|
|
220
|
+
{
|
|
221
|
+
internalType: 'uint8',
|
|
222
|
+
name: 'v',
|
|
223
|
+
type: 'uint8',
|
|
224
|
+
},
|
|
225
|
+
{ internalType: 'bytes32', name: 'r', type: 'bytes32' },
|
|
226
|
+
{
|
|
227
|
+
internalType: 'bytes32',
|
|
228
|
+
name: 's',
|
|
229
|
+
type: 'bytes32',
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
name: 'permit',
|
|
233
|
+
outputs: [],
|
|
234
|
+
payable: false,
|
|
235
|
+
stateMutability: 'nonpayable',
|
|
236
|
+
type: 'function',
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
constant: false,
|
|
240
|
+
inputs: [
|
|
241
|
+
{ internalType: 'address', name: 'usr', type: 'address' },
|
|
242
|
+
{
|
|
243
|
+
internalType: 'uint256',
|
|
244
|
+
name: 'wad',
|
|
245
|
+
type: 'uint256',
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
name: 'pull',
|
|
249
|
+
outputs: [],
|
|
250
|
+
payable: false,
|
|
251
|
+
stateMutability: 'nonpayable',
|
|
252
|
+
type: 'function',
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
constant: false,
|
|
256
|
+
inputs: [
|
|
257
|
+
{ internalType: 'address', name: 'usr', type: 'address' },
|
|
258
|
+
{
|
|
259
|
+
internalType: 'uint256',
|
|
260
|
+
name: 'wad',
|
|
261
|
+
type: 'uint256',
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
name: 'push',
|
|
265
|
+
outputs: [],
|
|
266
|
+
payable: false,
|
|
267
|
+
stateMutability: 'nonpayable',
|
|
268
|
+
type: 'function',
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
constant: false,
|
|
272
|
+
inputs: [{ internalType: 'address', name: 'guy', type: 'address' }],
|
|
273
|
+
name: 'rely',
|
|
274
|
+
outputs: [],
|
|
275
|
+
payable: false,
|
|
276
|
+
stateMutability: 'nonpayable',
|
|
277
|
+
type: 'function',
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
constant: true,
|
|
281
|
+
inputs: [],
|
|
282
|
+
name: 'symbol',
|
|
283
|
+
outputs: [{ internalType: 'string', name: '', type: 'string' }],
|
|
284
|
+
payable: false,
|
|
285
|
+
stateMutability: 'view',
|
|
286
|
+
type: 'function',
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
constant: true,
|
|
290
|
+
inputs: [],
|
|
291
|
+
name: 'totalSupply',
|
|
292
|
+
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
|
293
|
+
payable: false,
|
|
294
|
+
stateMutability: 'view',
|
|
295
|
+
type: 'function',
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
constant: false,
|
|
299
|
+
inputs: [
|
|
300
|
+
{ internalType: 'address', name: 'dst', type: 'address' },
|
|
301
|
+
{
|
|
302
|
+
internalType: 'uint256',
|
|
303
|
+
name: 'wad',
|
|
304
|
+
type: 'uint256',
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
name: 'transfer',
|
|
308
|
+
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
|
|
309
|
+
payable: false,
|
|
310
|
+
stateMutability: 'nonpayable',
|
|
311
|
+
type: 'function',
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
constant: false,
|
|
315
|
+
inputs: [
|
|
316
|
+
{ internalType: 'address', name: 'src', type: 'address' },
|
|
317
|
+
{
|
|
318
|
+
internalType: 'address',
|
|
319
|
+
name: 'dst',
|
|
320
|
+
type: 'address',
|
|
321
|
+
},
|
|
322
|
+
{ internalType: 'uint256', name: 'wad', type: 'uint256' },
|
|
323
|
+
],
|
|
324
|
+
name: 'transferFrom',
|
|
325
|
+
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
|
|
326
|
+
payable: false,
|
|
327
|
+
stateMutability: 'nonpayable',
|
|
328
|
+
type: 'function',
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
constant: true,
|
|
332
|
+
inputs: [],
|
|
333
|
+
name: 'version',
|
|
334
|
+
outputs: [{ internalType: 'string', name: '', type: 'string' }],
|
|
335
|
+
payable: false,
|
|
336
|
+
stateMutability: 'view',
|
|
337
|
+
type: 'function',
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
constant: true,
|
|
341
|
+
inputs: [{ internalType: 'address', name: '', type: 'address' }],
|
|
342
|
+
name: 'wards',
|
|
343
|
+
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
|
|
344
|
+
payable: false,
|
|
345
|
+
stateMutability: 'view',
|
|
346
|
+
type: 'function',
|
|
347
|
+
},
|
|
348
|
+
]
|
package/src/abi/index.js
ADDED
package/src/constants.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CHAIN_IDS = { ethereum: 1, ethereumclassic: 61 }
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import ethUtil from 'ethereumjs-util'
|
|
2
|
+
|
|
3
|
+
// TODO: migrate to using solidity-contract
|
|
4
|
+
export default function createContract(addresses, currency) {
|
|
5
|
+
// First 4 bytes of Keccak256('transfer(address,uint256)')
|
|
6
|
+
// const TRANSFER_METHOD_ID = ethUtil.bufferToHex(ethUtil.sha3('transfer(address,uint256)').slice(0, 4))
|
|
7
|
+
const TRANSFER_METHOD_ID = '0xa9059cbb'
|
|
8
|
+
|
|
9
|
+
// Keccak256('Transfer(address,address,uint256)')
|
|
10
|
+
// const TRANSFER_EVENT_ID = ethUtil.bufferToHex(ethUtil.sha3('Transfer(address,address,uint256)'))
|
|
11
|
+
const TRANSFER_EVENT_ID = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
|
|
12
|
+
|
|
13
|
+
const transfer = {
|
|
14
|
+
METHOD_ID: TRANSFER_METHOD_ID,
|
|
15
|
+
EVENT_ID: TRANSFER_EVENT_ID,
|
|
16
|
+
|
|
17
|
+
build(to, bufAmount) {
|
|
18
|
+
return Buffer.concat([
|
|
19
|
+
ethUtil.toBuffer(TRANSFER_METHOD_ID),
|
|
20
|
+
ethUtil.zeros(12),
|
|
21
|
+
ethUtil.toBuffer(to),
|
|
22
|
+
ethUtil.zeros(32 - bufAmount.length),
|
|
23
|
+
bufAmount,
|
|
24
|
+
])
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
test(data) {
|
|
28
|
+
return ethUtil.bufferToHex(data.slice(0, 4)) === TRANSFER_METHOD_ID
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
parse(data) {
|
|
32
|
+
if (!transfer.test(data)) throw new Error('It is not transfer data')
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
to: ethUtil.bufferToHex(data.slice(16, 36)),
|
|
36
|
+
amount: currency.baseUnit(ethUtil.bufferToHex(data.slice(36, 68))),
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ethUtil.bufferToHex(ethUtil.sha3('approve(address,uint256)').slice(0, 4))
|
|
42
|
+
const APPROVE_METHOD_ID = '0x095ea7b3'
|
|
43
|
+
|
|
44
|
+
// TODO: not verified:
|
|
45
|
+
// ethUtil.bufferToHex(ethUtil.sha3('Approve(address,address,uint256)'))
|
|
46
|
+
const APPROVE_EVENT_ID = '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925'
|
|
47
|
+
|
|
48
|
+
const approve = {
|
|
49
|
+
METHOD_ID: APPROVE_METHOD_ID,
|
|
50
|
+
EVENT_ID: APPROVE_EVENT_ID,
|
|
51
|
+
|
|
52
|
+
build(address, bufAmount) {
|
|
53
|
+
return Buffer.concat([
|
|
54
|
+
ethUtil.toBuffer(APPROVE_METHOD_ID),
|
|
55
|
+
ethUtil.zeros(12),
|
|
56
|
+
ethUtil.toBuffer(address),
|
|
57
|
+
ethUtil.zeros(32 - bufAmount.length),
|
|
58
|
+
bufAmount,
|
|
59
|
+
])
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
test(data) {
|
|
63
|
+
return ethUtil.bufferToHex(data.slice(0, 4)) === APPROVE_METHOD_ID
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
parse(data) {
|
|
67
|
+
if (!approve.test(data)) throw new Error('It is not approve data')
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
address: ethUtil.bufferToHex(data.slice(16, 36)),
|
|
71
|
+
amount: currency.baseUnit(ethUtil.bufferToHex(data.slice(36, 68))),
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { addresses, transfer, approve }
|
|
77
|
+
}
|
package/src/encode.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import etherUtil from 'ethereumjs-util'
|
|
2
|
+
|
|
3
|
+
export function validate(address) {
|
|
4
|
+
if (typeof address !== 'string') return false
|
|
5
|
+
|
|
6
|
+
// https://github.com/ethereumjs/ethereumjs-util/issues/54
|
|
7
|
+
if (address.slice(0, 2) !== '0x' || address.length !== 42) return false
|
|
8
|
+
|
|
9
|
+
if (!hasChecksum(address)) return true
|
|
10
|
+
return etherUtil.isValidChecksumAddress(address)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function hasChecksum(address) {
|
|
14
|
+
if (/^0x[0-9A-F]{40}$/.test(address) || /^0x[0-9a-f]{40}$/.test(address)) return false
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function encodePrivate(privKey) {
|
|
19
|
+
return '0x' + privKey.toString('hex')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function encodePublic(compressedPubKey) {
|
|
23
|
+
const hash160bits = etherUtil.publicToAddress(compressedPubKey, true)
|
|
24
|
+
return etherUtil.toChecksumAddress(hash160bits.toString('hex'))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isValidPrivate(privateKey) {
|
|
28
|
+
if (privateKey.length !== 32 || !etherUtil.isValidPrivate(privateKey)) {
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
return true
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function encodePublicFromPrivate(privateKey) {
|
|
35
|
+
return etherUtil.toChecksumAddress(etherUtil.privateToAddress(privateKey).toString('hex'))
|
|
36
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
import createUnsignedTx from './create-unsigned-tx'
|
|
3
|
+
import signUnsignedTx from './sign-unsigned-tx'
|
|
4
|
+
|
|
5
|
+
import type { ParsedTransaction, SignedTransaction } from '@exodus/models/lib/types'
|
|
6
|
+
|
|
7
|
+
export default function createAndSignTx(
|
|
8
|
+
input: ParsedTransaction,
|
|
9
|
+
privateKey: Buffer
|
|
10
|
+
): SignedTransaction {
|
|
11
|
+
const unsignedTx = createUnsignedTx(input)
|
|
12
|
+
return signUnsignedTx(unsignedTx, privateKey)
|
|
13
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import ethUtil from 'ethereumjs-util'
|
|
2
|
+
import { _isEthereumToken, currency2buffer } from '../utils'
|
|
3
|
+
import { CHAIN_IDS } from '../constants'
|
|
4
|
+
|
|
5
|
+
import type { UnsignedTransaction } from '#/app-models'
|
|
6
|
+
|
|
7
|
+
export default function createUnsignedTx({
|
|
8
|
+
baseAsset,
|
|
9
|
+
asset,
|
|
10
|
+
address,
|
|
11
|
+
amount,
|
|
12
|
+
nonce,
|
|
13
|
+
txInput,
|
|
14
|
+
gasLimit,
|
|
15
|
+
gasPrice,
|
|
16
|
+
privateKey,
|
|
17
|
+
fromAddress,
|
|
18
|
+
chainId,
|
|
19
|
+
}): UnsignedTransaction {
|
|
20
|
+
if (txInput) {
|
|
21
|
+
txInput = ethUtil.toBuffer(txInput) // If txInput is already a Buffer, then it is passed through
|
|
22
|
+
} else {
|
|
23
|
+
txInput = Buffer.alloc(0)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!chainId) chainId = CHAIN_IDS[baseAsset.name] // mainnet
|
|
27
|
+
|
|
28
|
+
const isEthereumToken = _isEthereumToken(asset)
|
|
29
|
+
const to = isEthereumToken ? asset.contract.addresses.current : address
|
|
30
|
+
let value = currency2buffer(isEthereumToken ? baseAsset.currency.ZERO : amount)
|
|
31
|
+
|
|
32
|
+
// TODO: check: present on desktop missing on mobile. This insures we never have a buffer specifying 0.
|
|
33
|
+
// In ETH, a buffer of zero length and a buffer specifying 0 imply different things
|
|
34
|
+
if (value.equals(Buffer.from([0]))) value = Buffer.alloc(0)
|
|
35
|
+
|
|
36
|
+
if (!fromAddress) {
|
|
37
|
+
if (privateKey) fromAddress = ethUtil.bufferToHex(ethUtil.privateToAddress(privateKey))
|
|
38
|
+
else throw new Error('createTx: Must pass fromAddress or privateKey in options')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const txData = {
|
|
42
|
+
nonce: nonce === 0 ? Buffer.alloc(0) : ethUtil.intToBuffer(nonce), // mobile: { nonce: ethUtil.intToBuffer(nonce) }
|
|
43
|
+
gasPrice: currency2buffer(gasPrice.toBase()), // mobile: { gasPrice: currency2buffer(gasPrice) }
|
|
44
|
+
gasLimit: ethUtil.intToBuffer(gasLimit),
|
|
45
|
+
to,
|
|
46
|
+
value,
|
|
47
|
+
data: txInput,
|
|
48
|
+
chainId,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const txMeta = {
|
|
52
|
+
assetName: asset.name,
|
|
53
|
+
// TODO: We should move away from putting the PK into txMeta, it is a bad practice. It is a potential PK exposure risk if the wrong PK gets included here.
|
|
54
|
+
privateKey, // Used for importing private key
|
|
55
|
+
fee: baseAsset.currency.baseUnit(gasPrice.toBase().toNumber() * gasLimit),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
txData,
|
|
60
|
+
txMeta,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
import * as ethUtil from 'ethereumjs-util'
|
|
3
|
+
import assets from '@exodus/assets'
|
|
4
|
+
import { _isEthereumToken, buffer2currency } from '../utils'
|
|
5
|
+
import { CHAIN_IDS } from '../constants'
|
|
6
|
+
|
|
7
|
+
import type { UnsignedTransaction, ParsedTransaction } from '@exodus/models/lib/types'
|
|
8
|
+
|
|
9
|
+
export default function parseUnsignedTx(unsignedTx: UnsignedTransaction): ParsedTransaction {
|
|
10
|
+
const {
|
|
11
|
+
txData,
|
|
12
|
+
txMeta: { assetName },
|
|
13
|
+
} = unsignedTx
|
|
14
|
+
const asset = assets[assetName]
|
|
15
|
+
const isEthereumToken = _isEthereumToken(asset)
|
|
16
|
+
const baseAsset = isEthereumToken ? assets.ethereum : asset
|
|
17
|
+
|
|
18
|
+
const gasPrice = buffer2currency({ asset: baseAsset, value: txData.gasPrice })
|
|
19
|
+
const gasLimit = ethUtil.bufferToInt(txData.gasLimit)
|
|
20
|
+
|
|
21
|
+
let { to, chainId, data } = txData
|
|
22
|
+
let amount
|
|
23
|
+
const fee = gasPrice.mul(gasLimit)
|
|
24
|
+
if (isEthereumToken) {
|
|
25
|
+
const parsed = asset.contract.transfer.parse(data)
|
|
26
|
+
to = parsed.to
|
|
27
|
+
amount = parsed.amount
|
|
28
|
+
} else {
|
|
29
|
+
amount = buffer2currency({ asset: baseAsset, value: txData.value })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (chainId !== CHAIN_IDS[baseAsset.name]) {
|
|
33
|
+
throw new Error(`invalid chainId ${chainId} for asset ${assetName}`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
baseAsset,
|
|
38
|
+
asset,
|
|
39
|
+
from: null, // TODO: how?
|
|
40
|
+
to,
|
|
41
|
+
amount,
|
|
42
|
+
fee,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
import EthereumTx from 'ethereumjs-tx'
|
|
3
|
+
|
|
4
|
+
import type { UnsignedTransaction, SignedTransaction } from '@exodus/models/lib/types'
|
|
5
|
+
|
|
6
|
+
export default function signUnsignedTx(
|
|
7
|
+
unsignedTx: UnsignedTransaction,
|
|
8
|
+
privateKey: Buffer
|
|
9
|
+
): SignedTransaction {
|
|
10
|
+
const { txData } = unsignedTx
|
|
11
|
+
|
|
12
|
+
const tx = new EthereumTx(txData)
|
|
13
|
+
tx.sign(privateKey)
|
|
14
|
+
|
|
15
|
+
// serialize and get txId
|
|
16
|
+
const rawTx = tx.serialize()
|
|
17
|
+
const txId = tx.hash().toString('hex')
|
|
18
|
+
return { rawTx, txId }
|
|
19
|
+
}
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import baseX from 'base-x'
|
|
2
|
+
import * as ethUtil from 'ethereumjs-util'
|
|
3
|
+
|
|
4
|
+
const base10 = baseX('0123456789')
|
|
5
|
+
const base16 = baseX('0123456789abcdef')
|
|
6
|
+
|
|
7
|
+
export const _isEthereumToken = (asset) => asset.assetType === 'ETHEREUM_ERC20'
|
|
8
|
+
|
|
9
|
+
export function buffer2currency({ asset, value }) {
|
|
10
|
+
return asset.currency.baseUnit(base10.encode(value)).toDefault()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function currency2buffer(value) {
|
|
14
|
+
const b10Value = value
|
|
15
|
+
.toBase()
|
|
16
|
+
.floor() // probably not necessary, but to be safe
|
|
17
|
+
.toString({ unit: false })
|
|
18
|
+
|
|
19
|
+
// Desktop: const hexValue = value.toBase()._number.toString(16)
|
|
20
|
+
const hexValue = base16.encode(base10.decode(b10Value))
|
|
21
|
+
|
|
22
|
+
if (hexValue.includes('.')) {
|
|
23
|
+
// <-- probably not necessary anymore
|
|
24
|
+
throw new RangeError(`${value.toBase().toString()} can not be converted to Buffer`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return Buffer.from(ethUtil.padToEven(hexValue), 'hex')
|
|
28
|
+
}
|