Merge pull request #88 from Permissionless-Software-Foundation/dh-create-order

feat(order): Modify createOrder() to work with the user model
This commit is contained in:
Chris Troutner
2025-05-06 18:45:09 -07:00
committed by GitHub
7 changed files with 151 additions and 14 deletions
+43
View File
@@ -54,6 +54,7 @@ class WalletAdapter {
this.deseralizeTx = this.deseralizeTx.bind(this)
this.completeTx = this.completeTx.bind(this)
this.reclaimTokens = this.reclaimTokens.bind(this)
this.moveTokensFromCustomWallet = this.moveTokensFromCustomWallet.bind(this)
}
// Open the wallet file, or create one if the file doesn't exist.
@@ -575,6 +576,48 @@ class WalletAdapter {
throw err
}
}
async moveTokensFromCustomWallet (inObj = {}) {
try {
const { tokenId, qty, wallet } = inObj
const keyPair = await this.getKeyPair()
console.log('keyPair: ', keyPair)
const receiver = {
address: keyPair.cashAddress,
tokenId,
qty
}
console.log('receiver: ', receiver)
// Update the UTXO store of the wallet.
await wallet.initialize()
// Get the token type of the token being moved.
// Combine Fungible and NFT token UTXOs.
let tokenUtxos = wallet.utxos.utxoStore.slpUtxos.type1.tokens.concat(
wallet.utxos.utxoStore.slpUtxos.nft.tokens)
// Get token UTXOs that match the token in the order.
tokenUtxos = tokenUtxos.filter(
x => x.tokenId === tokenId
)
const txid = await wallet.sendTokens(receiver, 3)
console.log('txid: ', txid)
const utxoInfo = {
txid,
vout: 1,
hdIndex: wallet.walletInfo.hdIndex,
tokenType: tokenUtxos[0].tokenType
}
return utxoInfo
} catch (err) {
console.error('Error in wallet.js/moveTokens()')
throw err
}
}
}
export default WalletAdapter
@@ -35,6 +35,8 @@ class OrderRESTControllerLib {
// console.log('body: ', ctx.request.body)
const orderObj = ctx.request.body.order
const user = ctx.state.user
orderObj.userId = user._id
console.log('orderObj: ', orderObj)
const { eventId, noteId } = await _this.useCases.order.createOrder(orderObj)
+9 -1
View File
@@ -7,6 +7,7 @@ import Router from 'koa-router'
// Local libraries.
import OrderRESTControllerLib from './controller.js'
import Validators from '../middleware/validators.js'
let _this
@@ -32,12 +33,14 @@ class OrderRouter {
}
// Encapsulate dependencies.
this.validators = new Validators()
this.orderRESTController = new OrderRESTControllerLib(dependencies)
// Instantiate the router and set the base route.
const baseUrl = '/order'
this.router = new Router({ prefix: baseUrl })
this.createOrder = this.createOrder.bind(this)
_this = this
}
@@ -49,7 +52,7 @@ class OrderRouter {
}
// Define the routes and attach the controller.
this.router.post('/', _this.orderRESTController.createOrder)
this.router.post('/', this.createOrder)
this.router.get('/list/all/:page', _this.orderRESTController.listOrders)
this.router.post('/delete', _this.orderRESTController.deleteOrder)
@@ -57,6 +60,11 @@ class OrderRouter {
app.use(_this.router.routes())
app.use(_this.router.allowedMethods())
}
async createOrder (ctx, next) {
await _this.validators.ensureUser(ctx, next)
await _this.orderRESTController.createOrder(ctx, next)
}
}
export default OrderRouter
+39 -12
View File
@@ -25,6 +25,7 @@ class OrderLib {
// Encapsulate dependencies
this.orderEntity = new OrderEntity()
this.OrderModel = this.adapters.localdb.Order
this.UserModel = this.adapters.localdb.Users
this.bch = this.adapters.bch
this.config = config
this.retryQueue = new RetryQueue({ retryPeriod: 1000, attempts: 3 })
@@ -43,8 +44,31 @@ class OrderLib {
if (!entryObj.tokenId) throw new Error('entry does not contain required properties')
const user = await this.UserModel.findById(entryObj.userId)
if (!user) throw new Error('user not found')
console.log(`Using FullStack.cash: ${this.config.useFullStackCash}`)
const advancedConfig = {}
if (this.config.useFullStackCash) {
advancedConfig.interface = 'rest-api'
advancedConfig.restURL = this.config.apiServer
advancedConfig.apiToken = this.config.apiToken
} else {
advancedConfig.interface = 'consumer-api'
advancedConfig.restURL = this.config.consumerUrl
}
// Instantiate minimal-slp-wallet with the user's mnemonic.
const BchWallet = this.adapters.wallet.BchWallet
const userWallet = new BchWallet(user.mnemonic, advancedConfig)
// Wait for wallet to initialize.
await userWallet.walletInfoPromise
await userWallet.initialize()
// Specify the address to send payment.
entryObj.makerAddr = this.adapters.wallet.bchWallet.walletInfo.cashAddress
entryObj.makerAddr = userWallet.walletInfo.cashAddress
console.log('entryObj.makerAddr: ', entryObj.makerAddr)
// console.log('entryObj.makerAddr: ', entryObj.makerAddr)
// Input Validation
@@ -53,7 +77,8 @@ class OrderLib {
// Optimize the wallet to speed up working with it.
console.log('Optimizing wallet before creating new order.')
await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.optimize, {})
// await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.optimize, {})
await userWallet.optimize()
// Ensure sufficient tokens exist to create the order.
// await this.ensureFunds(orderEntity)
@@ -61,24 +86,26 @@ class OrderLib {
// Get Ticker for token ID.
// const tokenData = await this.adapters.wallet.bchWallet.getTxData([entryObj.tokenId])
const tokenData = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.getTxData, [entryObj.tokenId])
// const tokenData = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.getTxData, [entryObj.tokenId])
const tokenData = await userWallet.getTxData([entryObj.tokenId])
// console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`)
orderEntity.ticker = tokenData[0].tokenTicker
// Move the tokens to holding address.
const moveObj = {
tokenId: orderEntity.tokenId,
qty: orderEntity.numTokens
qty: orderEntity.numTokens,
wallet: userWallet
}
// const utxoInfo = await this.adapters.wallet.moveTokens(moveObj)
const utxoInfo = await this.retryQueue.addToQueue(this.adapters.wallet.moveTokens, moveObj)
const utxoInfo = await this.retryQueue.addToQueue(this.adapters.wallet.moveTokensFromCustomWallet, moveObj)
// console.log('utxoInfo: ', utxoInfo)
// Update the UTXO store for the wallet.
await this.adapters.wallet.bchWallet.bchjs.Util.sleep(3000)
await userWallet.bchjs.Util.sleep(3000)
// await this.adapters.wallet.bchWallet.getUtxos()
await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.initialize, {})
// await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.initialize, {})
await userWallet.initialize()
// Update the order with the new UTXO information.
orderEntity.utxoTxid = utxoInfo.txid
orderEntity.utxoVout = utxoInfo.vout
@@ -159,7 +186,7 @@ class OrderLib {
)
}
//
//
} else {
// Buy Order
throw new Error('Buy orders are not supported yet.')
@@ -286,11 +313,11 @@ class OrderLib {
try {
const data = await this.OrderModel.find({})
// Sort entries so newest entries show first.
// Sort entries so newest entries show first.
.sort('-timestamp')
// Skip to the start of the selected page.
// Skip to the start of the selected page.
.skip(page * DEFAULT_ENTRIES_PER_PAGE)
// Only return 20 results.
// Only return 20 results.
.limit(DEFAULT_ENTRIES_PER_PAGE)
return data
@@ -65,6 +65,9 @@ describe('#Order-REST-Router', () => {
ctx.request.body = {
order: {}
}
ctx.state.user = {
id: 'testUserId'
}
// Mock dependencies
sandbox.stub(uut.useCases.order, 'createOrder').resolves({ eventId: 'testEventId', noteId: 'testNoteId' })
@@ -80,6 +83,9 @@ describe('#Order-REST-Router', () => {
ctx.request.body = {
order: {}
}
ctx.state.user = {
id: 'testUserId'
}
// Force an error
sandbox
+1
View File
@@ -152,6 +152,7 @@ const wallet = {
bchWallet: new MockBchWallet(),
BchWallet: MockBchWallet,
moveTokens: async () => {},
moveTokensFromCustomWallet: async () => {},
moveBch: async () => {},
reclaimTokens: async ()=>{},
generatePartialTx: async ()=>{},
+51 -1
View File
@@ -173,8 +173,9 @@ describe('#order-use-case', () => {
// Mock dependencies and force expected code path
sandbox.stub(uut.orderEntity, 'inputValidate').returns(entryObj)
sandbox.stub(uut, 'ensureFunds').resolves()
sandbox.stub(uut.UserModel, 'findById').resolves({ mnemonic: 'testMnemonic' })
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.Util, 'sleep').resolves()
sandbox.stub(uut.adapters.wallet, 'moveTokens').resolves({ txid: 'fakeTxid', vout: 0, hdIndex: 1 })
sandbox.stub(uut.adapters.wallet, 'moveTokensFromCustomWallet').resolves({ txid: 'fakeTxid', vout: 0, hdIndex: 1 })
sandbox.stub(uut.adapters.wallet.bchWallet, 'initialize').resolves()
sandbox.stub(uut.adapters.nostr, 'post').resolves('fakeEvenetId')
@@ -184,6 +185,55 @@ describe('#order-use-case', () => {
assert.property(result, 'eventId')
assert.property(result, 'noteId')
})
it('should create an order with consumer-api', async () => {
uut.config.useFullStackCash = false
const entryObj = {
lokadId: 'SWP',
messageType: 1,
messageClass: 1,
tokenId: 'token-id',
buyOrSell: 'sell',
rateInBaseUnit: 1000,
minUnitsToExchange: 1250,
numTokens: 1,
ticker: 'TEST'
}
// Mock dependencies and force expected code path
sandbox.stub(uut.orderEntity, 'inputValidate').returns(entryObj)
sandbox.stub(uut, 'ensureFunds').resolves()
sandbox.stub(uut.UserModel, 'findById').resolves({ mnemonic: 'testMnemonic' })
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.Util, 'sleep').resolves()
sandbox.stub(uut.adapters.wallet, 'moveTokensFromCustomWallet').resolves({ txid: 'fakeTxid', vout: 0, hdIndex: 1 })
sandbox.stub(uut.adapters.wallet.bchWallet, 'initialize').resolves()
sandbox.stub(uut.adapters.nostr, 'post').resolves('fakeEvenetId')
const result = await uut.createOrder(entryObj)
console.log('result: ', result)
assert.property(result, 'eventId')
assert.property(result, 'noteId')
})
it('should throw error if user is not found', async () => {
try {
const entryObj = {
lokadId: 'SWP',
messageType: 1,
messageClass: 1,
tokenId: 'token-id',
buyOrSell: 'sell',
rateInBaseUnit: 1000,
minUnitsToExchange: 1250,
numTokens: 1,
ticker: 'TEST'
}
await uut.createOrder(entryObj)
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'user not found')
}
})
it('should catch and throw an error', async () => {
try {