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
3 changes: 3 additions & 0 deletions examples/rgbpp/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ CKB_INDEXER_URL=https://testnet.ckb.dev/indexer
# Read more about P2WPKH in BIP141: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wpkh
BTC_PRIVATE_KEY=private-key

# The BTC address type to use, available options: P2WPKH or P2TR
BTC_ADDRESS_TYPE=P2WPKH

# The BTC assets api url which should be matched with IS_MAINNET
VITE_BTC_SERVICE_URL=https://btc-assets-api.testnet.mibao.pro

Expand Down
9 changes: 6 additions & 3 deletions examples/rgbpp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,18 @@ CKB_INDEXER_URL=https://testnet.ckb.dev/indexer
# Read more about P2WPKH in BIP141: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wpkh
BTC_PRIVATE_KEY=private-key

# The BTC address type to use, available options: P2WPKH or P2TR
BTC_ADDRESS_TYPE=P2WPKH

# The BTC assets api url which should be matched with IS_MAINNET
VITE_BTC_SERVICE_URL=https://btc-assets-api.testnet.mibao.pro;
VITE_BTC_SERVICE_URL=https://btc-assets-api.testnet.mibao.pro

# The BTC assets api token which should be matched with IS_MAINNET
# To get an access token, please refer to https://github.com/ckb-cell/rgbpp-sdk/tree/develop/packages/service#get-an-access-token
VITE_BTC_SERVICE_TOKEN=;
VITE_BTC_SERVICE_TOKEN=

# The BTC assets api origin which should be matched with IS_MAINNET
VITE_BTC_SERVICE_ORIGIN=https://btc-test.app;
VITE_BTC_SERVICE_ORIGIN=https://btc-test.app
```

## RGB++ xUDT Examples
Expand Down
36 changes: 23 additions & 13 deletions examples/rgbpp/env.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import dotenv from 'dotenv';
import {
blake160,
bytesToHex,
privateKeyToPublicKey,
scriptToAddress,
systemScripts,
} from '@nervosnetwork/ckb-sdk-utils';
import { DataSource, BtcAssetsApi } from 'rgbpp';
import { ECPair, ECPairInterface, bitcoin, NetworkType } from 'rgbpp/btc';
import dotenv from 'dotenv';
import { NetworkType, AddressType, DataSource } from 'rgbpp/btc';
import { BtcAssetsApi } from 'rgbpp/service';
import { Collector } from 'rgbpp/ckb';
import { createBtcAccount } from './shared/btc-account';

dotenv.config({ path: __dirname + '/.env' });

export const isMainnet = process.env.IS_MAINNET === 'true' ? true : false;
/**
* Network
*/

export const isMainnet = process.env.IS_MAINNET === 'true';

/**
* CKB
*/

export const collector = new Collector({
ckbNodeUrl: process.env.CKB_NODE_URL!,
Expand All @@ -25,20 +34,21 @@ const secp256k1Lock: CKBComponents.Script = {
};
export const ckbAddress = scriptToAddress(secp256k1Lock, isMainnet);

/**
* BTC
*/

export const BTC_PRIVATE_KEY = process.env.BTC_PRIVATE_KEY!;
export const BTC_SERVICE_URL = process.env.VITE_BTC_SERVICE_URL!;
export const BTC_SERVICE_TOKEN = process.env.VITE_BTC_SERVICE_TOKEN!;
export const BTC_SERVICE_ORIGIN = process.env.VITE_BTC_SERVICE_ORIGIN!;

const network = isMainnet ? bitcoin.networks.bitcoin : bitcoin.networks.testnet;
export const btcKeyPair: ECPairInterface = ECPair.fromPrivateKey(Buffer.from(BTC_PRIVATE_KEY, 'hex'), { network });
// The Native Segwit P2WPKH address will be generated with the BTC private key
// Read more about P2WPKH in BIP141: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wpkh
export const { address: btcAddress } = bitcoin.payments.p2wpkh({
pubkey: btcKeyPair.publicKey,
network,
});

// Read more about the available address types:
// - P2WPKH: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wpkh
// - P2TR: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki
const addressType = process.env.BTC_ADDRESS_TYPE === 'P2TR' ? AddressType.P2TR : AddressType.P2WPKH;
const networkType = isMainnet ? NetworkType.MAINNET : NetworkType.TESTNET;
export const btcAccount = createBtcAccount(BTC_PRIVATE_KEY, addressType, networkType);

export const btcService = BtcAssetsApi.fromToken(BTC_SERVICE_URL, BTC_SERVICE_TOKEN, BTC_SERVICE_ORIGIN);
export const btcDataSource = new DataSource(btcService, networkType);
101 changes: 101 additions & 0 deletions examples/rgbpp/shared/btc-account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import {
addressToScriptPublicKeyHex,
networkTypeToNetwork,
remove0x,
toXOnly,
transactionToHex,
tweakSigner,
} from 'rgbpp/btc';
import { AddressType, NetworkType, bitcoin, ECPair } from 'rgbpp/btc';
import { BtcAssetsApi } from 'rgbpp/service';

export interface BtcAccount {
from: string;
fromPubkey?: string;
keyPair: bitcoin.Signer;
payment: bitcoin.Payment;
addressType: AddressType;
networkType: NetworkType;
}

export function createBtcAccount(privateKey: string, addressType: AddressType, networkType: NetworkType): BtcAccount {
const network = networkTypeToNetwork(networkType);

const key = Buffer.from(remove0x(privateKey), 'hex');
const keyPair = ECPair.fromPrivateKey(key, { network });

if (addressType === AddressType.P2WPKH) {
const p2wpkh = bitcoin.payments.p2wpkh({
pubkey: keyPair.publicKey,
network,
});
return {
from: p2wpkh.address!,
payment: p2wpkh,
keyPair,
addressType,
networkType,
};
} else if (addressType === AddressType.P2TR) {
const p2tr = bitcoin.payments.p2tr({
internalPubkey: toXOnly(keyPair.publicKey),
network,
});
return {
from: p2tr.address!,
fromPubkey: keyPair.publicKey.toString('hex'),
payment: p2tr,
keyPair,
addressType,
networkType,
};
} else {
throw new Error('Unsupported address type, only support P2WPKH and P2TR');
}
}

export function signPsbt(psbt: bitcoin.Psbt, account: BtcAccount): bitcoin.Psbt {
const accountScript = addressToScriptPublicKeyHex(account.from, account.networkType);
const tweakedSigner = tweakSigner(account.keyPair, {
network: account.payment.network,
});

psbt.data.inputs.forEach((input, index) => {
if (input.witnessUtxo) {
const script = input.witnessUtxo.script.toString('hex');
if (script === accountScript && account.addressType === AddressType.P2WPKH) {
psbt.signInput(index, account.keyPair);
}
if (script === accountScript && account.addressType === AddressType.P2TR) {
psbt.signInput(index, tweakedSigner);
}
}
});

return psbt;
}

export async function signAndSendPsbt(
psbt: bitcoin.Psbt,
account: BtcAccount,
service: BtcAssetsApi,
): Promise<{
txId: string;
txHex: string;
rawTxHex: string;
}> {
signPsbt(psbt, account);
psbt.finalizeAllInputs();

const tx = psbt.extractTransaction();
const txHex = tx.toHex();

const { txid } = await service.sendBtcTransaction(txHex);

return {
txHex,
txId: txid,
// Exclude witness from the BTC_TX for unlocking RGBPP assets
rawTxHex: transactionToHex(tx, false),
};
}
12 changes: 5 additions & 7 deletions examples/rgbpp/spore/4-transfer-spore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { buildRgbppLockArgs } from 'rgbpp/ckb';
import { genTransferSporeCkbVirtualTx, sendRgbppUtxos } from 'rgbpp';
import { getSporeTypeScript, Hex } from 'rgbpp/ckb';
import { serializeScript } from '@nervosnetwork/ckb-sdk-utils';
import { isMainnet, collector, btcAddress, btcDataSource, btcKeyPair, btcService } from '../env';
import { isMainnet, collector, btcDataSource, btcService, btcAccount } from '../env';
import { saveCkbVirtualTxResult } from '../shared/utils';
import { signAndSendPsbt } from '../shared/btc-account';

interface SporeTransferParams {
sporeRgbppLockArgs: Hex;
Expand Down Expand Up @@ -36,16 +37,13 @@ const transferSpore = async ({ sporeRgbppLockArgs, toBtcAddress, sporeTypeArgs }
tos: [toBtcAddress],
needPaymaster: needPaymasterCell,
ckbCollector: collector,
from: btcAddress!,
from: btcAccount.from,
fromPubkey: btcAccount.fromPubkey,
source: btcDataSource,
feeRate: 30,
});
psbt.signAllInputs(btcKeyPair);
psbt.finalizeAllInputs();

const btcTx = psbt.extractTransaction();
const { txid: btcTxId } = await btcService.sendBtcTransaction(btcTx.toHex());

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

await btcService.sendRgbppCkbTransaction({ btc_txid: btcTxId, ckb_virtual_result: ckbVirtualTxResult });
Expand Down
14 changes: 6 additions & 8 deletions examples/rgbpp/spore/5-leap-spore-to-ckb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { buildRgbppLockArgs } from 'rgbpp/ckb';
import { genLeapSporeFromBtcToCkbVirtualTx, sendRgbppUtxos } from 'rgbpp';
import { getSporeTypeScript, Hex } from 'rgbpp/ckb';
import { serializeScript } from '@nervosnetwork/ckb-sdk-utils';
import { isMainnet, collector, btcAddress, btcDataSource, btcKeyPair, btcService } from '../env';
import { isMainnet, collector, btcDataSource, btcService, btcAccount } from '../env';
import { saveCkbVirtualTxResult } from '../shared/utils';
import { signAndSendPsbt } from '../shared/btc-account';

interface SporeLeapParams {
sporeRgbppLockArgs: Hex;
Expand Down Expand Up @@ -34,19 +35,16 @@ const leapSporeFromBtcToCkb = async ({ sporeRgbppLockArgs, toCkbAddress, sporeTy
const psbt = await sendRgbppUtxos({
ckbVirtualTx: ckbRawTx,
commitment,
tos: [btcAddress!],
tos: [btcAccount.from],
needPaymaster: needPaymasterCell,
ckbCollector: collector,
from: btcAddress!,
from: btcAccount.from,
fromPubkey: btcAccount.fromPubkey,
source: btcDataSource,
feeRate: 30,
});
psbt.signAllInputs(btcKeyPair);
psbt.finalizeAllInputs();

const btcTx = psbt.extractTransaction();
const { txid: btcTxId } = await btcService.sendBtcTransaction(btcTx.toHex());

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

await btcService.sendRgbppCkbTransaction({ btc_txid: btcTxId, ckb_virtual_result: ckbVirtualTxResult });
Expand Down
16 changes: 6 additions & 10 deletions examples/rgbpp/spore/launch/2-create-cluster.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { BtcAssetsApiError, genCreateClusterCkbVirtualTx, sendRgbppUtxos } from 'rgbpp';
import { isMainnet, collector, btcAddress, btcDataSource, btcKeyPair, btcService } from '../../env';
import { isMainnet, collector, btcAccount, btcDataSource, btcService } from '../../env';
import { CLUSTER_DATA } from './0-cluster-info';
import { transactionToHex } from 'rgbpp/btc';
import {
appendCkbTxWitnesses,
buildRgbppLockArgs,
Expand All @@ -10,6 +9,7 @@ import {
updateCkbTxWithRealBtcTxId,
} from 'rgbpp/ckb';
import { saveCkbVirtualTxResult } from '../../shared/utils';
import { signAndSendPsbt } from '../../shared/btc-account';

// Warning: Before runing this file, please run 1-prepare-cluster.ts
const createCluster = async ({ ownerRgbppLockArgs }: { ownerRgbppLockArgs: string }) => {
Expand All @@ -32,20 +32,16 @@ const createCluster = async ({ ownerRgbppLockArgs }: { ownerRgbppLockArgs: strin
const psbt = await sendRgbppUtxos({
ckbVirtualTx: ckbRawTx,
commitment,
tos: [btcAddress!],
tos: [btcAccount.from],
needPaymaster: needPaymasterCell,
ckbCollector: collector,
from: btcAddress!,
from: btcAccount.from,
fromPubkey: btcAccount.fromPubkey,
source: btcDataSource,
feeRate: 30,
});
psbt.signAllInputs(btcKeyPair);
psbt.finalizeAllInputs();

const btcTx = psbt.extractTransaction();
const btcTxBytes = transactionToHex(btcTx, false);
const { txid: btcTxId } = await btcService.sendBtcTransaction(btcTx.toHex());

const { txId: btcTxId, rawTxHex: btcTxBytes } = await signAndSendPsbt(psbt, btcAccount, btcService);
console.log('BTC TxId: ', btcTxId);

const interval = setInterval(async () => {
Expand Down
26 changes: 7 additions & 19 deletions examples/rgbpp/spore/launch/3-create-spores.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
import { BtcAssetsApiError, genCreateSporeCkbVirtualTx, sendRgbppUtxos } from 'rgbpp';
import {
isMainnet,
collector,
btcAddress,
btcDataSource,
btcKeyPair,
btcService,
CKB_PRIVATE_KEY,
ckbAddress,
} from '../../env';
import { isMainnet, collector, btcDataSource, btcService, CKB_PRIVATE_KEY, ckbAddress, btcAccount } from '../../env';
import {
Hex,
appendCkbTxWitnesses,
Expand All @@ -19,8 +10,9 @@ import {
updateCkbTxWithRealBtcTxId,
RawSporeData,
} from 'rgbpp/ckb';
import { transactionToHex, utf8ToBuffer } from 'rgbpp/btc';
import { utf8ToBuffer } from 'rgbpp/btc';
import { saveCkbVirtualTxResult } from '../../shared/utils';
import { signAndSendPsbt } from '../../shared/btc-account';

interface SporeCreateParams {
clusterRgbppLockArgs: Hex;
Expand All @@ -47,24 +39,20 @@ const createSpores = async ({ clusterRgbppLockArgs, receivers }: SporeCreatePara

// Send BTC tx
// The first btc address is the owner of the cluster cell and the rest btc addresses are spore receivers
const btcTos = [btcAddress!, ...receivers.map((receiver) => receiver.toBtcAddress)];
const btcTos = [btcAccount.from, ...receivers.map((receiver) => receiver.toBtcAddress)];
const psbt = await sendRgbppUtxos({
ckbVirtualTx: ckbRawTx,
commitment,
tos: btcTos,
needPaymaster: needPaymasterCell,
ckbCollector: collector,
from: btcAddress!,
from: btcAccount.from,
fromPubkey: btcAccount.fromPubkey,
source: btcDataSource,
feeRate: 120,
});
psbt.signAllInputs(btcKeyPair);
psbt.finalizeAllInputs();

const btcTx = psbt.extractTransaction();
const btcTxBytes = transactionToHex(btcTx, false);
const { txid: btcTxId } = await btcService.sendBtcTransaction(btcTx.toHex());

const { txId: btcTxId, rawTxHex: btcTxBytes } = await signAndSendPsbt(psbt, btcAccount, btcService);
console.log('BTC TxId: ', btcTxId);

const interval = setInterval(async () => {
Expand Down
15 changes: 6 additions & 9 deletions examples/rgbpp/spore/local/4-transfer-spore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import {
generateSporeTransferCoBuild,
genTransferSporeCkbVirtualTx,
} from 'rgbpp/ckb';
import { sendRgbppUtxos, transactionToHex } from 'rgbpp/btc';
import { sendRgbppUtxos } from 'rgbpp/btc';
import { BtcAssetsApiError } from 'rgbpp';
import { serializeScript } from '@nervosnetwork/ckb-sdk-utils';
import { isMainnet, collector, btcAddress, btcDataSource, btcKeyPair, btcService } from '../../env';
import { isMainnet, collector, btcDataSource, btcService, btcAccount } from '../../env';
import { saveCkbVirtualTxResult } from '../../shared/utils';
import { signAndSendPsbt } from '../../shared/btc-account';

interface SporeTransferParams {
sporeRgbppLockArgs: Hex;
Expand Down Expand Up @@ -50,17 +51,13 @@ const transferSpore = async ({ sporeRgbppLockArgs, toBtcAddress, sporeTypeArgs }
tos: [toBtcAddress],
needPaymaster: needPaymasterCell,
ckbCollector: collector,
from: btcAddress!,
from: btcAccount.from,
fromPubkey: btcAccount.fromPubkey,
source: btcDataSource,
feeRate: 30,
});
psbt.signAllInputs(btcKeyPair);
psbt.finalizeAllInputs();

const btcTx = psbt.extractTransaction();
const btcTxBytes = transactionToHex(btcTx, false);
const { txid: btcTxId } = await btcService.sendBtcTransaction(btcTx.toHex());

const { txId: btcTxId, rawTxHex: btcTxBytes } = await signAndSendPsbt(psbt, btcAccount, btcService);
console.log('BTC TxId: ', btcTxId);

const interval = setInterval(async () => {
Expand Down
Loading