Skip to content

Commit

Permalink
Merge pull request #291 from utxostack/xudt-compatible
Browse files Browse the repository at this point in the history
Support RGB++ compatible xUDT assets
  • Loading branch information
duanyytop authored Jan 6, 2025
2 parents c3186c4 + 4d5afce commit 3240864
Show file tree
Hide file tree
Showing 40 changed files with 820 additions and 80 deletions.
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

1 comment on commit 3240864

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New snapshot version of the rgbpp-sdk packages have been released:

Name Version
@rgbpp-sdk/btc 0.0.0-snap-20250106033736
@rgbpp-sdk/ckb 0.0.0-snap-20250106033736
rgbpp 0.0.0-snap-20250106033736
@rgbpp-sdk/service 0.0.0-snap-20250106033736

Please sign in to comment.