Skip to content

Commit

Permalink
Merge pull request #43 from ckb-cell/debug-examples
Browse files Browse the repository at this point in the history
feat(rgbpp-sdk): Add examples and fix btc/ckb bugs
  • Loading branch information
Flouse authored Mar 24, 2024
2 parents f5a861f + 35f09c1 commit 709c181
Show file tree
Hide file tree
Showing 35 changed files with 1,541 additions and 459 deletions.
60 changes: 60 additions & 0 deletions examples/rgbpp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
## RGB++ Examples

**All examples are just to demonstrate the use of RGB++ SDK. SPV proof is not ready yet, so these examples do not involve the verification of SPV proof.**

### What you must know about BTC transaction id

**The btc tx transaction id(hash) displayed on BTC explorer is different from the btc transaction id(hash) in RGB++ lock args. They are in reverse byte order.**

We follow the following two rules:

- Whenever you're working with transaction/block hashes **internally** (e.g. inside raw bitcoin data), you use the **natural** byte order.
- Whenever you're **displaying or searching** for transaction/block hashes, you use the **reverse** byte order.

For detailed rules, please refer to [Byte Order](https://learnmeabitcoin.com/technical/general/byte-order/)

For example, the BTC transaction id(hash) of the RGB++ lock args like this:

```
4abc778213bc4da692f93745c2b07410ef2bfaee70417784d4ee8969fb258001
```

But when you're searching for this transaction in [Bitcoin Core](https://bitcoin.org/en/bitcoin-core/) or on a block explorer, you'll see this byte order:

```
018025fb6989eed484774170eefa2bef1074b0c24537f992a64dbc138277bc4a
```

### Mint XUDT
As a simple example, Omiga protocol is reused for a quick demonstration of XUDT asset insurance.
Developers have the option to utilize an existing XUDT (User-Defined Token) on CKB.

```shell
npx ts-node examples/rgbpp/src/1-mint-xudt.ts
```

### Jump XUDT from CKB to BTC

```shell
npx ts-node examples/rgbpp/src/2-ckb-jump-btc.ts
```

### Transfer RGB++ asset on BTC

```shell
npx ts-node examples/rgbpp/src/3-btc-transfer.ts
```

### Jump RGB++ asset from BTC to CKB

```shell
npx ts-node examples/rgbpp/src/4-btc-jump-ckb.ts
```

### Unlock BTC time cells on CKB

**Warning: Wait at least 6 BTC confirmation blocks to unlock the BTC time cells after 4-btc-jump-ckb.ts**

```shell
npx ts-node examples/rgbpp/src/5-spend-btc-time-cell.ts
```
18 changes: 18 additions & 0 deletions examples/rgbpp/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "rgbpp-examples",
"version": "0.1.0",
"description": "Examples used for RGBPP assets issuance, transfer, and jumping between BTC and CKB",
"private": true,
"dependencies": {
"@ckb-lumos/base": "0.21.1",
"@nervosnetwork/ckb-sdk-utils": "^0.109.1",
"@rgbpp-sdk/ckb": "workspace:^",
"@rgbpp-sdk/btc": "workspace:^",
"axios": "^1.6.8",
"ckb-omiga": "^0.0.12"
},
"devDependencies": {
"typescript": "^5.4.2",
"@types/node": "^20.11.28"
}
}
44 changes: 44 additions & 0 deletions examples/rgbpp/src/1-mint-xudt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { AddressPrefix } from '@nervosnetwork/ckb-sdk-utils';
import { blockchain } from '@ckb-lumos/base';
import { buildMintTx, Collector } from 'ckb-omiga';

// CKB SECP256K1 private key
const CKB_TEST_PRIVATE_KEY = '0x0000000000000000000000000000000000000000000000000000000000000001';

// To simplify, we reuse the Omiga protocol to quickly issue XUDT assets on Testnet CKB
const mintXudt = async () => {
const collector = new Collector({
ckbNodeUrl: 'https://testnet.ckb.dev/rpc',
ckbIndexerUrl: 'https://testnet.ckb.dev/indexer',
});

const address = collector.getCkb().utils.privateKeyToAddress(CKB_TEST_PRIVATE_KEY, { prefix: AddressPrefix.Testnet });
// ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqt4z78ng4yutl5u6xsv27ht6q08mhujf8s2r0n40
console.log('ckb address: ', address);

const mintLimit = BigInt(1000) * BigInt(10 ** 8);
const inscriptionId = '0xd378891e711cf5c612321b7f51529215187403c61cbb27bc4413fded871b73d5';

const rawTx = await buildMintTx({ collector, address, inscriptionId, mintLimit });

const secp256k1Dep: CKBComponents.CellDep = {
outPoint: {
txHash: '0xf8de3bb47d055cdf460d93a2a6e1b05f7432f9777c8c474abf4eec1d4aee5d37',
index: '0x0',
},
depType: 'depGroup',
};
const witnessArgs = blockchain.WitnessArgs.unpack(rawTx.witnesses[0]) as CKBComponents.WitnessArgs;
let unsignedTx: CKBComponents.RawTransactionToSign = {
...rawTx,
cellDeps: [...rawTx.cellDeps, secp256k1Dep],
witnesses: [witnessArgs, ...rawTx.witnesses.slice(1)],
};
const signedTx = collector.getCkb().signTransaction(CKB_TEST_PRIVATE_KEY)(unsignedTx);

let txHash = await collector.getCkb().rpc.sendTransaction(signedTx, 'passthrough');
console.info(`Xudt has been minted with tx hash ${txHash}`);
};

mintXudt();

49 changes: 49 additions & 0 deletions examples/rgbpp/src/2-ckb-jump-btc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { AddressPrefix, privateKeyToAddress, serializeScript } from '@nervosnetwork/ckb-sdk-utils';
import { genCkbJumpBtcVirtualTx, Collector, getSecp256k1CellDep, buildRgbppLockArgs } from '@rgbpp-sdk/ckb';

// CKB SECP256K1 private key
const CKB_TEST_PRIVATE_KEY = '0x0000000000000000000000000000000000000000000000000000000000000001';

const jumpFromCkbToBtc = async ({ outIndex, btcTxId }: { outIndex: number; btcTxId: string }) => {
const collector = new Collector({
ckbNodeUrl: 'https://testnet.ckb.dev/rpc',
ckbIndexerUrl: 'https://testnet.ckb.dev/indexer',
});
const address = privateKeyToAddress(CKB_TEST_PRIVATE_KEY, { prefix: AddressPrefix.Testnet });
console.log('ckb address: ', address);

const toRgbppLockArgs = buildRgbppLockArgs(outIndex, btcTxId);

const xudtType: CKBComponents.Script = {
codeHash: '0x25c29dc317811a6f6f3985a7a9ebc4838bd388d19d0feeecf0bcd60f6c0975bb',
hashType: 'type',
args: '0x1ba116c119d1cfd98a53e9d1a615cf2af2bb87d95515c9d217d367054cfc696b',
};

const ckbRawTx = await genCkbJumpBtcVirtualTx({
collector,
fromCkbAddress: address,
toRgbppLockArgs,
xudtTypeBytes: serializeScript(xudtType),
transferAmount: BigInt(800_0000_0000),
});

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

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

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

// Use your real BTC UTXO information on the BTC Testnet
jumpFromCkbToBtc({
outIndex: 1,
btcTxId: 'dad1814249f3bcbcb1e26436108c8f48b9d71456938cf62e122357caf7913013',
});

117 changes: 117 additions & 0 deletions examples/rgbpp/src/3-btc-transfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { AddressPrefix, privateKeyToAddress, serializeScript } from '@nervosnetwork/ckb-sdk-utils';
import {
Collector,
SPVService,
appendCkbTxWitnesses,
buildRgbppLockArgs,
genBtcTransferCkbVirtualTx,
sendCkbTx,
updateCkbTxWithRealBtcTxId,
} from '@rgbpp-sdk/ckb';
import { transactionToHex, sendRgbppUtxos, BtcAssetsApi, DataSource, ECPair, bitcoin, NetworkType } from '@rgbpp-sdk/btc';

// CKB SECP256K1 private key
const CKB_TEST_PRIVATE_KEY = '0x0000000000000000000000000000000000000000000000000000000000000001';
// BTC SECP256K1 private key
const BTC_TEST_PRIVATE_KEY = '0x0000000000000000000000000000000000000000000000000000000000000001';
// https://btc-assets-api-develop.vercel.app/docs/static/index.html
const BTC_ASSETS_API_URL = 'https://btc-assets-api-url';
// https://btc-assets-api-develop.vercel.app/docs/static/index.html#/Token/post_token_generate
const BTC_ASSETS_TOKEN = '';
// See https://github.com/ckb-cell/ckb-bitcoin-spv-service#json-rpc-api-reference
const SPV_SERVICE_URL = 'https://ckb-bitcoin-spv-service.testnet.mibao.pro';

interface Params {
rgbppLockArgsList: string[];
toBtcAddress: string;
transferAmount: bigint;
}
const transferRgbppOnBtc = async ({ rgbppLockArgsList, toBtcAddress, transferAmount }: Params) => {
const collector = new Collector({
ckbNodeUrl: 'https://testnet.ckb.dev/rpc',
ckbIndexerUrl: 'https://testnet.ckb.dev/indexer',
});
const ckbAddress = privateKeyToAddress(CKB_TEST_PRIVATE_KEY, { prefix: AddressPrefix.Testnet });
console.log('ckb address: ', ckbAddress);
// const fromLock = addressToScript(ckbAddress);

const network = bitcoin.networks.testnet;
const keyPair = ECPair.fromPrivateKey(Buffer.from(BTC_TEST_PRIVATE_KEY, 'hex'), { network });
const { address: btcAddress } = bitcoin.payments.p2wpkh({
pubkey: keyPair.publicKey,
network,
});

console.log('btc address: ', btcAddress);

const networkType = NetworkType.TESTNET;
const service = BtcAssetsApi.fromToken(BTC_ASSETS_API_URL, BTC_ASSETS_TOKEN, 'http://localhost');
const source = new DataSource(service, networkType);

const xudtType: CKBComponents.Script = {
codeHash: '0x25c29dc317811a6f6f3985a7a9ebc4838bd388d19d0feeecf0bcd60f6c0975bb',
hashType: 'type',
args: '0x1ba116c119d1cfd98a53e9d1a615cf2af2bb87d95515c9d217d367054cfc696b',
};

const ckbVirtualTxResult = await genBtcTransferCkbVirtualTx({
collector,
rgbppLockArgsList,
xudtTypeBytes: serializeScript(xudtType),
transferAmount,
isMainnet: false,
});

const { commitment, ckbRawTx } = ckbVirtualTxResult;

// Send BTC tx
const psbt = await sendRgbppUtxos({
ckbVirtualTx: ckbRawTx,
commitment,
tos: [toBtcAddress],
ckbCollector: collector,
from: btcAddress!,
source,
});
psbt.signAllInputs(keyPair);
psbt.finalizeAllInputs();

const btcTx = psbt.extractTransaction();
// Remove the witness from BTC tx for RGBPP unlock
const btcTxBytes = transactionToHex(btcTx, false);
let { txid: btcTxId } = await service.sendTransaction(btcTx.toHex());

console.log('BTC Tx bytes: ', btcTxBytes);
console.log('BTC TxId: ', btcTxId);

// Update CKB transaction with the real BTC txId
const newCkbRawTx = updateCkbTxWithRealBtcTxId({ ckbRawTx, btcTxId, isMainnet: false });

const spvService = new SPVService(SPV_SERVICE_URL);
// Use an exist BTC transaction id to get the tx proof and the contract will not verify the tx proof now
btcTxId = '018025fb6989eed484774170eefa2bef1074b0c24537f992a64dbc138277bc4a';

let ckbTx = await appendCkbTxWitnesses({
ckbRawTx: newCkbRawTx,
btcTxBytes,
spvService,
btcTxIndexInBlock: 0, // ignore spv proof now
btcTxId,
});

console.log(JSON.stringify(ckbTx));

const txHash = await sendCkbTx({ collector, signedTx: ckbTx });
console.info(`Rgbpp asset has been transferred on BTC and tx hash is ${txHash}`);
};


// Use your real BTC UTXO information on the BTC Testnet
// rgbppLockArgs: outIndexU32 + btcTxId
transferRgbppOnBtc({
rgbppLockArgsList: [buildRgbppLockArgs(1, '53e7c02eba522d1e3b0698b4bf5405c25c33b32e7df84a1a6c19e2cf165681f0')],
toBtcAddress: 'tb1qvt7p9g6mw70sealdewtfp0sekquxuru6j3gwmt',
// To simplify, keep the transferAmount the same as 2-ckb-jump-btc
transferAmount: BigInt(800_0000_0000),
});

Loading

1 comment on commit 709c181

@github-actions
Copy link

Choose a reason for hiding this comment

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

[{"name":"@rgbpp-sdk/btc","version":"0.0.0-snap-20240324133702"},{"name":"@rgbpp-sdk/ckb","version":"0.0.0-snap-20240324133702"}]

Please sign in to comment.