nexa-wallet-sdk 0.1.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/.parcel-cache/3e09f086f3c4d605-AssetGraph +0 -0
- package/.parcel-cache/5eac57ec674cdae8-AssetGraph +0 -0
- package/.parcel-cache/data.mdb +0 -0
- package/.parcel-cache/e43547b6c9167b58-RequestGraph +0 -0
- package/.parcel-cache/ecfe15d74834bbfd-BundleGraph +0 -0
- package/.parcel-cache/lock.mdb +0 -0
- package/.parcel-cache/snapshot-e43547b6c9167b58.txt +2 -0
- package/README.md +445 -0
- package/dist/browser/index.js +2456 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/index.d.ts +918 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2915 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2456 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +90 -0
- package/spec.md +257 -0
- package/src/index.ts +93 -0
- package/src/models/rostrum.entities.ts +159 -0
- package/src/models/transaction.entities.ts +46 -0
- package/src/models/wallet.entities.ts +42 -0
- package/src/network/RostrumProvider.ts +137 -0
- package/src/types.ts +0 -0
- package/src/utils/CommonUtils.ts +123 -0
- package/src/utils/TXUtils.ts +445 -0
- package/src/utils/TokenUtils.ts +75 -0
- package/src/utils/ValidationUtils.ts +86 -0
- package/src/utils/WalletUtils.ts +522 -0
- package/src/utils/WatchOnlyTXUtils.ts +275 -0
- package/src/wallet/Wallet.ts +397 -0
- package/src/wallet/WatchOnlyWallet.ts +169 -0
- package/src/wallet/accounts/AccountStore.ts +173 -0
- package/src/wallet/accounts/interfaces/BaseAccountInterface.ts +56 -0
- package/src/wallet/accounts/models/DappAccount.ts +80 -0
- package/src/wallet/accounts/models/DefaultAccount.ts +96 -0
- package/src/wallet/accounts/models/VaultAccount.ts +81 -0
- package/src/wallet/transactions/WalletTransactionCreator.ts +145 -0
- package/src/wallet/transactions/WatchOnlyTransactionCreator.ts +189 -0
- package/src/wallet/transactions/interfaces/TransactionCreator.ts +438 -0
- package/tests/core/tx/transactioncreator.test.ts +455 -0
- package/tests/core/tx/wallettransactioncreator.test.ts +362 -0
- package/tests/core/tx/watchonlytransactioncreator.test.ts +258 -0
- package/tests/core/wallet/accountstore.test.ts +341 -0
- package/tests/core/wallet/wallet.test.ts +69 -0
- package/tests/core/watchonlywallet/watchonlywallet.test.ts +251 -0
- package/tests/index.test.ts +12 -0
- package/tsconfig.json +113 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { rostrumProvider } from '../network/RostrumProvider';
|
|
2
|
+
import { WatchOnlyAddress } from '../models/wallet.entities';
|
|
3
|
+
import { MAX_INT64, isNullOrEmpty } from './CommonUtils';
|
|
4
|
+
import {
|
|
5
|
+
Address,
|
|
6
|
+
AddressType,
|
|
7
|
+
GroupToken,
|
|
8
|
+
Networkish,
|
|
9
|
+
Transaction,
|
|
10
|
+
TransactionBuilder,
|
|
11
|
+
UnitUtils,
|
|
12
|
+
UTXO
|
|
13
|
+
} from "libnexa-ts";
|
|
14
|
+
import { PermissionLabel, TxOptions } from "../models/transaction.entities";
|
|
15
|
+
import {dupAuthority, isAuthFit} from "./TokenUtils";
|
|
16
|
+
|
|
17
|
+
const MAX_INPUTS_OUTPUTS = 250;
|
|
18
|
+
|
|
19
|
+
export async function watchOnlyPopulateNexaInputsAndChange(txBuilder: TransactionBuilder, addresses: WatchOnlyAddress[], totalTxValue: bigint, options: TxOptions): Promise<string[]> {
|
|
20
|
+
if (isNullOrEmpty(addresses)) {
|
|
21
|
+
throw new Error("Not enough Nexa balance.");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let usedAddresses = new Set<string>();
|
|
25
|
+
let origAmount = options.isConsolidate ? 0 : Number(totalTxValue);
|
|
26
|
+
|
|
27
|
+
for (let item of addresses) {
|
|
28
|
+
let utxos = await rostrumProvider.getNexaUtxos(item.address);
|
|
29
|
+
for (let utxo of utxos) {
|
|
30
|
+
let input: UTXO = {
|
|
31
|
+
outpoint: utxo.outpoint_hash,
|
|
32
|
+
address: item.address,
|
|
33
|
+
satoshis: utxo.value,
|
|
34
|
+
templateData: options.templateData
|
|
35
|
+
}
|
|
36
|
+
txBuilder.from(input);
|
|
37
|
+
|
|
38
|
+
if (!usedAddresses.has(item.address)) {
|
|
39
|
+
usedAddresses.add(item.address);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (options.isConsolidate) {
|
|
43
|
+
// need to handle change
|
|
44
|
+
txBuilder.change(options.toChange ?? item.address);
|
|
45
|
+
|
|
46
|
+
if (txBuilder.transaction.inputs.length > MAX_INPUTS_OUTPUTS) {
|
|
47
|
+
return Array.from(usedAddresses.values());
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
let tx = txBuilder.transaction;
|
|
51
|
+
if (tx.inputs.length > MAX_INPUTS_OUTPUTS) {
|
|
52
|
+
throw new Error("Too many inputs. Consider consolidate transactions or reduce the send amount.");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let unspent = tx.getUnspentValue();
|
|
56
|
+
if (unspent < 0n) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (unspent == 0n && options.feeFromAmount) {
|
|
61
|
+
let txFee = tx.estimateRequiredFee();
|
|
62
|
+
tx.updateOutputAmount(0, origAmount - txFee);
|
|
63
|
+
return Array.from(usedAddresses.values());
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
txBuilder.change(options.toChange ?? item.address);
|
|
67
|
+
if (options.feeFromAmount) {
|
|
68
|
+
let hasChange = tx.getChangeOutput();
|
|
69
|
+
let txFee = tx.estimateRequiredFee();
|
|
70
|
+
tx.updateOutputAmount(0, origAmount - txFee);
|
|
71
|
+
|
|
72
|
+
// edge case where change added after update
|
|
73
|
+
if (!hasChange && tx.getChangeOutput()) {
|
|
74
|
+
txFee = tx.estimateRequiredFee();
|
|
75
|
+
tx.updateOutputAmount(0, origAmount - txFee);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// check again after change output manipulation
|
|
80
|
+
if (tx.getUnspentValue() < tx.estimateRequiredFee()) {
|
|
81
|
+
// try to add more utxos to satisfy the minimum fee
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
return Array.from(usedAddresses.values());
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (options.isConsolidate) {
|
|
90
|
+
if (usedAddresses.size > 0) {
|
|
91
|
+
return Array.from(usedAddresses.values());
|
|
92
|
+
}
|
|
93
|
+
throw new Error("Not enough Nexa balance.");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let err = {
|
|
97
|
+
errorMsg: "Not enough Nexa balance.",
|
|
98
|
+
amount: UnitUtils.formatNEXA(Number(totalTxValue)),
|
|
99
|
+
fee: UnitUtils.formatNEXA(txBuilder.transaction.estimateRequiredFee())
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
throw new Error(JSON.stringify(err));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function watchOnlyPopulateTokenInputsAndChange(txBuilder: TransactionBuilder, addresses: WatchOnlyAddress[], token: string, outTokenAmount: bigint): Promise<string[]> {
|
|
106
|
+
if (isNullOrEmpty(addresses)) {
|
|
107
|
+
throw new Error("Not enough token balance.");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let usedKeys = new Set<string>();
|
|
111
|
+
let inTokenAmount = 0n;
|
|
112
|
+
|
|
113
|
+
for (let item of addresses) {
|
|
114
|
+
let utxos = await rostrumProvider.getTokenUtxos(item.address, token);
|
|
115
|
+
for (let utxo of utxos) {
|
|
116
|
+
if (utxo.token_amount < 0) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
txBuilder.from({
|
|
120
|
+
outpoint: utxo.outpoint_hash,
|
|
121
|
+
address: item.address,
|
|
122
|
+
satoshis: utxo.value,
|
|
123
|
+
groupId: utxo.group,
|
|
124
|
+
groupAmount: BigInt(utxo.token_amount),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
inTokenAmount = inTokenAmount + BigInt(utxo.token_amount);
|
|
128
|
+
if (!usedKeys.has(item.address)) {
|
|
129
|
+
usedKeys.add(item.address);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (inTokenAmount > MAX_INT64) {
|
|
133
|
+
throw new Error("Token inputs exceeded max amount. Consider sending in small chunks");
|
|
134
|
+
}
|
|
135
|
+
if (txBuilder.transaction.inputs.length > MAX_INPUTS_OUTPUTS) {
|
|
136
|
+
throw new Error("Too many inputs. Consider consolidating transactions or reduce the send amount.");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (inTokenAmount == outTokenAmount) {
|
|
140
|
+
return Array.from(usedKeys.values());
|
|
141
|
+
}
|
|
142
|
+
if (inTokenAmount > outTokenAmount) {
|
|
143
|
+
// change
|
|
144
|
+
txBuilder.to(item.address, Transaction.DUST_AMOUNT, token, inTokenAmount - outTokenAmount);
|
|
145
|
+
return Array.from(usedKeys.values());
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
throw new Error("Not enough token balance");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function watchOnlyBuildCreateGroupTransaction(
|
|
154
|
+
txBuilder: TransactionBuilder,
|
|
155
|
+
addresses: WatchOnlyAddress[],
|
|
156
|
+
opReturnData: string,
|
|
157
|
+
network: Networkish
|
|
158
|
+
): Promise<string[]> {
|
|
159
|
+
let outpoint = '', idHex = '';
|
|
160
|
+
let usedKeys: string[] = [];
|
|
161
|
+
for (let item of addresses) {
|
|
162
|
+
let utxos = await rostrumProvider.getNexaUtxos(item.address);
|
|
163
|
+
for (let utxo of utxos) {
|
|
164
|
+
txBuilder.from({
|
|
165
|
+
outpoint: utxo.outpoint_hash,
|
|
166
|
+
address: item.address,
|
|
167
|
+
satoshis: utxo.value
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (isNullOrEmpty(outpoint)) {
|
|
171
|
+
outpoint = utxo.outpoint_hash;
|
|
172
|
+
let id = GroupToken.findGroupId(Buffer.from(outpoint, 'hex'), Buffer.from(opReturnData, 'hex'), GroupToken.authFlags.ACTIVE_FLAG_BITS);
|
|
173
|
+
const groupId = new Address(id.hashBuffer, network, AddressType.GroupIdAddress).toString()
|
|
174
|
+
txBuilder.to(item.address, Transaction.DUST_AMOUNT, groupId, GroupToken.authFlags.ACTIVE_FLAG_BITS | id.nonce)
|
|
175
|
+
idHex = id.hashBuffer.toString('hex');
|
|
176
|
+
usedKeys.push(item.address);
|
|
177
|
+
return usedKeys
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
throw new Error("Not enough Nexa balance.");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function watchOnlyPrepareDeleteTransaction(txBuilder: TransactionBuilder, addresses: WatchOnlyAddress[], outpoint: string): Promise<string[]> {
|
|
186
|
+
let utxo = await rostrumProvider.getUtxo(outpoint);
|
|
187
|
+
let address = utxo.addresses[0];
|
|
188
|
+
|
|
189
|
+
txBuilder.from({
|
|
190
|
+
outpoint: outpoint,
|
|
191
|
+
address: address,
|
|
192
|
+
satoshis: utxo.amount
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
let addrKey = addresses.find(k => k.address === address);
|
|
196
|
+
|
|
197
|
+
if (!addrKey) {
|
|
198
|
+
throw new Error('UTXO associated key not found in the wallet');
|
|
199
|
+
}
|
|
200
|
+
return [addrKey.address];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export async function watchOnlyPopulateTokenAuth(txBuilder: TransactionBuilder, addresses: WatchOnlyAddress[], token: string, perm: PermissionLabel, subgroup = ''): Promise<string[]> {
|
|
204
|
+
for (let item of addresses) {
|
|
205
|
+
let utxos = await rostrumProvider.getTokenUtxos(item.address, token);
|
|
206
|
+
for (let utxo of utxos) {
|
|
207
|
+
if (!isAuthFit(utxo.token_amount, perm)) {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
txBuilder.from({
|
|
212
|
+
outpoint: utxo.outpoint_hash,
|
|
213
|
+
address: item.address,
|
|
214
|
+
satoshis: utxo.value
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
if (perm === 'subgroup') {
|
|
218
|
+
txBuilder.to(item.address, Transaction.DUST_AMOUNT, subgroup, dupAuthority(utxo.token_amount, false));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// if renew flag included, we don't want to burn it
|
|
222
|
+
if (GroupToken.allowsRenew(BigInt.asUintN(64, BigInt(utxo.token_amount)))) {
|
|
223
|
+
txBuilder.to(item.address, Transaction.DUST_AMOUNT, token, dupAuthority(utxo.token_amount));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return [item.address];
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
throw new Error("The requested authority not found");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function watchOnlyPopulateAndDuplicateTokenAuths(txBuilder: TransactionBuilder, addresses: WatchOnlyAddress[], token: string, perms: PermissionLabel[], toAddr?: string): Promise<string[]> {
|
|
234
|
+
let usedAddresses: string[] = [];
|
|
235
|
+
|
|
236
|
+
let reqiredPerms = new Set(perms);
|
|
237
|
+
reqiredPerms.add('authorise');
|
|
238
|
+
|
|
239
|
+
for (let item of addresses) {
|
|
240
|
+
let utxos = await rostrumProvider.getTokenUtxos(item.address, token);
|
|
241
|
+
for (let utxo of utxos) {
|
|
242
|
+
if (utxo.token_amount > 0) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let found = false;
|
|
247
|
+
for (let perm of reqiredPerms) {
|
|
248
|
+
if (isAuthFit(utxo.token_amount, perm)) {
|
|
249
|
+
reqiredPerms.delete(perm);
|
|
250
|
+
found = true;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!found) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
txBuilder.from({
|
|
259
|
+
outpoint: utxo.outpoint_hash,
|
|
260
|
+
address: item.address,
|
|
261
|
+
satoshis: utxo.value
|
|
262
|
+
});
|
|
263
|
+
usedAddresses.push(item.address);
|
|
264
|
+
|
|
265
|
+
// duplicate
|
|
266
|
+
txBuilder.to(toAddr != null ? toAddr : item.address, Transaction.DUST_AMOUNT, token, dupAuthority(utxo.token_amount));
|
|
267
|
+
|
|
268
|
+
if (reqiredPerms.size === 0) {
|
|
269
|
+
return usedAddresses;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
throw new Error("The required authorities not found");
|
|
275
|
+
}
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import {HDPrivateKey, Message, Networkish, Networks, TransactionBuilder,} from "libnexa-ts";
|
|
2
|
+
import * as Bip39 from 'bip39'
|
|
3
|
+
import {rostrumProvider} from "../network/RostrumProvider";
|
|
4
|
+
import {AccountType, discoverWallet,} from "../utils/WalletUtils";
|
|
5
|
+
import {isBuffer, isNil, isString} from "lodash-es";
|
|
6
|
+
import WalletTransactionCreator from "./transactions/WalletTransactionCreator";
|
|
7
|
+
import AccountStore from "./accounts/AccountStore";
|
|
8
|
+
import {BaseAccount} from "./accounts/interfaces/BaseAccountInterface";
|
|
9
|
+
import ValidationUtils from "../utils/ValidationUtils";
|
|
10
|
+
import {AddressKey} from "../models/wallet.entities";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Main Wallet class for managing Nexa blockchain wallet operations
|
|
14
|
+
*
|
|
15
|
+
* This class provides comprehensive wallet functionality including:
|
|
16
|
+
* - Creating wallets from seed phrases or private keys
|
|
17
|
+
* - Account discovery and management
|
|
18
|
+
* - Transaction creation and signing
|
|
19
|
+
* - Message signing and verification
|
|
20
|
+
* - Multi-account support with different account types
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* // Create a new wallet with random seed phrase
|
|
25
|
+
* const wallet = Wallet.create();
|
|
26
|
+
*
|
|
27
|
+
* // Restore wallet from existing seed phrase
|
|
28
|
+
* const wallet = Wallet.fromSeedPhrase('your twelve word seed phrase here');
|
|
29
|
+
*
|
|
30
|
+
* // Initialize wallet (discovers accounts and balances)
|
|
31
|
+
* await wallet.initialize();
|
|
32
|
+
*
|
|
33
|
+
* // Create a new account
|
|
34
|
+
* const account = await wallet.newAccount('DefaultAccount');
|
|
35
|
+
*
|
|
36
|
+
* // Create and send a transaction
|
|
37
|
+
* const tx = wallet.newTransaction(account)
|
|
38
|
+
* .to('nexa:address', 1000000) // 1 NEXA in satoshis
|
|
39
|
+
* .sign();
|
|
40
|
+
*
|
|
41
|
+
* const txId = await wallet.sendTransaction(tx.toHex());
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export default class Wallet {
|
|
45
|
+
|
|
46
|
+
/** The master HD private key derived from the seed phrase */
|
|
47
|
+
private readonly masterKey!: HDPrivateKey;
|
|
48
|
+
|
|
49
|
+
/** Store for managing wallet accounts */
|
|
50
|
+
private _accountStore: AccountStore;
|
|
51
|
+
|
|
52
|
+
/** The blockchain network this wallet operates on */
|
|
53
|
+
private readonly _network: Networkish
|
|
54
|
+
|
|
55
|
+
/** The BIP39 seed phrase used to generate this wallet (if created from phrase) */
|
|
56
|
+
private readonly phrase?: string;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Creates a new Wallet instance
|
|
60
|
+
*
|
|
61
|
+
* @param data - Optional wallet data:
|
|
62
|
+
* - undefined: Generate new random seed phrase
|
|
63
|
+
* - string: Use as BIP39 seed phrase
|
|
64
|
+
* - HDPrivateKey: Use as master key directly
|
|
65
|
+
* @param network - Network name ('mainnet', 'testnet', 'regtest'). Defaults to 'mainnet'
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* // Create new wallet with random seed
|
|
70
|
+
* const wallet = new Wallet();
|
|
71
|
+
*
|
|
72
|
+
* // Create from seed phrase
|
|
73
|
+
* const wallet = new Wallet('abandon abandon abandon...');
|
|
74
|
+
*
|
|
75
|
+
* // Create from master key
|
|
76
|
+
* const masterKey = HDPrivateKey.fromString('xprv...');
|
|
77
|
+
* const wallet = new Wallet(masterKey);
|
|
78
|
+
*
|
|
79
|
+
* // Create on testnet
|
|
80
|
+
* const wallet = new Wallet(undefined, 'testnet');
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
constructor(data?: string | HDPrivateKey | undefined, network?: string) {
|
|
84
|
+
this._network = Networks.get(network) ?? Networks.mainnet
|
|
85
|
+
this._accountStore = new AccountStore()
|
|
86
|
+
if(isNil(data)) {
|
|
87
|
+
this.phrase = Bip39.generateMnemonic(128, undefined, Bip39.wordlists.english)
|
|
88
|
+
const seed = Bip39.mnemonicToSeedSync(this.phrase, '')
|
|
89
|
+
|
|
90
|
+
const masterKey = HDPrivateKey.fromSeed(seed, this._network ?? Networks.mainnet)
|
|
91
|
+
this.masterKey = masterKey.deriveChild(44, true).deriveChild(29223, true)
|
|
92
|
+
} else if(data instanceof HDPrivateKey) {
|
|
93
|
+
this.masterKey = data
|
|
94
|
+
} else if (isString(data)) {
|
|
95
|
+
this.phrase = data
|
|
96
|
+
const seed = Bip39.mnemonicToSeedSync(this.phrase, '')
|
|
97
|
+
|
|
98
|
+
const masterKey = HDPrivateKey.fromSeed(seed, this._network ?? Networks.mainnet)
|
|
99
|
+
this.masterKey = masterKey.deriveChild(44, true).deriveChild(29223, true)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Create a new wallet with a randomly generated seed phrase
|
|
105
|
+
*
|
|
106
|
+
* This is the recommended way to create a new wallet for first-time users.
|
|
107
|
+
* The generated seed phrase should be securely stored by the user.
|
|
108
|
+
*
|
|
109
|
+
* @returns A new Wallet instance with a random 12-word seed phrase
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```typescript
|
|
113
|
+
* const wallet = Wallet.create();
|
|
114
|
+
* console.log(wallet.export().phrase); // Store this securely!
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
public static create(): Wallet {
|
|
118
|
+
return new Wallet()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Create a wallet from an existing BIP39 seed phrase
|
|
123
|
+
*
|
|
124
|
+
* Use this method to restore a wallet from a previously generated seed phrase.
|
|
125
|
+
* The seed phrase should be a valid BIP39 mnemonic.
|
|
126
|
+
*
|
|
127
|
+
* @param phrase - The BIP39 seed phrase (12 or 24 words)
|
|
128
|
+
* @param network - Optional network name ('mainnet', 'testnet', 'regtest')
|
|
129
|
+
* @returns A new Wallet instance restored from the seed phrase
|
|
130
|
+
* @throws {Error} If the seed phrase is invalid or not provided
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* const wallet = Wallet.fromSeedPhrase(
|
|
135
|
+
* 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
|
|
136
|
+
* 'testnet'
|
|
137
|
+
* );
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
public static fromSeedPhrase(phrase: string, network?: string): Wallet {
|
|
141
|
+
ValidationUtils.validateArgument(isString(phrase), 'seedphrase must be provided')
|
|
142
|
+
return new Wallet(phrase, network)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Create a wallet from an extended private key (xpriv)
|
|
147
|
+
*
|
|
148
|
+
* Use this method to create a wallet from a master private key in extended format.
|
|
149
|
+
* This is useful for advanced users who want to use a specific key derivation.
|
|
150
|
+
*
|
|
151
|
+
* @param xpriv - The extended private key string (starts with 'xprv')
|
|
152
|
+
* @param network - Optional network name ('mainnet', 'testnet', 'regtest')
|
|
153
|
+
* @returns A new Wallet instance using the provided master key
|
|
154
|
+
* @throws {Error} If the private key is invalid or not provided
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```typescript
|
|
158
|
+
* const wallet = Wallet.fromXpriv(
|
|
159
|
+
* 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi'
|
|
160
|
+
* );
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
public static fromXpriv(xpriv: string, network?: string): Wallet {
|
|
164
|
+
ValidationUtils.validateArgument(isString(xpriv), 'private key must be provided')
|
|
165
|
+
const masterKey = HDPrivateKey.fromString(xpriv)
|
|
166
|
+
return new Wallet(masterKey, network)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Initialize the wallet by discovering accounts and loading balances
|
|
171
|
+
*
|
|
172
|
+
* This method performs account discovery using the BIP44 derivation path
|
|
173
|
+
* and scans for existing accounts with transaction history or balances.
|
|
174
|
+
* Must be called before using the wallet's accounts.
|
|
175
|
+
*
|
|
176
|
+
* @returns Promise that resolves when initialization is complete
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```typescript
|
|
180
|
+
* const wallet = Wallet.fromSeedPhrase('your seed phrase');
|
|
181
|
+
* await wallet.initialize();
|
|
182
|
+
*
|
|
183
|
+
* // Now you can access discovered accounts
|
|
184
|
+
* const accounts = wallet.accountStore.listAccounts();
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
public async initialize(): Promise<void> {
|
|
188
|
+
const walletAccounts: BaseAccount[] = await discoverWallet(this.masterKey)
|
|
189
|
+
for(const account of walletAccounts){
|
|
190
|
+
this._accountStore.importAccount(account)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Create a new transaction builder for this wallet
|
|
196
|
+
*
|
|
197
|
+
* @param fromAccount - The account to send the transaction from
|
|
198
|
+
* @param x - Optional existing transaction data:
|
|
199
|
+
* - TransactionBuilder: Use existing transaction builder
|
|
200
|
+
* - string: Parse from hex string
|
|
201
|
+
* - Buffer: Parse from binary buffer
|
|
202
|
+
* - undefined: Create new empty transaction
|
|
203
|
+
* @returns A new WalletTransactionCreator instance
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```typescript
|
|
207
|
+
* const account = wallet.accountStore.getAccount(0);
|
|
208
|
+
* const tx = wallet.newTransaction(account)
|
|
209
|
+
* .to('nexa:address', 1000000) // 1 NEXA
|
|
210
|
+
* .sign();
|
|
211
|
+
*
|
|
212
|
+
* // Or from existing transaction hex
|
|
213
|
+
* const tx = wallet.newTransaction(account, 'raw_tx_hex')
|
|
214
|
+
* .sign();
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
public newTransaction(fromAccount: BaseAccount, x?: TransactionBuilder | string | Buffer): WalletTransactionCreator {
|
|
218
|
+
let tx: WalletTransactionCreator;
|
|
219
|
+
|
|
220
|
+
if (x instanceof TransactionBuilder) {
|
|
221
|
+
tx = new WalletTransactionCreator(fromAccount, x);
|
|
222
|
+
} else if (isString(x)) {
|
|
223
|
+
tx = new WalletTransactionCreator(fromAccount).parseTxHex(x);
|
|
224
|
+
} else if (isBuffer(x) && !isNil(x)) {
|
|
225
|
+
tx = new WalletTransactionCreator(fromAccount).parseTxBuffer(x);
|
|
226
|
+
} else {
|
|
227
|
+
tx = new WalletTransactionCreator(fromAccount);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return tx.onNetwork(this._network);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Create a new account for this wallet
|
|
235
|
+
*
|
|
236
|
+
* @param accountType - The type of account to create:
|
|
237
|
+
* - 'DefaultAccount': Standard account for general use
|
|
238
|
+
* - 'VaultAccount': Secured account with additional protection
|
|
239
|
+
* - 'DappAccount': Account optimized for dApp interactions
|
|
240
|
+
* @returns Promise that resolves to the newly created account
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```typescript
|
|
244
|
+
* const defaultAccount = await wallet.newAccount('DefaultAccount');
|
|
245
|
+
* const vaultAccount = await wallet.newAccount('VaultAccount');
|
|
246
|
+
* const dappAccount = await wallet.newAccount('DappAccount');
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
public async newAccount(accountType: AccountType): Promise<BaseAccount>{
|
|
250
|
+
return await this.accountStore.createAccount(accountType, this.masterKey)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Broadcast a signed transaction to the Nexa network
|
|
255
|
+
*
|
|
256
|
+
* @param transaction - The signed transaction in hex format
|
|
257
|
+
* @returns Promise that resolves to the transaction ID (txid)
|
|
258
|
+
* @throws {Error} If the transaction is invalid or broadcast fails
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```typescript
|
|
262
|
+
* const tx = wallet.newTransaction(account)
|
|
263
|
+
* .to('nexa:address', 1000000)
|
|
264
|
+
* .sign();
|
|
265
|
+
*
|
|
266
|
+
* const txId = await wallet.sendTransaction(tx.toHex());
|
|
267
|
+
* console.log('Transaction sent:', txId);
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
public async sendTransaction(transaction: string): Promise<string> {
|
|
271
|
+
ValidationUtils.validateArgument(isString(transaction), 'transaction must be present and valid')
|
|
272
|
+
return rostrumProvider.broadcast(transaction)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Sign a message using a specific address from this wallet
|
|
277
|
+
*
|
|
278
|
+
* The message is signed using the private key associated with the given address.
|
|
279
|
+
* This can be used for authentication or to prove ownership of an address.
|
|
280
|
+
*
|
|
281
|
+
* @param message - The message to sign
|
|
282
|
+
* @param addressToUse - The address whose private key should sign the message
|
|
283
|
+
* @returns The signature as a base64-encoded string
|
|
284
|
+
* @throws {Error} If the address is not owned by this wallet
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* ```typescript
|
|
288
|
+
* const account = wallet.accountStore.getAccount(0);
|
|
289
|
+
* const address = account.getReceiveAddress();
|
|
290
|
+
* const signature = wallet.signMessage('Hello World', address);
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
public signMessage(message: string, addressToUse: string): string {
|
|
294
|
+
let msg = new Message(message);
|
|
295
|
+
const addressKey = this.accountStore.findKeyForAddress(addressToUse)
|
|
296
|
+
ValidationUtils.validateArgument(isNil(addressKey), "You dont own this private key")
|
|
297
|
+
return msg.sign(addressKey?.key.privateKey!)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Verify a message signature against an address
|
|
302
|
+
*
|
|
303
|
+
* This method can verify signatures created by any address, not just addresses
|
|
304
|
+
* owned by this wallet. It's useful for verifying messages from other parties.
|
|
305
|
+
*
|
|
306
|
+
* @param message - The original message that was signed
|
|
307
|
+
* @param signature - The signature to verify (base64-encoded)
|
|
308
|
+
* @param address - The address that supposedly signed the message
|
|
309
|
+
* @returns true if the signature is valid, false otherwise
|
|
310
|
+
* @throws {Error} If any parameters are missing or invalid
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* ```typescript
|
|
314
|
+
* const isValid = wallet.verifyMessage(
|
|
315
|
+
* 'Hello World',
|
|
316
|
+
* 'signature_string',
|
|
317
|
+
* 'nexa:address'
|
|
318
|
+
* );
|
|
319
|
+
* console.log('Signature valid:', isValid);
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
public verifyMessage(message: string, signature: string, address: string): boolean {
|
|
323
|
+
ValidationUtils.validateArgument(!isNil(message), 'message is required')
|
|
324
|
+
ValidationUtils.validateArgument(!isNil(signature), 'signature is required')
|
|
325
|
+
ValidationUtils.validateArgument(!isNil(address), 'address is required ')
|
|
326
|
+
let msg = new Message(message);
|
|
327
|
+
const addressKey = this.accountStore.findKeyForAddress(address)
|
|
328
|
+
ValidationUtils.validateArgument(isNil(addressKey), "You dont own this private key")
|
|
329
|
+
return msg.verify(address, signature)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Export the wallet data for backup or storage
|
|
334
|
+
*
|
|
335
|
+
* Returns an object containing the wallet's seed phrase, master key, and accounts.
|
|
336
|
+
* This data can be used to restore the wallet later. The seed phrase should be
|
|
337
|
+
* stored securely as it provides full access to the wallet.
|
|
338
|
+
*
|
|
339
|
+
* @returns Object containing wallet data
|
|
340
|
+
* @property {string} phrase - The BIP39 seed phrase (if available)
|
|
341
|
+
* @property {HDPrivateKey} masterKey - The master private key
|
|
342
|
+
* @property {BaseAccount[]} accounts - Array of discovered accounts
|
|
343
|
+
*
|
|
344
|
+
* @example
|
|
345
|
+
* ```typescript
|
|
346
|
+
* const walletData = wallet.export();
|
|
347
|
+
*
|
|
348
|
+
* // Store the seed phrase securely
|
|
349
|
+
* const seedPhrase = walletData.phrase;
|
|
350
|
+
*
|
|
351
|
+
* // Later, restore the wallet
|
|
352
|
+
* const restoredWallet = Wallet.fromSeedPhrase(seedPhrase);
|
|
353
|
+
* ```
|
|
354
|
+
*/
|
|
355
|
+
public export(): any {
|
|
356
|
+
return {
|
|
357
|
+
phrase: this.phrase,
|
|
358
|
+
masterKey: this.masterKey,
|
|
359
|
+
accounts: this._accountStore.listAccounts(),
|
|
360
|
+
accountIndexes: this.accountStore.listAccounts().keys()
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Get the account store for managing wallet accounts
|
|
366
|
+
*
|
|
367
|
+
* The account store provides methods to create, import, and manage accounts
|
|
368
|
+
* within this wallet. Each account has its own set of addresses and keys.
|
|
369
|
+
*
|
|
370
|
+
* @returns The wallet's account store
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```typescript
|
|
374
|
+
* const accountStore = wallet.accountStore;
|
|
375
|
+
* const accounts = accountStore.listAccounts();
|
|
376
|
+
* const firstAccount = accountStore.getAccount(0);
|
|
377
|
+
* ```
|
|
378
|
+
*/
|
|
379
|
+
get accountStore(): AccountStore {
|
|
380
|
+
return this._accountStore;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Get the network this wallet is operating on
|
|
385
|
+
*
|
|
386
|
+
* @returns The network object (mainnet, testnet, or regtest)
|
|
387
|
+
*
|
|
388
|
+
* @example
|
|
389
|
+
* ```typescript
|
|
390
|
+
* const network = wallet.network;
|
|
391
|
+
* console.log('Network:', network.name);
|
|
392
|
+
* ```
|
|
393
|
+
*/
|
|
394
|
+
get network(): Networkish {
|
|
395
|
+
return this._network;
|
|
396
|
+
}
|
|
397
|
+
}
|