diff --git a/src/adapters/wallet.js b/src/adapters/wallet.js index d1a775e..c0898e3 100644 --- a/src/adapters/wallet.js +++ b/src/adapters/wallet.js @@ -303,6 +303,36 @@ class WalletAdapter { // return true } + + // Move tokens to an address controlled by the HD wallet, to generate a + // segregated UTXO. + async moveTokens (inObj = {}) { + try { + const { tokenId, qty } = inObj + + const keyPair = await this.getKeyPair() + console.log('keyPair: ', keyPair) + + const receiver = { + address: keyPair.cashAddress, + tokenId, + qty + } + + const txid = await this.bchWallet.sendTokens(receiver, 3) + + const utxoInfo = { + txid, + vout: 1, + hdIndex: keyPair.hdIndex + } + + return utxoInfo + } catch (err) { + console.error('Error in wallet.js/moveTokens()') + throw err + } + } } module.exports = WalletAdapter diff --git a/src/use-cases/offer/index.js b/src/use-cases/offer/index.js index 5acda64..f23801e 100644 --- a/src/use-cases/offer/index.js +++ b/src/use-cases/offer/index.js @@ -119,28 +119,30 @@ class OfferUseCases { // Create a partially signed transaction. // https://github.com/Permissionless-Software-Foundation/bch-js-examples/blob/master/bch/applications/collaborate/sell-slp/e2e-exchange/step2-purchase-tx.js#L59 - const partialTxHex = await this.adapters.wallet.generatePartialTx(offerInfo) - // return partialTxHex + // const partialTxHex = await this.adapters.wallet.generatePartialTx(offerInfo) + // // return partialTxHex + // + // // Create valid Offer object + // const takenOfferInfo = Object.assign({}, offerInfo) + // takenOfferInfo.patialTxHex = partialTxHex + // delete takenOfferInfo.p2wdbHash + // delete takenOfferInfo._id + // takenOfferInfo.offerHash = offerInfo.p2wdbHash + // + // // Write offer info to the P2WDB + // // TODO: This will trigger the webhook. Find some way of triggering the + // // webhook on new offers, but not on counteroffers + // const p2wdbObj = { + // wif: this.adapters.wallet.bchWallet.walletInfo.privateKey, + // data: takenOfferInfo, + // appId: this.config.p2wdbAppId + // } + // const hash = await this.adapters.p2wdb.write(p2wdbObj) + // + // // Return the P2WDB CID + // return hash - // Create valid Offer object - const takenOfferInfo = Object.assign({}, offerInfo) - takenOfferInfo.patialTxHex = partialTxHex - delete takenOfferInfo.p2wdbHash - delete takenOfferInfo._id - takenOfferInfo.offerHash = offerInfo.p2wdbHash - - // Write offer info to the P2WDB - // TODO: This will trigger the webhook. Find some way of triggering the - // webhook on new offers, but not on counteroffers - const p2wdbObj = { - wif: this.adapters.wallet.bchWallet.walletInfo.privateKey, - data: takenOfferInfo, - appId: this.config.p2wdbAppId - } - const hash = await this.adapters.p2wdb.write(p2wdbObj) - - // Return the P2WDB CID - return hash + return 'fake-hash' } catch (err) { console.error('Error in use-cases/offer/takeOffer(): ', err) throw err @@ -155,6 +157,7 @@ class OfferUseCases { // console.log(`walletInfo: ${JSON.stringify(this.adapters.wallet.bchWallet.walletInfo, null, 2)}`) await this.adapters.wallet.bchWallet.walletInfoPromise + // console.log(`utxos: ${JSON.stringify(this.adapters.wallet.bchWallet.utxos.utxoStore, null, 2)}`) // Ensure the app wallet has enough funds to write to the P2WDB. const wif = this.adapters.wallet.bchWallet.walletInfo.privateKey @@ -164,12 +167,19 @@ class OfferUseCases { if (offerEntity.buyOrSell.includes('sell')) { // Sell Offer + // Calculate the sats needed + const satsNeeded = offerEntity.numTokens * parseInt(offerEntity.rateInBaseUnit) + if (isNaN(satsNeeded)) { + throw new Error('Could not calculate sats needed!') + } + // Ensure the app wallet controlls enough BCH to pay for the tokens. - const satsNeeded = offerEntity.numTokens * parseInt(offerEntity.rateInSats) const balance = await this.adapters.wallet.bchWallet.getBalance() console.log(`wallet balance: ${balance}, sats needed: ${satsNeeded}`) const SATS_MARGIN = 5000 - if (satsNeeded + SATS_MARGIN > balance) { throw new Error('App wallet does not control enough BCH to purchase the tokens.') } + if (satsNeeded + SATS_MARGIN > balance) { + throw new Error('App wallet does not control enough BCH to purchase the tokens.') + } // } else { diff --git a/src/use-cases/order.js b/src/use-cases/order.js index c8b83be..3a802c2 100644 --- a/src/use-cases/order.js +++ b/src/use-cases/order.js @@ -37,7 +37,11 @@ class OrderLib { await this.ensureFunds(orderEntity) // Move the tokens to holding address. - const utxoInfo = await this.moveTokens(orderEntity) + const moveObj = { + tokenId: orderEntity.tokenId, + qty: orderEntity.numTokens + } + const utxoInfo = await this.adapters.wallet.moveTokens(moveObj) console.log('utxoInfo: ', utxoInfo) // Update the UTXO store for the wallet. @@ -76,31 +80,31 @@ class OrderLib { // Move the tokens indicated in the order to a temporary holding address. // This will generate the UTXO used in the Signal message. This function // moves the funds and returns the UTXO information. - async moveTokens (orderEntity) { - try { - const keyPair = await this.adapters.wallet.getKeyPair() - console.log('keyPair: ', keyPair) - - const receiver = { - address: keyPair.cashAddress, - tokenId: orderEntity.tokenId, - qty: orderEntity.numTokens - } - - const txid = await this.adapters.wallet.bchWallet.sendTokens(receiver, 3) - - const utxoInfo = { - txid, - vout: 1, - hdIndex: keyPair.hdIndex - } - - return utxoInfo - } catch (err) { - console.error('Error in moveTokens(): ', err) - throw err - } - } + // async moveTokens (orderEntity) { + // try { + // const keyPair = await this.adapters.wallet.getKeyPair() + // console.log('keyPair: ', keyPair) + // + // const receiver = { + // address: keyPair.cashAddress, + // tokenId: orderEntity.tokenId, + // qty: orderEntity.numTokens + // } + // + // const txid = await this.adapters.wallet.bchWallet.sendTokens(receiver, 3) + // + // const utxoInfo = { + // txid, + // vout: 1, + // hdIndex: keyPair.hdIndex + // } + // + // return utxoInfo + // } catch (err) { + // console.error('Error in moveTokens(): ', err) + // throw err + // } + // } // Ensure that the wallet has enough BCH and tokens to complete the requested // trade. diff --git a/test/integration/adapters/wallet-adapter-integration.js b/test/integration/adapters/wallet-adapter-integration.js index 260c60e..93258d5 100644 --- a/test/integration/adapters/wallet-adapter-integration.js +++ b/test/integration/adapters/wallet-adapter-integration.js @@ -38,4 +38,27 @@ describe('#wallet', () => { assert.equal(walletInstance.ar.interface, 'rest-api') }) }) + + describe('#moveTokens', () => { + it('should move tokens to a new address in the HD wallet', async () => { + const walletData = await uut.openWallet() + + // Force usage of FullStack.cash + uut.config.useFullStackCash = true + + await uut.instanceWallet(walletData) + + const inObj = { + tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2', + qty: 1 + } + + const result = await uut.moveTokens(inObj) + // console.log('result: ', result) + + assert.property(result, 'txid') + assert.property(result, 'vout') + assert.property(result, 'hdIndex') + }) + }) }) diff --git a/test/integration/use-cases/offer-use-case-integration.js b/test/integration/use-cases/offer-use-case-integration.js index f85ba0c..81b4cab 100644 --- a/test/integration/use-cases/offer-use-case-integration.js +++ b/test/integration/use-cases/offer-use-case-integration.js @@ -24,27 +24,6 @@ describe('#offer-use-case.js', () => { }) describe('#ensureFunds', () => { - it('should throw error if wallet does not have enough funds', async () => { - try { - const offerEntity = { - lokadId: 'SWP', - messageType: 1, - messageClass: 1, - tokenId: 'token-id', - buyOrSell: 'sell', - rateInSats: 1000, - minSatsToExchange: 1250, - numTokens: 1 - } - - await uut.ensureFunds(offerEntity) - - assert.fail('Unexpected code path') - } catch (err) { - assert.include(err.message, 'App wallet does not have enough tokens to satisfy the SELL offer.') - } - }) - it('should return true if wallet has enough funds', async () => { const offerEntity = { lokadId: 'SWP', @@ -52,8 +31,8 @@ describe('#offer-use-case.js', () => { messageClass: 1, tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2', buyOrSell: 'sell', - rateInSats: 1000, - minSatsToExchange: 1250, + rateInBaseUnit: 1000, + minUnitsToExchange: 1250, numTokens: 1 } diff --git a/test/unit/adapters/wallet.adapter.unit.js b/test/unit/adapters/wallet.adapter.unit.js index 0892865..4a6c7bc 100644 --- a/test/unit/adapters/wallet.adapter.unit.js +++ b/test/unit/adapters/wallet.adapter.unit.js @@ -253,6 +253,32 @@ describe('#wallet', () => { } }) }) + + describe('#moveTokens', () => { + it('should move tokens to a new address in the HD wallet', async () => { + // Mock dependencies + sandbox.stub(uut, 'getKeyPair').resolves({ + cashAddress: 'bitcoincash:qqsj63493jk4p05zzdgqzc29k5unqtet9vv8l4x0yt', + wif: 'L4qKTMCwjH9jHnYtNh9Vsrxj7Hg6zmoN8E2v7N47UKvNVEjw7FU8', + hdIndex: 6 + }) + uut.bchWallet = { + sendTokens: async () => 'fake-txid' + } + + const inObj = { + tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2', + qty: 1 + } + + const result = await uut.moveTokens(inObj) + // console.log('result: ', result) + + assert.property(result, 'txid') + assert.property(result, 'vout') + assert.property(result, 'hdIndex') + }) + }) }) const deleteFile = (filepath) => { diff --git a/test/unit/mocks/adapters/index.js b/test/unit/mocks/adapters/index.js index 36d2a73..2ea38f4 100644 --- a/test/unit/mocks/adapters/index.js +++ b/test/unit/mocks/adapters/index.js @@ -126,7 +126,8 @@ const wallet = { getKeyPair: async () => { return { cashAddress: 'fakeAddr', wif: 'fakeWif', hdIndex: 1 } }, - bchWallet: new MockBchWallet() + bchWallet: new MockBchWallet(), + moveTokens: async () => {} } const p2wdb = { diff --git a/test/unit/use-cases/order.use-case.unit.js b/test/unit/use-cases/order.use-case.unit.js index 914fb94..0afa081 100644 --- a/test/unit/use-cases/order.use-case.unit.js +++ b/test/unit/use-cases/order.use-case.unit.js @@ -65,34 +65,34 @@ describe('#order-use-case', () => { }) }) - describe('#moveTokens', () => { - it('should move tokens to the holding address', async () => { - // Mock dependencies - // sandbox - // .stub(uut.adapters.wallet.bchWallet, 'sendTokens') - // .resolves('fakeTxid') - - const orderEntity = { - lokadId: 'SWP', - messageType: 1, - messageClass: 1, - tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2', - buyOrSell: 'sell', - rateInSats: 1000, - minSatsToExchange: 0, - numTokens: 1 - } - - const result = await uut.moveTokens(orderEntity) - // console.log('result: ', result) - - assert.property(result, 'txid') - assert.property(result, 'vout') - - assert.equal(result.txid, 'fakeTxid') - assert.equal(result.vout, 1) - }) - }) + // describe('#moveTokens', () => { + // it('should move tokens to the holding address', async () => { + // // Mock dependencies + // // sandbox + // // .stub(uut.adapters.wallet.bchWallet, 'sendTokens') + // // .resolves('fakeTxid') + // + // const orderEntity = { + // lokadId: 'SWP', + // messageType: 1, + // messageClass: 1, + // tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2', + // buyOrSell: 'sell', + // rateInBaseUnit: 1000, + // minUnitsToExchange: 0, + // numTokens: 1 + // } + // + // const result = await uut.moveTokens(orderEntity) + // // console.log('result: ', result) + // + // assert.property(result, 'txid') + // assert.property(result, 'vout') + // + // assert.equal(result.txid, 'fakeTxid') + // assert.equal(result.vout, 1) + // }) + // }) describe('#createOrder', () => { it('should create an order and return the hash', async () => { @@ -102,8 +102,8 @@ describe('#order-use-case', () => { messageClass: 1, tokenId: 'token-id', buyOrSell: 'sell', - rateInSats: 1000, - minSatsToExchange: 1250, + rateInBaseUnit: 1000, + minUnitsToExchange: 1250, numTokens: 1 } @@ -111,7 +111,7 @@ describe('#order-use-case', () => { // sandbox.stub(uut.adapters.wallet, 'burnPsf').resolves('fakeTxid') sandbox.stub(uut.orderEntity, 'validate').returns(entryObj) sandbox.stub(uut, 'ensureFunds').resolves() - sandbox.stub(uut, 'moveTokens').resolves({ txid: 'fakeTxid', vout: 0, hdIndex: 1 }) + sandbox.stub(uut.adapters.wallet, 'moveTokens').resolves({ txid: 'fakeTxid', vout: 0, hdIndex: 1 }) sandbox.stub(uut.adapters.wallet.bchWallet, 'getUtxos').resolves() // sandbox // .stub(uut.adapters.wallet, 'generateSignature')