From e57faf8f26a61721cea711732e44dcd3bcf182d9 Mon Sep 17 00:00:00 2001 From: Daniel Gonzalez Date: Thu, 16 Oct 2025 18:38:06 -0400 Subject: [PATCH] feat(offer): Store mutable data in bch-dex Offer db model --- src/adapters/wallet.js | 20 +++++++++++++ src/use-cases/offer/index.js | 18 ++++++++++++ test/unit/adapters/wallet.unit.js | 31 ++++++++++++++++++++ test/unit/mocks/adapters/wallet.js | 1 + test/unit/mocks/use-cases/offer-mock-data.js | 17 ++++++++++- test/unit/use-cases/offer.use-case.unit.js | 16 ++++++++++ 6 files changed, 102 insertions(+), 1 deletion(-) diff --git a/src/adapters/wallet.js b/src/adapters/wallet.js index 92530ad..e3467de 100644 --- a/src/adapters/wallet.js +++ b/src/adapters/wallet.js @@ -56,6 +56,7 @@ class WalletAdapter { this.completeTx = this.completeTx.bind(this) this.reclaimTokens = this.reclaimTokens.bind(this) this.moveTokensFromCustomWallet = this.moveTokensFromCustomWallet.bind(this) + this.cid2json = this.cid2json.bind(this) } // Open the wallet file, or create one if the file doesn't exist. @@ -677,6 +678,25 @@ class WalletAdapter { throw err } } + + async cid2json (urlOrCid) { + try { + // Input validation + if (!urlOrCid || typeof urlOrCid !== 'string') { + throw new Error('urlOrCid must be a string!') + } + // Extract the cid from the url or cid. + const cid = urlOrCid.split('/').pop() + console.log('cid to json: ', cid) + const jsonRes = await this.bchWallet.cid2json({ cid }) + const json = jsonRes.json + // console.log('json: ', json) + return json + } catch (err) { + console.error('Error in wallet.js/cid2json()', err.message) + throw err + } + } } export default WalletAdapter diff --git a/src/use-cases/offer/index.js b/src/use-cases/offer/index.js index 9fdbc71..c8a5e29 100644 --- a/src/use-cases/offer/index.js +++ b/src/use-cases/offer/index.js @@ -133,6 +133,24 @@ class OfferUseCases { const tokenData = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.getTokenData, tokenId) // console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`) + // Store the mutable and immutable data cids. + const mutableDataCid = tokenData.mutableData + const immutableDataCid = tokenData.immutableData + offerEntity.mutableDataCid = mutableDataCid + offerEntity.immutableDataCid = immutableDataCid + // Get the mutable data from the cid if it exists. + if (mutableDataCid && typeof mutableDataCid === 'string') { + const mutableData = await this.retryQueue.addToQueue(this.adapters.wallet.cid2json, mutableDataCid) + // console.log('mutableData: ', mutableData) + if (mutableData) { + offerEntity.tokenIconUrl = mutableData.tokenIcon + offerEntity.tokenCategories = mutableData.category + offerEntity.tokenTags = mutableData.tags + offerEntity.lastUpdatedTokenData = new Date().getTime() + } + } + + console.log('offerEntity: ', offerEntity) // Generate a 'display category' for the token. This will allow the // front end UI to figure out how to display the token. const displayCategory = this.categorizeToken(offerEntity, tokenData) diff --git a/test/unit/adapters/wallet.unit.js b/test/unit/adapters/wallet.unit.js index a658d53..c73ac45 100644 --- a/test/unit/adapters/wallet.unit.js +++ b/test/unit/adapters/wallet.unit.js @@ -493,4 +493,35 @@ describe('#wallet', () => { } }) }) + 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') + } + }) + }) }) diff --git a/test/unit/mocks/adapters/wallet.js b/test/unit/mocks/adapters/wallet.js index 22a60de..3bf4223 100644 --- a/test/unit/mocks/adapters/wallet.js +++ b/test/unit/mocks/adapters/wallet.js @@ -54,6 +54,7 @@ class MockBchWallet { } }; this.optimize = async () => { }; + this.cid2json = async () => {}; this.ar = new AdapterRoute() // Environment variable is used by wallet-balance.unit.js to force an error. if (process.env.NO_UTXO) { diff --git a/test/unit/mocks/use-cases/offer-mock-data.js b/test/unit/mocks/use-cases/offer-mock-data.js index a814667..7699c0e 100644 --- a/test/unit/mocks/use-cases/offer-mock-data.js +++ b/test/unit/mocks/use-cases/offer-mock-data.js @@ -126,6 +126,20 @@ const fungibleTokenData01 = { "immutableData": "troutsblog.com", "mutableData": "" } +const mutableDataMock = { + schema: 'ps007-v1.0.3', + schemaDocs: 'https://github.com/Permissionless-Software-Foundation/specifications/blob/master/ps007-token-data-schema.md', + tokenIcon: 'https://files.tokentiger.com/ipfs/view/bafkreidr6wfd6mcmwpea7abm5uk5rrprc2wfbcvo5wdcmtlolrpjab5oqm', + fullSizedUrl: '', + nsfw: false, + userData: { currentUrl: 'https://tokentiger.com' }, + jsonLd: {}, + about: 'This is AI generated art.', + category: '', + tags: [], + mediaType: 'image', + currentOwner: {}, +} const offerMockData = { data: { @@ -214,5 +228,6 @@ export default { fungibleTokenData01, offerMockData, deserealizeTxMockNoOperatorOut, - deserealizeTxMock + deserealizeTxMock, + mutableDataMock }; diff --git a/test/unit/use-cases/offer.use-case.unit.js b/test/unit/use-cases/offer.use-case.unit.js index b7ca050..f9ad063 100644 --- a/test/unit/use-cases/offer.use-case.unit.js +++ b/test/unit/use-cases/offer.use-case.unit.js @@ -108,6 +108,22 @@ describe('#offer-use-case', () => { assert.isFalse(result) }) + it('should create offer with mutable data', async () => { + const tokenDataMock = mockData.nftTokenData01 + const offerObj = mockData.offerMockData + const mutableDataMock = mockData.mutableDataMock + console.log('mutableDataMock: ', mutableDataMock) + // Mock dependencies + // sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false) + 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) + }) it('should create offer', async () => { const tokenDataMock = mockData.simpleNftTokenData01 const offerObj = mockData.offerMockData