@tetherto/wdk-wallet-evm 1.0.0-beta.2
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/.editorconfig +11 -0
- package/LICENSE +176 -0
- package/README.md +420 -0
- package/bare.js +20 -0
- package/hardhat.config.cjs +16 -0
- package/index.js +31 -0
- package/package.json +67 -0
- package/src/memory-safe/hd-node-wallet.js +196 -0
- package/src/memory-safe/signing-key.js +77 -0
- package/src/wallet-account-evm.js +200 -0
- package/src/wallet-account-read-only-evm.js +191 -0
- package/src/wallet-manager-evm.js +127 -0
- package/types/index.d.ts +11 -0
- package/types/src/wallet-account-evm.d.ts +91 -0
- package/types/src/wallet-account-read-only-evm.d.ts +112 -0
- package/types/src/wallet-manager-evm.d.ts +68 -0
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tetherto/wdk-wallet-evm",
|
|
3
|
+
"version": "1.0.0-beta.2",
|
|
4
|
+
"description": "A simple package to manage BIP-32 wallets for evm blockchains.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"wdk",
|
|
7
|
+
"wallet",
|
|
8
|
+
"bip-32",
|
|
9
|
+
"ethereum"
|
|
10
|
+
],
|
|
11
|
+
"author": "Tether",
|
|
12
|
+
"license": "Apache-2.0",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git://github.com/tetherto/wdk-wallet-evm.git"
|
|
16
|
+
},
|
|
17
|
+
"main": "index.js",
|
|
18
|
+
"type": "module",
|
|
19
|
+
"types": "./types",
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build:types": "tsc",
|
|
22
|
+
"lint": "standard",
|
|
23
|
+
"lint:fix": "standard --fix",
|
|
24
|
+
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
|
|
25
|
+
"test:coverage": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage",
|
|
26
|
+
"test:integration": "cross-env NODE_OPTIONS=--experimental-vm-modules jest tests/integration",
|
|
27
|
+
"test:integration:coverage": "cross-env NODE_OPTIONS=--experimental-vm-modules jest tests/integration --coverage"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@noble/curves": "1.9.2",
|
|
31
|
+
"@noble/hashes": "1.8.0",
|
|
32
|
+
"@noble/secp256k1": "2.2.3",
|
|
33
|
+
"@tetherto/wdk-wallet": "^1.0.0-beta.1",
|
|
34
|
+
"bare-wdk-runtime": "2.0.0",
|
|
35
|
+
"bip39": "3.1.0",
|
|
36
|
+
"ethers": "6.14.3",
|
|
37
|
+
"sodium-universal": "5.0.1"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@nomicfoundation/hardhat-ethers": "3.0.9",
|
|
41
|
+
"cross-env": "7.0.3",
|
|
42
|
+
"hardhat": "2.24.2",
|
|
43
|
+
"jest": "29.7.0",
|
|
44
|
+
"standard": "17.1.2",
|
|
45
|
+
"typescript": "5.8.3"
|
|
46
|
+
},
|
|
47
|
+
"exports": {
|
|
48
|
+
".": {
|
|
49
|
+
"types": "./types/index.d.ts",
|
|
50
|
+
"bare": "./bare.js",
|
|
51
|
+
"default": "./index.js"
|
|
52
|
+
},
|
|
53
|
+
"./package": {
|
|
54
|
+
"default": "./package.json"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"publishConfig": {
|
|
58
|
+
"access": "restricted",
|
|
59
|
+
"registry": "https://registry.npmjs.org/"
|
|
60
|
+
},
|
|
61
|
+
"standard": {
|
|
62
|
+
"ignore": [
|
|
63
|
+
"bare.js",
|
|
64
|
+
"tests/**/*.js"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// Copyright 2024 Tether Operations Limited
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
'use strict'
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
assert, assertArgument, assertPrivate, BaseWallet, computeHmac, dataSlice, defineProperties,
|
|
19
|
+
getBytes, getNumber, hexlify, isBytesLike, ripemd160, sha256
|
|
20
|
+
} from 'ethers'
|
|
21
|
+
|
|
22
|
+
import * as secp256k1 from '@noble/secp256k1'
|
|
23
|
+
|
|
24
|
+
import MemorySafeSigningKey from './signing-key.js'
|
|
25
|
+
|
|
26
|
+
const MasterSecret = new Uint8Array([66, 105, 116, 99, 111, 105, 110, 32, 115, 101, 101, 100])
|
|
27
|
+
|
|
28
|
+
const HardenedBit = 0x80000000
|
|
29
|
+
|
|
30
|
+
const _guard = { }
|
|
31
|
+
|
|
32
|
+
function serI (index, chainCode, publicKey, privateKeyBuffer) {
|
|
33
|
+
const data = new Uint8Array(37)
|
|
34
|
+
|
|
35
|
+
if (index & HardenedBit) {
|
|
36
|
+
assert(privateKeyBuffer != null, 'cannot derive child of neutered node', 'UNSUPPORTED_OPERATION', {
|
|
37
|
+
operation: 'deriveChild'
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
data.set(getBytes(privateKeyBuffer), 1)
|
|
41
|
+
} else {
|
|
42
|
+
data.set(getBytes(publicKey))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (let i = 24; i >= 0; i -= 8) { data[33 + (i >> 3)] = ((index >> (24 - i)) & 0xff) }
|
|
46
|
+
const I = getBytes(computeHmac('sha512', chainCode, data))
|
|
47
|
+
|
|
48
|
+
return { IL: I.slice(0, 32), IR: I.slice(32) }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function derivePath (node, path) {
|
|
52
|
+
const components = path.split('/')
|
|
53
|
+
|
|
54
|
+
assertArgument(components.length > 0, 'invalid path', 'path', path)
|
|
55
|
+
|
|
56
|
+
if (components[0] === 'm') {
|
|
57
|
+
assertArgument(node.depth === 0, `cannot derive root path (i.e. path starting with "m/") for a node at non-zero depth ${node.depth}`, 'path', path)
|
|
58
|
+
components.shift()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let result = node
|
|
62
|
+
for (let i = 0; i < components.length; i++) {
|
|
63
|
+
const component = components[i]
|
|
64
|
+
|
|
65
|
+
if (component.match(/^[0-9]+'$/)) {
|
|
66
|
+
const index = parseInt(component.substring(0, component.length - 1))
|
|
67
|
+
assertArgument(index < HardenedBit, 'invalid path index', `path[${i}]`, component)
|
|
68
|
+
result = result.deriveChild(HardenedBit + index)
|
|
69
|
+
} else if (component.match(/^[0-9]+$/)) {
|
|
70
|
+
const index = parseInt(component)
|
|
71
|
+
assertArgument(index < HardenedBit, 'invalid path index', `path[${i}]`, component)
|
|
72
|
+
result = result.deriveChild(index)
|
|
73
|
+
} else {
|
|
74
|
+
assertArgument(false, 'invalid path component', `path[${i}]`, component)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function addToPrivateKey (privateKey, x) {
|
|
82
|
+
let carry = 0
|
|
83
|
+
|
|
84
|
+
for (let i = 31; i >= 0; i--) {
|
|
85
|
+
const sum = privateKey[i] + x[i] + carry
|
|
86
|
+
privateKey[i] = sum & 0xff
|
|
87
|
+
carry = sum >> 8
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return carry > 0
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function subtractCurveOrderFromPrivateKey (privateKey) {
|
|
94
|
+
let carry = 0
|
|
95
|
+
|
|
96
|
+
for (let i = 31; i >= 0; i--) {
|
|
97
|
+
const curveOrderByte = Number((secp256k1.CURVE.n >> BigInt(8 * (31 - i))) & 0xffn)
|
|
98
|
+
const diff = privateKey[i] - curveOrderByte - carry
|
|
99
|
+
privateKey[i] = diff < 0 ? diff + 256 : diff
|
|
100
|
+
carry = diff < 0 ? 1 : 0
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function compareWithCurveOrder (buffer, offset = 0) {
|
|
105
|
+
for (let i = 0; i < 32; i++) {
|
|
106
|
+
const curveOrderByte = Number((secp256k1.CURVE.n >> BigInt(8 * (31 - i))) & 0xffn)
|
|
107
|
+
if (buffer[offset + i] > curveOrderByte) return 1
|
|
108
|
+
if (buffer[offset + i] < curveOrderByte) return -1
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return 0
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** @internal */
|
|
115
|
+
export default class MemorySafeHDNodeWallet extends BaseWallet {
|
|
116
|
+
constructor (guard, signingKey, parentFingerprint, chainCode, path, index, depth, mnemonic, provider) {
|
|
117
|
+
super(signingKey, provider)
|
|
118
|
+
assertPrivate(guard, _guard, 'MemorySafeHDNodeWallet')
|
|
119
|
+
|
|
120
|
+
defineProperties(this, { publicKey: signingKey.compressedPublicKey })
|
|
121
|
+
|
|
122
|
+
const fingerprint = dataSlice(ripemd160(sha256(this.publicKey)), 0, 4)
|
|
123
|
+
defineProperties(this, {
|
|
124
|
+
parentFingerprint,
|
|
125
|
+
fingerprint,
|
|
126
|
+
chainCode,
|
|
127
|
+
path,
|
|
128
|
+
index,
|
|
129
|
+
depth
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
defineProperties(this, { mnemonic })
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
connect (provider) {
|
|
136
|
+
return new MemorySafeHDNodeWallet(_guard, this.signingKey, this.parentFingerprint,
|
|
137
|
+
this.chainCode, this.path, this.index, this.depth, this.mnemonic, provider)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
get privateKeyBuffer () {
|
|
141
|
+
return this.signingKey.privateKeyBuffer
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
get publicKeyBuffer () {
|
|
145
|
+
return this.signingKey.publicKeyBuffer
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
deriveChild (_index) {
|
|
149
|
+
const index = getNumber(_index, 'index')
|
|
150
|
+
assertArgument(index <= 0xffffffff, 'invalid index', 'index', index)
|
|
151
|
+
|
|
152
|
+
let path = this.path
|
|
153
|
+
if (path) {
|
|
154
|
+
path += '/' + (index & ~HardenedBit)
|
|
155
|
+
if (index & HardenedBit) { path += "'" }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const { IR, IL } = serI(index, this.chainCode, this.publicKey, this.privateKeyBuffer)
|
|
159
|
+
|
|
160
|
+
const overflow = addToPrivateKey(this.privateKeyBuffer, IL)
|
|
161
|
+
|
|
162
|
+
if (overflow || compareWithCurveOrder(this.privateKeyBuffer) >= 0) {
|
|
163
|
+
subtractCurveOrderFromPrivateKey(this.privateKeyBuffer)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const ki = new MemorySafeSigningKey(this.privateKeyBuffer)
|
|
167
|
+
|
|
168
|
+
return new MemorySafeHDNodeWallet(_guard, ki, this.fingerprint, hexlify(IR),
|
|
169
|
+
path, index, this.depth + 1, this.mnemonic, this.provider)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
derivePath (path) {
|
|
173
|
+
return derivePath(this, path)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
dispose () {
|
|
177
|
+
this.signingKey.dispose()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
static fromSeed (seed) {
|
|
181
|
+
return MemorySafeHDNodeWallet._fromSeed(seed, null)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
static _fromSeed (_seed, mnemonic) {
|
|
185
|
+
assertArgument(isBytesLike(_seed), 'invalid seed', 'seed', '[REDACTED]')
|
|
186
|
+
|
|
187
|
+
const seed = getBytes(_seed, 'seed')
|
|
188
|
+
assertArgument(seed.length >= 16 && seed.length <= 64, 'invalid seed', 'seed', '[REDACTED]')
|
|
189
|
+
|
|
190
|
+
const I = getBytes(computeHmac('sha512', MasterSecret, seed))
|
|
191
|
+
const signingKey = new MemorySafeSigningKey(I.slice(0, 32))
|
|
192
|
+
|
|
193
|
+
return new MemorySafeHDNodeWallet(_guard, signingKey, '0x00000000', hexlify(I.slice(32)),
|
|
194
|
+
'm', 0, 0, mnemonic, null)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Copyright 2024 Tether Operations Limited
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
'use strict'
|
|
16
|
+
|
|
17
|
+
import { hmac } from '@noble/hashes/hmac'
|
|
18
|
+
import { sha256 } from '@noble/hashes/sha256'
|
|
19
|
+
import * as secp256k1 from '@noble/secp256k1'
|
|
20
|
+
|
|
21
|
+
import { assertArgument, dataLength, getBytesCopy, Signature, SigningKey, toBeHex } from 'ethers'
|
|
22
|
+
|
|
23
|
+
// eslint-disable-next-line camelcase
|
|
24
|
+
import { sodium_memzero } from 'sodium-universal'
|
|
25
|
+
|
|
26
|
+
const NULL = '0x0000000000000000000000000000000000000000000000000000000000000000'
|
|
27
|
+
|
|
28
|
+
secp256k1.etc.hmacSha256Sync = (key, ...messages) => {
|
|
29
|
+
return hmac(sha256, key, secp256k1.etc.concatBytes(...messages))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** @internal */
|
|
33
|
+
export default class MemorySafeSigningKey extends SigningKey {
|
|
34
|
+
constructor (privateKeyBuffer) {
|
|
35
|
+
super(NULL)
|
|
36
|
+
|
|
37
|
+
this._privateKeyBuffer = privateKeyBuffer
|
|
38
|
+
|
|
39
|
+
this._publicKeyBuffer = secp256k1.getPublicKey(privateKeyBuffer, true)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get publicKey () {
|
|
43
|
+
return SigningKey.computePublicKey(this._privateKeyBuffer)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get compressedPublicKey () {
|
|
47
|
+
return SigningKey.computePublicKey(this._privateKeyBuffer, true)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get privateKeyBuffer () {
|
|
51
|
+
return this._privateKeyBuffer
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get publicKeyBuffer () {
|
|
55
|
+
return this._publicKeyBuffer
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
sign (digest) {
|
|
59
|
+
assertArgument(dataLength(digest) === 32, 'invalid digest length', 'digest', digest)
|
|
60
|
+
|
|
61
|
+
const sig = secp256k1.sign(getBytesCopy(digest), this._privateKeyBuffer, {
|
|
62
|
+
lowS: true
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
return Signature.from({
|
|
66
|
+
r: toBeHex(sig.r, 32),
|
|
67
|
+
s: toBeHex(sig.s, 32),
|
|
68
|
+
v: (sig.recovery ? 0x1c : 0x1b)
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
dispose () {
|
|
73
|
+
sodium_memzero(this._privateKeyBuffer)
|
|
74
|
+
|
|
75
|
+
this._privateKeyBuffer = undefined
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// Copyright 2024 Tether Operations Limited
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
'use strict'
|
|
16
|
+
|
|
17
|
+
import { verifyMessage } from 'ethers'
|
|
18
|
+
|
|
19
|
+
import * as bip39 from 'bip39'
|
|
20
|
+
|
|
21
|
+
import WalletAccountReadOnlyEvm from './wallet-account-read-only-evm.js'
|
|
22
|
+
|
|
23
|
+
import MemorySafeHDNodeWallet from './memory-safe/hd-node-wallet.js'
|
|
24
|
+
|
|
25
|
+
/** @typedef {import('ethers').HDNodeWallet} HDNodeWallet */
|
|
26
|
+
|
|
27
|
+
/** @typedef {import('@tetherto/wdk-wallet').IWalletAccount} IWalletAccount */
|
|
28
|
+
|
|
29
|
+
/** @typedef {import('@tetherto/wdk-wallet').KeyPair} KeyPair */
|
|
30
|
+
/** @typedef {import('@tetherto/wdk-wallet').TransactionResult} TransactionResult */
|
|
31
|
+
/** @typedef {import('@tetherto/wdk-wallet').TransferOptions} TransferOptions */
|
|
32
|
+
/** @typedef {import('@tetherto/wdk-wallet').TransferResult} TransferResult */
|
|
33
|
+
|
|
34
|
+
/** @typedef {import('./wallet-account-read-only-evm.js').EvmTransaction} EvmTransaction */
|
|
35
|
+
/** @typedef {import('./wallet-account-read-only-evm.js').EvmWalletConfig} EvmWalletConfig */
|
|
36
|
+
|
|
37
|
+
const BIP_44_ETH_DERIVATION_PATH_PREFIX = "m/44'/60'"
|
|
38
|
+
|
|
39
|
+
/** @implements {IWalletAccount} */
|
|
40
|
+
export default class WalletAccountEvm extends WalletAccountReadOnlyEvm {
|
|
41
|
+
/**
|
|
42
|
+
* Creates a new evm wallet account.
|
|
43
|
+
*
|
|
44
|
+
* @param {string | Uint8Array} seed - The wallet's [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) seed phrase.
|
|
45
|
+
* @param {string} path - The BIP-44 derivation path (e.g. "0'/0/0").
|
|
46
|
+
* @param {EvmWalletConfig} [config] - The configuration object.
|
|
47
|
+
*/
|
|
48
|
+
constructor (seed, path, config = {}) {
|
|
49
|
+
if (typeof seed === 'string') {
|
|
50
|
+
if (!bip39.validateMnemonic(seed)) {
|
|
51
|
+
throw new Error('The seed phrase is invalid.')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
seed = bip39.mnemonicToSeedSync(seed)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
path = BIP_44_ETH_DERIVATION_PATH_PREFIX + '/' + path
|
|
58
|
+
|
|
59
|
+
const account = MemorySafeHDNodeWallet.fromSeed(seed)
|
|
60
|
+
.derivePath(path)
|
|
61
|
+
|
|
62
|
+
super(account.address, config)
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The wallet account configuration.
|
|
66
|
+
*
|
|
67
|
+
* @protected
|
|
68
|
+
* @type {EvmWalletConfig}
|
|
69
|
+
*/
|
|
70
|
+
this._config = config
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The account.
|
|
74
|
+
*
|
|
75
|
+
* @protected
|
|
76
|
+
* @type {HDNodeWallet}
|
|
77
|
+
*/
|
|
78
|
+
this._account = account
|
|
79
|
+
|
|
80
|
+
if (this._provider) {
|
|
81
|
+
this._account = this._account.connect(this._provider)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The derivation path's index of this account.
|
|
87
|
+
*
|
|
88
|
+
* @type {number}
|
|
89
|
+
*/
|
|
90
|
+
get index () {
|
|
91
|
+
return this._account.index
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The derivation path of this account (see [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)).
|
|
96
|
+
*
|
|
97
|
+
* @type {string}
|
|
98
|
+
*/
|
|
99
|
+
get path () {
|
|
100
|
+
return this._account.path
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The account's key pair.
|
|
105
|
+
*
|
|
106
|
+
* @type {KeyPair}
|
|
107
|
+
*/
|
|
108
|
+
get keyPair () {
|
|
109
|
+
return {
|
|
110
|
+
privateKey: this._account.privateKeyBuffer,
|
|
111
|
+
publicKey: this._account.publicKeyBuffer
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Signs a message.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} message - The message to sign.
|
|
119
|
+
* @returns {Promise<string>} The message's signature.
|
|
120
|
+
*/
|
|
121
|
+
async sign (message) {
|
|
122
|
+
return await this._account.signMessage(message)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Verifies a message's signature.
|
|
127
|
+
*
|
|
128
|
+
* @param {string} message - The original message.
|
|
129
|
+
* @param {string} signature - The signature to verify.
|
|
130
|
+
* @returns {Promise<boolean>} True if the signature is valid.
|
|
131
|
+
*/
|
|
132
|
+
async verify (message, signature) {
|
|
133
|
+
const address = await verifyMessage(message, signature)
|
|
134
|
+
|
|
135
|
+
return address.toLowerCase() === this._account.address.toLowerCase()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Sends a transaction.
|
|
140
|
+
*
|
|
141
|
+
* @param {EvmTransaction} tx - The transaction.
|
|
142
|
+
* @returns {Promise<TransactionResult>} The transaction's result.
|
|
143
|
+
*/
|
|
144
|
+
async sendTransaction (tx) {
|
|
145
|
+
if (!this._account.provider) {
|
|
146
|
+
throw new Error('The wallet must be connected to a provider to send transactions.')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const { fee } = await this.quoteSendTransaction(tx)
|
|
150
|
+
|
|
151
|
+
const { hash } = await this._account.sendTransaction({
|
|
152
|
+
from: await this.getAddress(),
|
|
153
|
+
...tx
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
return { hash, fee }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Transfers a token to another address.
|
|
161
|
+
*
|
|
162
|
+
* @param {TransferOptions} options - The transfer's options.
|
|
163
|
+
* @returns {Promise<TransferResult>} The transfer's result.
|
|
164
|
+
*/
|
|
165
|
+
async transfer (options) {
|
|
166
|
+
if (!this._account.provider) {
|
|
167
|
+
throw new Error('The wallet must be connected to a provider to transfer tokens.')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const tx = await WalletAccountEvm._getTransferTransaction(options)
|
|
171
|
+
|
|
172
|
+
const { fee } = await this.quoteSendTransaction(tx)
|
|
173
|
+
|
|
174
|
+
if (this._config.transferMaxFee !== undefined && fee >= this._config.transferMaxFee) {
|
|
175
|
+
throw new Error('Exceeded maximum fee cost for transfer operation.')
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const { hash } = await this._account.sendTransaction(tx)
|
|
179
|
+
|
|
180
|
+
return { hash, fee }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Returns a read-only copy of the account.
|
|
185
|
+
*
|
|
186
|
+
* @returns {Promise<WalletAccountReadOnlyEvm>} The read-only account.
|
|
187
|
+
*/
|
|
188
|
+
async toReadOnlyAccount () {
|
|
189
|
+
const readOnlyAccount = new WalletAccountReadOnlyEvm(this._account.address, this._config)
|
|
190
|
+
|
|
191
|
+
return readOnlyAccount
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Disposes the wallet account, erasing the private key from the memory.
|
|
196
|
+
*/
|
|
197
|
+
dispose () {
|
|
198
|
+
this._account.dispose()
|
|
199
|
+
}
|
|
200
|
+
}
|