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,341 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, vi } from 'vitest'
|
|
2
|
+
import { HDPrivateKey, Networks } from 'libnexa-ts'
|
|
3
|
+
import AccountStore from '../../../src/wallet/accounts/AccountStore'
|
|
4
|
+
import { AccountType } from '../../../src/utils/WalletUtils'
|
|
5
|
+
import { BaseAccount } from '../../../src/wallet/accounts/interfaces/BaseAccountInterface'
|
|
6
|
+
import DAppAccount from '../../../src/wallet/accounts/models/DappAccount'
|
|
7
|
+
import VaultAccount from '../../../src/wallet/accounts/models/VaultAccount'
|
|
8
|
+
import DefaultAccount from '../../../src/wallet/accounts/models/DefaultAccount'
|
|
9
|
+
|
|
10
|
+
// Mock the WalletUtils
|
|
11
|
+
vi.mock('../../../src/utils/WalletUtils', () => ({
|
|
12
|
+
AccountType: {
|
|
13
|
+
NEXA_ACCOUNT: 0,
|
|
14
|
+
VAULT_ACCOUNT: 1,
|
|
15
|
+
DAPP_ACCOUNT: 2
|
|
16
|
+
},
|
|
17
|
+
getNextAccountIndex: vi.fn().mockResolvedValue(0),
|
|
18
|
+
generateAccountKey: vi.fn().mockReturnValue({
|
|
19
|
+
deriveChild: vi.fn().mockReturnValue({
|
|
20
|
+
privateKey: {
|
|
21
|
+
toAddress: vi.fn().mockReturnValue({
|
|
22
|
+
toString: vi.fn().mockReturnValue('nexatest:address123')
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
}),
|
|
27
|
+
generateKeyAndAddress: vi.fn().mockReturnValue({
|
|
28
|
+
key: {
|
|
29
|
+
privateKey: {
|
|
30
|
+
toAddress: vi.fn().mockReturnValue({
|
|
31
|
+
toString: vi.fn().mockReturnValue('nexatest:address123')
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
address: 'nexatest:address123',
|
|
36
|
+
balance: '0',
|
|
37
|
+
tokensBalance: {}
|
|
38
|
+
}),
|
|
39
|
+
generateKeysAndAddresses: vi.fn().mockReturnValue({
|
|
40
|
+
receiveKeys: [{
|
|
41
|
+
key: { privateKey: {} },
|
|
42
|
+
address: 'nexatest:receive123',
|
|
43
|
+
balance: '0',
|
|
44
|
+
tokensBalance: {}
|
|
45
|
+
}],
|
|
46
|
+
changeKeys: [{
|
|
47
|
+
key: { privateKey: {} },
|
|
48
|
+
address: 'nexatest:change123',
|
|
49
|
+
balance: '0',
|
|
50
|
+
tokensBalance: {}
|
|
51
|
+
}]
|
|
52
|
+
})
|
|
53
|
+
}))
|
|
54
|
+
|
|
55
|
+
// Mock the account classes
|
|
56
|
+
vi.mock('../../../src/wallet/accounts/models/DappAccount', () => ({
|
|
57
|
+
default: vi.fn().mockImplementation((purpose, index, addressKey) => ({
|
|
58
|
+
purpose,
|
|
59
|
+
index,
|
|
60
|
+
addressKey,
|
|
61
|
+
accountKeys: {
|
|
62
|
+
receiveKeys: [addressKey],
|
|
63
|
+
changeKeys: []
|
|
64
|
+
},
|
|
65
|
+
loadBalances: vi.fn().mockResolvedValue(undefined),
|
|
66
|
+
getAccountStoreKey: vi.fn().mockReturnValue(`${purpose}.${index}`)
|
|
67
|
+
}))
|
|
68
|
+
}))
|
|
69
|
+
|
|
70
|
+
vi.mock('../../../src/wallet/accounts/models/VaultAccount', () => ({
|
|
71
|
+
default: vi.fn().mockImplementation((purpose, index, addressKey) => ({
|
|
72
|
+
purpose,
|
|
73
|
+
index,
|
|
74
|
+
addressKey,
|
|
75
|
+
accountKeys: {
|
|
76
|
+
receiveKeys: [addressKey],
|
|
77
|
+
changeKeys: []
|
|
78
|
+
},
|
|
79
|
+
loadBalances: vi.fn().mockResolvedValue(undefined),
|
|
80
|
+
getAccountStoreKey: vi.fn().mockReturnValue(`${purpose}.${index}`)
|
|
81
|
+
}))
|
|
82
|
+
}))
|
|
83
|
+
|
|
84
|
+
vi.mock('../../../src/wallet/accounts/models/DefaultAccount', () => ({
|
|
85
|
+
default: vi.fn().mockImplementation((index, indexes, keys) => ({
|
|
86
|
+
index,
|
|
87
|
+
indexes,
|
|
88
|
+
accountKeys: keys,
|
|
89
|
+
loadBalances: vi.fn().mockResolvedValue(undefined),
|
|
90
|
+
getAccountStoreKey: vi.fn().mockReturnValue(String(index))
|
|
91
|
+
}))
|
|
92
|
+
}))
|
|
93
|
+
|
|
94
|
+
describe('AccountStore', () => {
|
|
95
|
+
let accountStore: AccountStore
|
|
96
|
+
let mockMasterKey: HDPrivateKey
|
|
97
|
+
|
|
98
|
+
beforeEach(() => {
|
|
99
|
+
accountStore = new AccountStore()
|
|
100
|
+
mockMasterKey = {} as HDPrivateKey
|
|
101
|
+
vi.clearAllMocks()
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
describe('constructor', () => {
|
|
105
|
+
test('should create empty account store', () => {
|
|
106
|
+
expect(accountStore).toBeInstanceOf(AccountStore)
|
|
107
|
+
expect(accountStore.listAccounts().size).toBe(0)
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
describe('createAccount', () => {
|
|
112
|
+
test('should create DApp account', async () => {
|
|
113
|
+
const account = await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
|
|
114
|
+
|
|
115
|
+
expect(account).toBeDefined()
|
|
116
|
+
expect(DAppAccount).toHaveBeenCalledWith(2, 0, expect.any(Object))
|
|
117
|
+
expect(accountStore.listAccounts().size).toBe(1)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('should create Vault account', async () => {
|
|
121
|
+
const account = await accountStore.createAccount(AccountType.VAULT_ACCOUNT, mockMasterKey)
|
|
122
|
+
|
|
123
|
+
expect(account).toBeDefined()
|
|
124
|
+
expect(VaultAccount).toHaveBeenCalledWith(1, 0, expect.any(Object))
|
|
125
|
+
expect(accountStore.listAccounts().size).toBe(1)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('should create Default NEXA account', async () => {
|
|
129
|
+
const account = await accountStore.createAccount(AccountType.NEXA_ACCOUNT, mockMasterKey)
|
|
130
|
+
|
|
131
|
+
expect(account).toBeDefined()
|
|
132
|
+
expect(DefaultAccount).toHaveBeenCalledWith(0, expect.any(Object), expect.any(Object))
|
|
133
|
+
expect(accountStore.listAccounts().size).toBe(1)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test('should return existing account if already created', async () => {
|
|
137
|
+
const account1 = await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
|
|
138
|
+
const account2 = await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
|
|
139
|
+
|
|
140
|
+
expect(account1).toBe(account2)
|
|
141
|
+
expect(accountStore.listAccounts().size).toBe(1)
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
describe('getAccountStoreKey', () => {
|
|
146
|
+
test('should generate correct key for DApp account', () => {
|
|
147
|
+
const key = accountStore['getAccountStoreKey'](AccountType.DAPP_ACCOUNT, 0)
|
|
148
|
+
expect(key).toBe('2.0')
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test('should generate correct key for Vault account', () => {
|
|
152
|
+
const key = accountStore['getAccountStoreKey'](AccountType.VAULT_ACCOUNT, 1)
|
|
153
|
+
expect(key).toBe('1.1')
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test('should generate correct key for Default account', () => {
|
|
157
|
+
const key = accountStore['getAccountStoreKey'](AccountType.NEXA_ACCOUNT, 0)
|
|
158
|
+
expect(key).toBe('0')
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
describe('findKeyForAddress', () => {
|
|
163
|
+
test('should find key for existing address', async () => {
|
|
164
|
+
const mockAccount = {
|
|
165
|
+
accountKeys: {
|
|
166
|
+
receiveKeys: [{
|
|
167
|
+
address: 'nexatest:target123',
|
|
168
|
+
key: { privateKey: 'mock-key' }
|
|
169
|
+
}],
|
|
170
|
+
changeKeys: [{
|
|
171
|
+
address: 'nexatest:change123',
|
|
172
|
+
key: { privateKey: 'mock-key-2' }
|
|
173
|
+
}]
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
accountStore['_accounts'].set('test', mockAccount as any)
|
|
178
|
+
|
|
179
|
+
const result = accountStore.findKeyForAddress('nexatest:target123')
|
|
180
|
+
expect(result).toBeDefined()
|
|
181
|
+
expect(result?.address).toBe('nexatest:target123')
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test('should return null for non-existent address', () => {
|
|
185
|
+
const result = accountStore.findKeyForAddress('nexatest:nonexistent')
|
|
186
|
+
expect(result).toBeNull()
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('should search through both receive and change keys', async () => {
|
|
190
|
+
const mockAccount = {
|
|
191
|
+
accountKeys: {
|
|
192
|
+
receiveKeys: [{
|
|
193
|
+
address: 'nexatest:receive123',
|
|
194
|
+
key: { privateKey: 'mock-key' }
|
|
195
|
+
}],
|
|
196
|
+
changeKeys: [{
|
|
197
|
+
address: 'nexatest:change123',
|
|
198
|
+
key: { privateKey: 'mock-key-2' }
|
|
199
|
+
}]
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
accountStore['_accounts'].set('test', mockAccount as any)
|
|
204
|
+
|
|
205
|
+
const receiveResult = accountStore.findKeyForAddress('nexatest:receive123')
|
|
206
|
+
const changeResult = accountStore.findKeyForAddress('nexatest:change123')
|
|
207
|
+
|
|
208
|
+
expect(receiveResult?.address).toBe('nexatest:receive123')
|
|
209
|
+
expect(changeResult?.address).toBe('nexatest:change123')
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
describe('importAccount', () => {
|
|
214
|
+
test('should import new account', () => {
|
|
215
|
+
const mockAccount = {
|
|
216
|
+
getAccountStoreKey: vi.fn().mockReturnValue('imported.0')
|
|
217
|
+
} as any
|
|
218
|
+
|
|
219
|
+
accountStore.importAccount(mockAccount)
|
|
220
|
+
|
|
221
|
+
expect(accountStore.listAccounts().size).toBe(1)
|
|
222
|
+
expect(accountStore.getAccount('imported.0')).toBe(mockAccount)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test('should throw error for duplicate account', () => {
|
|
226
|
+
const mockAccount = {
|
|
227
|
+
getAccountStoreKey: vi.fn().mockReturnValue('duplicate.0')
|
|
228
|
+
} as any
|
|
229
|
+
|
|
230
|
+
accountStore.importAccount(mockAccount)
|
|
231
|
+
|
|
232
|
+
expect(() => {
|
|
233
|
+
accountStore.importAccount(mockAccount)
|
|
234
|
+
}).toThrow('Account already exists!')
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
describe('exportAccount', () => {
|
|
239
|
+
test('should export existing account', () => {
|
|
240
|
+
const mockAccount = {
|
|
241
|
+
getAccountStoreKey: vi.fn().mockReturnValue('export.0')
|
|
242
|
+
} as any
|
|
243
|
+
|
|
244
|
+
accountStore['_accounts'].set('export.0', mockAccount)
|
|
245
|
+
|
|
246
|
+
const exported = accountStore.exportAccount('export.0')
|
|
247
|
+
expect(exported).toBe(mockAccount)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
test('should throw error for non-existent account', () => {
|
|
251
|
+
expect(() => {
|
|
252
|
+
accountStore.exportAccount('nonexistent')
|
|
253
|
+
}).toThrow('Cannot find account!')
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
describe('removeAccount', () => {
|
|
258
|
+
test('should remove existing account', () => {
|
|
259
|
+
const mockAccount = {
|
|
260
|
+
getAccountStoreKey: vi.fn().mockReturnValue('remove.0')
|
|
261
|
+
} as any
|
|
262
|
+
|
|
263
|
+
accountStore['_accounts'].set('remove.0', mockAccount)
|
|
264
|
+
expect(accountStore.listAccounts().size).toBe(1)
|
|
265
|
+
|
|
266
|
+
accountStore.removeAccount('remove.0')
|
|
267
|
+
expect(accountStore.listAccounts().size).toBe(0)
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
test('should throw error for non-existent account', () => {
|
|
271
|
+
expect(() => {
|
|
272
|
+
accountStore.removeAccount('nonexistent')
|
|
273
|
+
}).toThrow('Cannot find account!')
|
|
274
|
+
})
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
describe('listAccounts', () => {
|
|
278
|
+
test('should return all accounts', async () => {
|
|
279
|
+
await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
|
|
280
|
+
await accountStore.createAccount(AccountType.VAULT_ACCOUNT, mockMasterKey)
|
|
281
|
+
|
|
282
|
+
const accounts = accountStore.listAccounts()
|
|
283
|
+
expect(accounts.size).toBe(2)
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
test('should return empty map when no accounts', () => {
|
|
287
|
+
const accounts = accountStore.listAccounts()
|
|
288
|
+
expect(accounts.size).toBe(0)
|
|
289
|
+
expect(accounts).toBeInstanceOf(Map)
|
|
290
|
+
})
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
describe('getAccount', () => {
|
|
294
|
+
test('should return account by index', async () => {
|
|
295
|
+
const account = await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
|
|
296
|
+
|
|
297
|
+
const retrieved = accountStore.getAccount('2.0')
|
|
298
|
+
expect(retrieved).toBe(account)
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
test('should return undefined for non-existent account', () => {
|
|
302
|
+
const retrieved = accountStore.getAccount('nonexistent')
|
|
303
|
+
expect(retrieved).toBeUndefined()
|
|
304
|
+
})
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
describe('error handling', () => {
|
|
308
|
+
test('should handle account creation errors gracefully', async () => {
|
|
309
|
+
// Mock getNextAccountIndex to throw an error
|
|
310
|
+
const { getNextAccountIndex } = await import('../../../src/utils/WalletUtils')
|
|
311
|
+
vi.mocked(getNextAccountIndex).mockRejectedValueOnce(new Error('Network error'))
|
|
312
|
+
|
|
313
|
+
await expect(
|
|
314
|
+
accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
|
|
315
|
+
).rejects.toThrow('Network error')
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
describe('integration tests', () => {
|
|
320
|
+
test('should handle multiple account types', async () => {
|
|
321
|
+
const dappAccount = await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
|
|
322
|
+
const vaultAccount = await accountStore.createAccount(AccountType.VAULT_ACCOUNT, mockMasterKey)
|
|
323
|
+
const nexaAccount = await accountStore.createAccount(AccountType.NEXA_ACCOUNT, mockMasterKey)
|
|
324
|
+
|
|
325
|
+
expect(accountStore.listAccounts().size).toBe(3)
|
|
326
|
+
expect(accountStore.getAccount('2.0')).toBe(dappAccount)
|
|
327
|
+
expect(accountStore.getAccount('1.0')).toBe(vaultAccount)
|
|
328
|
+
expect(accountStore.getAccount('0')).toBe(nexaAccount)
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
test('should maintain account independence', async () => {
|
|
332
|
+
const account1 = await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
|
|
333
|
+
const account2 = await accountStore.createAccount(AccountType.VAULT_ACCOUNT, mockMasterKey)
|
|
334
|
+
|
|
335
|
+
accountStore.removeAccount('2.0')
|
|
336
|
+
|
|
337
|
+
expect(accountStore.getAccount('2.0')).toBeUndefined()
|
|
338
|
+
expect(accountStore.getAccount('1.0')).toBe(account2)
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {describe, test, expect} from "vitest";
|
|
2
|
+
import {Wallet} from "../../../src";
|
|
3
|
+
import {rostrumProvider} from "../../../src/network/RostrumProvider";
|
|
4
|
+
import WatchOnlyWallet from "../../../src/wallet/WatchOnlyWallet";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Test suite for the main Wallet class
|
|
8
|
+
*
|
|
9
|
+
* Tests wallet initialization, account management, and transaction creation
|
|
10
|
+
* on the Nexa testnet.
|
|
11
|
+
*/
|
|
12
|
+
describe('#Wallet', {timeout: 100000}, function() {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Test wallet initialization and basic transaction creation
|
|
16
|
+
*
|
|
17
|
+
* This test:
|
|
18
|
+
* - Creates a wallet from a seed phrase
|
|
19
|
+
* - Initializes it to discover accounts
|
|
20
|
+
* - Creates a transaction with OP_RETURN data
|
|
21
|
+
* - Tests the full transaction flow
|
|
22
|
+
*/
|
|
23
|
+
test('Init wallet', async () => {
|
|
24
|
+
await rostrumProvider.connect()
|
|
25
|
+
const wallet = new Wallet('mask wool cram snap cross wood rebuild among total vicious script diamond', 'testnet')
|
|
26
|
+
await wallet.initialize()
|
|
27
|
+
|
|
28
|
+
const defaultAccount = wallet.accountStore.getAccount('2.0')
|
|
29
|
+
expect(defaultAccount).toBeDefined()
|
|
30
|
+
if(!defaultAccount){
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const transactions = await defaultAccount.getTransactions()
|
|
35
|
+
expect(transactions).toBeDefined()
|
|
36
|
+
|
|
37
|
+
const newAddress = defaultAccount.getNewAddress()
|
|
38
|
+
expect(newAddress).toBeDefined()
|
|
39
|
+
|
|
40
|
+
const tx = await wallet.newTransaction(defaultAccount)
|
|
41
|
+
.onNetwork('testnet')
|
|
42
|
+
.sendTo('nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc', '10000')
|
|
43
|
+
.addOpReturn("First wallet SDK transaction, Woo!!")
|
|
44
|
+
.populate()
|
|
45
|
+
.sign()
|
|
46
|
+
.build()
|
|
47
|
+
expect(tx).toBeDefined()
|
|
48
|
+
|
|
49
|
+
const watchOnlyWallet = new WatchOnlyWallet([{
|
|
50
|
+
address: 'nexatest:nqtsq5g5dsgh6mwjchqypn8hvdrjue0xpmz293fl7rm926xv',
|
|
51
|
+
}], 'testnet')
|
|
52
|
+
|
|
53
|
+
const tx2 = await watchOnlyWallet.newTransaction()
|
|
54
|
+
.sendTo('nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc', '100000')
|
|
55
|
+
.addOpReturn("Second wallet SDK transaction, Woo!!")
|
|
56
|
+
.populate()
|
|
57
|
+
.build()
|
|
58
|
+
expect(tx2).toBeDefined()
|
|
59
|
+
|
|
60
|
+
const parsedTx = await wallet.newTransaction(defaultAccount, tx2).sign().build()
|
|
61
|
+
expect(parsedTx).toBeDefined()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
const walletData = wallet.export()
|
|
66
|
+
expect(walletData).toBeDefined()
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
});
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, vi } from 'vitest'
|
|
2
|
+
import { Networks, TransactionBuilder } from 'libnexa-ts'
|
|
3
|
+
import WatchOnlyWallet from '../../../src/wallet/WatchOnlyWallet'
|
|
4
|
+
import { WatchOnlyAddress } from '../../../src/models/wallet.entities'
|
|
5
|
+
|
|
6
|
+
// Mock the rostrumProvider
|
|
7
|
+
vi.mock('../../../src/network/RostrumProvider', () => ({
|
|
8
|
+
rostrumProvider: {
|
|
9
|
+
broadcast: vi.fn().mockResolvedValue('txid123'),
|
|
10
|
+
subscribeToAddresses: vi.fn().mockResolvedValue(undefined)
|
|
11
|
+
}
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
// Mock the ValidationUtils
|
|
15
|
+
vi.mock('../../../src/utils/ValidationUtils', () => ({
|
|
16
|
+
default: {
|
|
17
|
+
validateArgument: vi.fn()
|
|
18
|
+
}
|
|
19
|
+
}))
|
|
20
|
+
|
|
21
|
+
// Mock the WalletUtils
|
|
22
|
+
vi.mock('../../../src/utils/WalletUtils', () => ({
|
|
23
|
+
isValidNexaAddress: vi.fn().mockReturnValue(true)
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
describe('WatchOnlyWallet', () => {
|
|
27
|
+
let mockAddresses: WatchOnlyAddress[]
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
mockAddresses = [
|
|
31
|
+
{ address: 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc' },
|
|
32
|
+
{ address: 'nexatest:nqtsq5g5dsgh6mwjchqypn8hvdrjue0xpmz293fl7rm926xv' }
|
|
33
|
+
]
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
describe('constructor', () => {
|
|
37
|
+
test('should create wallet with valid addresses and default network', () => {
|
|
38
|
+
const wallet = new WatchOnlyWallet(mockAddresses)
|
|
39
|
+
|
|
40
|
+
expect(wallet).toBeInstanceOf(WatchOnlyWallet)
|
|
41
|
+
expect(wallet.getWatchedAddresses()).toHaveLength(2)
|
|
42
|
+
expect(wallet.getWatchedAddresses()[0].address).toBe(mockAddresses[0].address)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('should create wallet with custom network', () => {
|
|
46
|
+
const wallet = new WatchOnlyWallet(mockAddresses, 'testnet')
|
|
47
|
+
|
|
48
|
+
expect(wallet).toBeInstanceOf(WatchOnlyWallet)
|
|
49
|
+
expect(wallet['_network']).toBe(Networks.testnet)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('should throw error for invalid network type', () => {
|
|
53
|
+
expect(() => {
|
|
54
|
+
new WatchOnlyWallet(mockAddresses, 123 as any)
|
|
55
|
+
}).toThrow('Network must be a string')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('should throw error for invalid network', () => {
|
|
59
|
+
expect(() => {
|
|
60
|
+
new WatchOnlyWallet(mockAddresses, 'invalidnetwork')
|
|
61
|
+
}).toThrow('Invalid network: invalidnetwork')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('should throw error for null addresses', () => {
|
|
65
|
+
expect(() => {
|
|
66
|
+
new WatchOnlyWallet(null as any)
|
|
67
|
+
}).toThrow('addresesToWatch is required')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('should throw error for non-array addresses', () => {
|
|
71
|
+
expect(() => {
|
|
72
|
+
new WatchOnlyWallet('not-an-array' as any)
|
|
73
|
+
}).toThrow('addressesToWatch must be an array')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('should throw error for empty addresses array', () => {
|
|
77
|
+
expect(() => {
|
|
78
|
+
new WatchOnlyWallet([])
|
|
79
|
+
}).toThrow('addressesToWatch cannot be empty')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('should throw error for invalid address object', () => {
|
|
83
|
+
expect(() => {
|
|
84
|
+
new WatchOnlyWallet(['invalid'] as any)
|
|
85
|
+
}).toThrow('addressesToWatch[0] must be an object')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('should throw error for missing address property', () => {
|
|
89
|
+
expect(() => {
|
|
90
|
+
new WatchOnlyWallet([{ notAddress: 'test' }] as any)
|
|
91
|
+
}).toThrow('addressesToWatch[0].address must be a string')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('should throw error for empty address string', () => {
|
|
95
|
+
expect(() => {
|
|
96
|
+
new WatchOnlyWallet([{ address: '' }])
|
|
97
|
+
}).toThrow('addressesToWatch[0].address cannot be empty')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('should throw error for duplicate addresses', () => {
|
|
101
|
+
const duplicates = [
|
|
102
|
+
{ address: 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc' },
|
|
103
|
+
{ address: 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc' }
|
|
104
|
+
]
|
|
105
|
+
expect(() => {
|
|
106
|
+
new WatchOnlyWallet(duplicates)
|
|
107
|
+
}).toThrow('Duplicate address found')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('should handle optional xPub property', () => {
|
|
111
|
+
const addressesWithXPub = [
|
|
112
|
+
{
|
|
113
|
+
address: 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc',
|
|
114
|
+
xPub: {} as any
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
const wallet = new WatchOnlyWallet(addressesWithXPub)
|
|
118
|
+
|
|
119
|
+
expect(wallet.getWatchedAddresses()[0].xPub).toBeDefined()
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('should handle optional derivationPath property', () => {
|
|
123
|
+
const addressesWithPath = [
|
|
124
|
+
{
|
|
125
|
+
address: 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc',
|
|
126
|
+
derivationPath: "m/44'/29223'/0'/0/0"
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
const wallet = new WatchOnlyWallet(addressesWithPath)
|
|
130
|
+
|
|
131
|
+
expect(wallet.getWatchedAddresses()[0].derivationPath).toBe("m/44'/29223'/0'/0/0")
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
describe('newTransaction', () => {
|
|
136
|
+
let wallet: WatchOnlyWallet
|
|
137
|
+
|
|
138
|
+
beforeEach(() => {
|
|
139
|
+
wallet = new WatchOnlyWallet(mockAddresses, 'testnet')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('should create transaction without parameters', () => {
|
|
143
|
+
const tx = wallet.newTransaction()
|
|
144
|
+
|
|
145
|
+
expect(tx).toBeDefined()
|
|
146
|
+
expect(tx.network).toBe(Networks.testnet)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('should create transaction with TransactionBuilder', () => {
|
|
150
|
+
const txBuilder = new TransactionBuilder()
|
|
151
|
+
const tx = wallet.newTransaction(txBuilder)
|
|
152
|
+
|
|
153
|
+
expect(tx).toBeDefined()
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test('should create transaction with hex string', () => {
|
|
157
|
+
const hexTx = 'deadbeef'
|
|
158
|
+
const tx = wallet.newTransaction(hexTx)
|
|
159
|
+
|
|
160
|
+
expect(tx).toBeDefined()
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
test('should create transaction with buffer', () => {
|
|
164
|
+
const bufferTx = Buffer.from('deadbeef', 'hex')
|
|
165
|
+
const tx = wallet.newTransaction(bufferTx)
|
|
166
|
+
|
|
167
|
+
expect(tx).toBeDefined()
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
describe('sendTransaction', () => {
|
|
172
|
+
let wallet: WatchOnlyWallet
|
|
173
|
+
|
|
174
|
+
beforeEach(() => {
|
|
175
|
+
wallet = new WatchOnlyWallet(mockAddresses)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test('should broadcast transaction and return txid', async () => {
|
|
179
|
+
const txHex = 'deadbeef'
|
|
180
|
+
const result = await wallet.sendTransaction(txHex)
|
|
181
|
+
|
|
182
|
+
expect(result).toBe('txid123')
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
describe('subscribeToAddressNotifications', () => {
|
|
187
|
+
let wallet: WatchOnlyWallet
|
|
188
|
+
|
|
189
|
+
beforeEach(() => {
|
|
190
|
+
wallet = new WatchOnlyWallet(mockAddresses)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
test('should subscribe to address notifications', async () => {
|
|
194
|
+
const callback = vi.fn()
|
|
195
|
+
await wallet.subscribeToAddressNotifications(callback)
|
|
196
|
+
|
|
197
|
+
// Should not throw and complete successfully
|
|
198
|
+
expect(callback).not.toHaveBeenCalled() // Callback is passed to provider, not called directly
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
describe('getWatchedAddresses', () => {
|
|
203
|
+
test('should return copy of watched addresses', () => {
|
|
204
|
+
const wallet = new WatchOnlyWallet(mockAddresses)
|
|
205
|
+
const addresses = wallet.getWatchedAddresses()
|
|
206
|
+
|
|
207
|
+
expect(addresses).toHaveLength(2)
|
|
208
|
+
expect(addresses).not.toBe(wallet['_addressesToWatch']) // Should be a copy
|
|
209
|
+
expect(addresses[0].address).toBe(mockAddresses[0].address)
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
describe('validation edge cases', () => {
|
|
214
|
+
test('should trim whitespace from addresses', () => {
|
|
215
|
+
const addressesWithWhitespace = [
|
|
216
|
+
{ address: ' nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc ' }
|
|
217
|
+
]
|
|
218
|
+
const wallet = new WatchOnlyWallet(addressesWithWhitespace)
|
|
219
|
+
|
|
220
|
+
expect(wallet.getWatchedAddresses()[0].address).toBe('nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc')
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
test('should handle mixed address formats', () => {
|
|
224
|
+
const mixedAddresses = [
|
|
225
|
+
'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc',
|
|
226
|
+
{ address: 'nexatest:nqtsq5g5dsgh6mwjchqypn8hvdrjue0xpmz293fl7rm926xv' }
|
|
227
|
+
]
|
|
228
|
+
// This should work with the updated from() method in transaction creator
|
|
229
|
+
const wallet = new WatchOnlyWallet([
|
|
230
|
+
{ address: mixedAddresses[0] as string },
|
|
231
|
+
mixedAddresses[1] as WatchOnlyAddress
|
|
232
|
+
])
|
|
233
|
+
|
|
234
|
+
expect(wallet.getWatchedAddresses()).toHaveLength(2)
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
describe('network handling', () => {
|
|
239
|
+
test('should default to mainnet when network is undefined', () => {
|
|
240
|
+
const wallet = new WatchOnlyWallet(mockAddresses, undefined)
|
|
241
|
+
|
|
242
|
+
expect(wallet['_network']).toBe(Networks.mainnet)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
test('should handle testnet correctly', () => {
|
|
246
|
+
const wallet = new WatchOnlyWallet(mockAddresses, 'testnet')
|
|
247
|
+
|
|
248
|
+
expect(wallet['_network']).toBe(Networks.testnet)
|
|
249
|
+
})
|
|
250
|
+
})
|
|
251
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import walletSdk from "../src/index";
|
|
3
|
+
|
|
4
|
+
describe('#versionGuard', function() {
|
|
5
|
+
test('global._walletSdk_ver should be defined', () => {
|
|
6
|
+
expect(global._walletSdk_ver).toBe(walletSdk.version);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('throw an error if version is already defined', () => {
|
|
10
|
+
expect(() => walletSdk.versionGuard('version')).toThrow('More than one instance of Wallet SDK');
|
|
11
|
+
});
|
|
12
|
+
});
|