From 0bc503b05529c2f6200572dd40c0397398460594 Mon Sep 17 00:00:00 2001 From: Dylan Duan Date: Tue, 27 Aug 2024 11:50:12 +0800 Subject: [PATCH 1/7] feat: Exclude pending tx for collecting cells --- src/collector/index.ts | 45 +++++++++++++++++++++++++++++++++++++---- src/order/batchMaker.ts | 7 ++----- src/order/cancel.ts | 7 ++++++- src/order/maker.ts | 23 +++++++++++---------- src/order/taker.ts | 17 +++++++--------- src/types/dex.ts | 1 + 6 files changed, 69 insertions(+), 31 deletions(-) diff --git a/src/collector/index.ts b/src/collector/index.ts index 533b055..06556a7 100644 --- a/src/collector/index.ts +++ b/src/collector/index.ts @@ -5,10 +5,20 @@ import { IndexerCell, CollectResult, IndexerCapacity, CollectUdtResult as Collec import { MIN_CAPACITY } from '../constants' import { CapacityNotEnoughException, IndexerException, UdtAmountNotEnoughException } from '../exceptions' import { leToU128 } from '../utils' +import { serializeOutPoint } from '@nervosnetwork/ckb-sdk-utils' + +export interface CollectConfig { + minCellCapacity?: bigint + errMsg?: string + excludePoolTx?: boolean +} + +const MAX_QUEUE_CAPACITY = 50 export class Collector { private ckbNodeUrl: string private ckbIndexerUrl: string + private queue: string[] = [] constructor({ ckbNodeUrl, ckbIndexerUrl }: { ckbNodeUrl: string; ckbIndexerUrl: string }) { this.ckbNodeUrl = ckbNodeUrl @@ -117,11 +127,14 @@ export class Collector { } } - collectInputs(liveCells: IndexerCell[], needCapacity: bigint, fee: bigint, minCapacity?: bigint, errMsg?: string): CollectResult { - const changeCapacity = minCapacity ?? MIN_CAPACITY + collectInputs(liveCells: IndexerCell[], needCapacity: bigint, fee: bigint, config?: CollectConfig): CollectResult { + const changeCapacity = config?.minCellCapacity ?? MIN_CAPACITY let inputs: CKBComponents.CellInput[] = [] let sum = BigInt(0) for (let cell of liveCells) { + if (config?.excludePoolTx && this.isInQueue(cell.outPoint)) { + continue + } inputs.push({ previousOutput: { txHash: cell.outPoint.txHash, @@ -135,17 +148,21 @@ export class Collector { } } if (sum < needCapacity + changeCapacity + fee) { - const message = errMsg ?? 'Insufficient free CKB balance' + const message = config?.errMsg ?? 'Insufficient free CKB balance' throw new CapacityNotEnoughException(message) } + this.pushToQueue(inputs.map(input => input.previousOutput!)) return { inputs, capacity: sum } } - collectUdtInputs(liveCells: IndexerCell[], needAmount: bigint): CollectUdtResult { + collectUdtInputs(liveCells: IndexerCell[], needAmount: bigint, excludePoolTx?: boolean): CollectUdtResult { let inputs: CKBComponents.CellInput[] = [] let sumCapacity = BigInt(0) let sumAmount = BigInt(0) for (let cell of liveCells) { + if (excludePoolTx && this.isInQueue(cell.outPoint)) { + continue + } inputs.push({ previousOutput: { txHash: cell.outPoint.txHash, @@ -162,6 +179,7 @@ export class Collector { if (sumAmount < needAmount) { throw new UdtAmountNotEnoughException('Insufficient UDT balance') } + this.pushToQueue(inputs.map(input => input.previousOutput!)) return { inputs, capacity: sumCapacity, amount: sumAmount } } @@ -170,4 +188,23 @@ export class Collector { const { cell } = await ckb.rpc.getLiveCell(outPoint, true) return cell } + + pushToQueue(outPoints: CKBComponents.OutPoint[]) { + const serializedHexList = outPoints.map(serializeOutPoint) + for (const serializedHex of serializedHexList) { + if (this.queue.length >= MAX_QUEUE_CAPACITY) { + this.queue.shift() + } + this.queue.push(serializedHex) + } + } + + isInQueue(outPoint: CKBComponents.OutPoint) { + const serializedHex = serializeOutPoint(outPoint) + return this.queue.includes(serializedHex) + } + + clearQueue() { + this.queue = [] + } } diff --git a/src/order/batchMaker.ts b/src/order/batchMaker.ts index 2e432ab..bdcb325 100644 --- a/src/order/batchMaker.ts +++ b/src/order/batchMaker.ts @@ -85,13 +85,10 @@ export const buildMultiNftsMakerTx = async ( const needCKB = ((orderNeedCapacity + minCellCapacity + CKB_UNIT) / CKB_UNIT).toString() const errMsg = `At least ${needCKB} free CKB (refundable) is required to place a sell order.` - const { inputs: emptyInputs, capacity: emptyInputsCapacity } = collector.collectInputs( - emptyCells, - orderNeedCapacity, - txFee, + const { inputs: emptyInputs, capacity: emptyInputsCapacity } = collector.collectInputs(emptyCells, orderNeedCapacity, txFee, { minCellCapacity, errMsg, - ) + }) const nftInputList: CKBComponents.CellInput[] = [] let sporeCoBuildNftCellList = [] let sporeCoBuildOutputList = [] diff --git a/src/order/cancel.ts b/src/order/cancel.ts index 74e0cdf..30b561d 100644 --- a/src/order/cancel.ts +++ b/src/order/cancel.ts @@ -31,6 +31,7 @@ export const buildCancelTx = async ({ fee, estimateWitnessSize, ckbAsset = CKBAsset.XUDT, + excludePoolTx, }: CancelParams): Promise => { let txFee = fee ?? MAX_FEE const isMainnet = seller.startsWith('ckb') @@ -78,7 +79,11 @@ export const buildCancelTx = async ({ const minCellCapacity = calculateEmptyCellMinCapacity(sellerLock) const errMsg = `Insufficient CKB available balance to pay transaction fee` - const { inputs: emptyInputs, capacity: inputsCapacity } = collector.collectInputs(emptyCells, minCellCapacity, txFee, BigInt(0), errMsg) + const { inputs: emptyInputs, capacity: inputsCapacity } = collector.collectInputs(emptyCells, minCellCapacity, txFee, { + minCellCapacity: BigInt(0), + errMsg, + excludePoolTx, + }) inputs = [...orderInputs, ...emptyInputs] if (isUdtAsset(ckbAsset)) { diff --git a/src/order/maker.ts b/src/order/maker.ts index 80f4ce1..4c5d483 100644 --- a/src/order/maker.ts +++ b/src/order/maker.ts @@ -65,6 +65,7 @@ export const buildMakerTx = async ({ fee, estimateWitnessSize, ckbAsset = CKBAsset.XUDT, + excludePoolTx, }: MakerParams): Promise => { let txFee = fee ?? MAX_FEE const isMainnet = seller.startsWith('ckb') @@ -101,20 +102,22 @@ export const buildMakerTx = async ({ if (!udtCells || udtCells.length === 0) { throw new AssetException('The address has no UDT cells') } - let { inputs: udtInputs, capacity: sumInputsCapacity, amount: inputsAmount } = collector.collectUdtInputs(udtCells, listAmount) + let { + inputs: udtInputs, + capacity: sumInputsCapacity, + amount: inputsAmount, + } = collector.collectUdtInputs(udtCells, listAmount, excludePoolTx) orderCellCapacity = calculateUdtCellCapacity(orderLock, assetTypeScript) const udtCellCapacity = calculateUdtCellCapacity(sellerLock, assetTypeScript) if (sumInputsCapacity < orderCellCapacity + udtCellCapacity + minCellCapacity + txFee) { const needCKB = ((orderCellCapacity + minCellCapacity + CKB_UNIT) / CKB_UNIT).toString() const errMsg = `At least ${needCKB} free CKB (refundable) is required to place a sell order.` - const { inputs: emptyInputs, capacity: emptyInputsCapacity } = collector.collectInputs( - emptyCells, - orderCellCapacity, - txFee, + const { inputs: emptyInputs, capacity: emptyInputsCapacity } = collector.collectInputs(emptyCells, orderCellCapacity, txFee, { minCellCapacity, errMsg, - ) + excludePoolTx, + }) inputs = [...emptyInputs, ...udtInputs] sumInputsCapacity += emptyInputsCapacity } else { @@ -158,13 +161,11 @@ export const buildMakerTx = async ({ const needCKB = ((orderNeedCapacity + minCellCapacity + CKB_UNIT) / CKB_UNIT).toString() const errMsg = `At least ${needCKB} free CKB (refundable) is required to place a sell order.` - const { inputs: emptyInputs, capacity: emptyInputsCapacity } = collector.collectInputs( - emptyCells, - orderNeedCapacity, - txFee, + const { inputs: emptyInputs, capacity: emptyInputsCapacity } = collector.collectInputs(emptyCells, orderNeedCapacity, txFee, { minCellCapacity, errMsg, - ) + excludePoolTx, + }) const nftInput: CKBComponents.CellInput = { previousOutput: nftCell.outPoint, since: '0x0', diff --git a/src/order/taker.ts b/src/order/taker.ts index 7d9dcac..6ba95f3 100644 --- a/src/order/taker.ts +++ b/src/order/taker.ts @@ -89,6 +89,7 @@ export const buildTakerTx = async ({ fee, estimateWitnessSize, ckbAsset = CKBAsset.XUDT, + excludePoolTx, }: TakerParams): Promise => { let txFee = fee ?? MAX_FEE const isMainnet = buyer.startsWith('ckb') @@ -147,13 +148,11 @@ export const buildTakerTx = async ({ const minCellCapacity = calculateEmptyCellMinCapacity(buyerLock) const needCKB = ((needExtraInputsCapacity + minCellCapacity + CKB_UNIT) / CKB_UNIT).toString() const errMsg = `At least ${needCKB} free CKB is required to take the order.` - const { inputs: emptyInputs, capacity: inputsCapacity } = collector.collectInputs( - emptyCells, - needExtraInputsCapacity, - txFee, + const { inputs: emptyInputs, capacity: inputsCapacity } = collector.collectInputs(emptyCells, needExtraInputsCapacity, txFee, { minCellCapacity, errMsg, - ) + excludePoolTx, + }) inputs = [...orderInputs, ...emptyInputs] changeCapacity = inputsCapacity - needExtraInputsCapacity - txFee @@ -172,13 +171,11 @@ export const buildTakerTx = async ({ const minCellCapacity = calculateEmptyCellMinCapacity(buyerLock) const needCKB = ((dexSellerOutputsCapacity + minCellCapacity + CKB_UNIT) / CKB_UNIT).toString() const errMsg = `At least ${needCKB} free CKB is required to take the order.` - const { inputs: emptyInputs, capacity: emptyInputsCapacity } = collector.collectInputs( - emptyCells, - dexSellerOutputsCapacity, - txFee, + const { inputs: emptyInputs, capacity: emptyInputsCapacity } = collector.collectInputs(emptyCells, dexSellerOutputsCapacity, txFee, { minCellCapacity, errMsg, - ) + excludePoolTx, + }) inputs = [...orderInputs, ...emptyInputs] const sumInputsCapacity = dexInputsCapacity + emptyInputsCapacity diff --git a/src/types/dex.ts b/src/types/dex.ts index cd64935..54cdaf4 100644 --- a/src/types/dex.ts +++ b/src/types/dex.ts @@ -22,6 +22,7 @@ interface BaseParams { joyID?: JoyIDConfig ckbAsset?: CKBAsset estimateWitnessSize?: number + excludePoolTx?: boolean } export interface MakerParams extends BaseParams { From 3babd4026b7c70d3eaa57d6c015292d49bd6b22a Mon Sep 17 00:00:00 2001 From: Dylan Duan Date: Tue, 27 Aug 2024 12:10:55 +0800 Subject: [PATCH 2/7] feat: Update maker example with queue --- example/maker.ts | 30 +++++++++++++++++++++++++++--- example/secp256r1.ts | 11 +++++++++-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/example/maker.ts b/example/maker.ts index 5fdd582..5ea51f2 100644 --- a/example/maker.ts +++ b/example/maker.ts @@ -38,12 +38,12 @@ const maker = async () => { connectData, } - const listAmount = BigInt(500_0000_0000) - const totalValue = BigInt(800_0000_0000) + const listAmount = BigInt(200_0000_0000) + const totalValue = BigInt(500_0000_0000) const xudtType: CKBComponents.Script = { codeHash: '0x25c29dc317811a6f6f3985a7a9ebc4838bd388d19d0feeecf0bcd60f6c0975bb', hashType: 'type', - args: '0xaafd7e7eab79726c669d7565888b194dc06bd1dbec16749a721462151e4f1762', + args: '0x562e4e8a2f64a3e9c24beb4b7dd002d0ad3b842d0cc77924328e36ad114e3ebe', } const { rawTx, listPackage, txFee } = await buildMakerTx({ @@ -56,6 +56,8 @@ const maker = async () => { totalValue, assetType: append0x(serializeScript(xudtType)), ckbAsset: CKBAsset.XUDT, + // If you want to continually list xUDT without blockchain committed, excludePoolTx should be true + // excludePoolTx: true }) const key = keyFromP256Private(SELLER_MAIN_PRIVATE_KEY) @@ -67,6 +69,28 @@ const maker = async () => { let txHash = await collector.getCkb().rpc.sendTransaction(signedTx, 'passthrough') console.info(`The udt asset has been listed with tx hash: ${txHash}`) + + // You can list xUDT continually without blockchain committed when the transactions of the pool are excluded + // setTimeout(async () => { + // const { rawTx, listPackage, txFee } = await buildMakerTx({ + // collector, + // joyID, + // seller, + // // The UDT amount to list and it's optional for NFT asset + // listAmount, + // // The price whose unit is shannon for CKB native token + // totalValue, + // assetType: append0x(serializeScript(xudtType)), + // ckbAsset: CKBAsset.XUDT, + // excludePoolTx: true, + // }) + + // const key = keyFromP256Private(SELLER_MAIN_PRIVATE_KEY) + // const signedTx = signSecp256r1Tx(key, rawTx) + + // let txHash = await collector.getCkb().rpc.sendTransaction(signedTx, 'passthrough') + // console.info(`The udt asset has been continually listed with tx hash: ${txHash}`) + // }, 500) } maker() diff --git a/example/secp256r1.ts b/example/secp256r1.ts index 6228a0e..50f9a18 100644 --- a/example/secp256r1.ts +++ b/example/secp256r1.ts @@ -1,7 +1,14 @@ -import { bytesToHex, hexToBytes, PERSONAL, rawTransactionToHash, serializeWitnessArgs, toUint64Le } from '@nervosnetwork/ckb-sdk-utils' +import { + bytesToHex, + hexToBytes, + PERSONAL, + rawTransactionToHash, + serializeWitnessArgs, + toUint64Le, + blake2b, +} from '@nervosnetwork/ckb-sdk-utils' import { ec as EC } from 'elliptic' import sha256 from 'fast-sha256' -import blake2b from '@nervosnetwork/ckb-sdk-utils/lib/crypto/blake2b' import { append0x, getPublicKey, remove0x } from '../src/utils' import { Hex } from '../src/types' import { SECP256R1_PUBKEY_SIG_LEN, WITNESS_NATIVE_MODE } from '../src/constants' From 1a00f4c2ca6b5b847e7c8f26deff4e6f779d1f17 Mon Sep 17 00:00:00 2001 From: Dylan Duan Date: Tue, 27 Aug 2024 12:12:37 +0800 Subject: [PATCH 3/7] chore: Bump version to v0.7.0-beta1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cce4641..b19d322 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nervina-labs/ckb-dex", - "version": "0.6.1", + "version": "0.7.0-beta1", "description": "The JavaScript SDK for CKB DEX", "author": "duanyytop ", "license": "MIT", From a8779b71bbc9c5f7b641527395594a89048ccc85 Mon Sep 17 00:00:00 2001 From: Dylan Duan Date: Thu, 29 Aug 2024 14:46:14 +0800 Subject: [PATCH 4/7] feat: Add excludePoolTx to batchMaker --- src/order/batchMaker.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/order/batchMaker.ts b/src/order/batchMaker.ts index bdcb325..40cb56b 100644 --- a/src/order/batchMaker.ts +++ b/src/order/batchMaker.ts @@ -25,7 +25,7 @@ import { OrderArgs } from './orderArgs' import { calculateNFTMakerListPackage } from './maker' export const buildMultiNftsMakerTx = async ( - { collector, joyID, seller, fee, estimateWitnessSize, ckbAsset = CKBAsset.SPORE }: MakerParams, + { collector, joyID, seller, fee, estimateWitnessSize, ckbAsset = CKBAsset.SPORE, excludePoolTx }: MakerParams, nfts: { totalValue: bigint; assetType: string }[], ) => { let txFee = fee ?? MAX_FEE @@ -39,7 +39,7 @@ export const buildMultiNftsMakerTx = async ( throw new NoLiveCellException('The address has no empty cells') } if (isUdtAsset(ckbAsset)) { - throw new NoSupportUDTAssetException('Just support nft asset') + throw new NoSupportUDTAssetException('Ony support NFT asset') } const minCellCapacity = calculateEmptyCellMinCapacity(sellerLock) @@ -53,9 +53,7 @@ export const buildMultiNftsMakerTx = async ( let orderNeedCapacity = BigInt(0) let nftCellList = [] - for (let i = 0; i < nfts.length; i++) { - const nft = nfts[i] - + for (let nft of nfts) { const assetTypeScript = blockchain.Script.unpack(nft.assetType) as CKBComponents.Script const setup = isUdtAsset(ckbAsset) ? 0 : 4 const orderArgs = new OrderArgs(sellerLock, setup, nft.totalValue) @@ -88,14 +86,13 @@ export const buildMultiNftsMakerTx = async ( const { inputs: emptyInputs, capacity: emptyInputsCapacity } = collector.collectInputs(emptyCells, orderNeedCapacity, txFee, { minCellCapacity, errMsg, + excludePoolTx, }) const nftInputList: CKBComponents.CellInput[] = [] let sporeCoBuildNftCellList = [] let sporeCoBuildOutputList = [] for (let i = 0; i < nftCellList.length; i++) { - const nftCell = nftCellList[i].nftCell - const orderLock = nftCellList[i].orderLock - const orderCellCapacity = nftCellList[i].orderCellCapacity + const { nftCell, orderLock, orderCellCapacity } = nftCellList[i] const nftInput: CKBComponents.CellInput = { previousOutput: nftCell.outPoint, From 45956345298a041b7c907687229ef61c12f6fe55 Mon Sep 17 00:00:00 2001 From: Dylan Duan Date: Thu, 29 Aug 2024 14:47:28 +0800 Subject: [PATCH 5/7] chore: Bump version to v0.7.0-beta2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b19d322..902ba67 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nervina-labs/ckb-dex", - "version": "0.7.0-beta1", + "version": "0.7.0-beta2", "description": "The JavaScript SDK for CKB DEX", "author": "duanyytop ", "license": "MIT", From 36de538139f24a8a1ccf34d880ab74887764b4d3 Mon Sep 17 00:00:00 2001 From: Dylan Duan Date: Thu, 29 Aug 2024 15:35:51 +0800 Subject: [PATCH 6/7] feat: Add getQueue to collector --- example/cancel.ts | 49 +++++++++++++++++++++++++++++++----------- src/collector/index.ts | 9 +++++++- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/example/cancel.ts b/example/cancel.ts index ef96150..83d3446 100644 --- a/example/cancel.ts +++ b/example/cancel.ts @@ -39,31 +39,56 @@ const cancel = async () => { const xudtOrderOutPoints: CKBComponents.OutPoint[] = [ { - txHash: '0x835a283e8371c1e55db27e0e09bf468175b047268ca82609e74ef6ee9e81403c', - index: '0x0', - }, - { - txHash: '0x48d64acadc78709ac2de78c88ec3cd015c5d1cb02a0afa986d408b30e82a2eb6', + txHash: '0x81176763a95aa71d59be015c9769bf65302520ac11b6d668630071d841659b66', index: '0x0', }, + // { + // txHash: '0x48d64acadc78709ac2de78c88ec3cd015c5d1cb02a0afa986d408b30e82a2eb6', + // index: '0x0', + // }, ] + console.log('Queue first state', collector.getQueue()) + const { rawTx, txFee, witnessIndex } = await buildCancelTx({ collector, joyID, seller, orderOutPoints: xudtOrderOutPoints.map(serializeOutPoint), + excludePoolTx: true, }) - const key = keyFromP256Private(SELLER_MAIN_PRIVATE_KEY) - const signedTx = signSecp256r1Tx(key, rawTx, witnessIndex) + console.log('First', JSON.stringify(rawTx)) + console.log('Queue second state', collector.getQueue()) + + // const key = keyFromP256Private(SELLER_MAIN_PRIVATE_KEY) + // const signedTx = signSecp256r1Tx(key, rawTx, witnessIndex) + + // // You can call the `signRawTransaction` method to sign the raw tx with JoyID wallet through @joyid/ckb SDK + // // please make sure the seller address is the JoyID wallet ckb address + // // const signedTx = await signRawTransaction(rawTx as CKBTransaction, seller) + + // let txHash = await collector.getCkb().rpc.sendTransaction(signedTx, 'passthrough') + // console.info(`The udt asset has been cancelled with tx hash: ${txHash}`) - // You can call the `signRawTransaction` method to sign the raw tx with JoyID wallet through @joyid/ckb SDK - // please make sure the seller address is the JoyID wallet ckb address - // const signedTx = await signRawTransaction(rawTx as CKBTransaction, seller) + setTimeout(async () => { + const xudtOrderOutPoints: CKBComponents.OutPoint[] = [ + { + txHash: '0x6669bb0a0bdcdb2e3a467ec3379155872182fa6df4472fc113313008ec5e255c', + index: '0x0', + }, + ] + const { rawTx, txFee, witnessIndex } = await buildCancelTx({ + collector, + joyID, + seller, + orderOutPoints: xudtOrderOutPoints.map(serializeOutPoint), + excludePoolTx: true, + }) - let txHash = await collector.getCkb().rpc.sendTransaction(signedTx, 'passthrough') - console.info(`The udt asset has been cancelled with tx hash: ${txHash}`) + console.log('Second', JSON.stringify(rawTx)) + console.log('Queue last state', collector.getQueue()) + }, 500) } cancel() diff --git a/src/collector/index.ts b/src/collector/index.ts index 06556a7..ed7bf5e 100644 --- a/src/collector/index.ts +++ b/src/collector/index.ts @@ -13,7 +13,7 @@ export interface CollectConfig { excludePoolTx?: boolean } -const MAX_QUEUE_CAPACITY = 50 +const MAX_QUEUE_CAPACITY = 30 export class Collector { private ckbNodeUrl: string @@ -192,6 +192,9 @@ export class Collector { pushToQueue(outPoints: CKBComponents.OutPoint[]) { const serializedHexList = outPoints.map(serializeOutPoint) for (const serializedHex of serializedHexList) { + if (this.queue.includes(serializedHex)) { + continue + } if (this.queue.length >= MAX_QUEUE_CAPACITY) { this.queue.shift() } @@ -204,6 +207,10 @@ export class Collector { return this.queue.includes(serializedHex) } + getQueue() { + return this.queue + } + clearQueue() { this.queue = [] } From c756df839eed4abfd0d24eff8ced89e6dd7cc9f1 Mon Sep 17 00:00:00 2001 From: Dylan Duan Date: Mon, 9 Sep 2024 08:35:55 +0800 Subject: [PATCH 7/7] chore: Bump version to v0.7.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 902ba67..0332a74 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nervina-labs/ckb-dex", - "version": "0.7.0-beta2", + "version": "0.7.0", "description": "The JavaScript SDK for CKB DEX", "author": "duanyytop ", "license": "MIT",