Skip to content

Commit

Permalink
Merge pull request #32 from nervina-labs/feat/collect-queue
Browse files Browse the repository at this point in the history
Add CKB cell queue to send transactions continually
  • Loading branch information
duanyytop authored Sep 9, 2024
2 parents ffbd413 + c756df8 commit 818ac92
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 57 deletions.
49 changes: 37 additions & 12 deletions example/cancel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
30 changes: 27 additions & 3 deletions example/maker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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)
Expand All @@ -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()
11 changes: 9 additions & 2 deletions example/secp256r1.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nervina-labs/ckb-dex",
"version": "0.6.1",
"version": "0.7.0",
"description": "The JavaScript SDK for CKB DEX",
"author": "duanyytop <[email protected]>",
"license": "MIT",
Expand Down
52 changes: 48 additions & 4 deletions src/collector/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 30

export class Collector {
private ckbNodeUrl: string
private ckbIndexerUrl: string
private queue: string[] = []

constructor({ ckbNodeUrl, ckbIndexerUrl }: { ckbNodeUrl: string; ckbIndexerUrl: string }) {
this.ckbNodeUrl = ckbNodeUrl
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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 }
}

Expand All @@ -170,4 +188,30 @@ 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.includes(serializedHex)) {
continue
}
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)
}

getQueue() {
return this.queue
}

clearQueue() {
this.queue = []
}
}
20 changes: 7 additions & 13 deletions src/order/batchMaker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -85,20 +83,16 @@ 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,
)
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,
Expand Down
7 changes: 6 additions & 1 deletion src/order/cancel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const buildCancelTx = async ({
fee,
estimateWitnessSize,
ckbAsset = CKBAsset.XUDT,
excludePoolTx,
}: CancelParams): Promise<TakerResult> => {
let txFee = fee ?? MAX_FEE
const isMainnet = seller.startsWith('ckb')
Expand Down Expand Up @@ -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)) {
Expand Down
23 changes: 12 additions & 11 deletions src/order/maker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const buildMakerTx = async ({
fee,
estimateWitnessSize,
ckbAsset = CKBAsset.XUDT,
excludePoolTx,
}: MakerParams): Promise<MakerResult> => {
let txFee = fee ?? MAX_FEE
const isMainnet = seller.startsWith('ckb')
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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',
Expand Down
Loading

0 comments on commit 818ac92

Please sign in to comment.