@dolaned/wallet-sdk-ts
Version:
Wallet SDK for the Nexa blockchain
252 lines (201 loc) • 9.17 kB
text/typescript
import { describe, test, expect, beforeEach, vi } from 'vitest'
import { Networks, TransactionBuilder } from 'libnexa-ts'
import WatchOnlyWallet from '../../../src/wallet/WatchOnlyWallet'
import { WatchOnlyAddress } from '../../../src/models/wallet.entities'
// Mock the rostrumProvider
vi.mock('../../../src/network/RostrumProvider', () => ({
rostrumProvider: {
broadcast: vi.fn().mockResolvedValue('txid123'),
subscribeToAddresses: vi.fn().mockResolvedValue(undefined)
}
}))
// Mock the ValidationUtils
vi.mock('../../../src/utils/ValidationUtils', () => ({
default: {
validateArgument: vi.fn()
}
}))
// Mock the WalletUtils
vi.mock('../../../src/utils/WalletUtils', () => ({
isValidNexaAddress: vi.fn().mockReturnValue(true)
}))
describe('WatchOnlyWallet', () => {
let mockAddresses: WatchOnlyAddress[]
beforeEach(() => {
mockAddresses = [
{ address: 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc' },
{ address: 'nexatest:nqtsq5g5dsgh6mwjchqypn8hvdrjue0xpmz293fl7rm926xv' }
]
})
describe('constructor', () => {
test('should create wallet with valid addresses and default network', () => {
const wallet = new WatchOnlyWallet(mockAddresses)
expect(wallet).toBeInstanceOf(WatchOnlyWallet)
expect(wallet.getWatchedAddresses()).toHaveLength(2)
expect(wallet.getWatchedAddresses()[0].address).toBe(mockAddresses[0].address)
})
test('should create wallet with custom network', () => {
const wallet = new WatchOnlyWallet(mockAddresses, 'testnet')
expect(wallet).toBeInstanceOf(WatchOnlyWallet)
expect(wallet['_network']).toBe(Networks.testnet)
})
test('should throw error for invalid network type', () => {
expect(() => {
new WatchOnlyWallet(mockAddresses, 123 as any)
}).toThrow('Network must be a string')
})
test('should throw error for invalid network', () => {
expect(() => {
new WatchOnlyWallet(mockAddresses, 'invalidnetwork')
}).toThrow('Invalid network: invalidnetwork')
})
test('should throw error for null addresses', () => {
expect(() => {
new WatchOnlyWallet(null as any)
}).toThrow('addresesToWatch is required')
})
test('should throw error for non-array addresses', () => {
expect(() => {
new WatchOnlyWallet('not-an-array' as any)
}).toThrow('addressesToWatch must be an array')
})
test('should throw error for empty addresses array', () => {
expect(() => {
new WatchOnlyWallet([])
}).toThrow('addressesToWatch cannot be empty')
})
test('should throw error for invalid address object', () => {
expect(() => {
new WatchOnlyWallet(['invalid'] as any)
}).toThrow('addressesToWatch[0] must be an object')
})
test('should throw error for missing address property', () => {
expect(() => {
new WatchOnlyWallet([{ notAddress: 'test' }] as any)
}).toThrow('addressesToWatch[0].address must be a string')
})
test('should throw error for empty address string', () => {
expect(() => {
new WatchOnlyWallet([{ address: '' }])
}).toThrow('addressesToWatch[0].address cannot be empty')
})
test('should throw error for duplicate addresses', () => {
const duplicates = [
{ address: 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc' },
{ address: 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc' }
]
expect(() => {
new WatchOnlyWallet(duplicates)
}).toThrow('Duplicate address found')
})
test('should handle optional xPub property', () => {
const addressesWithXPub = [
{
address: 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc',
xPub: {} as any
}
]
const wallet = new WatchOnlyWallet(addressesWithXPub)
expect(wallet.getWatchedAddresses()[0].xPub).toBeDefined()
})
test('should handle optional derivationPath property', () => {
const addressesWithPath = [
{
address: 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc',
derivationPath: "m/44'/29223'/0'/0/0"
}
]
const wallet = new WatchOnlyWallet(addressesWithPath)
expect(wallet.getWatchedAddresses()[0].derivationPath).toBe("m/44'/29223'/0'/0/0")
})
})
describe('newTransaction', () => {
let wallet: WatchOnlyWallet
beforeEach(() => {
wallet = new WatchOnlyWallet(mockAddresses, 'testnet')
})
test('should create transaction without parameters', () => {
const tx = wallet.newTransaction()
expect(tx).toBeDefined()
expect(tx.network).toBe(Networks.testnet)
})
test('should create transaction with TransactionBuilder', () => {
const txBuilder = new TransactionBuilder()
const tx = wallet.newTransaction(txBuilder)
expect(tx).toBeDefined()
})
test('should create transaction with hex string', () => {
const hexTx = 'deadbeef'
const tx = wallet.newTransaction(hexTx)
expect(tx).toBeDefined()
})
test('should create transaction with buffer', () => {
const bufferTx = Buffer.from('deadbeef', 'hex')
const tx = wallet.newTransaction(bufferTx)
expect(tx).toBeDefined()
})
})
describe('sendTransaction', () => {
let wallet: WatchOnlyWallet
beforeEach(() => {
wallet = new WatchOnlyWallet(mockAddresses)
})
test('should broadcast transaction and return txid', async () => {
const txHex = 'deadbeef'
const result = await wallet.sendTransaction(txHex)
expect(result).toBe('txid123')
})
})
describe('subscribeToAddressNotifications', () => {
let wallet: WatchOnlyWallet
beforeEach(() => {
wallet = new WatchOnlyWallet(mockAddresses)
})
test('should subscribe to address notifications', async () => {
const callback = vi.fn()
await wallet.subscribeToAddressNotifications(callback)
// Should not throw and complete successfully
expect(callback).not.toHaveBeenCalled() // Callback is passed to provider, not called directly
})
})
describe('getWatchedAddresses', () => {
test('should return copy of watched addresses', () => {
const wallet = new WatchOnlyWallet(mockAddresses)
const addresses = wallet.getWatchedAddresses()
expect(addresses).toHaveLength(2)
expect(addresses).not.toBe(wallet['_addressesToWatch']) // Should be a copy
expect(addresses[0].address).toBe(mockAddresses[0].address)
})
})
describe('validation edge cases', () => {
test('should trim whitespace from addresses', () => {
const addressesWithWhitespace = [
{ address: ' nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc ' }
]
const wallet = new WatchOnlyWallet(addressesWithWhitespace)
expect(wallet.getWatchedAddresses()[0].address).toBe('nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc')
})
test('should handle mixed address formats', () => {
const mixedAddresses = [
'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc',
{ address: 'nexatest:nqtsq5g5dsgh6mwjchqypn8hvdrjue0xpmz293fl7rm926xv' }
]
// This should work with the updated from() method in transaction creator
const wallet = new WatchOnlyWallet([
{ address: mixedAddresses[0] as string },
mixedAddresses[1] as WatchOnlyAddress
])
expect(wallet.getWatchedAddresses()).toHaveLength(2)
})
})
describe('network handling', () => {
test('should default to mainnet when network is undefined', () => {
const wallet = new WatchOnlyWallet(mockAddresses, undefined)
expect(wallet['_network']).toBe(Networks.mainnet)
})
test('should handle testnet correctly', () => {
const wallet = new WatchOnlyWallet(mockAddresses, 'testnet')
expect(wallet['_network']).toBe(Networks.testnet)
})
})
})