@dolaned/wallet-sdk-ts
Version:
Wallet SDK for the Nexa blockchain
363 lines (293 loc) • 12.3 kB
text/typescript
import { describe, test, expect, beforeEach, vi } from 'vitest'
import { TransactionBuilder, Networks, PrivateKey } from 'libnexa-ts'
import WalletTransactionCreator from '../../../src/wallet/transactions/WalletTransactionCreator'
import { BaseAccount } from '../../../src/wallet/accounts/interfaces/BaseAccountInterface'
import { AddressKey } from '../../../src/models/wallet.entities'
// Mock the TXUtils functions
vi.mock('../../../src/utils/TXUtils', () => ({
populateTokenAuth: vi.fn().mockResolvedValue([{} as PrivateKey]),
buildCreateGroupTransaction: vi.fn().mockResolvedValue([{} as PrivateKey]),
populateAndDuplicateTokenAuths: vi.fn().mockResolvedValue([{} as PrivateKey]),
prepareDeleteTransaction: vi.fn().mockResolvedValue([{} as PrivateKey]),
populateTokenInputsAndChange: vi.fn().mockResolvedValue([{} as PrivateKey]),
populateNexaInputsAndChange: vi.fn().mockResolvedValue([{} as PrivateKey])
}))
// Mock the rostrumProvider
vi.mock('../../../src/network/RostrumProvider', () => ({
rostrumProvider: {
getUtxo: vi.fn().mockResolvedValue({
tx_hash: 'mock-tx-hash',
amount: 1000000,
scriptpubkey: 'mock-scriptpubkey',
addresses: ['nexatest:address123']
}),
getBlockTip: vi.fn().mockResolvedValue({
height: 100000
})
}
}))
describe('WalletTransactionCreator', () => {
let creator: WalletTransactionCreator
let mockAccount: BaseAccount
beforeEach(() => {
mockAccount = {
accountKeys: {
receiveKeys: [
{
key: { privateKey: {} as PrivateKey },
address: 'nexatest:receive123',
balance: '1000000',
tokensBalance: {}
}
],
changeKeys: [
{
key: { privateKey: {} as PrivateKey },
address: 'nexatest:change123',
balance: '500000',
tokensBalance: {}
}
]
}
} as BaseAccount
creator = new WalletTransactionCreator(mockAccount)
})
describe('constructor', () => {
test('should create instance with account', () => {
expect(creator).toBeInstanceOf(WalletTransactionCreator)
})
test('should create instance with account and transaction builder', () => {
const txBuilder = new TransactionBuilder()
const creator = new WalletTransactionCreator(mockAccount, txBuilder)
expect(creator).toBeInstanceOf(WalletTransactionCreator)
})
test('should throw error for account without receive keys', () => {
const invalidAccount = {
accountKeys: {
receiveKeys: [],
changeKeys: []
}
} as unknown as BaseAccount
expect(() => {
new WalletTransactionCreator(invalidAccount)
}).toThrow('No receive keys available in account')
})
test('should throw error for account without account keys', () => {
const invalidAccount = {} as BaseAccount
expect(() => {
new WalletTransactionCreator(invalidAccount)
}).toThrow('Account keys are not initialized')
})
})
describe('fromAccount', () => {
test('should set account and return this for chaining', () => {
const newAccount = {
accountKeys: {
receiveKeys: [{
key: {privateKey: {} as PrivateKey},
address: 'nexatest:new123',
balance: '0',
tokensBalance: {}
}],
changeKeys: []
}
} as unknown as BaseAccount
const result = creator.fromAccount(newAccount)
expect(result).toBe(creator)
expect(creator['_account']).toBe(newAccount)
})
})
describe('parseTxHex', () => {
test('should add parse operation to builder', () => {
const hex = 'deadbeef'
const result = creator.parseTxHex(hex)
expect(result).toBe(creator)
expect(creator.builder).toHaveLength(1)
})
})
describe('parseTxBuffer', () => {
test('should add parse operation to builder', () => {
const buffer = Buffer.from('deadbeef', 'hex')
const result = creator.parseTxBuffer(buffer)
expect(result).toBe(creator)
expect(creator.builder).toHaveLength(1)
})
})
describe('mint', () => {
test('should add mint operation to builder', () => {
const token = 'nexatest:tqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'
const amount = '1000'
const result = creator.mint(token, amount)
expect(result).toBe(creator)
expect(creator.builder).toHaveLength(1)
})
test('should be chainable', () => {
const token = 'nexatest:tqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'
const amount = '1000'
const result = creator
.mint(token, amount)
.mint(token, amount)
expect(result).toBe(creator)
expect(creator.builder).toHaveLength(2)
})
})
describe('melt', () => {
test('should add melt operation to builder', () => {
const token = 'nexatest:tqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'
const amount = '1000'
const result = creator.melt(token, amount)
expect(result).toBe(creator)
expect(creator.builder).toHaveLength(1)
})
test('should be chainable', () => {
const token = 'nexatest:tqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'
const amount = '1000'
const result = creator
.melt(token, amount)
.melt(token, amount)
expect(result).toBe(creator)
expect(creator.builder).toHaveLength(2)
})
})
describe('populate', () => {
test('should add populate operation to builder', () => {
const result = creator.populate()
expect(result).toBe(creator)
expect(creator.builder).toHaveLength(1)
})
test('should validate account before populating', () => {
const invalidCreator = new WalletTransactionCreator(mockAccount)
invalidCreator['_account'] = null as any
expect(() => {
invalidCreator.populate()
}).toThrow('Account must be set before performing transactions')
})
})
describe('sign', () => {
test('should add sign operation to builder', () => {
const result = creator.sign()
expect(result).toBe(creator)
expect(creator.builder).toHaveLength(1)
})
test('should be chainable', () => {
const result = creator
.populate()
.sign()
expect(result).toBe(creator)
expect(creator.builder).toHaveLength(2)
})
})
describe('findPrivateKeyFromAddress', () => {
test('should find key for existing address', () => {
const result = creator['findPrivateKeyFromAddress']('nexatest:receive123')
expect(result).toBeDefined()
expect(result?.address).toBe('nexatest:receive123')
})
test('should find key in change addresses', () => {
const result = creator['findPrivateKeyFromAddress']('nexatest:change123')
expect(result).toBeDefined()
expect(result?.address).toBe('nexatest:change123')
})
test('should return undefined for non-existent address', () => {
const result = creator['findPrivateKeyFromAddress']('nexatest:nonexistent')
expect(result).toBeUndefined()
})
})
describe('validateAccount', () => {
test('should not throw for valid account', () => {
expect(() => {
creator['validateAccount']()
}).not.toThrow()
})
test('should throw for null account', () => {
creator['_account'] = null as any
expect(() => {
creator['validateAccount']()
}).toThrow('Account must be set before performing transactions')
})
test('should throw for account without keys', () => {
creator['_account'] = {} as BaseAccount
expect(() => {
creator['validateAccount']()
}).toThrow('Account keys are not initialized')
})
test('should throw for account without receive keys', () => {
creator['_account'] = {
accountKeys: {
receiveKeys: [],
changeKeys: []
}
} as unknown as BaseAccount
expect(() => {
creator['validateAccount']()
}).toThrow('No receive keys available in account')
})
})
describe('integration tests', () => {
test('should handle complete transaction flow', () => {
const token = 'nexatest:tqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'
const amount = '1000'
const toAddr = 'nexatest:nqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'
const result = creator
.onNetwork('testnet')
.sendTo(toAddr, amount)
.mint(token, amount)
.populate()
.sign()
expect(result).toBe(creator)
expect(creator.builder).toHaveLength(4) // sendTo, mint, populate, sign
expect(creator.network).toBe(Networks.testnet)
})
test('should handle token operations', () => {
const token = 'nexatest:tqtsq5g5jsdmqqywaqd82lhnnk3a8wqunjz6gtxdtavnnekc'
const amount = '1000'
const result = creator
.mint(token, amount)
.melt(token, amount)
.populate()
.sign()
expect(result).toBe(creator)
expect(creator.builder).toHaveLength(4)
})
test('should handle token creation', () => {
const result = creator
.token('Test Token', 'TEST', 2, 'https://test.com', 'hash123')
.populate()
.sign()
expect(result).toBe(creator)
expect(creator.builder).toHaveLength(3)
})
test('should handle transaction parsing and signing', () => {
const hexTx = 'deadbeef'
const result = creator
.parseTxHex(hexTx)
.sign()
expect(result).toBe(creator)
expect(creator.builder).toHaveLength(2)
})
})
describe('error handling', () => {
test('should handle async errors in builder functions', async () => {
const errorFunction = vi.fn().mockRejectedValue(new Error('Test error'))
creator.builder = [errorFunction]
await expect(creator.build()).rejects.toThrow('Test error')
})
test('should handle validation errors during operations', () => {
const invalidCreator = new WalletTransactionCreator(mockAccount)
invalidCreator['_account'] = null as any
expect(() => {
invalidCreator.mint('token', '1000')
}).not.toThrow() // Error should be thrown when builder executes, not when adding to builder
})
})
describe('network handling', () => {
test('should set network correctly', () => {
const result = creator.onNetwork('testnet')
expect(result).toBe(creator)
expect(creator.network).toBe(Networks.testnet)
})
test('should default to mainnet', () => {
expect(creator.network).toBe(Networks.mainnet)
})
})
})