Skip to content

Commit

Permalink
feat: dex cancel order
Browse files Browse the repository at this point in the history
  • Loading branch information
duanyytop committed Feb 2, 2024
1 parent 00ea193 commit 3aed494
Show file tree
Hide file tree
Showing 8 changed files with 577 additions and 915 deletions.
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"trailingComma": "all",
"semi": false,
"singleQuote": true,
"printWidth": 120,
"printWidth": 140,
"arrowParens": "avoid"
}
1,233 changes: 411 additions & 822 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

8 changes: 1 addition & 7 deletions src/collector/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,7 @@ export class Collector {
return new CKB(this.ckbNodeUrl)
}

async getCells({
lock,
type,
}: {
lock?: CKBComponents.Script
type?: CKBComponents.Script
}): Promise<IndexerCell[] | undefined> {
async getCells({ lock, type }: { lock?: CKBComponents.Script; type?: CKBComponents.Script }): Promise<IndexerCell[] | undefined> {
let param: any
if (lock) {
const filter = type
Expand Down
12 changes: 4 additions & 8 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,18 +122,14 @@ const MainnetInfo = {
} as CKBComponents.CellDep,
}

export const getJoyIDLockScript = (isMainnet = false) =>
isMainnet ? MainnetInfo.JoyIDLockScript : TestnetInfo.JoyIDLockScript
export const getJoyIDLockScript = (isMainnet = false) => (isMainnet ? MainnetInfo.JoyIDLockScript : TestnetInfo.JoyIDLockScript)
export const getJoyIDCellDep = (isMainnet = false) => (isMainnet ? MainnetInfo.JoyIDLockDep : TestnetInfo.JoyIDLockDep)

export const getCotaTypeScript = (isMainnet = false) =>
isMainnet ? MainnetInfo.CotaTypeScript : TestnetInfo.CotaTypeScript
export const getCotaTypeScript = (isMainnet = false) => (isMainnet ? MainnetInfo.CotaTypeScript : TestnetInfo.CotaTypeScript)
export const getCotaCellDep = (isMainnet = false) => (isMainnet ? MainnetInfo.CotaTypeDep : TestnetInfo.CotaTypeDep)

export const getDexLockScript = (isMainnet = false) =>
isMainnet ? MainnetInfo.DexLockScript : TestnetInfo.DexLockScript
export const getDexLockScript = (isMainnet = false) => (isMainnet ? MainnetInfo.DexLockScript : TestnetInfo.DexLockScript)
export const getDexCellDep = (isMainnet = false) => (isMainnet ? MainnetInfo.DexLockDep : TestnetInfo.DexLockDep)

export const getXudtTypeScript = (isMainnet = false) =>
isMainnet ? MainnetInfo.XUDTTypeScript : TestnetInfo.XUDTTypeScript
export const getXudtTypeScript = (isMainnet = false) => (isMainnet ? MainnetInfo.XUDTTypeScript : TestnetInfo.XUDTTypeScript)
export const getXudtDep = (isMainnet = false) => (isMainnet ? MainnetInfo.XUDTTypeDep : TestnetInfo.XUDTTypeDep)
133 changes: 133 additions & 0 deletions src/order/cancel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { addressToScript, blake160, getTransactionSize, serializeScript, serializeWitnessArgs } from '@nervosnetwork/ckb-sdk-utils'
import { getCotaTypeScript, getXudtDep, getJoyIDCellDep, getDexCellDep, MAX_FEE, JOYID_ESTIMATED_WITNESS_LOCK_SIZE } from '../constants'
import { CancelParams, Hex, SubkeyUnlockReq, TakerResult } from '../types'
import { append0x, leToU128, u128ToLe } from '../utils'
import { XudtException, NoCotaCellException, NoLiveCellException } from '../exceptions'
import { calculateEmptyCellMinCapacity, calculateTransactionFee, calculateXudtCellCapacity, deserializeOutPoints } from './helper'
import { CKBTransaction } from '@joyid/ckb'

export const cleanUpXudtOutputs = (orderCells: CKBComponents.LiveCell[], sellerLock: CKBComponents.Script) => {
const orderXudtTypes = new Set(orderCells.map(cell => cell.output.type))
const xudtOutputs: CKBComponents.CellOutput[] = []
const xudtOutputsData: Hex[] = []
let sumXudtCapacity = BigInt(0)

for (const orderXudtType of orderXudtTypes) {
sumXudtCapacity += calculateXudtCellCapacity(sellerLock, orderXudtType!)
xudtOutputs.push({
lock: sellerLock,
type: orderXudtType,
capacity: append0x(calculateXudtCellCapacity(sellerLock, orderXudtType!).toString(16)),
})
const xudtAmount = orderCells
.filter(cell => cell.output.type === orderXudtType)
.map(cell => leToU128(cell.data?.content!))
.reduce((prev, current) => prev + current, BigInt(0))
xudtOutputsData.push(append0x(u128ToLe(xudtAmount)))
}
return { xudtOutputs, xudtOutputsData, sumXudtCapacity }
}

export const buildCancelTx = async ({ collector, joyID, seller, orderOutPoints, fee }: CancelParams): Promise<TakerResult> => {
const txFee = fee ?? MAX_FEE
const isMainnet = seller.startsWith('ckb')
const sellerLock = addressToScript(seller)

const emptyCells = await collector.getCells({
lock: sellerLock,
})
if (!emptyCells || emptyCells.length === 0) {
throw new NoLiveCellException('The address has no empty cells')
}

const outPoints = deserializeOutPoints(orderOutPoints)

let orderInputsCapacity = BigInt(0)
// Fetch xudt order cells with outPoints
const orderCells: CKBComponents.LiveCell[] = []
for await (const outPoint of outPoints) {
const cell = await collector.getLiveCell(outPoint)
if (cell.output.lock !== sellerLock) {
throw new XudtException('The xudt cell does not belong to the seller address')
}
if (!cell.output.type || !cell.data) {
throw new XudtException('Xudt cell must have type script')
}
orderInputsCapacity += BigInt(cell.output.capacity)
orderCells.push(cell)
}

const { xudtOutputs, xudtOutputsData, sumXudtCapacity } = cleanUpXudtOutputs(orderCells, sellerLock)

const outputs = xudtOutputs
const outputsData = xudtOutputsData

const minCellCapacity = calculateEmptyCellMinCapacity(sellerLock)
const { inputs: emptyInputs, capacity: inputsCapacity } = collector.collectInputs(emptyCells, minCellCapacity, txFee, minCellCapacity)
const orderInputs: CKBComponents.CellInput[] = outPoints.map(outPoint => ({
previousOutput: outPoint,
since: '0x0',
}))
const inputs = [...emptyInputs, ...orderInputs]

const changeCapacity = inputsCapacity + orderInputsCapacity - sumXudtCapacity - txFee
const changeOutput: CKBComponents.CellOutput = {
lock: sellerLock,
capacity: append0x(changeCapacity.toString(16)),
}
outputs.push(changeOutput)
outputsData.push('0x')

let cellDeps: CKBComponents.CellDep[] = [getXudtDep(isMainnet), getDexCellDep(isMainnet)]
if (joyID) {
cellDeps.push(getJoyIDCellDep(isMainnet))
}

const emptyWitness = { lock: '', inputType: '', outputType: '' }
const witnesses = inputs.map((_, index) => (index === 0 ? serializeWitnessArgs(emptyWitness) : '0x'))
if (joyID && joyID.connectData.keyType === 'sub_key') {
const pubkeyHash = append0x(blake160(append0x(joyID.connectData.pubkey), 'hex'))
const req: SubkeyUnlockReq = {
lockScript: serializeScript(sellerLock),
pubkeyHash,
algIndex: 1, // secp256r1
}
const { unlockEntry } = await joyID.aggregator.generateSubkeyUnlockSmt(req)
const emptyWitness = {
lock: '',
inputType: '',
outputType: append0x(unlockEntry),
}
witnesses[0] = serializeWitnessArgs(emptyWitness)

const cotaType = getCotaTypeScript(isMainnet)
const cotaCells = await collector.getCells({ lock: sellerLock, type: cotaType })
if (!cotaCells || cotaCells.length === 0) {
throw new NoCotaCellException("Cota cell doesn't exist")
}
const cotaCell = cotaCells[0]
const cotaCellDep: CKBComponents.CellDep = {
outPoint: cotaCell.outPoint,
depType: 'code',
}
cellDeps = [cotaCellDep, ...cellDeps]
}
const tx: CKBComponents.RawTransaction = {
version: '0x0',
cellDeps,
headerDeps: [],
inputs,
outputs,
outputsData,
witnesses,
}

if (txFee === MAX_FEE) {
const txSize = getTransactionSize(tx) + (joyID ? JOYID_ESTIMATED_WITNESS_LOCK_SIZE : 0)
const estimatedTxFee = calculateTransactionFee(txSize)
const estimatedChangeCapacity = changeCapacity + (MAX_FEE - estimatedTxFee)
tx.outputs[tx.outputs.length - 1].capacity = append0x(estimatedChangeCapacity.toString(16))
}

return { rawTx: tx as CKBTransaction, txFee }
}
15 changes: 14 additions & 1 deletion src/order/helper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import BigNumber from 'bignumber.js'
import { CKB_UNIT } from '../constants'
import { remove0x } from '../utils'
import { append0x, remove0x } from '../utils'
import { getTransactionSize } from '@nervosnetwork/ckb-sdk-utils'
import { Hex } from '../types'
import { blockchain } from '@ckb-lumos/base'

// minimum occupied capacity and 1 ckb for transaction fee
export const calculateXudtCellCapacity = (lock: CKBComponents.Script, xudtType: CKBComponents.Script): bigint => {
Expand All @@ -24,3 +26,14 @@ export const calculateTransactionFee = (txSize: number): bigint => {
const fee = BigNumber(txSize).multipliedBy(defaultFeeRate).div(ratio)
return BigInt(fee.toFixed(0, BigNumber.ROUND_CEIL).toString())
}

export const deserializeOutPoints = (outPointHexList: Hex[]) => {
const outPoints = outPointHexList.map(outPoint => {
const op = blockchain.OutPoint.unpack(outPoint)
return {
txHash: op.txHash,
index: append0x(op.index.toString(16)),
} as CKBComponents.OutPoint
})
return outPoints
}
29 changes: 4 additions & 25 deletions src/order/maker.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,6 @@
import {
addressToScript,
blake160,
getTransactionSize,
serializeScript,
serializeWitnessArgs,
} from '@nervosnetwork/ckb-sdk-utils'
import { addressToScript, blake160, getTransactionSize, serializeScript, serializeWitnessArgs } from '@nervosnetwork/ckb-sdk-utils'
import { blockchain } from '@ckb-lumos/base'
import {
getDexLockScript,
getCotaTypeScript,
getXudtDep,
getJoyIDCellDep,
MAX_FEE,
JOYID_ESTIMATED_WITNESS_LOCK_SIZE,
} from '../constants'
import { getDexLockScript, getCotaTypeScript, getXudtDep, getJoyIDCellDep, MAX_FEE, JOYID_ESTIMATED_WITNESS_LOCK_SIZE } from '../constants'
import { Hex, SubkeyUnlockReq, MakerParams, MakerResult } from '../types'
import { append0x, remove0x, u128ToBe, u128ToLe } from '../utils'
import { XudtException, NoCotaCellException, NoLiveCellException } from '../exceptions'
Expand Down Expand Up @@ -55,11 +42,7 @@ export const buildMakerTx = async ({
if (!xudtCells || xudtCells.length === 0) {
throw new XudtException('The address has no xudt cells')
}
let {
inputs,
capacity: xudtInputsCapacity,
amount: inputsAmount,
} = collector.collectXudtInputs(xudtCells, listAmount)
let { inputs, capacity: xudtInputsCapacity, amount: inputsAmount } = collector.collectXudtInputs(xudtCells, listAmount)
inputs = [...emptyInputs, ...inputs]

// build dex and other outputs and outputsData
Expand Down Expand Up @@ -139,12 +122,8 @@ export const buildMakerTx = async ({
witnesses,
}

let txSize = getTransactionSize(tx)
if (joyID) {
txSize += JOYID_ESTIMATED_WITNESS_LOCK_SIZE
}

if (txFee === MAX_FEE) {
const txSize = getTransactionSize(tx) + (joyID ? JOYID_ESTIMATED_WITNESS_LOCK_SIZE : 0)
const estimatedTxFee = calculateTransactionFee(txSize)
const estimatedChangeCapacity = changeCapacity + (MAX_FEE - estimatedTxFee)
tx.outputs[tx.outputs.length - 1].capacity = append0x(estimatedChangeCapacity.toString(16))
Expand Down
60 changes: 9 additions & 51 deletions src/order/taker.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,9 @@
import {
addressToScript,
blake160,
getTransactionSize,
serializeScript,
serializeWitnessArgs,
} from '@nervosnetwork/ckb-sdk-utils'
import {
getCotaTypeScript,
getXudtDep,
getJoyIDCellDep,
getDexCellDep,
MAX_FEE,
JOYID_ESTIMATED_WITNESS_LOCK_SIZE,
} from '../constants'
import { addressToScript, blake160, getTransactionSize, serializeScript, serializeWitnessArgs } from '@nervosnetwork/ckb-sdk-utils'
import { getCotaTypeScript, getXudtDep, getJoyIDCellDep, getDexCellDep, MAX_FEE, JOYID_ESTIMATED_WITNESS_LOCK_SIZE } from '../constants'
import { Hex, SubkeyUnlockReq, TakerParams, TakerResult } from '../types'
import { append0x, leToU128, u128ToLe } from '../utils'
import { XudtException, NoCotaCellException, NoLiveCellException } from '../exceptions'
import { calculateEmptyCellMinCapacity, calculateTransactionFee, calculateXudtCellCapacity } from './helper'
import { blockchain } from '@ckb-lumos/base'
import { calculateEmptyCellMinCapacity, calculateTransactionFee, calculateXudtCellCapacity, deserializeOutPoints } from './helper'
import { OrderArgs } from './orderArgs'
import { CKBTransaction } from '@joyid/ckb'

Expand Down Expand Up @@ -62,13 +48,7 @@ export const matchOrderOutputs = (orderCells: CKBComponents.LiveCell[]) => {
return { orderOutputs, orderOutputsData, sumOrderCapacity }
}

export const buildTakerTx = async ({
collector,
joyID,
buyer,
orderOutPoints,
fee,
}: TakerParams): Promise<TakerResult> => {
export const buildTakerTx = async ({ collector, joyID, buyer, orderOutPoints, fee }: TakerParams): Promise<TakerResult> => {
const txFee = fee ?? MAX_FEE
const isMainnet = buyer.startsWith('ckb')
const buyerLock = addressToScript(buyer)
Expand All @@ -81,13 +61,7 @@ export const buildTakerTx = async ({
}

// Deserialize outPointHex array to outPoint array
const outPoints = orderOutPoints.map(outPoint => {
const outPoint_ = blockchain.OutPoint.unpack(outPoint)
return {
txHash: outPoint_.txHash,
index: append0x(outPoint_.index.toString(16)),
} as CKBComponents.OutPoint
})
const outPoints = deserializeOutPoints(orderOutPoints)

// Fetch xudt order cells with outPoints
const orderCells: CKBComponents.LiveCell[] = []
Expand All @@ -107,12 +81,7 @@ export const buildTakerTx = async ({
const outputsData = [...orderOutputsData, ...xudtOutputsData]

const minCellCapacity = calculateEmptyCellMinCapacity(buyerLock)
const { inputs: emptyInputs, capacity: inputsCapacity } = collector.collectInputs(
emptyCells,
needInputsCapacity,
txFee,
minCellCapacity,
)
const { inputs: emptyInputs, capacity: inputsCapacity } = collector.collectInputs(emptyCells, needInputsCapacity, txFee, minCellCapacity)
const orderInputs: CKBComponents.CellInput[] = outPoints.map(outPoint => ({
previousOutput: outPoint,
since: '0x0',
Expand All @@ -132,15 +101,8 @@ export const buildTakerTx = async ({
cellDeps.push(getJoyIDCellDep(isMainnet))
}

const witnesses = []
for (let index = 0; index < inputs.length; index++) {
if (index === orderInputs.length) {
const emptyWitness = { lock: '', inputType: '', outputType: '' }
witnesses.push(serializeWitnessArgs(emptyWitness))
continue
}
witnesses.push('0x')
}
const emptyWitness = { lock: '', inputType: '', outputType: '' }
const witnesses = inputs.map((_, index) => (index === orderInputs.length ? serializeWitnessArgs(emptyWitness) : '0x'))
if (joyID && joyID.connectData.keyType === 'sub_key') {
const pubkeyHash = append0x(blake160(append0x(joyID.connectData.pubkey), 'hex'))
const req: SubkeyUnlockReq = {
Expand Down Expand Up @@ -178,12 +140,8 @@ export const buildTakerTx = async ({
witnesses,
}

let txSize = getTransactionSize(tx)
if (joyID) {
txSize += JOYID_ESTIMATED_WITNESS_LOCK_SIZE
}

if (txFee === MAX_FEE) {
const txSize = getTransactionSize(tx) + (joyID ? JOYID_ESTIMATED_WITNESS_LOCK_SIZE : 0)
const estimatedTxFee = calculateTransactionFee(txSize)
const estimatedChangeCapacity = changeCapacity + (MAX_FEE - estimatedTxFee)
tx.outputs[tx.outputs.length - 1].capacity = append0x(estimatedChangeCapacity.toString(16))
Expand Down

0 comments on commit 3aed494

Please sign in to comment.