Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rgbpp): support for batch transferring of RGBPP XUDT assets #270

Merged
merged 17 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
3c0eb05
feat: support for batch transferring of RGBPP XUDT assets
ShookLyngs Aug 6, 2024
a31a376
chore: add changeset for 3c0eb05d
ShookLyngs Aug 6, 2024
7f84d39
test: update rgbpp tests for the btc-transfer-all feature
ShookLyngs Aug 7, 2024
1357c68
test: add a default value for IS_MAINNET in the rgbpp test env
ShookLyngs Aug 7, 2024
f7f1af4
chore: add ckb node/indexer url as env variables to the test workflow
ShookLyngs Aug 7, 2024
6441f92
docs: add a description of the buildRgbppTransferTx() API to the READ…
ShookLyngs Aug 7, 2024
2852645
fix: wrong ckb node/indexer url specified in the test workflow
ShookLyngs Aug 9, 2024
c492cf3
refactor: move btc/ckb utils from the rgbpp lib to sub-libs
ShookLyngs Aug 9, 2024
0f8f523
refactor: remove saveJson() util in the rgbpp tests
ShookLyngs Aug 9, 2024
37074c4
refactor: remove "sent" and "retry" in types the sendRgbppTxGroups() …
ShookLyngs Aug 10, 2024
699be77
chore: update changeset for c492cf3f
ShookLyngs Aug 10, 2024
b1865c2
refactor: remove deprecated error in rgbpp lib
ShookLyngs Aug 11, 2024
736d088
refactor: add "fromPubkey" to RgbppTransferBtcParams in the rgbpp lib
ShookLyngs Aug 11, 2024
75840f8
fix: missing check statement in decodeUtxoId() method
ShookLyngs Aug 11, 2024
1440fb8
refactor: rename some props with some fixes in the AssetSummarizer
ShookLyngs Aug 11, 2024
c7f37a3
docs: update rgbpp README with improved type descriptions
ShookLyngs Aug 11, 2024
cd64417
fix: if the target utxo is bound to any unsupported-type cells, mark …
ShookLyngs Aug 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/twenty-jeans-warn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'rgbpp': minor
---

Support for batch transferring of RGBPP XUDT assets

- Add `buildRgbppTransferAllTxs()` API to generate one or more BTC/CKB transaction groups for transferring the entire amount of a specific type of RGBPP XUDT asset from one or more BTC addresses to a recipient
- Add `sendRgbppTxGroups()` API for sending BTC/CKB transaction groups to the `BtcAssetsApi`
2 changes: 2 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ jobs:
- name: Run tests for packages
run: pnpm run test:packages
env:
VITE_CKB_NODE_URL: https://testnet.ckb.dev/rpc
VITE_CKB_INDEXER_URL: https://testnet.ckb.dev/indexer
VITE_BTC_SERVICE_URL: https://btc-assets-api.testnet.mibao.pro
VITE_BTC_SERVICE_TOKEN: ${{ secrets.TESTNET_SERVICE_TOKEN }}
VITE_BTC_SERVICE_ORIGIN: https://btc-assets-api.testnet.mibao.pro
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"tsx": "4.16.3",
"tsup": "^8.1.0",
"typescript": "^5.4.3",
"vitest": "1.6.0"
"vitest": "2.0.5"
},
"lint-staged": {
"{packages,apps,examples,tests}/**/*.{js,jsx,ts,tsx}": [
Expand Down
11 changes: 11 additions & 0 deletions packages/rgbpp/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Network
VITE_IS_MAINNET=false

# CKB
VITE_CKB_NODE_URL=https://testnet.ckb.dev/rpc
VITE_CKB_INDEXER_URL=https://testnet.ckb.dev/indexer

# BTC
VITE_BTC_SERVICE_URL=https://btc-assets-api.testnet.mibao.pro
VITE_BTC_SERVICE_TOKEN=
VITE_BTC_SERVICE_ORIGIN=
51 changes: 48 additions & 3 deletions packages/rgbpp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ $ pnpm add rgbpp

## Transfer RGB++ Assets on BTC

The function `buildRgbppTransferTx` will generate CKB virtual transaction and related BTC transaction with the commitment for the RGB++ assets transfer on BTC.
The function `buildRgbppTransferTx` will generate a CKB virtual transaction and a related BTC transaction with the commitment for the RGB++ assets transfer on BTC.

The `btcPsbtHex` can be used to construct bitcoin PSBT to sign and send BTC transaction with BTC wallet, and then the BTC transaction id and `ckbVirtualTxResult` will be used to post to RGB++ Queue Service to complete the isomorphic CKB transaction.

Expand All @@ -39,7 +39,7 @@ const { ckbVirtualTxResult, btcPsbtHex } = await buildRgbppTransferTx({
isMainnet,
});

// Construct SPBT with btcPsbtHex to sign and send BTC transaction with the BTC key pair
// Construct PSBT with btcPsbtHex to sign and send BTC transaction with the BTC key pair
const psbt = bitcoin.Psbt.fromHex(btcPsbtHex);
psbt.signAllInputs(btcKeyPair);
psbt.finalizeAllInputs();
Expand All @@ -49,4 +49,49 @@ const { txid: btcTxId } = await btcService.sendBtcTransaction(btcTx.toHex());

// Post the BTC txId and ckbVirtualTxResult to the RGB++ Queue Service
await btcService.sendRgbppCkbTransaction({ btc_txid: btcTxId, ckb_virtual_result: ckbVirtualTxResult });
```
```

## Transfer all balance of an RGB++ Asset on BTC

Similar to using the `buildRgbppTransferTx` function, the function `buildRgbppTransferAllTxs` will generate a list of RGB++ transaction groups (a transaction group includes a CKB virtual transaction and a BTC isomorphic transaction).

You should sign all the PSBTs in the `transactions` and send all the BTC transactions, and then post all the BTC txIds and ckbVirtualTxResults to the RGB++ Queue Service. You can also review the transfer details in the `summary` object.

```TypeScript
const { transactions, summary } = await buildRgbppTransferAllTxs({
ckb: {
xudtTypeArgs,
collector,
},
btc: {
assetAddresses,
fromAddress,
toAddress,
dataSource,
feeRate,
pubkeyMap,
},
isMainnet,
});
Flouse marked this conversation as resolved.
Show resolved Hide resolved

// Sign BTC PSBTs with all the related BTC key pairs, and convert them to BTC transactions
const signedGroups: RgbppTxGroup[] = transactions.map((group) => {
const psbt = bitcoin.Psbt.fromHex(group.btc.psbtHex);
signPsbt(psbt, keyPair);
psbt.finalizeAllInputs();

return {
ckbVirtualTxResult: JSON.stringify(group.ckb.virtualTxResult),
btcTxHex: psbt.extractTransaction().toHex(),
};
});

// Post the transaction groups to the RGB++ Queue Service
const sentResult = await sendRgbppTxGroups({
txGroups: signedGroups,
btcService: btcSource.service,
});

// Review the summary of the transfer
console.log(summary);
```
12 changes: 10 additions & 2 deletions packages/rgbpp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "rgbpp",
"version": "0.5.0",
"scripts": {
"test": "vitest",
"build": "tsup",
"lint": "tsc && eslint --ext .ts src/* && prettier --check 'src/*.ts'",
"lint:fix": "tsc && eslint --fix --ext .ts src/* && prettier --write 'src/*.ts'"
Expand Down Expand Up @@ -58,12 +59,19 @@
"dist"
],
"dependencies": {
"@ckb-lumos/base": "^0.22.2",
"@ckb-lumos/codec": "^0.22.2",
"@nervosnetwork/ckb-sdk-utils": "0.109.2",
"@rgbpp-sdk/btc": "workspace:*",
"@rgbpp-sdk/ckb": "workspace:*",
"@rgbpp-sdk/service": "workspace:*",
"@nervosnetwork/ckb-sdk-utils": "0.109.2"
"@rgbpp-sdk/service": "workspace:*"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@types/node": "^20.3.1",
"lodash": "^4.17.21",
"zod": "^3.23.8"
}
}
12 changes: 10 additions & 2 deletions packages/rgbpp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,13 @@ export type { SendBtcProps, SendUtxosProps, SendRgbppUtxosProps } from '@rgbpp-s
/**
* RGB++
*/
export { buildRgbppTransferTx } from './rgbpp/xudt';
export type { RgbppTransferTxParams, RgbppTransferTxResult } from './rgbpp/types';
export type {
RgbppTransferTxParams,
RgbppTransferTxResult,
RgbppTransferAllTxsParams,
RgbppTransferAllTxsResult,
} from './rgbpp/types/xudt';
export { RgbppError, RgbppErrorCodes } from './rgbpp/error';
export { buildRgbppTransferTx } from './rgbpp/xudt/btc-transfer';
export { buildRgbppTransferAllTxs } from './rgbpp/xudt/btc-transfer-all';
export { sendRgbppTxGroups } from './rgbpp/utils/transaction';
27 changes: 27 additions & 0 deletions packages/rgbpp/src/rgbpp/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export enum RgbppErrorCodes {
UNKNOWN,

CANNOT_DECODE_UTXO_ID = 20,
UNEXPECTED_CKB_VTX_OUTPUTS_LENGTH,
}

export const RgbppErrorMessages = {
[RgbppErrorCodes.UNKNOWN]: 'Unknown error',

[RgbppErrorCodes.CANNOT_DECODE_UTXO_ID]: 'Cannot decode UtxoId',
[RgbppErrorCodes.UNEXPECTED_CKB_VTX_OUTPUTS_LENGTH]: 'Unexpected length of the CkbVirtualTx outputs',
};

export class RgbppError extends Error {
public code = RgbppErrorCodes.UNKNOWN;
constructor(code: RgbppErrorCodes, message = RgbppErrorMessages[code] || 'Unknown error') {
super(message);
this.code = code;
Object.setPrototypeOf(this, RgbppError.prototype);
}

static withComment(code: RgbppErrorCodes, comment?: string): RgbppError {
const message: string | undefined = RgbppErrorMessages[code];
return new RgbppError(code, comment ? `${message}: ${comment}` : message);
}
}
100 changes: 100 additions & 0 deletions packages/rgbpp/src/rgbpp/summary/asset-summarizer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
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';

export interface AssetSummary {
amount: bigint;
utxos: number;
Flouse marked this conversation as resolved.
Show resolved Hide resolved
cells: number;
}

export interface AssetGroupSummary {
utxoId: string;
cellIds: string[];
assets: Record<string, AssetSummary>;
}
ShookLyngs marked this conversation as resolved.
Show resolved Hide resolved

export interface TransactionGroupSummary {
utxos: number;
cells: number;
ShookLyngs marked this conversation as resolved.
Show resolved Hide resolved
utxoIds: string[];
cellIds: string[];
assets: Record<string, AssetSummary>;
}

export class AssetSummarizer {
groups: AssetGroupSummary[] = [];

constructor() {}

addGroup(utxo: Utxo, cells: Cell[]): AssetGroupSummary {
const utxoId = encodeUtxoId(utxo.txid, utxo.vout);
const assets: Record<string, Omit<AssetSummary, 'xudtTypeArgs'>> = {};
ShookLyngs marked this conversation as resolved.
Show resolved Hide resolved
const cellIds: string[] = [];

for (const cell of cells) {
cellIds.push(encodeCellId(cell.outPoint!.txHash, cell.outPoint!.index));
const xudtTypeArgs = cell.cellOutput.type?.args ?? 'empty';
const amount = leToU128(cell.data.substring(0, 34));
if (assets[xudtTypeArgs] === undefined) {
assets[xudtTypeArgs] = {
utxos: 1,
cells: 0,
amount: 0n,
};
}

assets[xudtTypeArgs]!.cells += 1;
assets[xudtTypeArgs]!.amount += amount;
}

const result: AssetGroupSummary = {
utxoId,
cellIds,
assets,
};

this.groups.push(result);
return result;
}

addGroups(groups: { utxo: Utxo; cells: Cell[] }[]): TransactionGroupSummary {
const groupResults = groups.map((group) => this.addGroup(group.utxo, group.cells));
return this.summarizeGroups(groupResults);
}

summarizeGroups(groups?: AssetGroupSummary[]): TransactionGroupSummary {
ShookLyngs marked this conversation as resolved.
Show resolved Hide resolved
const targetGroups = groups ?? this.groups;
const utxoIds = targetGroups.map((summary) => summary.utxoId);
const cellIds = targetGroups.flatMap((summary) => summary.cellIds);
const assets = targetGroups.reduce(
(result, summary) => {
for (const xudtTypeArgs in summary.assets) {
if (result[xudtTypeArgs] === undefined) {
result[xudtTypeArgs] = {
utxos: 0,
cells: 0,
amount: 0n,
};
}

result[xudtTypeArgs]!.utxos += summary.assets[xudtTypeArgs]!.utxos;
result[xudtTypeArgs]!.cells += summary.assets[xudtTypeArgs]!.cells;
Flouse marked this conversation as resolved.
Show resolved Hide resolved
result[xudtTypeArgs]!.amount += summary.assets[xudtTypeArgs]!.amount;
}
return result;
},
{} as Record<string, Omit<AssetSummary, 'xudtTypeArgs'>>,
);

return {
utxos: utxoIds.length,
cells: cellIds.length,
utxoIds,
cellIds,
assets,
};
}
}
42 changes: 0 additions & 42 deletions packages/rgbpp/src/rgbpp/types.ts

This file was deleted.

Loading