Skip to content
This repository was archived by the owner on May 27, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions .changeset/gold-rules-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@rgbpp-sdk/service': minor
'rgbpp': minor
'@rgbpp-sdk/ckb': minor
---

Support compatible xUDT RGB++ assets

- Fetch compatible xUDT `cellDeps` to build CKB transactions from the `typeid-contract-cell-deps` GitHub repository
- Update the `ckb` package to support RGB++ compatible xUDT assets leaping and transferring
- Add optional parameter `compatibleXudtTypeScript` to the functions of the `rgbpp` package to transfer RGB++ compatible xUDT assets
- Add RGB++ compatible xUDT assets leaping and transferring examples
- Add RGB++ compatible xUDT assets integration tests
- Add `assets/type` API to the service package
5 changes: 0 additions & 5 deletions .changeset/odd-cheetahs-shake.md

This file was deleted.

25 changes: 18 additions & 7 deletions .github/workflows/integration-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:

strategy:
matrix:
env_set: [ xudt, spore ]
env_set: [ xudt, spore, compatible-xudt ]

steps:
- name: Checkout rgbpp-sdk
Expand Down Expand Up @@ -49,9 +49,9 @@ jobs:
if: ${{ matrix.env_set == 'xudt' }}
run: pnpm run integration:xudt
env:
VITE_SERVICE_URL: https://api.signet.rgbpp.io
VITE_SERVICE_TOKEN: ${{ secrets.SIGNET_SERVICE_TOKEN }}
VITE_SERVICE_ORIGIN: https://api.signet.rgbpp.io
VITE_SERVICE_URL: https://btc-assets-api.testnet.mibao.pro
VITE_SERVICE_TOKEN: ${{ secrets.TESTNET_SERVICE_TOKEN }}
VITE_SERVICE_ORIGIN: https://btc-assets-api.testnet.mibao.pro
INTEGRATION_CKB_PRIVATE_KEY: ${{ secrets.INTEGRATION_CKB_PRIVATE_KEY }}
INTEGRATION_BTC_PRIVATE_KEY: ${{ secrets.INTEGRATION_BTC_PRIVATE_KEY }}

Expand All @@ -60,8 +60,19 @@ jobs:
if: ${{ matrix.env_set == 'spore' }}
run: pnpm run integration:spore
env:
VITE_SERVICE_URL: https://api.signet.rgbpp.io
VITE_SERVICE_TOKEN: ${{ secrets.SIGNET_SERVICE_TOKEN }}
VITE_SERVICE_ORIGIN: https://api.signet.rgbpp.io
VITE_SERVICE_URL: https://btc-assets-api.testnet.mibao.pro
VITE_SERVICE_TOKEN: ${{ secrets.TESTNET_SERVICE_TOKEN }}
VITE_SERVICE_ORIGIN: https://btc-assets-api.testnet.mibao.pro
INTEGRATION_CKB_PRIVATE_KEY: ${{ secrets.INTEGRATION_CKB_SPORE_PRIVATE_KEY }}
INTEGRATION_BTC_PRIVATE_KEY: ${{ secrets.INTEGRATION_BTC_SPORE_PRIVATE_KEY }}

- name: Run integration:compatible-xudt script
working-directory: ./tests/rgbpp
if: ${{ matrix.env_set == 'compatible-xudt' }}
run: pnpm run integration:compatible-xudt
env:
VITE_SERVICE_URL: https://btc-assets-api.testnet.mibao.pro
VITE_SERVICE_TOKEN: ${{ secrets.TESTNET_SERVICE_TOKEN }}
VITE_SERVICE_ORIGIN: https://btc-assets-api.testnet.mibao.pro
INTEGRATION_CKB_PRIVATE_KEY: ${{ secrets.INTEGRATION_CKB_compatible_xudt_PRIVATE_KEY }}
INTEGRATION_BTC_PRIVATE_KEY: ${{ secrets.INTEGRATION_BTC_compatible_xudt_PRIVATE_KEY }}
56 changes: 56 additions & 0 deletions examples/rgbpp/xudt/compatible-xudt/1-ckb-leap-btc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { serializeScript } from '@nervosnetwork/ckb-sdk-utils';
import { genCkbJumpBtcVirtualTx } from 'rgbpp';
import { getSecp256k1CellDep, buildRgbppLockArgs } from 'rgbpp/ckb';
import { CKB_PRIVATE_KEY, isMainnet, collector, ckbAddress, BTC_TESTNET_TYPE } from '../../env';

interface LeapToBtcParams {
outIndex: number;
btcTxId: string;
transferAmount: bigint;
compatibleXudtTypeScript: CKBComponents.Script;
}

const leapRusdFromCkbToBtc = async ({
outIndex,
btcTxId,
transferAmount,
compatibleXudtTypeScript,
}: LeapToBtcParams) => {
const toRgbppLockArgs = buildRgbppLockArgs(outIndex, btcTxId);

const ckbRawTx = await genCkbJumpBtcVirtualTx({
collector,
fromCkbAddress: ckbAddress,
toRgbppLockArgs,
xudtTypeBytes: serializeScript(compatibleXudtTypeScript),
transferAmount,
btcTestnetType: BTC_TESTNET_TYPE,
});

const emptyWitness = { lock: '', inputType: '', outputType: '' };
const unsignedTx: CKBComponents.RawTransactionToSign = {
...ckbRawTx,
cellDeps: [...ckbRawTx.cellDeps, getSecp256k1CellDep(isMainnet)],
witnesses: [emptyWitness, ...ckbRawTx.witnesses.slice(1)],
};

const signedTx = collector.getCkb().signTransaction(CKB_PRIVATE_KEY)(unsignedTx);

const txHash = await collector.getCkb().rpc.sendTransaction(signedTx, 'passthrough');
console.info(`Rgbpp compatible xUDT asset has been leaped from CKB to BTC and CKB tx hash is ${txHash}`);
};

// Please use your real BTC UTXO information on the BTC Testnet
// BTC Testnet3: https://mempool.space/testnet
// BTC Signet: https://mempool.space/signet
leapRusdFromCkbToBtc({
outIndex: 4,
btcTxId: '44de1b4e3ddaa95cc85cc8b1c60f3e439d343002f0c60980fb4c70841ee0c75e',
// Please use your own RGB++ compatible xUDT asset's type script
compatibleXudtTypeScript: {
codeHash: '0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a',
hashType: 'type',
args: '0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b',
},
transferAmount: BigInt(1000_0000),
});
85 changes: 85 additions & 0 deletions examples/rgbpp/xudt/compatible-xudt/2-btc-transfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { buildRgbppLockArgs } from 'rgbpp/ckb';
import { buildRgbppTransferTx } from 'rgbpp';
import { isMainnet, collector, btcService, btcAccount, btcDataSource, BTC_TESTNET_TYPE } from '../../env';
import { saveCkbVirtualTxResult } from '../../shared/utils';
import { signAndSendPsbt } from '../../shared/btc-account';
import { bitcoin } from 'rgbpp/btc';

interface RgbppTransferParams {
rgbppLockArgsList: string[];
toBtcAddress: string;
transferAmount: bigint;
compatibleXudtTypeScript: CKBComponents.Script;
}

const transferRusdOnBtc = async ({
rgbppLockArgsList,
toBtcAddress,
compatibleXudtTypeScript,
transferAmount,
}: RgbppTransferParams) => {
const { ckbVirtualTxResult, btcPsbtHex } = await buildRgbppTransferTx({
ckb: {
collector,
xudtTypeArgs: compatibleXudtTypeScript.args,
rgbppLockArgsList,
transferAmount,
compatibleXudtTypeScript,
},
btc: {
fromAddress: btcAccount.from,
toAddress: toBtcAddress,
fromPubkey: btcAccount.fromPubkey,
dataSource: btcDataSource,
testnetType: BTC_TESTNET_TYPE,
},
isMainnet,
});

// Save ckbVirtualTxResult
saveCkbVirtualTxResult(ckbVirtualTxResult, '2-btc-transfer');

// Send BTC tx
const psbt = bitcoin.Psbt.fromHex(btcPsbtHex);
const { txId: btcTxId } = await signAndSendPsbt(psbt, btcAccount, btcService);
console.log(`BTC ${BTC_TESTNET_TYPE} TxId: ${btcTxId}`);

await btcService.sendRgbppCkbTransaction({ btc_txid: btcTxId, ckb_virtual_result: ckbVirtualTxResult });

try {
const interval = setInterval(async () => {
const { state, failedReason } = await btcService.getRgbppTransactionState(btcTxId);
console.log('state', state);
if (state === 'completed' || state === 'failed') {
clearInterval(interval);
if (state === 'completed') {
const { txhash: txHash } = await btcService.getRgbppTransactionHash(btcTxId);
console.info(
`Rgbpp compatible xUDT asset has been transferred on BTC and the related CKB tx hash is ${txHash}`,
);
} else {
console.warn(`Rgbpp CKB transaction failed and the reason is ${failedReason} `);
}
}
}, 30 * 1000);
} catch (error) {
console.error(error);
}
};

// Please use your real BTC UTXO information on the BTC Testnet
// BTC Testnet3: https://mempool.space/testnet
// BTC Signet: https://mempool.space/signet

// rgbppLockArgs: outIndexU32 + btcTxId
transferRusdOnBtc({
rgbppLockArgsList: [buildRgbppLockArgs(4, '44de1b4e3ddaa95cc85cc8b1c60f3e439d343002f0c60980fb4c70841ee0c75e')],
toBtcAddress: 'tb1qvt7p9g6mw70sealdewtfp0sekquxuru6j3gwmt',
// Please use your own RGB++ compatible xudt asset's type script
compatibleXudtTypeScript: {
codeHash: '0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a',
hashType: 'type',
args: '0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b',
},
transferAmount: BigInt(100_0000),
});
89 changes: 89 additions & 0 deletions examples/rgbpp/xudt/compatible-xudt/3-btc-leap-ckb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { buildRgbppLockArgs } from 'rgbpp/ckb';
import { serializeScript } from '@nervosnetwork/ckb-sdk-utils';
import { genBtcJumpCkbVirtualTx, sendRgbppUtxos } from 'rgbpp';
import { isMainnet, collector, btcService, btcDataSource, btcAccount, BTC_TESTNET_TYPE } from '../../env';
import { saveCkbVirtualTxResult } from '../../shared/utils';
import { signAndSendPsbt } from '../../shared/btc-account';

interface LeapToCkbParams {
rgbppLockArgsList: string[];
toCkbAddress: string;
transferAmount: bigint;
compatibleXudtTypeScript: CKBComponents.Script;
}

const leapRusdFromBtcToCKB = async ({
rgbppLockArgsList,
toCkbAddress,
compatibleXudtTypeScript,
transferAmount,
}: LeapToCkbParams) => {
const ckbVirtualTxResult = await genBtcJumpCkbVirtualTx({
collector,
rgbppLockArgsList,
xudtTypeBytes: serializeScript(compatibleXudtTypeScript),
transferAmount,
toCkbAddress,
isMainnet,
btcTestnetType: BTC_TESTNET_TYPE,
// btcConfirmationBlocks: 20, // default value is 6
});

// Save ckbVirtualTxResult
saveCkbVirtualTxResult(ckbVirtualTxResult, '3-btc-leap-ckb');

const { commitment, ckbRawTx } = ckbVirtualTxResult;

// Send BTC tx
const psbt = await sendRgbppUtxos({
ckbVirtualTx: ckbRawTx,
commitment,
tos: [btcAccount.from],
ckbCollector: collector,
from: btcAccount.from,
fromPubkey: btcAccount.fromPubkey,
source: btcDataSource,
});

const { txId: btcTxId } = await signAndSendPsbt(psbt, btcAccount, btcService);
console.log(`BTC ${BTC_TESTNET_TYPE} TxId: ${btcTxId}`);

await btcService.sendRgbppCkbTransaction({ btc_txid: btcTxId, ckb_virtual_result: ckbVirtualTxResult });

try {
const interval = setInterval(async () => {
const { state, failedReason } = await btcService.getRgbppTransactionState(btcTxId);
console.log('state', state);
if (state === 'completed' || state === 'failed') {
clearInterval(interval);
if (state === 'completed') {
const { txhash: txHash } = await btcService.getRgbppTransactionHash(btcTxId);
console.info(
`Rgbpp compatible xUDT asset has been leaped from BTC to CKB and the related CKB tx hash is ${txHash}`,
);
} else {
console.warn(`Rgbpp CKB transaction failed and the reason is ${failedReason} `);
}
}
}, 30 * 1000);
} catch (error) {
console.error(error);
}
};

// Please use your real BTC UTXO information on the BTC Testnet
// BTC Testnet3: https://mempool.space/testnet
// BTC Signet: https://mempool.space/signet

// rgbppLockArgs: outIndexU32 + btcTxId
leapRusdFromBtcToCKB({
rgbppLockArgsList: [buildRgbppLockArgs(1, '58ebbdec0dfd464280658e36fadc11c41945de2c4b5b59463dad6e045a7e5faf')],
toCkbAddress: 'ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq0e4xk4rmg5jdkn8aams492a7jlg73ue0gc0ddfj',
// Please use your own RGB++ compatible xudt asset's type script
compatibleXudtTypeScript: {
codeHash: '0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a',
hashType: 'type',
args: '0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b',
},
transferAmount: BigInt(100_0000),
});
42 changes: 42 additions & 0 deletions examples/rgbpp/xudt/compatible-xudt/4-unlock-btc-time-cell.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { buildBtcTimeCellsSpentTx, signBtcTimeCellSpentTx } from 'rgbpp';
import { sendCkbTx, getBtcTimeLockScript } from 'rgbpp/ckb';
import { BTC_TESTNET_TYPE, CKB_PRIVATE_KEY, btcService, ckbAddress, collector, isMainnet } from '../../env';

// Warning: Wait at least 6 BTC confirmation blocks to spend the BTC time cells after 3-btc-leap-ckb.ts
const unlockRusdBtcTimeCell = async ({ btcTimeCellArgs }: { btcTimeCellArgs: string }) => {
const btcTimeCells = await collector.getCells({
lock: {
...getBtcTimeLockScript(isMainnet, BTC_TESTNET_TYPE),
args: btcTimeCellArgs,
},
isDataMustBeEmpty: false,
});

if (!btcTimeCells || btcTimeCells.length === 0) {
throw new Error('No btc time cell found');
}

const ckbRawTx: CKBComponents.RawTransaction = await buildBtcTimeCellsSpentTx({
btcTimeCells,
btcAssetsApi: btcService,
isMainnet,
btcTestnetType: BTC_TESTNET_TYPE,
});

const signedTx = await signBtcTimeCellSpentTx({
secp256k1PrivateKey: CKB_PRIVATE_KEY,
collector,
masterCkbAddress: ckbAddress,
ckbRawTx,
isMainnet,
});

const txHash = await sendCkbTx({ collector, signedTx });
console.info(`BTC time cell has been spent and CKB tx hash is ${txHash}`);
};

// The btcTimeCellArgs is from the outputs[0].lock.args(BTC Time lock args) of the 3-btc-leap-ckb.ts CKB transaction
unlockRusdBtcTimeCell({
btcTimeCellArgs:
'0x7d00000010000000590000005d000000490000001000000030000000310000009bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce80114000000f9a9ad51ed14936d33f7bb854aaefa5f47a3ccbd0600000038036f35121682517b5f79732fc6a182e0050cfe1ad4cce0a1314c229a1ba364',
});
31 changes: 31 additions & 0 deletions examples/rgbpp/xudt/compatible-xudt/assets-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { serializeScript } from '@nervosnetwork/ckb-sdk-utils';
import { btcService } from '../../env';

(async () => {
const assets = await btcService.getRgbppAssetsByBtcAddress('tb1qvt7p9g6mw70sealdewtfp0sekquxuru6j3gwmt', {
type_script: serializeScript({
codeHash: '0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a',
hashType: 'type',
args: '0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b',
}),
});
console.log('RUSD Assets: ', JSON.stringify(assets));

const activities = await btcService.getRgbppActivityByBtcAddress('tb1qvt7p9g6mw70sealdewtfp0sekquxuru6j3gwmt', {
type_script: serializeScript({
codeHash: '0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a',
hashType: 'type',
args: '0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b',
}),
});
console.log('RUSD Activities: ', JSON.stringify(activities));

const info = await btcService.getRgbppAssetInfoByTypeScript(
serializeScript({
codeHash: '0x25c29dc317811a6f6f3985a7a9ebc4838bd388d19d0feeecf0bcd60f6c0975bb',
hashType: 'type',
args: '0x661cfbe2124b3e79e50e505c406be5b2dcf9da15d8654b749ec536fa4c2eaaae',
}),
);
console.log('Standard xUDT info: ', JSON.stringify(info));
})();
Loading
Loading