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.
Files changed (48) hide show
  1. package/.parcel-cache/3e09f086f3c4d605-AssetGraph +0 -0
  2. package/.parcel-cache/5eac57ec674cdae8-AssetGraph +0 -0
  3. package/.parcel-cache/data.mdb +0 -0
  4. package/.parcel-cache/e43547b6c9167b58-RequestGraph +0 -0
  5. package/.parcel-cache/ecfe15d74834bbfd-BundleGraph +0 -0
  6. package/.parcel-cache/lock.mdb +0 -0
  7. package/.parcel-cache/snapshot-e43547b6c9167b58.txt +2 -0
  8. package/README.md +445 -0
  9. package/dist/browser/index.js +2456 -0
  10. package/dist/browser/index.js.map +1 -0
  11. package/dist/index.d.ts +918 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +2915 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/index.mjs +2456 -0
  16. package/dist/index.mjs.map +1 -0
  17. package/package.json +90 -0
  18. package/spec.md +257 -0
  19. package/src/index.ts +93 -0
  20. package/src/models/rostrum.entities.ts +159 -0
  21. package/src/models/transaction.entities.ts +46 -0
  22. package/src/models/wallet.entities.ts +42 -0
  23. package/src/network/RostrumProvider.ts +137 -0
  24. package/src/types.ts +0 -0
  25. package/src/utils/CommonUtils.ts +123 -0
  26. package/src/utils/TXUtils.ts +445 -0
  27. package/src/utils/TokenUtils.ts +75 -0
  28. package/src/utils/ValidationUtils.ts +86 -0
  29. package/src/utils/WalletUtils.ts +522 -0
  30. package/src/utils/WatchOnlyTXUtils.ts +275 -0
  31. package/src/wallet/Wallet.ts +397 -0
  32. package/src/wallet/WatchOnlyWallet.ts +169 -0
  33. package/src/wallet/accounts/AccountStore.ts +173 -0
  34. package/src/wallet/accounts/interfaces/BaseAccountInterface.ts +56 -0
  35. package/src/wallet/accounts/models/DappAccount.ts +80 -0
  36. package/src/wallet/accounts/models/DefaultAccount.ts +96 -0
  37. package/src/wallet/accounts/models/VaultAccount.ts +81 -0
  38. package/src/wallet/transactions/WalletTransactionCreator.ts +145 -0
  39. package/src/wallet/transactions/WatchOnlyTransactionCreator.ts +189 -0
  40. package/src/wallet/transactions/interfaces/TransactionCreator.ts +438 -0
  41. package/tests/core/tx/transactioncreator.test.ts +455 -0
  42. package/tests/core/tx/wallettransactioncreator.test.ts +362 -0
  43. package/tests/core/tx/watchonlytransactioncreator.test.ts +258 -0
  44. package/tests/core/wallet/accountstore.test.ts +341 -0
  45. package/tests/core/wallet/wallet.test.ts +69 -0
  46. package/tests/core/watchonlywallet/watchonlywallet.test.ts +251 -0
  47. package/tests/index.test.ts +12 -0
  48. 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
+ });