Skip to content

Commit 2b075dc

Browse files
authored
Merge pull request #311 from utxostack/release/0.7.0
Merge release/0.7.0 to main branch
2 parents 2539bd8 + f4c9ff3 commit 2b075dc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+4479
-1187
lines changed

.github/workflows/integration-test.yaml

+12-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020

2121
strategy:
2222
matrix:
23-
env_set: [ xudt, spore ]
23+
env_set: [ xudt, spore, compatible-xudt ]
2424

2525
steps:
2626
- name: Checkout rgbpp-sdk
@@ -65,3 +65,14 @@ jobs:
6565
VITE_SERVICE_ORIGIN: https://btc-assets-api.testnet.mibao.pro
6666
INTEGRATION_CKB_PRIVATE_KEY: ${{ secrets.INTEGRATION_CKB_SPORE_PRIVATE_KEY }}
6767
INTEGRATION_BTC_PRIVATE_KEY: ${{ secrets.INTEGRATION_BTC_SPORE_PRIVATE_KEY }}
68+
69+
- name: Run integration:compatible-xudt script
70+
working-directory: ./tests/rgbpp
71+
if: ${{ matrix.env_set == 'compatible-xudt' }}
72+
run: pnpm run integration:compatible-xudt
73+
env:
74+
VITE_SERVICE_URL: https://btc-assets-api.testnet.mibao.pro
75+
VITE_SERVICE_TOKEN: ${{ secrets.TESTNET_SERVICE_TOKEN }}
76+
VITE_SERVICE_ORIGIN: https://btc-assets-api.testnet.mibao.pro
77+
INTEGRATION_CKB_PRIVATE_KEY: ${{ secrets.INTEGRATION_CKB_compatible_xudt_PRIVATE_KEY }}
78+
INTEGRATION_BTC_PRIVATE_KEY: ${{ secrets.INTEGRATION_BTC_compatible_xudt_PRIVATE_KEY }}

examples/rgbpp/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
- xUDT directory: The examples for RGB++ UDT issuance, transfer, transferAll and leap
44
- Spore directory: The examples for RGB++ Spore creation, transfer and leap
5+
- compatible-xudt directory: The examples for RGB++ compatible UDT issuance, transfer, transferAll and leap
6+
- If you want to get the latest compatible xUDT list, `CompatibleXUDTRegistry.refreshCache` should be called first
57

68
> [!TIP]
79
> All the parameters of the examples should be repalced with your own, including BTC private key, CKB private key, BTC Service origin, BTC Service token, BTC UTXO, xUDT type args, Spore type args, etc.

examples/rgbpp/env.ts

+97-3
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,16 @@ import {
77
systemScripts,
88
} from '@nervosnetwork/ckb-sdk-utils';
99
import { NetworkType, AddressType, DataSource } from 'rgbpp/btc';
10-
import { BtcAssetsApi } from 'rgbpp/service';
11-
import { BTCTestnetType, Collector } from 'rgbpp/ckb';
10+
import { BtcAssetsApi, OfflineBtcAssetsDataSource, OfflineBtcUtxo, BtcApiUtxo, SpvProofEntry } from 'rgbpp/service';
11+
import {
12+
BTCTestnetType,
13+
Collector,
14+
Hex,
15+
OfflineCollector,
16+
remove0x,
17+
unpackRgbppLockArgs,
18+
fetchCellDepsJson,
19+
} from 'rgbpp/ckb';
1220
import { createBtcAccount } from './shared/btc-account';
1321

1422
dotenv.config({ path: __dirname + '/.env' });
@@ -48,8 +56,94 @@ export const BTC_SERVICE_ORIGIN = process.env.VITE_BTC_SERVICE_ORIGIN!;
4856
// - P2WPKH: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wpkh
4957
// - P2TR: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki
5058
const addressType = process.env.BTC_ADDRESS_TYPE === 'P2TR' ? AddressType.P2TR : AddressType.P2WPKH;
51-
const networkType = isMainnet ? NetworkType.MAINNET : NetworkType.TESTNET;
59+
export const networkType = isMainnet ? NetworkType.MAINNET : NetworkType.TESTNET;
5260
export const btcAccount = createBtcAccount(BTC_PRIVATE_KEY, addressType, networkType);
5361

5462
export const btcService = BtcAssetsApi.fromToken(BTC_SERVICE_URL, BTC_SERVICE_TOKEN, BTC_SERVICE_ORIGIN);
5563
export const btcDataSource = new DataSource(btcService, networkType);
64+
65+
// offline data source
66+
export const initOfflineCkbCollector = async (
67+
queries: {
68+
lock?: CKBComponents.Script;
69+
type?: CKBComponents.Script;
70+
isDataMustBeEmpty?: boolean;
71+
outputCapacityRange?: Hex[];
72+
withEmptyType?: boolean;
73+
}[],
74+
) => {
75+
const cells = (
76+
await Promise.all(
77+
queries.map(async (query) => {
78+
let cells = await collector.getCells(query);
79+
if (!cells || cells.length === 0) {
80+
throw new Error(`No cells found for query ${JSON.stringify(query)}`);
81+
}
82+
if (query.withEmptyType) {
83+
cells = cells.filter((cell) => !cell.output.type);
84+
}
85+
if (cells.length === 0) {
86+
throw new Error(`No cells found for query ${JSON.stringify(query)} with type ${query.type}`);
87+
}
88+
return cells;
89+
}),
90+
)
91+
).flat();
92+
93+
return {
94+
cells,
95+
collector: new OfflineCollector(cells),
96+
};
97+
};
98+
99+
export const initOfflineBtcDataSource = async (
100+
rgbppLockArgsList: string[],
101+
address: string,
102+
spvProofs: SpvProofEntry[] = [],
103+
) => {
104+
const btcTxIds = rgbppLockArgsList.map((rgbppLockArgs) => remove0x(unpackRgbppLockArgs(rgbppLockArgs).btcTxId));
105+
const btcTxs = await Promise.all(
106+
btcTxIds.map(async (btcTxId) => {
107+
const tx = await btcService.getBtcTransaction(btcTxId);
108+
if (!tx) {
109+
throw new Error(`BTC tx ${btcTxId} not found`);
110+
}
111+
return tx;
112+
}),
113+
);
114+
115+
const utxoMap = new Map<string, OfflineBtcUtxo>();
116+
const keyOf = (utxo: BtcApiUtxo) => `${utxo.txid}:${utxo.vout}`;
117+
(await btcService.getBtcUtxos(address)).forEach((utxo) => {
118+
utxoMap.set(keyOf(utxo), {
119+
...utxo,
120+
address,
121+
nonRgbpp: false,
122+
});
123+
});
124+
(
125+
await btcService.getBtcUtxos(address, {
126+
only_non_rgbpp_utxos: true,
127+
})
128+
).forEach((utxo) => {
129+
utxoMap.set(keyOf(utxo), {
130+
...utxo,
131+
address,
132+
nonRgbpp: true,
133+
});
134+
});
135+
136+
return new DataSource(
137+
new OfflineBtcAssetsDataSource({ txs: btcTxs, utxos: Array.from(utxoMap.values()), rgbppSpvProofs: spvProofs }),
138+
networkType,
139+
);
140+
};
141+
142+
let vendorCellDeps: Awaited<ReturnType<typeof fetchCellDepsJson>>;
143+
(async () => {
144+
vendorCellDeps = await fetchCellDepsJson();
145+
if (!vendorCellDeps) {
146+
throw new Error('Failed to fetch vendor cell deps');
147+
}
148+
})();
149+
export { vendorCellDeps };

examples/rgbpp/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"lint:fix": "tsc && eslint --fix --ext .js,.ts . && prettier --write '**/*.{js,ts}'"
1111
},
1212
"dependencies": {
13-
"@nervosnetwork/ckb-sdk-utils": "0.109.3",
13+
"@nervosnetwork/ckb-sdk-utils": "0.109.5",
1414
"rgbpp": "workspace:*"
1515
},
1616
"devDependencies": {

examples/rgbpp/shared/btc-account.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export async function signAndSendPsbt(
8787
signPsbt(psbt, account);
8888
psbt.finalizeAllInputs();
8989

90-
const tx = psbt.extractTransaction();
90+
const tx = psbt.extractTransaction(true);
9191
const txHex = tx.toHex();
9292

9393
const { txid } = await service.sendBtcTransaction(txHex);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { serializeScript } from '@nervosnetwork/ckb-sdk-utils';
2+
import { genCkbJumpBtcVirtualTx } from 'rgbpp';
3+
import { getSecp256k1CellDep, buildRgbppLockArgs, CompatibleXUDTRegistry } from 'rgbpp/ckb';
4+
import { CKB_PRIVATE_KEY, isMainnet, collector, ckbAddress, BTC_TESTNET_TYPE } from '../../env';
5+
6+
interface LeapToBtcParams {
7+
outIndex: number;
8+
btcTxId: string;
9+
transferAmount: bigint;
10+
compatibleXudtTypeScript: CKBComponents.Script;
11+
}
12+
13+
const leapRusdFromCkbToBtc = async ({
14+
outIndex,
15+
btcTxId,
16+
transferAmount,
17+
compatibleXudtTypeScript,
18+
}: LeapToBtcParams) => {
19+
const toRgbppLockArgs = buildRgbppLockArgs(outIndex, btcTxId);
20+
21+
// Refresh the cache by fetching the latest compatible xUDT list from the specified URL.
22+
// The default URL is:
23+
// https://raw.githubusercontent.com/utxostack/typeid-contract-cell-deps/main/compatible-udt.json
24+
// You can set your own trusted URL to fetch the compatible xUDT list.
25+
// await CompatibleXUDTRegistry.refreshCache("https://your-own-trusted-compatible-xudt-url");
26+
await CompatibleXUDTRegistry.refreshCache();
27+
28+
const ckbRawTx = await genCkbJumpBtcVirtualTx({
29+
collector,
30+
fromCkbAddress: ckbAddress,
31+
toRgbppLockArgs,
32+
xudtTypeBytes: serializeScript(compatibleXudtTypeScript),
33+
transferAmount,
34+
btcTestnetType: BTC_TESTNET_TYPE,
35+
});
36+
37+
const emptyWitness = { lock: '', inputType: '', outputType: '' };
38+
const unsignedTx: CKBComponents.RawTransactionToSign = {
39+
...ckbRawTx,
40+
cellDeps: [...ckbRawTx.cellDeps, getSecp256k1CellDep(isMainnet)],
41+
witnesses: [emptyWitness, ...ckbRawTx.witnesses.slice(1)],
42+
};
43+
44+
const signedTx = collector.getCkb().signTransaction(CKB_PRIVATE_KEY)(unsignedTx);
45+
46+
const txHash = await collector.getCkb().rpc.sendTransaction(signedTx, 'passthrough');
47+
console.info(`Rgbpp compatible xUDT asset has been leaped from CKB to BTC and CKB tx hash is ${txHash}`);
48+
};
49+
50+
// Please use your real BTC UTXO information on the BTC Testnet
51+
// BTC Testnet3: https://mempool.space/testnet
52+
// BTC Signet: https://mempool.space/signet
53+
leapRusdFromCkbToBtc({
54+
outIndex: 4,
55+
btcTxId: '44de1b4e3ddaa95cc85cc8b1c60f3e439d343002f0c60980fb4c70841ee0c75e',
56+
// Please use your own RGB++ compatible xUDT asset's type script
57+
compatibleXudtTypeScript: {
58+
codeHash: '0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a',
59+
hashType: 'type',
60+
args: '0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b',
61+
},
62+
transferAmount: BigInt(1000_0000),
63+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { buildRgbppLockArgs, CompatibleXUDTRegistry } from 'rgbpp/ckb';
2+
import { buildRgbppTransferTx } from 'rgbpp';
3+
import { isMainnet, collector, btcService, btcAccount, btcDataSource, BTC_TESTNET_TYPE } from '../../env';
4+
import { saveCkbVirtualTxResult } from '../../shared/utils';
5+
import { signAndSendPsbt } from '../../shared/btc-account';
6+
import { bitcoin } from 'rgbpp/btc';
7+
8+
interface RgbppTransferParams {
9+
rgbppLockArgsList: string[];
10+
toBtcAddress: string;
11+
transferAmount: bigint;
12+
compatibleXudtTypeScript: CKBComponents.Script;
13+
}
14+
15+
const transferRusdOnBtc = async ({
16+
rgbppLockArgsList,
17+
toBtcAddress,
18+
compatibleXudtTypeScript,
19+
transferAmount,
20+
}: RgbppTransferParams) => {
21+
// Refresh the cache by fetching the latest compatible xUDT list from the specified URL.
22+
// The default URL is:
23+
// https://raw.githubusercontent.com/utxostack/typeid-contract-cell-deps/main/compatible-udt.json
24+
// You can set your own trusted URL to fetch the compatible xUDT list.
25+
// await CompatibleXUDTRegistry.refreshCache("https://your-own-trusted-compatible-xudt-url");
26+
await CompatibleXUDTRegistry.refreshCache();
27+
28+
const { ckbVirtualTxResult, btcPsbtHex } = await buildRgbppTransferTx({
29+
ckb: {
30+
collector,
31+
xudtTypeArgs: compatibleXudtTypeScript.args,
32+
rgbppLockArgsList,
33+
transferAmount,
34+
compatibleXudtTypeScript,
35+
},
36+
btc: {
37+
fromAddress: btcAccount.from,
38+
toAddress: toBtcAddress,
39+
fromPubkey: btcAccount.fromPubkey,
40+
dataSource: btcDataSource,
41+
testnetType: BTC_TESTNET_TYPE,
42+
},
43+
isMainnet,
44+
});
45+
46+
// Save ckbVirtualTxResult
47+
saveCkbVirtualTxResult(ckbVirtualTxResult, '2-btc-transfer');
48+
49+
// Send BTC tx
50+
const psbt = bitcoin.Psbt.fromHex(btcPsbtHex);
51+
const { txId: btcTxId } = await signAndSendPsbt(psbt, btcAccount, btcService);
52+
console.log(`BTC ${BTC_TESTNET_TYPE} TxId: ${btcTxId}`);
53+
54+
await btcService.sendRgbppCkbTransaction({ btc_txid: btcTxId, ckb_virtual_result: ckbVirtualTxResult });
55+
56+
try {
57+
const interval = setInterval(async () => {
58+
const { state, failedReason } = await btcService.getRgbppTransactionState(btcTxId);
59+
console.log('state', state);
60+
if (state === 'completed' || state === 'failed') {
61+
clearInterval(interval);
62+
if (state === 'completed') {
63+
const { txhash: txHash } = await btcService.getRgbppTransactionHash(btcTxId);
64+
console.info(
65+
`Rgbpp compatible xUDT asset has been transferred on BTC and the related CKB tx hash is ${txHash}`,
66+
);
67+
} else {
68+
console.warn(`Rgbpp CKB transaction failed and the reason is ${failedReason} `);
69+
}
70+
}
71+
}, 30 * 1000);
72+
} catch (error) {
73+
console.error(error);
74+
}
75+
};
76+
77+
// Please use your real BTC UTXO information on the BTC Testnet
78+
// BTC Testnet3: https://mempool.space/testnet
79+
// BTC Signet: https://mempool.space/signet
80+
81+
// rgbppLockArgs: outIndexU32 + btcTxId
82+
transferRusdOnBtc({
83+
rgbppLockArgsList: [buildRgbppLockArgs(4, '44de1b4e3ddaa95cc85cc8b1c60f3e439d343002f0c60980fb4c70841ee0c75e')],
84+
toBtcAddress: 'tb1qvt7p9g6mw70sealdewtfp0sekquxuru6j3gwmt',
85+
// Please use your own RGB++ compatible xudt asset's type script
86+
compatibleXudtTypeScript: {
87+
codeHash: '0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a',
88+
hashType: 'type',
89+
args: '0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b',
90+
},
91+
transferAmount: BigInt(100_0000),
92+
});

0 commit comments

Comments
 (0)