Skip to content
This repository was archived by the owner on May 27, 2025. It is now read-only.

Commit 5914a7c

Browse files
authored
Merge pull request #303 from fghdotio/feat/offline-utxo
feat: add offline mode support for compatible xUDT type scripts
2 parents ab3d914 + 72150df commit 5914a7c

File tree

15 files changed

+512
-22
lines changed

15 files changed

+512
-22
lines changed

.changeset/lazy-shrimps-roll.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'rgbpp': minor
3+
'@rgbpp-sdk/ckb': minor
4+
---
5+
6+
Add offline mode support for compatible xUDT type scripts:
7+
- Introduce an optional `offline` boolean parameter to the following methods:
8+
- `isUDTTypeSupported`
9+
- `isCompatibleUDTTypesSupported`
10+
- `CompatibleXUDTRegistry.getCompatibleTokens`
11+
- Add examples demonstrating compatible xUDT asset management in offline mode
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { addressToScript, serializeScript } from '@nervosnetwork/ckb-sdk-utils';
2+
import { genCkbJumpBtcVirtualTx } from 'rgbpp';
3+
import { getSecp256k1CellDep, buildRgbppLockArgs, signCkbTransaction } from 'rgbpp/ckb';
4+
import {
5+
CKB_PRIVATE_KEY,
6+
isMainnet,
7+
collector,
8+
ckbAddress,
9+
BTC_TESTNET_TYPE,
10+
initOfflineCkbCollector,
11+
vendorCellDeps,
12+
} from '../../../env';
13+
14+
interface LeapToBtcParams {
15+
outIndex: number;
16+
btcTxId: string;
17+
transferAmount: bigint;
18+
compatibleXudtTypeScript: CKBComponents.Script;
19+
}
20+
21+
const leapRusdFromCkbToBtc = async ({
22+
outIndex,
23+
btcTxId,
24+
transferAmount,
25+
compatibleXudtTypeScript,
26+
}: LeapToBtcParams) => {
27+
const toRgbppLockArgs = buildRgbppLockArgs(outIndex, btcTxId);
28+
29+
const { collector: offlineCollector } = await initOfflineCkbCollector([
30+
{ lock: addressToScript(ckbAddress), type: compatibleXudtTypeScript },
31+
{ lock: addressToScript(ckbAddress) },
32+
]);
33+
34+
const ckbRawTx = await genCkbJumpBtcVirtualTx({
35+
collector: offlineCollector,
36+
fromCkbAddress: ckbAddress,
37+
toRgbppLockArgs,
38+
xudtTypeBytes: serializeScript(compatibleXudtTypeScript),
39+
transferAmount,
40+
btcTestnetType: BTC_TESTNET_TYPE,
41+
vendorCellDeps,
42+
});
43+
44+
const emptyWitness = { lock: '', inputType: '', outputType: '' };
45+
const unsignedTx: CKBComponents.RawTransactionToSign = {
46+
...ckbRawTx,
47+
cellDeps: [...ckbRawTx.cellDeps, getSecp256k1CellDep(isMainnet)],
48+
witnesses: [emptyWitness, ...ckbRawTx.witnesses.slice(1)],
49+
};
50+
51+
const signedTx = signCkbTransaction(CKB_PRIVATE_KEY, unsignedTx);
52+
const txHash = await collector.getCkb().rpc.sendTransaction(signedTx, 'passthrough');
53+
console.info(`Rgbpp compatible xUDT asset has been leaped from CKB to BTC and CKB tx hash is ${txHash}`);
54+
};
55+
56+
// Please use your real BTC UTXO information on the BTC Testnet
57+
// BTC Testnet3: https://mempool.space/testnet
58+
// BTC Signet: https://mempool.space/signet
59+
leapRusdFromCkbToBtc({
60+
outIndex: 2,
61+
btcTxId: '4239d2f9fe566513b0604e4dfe10f3b85b6bebe25096cf426559a89c87c68d1a',
62+
compatibleXudtTypeScript: {
63+
codeHash: '0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a',
64+
hashType: 'type',
65+
args: '0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b',
66+
},
67+
transferAmount: BigInt(200_0000),
68+
});
69+
70+
/*
71+
npx tsx examples/rgbpp/xudt/offline/compatible-xudt/1-ckb-leap-btc.ts
72+
*/
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { addressToScript, serializeScript } from '@nervosnetwork/ckb-sdk-utils';
2+
import { BtcAssetsApiError, genBtcTransferCkbVirtualTx, sendRgbppUtxos } from 'rgbpp';
3+
4+
import {
5+
isMainnet,
6+
collector,
7+
btcService,
8+
CKB_PRIVATE_KEY,
9+
ckbAddress,
10+
btcAccount,
11+
BTC_TESTNET_TYPE,
12+
initOfflineCkbCollector,
13+
initOfflineBtcDataSource,
14+
vendorCellDeps,
15+
} from '../../../env';
16+
import {
17+
appendCkbTxWitnesses,
18+
buildRgbppLockArgs,
19+
sendCkbTx,
20+
updateCkbTxWithRealBtcTxId,
21+
genRgbppLockScript,
22+
appendIssuerCellToBtcBatchTransferToSign,
23+
addressToScriptHash,
24+
signCkbTransaction,
25+
} from 'rgbpp/ckb';
26+
import { saveCkbVirtualTxResult } from '../../../shared/utils';
27+
import { signAndSendPsbt } from '../../../shared/btc-account';
28+
29+
interface RgbppTransferParams {
30+
rgbppLockArgsList: string[];
31+
toBtcAddress: string;
32+
transferAmount: bigint;
33+
compatibleXudtTypeScript: CKBComponents.Script;
34+
}
35+
36+
const transferRusdOnBtc = async ({
37+
rgbppLockArgsList,
38+
toBtcAddress,
39+
compatibleXudtTypeScript,
40+
transferAmount,
41+
}: RgbppTransferParams) => {
42+
const rgbppLocks = rgbppLockArgsList.map((args) => genRgbppLockScript(args, isMainnet, BTC_TESTNET_TYPE));
43+
const { collector: offlineCollector } = await initOfflineCkbCollector([
44+
...rgbppLocks.map((lock) => ({ lock, type: compatibleXudtTypeScript })),
45+
{ lock: addressToScript(ckbAddress) },
46+
]);
47+
48+
const ckbVirtualTxResult = await genBtcTransferCkbVirtualTx({
49+
collector: offlineCollector,
50+
rgbppLockArgsList,
51+
xudtTypeBytes: serializeScript(compatibleXudtTypeScript),
52+
transferAmount,
53+
isMainnet,
54+
btcTestnetType: BTC_TESTNET_TYPE,
55+
vendorCellDeps,
56+
});
57+
58+
// Save ckbVirtualTxResult
59+
saveCkbVirtualTxResult(ckbVirtualTxResult, '2-compatible-xudt-btc-transfer-offline');
60+
61+
const { commitment, ckbRawTx, sumInputsCapacity } = ckbVirtualTxResult;
62+
63+
const btcOfflineDataSource = await initOfflineBtcDataSource(rgbppLockArgsList, btcAccount.from);
64+
65+
// Send BTC tx
66+
const psbt = await sendRgbppUtxos({
67+
ckbVirtualTx: ckbRawTx,
68+
commitment,
69+
tos: [toBtcAddress],
70+
needPaymaster: false,
71+
ckbCollector: offlineCollector,
72+
from: btcAccount.from,
73+
fromPubkey: btcAccount.fromPubkey,
74+
source: btcOfflineDataSource,
75+
feeRate: 128,
76+
});
77+
78+
const { txId: btcTxId, rawTxHex: btcTxBytes } = await signAndSendPsbt(psbt, btcAccount, btcService);
79+
console.log(`BTC ${BTC_TESTNET_TYPE} TxId: ${btcTxId}`);
80+
console.log('BTC tx bytes: ', btcTxBytes);
81+
82+
const interval = setInterval(async () => {
83+
try {
84+
console.log('Waiting for BTC tx and proof to be ready');
85+
const rgbppApiSpvProof = await btcService.getRgbppSpvProof(btcTxId, 0);
86+
clearInterval(interval);
87+
// Update CKB transaction with the real BTC txId
88+
const newCkbRawTx = updateCkbTxWithRealBtcTxId({ ckbRawTx, btcTxId, isMainnet });
89+
const ckbTx = await appendCkbTxWitnesses({
90+
ckbRawTx: newCkbRawTx,
91+
btcTxBytes,
92+
rgbppApiSpvProof,
93+
});
94+
95+
const { ckbRawTx: unsignedTx, inputCells } = await appendIssuerCellToBtcBatchTransferToSign({
96+
issuerAddress: ckbAddress,
97+
ckbRawTx: ckbTx,
98+
collector: offlineCollector,
99+
sumInputsCapacity,
100+
isMainnet,
101+
});
102+
103+
const keyMap = new Map<string, string>();
104+
keyMap.set(addressToScriptHash(ckbAddress), CKB_PRIVATE_KEY);
105+
const signedTx = signCkbTransaction(keyMap, unsignedTx, inputCells, true);
106+
107+
const txHash = await sendCkbTx({ collector, signedTx });
108+
console.info(`Rgbpp compatible xUDT asset has been transferred on BTC and the related CKB tx hash is ${txHash}`);
109+
} catch (error) {
110+
if (!(error instanceof BtcAssetsApiError)) {
111+
console.error(error);
112+
}
113+
}
114+
}, 20 * 1000);
115+
};
116+
117+
// Please use your real BTC UTXO information on the BTC Testnet
118+
// BTC Testnet3: https://mempool.space/testnet
119+
// BTC Signet: https://mempool.space/signet
120+
121+
// rgbppLockArgs: outIndexU32 + btcTxId
122+
transferRusdOnBtc({
123+
rgbppLockArgsList: [buildRgbppLockArgs(2, '4239d2f9fe566513b0604e4dfe10f3b85b6bebe25096cf426559a89c87c68d1a')],
124+
toBtcAddress: 'tb1qe68sv5pr5vdj2daw2v96pwvw5m9ca4ew35ewp5',
125+
// Please use your own RGB++ compatible xudt asset's type script
126+
compatibleXudtTypeScript: {
127+
codeHash: '0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a',
128+
hashType: 'type',
129+
args: '0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b',
130+
},
131+
transferAmount: BigInt(100_0000),
132+
});
133+
134+
/*
135+
npx tsx examples/rgbpp/xudt/offline/compatible-xudt/2-btc-transfer.ts
136+
*/
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import {
2+
buildRgbppLockArgs,
3+
genRgbppLockScript,
4+
appendIssuerCellToBtcBatchTransferToSign,
5+
signCkbTransaction,
6+
addressToScriptHash,
7+
appendCkbTxWitnesses,
8+
updateCkbTxWithRealBtcTxId,
9+
sendCkbTx,
10+
} from 'rgbpp/ckb';
11+
import { addressToScript, serializeScript } from '@nervosnetwork/ckb-sdk-utils';
12+
import { genBtcJumpCkbVirtualTx, sendRgbppUtxos, BtcAssetsApiError } from 'rgbpp';
13+
import {
14+
isMainnet,
15+
collector,
16+
btcService,
17+
btcAccount,
18+
BTC_TESTNET_TYPE,
19+
CKB_PRIVATE_KEY,
20+
ckbAddress,
21+
initOfflineCkbCollector,
22+
vendorCellDeps,
23+
initOfflineBtcDataSource,
24+
} from '../../../env';
25+
import { saveCkbVirtualTxResult } from '../../../shared/utils';
26+
import { signAndSendPsbt } from '../../../shared/btc-account';
27+
28+
interface LeapToCkbParams {
29+
rgbppLockArgsList: string[];
30+
toCkbAddress: string;
31+
transferAmount: bigint;
32+
compatibleXudtTypeScript: CKBComponents.Script;
33+
}
34+
35+
const leapRusdFromBtcToCKB = async ({
36+
rgbppLockArgsList,
37+
toCkbAddress,
38+
compatibleXudtTypeScript,
39+
transferAmount,
40+
}: LeapToCkbParams) => {
41+
const rgbppLocks = rgbppLockArgsList.map((args) => genRgbppLockScript(args, isMainnet, BTC_TESTNET_TYPE));
42+
const { collector: offlineCollector } = await initOfflineCkbCollector([
43+
...rgbppLocks.map((lock) => ({ lock, type: compatibleXudtTypeScript })),
44+
{ lock: addressToScript(ckbAddress) },
45+
]);
46+
47+
const ckbVirtualTxResult = await genBtcJumpCkbVirtualTx({
48+
collector: offlineCollector,
49+
rgbppLockArgsList,
50+
xudtTypeBytes: serializeScript(compatibleXudtTypeScript),
51+
transferAmount,
52+
toCkbAddress,
53+
isMainnet,
54+
btcTestnetType: BTC_TESTNET_TYPE,
55+
vendorCellDeps,
56+
});
57+
58+
// Save ckbVirtualTxResult
59+
saveCkbVirtualTxResult(ckbVirtualTxResult, '3-compatible-xudt-btc-leap-ckb-offline');
60+
61+
const { commitment, ckbRawTx, sumInputsCapacity } = ckbVirtualTxResult;
62+
63+
const btcOfflineDataSource = await initOfflineBtcDataSource(rgbppLockArgsList, btcAccount.from);
64+
65+
// Send BTC tx
66+
const psbt = await sendRgbppUtxos({
67+
ckbVirtualTx: ckbRawTx,
68+
commitment,
69+
tos: [btcAccount.from],
70+
ckbCollector: offlineCollector,
71+
from: btcAccount.from,
72+
fromPubkey: btcAccount.fromPubkey,
73+
source: btcOfflineDataSource,
74+
needPaymaster: false,
75+
feeRate: 128,
76+
});
77+
78+
const { txId: btcTxId, rawTxHex: btcTxBytes } = await signAndSendPsbt(psbt, btcAccount, btcService);
79+
console.log(`BTC ${BTC_TESTNET_TYPE} TxId: ${btcTxId}`);
80+
console.log('BTC tx bytes: ', btcTxBytes);
81+
82+
const interval = setInterval(async () => {
83+
try {
84+
console.log('Waiting for BTC tx and proof to be ready');
85+
const rgbppApiSpvProof = await btcService.getRgbppSpvProof(btcTxId, 0);
86+
clearInterval(interval);
87+
// Update CKB transaction with the real BTC txId
88+
const newCkbRawTx = updateCkbTxWithRealBtcTxId({ ckbRawTx, btcTxId, isMainnet });
89+
const ckbTx = await appendCkbTxWitnesses({
90+
ckbRawTx: newCkbRawTx,
91+
btcTxBytes,
92+
rgbppApiSpvProof,
93+
});
94+
95+
const { ckbRawTx: unsignedTx, inputCells } = await appendIssuerCellToBtcBatchTransferToSign({
96+
issuerAddress: ckbAddress,
97+
ckbRawTx: ckbTx,
98+
collector: offlineCollector,
99+
sumInputsCapacity,
100+
isMainnet,
101+
});
102+
103+
const keyMap = new Map<string, string>();
104+
keyMap.set(addressToScriptHash(ckbAddress), CKB_PRIVATE_KEY);
105+
const signedTx = signCkbTransaction(keyMap, unsignedTx, inputCells, true);
106+
107+
const txHash = await sendCkbTx({ collector, signedTx });
108+
console.info(
109+
`Rgbpp compatible xUDT asset has been leaped from BTC to CKB and the related CKB tx hash is ${txHash}`,
110+
);
111+
} catch (error) {
112+
if (!(error instanceof BtcAssetsApiError)) {
113+
console.error(error);
114+
}
115+
}
116+
}, 20 * 1000);
117+
};
118+
119+
// Please use your real BTC UTXO information on the BTC Testnet
120+
// BTC Testnet3: https://mempool.space/testnet
121+
// BTC Signet: https://mempool.space/signet
122+
123+
// rgbppLockArgs: outIndexU32 + btcTxId
124+
leapRusdFromBtcToCKB({
125+
rgbppLockArgsList: [buildRgbppLockArgs(2, 'daec93a97c8b7f6fdd33696f814f0292be966dc4ea4853400d3cada816c70f5d')],
126+
toCkbAddress: 'ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqfpu7pwavwf3yang8khrsklumayj6nyxhqpmh7fq',
127+
// Please use your own RGB++ compatible xudt asset's type script
128+
compatibleXudtTypeScript: {
129+
codeHash: '0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a',
130+
hashType: 'type',
131+
args: '0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b',
132+
},
133+
transferAmount: BigInt(10_0000),
134+
});
135+
136+
/*
137+
npx tsx examples/rgbpp/xudt/offline/compatible-xudt/3-btc-leap-ckb.ts
138+
*/

0 commit comments

Comments
 (0)