Skip to content

Commit

Permalink
refactor: move btc/ckb utils from the rgbpp lib to sub-libs
Browse files Browse the repository at this point in the history
  • Loading branch information
ShookLyngs committed Aug 9, 2024
1 parent 2852645 commit c492cf3
Show file tree
Hide file tree
Showing 16 changed files with 220 additions and 87 deletions.
15 changes: 10 additions & 5 deletions packages/btc/src/api/sendRgbppUtxos.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { Collector, checkCkbTxInputsCapacitySufficient } from '@rgbpp-sdk/ckb';
import { isRgbppLockCell, isBtcTimeLockCell, calculateCommitment } from '@rgbpp-sdk/ckb';
import {
Collector,
isRgbppLockCell,
isBtcTimeLockCell,
calculateCommitment,
unpackRgbppLockArgs,
checkCkbTxInputsCapacitySufficient,
} from '@rgbpp-sdk/ckb';
import { bitcoin } from '../bitcoin';
import { BaseOutput, Utxo } from '../transaction/utxo';
import { AddressToPubkeyMap } from '../address';
Expand All @@ -8,7 +14,6 @@ import { NetworkType } from '../preset/types';
import { ErrorCodes, TxBuildError } from '../error';
import { InitOutput, TxAddressOutput, TxBuilder } from '../transaction/build';
import { networkTypeToConfig } from '../preset/config';
import { unpackRgbppLockArgs } from '../ckb/molecule';
import { createSendUtxosBuilder } from './sendUtxos';
import { limitPromiseBatchSize } from '../utils';

Expand Down Expand Up @@ -65,7 +70,7 @@ export async function createSendRgbppUtxosBuilder(props: SendRgbppUtxosProps): P
rgbppLockArgsList.map((rgbppLockArgs) => {
if (rgbppLockArgs) {
return limitPromiseBatchSize(() =>
props.source.getUtxo(rgbppLockArgs.btcTxid, rgbppLockArgs.outIndex, props.onlyConfirmedUtxos),
props.source.getUtxo(rgbppLockArgs.btcTxId, rgbppLockArgs.outIndex, props.onlyConfirmedUtxos),
);
}
return undefined;
Expand All @@ -85,7 +90,7 @@ export async function createSendRgbppUtxosBuilder(props: SendRgbppUtxosProps): P
if (!utxo) {
throw TxBuildError.withComment(
ErrorCodes.CANNOT_FIND_UTXO,
`hash: ${rgbppLockArgs.btcTxid}, index: ${rgbppLockArgs.outIndex}`,
`hash: ${rgbppLockArgs.btcTxId}, index: ${rgbppLockArgs.outIndex}`,
);
}

Expand Down
21 changes: 0 additions & 21 deletions packages/btc/src/ckb/molecule.ts

This file was deleted.

4 changes: 2 additions & 2 deletions packages/btc/src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ export enum ErrorCodes {
UNSUPPORTED_OP_RETURN_SCRIPT,
INVALID_FEE_RATE,
PAYMASTER_MISMATCH,
INVALID_UTXO_ID,

CKB_CANNOT_FIND_OUTPOINT = 40,
CKB_INVALID_CELL_LOCK,
CKB_INVALID_INPUTS,
CKB_INVALID_OUTPUTS,
CKB_UNMATCHED_COMMITMENT,
CKB_RGBPP_LOCK_UNPACK_ERROR,

MEMPOOL_API_RESPONSE_ERROR = 60,
}
Expand All @@ -45,13 +45,13 @@ export const ErrorMessages = {
[ErrorCodes.UNSUPPORTED_OP_RETURN_SCRIPT]: 'Unsupported OP_RETURN script format',
[ErrorCodes.INVALID_FEE_RATE]: 'Invalid fee rate provided or recommended',
[ErrorCodes.PAYMASTER_MISMATCH]: 'Paymaster mismatched',
[ErrorCodes.INVALID_UTXO_ID]: 'Invalid UtxoId',

[ErrorCodes.CKB_CANNOT_FIND_OUTPOINT]: 'Cannot find CKB cell by OutPoint, it may not exist or is not live',
[ErrorCodes.CKB_INVALID_CELL_LOCK]: 'Invalid CKB cell lock, it should be RgbppLock, RgbppTimeLock or null',
[ErrorCodes.CKB_INVALID_INPUTS]: 'Invalid input(s) found in the CKB VirtualTx',
[ErrorCodes.CKB_INVALID_OUTPUTS]: 'Invalid output(s) found in the CKB VirtualTx',
[ErrorCodes.CKB_UNMATCHED_COMMITMENT]: 'Invalid commitment found in the CKB VirtualTx',
[ErrorCodes.CKB_RGBPP_LOCK_UNPACK_ERROR]: 'Failed to unpack RgbppLockArgs from the CKB cell lock',

[ErrorCodes.MEMPOOL_API_RESPONSE_ERROR]: 'Mempool.space API returned an error',
};
Expand Down
40 changes: 40 additions & 0 deletions packages/btc/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import limitPromiseConcurrency from 'p-limit';
import { bitcoin, ecc, ECPair } from './bitcoin';
import { bytes } from '@ckb-lumos/codec';
import { BaseOutput } from './transaction/utxo';
import { ErrorCodes, TxBuildError } from './error';

interface TweakableSigner extends bitcoin.Signer {
privateKey?: Buffer;
Expand Down Expand Up @@ -77,6 +79,44 @@ export function transactionToHex(tx: bitcoin.Transaction, withWitness?: boolean)
return buffer.toString('hex');
}

/**
* Encode a UTXO's txid and vout to a string ID of "{txid}:{vout}".
*/
export function encodeUtxoId(txid: string, vout: number): string {
if (!txid || remove0x(txid).length !== 64) {
throw TxBuildError.withComment(ErrorCodes.INVALID_UTXO_ID, `txid=${txid}`);
}
if (vout < 0 || vout > 0xffffffff) {
throw TxBuildError.withComment(ErrorCodes.INVALID_UTXO_ID, `vout=${vout}`);
}

return `${remove0x(txid)}:${vout}`;
}

/**
* Decode a string ID of "{txid}:{vout}" format to a BaseOutput object.
*/
export function decodeUtxoId(utxoId: string): BaseOutput {
const parts = utxoId.split(':');
const txid = parts[0];
const vout = parts[1] ? parseInt(parts[1]) : undefined;
if (
!txid ||
txid.startsWith('0x') ||
txid.length !== 64 ||
typeof vout !== 'number' ||
isNaN(vout) ||
vout > 0xffffffff
) {
throw TxBuildError.withComment(ErrorCodes.INVALID_UTXO_ID, utxoId);
}

return {
txid,
vout,
};
}

/**
* Limits the batch size of promises when querying with Promise.all().
* @example
Expand Down
34 changes: 32 additions & 2 deletions packages/btc/tests/Utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { describe, expect, it } from 'vitest';
import { bitcoin, transactionToHex } from '../src';
import { bitcoin, decodeUtxoId, encodeUtxoId, transactionToHex } from '../src';

describe('Utils', () => {
it('Convert transaction to hex', () => {
it('transactionToHex()', () => {
const originalHex =
'02000000000101177e673414fb4a393f0e1faf27a317d92e9f1a7b9a3ff36713d46ef5b7a1a6190100000000ffffffff020000000000000000226a20849f5b17209de17af5a94f0111e2ba03d1409da87a0f06894abb85b3b5024726df3c0f000000000016001462fc12a35b779f0cf7edcb9690be19b0386e0f9a024830450221009d869f20ef22864e02603571ce40da0586c03f20f5b8fb6295a4d636141d39dc02207082fdef40b34f6189491cba98c861ddfc8889d91c48f11f4660f11e93b1153b012103e1c38cf06691d449961d2b8f261a9a238c53da91d3a1e948497f7b1fe717968000000000';
const tx = bitcoin.Transaction.fromHex(originalHex);
Expand All @@ -19,4 +19,34 @@ describe('Utils', () => {
'0200000001177e673414fb4a393f0e1faf27a317d92e9f1a7b9a3ff36713d46ef5b7a1a6190100000000ffffffff020000000000000000226a20849f5b17209de17af5a94f0111e2ba03d1409da87a0f06894abb85b3b5024726df3c0f000000000016001462fc12a35b779f0cf7edcb9690be19b0386e0f9a00000000',
);
});
it('encodeUtxoId()', () => {
expect(encodeUtxoId('0da44932270292fd3a4165f1f7ab81abf69b951e1e7d3b5c012e00a291c6b222', 0)).toEqual(
'0da44932270292fd3a4165f1f7ab81abf69b951e1e7d3b5c012e00a291c6b222:0',
);
expect(encodeUtxoId('0x0da44932270292fd3a4165f1f7ab81abf69b951e1e7d3b5c012e00a291c6b222', 0)).toEqual(
'0da44932270292fd3a4165f1f7ab81abf69b951e1e7d3b5c012e00a291c6b222:0',
);
expect(encodeUtxoId('0x0da44932270292fd3a4165f1f7ab81abf69b951e1e7d3b5c012e00a291c6b222', 0xffffffff)).toEqual(
'0da44932270292fd3a4165f1f7ab81abf69b951e1e7d3b5c012e00a291c6b222:4294967295',
);
expect(() => encodeUtxoId('0da44932270292fd3a4165f1f7ab81abf69b951e1e7d3b5c012e00a291c6b22', 0)).toThrowError();
expect(() =>
encodeUtxoId('0da44932270292fd3a4165f1f7ab81abf69b951e1e7d3b5c012e00a291c6b222', 0xffffffff01),
).toThrowError();
});
it('decodeUtxoId()', () => {
expect(decodeUtxoId('0da44932270292fd3a4165f1f7ab81abf69b951e1e7d3b5c012e00a291c6b222:0')).toStrictEqual({
txid: '0da44932270292fd3a4165f1f7ab81abf69b951e1e7d3b5c012e00a291c6b222',
vout: 0,
});
expect(decodeUtxoId('0da44932270292fd3a4165f1f7ab81abf69b951e1e7d3b5c012e00a291c6b222:4294967295')).toStrictEqual({
txid: '0da44932270292fd3a4165f1f7ab81abf69b951e1e7d3b5c012e00a291c6b222',
vout: 4294967295,
});

expect(() => decodeUtxoId('0x0da44932270292fd3a4165f1f7ab81abf69b951e1e7d3b5c012e00a291c6b222:0')).toThrowError();
expect(() =>
decodeUtxoId('0x0da44932270292fd3a4165f1f7ab81abf69b951e1e7d3b5c012e00a291c6b222:42949672951'),
).toThrowError();
});
});
8 changes: 8 additions & 0 deletions packages/ckb/src/error/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ enum ErrorCode {
RgbppCkbTxInputsExceeded = 109,
RgbppUtxoBindMultiTypeAssets = 110,
RgbppSporeTypeMismatch = 111,
InvalidCellId = 112,
}

export class CapacityNotEnoughError extends Error {
Expand Down Expand Up @@ -96,3 +97,10 @@ export class RgbppSporeTypeMismatchError extends Error {
super(message);
}
}

export class InvalidCellIdError extends Error {
code = ErrorCode.InvalidCellId;
constructor(message: string) {
super(message);
}
}
45 changes: 45 additions & 0 deletions packages/ckb/src/utils/id.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { describe, it, expect } from 'vitest';
import { encodeCellId, decodeCellId } from './id';

describe('cell id', () => {
it('encodeCellId', () => {
expect(encodeCellId('0x7610efaec3b9ce66349909fea88a1ae78cd488de3128bc6f71afc068306e0e65', '0x0')).toBe(
'0x7610efaec3b9ce66349909fea88a1ae78cd488de3128bc6f71afc068306e0e65:0x0',
);
expect(encodeCellId('0x7610efaec3b9ce66349909fea88a1ae78cd488de3128bc6f71afc068306e0e65', '0xffffffff')).toBe(
'0x7610efaec3b9ce66349909fea88a1ae78cd488de3128bc6f71afc068306e0e65:0xffffffff',
);

expect(() =>
encodeCellId('0x7610efaec3b9ce66349909fea88a1ae78cd488de3128bc6f71afc068306e0e6', '0x0'),
).toThrowError();
expect(() =>
encodeCellId('0x7610efaec3b9ce66349909fea88a1ae78cd488de3128bc6f71afc068306e0e65', '0xffffffff01'),
).toThrowError();
expect(() =>
encodeCellId('7610efaec3b9ce66349909fea88a1ae78cd488de3128bc6f71afc068306e0e65', '0x0'),
).toThrowError();
expect(() =>
encodeCellId('0x7610efaec3b9ce66349909fea88a1ae78cd488de3128bc6f71afc068306e0e65', '0'),
).toThrowError();
});
it('decodeCellId', () => {
expect(decodeCellId('0x7610efaec3b9ce66349909fea88a1ae78cd488de3128bc6f71afc068306e0e65:0x0')).toStrictEqual({
txHash: '0x7610efaec3b9ce66349909fea88a1ae78cd488de3128bc6f71afc068306e0e65',
index: '0x0',
});
expect(decodeCellId('0x7610efaec3b9ce66349909fea88a1ae78cd488de3128bc6f71afc068306e0e65:0xffffffff')).toStrictEqual(
{
txHash: '0x7610efaec3b9ce66349909fea88a1ae78cd488de3128bc6f71afc068306e0e65',
index: '0xffffffff',
},
);

expect(() => decodeCellId('0x7610efaec3b9ce66349909fea88a1ae78cd488de3128bc6f71afc068306e0e6:0x0')).toThrowError();
expect(() =>
decodeCellId('0x7610efaec3b9ce66349909fea88a1ae78cd488de3128bc6f71afc068306e0e65:0xffffffff01'),
).toThrowError();
expect(() => decodeCellId('7610efaec3b9ce66349909fea88a1ae78cd488de3128bc6f71afc068306e0e65:0x0')).toThrowError();
expect(() => decodeCellId('0x7610efaec3b9ce66349909fea88a1ae78cd488de3128bc6f71afc068306e0e65:0')).toThrowError();
});
});
37 changes: 37 additions & 0 deletions packages/ckb/src/utils/id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Hash, HexNumber, OutPoint, blockchain } from '@ckb-lumos/base';
import { InvalidCellIdError } from '../error';
import { append0x } from './hex';

export const encodeCellId = (txHash: Hash, index: HexNumber): string => {
if (!txHash.startsWith('0x') || !index.startsWith('0x')) {
throw new InvalidCellIdError(`Cannot encode CellId due to valid format: txHash=${txHash}, index=${index}`);
}
try {
blockchain.OutPoint.pack({
txHash,
index,
});
return `${txHash}:${index}`;
} catch {
throw new InvalidCellIdError(`Cannot encode CellId due to valid format: txHash=${txHash}, index=${index}`);
}
};

export const decodeCellId = (cellId: string): OutPoint => {
const [txHash, index] = cellId.split(':');
if (!txHash.startsWith('0x') || !index.startsWith('0x')) {
throw new InvalidCellIdError(`Cannot decode CellId: ${cellId}`);
}
try {
blockchain.OutPoint.pack({
txHash,
index,
});
return {
txHash: append0x(txHash),
index: append0x(index),
};
} catch {
throw new InvalidCellIdError(`Cannot decode CellId due to valid format: ${cellId}`);
}
};
1 change: 1 addition & 0 deletions packages/ckb/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './ckb-tx';
export * from './rgbpp';
export * from './spore';
export * from './cell-dep';
export * from './id';
7 changes: 7 additions & 0 deletions packages/ckb/src/utils/rgbpp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
throwErrorWhenTxInputsExceeded,
throwErrorWhenRgbppCellsInvalid,
isRgbppCapacitySufficientForChange,
unpackRgbppLockArgs,
} from './rgbpp';
import { getXudtTypeScript } from '../constants';
import { IndexerCell, RgbppCkbVirtualTx } from '../types';
Expand Down Expand Up @@ -221,6 +222,12 @@ describe('rgbpp tests', () => {
expect('0x020000000000000000000000000000000000000000000000000000000000000000000000').toBe(buildPreLockArgs(2));
});

it('unpackRgbppLockArgs', () => {
const unpacked = unpackRgbppLockArgs('0x0200000006ec22c2def100bba3e295a1ff279c490d227151bf3166a4f3f008906c849399');
expect('0x9993846c9008f0f3a46631bf5171220d499c27ffa195e2a3bb00f1dec222ec06').toBe(unpacked.btcTxId);
expect(2).toBe(unpacked.outIndex);
});

it('replaceRealBtcTxId', () => {
const rgbppLockArgs = '0x020000000000000000000000000000000000000000000000000000000000000000000000';
const realBtcTxId = '0x9993846c9008f0f3a46631bf5171220d499c27ffa195e2a3bb00f1dec222ec06';
Expand Down
15 changes: 14 additions & 1 deletion packages/ckb/src/utils/rgbpp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import {
getBtcTimeLockScript,
getRgbppLockScript,
} from '../constants';
import { RGBPPLock } from '../schemas/generated/rgbpp';
import { BTCTimeLock } from '../schemas/generated/rgbpp';
import { Script } from '../schemas/generated/blockchain';
import { bytes } from '@ckb-lumos/codec';
import { bytes, BytesLike } from '@ckb-lumos/codec';
import { toCamelcase } from './case-parser';
import {
InputsOrOutputsLenError,
Expand Down Expand Up @@ -142,6 +143,18 @@ export const buildPreLockArgs = (outIndex: number) => {
return buildRgbppLockArgs(outIndex, RGBPP_TX_ID_PLACEHOLDER);
};

export interface RgbppLockArgs {
btcTxId: Hex;
outIndex: number;
}
export const unpackRgbppLockArgs = (source: BytesLike): RgbppLockArgs => {
const unpacked = RGBPPLock.unpack(source);
return {
btcTxId: reverseHex(unpacked.btcTxid),
outIndex: unpacked.outIndex,
};
};

export const compareInputs = (a: IndexerCell, b: IndexerCell) => {
if (a.output.lock.args < b.output.lock.args) {
return -1;
Expand Down
6 changes: 2 additions & 4 deletions packages/rgbpp/src/rgbpp/summary/asset-summarizer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Cell } from '@ckb-lumos/base';
import { Utxo } from '@rgbpp-sdk/btc';
import { leToU128 } from '@rgbpp-sdk/ckb';
import { encodeCellId } from '../utils/ckb';
import { encodeUtxoId } from '../utils/btc';
import { Utxo, encodeUtxoId } from '@rgbpp-sdk/btc';
import { leToU128, encodeCellId } from '@rgbpp-sdk/ckb';

export interface AssetSummary {
amount: bigint;
Expand Down
18 changes: 0 additions & 18 deletions packages/rgbpp/src/rgbpp/utils/btc.ts

This file was deleted.

Loading

0 comments on commit c492cf3

Please sign in to comment.