mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex.git
synced 2026-09-21 16:52:00 -07:00
528 lines
17 KiB
JavaScript
528 lines
17 KiB
JavaScript
|
|
/*
|
|
Unit tests for the adapter/wallet.js library.
|
|
*/
|
|
|
|
// Global npm libraries
|
|
import { assert } from 'chai'
|
|
import sinon from 'sinon'
|
|
|
|
import WalletAdapter from '../../../src/adapters/wallet.js'
|
|
import { MockBchWallet } from '../mocks/adapters/wallet.js'
|
|
import { BchTokenSweepMock } from '../mocks/bch-token-sweep-mock.js'
|
|
import offerMockData from '../mocks/use-cases/offer-mock-data.js'
|
|
import fs from 'fs'
|
|
import * as url from 'url'
|
|
|
|
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
|
|
|
const testWalletFile = `${__dirname.toString()}/../../../test-wallet.json`
|
|
|
|
describe('#wallet', () => {
|
|
let uut
|
|
let sandbox
|
|
let testWalletData
|
|
|
|
before(() => {
|
|
// try to remove the test wallet file
|
|
try {
|
|
fs.unlinkSync(testWalletFile)
|
|
} catch (error) {
|
|
console.log('error fsUnlinkSync: ', error)
|
|
|
|
// silently fail
|
|
}
|
|
})
|
|
after(() => {
|
|
// try to remove the test wallet file
|
|
try {
|
|
fs.unlinkSync(testWalletFile)
|
|
} catch (error) {
|
|
console.log('error fsUnlinkSync: ', error)
|
|
// silently fail
|
|
}
|
|
})
|
|
|
|
beforeEach(() => {
|
|
uut = new WalletAdapter()
|
|
uut.BchWallet = MockBchWallet
|
|
uut.bchWallet = new MockBchWallet()
|
|
uut.BchTokenSweep = BchTokenSweepMock
|
|
uut.WALLET_FILE = testWalletFile
|
|
uut.bitcoinJs = {
|
|
Transaction: {
|
|
fromHex: () => { return {} },
|
|
SIGHASH_ALL: () => { return {} }
|
|
},
|
|
TransactionBuilder: {
|
|
fromTransaction: () => { return { sign: () => { return {} }, build: () => { return { toHex: () => { return 'hex' } } } } }
|
|
},
|
|
ECPair: {
|
|
fromWIF: () => { return {} }
|
|
}
|
|
}
|
|
sandbox = sinon.createSandbox()
|
|
})
|
|
|
|
afterEach(() => sandbox.restore())
|
|
|
|
describe('#openWallet', () => {
|
|
it('should create a new wallet when wallet file does not exist', async () => {
|
|
// Ensure we open the test file, not the production wallet file.
|
|
const result = await uut.openWallet()
|
|
testWalletData = result
|
|
// console.log('result: ', result)
|
|
assert.property(result, 'mnemonic')
|
|
assert.property(result, 'privateKey')
|
|
assert.property(result, 'publicKey')
|
|
assert.property(result, 'cashAddress')
|
|
assert.property(result, 'address')
|
|
assert.property(result, 'slpAddress')
|
|
assert.property(result, 'legacyAddress')
|
|
assert.property(result, 'hdPath')
|
|
})
|
|
|
|
it('should open existing wallet file', async () => {
|
|
// This test case uses the file created in the previous test case.
|
|
// Ensure we open the test file, not the production wallet file.
|
|
const result = await uut.openWallet()
|
|
// console.log('result: ', result)
|
|
assert.property(result, 'mnemonic')
|
|
assert.property(result, 'privateKey')
|
|
assert.property(result, 'publicKey')
|
|
assert.property(result, 'cashAddress')
|
|
assert.property(result, 'address')
|
|
assert.property(result, 'slpAddress')
|
|
assert.property(result, 'legacyAddress')
|
|
assert.property(result, 'hdPath')
|
|
})
|
|
|
|
it('should catch and throw an error', async () => {
|
|
try {
|
|
uut.WALLET_FILE = ''
|
|
// Force an error
|
|
uut.BchWallet = () => {
|
|
}
|
|
await uut.openWallet()
|
|
// console.log('result: ', result)
|
|
assert.fail('Unexpected code path')
|
|
} catch (err) {
|
|
// console.log('err: ', err)
|
|
assert.include(err.message, 'this.BchWallet is not a constructor')
|
|
}
|
|
})
|
|
})
|
|
describe('#instanceWallet', () => {
|
|
it('should create an instance of BchWallet with rest-api', async () => {
|
|
uut.config.useFullStackCash = true
|
|
const result = await uut.instanceWallet(testWalletData)
|
|
assert.equal(result.advancedOptions.interface, 'rest-api')
|
|
})
|
|
|
|
it('should create an instance of BchWallet with consumer-api', async () => {
|
|
uut.config.useFullStackCash = false
|
|
const result = await uut.instanceWallet(testWalletData)
|
|
assert.equal(result.advancedOptions.interface, 'consumer-api')
|
|
})
|
|
it('should catch and throw an error if no mnemonic is provided', async () => {
|
|
try {
|
|
// Force an error
|
|
await uut.instanceWallet({})
|
|
assert.fail('Unexpected code path')
|
|
} catch (err) {
|
|
// console.log('err: ', err)
|
|
assert.include(err.message, 'Wallet data is not formatted correctly. Can not read mnemonic in wallet file!')
|
|
}
|
|
})
|
|
})
|
|
describe('#incrementNextAddress', () => {
|
|
it('should increment the nextAddress property', async () => {
|
|
const result = await uut.incrementNextAddress()
|
|
assert.equal(result, 2)
|
|
})
|
|
|
|
it('should catch and throw an error', async () => {
|
|
try {
|
|
// Force an error
|
|
sandbox.stub(uut, 'openWallet').rejects(new Error('test error'))
|
|
await uut.incrementNextAddress()
|
|
assert.fail('Unexpected code path')
|
|
} catch (err) {
|
|
assert.include(err.message, 'test error')
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('#getKeyPair', () => {
|
|
it('should return an object with a key pair', async () => {
|
|
// Ensure we open the test file, not the production wallet file.
|
|
// uut.WALLET_FILE = testWalletFile
|
|
uut.config.walletFile = testWalletFile
|
|
|
|
const result = await uut.getKeyPair()
|
|
// console.log('result: ', result)
|
|
|
|
assert.property(result, 'cashAddress')
|
|
assert.property(result, 'wif')
|
|
assert.property(result, 'hdIndex')
|
|
})
|
|
|
|
it('should catch and throw an error', async () => {
|
|
try {
|
|
// Force an error
|
|
sandbox
|
|
.stub(uut, 'incrementNextAddress')
|
|
.rejects(new Error('test error'))
|
|
await uut.getKeyPair()
|
|
assert.fail('Unexpected code path')
|
|
} catch (err) {
|
|
assert.include(err.message, 'test error')
|
|
}
|
|
})
|
|
})
|
|
describe('#generateSignature', () => {
|
|
it('should sign message', async () => {
|
|
const result = await uut.generateSignature('unit test message')
|
|
// console.log('result: ', result)
|
|
|
|
assert.isString(result)
|
|
})
|
|
|
|
it('should catch and throw an error', async () => {
|
|
try {
|
|
await uut.generateSignature()
|
|
} catch (err) {
|
|
assert.include(err.message, 'message is required!')
|
|
}
|
|
})
|
|
})
|
|
describe('#generatePartialTx', () => {
|
|
it('should generate tx for fungible token', async () => {
|
|
const utxoInfo = {
|
|
txid: '241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
|
vout: 0,
|
|
sats: 1000,
|
|
wif: 'L2fzmF5hCeTZRVnr7Cyu66ucJAAKhkhkfinGVuUAKDtvAfwEx8a3'
|
|
}
|
|
|
|
sandbox.stub(uut.bchWallet, 'getTxData').resolves([offerMockData.fungibleTokenData01.genesisData])
|
|
const result = await uut.generatePartialTx(offerMockData.fungibleOffer01, utxoInfo)
|
|
|
|
assert.isString(result)
|
|
})
|
|
it('should generate tx for nft', async () => {
|
|
const utxoInfo = {
|
|
txid: '241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
|
vout: 0,
|
|
sats: 1000,
|
|
wif: 'L2fzmF5hCeTZRVnr7Cyu66ucJAAKhkhkfinGVuUAKDtvAfwEx8a3'
|
|
}
|
|
|
|
sandbox.stub(uut.bchWallet, 'getTxData').resolves([offerMockData.nftTokenData01.genesisData])
|
|
const result = await uut.generatePartialTx(offerMockData.nftOffer01, utxoInfo)
|
|
// console.log('result: ', result)
|
|
|
|
assert.isString(result)
|
|
})
|
|
|
|
it('should handle unknown token type', async () => {
|
|
try {
|
|
const utxoInfo = {
|
|
txid: '241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
|
vout: 0,
|
|
sats: 1000,
|
|
wif: 'L2fzmF5hCeTZRVnr7Cyu66ucJAAKhkhkfinGVuUAKDtvAfwEx8a3'
|
|
}
|
|
|
|
const offerMock = Object.assign({}, offerMockData.nftOffer01)
|
|
offerMock.tokenType = 100
|
|
await uut.generatePartialTx(offerMock, utxoInfo)
|
|
} catch (err) {
|
|
assert.include(err.message, 'Unknown token type')
|
|
}
|
|
})
|
|
it('should handle multiple outputs', async () => {
|
|
const utxoInfo = {
|
|
txid: '241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
|
vout: 0,
|
|
sats: 1000,
|
|
wif: 'L2fzmF5hCeTZRVnr7Cyu66ucJAAKhkhkfinGVuUAKDtvAfwEx8a3'
|
|
}
|
|
|
|
sandbox.stub(uut.bchWallet, 'getTxData').resolves([offerMockData.nftTokenData01.genesisData])
|
|
sandbox.stub(uut.bchWallet.bchjs.SLP.NFT1, 'generateNFTChildSendOpReturn').returns({ outputs: 5 })
|
|
const res = await uut.generatePartialTx(offerMockData.nftOffer01, utxoInfo)
|
|
assert.isUndefined(res)
|
|
})
|
|
})
|
|
|
|
describe('#moveTokens', () => {
|
|
it('should move tokens', async () => {
|
|
sandbox.stub(uut, 'getKeyPair').resolves({
|
|
cashAddress: 'cashAddress',
|
|
wif: 'wif',
|
|
hdIndex: 11
|
|
})
|
|
const input = {
|
|
tokenId: uut.bchWallet.utxos.utxoStore.slpUtxos.type1.tokens[0].tokenId,
|
|
qty: 1
|
|
}
|
|
const res = await uut.moveTokens(input)
|
|
assert.isObject(res)
|
|
})
|
|
|
|
it('should throw an error if the tokenId is not found', async () => {
|
|
try {
|
|
sandbox.stub(uut, 'getKeyPair').resolves({
|
|
cashAddress: 'cashAddress',
|
|
wif: 'wif',
|
|
hdIndex: 11
|
|
})
|
|
const input = {
|
|
tokenId: '241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
|
qty: 1
|
|
}
|
|
await uut.moveTokens(input)
|
|
assert.fail('Unexpected code path')
|
|
} catch (error) {
|
|
assert.equal(error.message, 'tokenId not found!')
|
|
}
|
|
})
|
|
|
|
it('should throw an error if the tokenId is not provided', async () => {
|
|
try {
|
|
await uut.moveTokens({
|
|
qty: 1
|
|
})
|
|
assert.fail('Unexpected code path')
|
|
} catch (error) {
|
|
assert.equal(error.message, 'tokenId must be a string!')
|
|
}
|
|
})
|
|
it('should throw an error if the qty is not provided', async () => {
|
|
try {
|
|
await uut.moveTokens({
|
|
tokenId: 'tokenId'
|
|
})
|
|
assert.fail('Unexpected code path')
|
|
} catch (error) {
|
|
assert.equal(error.message, 'qty must be a number!')
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('#moveBch', () => {
|
|
it('should move bch', async () => {
|
|
sandbox.stub(uut, 'getKeyPair').resolves({
|
|
cashAddress: 'cashAddress',
|
|
wif: 'wif',
|
|
hdIndex: 11
|
|
})
|
|
|
|
const res = await uut.moveBch(100)
|
|
assert.isObject(res)
|
|
})
|
|
|
|
it('should throw an error if the amountSat is not provided', async () => {
|
|
try {
|
|
sandbox.stub(uut, 'getKeyPair').resolves({
|
|
cashAddress: 'cashAddress',
|
|
wif: 'wif',
|
|
hdIndex: 11
|
|
})
|
|
|
|
await uut.moveBch()
|
|
assert.fail('Unexpected code path')
|
|
} catch (error) {
|
|
assert.equal(error.message, 'amountSat is required!')
|
|
}
|
|
})
|
|
})
|
|
describe('#moveTokensFromCustomWallet', () => {
|
|
it('should move tokens from a custom wallet', async () => {
|
|
sandbox.stub(uut, 'getKeyPair').resolves({
|
|
cashAddress: 'cashAddress',
|
|
wif: 'wif',
|
|
hdIndex: 11
|
|
})
|
|
|
|
const customWallet = new MockBchWallet()
|
|
customWallet.getKeyPair = () => { return { cashAddress: 'cashAddress', wif: 'wif', hdIndex: 11 } }
|
|
customWallet.sendTokens = () => { return 'move token tx id result' }
|
|
customWallet.utxos.utxoStore.slpUtxos.type1.tokens = [
|
|
{
|
|
tokenId: 'tokenId',
|
|
tokenType: 1,
|
|
qty: 1
|
|
}
|
|
]
|
|
const inObj = {
|
|
qty: 1,
|
|
wallet: customWallet,
|
|
tokenId: 'tokenId'
|
|
}
|
|
const result = await uut.moveTokensFromCustomWallet(inObj)
|
|
assert.equal(result.txid, 'move token tx id result')
|
|
assert.equal(result.hdIndex, 11)
|
|
assert.equal(result.tokenType, 1)
|
|
assert.equal(result.vout, 1)
|
|
})
|
|
it('should throw an error if the wallet is not provided', async () => {
|
|
try {
|
|
const inObj = {
|
|
qty: 1,
|
|
tokenId: 'tokenId'
|
|
}
|
|
await uut.moveTokensFromCustomWallet(inObj)
|
|
assert.fail('Unexpected code path')
|
|
} catch (error) {
|
|
assert.equal(error.message, 'wallet is required!')
|
|
}
|
|
})
|
|
it('should throw an error if the tokenId is not provided', async () => {
|
|
try {
|
|
const inObj = {
|
|
qty: 1,
|
|
wallet: new MockBchWallet()
|
|
}
|
|
await uut.moveTokensFromCustomWallet(inObj)
|
|
assert.fail('Unexpected code path')
|
|
} catch (error) {
|
|
assert.equal(error.message, 'tokenId must be a string!')
|
|
}
|
|
})
|
|
it('should throw an error if the qty is not provided', async () => {
|
|
try {
|
|
const inObj = {
|
|
tokenId: 'tokenId',
|
|
wallet: new MockBchWallet()
|
|
}
|
|
await uut.moveTokensFromCustomWallet(inObj)
|
|
assert.fail('Unexpected code path')
|
|
} catch (error) {
|
|
assert.equal(error.message, 'qty must be a number!')
|
|
}
|
|
})
|
|
})
|
|
describe('#deseralizeTx', () => {
|
|
it('should throw an error if the hex is not provided', async () => {
|
|
try {
|
|
await uut.deseralizeTx()
|
|
assert.fail('Unexpected code path')
|
|
} catch (error) {
|
|
assert.equal(error.message, 'hex must be a string!')
|
|
}
|
|
})
|
|
it('should decode hex', async () => {
|
|
try {
|
|
sandbox.stub(uut.bchWallet.bchjs.RawTransactions, 'decodeRawTransaction').resolves({})
|
|
const hex = '020000000286b2959cccdb80163b68bb1360abac8e0228ba7b70c779443168cc65c7c42d820100000000ffffffff879b066729eda979f1f062c83b7eccdb9e77f49b417e4723864b3861bf061c24000000006b483045022100b99c6161e18e4e34c1e438b6b03e808703695f217b30da42c3f787798a9f40cc02206d26b4f6799735779d4fac823bc60d276fbb3cea91c66dbfa136cf5b1bd0d8324121028dcd38a58c8295dbd105ef524014f77af4cddfa19b26c50926b4aaeebd95c7ceffffffff030000000000000000376a04534c500001010453454e4420a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b208000000000000006422020000000000001976a914bef6c5180648165704f22e71a1b011ba56336aee88acf81d0000000000001976a914c1f16a3876f8fbe3701a66d3cb3b9c7abb07a06c88ac00000000'
|
|
const result = await uut.deseralizeTx(hex)
|
|
assert.isObject(result)
|
|
} catch (error) {
|
|
assert.fail('Unexpected code path')
|
|
}
|
|
})
|
|
})
|
|
describe('#completeTx', () => {
|
|
it('should throw an error if the hex is not provided', async () => {
|
|
try {
|
|
const hdIndex = 11
|
|
await uut.completeTx(null, hdIndex)
|
|
assert.fail('Unexpected code path')
|
|
} catch (error) {
|
|
assert.equal(error.message, 'hex must be a string!')
|
|
}
|
|
})
|
|
it('should throw an error if the hdIndex is not provided', async () => {
|
|
try {
|
|
const hex = 'hex'
|
|
await uut.completeTx(hex)
|
|
assert.fail('Unexpected code path')
|
|
} catch (error) {
|
|
assert.equal(error.message, 'hdIndex must be a non-negative number!')
|
|
}
|
|
})
|
|
it('should throw an error if the mnemonic is not provided', async () => {
|
|
try {
|
|
const hex = 'hex'
|
|
const hdIndex = 11
|
|
await uut.completeTx(hex, hdIndex)
|
|
assert.fail('Unexpected code path')
|
|
} catch (error) {
|
|
assert.equal(error.message, 'mnemonic must be a string!')
|
|
}
|
|
})
|
|
it('should complete a transaction', async () => {
|
|
sandbox.stub(uut, 'getKeyPair').resolves({
|
|
cashAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
|
wif: 'L5D2UAam8tvo3uii5kpgaGyjvVMimdrXu8nWGQSQjuuAix6ji1YQ',
|
|
hdIndex: 11
|
|
})
|
|
sandbox.stub(uut, 'deseralizeTx').resolves({
|
|
txid: 'complete tx id result'
|
|
})
|
|
sandbox.stub(uut.retryQueue, 'addToQueue').resolves('complete tx id result')
|
|
|
|
const hex = 'hex'
|
|
const hdIndex = 11
|
|
const mnemonic = 'course abstract aerobic deer try switch turtle diet fence affair butter top'
|
|
|
|
const txid = await uut.completeTx(hex, hdIndex, mnemonic)
|
|
assert.equal(txid, 'complete tx id result')
|
|
})
|
|
})
|
|
|
|
describe('#reClaimTokens', () => {
|
|
it('should re-claim tokens', async () => {
|
|
const orderData = {
|
|
hdIndex: 11,
|
|
tokenId: 'tokenId',
|
|
numTokens: 1
|
|
}
|
|
const res = await uut.reclaimTokens(orderData)
|
|
assert.equal(res, 'fakeTxid')
|
|
})
|
|
it('should handle error if order hd index is not provided', async () => {
|
|
try {
|
|
const orderData = {}
|
|
await uut.reclaimTokens(orderData)
|
|
assert.fail('Unexpected code path')
|
|
} catch (error) {
|
|
assert.equal(error.message, 'orderData.hdIndex is required!')
|
|
}
|
|
})
|
|
})
|
|
describe('#cid2json', () => {
|
|
it('should throw error if cid is not provided', async () => {
|
|
try {
|
|
await uut.cid2json()
|
|
assert.fail('Unexpected code path')
|
|
} catch (error) {
|
|
assert.equal(error.message, 'urlOrCid must be a string!')
|
|
}
|
|
})
|
|
|
|
it('should convert cid to json', async () => {
|
|
try {
|
|
sandbox.stub(uut.bchWallet, 'cid2json').resolves({ json: offerMockData.mutableDataMock })
|
|
const cid = 'bafkreidr6wfd6mcmwpea7abm5uk5rrprc2wfbcvo5wdcmtlolrpjab5oqm'
|
|
const result = await uut.cid2json(cid)
|
|
assert.isObject(result)
|
|
} catch (error) {
|
|
assert.fail('Unexpected code path')
|
|
}
|
|
})
|
|
it('should convert ipfs url to json', async () => {
|
|
try {
|
|
sandbox.stub(uut.bchWallet, 'cid2json').resolves({ json: offerMockData.mutableDataMock })
|
|
const url = 'https://ipfs.io/ipfs/bafkreidr6wfd6mcmwpea7abm5uk5rrprc2wfbcvo5wdcmtlolrpjab5oqm'
|
|
const result = await uut.cid2json(url)
|
|
assert.isObject(result)
|
|
} catch (error) {
|
|
assert.fail('Unexpected code path')
|
|
}
|
|
})
|
|
})
|
|
})
|