Compare commits

..
11 Commits
Author SHA1 Message Date
DevOps Bot a43d916684 Adding local web2 interface 2026-07-13 16:21:34 -07:00
DevOps Bot c5debbde34 Customize for fullstackcash.net deployment
- dex-ui Dockerfile: Use FullStack-Agents/bch-dex-ui-v3 fork, fullstackcash branch
- start-production.sh: Add OPERATOR_ADDRESS and OPERATOR_PERCENTAGE env vars
2026-07-13 10:07:02 -07:00
Chris Troutner 8c17645479 fix(deserializeTx()): Adding additional error logging 2026-05-15 12:32:06 -07:00
Chris Troutner fa18daa103 Merge pull request #126 from Permissionless-Software-Foundation/ct-unstable
fix(minimal-slp-wallet): Updating to latest version
2026-05-15 11:26:25 -07:00
Chris Troutner b2f7ca3e44 fix(minimal-slp-wallet): Updating to latest version 2026-05-15 11:20:18 -07:00
Chris Troutner 3912db4c8d Merge pull request #125 from Permissionless-Software-Foundation/ct-unstable
fix(deseralizeTx): Updating URL for web service
2026-05-15 10:19:08 -07:00
Chris Troutner 9f3d1f866e fix(deseralizeTx): Updating URL for web service 2026-05-15 10:08:28 -07:00
Chris Troutner 0811024030 Merge pull request #123 from Permissionless-Software-Foundation/dh-token-data
feat(offer): Added tokenData to offer model
2025-12-04 08:08:45 -07:00
Daniel Gonzalez 737d35463d feat(offer): Added tokenData to offer model 2025-12-03 19:21:40 -04:00
Chris Troutner 6dddfbdcb6 Merge pull request #122 from Permissionless-Software-Foundation/dh-ctr-offr-page
feat(counterOffer): Added new properties to counter offer model
2025-11-25 18:59:41 -08:00
Daniel Gonzalez fc2d7b4b9b feat(counterOffer): Added new properties to counter offer model 2025-11-25 20:57:44 -04:00
13 changed files with 987 additions and 129 deletions
+1 -1
View File
@@ -80,7 +80,7 @@ export default {
? ( ? (
process.env.APISERVER process.env.APISERVER
? process.env.APISERVER ? process.env.APISERVER
: 'https://api.fullstack.cash/v5/' : 'https://bch.fullstack.cash/v6/'
) )
: 'https://free-bch.fullstack.cash/', : 'https://free-bch.fullstack.cash/',
+847 -113
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -74,7 +74,7 @@
"koa2-ratelimit": "0.9.1", "koa2-ratelimit": "0.9.1",
"libp2p": "2.7.2", "libp2p": "2.7.2",
"line-reader": "0.4.0", "line-reader": "0.4.0",
"minimal-slp-wallet": "5.13.2", "minimal-slp-wallet": "7.1.5",
"mongoose": "5.13.14", "mongoose": "5.13.14",
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7", "node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
"nodemailer": "6.7.5", "nodemailer": "6.7.5",
+2 -2
View File
@@ -46,14 +46,14 @@ WORKDIR /home/safeuser
# Clone the rest.bitcoin.com repository # Clone the rest.bitcoin.com repository
WORKDIR /home/safeuser WORKDIR /home/safeuser
RUN git clone https://github.com/Permissionless-Software-Foundation/bch-dex-ui-v3 RUN git clone https://github.com/FullStack-Agents/bch-dex-ui-v3
# Switch to the desired branch. `master` is usually stable, # Switch to the desired branch. `master` is usually stable,
# and `stage` has the most up-to-date changes. # and `stage` has the most up-to-date changes.
WORKDIR /home/safeuser/bch-dex-ui-v3 WORKDIR /home/safeuser/bch-dex-ui-v3
# For development: switch to unstable branch # For development: switch to unstable branch
RUN git checkout ct-unstable RUN git checkout fullstackcash
# Install dependencies # Install dependencies
#RUN mkdir .ipfsdata #RUN mkdir .ipfsdata
@@ -31,4 +31,12 @@ export APP_ID=bch-dex-001
#export P2WDB_URL=http://172.17.0.1:5667 #export P2WDB_URL=http://172.17.0.1:5667
export OPERATOR_ADDRESS=bitcoincash:qzauj67nsqqmtu2pql7wcak3ct7lu2fppqkj0xwvwl
export OPERATOR_PERCENTAGE=10.0
export FULLSTACKCASH=false
export APISERVER=http://192.168.2.156:5942/v6/
export WALLET_INTERFACE=web2
npm start npm start
+1 -1
View File
@@ -38,7 +38,7 @@ services:
- '4005:4005' # WebRTC - '4005:4005' # WebRTC
volumes: volumes:
- ../scripts/wallet.json:/home/safeuser/bch-dex/wallet.json - ../../../scripts/wallet.json:/home/safeuser/bch-dex/wallet.json
- ./bch-dex/start-production.sh:/home/safeuser/bch-dex/start-production.sh - ./bch-dex/start-production.sh:/home/safeuser/bch-dex/start-production.sh
restart: always restart: always
+4 -1
View File
@@ -4,8 +4,11 @@ const CounterOffer = new mongoose.Schema({
nostrEventId: { type: String, required: true }, // Nostr Event Id. nostrEventId: { type: String, required: true }, // Nostr Event Id.
takerAddr: { type: String, default: null }, takerAddr: { type: String, default: null },
takerNpub: { type: String, default: null }, takerNpub: { type: String, default: null },
makerNpub: { type: String, default: null },
counterOfferAddr: { type: String, default: null }, counterOfferAddr: { type: String, default: null },
counterOfferUtxo: { type: String, default: null } counterOfferUtxo: { type: String, default: null },
takerOfferUtxo: { type: String, default: null },
tokenId: { type: String, default: null }
}) })
export default mongoose.model('counterOffer', CounterOffer) export default mongoose.model('counterOffer', CounterOffer)
+1
View File
@@ -46,6 +46,7 @@ const Offer = new mongoose.Schema({
immutableDataCid: { type: String, default: null }, immutableDataCid: { type: String, default: null },
mutableDataCid: { type: String, default: null }, mutableDataCid: { type: String, default: null },
tokenIconUrl: { type: String, default: null }, tokenIconUrl: { type: String, default: null },
tokenData: { type: Object },
tokenCategories: { type: Array, default: [] }, tokenCategories: { type: Array, default: [] },
tokenTags: { type: Array, default: [] }, tokenTags: { type: Array, default: [] },
userDataStr: { type: String }, // Token user data userDataStr: { type: String }, // Token user data
+3 -2
View File
@@ -468,8 +468,9 @@ class WalletAdapter {
} }
// Ensure the URL points at FullStack.cash, since the web 3 infra does not // Ensure the URL points at FullStack.cash, since the web 3 infra does not
// yet support this call. // yet support this call.
// TODO: Remove this once the web 3 infra supports this call.
const oldUrl = this.bchWallet.bchjs.RawTransactions.restURL const oldUrl = this.bchWallet.bchjs.RawTransactions.restURL
this.bchWallet.bchjs.RawTransactions.restURL = 'https://api.fullstack.cash/v5/' this.bchWallet.bchjs.RawTransactions.restURL = 'https://bch.fullstack.cash/v6/'
// Use a full node to deserialize the transaction. // Use a full node to deserialize the transaction.
const txObj2 = await this.bchWallet.bchjs.RawTransactions.decodeRawTransaction(txHex) const txObj2 = await this.bchWallet.bchjs.RawTransactions.decodeRawTransaction(txHex)
@@ -480,7 +481,7 @@ class WalletAdapter {
return txObj2 return txObj2
} catch (err) { } catch (err) {
console.error('Error in wallet.js/deserializeTx()') console.error('Error in wallet.js/deserializeTx(): ', err)
throw err throw err
} }
} }
+17 -2
View File
@@ -16,8 +16,11 @@ class CounterOfferEntity {
nostrEventId, nostrEventId,
takerAddr, takerAddr,
takerNpub, takerNpub,
makerNpub,
counterOfferAddr, counterOfferAddr,
counterOfferUtxo counterOfferUtxo,
takerOfferUtxo,
tokenId
} = offerData.data } = offerData.data
// Input Validation // Input Validation
@@ -37,13 +40,25 @@ class CounterOfferEntity {
if (counterOfferUtxo && typeof counterOfferUtxo !== 'string') { if (counterOfferUtxo && typeof counterOfferUtxo !== 'string') {
throw new Error("Property 'counterOfferUtxo' must be a string.") throw new Error("Property 'counterOfferUtxo' must be a string.")
} }
if (takerOfferUtxo && typeof takerOfferUtxo !== 'string') {
throw new Error("Property 'takerOfferUtxo' must be a string.")
}
if (tokenId && typeof tokenId !== 'string') {
throw new Error("Property 'tokenId' must be a string.")
}
if (makerNpub && typeof makerNpub !== 'string') {
throw new Error("Property 'makerNpub' must be a string.")
}
const validatedCounterOfferData = { const validatedCounterOfferData = {
nostrEventId, nostrEventId,
takerAddr, takerAddr,
takerNpub, takerNpub,
counterOfferAddr, counterOfferAddr,
counterOfferUtxo counterOfferUtxo,
takerOfferUtxo,
tokenId,
makerNpub
} }
return validatedCounterOfferData return validatedCounterOfferData
+4
View File
@@ -148,6 +148,7 @@ class OfferUseCases {
const immutableDataCid = tokenData.immutableData const immutableDataCid = tokenData.immutableData
offerEntity.mutableDataCid = mutableDataCid offerEntity.mutableDataCid = mutableDataCid
offerEntity.immutableDataCid = immutableDataCid offerEntity.immutableDataCid = immutableDataCid
offerEntity.tokenData = tokenData
// Get the mutable data from the cid if it exists. // Get the mutable data from the cid if it exists.
if (mutableDataCid && typeof mutableDataCid === 'string') { if (mutableDataCid && typeof mutableDataCid === 'string') {
@@ -608,6 +609,8 @@ class OfferUseCases {
// Create counter offer entity for nostr event id if it not exist // Create counter offer entity for nostr event id if it not exist
const existingCounterOffer = await this.CounterOfferModel.findOne(({ nostrEventId: eventId })) const existingCounterOffer = await this.CounterOfferModel.findOne(({ nostrEventId: eventId }))
if (!existingCounterOffer) { if (!existingCounterOffer) {
// Add taker offer utxo to counter offer data.
offerData.data.takerOfferUtxo = offerData.data.utxoTxid
const counterOEntity = this.counterOfferEntity.validate(offerData) const counterOEntity = this.counterOfferEntity.validate(offerData)
// Create new counter offfer model and save it. // Create new counter offfer model and save it.
const counterOffer = new this.CounterOfferModel(counterOEntity) const counterOffer = new this.CounterOfferModel(counterOEntity)
@@ -998,6 +1001,7 @@ class OfferUseCases {
const immutableDataCid = tokenData.immutableData const immutableDataCid = tokenData.immutableData
offer.mutableDataCid = mutableDataCid offer.mutableDataCid = mutableDataCid
offer.immutableDataCid = immutableDataCid offer.immutableDataCid = immutableDataCid
offer.tokenData = tokenData
// Get the mutable data from the cid if it exists. // Get the mutable data from the cid if it exists.
if (mutableDataCid && typeof mutableDataCid === 'string') { if (mutableDataCid && typeof mutableDataCid === 'string') {
+64 -3
View File
@@ -11,7 +11,7 @@ let sandbox
let uut let uut
describe('#CounterOffer-Entity', () => { describe('#CounterOffer-Entity', () => {
before(async () => {}) before(async () => { })
beforeEach(() => { beforeEach(() => {
uut = new CounterOfferEntity() uut = new CounterOfferEntity()
@@ -24,7 +24,7 @@ describe('#CounterOffer-Entity', () => {
describe('#validate', () => { describe('#validate', () => {
it('should throw an error if data is not provided', () => { it('should throw an error if data is not provided', () => {
try { try {
uut.validate({ }) uut.validate({})
} catch (err) { } catch (err) {
assert.include(err.message, 'Input to counterOffer.validate() must be an object with a data property.') assert.include(err.message, 'Input to counterOffer.validate() must be an object with a data property.')
} }
@@ -59,12 +59,26 @@ describe('#CounterOffer-Entity', () => {
assert.include(err.message, "Property 'takerNpub' must be a string") assert.include(err.message, "Property 'takerNpub' must be a string")
} }
}) })
it('should throw an error if provided makerNpub is not string', () => {
try {
const inObj = {
nostrEventId: 'nostr event id',
takerAddr: 'taker address',
takerNpub: 'npub',
makerNpub: 1234
}
uut.validate({ data: inObj })
} catch (err) {
assert.include(err.message, "Property 'makerNpub' must be a string")
}
})
it('should throw an error if provided counterOfferAddr is not string', () => { it('should throw an error if provided counterOfferAddr is not string', () => {
try { try {
const inObj = { const inObj = {
nostrEventId: 'nostr event id', nostrEventId: 'nostr event id',
takerAddr: 'taker address', takerAddr: 'taker address',
takerNpub: 'npub', takerNpub: 'npub',
makerNpub: 'npub',
counterOfferAddr: 1234 counterOfferAddr: 1234
} }
uut.validate({ data: inObj }) uut.validate({ data: inObj })
@@ -78,6 +92,7 @@ describe('#CounterOffer-Entity', () => {
nostrEventId: 'nostr event id', nostrEventId: 'nostr event id',
takerAddr: 'taker address', takerAddr: 'taker address',
takerNpub: 'npub', takerNpub: 'npub',
makerNpub: 'npub',
counterOfferAddr: 'counter offer address', counterOfferAddr: 'counter offer address',
counterOfferUtxo: 1234 counterOfferUtxo: 1234
} }
@@ -86,13 +101,50 @@ describe('#CounterOffer-Entity', () => {
assert.include(err.message, "Property 'counterOfferUtxo' must be a string") assert.include(err.message, "Property 'counterOfferUtxo' must be a string")
} }
}) })
it('should throw an error if provided takerOfferUtxo is not string', () => {
try {
const inObj = {
nostrEventId: 'nostr event id',
takerAddr: 'taker address',
takerNpub: 'npub',
makerNpub: 'npub',
counterOfferAddr: 'counter offer address',
counterOfferUtxo: 'utxo txid',
takerOfferUtxo: 1234
}
uut.validate({ data: inObj })
} catch (err) {
assert.include(err.message, "Property 'takerOfferUtxo' must be a string")
}
})
it('should throw an error if provided tokenId is not string', () => {
try {
const inObj = {
nostrEventId: 'nostr event id',
takerAddr: 'taker address',
takerNpub: 'npub',
makerNpub: 'npub',
counterOfferAddr: 'counter offer address',
counterOfferUtxo: 'utxo txid',
takerOfferUtxo: 'utxo txid',
tokenId: 1234
}
uut.validate({ data: inObj })
} catch (err) {
assert.include(err.message, "Property 'tokenId' must be a string")
}
})
it('should return a counter offer object', () => { it('should return a counter offer object', () => {
const inObj = { const inObj = {
nostrEventId: 'nostr event id', nostrEventId: 'nostr event id',
takerAddr: 'taker address', takerAddr: 'taker address',
takerNpub: 'npub', takerNpub: 'npub',
makerNpub: 'npub',
counterOfferAddr: 'counter offer address', counterOfferAddr: 'counter offer address',
counterOfferUtxo: 'utxo txid' counterOfferUtxo: 'utxo txid',
takerOfferUtxo: 'utxo txid',
tokenId: 'token id'
} }
const counterOfferEntity = uut.validate({ data: inObj }) const counterOfferEntity = uut.validate({ data: inObj })
@@ -106,11 +158,20 @@ describe('#CounterOffer-Entity', () => {
assert.property(counterOfferEntity, 'takerNpub') assert.property(counterOfferEntity, 'takerNpub')
assert.equal(counterOfferEntity.takerNpub, inObj.takerNpub) assert.equal(counterOfferEntity.takerNpub, inObj.takerNpub)
assert.property(counterOfferEntity, 'makerNpub')
assert.equal(counterOfferEntity.makerNpub, inObj.makerNpub)
assert.property(counterOfferEntity, 'counterOfferAddr') assert.property(counterOfferEntity, 'counterOfferAddr')
assert.equal(counterOfferEntity.counterOfferAddr, inObj.counterOfferAddr) assert.equal(counterOfferEntity.counterOfferAddr, inObj.counterOfferAddr)
assert.property(counterOfferEntity, 'counterOfferUtxo') assert.property(counterOfferEntity, 'counterOfferUtxo')
assert.equal(counterOfferEntity.counterOfferUtxo, inObj.counterOfferUtxo) assert.equal(counterOfferEntity.counterOfferUtxo, inObj.counterOfferUtxo)
assert.property(counterOfferEntity, 'takerOfferUtxo')
assert.equal(counterOfferEntity.takerOfferUtxo, inObj.takerOfferUtxo)
assert.property(counterOfferEntity, 'tokenId')
assert.equal(counterOfferEntity.tokenId, inObj.tokenId)
}) })
}) })
}) })
+34 -3
View File
@@ -107,14 +107,14 @@ describe('#offer-use-case', () => {
const result = await uut.createOffer(offerObj) const result = await uut.createOffer(offerObj)
assert.isFalse(result) assert.isFalse(result)
}) })
it('should create offer with token data', async () => {
it('should create offer with mutable data', async () => {
const tokenDataMock = mockData.nftTokenData01 const tokenDataMock = mockData.nftTokenData01
const offerObj = mockData.offerMockData const offerObj = mockData.offerMockData
const mutableDataMock = mockData.mutableDataMock const mutableDataMock = mockData.mutableDataMock
const offerEntityMock = Object.assign({}, mockData.offerMockData.data)
// Mock dependencies // Mock dependencies
// sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false) sandbox.stub(uut.offerEntity, 'validate').returns(offerEntityMock)
sandbox.stub(uut, 'findOfferByTxid').throws(new Error('offer not found')) sandbox.stub(uut, 'findOfferByTxid').throws(new Error('offer not found'))
sandbox.stub(uut.retryQueue, 'addToQueue') sandbox.stub(uut.retryQueue, 'addToQueue')
.onCall(0).resolves({}) // Utxo Status call .onCall(0).resolves({}) // Utxo Status call
@@ -123,6 +123,31 @@ describe('#offer-use-case', () => {
const result = await uut.createOffer(offerObj) const result = await uut.createOffer(offerObj)
assert.isTrue(result) assert.isTrue(result)
console.log('offerEntityMock', offerEntityMock)
assert.property(offerEntityMock, 'tokenData')
})
it('should create offer with mutable data', async () => {
const tokenDataMock = mockData.nftTokenData01
const offerObj = mockData.offerMockData
const mutableDataMock = mockData.mutableDataMock
const offerEntityMock = Object.assign({}, mockData.offerMockData.data)
// Mock dependencies
sandbox.stub(uut.offerEntity, 'validate').returns(offerEntityMock)
sandbox.stub(uut, 'findOfferByTxid').throws(new Error('offer not found'))
sandbox.stub(uut.retryQueue, 'addToQueue')
.onCall(0).resolves({}) // Utxo Status call
.onCall(1).resolves(tokenDataMock) // Token Data call
.onCall(2).resolves(mutableDataMock)
const result = await uut.createOffer(offerObj)
assert.isTrue(result)
console.log('offerEntityMock', offerEntityMock)
assert.property(offerEntityMock, 'tokenIconUrl')
assert.property(offerEntityMock, 'tokenCategories')
assert.property(offerEntityMock, 'tokenTags')
assert.property(offerEntityMock, 'lastUpdatedTokenData')
}) })
it('should skip userData stringify error', async () => { it('should skip userData stringify error', async () => {
@@ -1106,6 +1131,12 @@ describe('#offer-use-case', () => {
const offer = await uut.syncOfferMutableData('tokenId') const offer = await uut.syncOfferMutableData('tokenId')
assert.isObject(offer) assert.isObject(offer)
assert.isNumber(offer.lastUpdatedTokenData) assert.isNumber(offer.lastUpdatedTokenData)
assert.property(offer, 'tokenData')
assert.property(offer, 'tokenIconUrl')
assert.property(offer, 'tokenCategories')
assert.property(offer, 'tokenTags')
assert.property(offer, 'lastUpdatedTokenData')
}) })
it('should skip userData stringify error', async () => { it('should skip userData stringify error', async () => {