First successful collaborative transaction and token trade

This commit is contained in:
Chris Troutner
2022-03-23 11:04:48 -07:00
parent 4201850389
commit 5910620048
7 changed files with 261 additions and 82 deletions
+2 -1
View File
@@ -35,7 +35,8 @@ module.exports = {
useFullStackCash: process.env.USE_FULLSTACKCASH ? true : false,
consumerUrl: process.env.CONSUMER_URL
? process.env.CONSUMER_URL
: 'https://free-bch.fullstack.cash',
// : 'https://free-bch.fullstack.cash',
: 'https://wa-usa-bch-consumer.fullstackcash.nl',
// P2WDB URL that will accept API calls from the p2wdb npm library.
p2wdbUrl: process.env.P2WDB_URL ? process.env.P2WDB_URL : 'https://p2wdb.fullstack.cash',
+78 -1
View File
@@ -10,10 +10,10 @@
"license": "MIT",
"dependencies": {
"@psf/bch-js": "6.2.1",
"@psf/bitcoincashjs-lib": "4.0.2",
"axios": "0.21.1",
"bch-message-lib": "2.1.4",
"bcryptjs": "2.4.3",
"bitcoincashjs-lib": "3.3.3",
"glob": "7.1.6",
"ipfs": "0.60.0",
"ipfs-coord": "7.1.6",
@@ -3114,6 +3114,39 @@
"safe-buffer": "^5.0.1"
}
},
"node_modules/bitcoin-ops": {
"version": "1.4.1",
"resolved": "http://94.130.170.209:4873/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz",
"integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==",
"license": "MIT"
},
"node_modules/bitcoincashjs-lib": {
"version": "3.3.3",
"resolved": "http://94.130.170.209:4873/bitcoincashjs-lib/-/bitcoincashjs-lib-3.3.3.tgz",
"integrity": "sha512-dk6GCVpCE+7oELy8l2Y/9vGR1ymlnsQffU7YmYNeBoUSTH9gckbKSk6xrg3O5b2P0C7+1SifQ8de7luCpq6k8g==",
"deprecated": "ooops",
"license": "MIT",
"dependencies": {
"bech32": "^1.1.2",
"bigi": "^1.4.0",
"bip66": "^1.1.0",
"bitcoin-ops": "^1.3.0",
"bs58check": "^2.0.0",
"create-hash": "^1.1.0",
"create-hmac": "^1.1.3",
"ecurve": "^1.0.0",
"merkle-lib": "^2.0.10",
"pushdata-bitcoin": "^1.0.1",
"randombytes": "^2.0.1",
"safe-buffer": "^5.0.1",
"typeforce": "^1.11.3",
"varuint-bitcoin": "^1.0.4",
"wif": "^2.0.1"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/bitcoinjs-message": {
"version": "2.0.0",
"resolved": "http://94.130.170.209:4873/bitcoinjs-message/-/bitcoinjs-message-2.0.0.tgz",
@@ -18842,6 +18875,15 @@
"node": ">=8"
}
},
"node_modules/pushdata-bitcoin": {
"version": "1.0.1",
"resolved": "http://94.130.170.209:4873/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz",
"integrity": "sha1-FZMdPNlnreUiBvUjqnMxrvfUOvc=",
"license": "MIT",
"dependencies": {
"bitcoin-ops": "^1.3.0"
}
},
"node_modules/q": {
"version": "1.5.1",
"dev": true,
@@ -24511,6 +24553,33 @@
"safe-buffer": "^5.0.1"
}
},
"bitcoin-ops": {
"version": "1.4.1",
"resolved": "http://94.130.170.209:4873/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz",
"integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow=="
},
"bitcoincashjs-lib": {
"version": "3.3.3",
"resolved": "http://94.130.170.209:4873/bitcoincashjs-lib/-/bitcoincashjs-lib-3.3.3.tgz",
"integrity": "sha512-dk6GCVpCE+7oELy8l2Y/9vGR1ymlnsQffU7YmYNeBoUSTH9gckbKSk6xrg3O5b2P0C7+1SifQ8de7luCpq6k8g==",
"requires": {
"bech32": "^1.1.2",
"bigi": "^1.4.0",
"bip66": "^1.1.0",
"bitcoin-ops": "^1.3.0",
"bs58check": "^2.0.0",
"create-hash": "^1.1.0",
"create-hmac": "^1.1.3",
"ecurve": "^1.0.0",
"merkle-lib": "^2.0.10",
"pushdata-bitcoin": "^1.0.1",
"randombytes": "^2.0.1",
"safe-buffer": "^5.0.1",
"typeforce": "^1.11.3",
"varuint-bitcoin": "^1.0.4",
"wif": "^2.0.1"
}
},
"bitcoinjs-message": {
"version": "2.0.0",
"resolved": "http://94.130.170.209:4873/bitcoinjs-message/-/bitcoinjs-message-2.0.0.tgz",
@@ -35682,6 +35751,14 @@
"escape-goat": "^2.0.0"
}
},
"pushdata-bitcoin": {
"version": "1.0.1",
"resolved": "http://94.130.170.209:4873/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz",
"integrity": "sha1-FZMdPNlnreUiBvUjqnMxrvfUOvc=",
"requires": {
"bitcoin-ops": "^1.3.0"
}
},
"q": {
"version": "1.5.1",
"dev": true
+1 -1
View File
@@ -28,10 +28,10 @@
"repository": "Permissionless-Software-Foundation/bch-dex",
"dependencies": {
"@psf/bch-js": "6.2.1",
"@psf/bitcoincashjs-lib": "4.0.2",
"axios": "0.21.1",
"bch-message-lib": "2.1.4",
"bcryptjs": "2.4.3",
"bitcoincashjs-lib": "3.3.3",
"glob": "7.1.6",
"ipfs": "0.60.0",
"ipfs-coord": "7.1.6",
+84 -26
View File
@@ -4,7 +4,7 @@
// Public npm libraries
const BchWallet = require('minimal-slp-wallet/index')
const bitcoinJs = require('@psf/bitcoincashjs-lib')
const bitcoinJs = require('bitcoincashjs-lib')
// Local libraries
const JsonFiles = require('./json-files')
@@ -86,6 +86,7 @@ class WalletAdapter {
// Wait for wallet to initialize.
await this.bchWallet.walletInfoPromise
console.log('BCH wallet initialized.')
return this.bchWallet
} catch (err) {
@@ -124,9 +125,12 @@ class WalletAdapter {
// and the index of the HD wallet that the key pair was generated from.
// TODO: Allow input integer. If input is used, use that as the index. If no
// input is provided, then call incrementNextAddress().
async getKeyPair () {
async getKeyPair (hdIndex = 0) {
try {
const hdIndex = await this.incrementNextAddress()
if (!hdIndex) {
// Increment the HD index and generate a new key pair.
hdIndex = await this.incrementNextAddress()
}
const mnemonic = this.bchWallet.walletInfo.mnemonic
@@ -198,7 +202,7 @@ class WalletAdapter {
// Get token info on the offered UTXO
const txData = await this.bchWallet.getTxData([offerInfo.utxoTxid])
console.log(`txData: ${JSON.stringify(txData, null, 2)}`)
// console.log(`txData: ${JSON.stringify(txData, null, 2)}`)
// Construct the UTXO being offered for sale.
const offeredUtxo = {
@@ -270,10 +274,7 @@ class WalletAdapter {
)
// Get seller address
// TODO: Seller should explicitly define the address to send to in the
// offerInfo object. Right now it retrieves the address from the UTXO
// for sale, which is not ideal.
const sellerAddr = txData[0].vout[1].scriptPubKey.addresses[0]
const sellerAddr = offerInfo.makerAddr
// Send payment to the offer side
transactionBuilder.addOutput(sellerAddr, satsNeeded)
@@ -292,7 +293,7 @@ class WalletAdapter {
buyerECPair,
null,
transactionBuilder.hashTypes.SIGHASH_ALL,
satsNeeded
utxoInfo.sats
)
const tx = transactionBuilder.transaction.buildIncomplete()
@@ -324,6 +325,9 @@ class WalletAdapter {
qty
}
// Update the UTXO store of the wallet.
await this.bchWallet.getUtxos()
const txid = await this.bchWallet.sendTokens(receiver, 3)
const utxoInfo = {
@@ -375,23 +379,6 @@ class WalletAdapter {
// representing the transaction.
async deseralizeTx (txHex) {
try {
// console.log('txHex: ', txHex)
//
// // Convert the hex string version of the transaction into a Buffer.
// const paymentBuffer = Buffer.from(txHex, 'hex')
//
// // Generate a Transaction object from the transaction binary data.
// const csTransaction = this.bitcoinJs.Transaction.fromBuffer(paymentBuffer)
// // console.log(`payment tx: ${JSON.stringify(csTransaction, null, 2)}`)
//
// // Instantiate the Transaction Builder.
// const csTransactionBuilder = this.bitcoinJs.TransactionBuilder.fromTransaction(
// csTransaction,
// 'mainnet'
// )
//
// return csTransactionBuilder
// Ensure the URL points at FullStack.cash, since the web 3 infra does not
// yet support this call.
const oldUrl = this.bchWallet.bchjs.RawTransactions.restURL
@@ -410,6 +397,77 @@ class WalletAdapter {
throw err
}
}
// Complete the partially signed transaction by signing the first input,
// then broadcasting the transaction to the network.
async completeTx (hex, hdIndex) {
try {
// console.log('hex: ', hex)
const bchjs = this.bchWallet.bchjs
// instance of transaction builder
// const transactionBuilder = new bchjs.TransactionBuilder()
// Convert the hex string version of the transaction into a Buffer.
// const paymentBuffer = Buffer.from(hex, 'hex')
// Generate a Transaction object from the transaction binary data.
// const csTransaction = this.bitcoinJs.Transaction.fromBuffer(paymentBuffer)
const csTransaction = this.bitcoinJs.Transaction.fromHex(hex)
// console.log(`payment tx: ${JSON.stringify(csTransaction, null, 2)}`)
// Instantiate the Transaction Builder.
const csTransactionBuilder = this.bitcoinJs.TransactionBuilder.fromTransaction(
csTransaction,
'mainnet'
)
// const csTransactionBuilder = bchjs.TransactionBuilder.fromTransaction(
// csTransaction,
// 'mainnet'
// )
// Get the keypair for the address used in the Order
const keyPair = await this.getKeyPair(hdIndex)
console.log(`maker keyPair: ${JSON.stringify(keyPair, null, 2)}`)
const makerECPair = bchjs.ECPair.fromWIF(keyPair.wif)
// Assumption: segregated UTXO has a value of 546 sats. It might be better
// to explicitly look this data up via the blockchain.
const dust = 546
// Coutnersign the Maker's input, representing the tokens for sale.
csTransactionBuilder.sign(
0,
makerECPair,
null,
this.bitcoinJs.Transaction.SIGHASH_ALL,
// csTransactionBuilder.hashTypes.SIGHASH_ALL,
dust
)
// build tx
const csTx = csTransactionBuilder.build()
// output rawhex
const csTxHex = csTx.toHex()
console.log(`Fully signed Tx hex: ${csTxHex}`)
// Debug: Display the fully signed TX
const txObj = await this.deseralizeTx(csTxHex)
console.log(`Final tx: ${JSON.stringify(txObj, null, 2)}`)
// return csTxHex
// Broadcast transaction to the network
const txid = await this.bchWallet.ar.sendTx(csTxHex)
return txid
} catch (err) {
console.error('Error in wallet.js/completeTx()')
throw err
}
}
}
module.exports = WalletAdapter
+28 -1
View File
@@ -140,6 +140,7 @@ class OfferUseCases {
// Move funds to create a segrated UTXO for taking the offer
const utxoInfo = await this.adapters.wallet.moveBch(satsToMove)
utxoInfo.sats = satsToMove
console.log('utxoInfo: ', utxoInfo)
// Create a partially signed transaction.
@@ -148,6 +149,10 @@ class OfferUseCases {
console.log('partialTxHex: ', partialTxHex)
// return partialTxHex
// Debug: Decode the transaction for manual QA
const txObj = await this.adapters.wallet.deseralizeTx(partialTxHex)
console.log(`partially-signed transaction: ${JSON.stringify(txObj, null, 2)}`)
// Create valid Offer object
const takenOfferInfo = Object.assign({}, offerInfo)
takenOfferInfo.partialTxHex = partialTxHex
@@ -264,9 +269,31 @@ class OfferUseCases {
const txObj = await this.adapters.wallet.deseralizeTx(txHex)
console.log(`txObj: ${JSON.stringify(txObj, null, 2)}`)
// Ensure the TX inputs and outputs match the original Order
// Ensure the 3rd output (vout=2) contains the required amount of BCH.
const satsToReceive = orderData.numTokens * parseInt(orderData.rateInBaseUnit)
if (isNaN(satsToReceive)) {
throw new Error('Could not calculate the amount of BCH offered in the Counter Offer')
}
const satsOut = this.adapters.wallet.bchWallet.bchjs.BitcoinCash.toSatoshi(txObj.vout[2].value)
const hasRequiredAmount = satsOut === satsToReceive
if (!hasRequiredAmount) {
throw new Error(`The Counter Offer has an output of ${satsOut}, which does not match the required ${satsToReceive} in the Offer.`)
}
// Ensure the 3rd output (vout=2) is going to the maker address specified
// in the Offer.
const addrInCounterOffer = txObj.vout[2].scriptPubKey.addresses[0]
const makerAddr = orderData.makerAddr
const hasCorrectAddr = makerAddr === addrInCounterOffer
if (!hasCorrectAddr) {
throw new Error(`The Counter Offer has an output address of ${addrInCounterOffer}, which does not match the Maker address of ${makerAddr} in the Offer.`)
}
// Sign and broadcast the transaction.
const txid = await this.adapters.wallet.completeTx(txHex, orderData.hdIndex)
console.log('txid: ', txid)
return txid
} catch (err) {
console.error('Error in acceptCounterOffer()')
throw err
+1 -1
View File
@@ -12,7 +12,7 @@ async function start () {
method: 'post',
url: `${LOCALHOST}/offer/take`,
data: {
offerCid: 'zdpuAs5Djp9VrnheYTSDVes6f4KJVds2u3juM3MxwFYbTV8m2'
offerCid: 'zdpuAwVTq7nF19VaDw6n49S2RvsVHNkYufZNP515Pqocyae9m'
}
}
@@ -3,7 +3,7 @@
*/
// Public npm libraries.
const assert = require('chai').assert
// const assert = require('chai').assert
// Local libraries.
const WalletAdapter = require('../../../src/adapters/wallet')
@@ -15,32 +15,71 @@ describe('#wallet', () => {
uut = new WalletAdapter()
})
describe('#instanceWallet', () => {
it('should instance the wallet using the web 3 infra by default', async () => {
const walletData = await uut.openWallet()
// console.log('walletData: ', walletData)
// describe('#instanceWallet', () => {
// it('should instance the wallet using the web 3 infra by default', async () => {
// const walletData = await uut.openWallet()
// // console.log('walletData: ', walletData)
//
// const walletInstance = await uut.instanceWallet(walletData)
// // console.log('walletInstance: ', walletInstance)
//
// assert.equal(walletInstance.ar.interface, 'consumer-api')
// })
//
// it('should instance using web 2 FullStack.cash infra', async () => {
// const walletData = await uut.openWallet()
//
// // Force usage of FullStack.cash
// uut.config.useFullStackCash = true
//
// const walletInstance = await uut.instanceWallet(walletData)
// // console.log('walletInstance: ', walletInstance)
//
// 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')
// })
// })
//
// describe('#moveBch', () => {
// it('should move BCH 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 amountSat = 1000
//
// const result = await uut.moveBch(amountSat)
// console.log('result: ', result)
// })
// })
const walletInstance = await uut.instanceWallet(walletData)
// console.log('walletInstance: ', walletInstance)
assert.equal(walletInstance.ar.interface, 'consumer-api')
})
it('should instance using web 2 FullStack.cash infra', async () => {
const walletData = await uut.openWallet()
// Force usage of FullStack.cash
uut.config.useFullStackCash = true
const walletInstance = await uut.instanceWallet(walletData)
// console.log('walletInstance: ', walletInstance)
assert.equal(walletInstance.ar.interface, 'rest-api')
})
})
describe('#moveTokens', () => {
it('should move tokens to a new address in the HD wallet', async () => {
describe('#completeTx', () => {
it('should complete the transaction with bchjs.TransactionBuilder', async () => {
const walletData = await uut.openWallet()
// Force usage of FullStack.cash
@@ -48,32 +87,9 @@ describe('#wallet', () => {
await uut.instanceWallet(walletData)
const inObj = {
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
qty: 1
}
const hex = '0200000002cf24c6f6e55fc84e7699223f7dac568aae991f1a49747758a797d06a516f718c0100000000ffffffff0c7a53c39a7f215c9fa0f409710cf5cb8926e40c104efcedb43d69d5e113fd9d000000006a47304402204d5d1ce837594a151ef8ccecf95b5c7ebb2e71560c6d2597fa559b92f9f032bc02206ef67cd2ef4b4df7207225c26c9982df184959ac389c3f49f1ff5dce641a72ee412103fdc2366a32184220e77fd50e7fff30e7c76b2b7246dea6d56b8177f9fca6fec7ffffffff030000000000000000376a04534c500001010453454e4420a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b208000000000000006422020000000000001976a914d46461bf6a5f3a8e0e0e92b85465ad79696ccb7688ac88130000000000001976a914d46461bf6a5f3a8e0e0e92b85465ad79696ccb7688ac00000000'
const result = await uut.moveTokens(inObj)
// console.log('result: ', result)
assert.property(result, 'txid')
assert.property(result, 'vout')
assert.property(result, 'hdIndex')
})
})
describe('#moveBch', () => {
it('should move BCH 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 amountSat = 1000
const result = await uut.moveBch(amountSat)
const result = await uut.completeTx(hex, 10)
console.log('result: ', result)
})
})