diff --git a/README.md b/README.md index 8a342530..8d9b7c98 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,8 @@ This repository offers utilities for Bitcoin and RGB++ asset integration. ### How to get an access token of Bitcoin/RGB++ Assets Service? See [Generate a JSON Web Token (JWT) for Bitcoin/RGB++ Assets Service](./packages/service/README.md#get-an-access-token) +### Where is the error code description for the RgbppLockScript? +See [RGB++ Lock Script Error Codes](https://github.com/nervosnetwork/ckb-script-error-codes/blob/main/by-type-hash/bc6c568a1a0d0a09f6844dc9d74ddb4343c32143ff25f727c59edf4fb72d6936.md) ## License diff --git a/examples/rgbpp/.env.example b/examples/rgbpp/.env.example index 281dcbcf..7a6d1e10 100644 --- a/examples/rgbpp/.env.example +++ b/examples/rgbpp/.env.example @@ -16,6 +16,8 @@ CKB_INDEXER_URL=https://testnet.ckb.dev/indexer # BTC Variables # The BTC private key whose format is 32bytes hex string without 0x prefix +# 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 BTC_PRIVATE_KEY=private-key # The BTC assets api url which should be matched with IS_MAINNET diff --git a/examples/rgbpp/README.md b/examples/rgbpp/README.md index af072c7d..c0810eb3 100644 --- a/examples/rgbpp/README.md +++ b/examples/rgbpp/README.md @@ -3,8 +3,18 @@ - xUDT directory: The examples for RGB++ UDT issuance, transfer, and leap - Spore directory: The examples for RGB++ Spore creation, transfer and leap +> [!TIP] +> 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. Please confirm whether the parameters are correct according to the code comments + ## How to Start +### Install dependencies and build packages + +``` +pnpm install && pnpm build:packages +``` +### Update .env + Copy the `.env.example` file to `.env`: ```shell @@ -31,6 +41,8 @@ CKB_INDEXER_URL=https://testnet.ckb.dev/indexer # BTC Variables # The BTC private key whose format is 32bytes hex string without 0x prefix +# 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 BTC_PRIVATE_KEY=private-key # The BTC assets api url which should be matched with IS_MAINNET @@ -44,23 +56,36 @@ VITE_BTC_SERVICE_TOKEN=; VITE_BTC_SERVICE_ORIGIN=https://btc-test.app; ``` - ## RGB++ xUDT Examples ### RGB++ xUDT Launch on BTC #### 1. Prepare Launch +> [!TIP] +> Please make sure the CKB private key in the .env is correct + ```shell +# Create a CKB empty rgbpp lock cell to launch RGB++ xUDT assets later npx ts-node xudt/launch/1-prepare-launch.ts ``` #### 2. Launch RGB++ xUDT on BTC +> [!TIP] +> Please make sure the `1-prepare-launch.ts` has been run and the corresponding CKB transaction has been committed + ```shell npx ts-node xudt/launch/2-launch-rgbpp.ts ``` + +When the command is executed successfully, the **RGB++ Asset type script args** will appear in the output log + #### 3. Distribute RGB++ xUDT on BTC +> [!TIP] +> Please make sure the `2-launch-rgbpp.ts` has been run and the corresponding BTC and CKB transactions have been committed +> The **RGB++ Asset type script args** in the above should be set to the `xudtTypeArgs` + ```shell npx ts-node xudt/launch/3-distribute-rgbpp.ts ``` @@ -103,14 +128,26 @@ npx ts-node xudt/4-unlock-btc-time.ts #### 1. Create RGB++ Cluster Cell +> [!TIP] +> Please make sure all the variables in the .env are correct +> The BTC UTXO of `1-prepare-cluster.ts` and `2-create-cluster.ts` should be same + ```shell +# Create a CKB empty rgbpp lock cell to create cluster later npx ts-node spore/launch/1-prepare-cluster.ts +# Create a cluster cell with rgbpp lock npx ts-node spore/launch/2-create-cluster.ts ``` +When the commands are executed successfully, the **clusterId** and **cluster rgbpp lock args** will appear in the output log + #### 2. Create RGB++ Spores with Cluster on BTC +> [!TIP] +> Please make sure the `2-create-cluster.ts` has been run and the corresponding BTC and CKB transactions have been committed +> The **clusterId** in the above should be set to the `clusterId` and the **cluster rgbpp lock args** should be set to the `clusterRgbppLockArgs` + ```shell npx ts-node spore/launch/3-create-spores.ts ``` @@ -147,6 +184,13 @@ npx ts-node spore/6-unlock-btc-time-cell.ts npx ts-node spore/7-leap-spore-to-btc.ts ``` +## FAQ + +If you have any questions, please refer to the FAQ first. + +See [RGBPP FAQ](https://github.com/ckb-cell/rgbpp-sdk/wiki/RGBPP--FAQ) + + ## What you must know about BTC transaction id **The BTC 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.** diff --git a/examples/rgbpp/env.ts b/examples/rgbpp/env.ts index fd50a805..05ec5b96 100644 --- a/examples/rgbpp/env.ts +++ b/examples/rgbpp/env.ts @@ -1,4 +1,10 @@ -import { AddressPrefix, privateKeyToAddress } from '@nervosnetwork/ckb-sdk-utils'; +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'; @@ -13,9 +19,11 @@ export const collector = new Collector({ ckbIndexerUrl: process.env.CKB_INDEXER_URL!, }); export const CKB_PRIVATE_KEY = process.env.CKB_SECP256K1_PRIVATE_KEY!; -export const ckbAddress = privateKeyToAddress(CKB_PRIVATE_KEY, { - prefix: isMainnet ? AddressPrefix.Mainnet : AddressPrefix.Testnet, -}); +const secp256k1Lock: CKBComponents.Script = { + ...systemScripts.SECP256K1_BLAKE160, + args: bytesToHex(blake160(privateKeyToPublicKey(CKB_PRIVATE_KEY))), +}; +export const ckbAddress = scriptToAddress(secp256k1Lock, isMainnet); export const BTC_PRIVATE_KEY = process.env.BTC_PRIVATE_KEY!; export const BTC_SERVICE_URL = process.env.VITE_BTC_SERVICE_URL!; @@ -24,6 +32,8 @@ 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, diff --git a/examples/rgbpp/shared/utils.ts b/examples/rgbpp/shared/utils.ts index 2c5d2e6f..1c14e7a4 100644 --- a/examples/rgbpp/shared/utils.ts +++ b/examples/rgbpp/shared/utils.ts @@ -5,6 +5,7 @@ import { SporeVirtualTxResult, SporeCreateVirtualTxResult, SporeTransferVirtualTxResult, + RgbppLaunchVirtualTxResult, } from 'rgbpp/ckb'; /** @@ -15,6 +16,7 @@ import { export type CkbVirtualTxResultType = | BaseCkbVirtualTxResult + | RgbppLaunchVirtualTxResult | SporeVirtualTxResult | SporeCreateVirtualTxResult | SporeTransferVirtualTxResult; diff --git a/examples/rgbpp/spore/4-transfer-spore.ts b/examples/rgbpp/spore/4-transfer-spore.ts index 7646f4fe..cf39c8e0 100644 --- a/examples/rgbpp/spore/4-transfer-spore.ts +++ b/examples/rgbpp/spore/4-transfer-spore.ts @@ -27,13 +27,14 @@ const transferSpore = async ({ sporeRgbppLockArgs, toBtcAddress, sporeTypeArgs } // Save ckbVirtualTxResult saveCkbVirtualTxResult(ckbVirtualTxResult, '4-transfer-spore'); - const { commitment, ckbRawTx } = ckbVirtualTxResult; + const { commitment, ckbRawTx, needPaymasterCell } = ckbVirtualTxResult; // Send BTC tx const psbt = await sendRgbppUtxos({ ckbVirtualTx: ckbRawTx, commitment, tos: [toBtcAddress], + needPaymaster: needPaymasterCell, ckbCollector: collector, from: btcAddress!, source: btcDataSource, @@ -68,10 +69,11 @@ const transferSpore = async ({ sporeRgbppLockArgs, toBtcAddress, sporeTypeArgs } } }; -// Use your real BTC UTXO information on the BTC Testnet +// Please use your real BTC UTXO information on the BTC Testnet // rgbppLockArgs: outIndexU32 + btcTxId transferSpore({ sporeRgbppLockArgs: buildRgbppLockArgs(2, 'd5868dbde4be5e49876b496449df10150c356843afb6f94b08f8d81f394bb350'), toBtcAddress: 'tb1qhp9fh9qsfeyh0yhewgu27ndqhs5qlrqwau28m7', + // Please use your own RGB++ spore asset's sporeTypeArgs sporeTypeArgs: '0x42898ea77062256f46e8f1b861d526ae47810ecc51ab50477945d5fa90452706', }); diff --git a/examples/rgbpp/spore/5-leap-spore-to-ckb.ts b/examples/rgbpp/spore/5-leap-spore-to-ckb.ts index 1f0ae59e..3fdbf5cf 100644 --- a/examples/rgbpp/spore/5-leap-spore-to-ckb.ts +++ b/examples/rgbpp/spore/5-leap-spore-to-ckb.ts @@ -28,13 +28,14 @@ const leapSporeFromBtcToCkb = async ({ sporeRgbppLockArgs, toCkbAddress, sporeTy // Save ckbVirtualTxResult saveCkbVirtualTxResult(ckbVirtualTxResult, '5-leap-spore-to-ckb'); - const { commitment, ckbRawTx } = ckbVirtualTxResult; + const { commitment, ckbRawTx, needPaymasterCell } = ckbVirtualTxResult; // Send BTC tx const psbt = await sendRgbppUtxos({ ckbVirtualTx: ckbRawTx, commitment, tos: [btcAddress!], + needPaymaster: needPaymasterCell, ckbCollector: collector, from: btcAddress!, source: btcDataSource, @@ -69,10 +70,11 @@ const leapSporeFromBtcToCkb = async ({ sporeRgbppLockArgs, toCkbAddress, sporeTy } }; -// Use your real BTC UTXO information on the BTC Testnet +// Please use your real BTC UTXO information on the BTC Testnet // rgbppLockArgs: outIndexU32 + btcTxId leapSporeFromBtcToCkb({ sporeRgbppLockArgs: buildRgbppLockArgs(3, 'd8a31796fbd42c546f6b22014b9b82b16586ce1df81b0e7ca9a552cdc492a0af'), toCkbAddress: 'ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq0e4xk4rmg5jdkn8aams492a7jlg73ue0gc0ddfj', + // Please use your own RGB++ spore asset's sporeTypeArgs sporeTypeArgs: '0x42898ea77062256f46e8f1b861d526ae47810ecc51ab50477945d5fa90452706', }); diff --git a/examples/rgbpp/spore/7-leap-spore-to-btc.ts b/examples/rgbpp/spore/7-leap-spore-to-btc.ts index c9deb00f..5f85e6c8 100644 --- a/examples/rgbpp/spore/7-leap-spore-to-btc.ts +++ b/examples/rgbpp/spore/7-leap-spore-to-btc.ts @@ -40,9 +40,10 @@ const leapSporeFromCkbToBtc = async ({ console.info(`RGB++ Spore has been jumped from CKB to BTC and tx hash is ${txHash}`); }; -// Use your real BTC UTXO information on the BTC Testnet +// Please use your real BTC UTXO information on the BTC Testnet leapSporeFromCkbToBtc({ outIndex: 1, btcTxId: '448897515cf07b4ca0cd38af9806399ede55775b4c760b274ed2322121ed185f', + // Please use your own RGB++ spore asset's sporeTypeArgs sporeTypeArgs: '0x42898ea77062256f46e8f1b861d526ae47810ecc51ab50477945d5fa90452706', }); diff --git a/examples/rgbpp/spore/launch/1-prepare-cluster.ts b/examples/rgbpp/spore/launch/1-prepare-cluster.ts index 128731a5..215b7951 100644 --- a/examples/rgbpp/spore/launch/1-prepare-cluster.ts +++ b/examples/rgbpp/spore/launch/1-prepare-cluster.ts @@ -70,6 +70,7 @@ const prepareClusterCell = async ({ outIndex, btcTxId }: { outIndex: number; btc console.info(`Cluster cell has been prepared and the tx hash ${txHash}`); }; +// Please use your real BTC UTXO information on the BTC Testnet prepareClusterCell({ outIndex: 3, btcTxId: 'aee4e8e3aa95e9e9ab1f0520714031d92d3263262099dcc7f7d64e62fa2fcb44', diff --git a/examples/rgbpp/spore/launch/2-create-cluster.ts b/examples/rgbpp/spore/launch/2-create-cluster.ts index 97e3cdd1..745e66e5 100644 --- a/examples/rgbpp/spore/launch/2-create-cluster.ts +++ b/examples/rgbpp/spore/launch/2-create-cluster.ts @@ -11,6 +11,7 @@ import { } from 'rgbpp/ckb'; import { saveCkbVirtualTxResult } from '../../shared/utils'; +// Warning: Before runing this file, please run 1-prepare-cluster.ts const createCluster = async ({ ownerRgbppLockArgs }: { ownerRgbppLockArgs: string }) => { const ckbVirtualTxResult = await genCreateClusterCkbVirtualTx({ collector, @@ -23,7 +24,7 @@ const createCluster = async ({ ownerRgbppLockArgs }: { ownerRgbppLockArgs: strin // Save ckbVirtualTxResult saveCkbVirtualTxResult(ckbVirtualTxResult, '2-create-cluster'); - const { commitment, ckbRawTx, clusterId } = ckbVirtualTxResult; + const { commitment, ckbRawTx, clusterId, needPaymasterCell } = ckbVirtualTxResult; console.log('clusterId: ', clusterId); @@ -32,6 +33,7 @@ const createCluster = async ({ ownerRgbppLockArgs }: { ownerRgbppLockArgs: strin ckbVirtualTx: ckbRawTx, commitment, tos: [btcAddress!], + needPaymaster: needPaymasterCell, ckbCollector: collector, from: btcAddress!, source: btcDataSource, @@ -79,7 +81,7 @@ const createCluster = async ({ ownerRgbppLockArgs }: { ownerRgbppLockArgs: strin }, 30 * 1000); }; -// Use your real BTC UTXO information on the BTC Testnet +// Please use your real BTC UTXO information on the BTC Testnet which should be same as the 1-prepare-cluster.ts // rgbppLockArgs: outIndexU32 + btcTxId createCluster({ ownerRgbppLockArgs: buildRgbppLockArgs(3, 'aee4e8e3aa95e9e9ab1f0520714031d92d3263262099dcc7f7d64e62fa2fcb44'), diff --git a/examples/rgbpp/spore/launch/3-create-spores.ts b/examples/rgbpp/spore/launch/3-create-spores.ts index 342b8e6a..8b6b4532 100644 --- a/examples/rgbpp/spore/launch/3-create-spores.ts +++ b/examples/rgbpp/spore/launch/3-create-spores.ts @@ -30,6 +30,7 @@ interface SporeCreateParams { }[]; } +// Warning: Before runing this file for the first time, please run 2-prepare-cluster.ts const createSpores = async ({ clusterRgbppLockArgs, receivers }: SporeCreateParams) => { const ckbVirtualTxResult = await genCreateSporeCkbVirtualTx({ collector, @@ -42,7 +43,7 @@ const createSpores = async ({ clusterRgbppLockArgs, receivers }: SporeCreatePara // Save ckbVirtualTxResult saveCkbVirtualTxResult(ckbVirtualTxResult, '3-create-spores'); - const { commitment, ckbRawTx, sumInputsCapacity, clusterCell } = ckbVirtualTxResult; + const { commitment, ckbRawTx, sumInputsCapacity, clusterCell, needPaymasterCell } = ckbVirtualTxResult; // Send BTC tx // The first btc address is the owner of the cluster cell and the rest btc addresses are spore receivers @@ -51,6 +52,7 @@ const createSpores = async ({ clusterRgbppLockArgs, receivers }: SporeCreatePara ckbVirtualTx: ckbRawTx, commitment, tos: btcTos, + needPaymaster: needPaymasterCell, ckbCollector: collector, from: btcAddress!, source: btcDataSource, @@ -114,7 +116,7 @@ const createSpores = async ({ clusterRgbppLockArgs, receivers }: SporeCreatePara }, 30 * 1000); }; -// Use your real BTC UTXO information on the BTC Testnet +// Please use your real BTC UTXO information on the BTC Testnet // rgbppLockArgs: outIndexU32 + btcTxId createSpores({ // The cluster cell will be spent and the new cluster cell will be created in each spore creation tx, diff --git a/examples/rgbpp/spore/local/4-transfer-spore.ts b/examples/rgbpp/spore/local/4-transfer-spore.ts index a695c818..9c947106 100644 --- a/examples/rgbpp/spore/local/4-transfer-spore.ts +++ b/examples/rgbpp/spore/local/4-transfer-spore.ts @@ -39,7 +39,7 @@ const transferSpore = async ({ sporeRgbppLockArgs, toBtcAddress, sporeTypeArgs } // Save ckbVirtualTxResult saveCkbVirtualTxResult(ckbVirtualTxResult, '4-transfer-spore-local'); - const { commitment, ckbRawTx, sporeCell } = ckbVirtualTxResult; + const { commitment, ckbRawTx, sporeCell, needPaymasterCell } = ckbVirtualTxResult; // console.log(JSON.stringify(ckbRawTx)) @@ -48,6 +48,7 @@ const transferSpore = async ({ sporeRgbppLockArgs, toBtcAddress, sporeTypeArgs } ckbVirtualTx: ckbRawTx, commitment, tos: [toBtcAddress], + needPaymaster: needPaymasterCell, ckbCollector: collector, from: btcAddress!, source: btcDataSource, @@ -91,11 +92,12 @@ const transferSpore = async ({ sporeRgbppLockArgs, toBtcAddress, sporeTypeArgs } }, 30 * 1000); }; -// Use your real BTC UTXO information on the BTC Testnet +// Please use your real BTC UTXO information on the BTC Testnet // rgbppLockArgs: outIndexU32 + btcTxId transferSpore({ // The spore rgbpp lock args is from 3-create-spore.ts sporeRgbppLockArgs: buildRgbppLockArgs(1, 'f203c8c13eacdbd126f85d286a963c85f233f8145363b1d997c4d552afb990e1'), toBtcAddress: 'tb1qhp9fh9qsfeyh0yhewgu27ndqhs5qlrqwau28m7', + // Please use your own RGB++ spore asset's sporeTypeArgs sporeTypeArgs: '0x42898ea77062256f46e8f1b861d526ae47810ecc51ab50477945d5fa90452706', }); diff --git a/examples/rgbpp/spore/local/5-leap-spore-to-ckb.ts b/examples/rgbpp/spore/local/5-leap-spore-to-ckb.ts index 06856e96..0f7bf610 100644 --- a/examples/rgbpp/spore/local/5-leap-spore-to-ckb.ts +++ b/examples/rgbpp/spore/local/5-leap-spore-to-ckb.ts @@ -39,7 +39,7 @@ const leapSpore = async ({ sporeRgbppLockArgs, toCkbAddress, sporeTypeArgs }: Sp // Save ckbVirtualTxResult saveCkbVirtualTxResult(ckbVirtualTxResult, '5-leap-spore-to-ckb-local'); - const { commitment, ckbRawTx, sporeCell } = ckbVirtualTxResult; + const { commitment, ckbRawTx, sporeCell, needPaymasterCell } = ckbVirtualTxResult; // console.log(JSON.stringify(ckbRawTx)) @@ -48,6 +48,7 @@ const leapSpore = async ({ sporeRgbppLockArgs, toCkbAddress, sporeTypeArgs }: Sp ckbVirtualTx: ckbRawTx, commitment, tos: [btcAddress!], + needPaymaster: needPaymasterCell, ckbCollector: collector, from: btcAddress!, source: btcDataSource, @@ -91,11 +92,12 @@ const leapSpore = async ({ sporeRgbppLockArgs, toCkbAddress, sporeTypeArgs }: Sp }, 30 * 1000); }; -// Use your real BTC UTXO information on the BTC Testnet +// Please use your real BTC UTXO information on the BTC Testnet // rgbppLockArgs: outIndexU32 + btcTxId leapSpore({ // The spore rgbpp lock args is from 3-create-spore.ts sporeRgbppLockArgs: buildRgbppLockArgs(3, 'd8a31796fbd42c546f6b22014b9b82b16586ce1df81b0e7ca9a552cdc492a0af'), toCkbAddress: 'ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq0e4xk4rmg5jdkn8aams492a7jlg73ue0gc0ddfj', + // Please use your own RGB++ spore asset's sporeTypeArgs sporeTypeArgs: '0x42898ea77062256f46e8f1b861d526ae47810ecc51ab50477945d5fa90452706', }); diff --git a/examples/rgbpp/tsconfig.json b/examples/rgbpp/tsconfig.json index b092d7f8..9134ad42 100644 --- a/examples/rgbpp/tsconfig.json +++ b/examples/rgbpp/tsconfig.json @@ -21,6 +21,6 @@ "skipLibCheck": true, "strict": true }, - "include": ["queue/**/*.ts", "xudt/**/*.ts"], + "include": ["spore", "xudt", "shared"], "exclude": ["node_modules"] } diff --git a/examples/rgbpp/xudt/1-ckb-leap-btc.ts b/examples/rgbpp/xudt/1-ckb-leap-btc.ts index aeb221de..a979acdc 100644 --- a/examples/rgbpp/xudt/1-ckb-leap-btc.ts +++ b/examples/rgbpp/xudt/1-ckb-leap-btc.ts @@ -30,7 +30,7 @@ const leapFromCkbToBtc = async ({ outIndex, btcTxId, xudtTypeArgs, transferAmoun const emptyWitness = { lock: '', inputType: '', outputType: '' }; const unsignedTx: CKBComponents.RawTransactionToSign = { ...ckbRawTx, - cellDeps: [...ckbRawTx.cellDeps, getSecp256k1CellDep(false)], + cellDeps: [...ckbRawTx.cellDeps, getSecp256k1CellDep(isMainnet)], witnesses: [emptyWitness, ...ckbRawTx.witnesses.slice(1)], }; @@ -40,10 +40,11 @@ const leapFromCkbToBtc = async ({ outIndex, btcTxId, xudtTypeArgs, transferAmoun 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 +// Please use your real BTC UTXO information on the BTC Testnet leapFromCkbToBtc({ outIndex: 1, btcTxId: '4ff1855b64b309afa19a8b9be3d4da99dcb18b083b65d2d851662995c7d99e7a', + // Please use your own RGB++ xudt asset's xudtTypeArgs xudtTypeArgs: '0x1ba116c119d1cfd98a53e9d1a615cf2af2bb87d95515c9d217d367054cfc696b', transferAmount: BigInt(800_0000_0000), }); diff --git a/examples/rgbpp/xudt/2-btc-transfer.ts b/examples/rgbpp/xudt/2-btc-transfer.ts index 0fdd274a..f0ccf195 100644 --- a/examples/rgbpp/xudt/2-btc-transfer.ts +++ b/examples/rgbpp/xudt/2-btc-transfer.ts @@ -68,11 +68,12 @@ const transfer = async ({ rgbppLockArgsList, toBtcAddress, xudtTypeArgs, transfe } }; -// Use your real BTC UTXO information on the BTC Testnet +// Please use your real BTC UTXO information on the BTC Testnet // rgbppLockArgs: outIndexU32 + btcTxId transfer({ rgbppLockArgsList: [buildRgbppLockArgs(1, '64252b582aea1249ed969a20385fae48bba35bf1ab9b3df3b0fcddc754ccf592')], toBtcAddress: 'tb1qvt7p9g6mw70sealdewtfp0sekquxuru6j3gwmt', + // Please use your own RGB++ xudt asset's xudtTypeArgs xudtTypeArgs: '0x1ba116c119d1cfd98a53e9d1a615cf2af2bb87d95515c9d217d367054cfc696b', transferAmount: BigInt(800_0000_0000), }); diff --git a/examples/rgbpp/xudt/3-btc-leap-ckb.ts b/examples/rgbpp/xudt/3-btc-leap-ckb.ts index 0f54b137..c038b08c 100644 --- a/examples/rgbpp/xudt/3-btc-leap-ckb.ts +++ b/examples/rgbpp/xudt/3-btc-leap-ckb.ts @@ -69,10 +69,12 @@ const leapFromBtcToCKB = async ({ rgbppLockArgsList, toCkbAddress, xudtTypeArgs, } }; +// Please use your real BTC UTXO information on the BTC Testnet // rgbppLockArgs: outIndexU32 + btcTxId leapFromBtcToCKB({ rgbppLockArgsList: [buildRgbppLockArgs(1, '6edd4b9327506fab09fb9a0f5e5f35136a6a94bd4c9dd79af04921618fa6c800')], toCkbAddress: 'ckt1qrfrwcdnvssswdwpn3s9v8fp87emat306ctjwsm3nmlkjg8qyza2cqgqq9kxr7vy7yknezj0vj0xptx6thk6pwyr0sxamv6q', + // Please use your own RGB++ xudt asset's xudtTypeArgs xudtTypeArgs: '0x1ba116c119d1cfd98a53e9d1a615cf2af2bb87d95515c9d217d367054cfc696b', transferAmount: BigInt(800_0000_0000), }); diff --git a/examples/rgbpp/xudt/launch/1-prepare-launch.ts b/examples/rgbpp/xudt/launch/1-prepare-launch.ts index dfd73a92..d3ea1a5b 100644 --- a/examples/rgbpp/xudt/launch/1-prepare-launch.ts +++ b/examples/rgbpp/xudt/launch/1-prepare-launch.ts @@ -81,6 +81,7 @@ const prepareLaunchCell = async ({ console.info(`Launch cell has been created and the tx hash ${txHash}`); }; +// Please use your real BTC UTXO information on the BTC Testnet prepareLaunchCell({ outIndex: 1, btcTxId: '6259ea7852e294afbd2aaf9ccd5c9c1f95087b0b08ba7e47ae35ce31170732bc', diff --git a/examples/rgbpp/xudt/launch/2-launch-rgbpp.ts b/examples/rgbpp/xudt/launch/2-launch-rgbpp.ts index d7708bcf..1d4034b1 100644 --- a/examples/rgbpp/xudt/launch/2-launch-rgbpp.ts +++ b/examples/rgbpp/xudt/launch/2-launch-rgbpp.ts @@ -16,6 +16,8 @@ interface Params { launchAmount: bigint; rgbppTokenInfo: RgbppTokenInfo; } + +// Warning: Before runing this file, please run 2-prepare-launch.ts const launchRgppAsset = async ({ ownerRgbppLockArgs, launchAmount, rgbppTokenInfo }: Params) => { const ckbVirtualTxResult = await genRgbppLaunchCkbVirtualTx({ collector, @@ -28,7 +30,7 @@ const launchRgppAsset = async ({ ownerRgbppLockArgs, launchAmount, rgbppTokenInf // Save ckbVirtualTxResult saveCkbVirtualTxResult(ckbVirtualTxResult, '2-launch-rgbpp'); - const { commitment, ckbRawTx } = ckbVirtualTxResult; + const { commitment, ckbRawTx, needPaymasterCell } = ckbVirtualTxResult; console.log('RGB++ Asset type script args: ', ckbRawTx.outputs[0].type?.args); @@ -37,6 +39,7 @@ const launchRgppAsset = async ({ ownerRgbppLockArgs, launchAmount, rgbppTokenInf ckbVirtualTx: ckbRawTx, commitment, tos: [btcAddress!], + needPaymaster: needPaymasterCell, ckbCollector: collector, from: btcAddress!, source: btcDataSource, @@ -73,7 +76,7 @@ const launchRgppAsset = async ({ ownerRgbppLockArgs, launchAmount, rgbppTokenInf }, 30 * 1000); }; -// Use your real BTC UTXO information on the BTC Testnet +// Please use your real BTC UTXO information on the BTC Testnet which should be same as the 1-prepare-launch.ts // rgbppLockArgs: outIndexU32 + btcTxId launchRgppAsset({ ownerRgbppLockArgs: buildRgbppLockArgs(1, '6259ea7852e294afbd2aaf9ccd5c9c1f95087b0b08ba7e47ae35ce31170732bc'), diff --git a/examples/rgbpp/xudt/launch/3-distribute-rgbpp.ts b/examples/rgbpp/xudt/launch/3-distribute-rgbpp.ts index e9c25300..3b6b26d5 100644 --- a/examples/rgbpp/xudt/launch/3-distribute-rgbpp.ts +++ b/examples/rgbpp/xudt/launch/3-distribute-rgbpp.ts @@ -28,6 +28,8 @@ interface Params { receivers: RgbppBtcAddressReceiver[]; xudtTypeArgs: string; } + +// Warning: Before runing this file for the first time, please run 2-launch-rgbpp.ts const distributeRgbppAssetOnBtc = async ({ rgbppLockArgsList, receivers, xudtTypeArgs }: Params) => { // Warning: Please replace with your real xUDT type script here const xudtType: CKBComponents.Script = { @@ -47,7 +49,7 @@ const distributeRgbppAssetOnBtc = async ({ rgbppLockArgsList, receivers, xudtTyp // Save ckbVirtualTxResult saveCkbVirtualTxResult(ckbVirtualTxResult, '3-distribute-rgbpp'); - const { commitment, ckbRawTx, sumInputsCapacity, rgbppChangeOutIndex } = ckbVirtualTxResult; + const { commitment, ckbRawTx, sumInputsCapacity, rgbppChangeOutIndex, needPaymasterCell } = ckbVirtualTxResult; // The first output utxo is OP_RETURN // Rgbpp change utxo position depends on the number of distributions, if 50 addresses are distributed, then the change utxo position is 51 @@ -58,6 +60,7 @@ const distributeRgbppAssetOnBtc = async ({ rgbppLockArgsList, receivers, xudtTyp ckbVirtualTx: ckbRawTx, commitment, tos: receivers.map((receiver) => receiver.toBtcAddress), + needPaymaster: needPaymasterCell, ckbCollector: collector, from: btcAddress!, source: btcDataSource, @@ -103,11 +106,12 @@ const distributeRgbppAssetOnBtc = async ({ rgbppLockArgsList, receivers, xudtTyp }, 20 * 1000); }; -// Use your real BTC UTXO information on the BTC Testnet +// Please use your real BTC UTXO information on the BTC Testnet // rgbppLockArgs: outIndexU32 + btcTxId distributeRgbppAssetOnBtc({ // Warning: If rgbpp assets are distributed continuously, then the position of the current rgbpp asset utxo depends on the position of the previous change utxo distributed rgbppLockArgsList: [buildRgbppLockArgs(2, '012bfee9c1e8a6e9e272b63ff54d5138efe910cc7aac413221cb3634ea176866')], + // The xudtTypeArgs comes from the logs "RGB++ Asset type script args" of 2-launch-rgbpp.ts xudtTypeArgs: '0x4c1ecf2f14edae73b76ccf115ecfa40ba68ee315c96bd4fcfd771c2fb4c69e8f', receivers: [ { diff --git a/examples/rgbpp/xudt/local/2-btc-transfer.ts b/examples/rgbpp/xudt/local/2-btc-transfer.ts index 92a4185d..e8cd4376 100644 --- a/examples/rgbpp/xudt/local/2-btc-transfer.ts +++ b/examples/rgbpp/xudt/local/2-btc-transfer.ts @@ -81,11 +81,12 @@ const transfer = async ({ rgbppLockArgsList, toBtcAddress, xudtTypeArgs, transfe }, 30 * 1000); }; -// Use your real BTC UTXO information on the BTC Testnet +// Please use your real BTC UTXO information on the BTC Testnet // rgbppLockArgs: outIndexU32 + btcTxId transfer({ rgbppLockArgsList: [buildRgbppLockArgs(1, '70b250e2a3cc7a33b47f7a4e94e41e1ee2501ce73b393d824db1dd4c872c5348')], toBtcAddress: 'tb1qvt7p9g6mw70sealdewtfp0sekquxuru6j3gwmt', + // Please use your own RGB++ asset's xudtTypeArgs xudtTypeArgs: '0x1ba116c119d1cfd98a53e9d1a615cf2af2bb87d95515c9d217d367054cfc696b', transferAmount: BigInt(800_0000_0000), }); diff --git a/examples/rgbpp/xudt/local/3-btc-leap-ckb.ts b/examples/rgbpp/xudt/local/3-btc-leap-ckb.ts index d42615a8..b3c329a7 100644 --- a/examples/rgbpp/xudt/local/3-btc-leap-ckb.ts +++ b/examples/rgbpp/xudt/local/3-btc-leap-ckb.ts @@ -84,10 +84,12 @@ const leapFromBtcToCkb = async ({ rgbppLockArgsList, toCkbAddress, xudtTypeArgs, }, 30 * 1000); }; +// Please use your real BTC UTXO information on the BTC Testnet // rgbppLockArgs: outIndexU32 + btcTxId leapFromBtcToCkb({ rgbppLockArgsList: [buildRgbppLockArgs(1, '24e622419156dd3a277a90bcbb40c7117462a18d5329dd1ada320ca8bdfba715')], toCkbAddress: 'ckt1qrfrwcdnvssswdwpn3s9v8fp87emat306ctjwsm3nmlkjg8qyza2cqgqq9kxr7vy7yknezj0vj0xptx6thk6pwyr0sxamv6q', + // Please use your own RGB++ xudt asset's xudtTypeArgs xudtTypeArgs: '0x1ba116c119d1cfd98a53e9d1a615cf2af2bb87d95515c9d217d367054cfc696b', transferAmount: BigInt(800_0000_0000), }); diff --git a/examples/xudt-on-ckb/1-issue-xudt.ts b/examples/xudt-on-ckb/1-issue-xudt.ts index 09d9c4b7..230a55f8 100644 --- a/examples/xudt-on-ckb/1-issue-xudt.ts +++ b/examples/xudt-on-ckb/1-issue-xudt.ts @@ -11,12 +11,11 @@ import { getUniqueTypeScript, u128ToLe, encodeRgbppTokenInfo, - getXudtDep, - getUniqueTypeDep, SECP256K1_WITNESS_LOCK_SIZE, calculateTransactionFee, generateUniqueTypeArgs, calculateXudtTokenInfoCellCapacity, + fetchTypeIdCellDeps, } from 'rgbpp/ckb'; import { CKB_PRIVATE_KEY, ckbAddress, collector, isMainnet } from './env'; @@ -77,7 +76,10 @@ const issueXudt = async ({ xudtTotalAmount, tokenInfo }: { xudtTotalAmount: bigi const emptyWitness = { lock: '', inputType: '', outputType: '' }; const witnesses = inputs.map((_, index) => (index === 0 ? emptyWitness : '0x')); - const cellDeps = [getSecp256k1CellDep(isMainnet), getUniqueTypeDep(isMainnet), getXudtDep(isMainnet)]; + const cellDeps = [ + getSecp256k1CellDep(isMainnet), + ...(await fetchTypeIdCellDeps(isMainnet, { xudt: true, unique: true })), + ]; const unsignedTx = { version: '0x0', diff --git a/examples/xudt-on-ckb/2-transfer-xudt.ts b/examples/xudt-on-ckb/2-transfer-xudt.ts index e9f8ede4..c5d01e0a 100644 --- a/examples/xudt-on-ckb/2-transfer-xudt.ts +++ b/examples/xudt-on-ckb/2-transfer-xudt.ts @@ -8,11 +8,10 @@ import { MIN_CAPACITY, append0x, u128ToLe, - getXudtDep, - getUniqueTypeDep, SECP256K1_WITNESS_LOCK_SIZE, calculateTransactionFee, NoXudtLiveCellError, + fetchTypeIdCellDeps, } from 'rgbpp/ckb'; import { CKB_PRIVATE_KEY, ckbAddress, collector, isMainnet } from './env'; @@ -104,7 +103,7 @@ const transferXudt = async ({ xudtType, receivers }: XudtTransferParams) => { const emptyWitness = { lock: '', inputType: '', outputType: '' }; const witnesses = inputs.map((_, index) => (index === 0 ? emptyWitness : '0x')); - const cellDeps = [getSecp256k1CellDep(isMainnet), getUniqueTypeDep(isMainnet), getXudtDep(isMainnet)]; + const cellDeps = [getSecp256k1CellDep(isMainnet), ...(await fetchTypeIdCellDeps(isMainnet, { xudt: true }))]; const unsignedTx = { version: '0x0', diff --git a/examples/xudt-on-ckb/env.ts b/examples/xudt-on-ckb/env.ts index 7cf52cd7..4367944f 100644 --- a/examples/xudt-on-ckb/env.ts +++ b/examples/xudt-on-ckb/env.ts @@ -1,4 +1,10 @@ -import { AddressPrefix, privateKeyToAddress } from '@nervosnetwork/ckb-sdk-utils'; +import { + blake160, + bytesToHex, + privateKeyToPublicKey, + scriptToAddress, + systemScripts, +} from '@nervosnetwork/ckb-sdk-utils'; import { Collector } from 'rgbpp/ckb'; import dotenv from 'dotenv'; @@ -11,6 +17,8 @@ export const collector = new Collector({ ckbIndexerUrl: process.env.CKB_INDEXER_URL!, }); export const CKB_PRIVATE_KEY = process.env.CKB_SECP256K1_PRIVATE_KEY!; -export const ckbAddress = privateKeyToAddress(CKB_PRIVATE_KEY, { - prefix: isMainnet ? AddressPrefix.Mainnet : AddressPrefix.Testnet, -}); +const secp256k1Lock: CKBComponents.Script = { + ...systemScripts.SECP256K1_BLAKE160, + args: bytesToHex(blake160(privateKeyToPublicKey(CKB_PRIVATE_KEY))), +}; +export const ckbAddress = scriptToAddress(secp256k1Lock, isMainnet); diff --git a/examples/xudt-on-ckb/package.json b/examples/xudt-on-ckb/package.json index e44b806e..792a1a7d 100644 --- a/examples/xudt-on-ckb/package.json +++ b/examples/xudt-on-ckb/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@nervosnetwork/ckb-sdk-utils": "^0.109.1", - "rgbpp": "0.1.0" + "rgbpp": "workspace:*" }, "devDependencies": { "dotenv": "^16.4.5", diff --git a/packages/btc/CHANGELOG.md b/packages/btc/CHANGELOG.md index aff4d077..ca3d4ca0 100644 --- a/packages/btc/CHANGELOG.md +++ b/packages/btc/CHANGELOG.md @@ -1,5 +1,26 @@ # @rgbpp-sdk/btc +## v0.3.0 + +### Minor Changes + +- [#200](https://github.com/ckb-cell/rgbpp-sdk/pull/200): Add p-limit and batch queries in the sendRgbppUtxos() and TxBuilder.validateInputs() to improve construction time ([@ShookLyngs](https://github.com/ShookLyngs)) + +- [#208](https://github.com/ckb-cell/rgbpp-sdk/pull/208): Adapt btc-assets-api#154, adding new props and return values to the /balance and /unspent APIs ([@ShookLyngs](https://github.com/ShookLyngs)) + +- Add `available_satoshi` and `total_satoshi` to the BtcAssetsApi.getBtcBalance() API +- Add `only_non_rgbpp_utxos` to the props of the BtcAssetsApi.getBtcUtxos() API +- Remove `service.getRgbppAssetsByBtcUtxo()` lines from the DataCollector.collectSatoshi() +- Remove `hasRgbppAssets` related variables/function from the DataCache + +- [#199](https://github.com/ckb-cell/rgbpp-sdk/pull/199): Add "needPaymaster" option to the sendRgbppUtxos() API to allow manually specifying whether a paymaster output is required ([@ShookLyngs](https://github.com/ShookLyngs)) + +### Patch Changes + +- Updated dependencies [[`d2d963c`](https://github.com/ckb-cell/rgbpp-sdk/commit/d2d963c8f40d0316491df5bdccca4eba7a33977c), [`4c77e69`](https://github.com/ckb-cell/rgbpp-sdk/commit/4c77e69cadc8ce3d24f631c1348dcd7141fb1099), [`4f05b1b`](https://github.com/ckb-cell/rgbpp-sdk/commit/4f05b1bba898b7acb58bdf20ae275164ad94523b)]: + - @rgbpp-sdk/ckb@0.3.0 + - @rgbpp-sdk/service@0.3.0 + ## v0.2.0 ### Minor Changes diff --git a/packages/btc/package.json b/packages/btc/package.json index 5140f506..bfa9e513 100644 --- a/packages/btc/package.json +++ b/packages/btc/package.json @@ -1,6 +1,6 @@ { "name": "@rgbpp-sdk/btc", - "version": "0.2.0", + "version": "0.3.0", "scripts": { "test": "vitest", "build": "tsc -p tsconfig.build.json", @@ -24,7 +24,8 @@ "bip32": "^4.0.0", "bitcoinjs-lib": "^6.1.5", "ecpair": "^2.1.0", - "lodash": "^4.17.21" + "lodash": "^4.17.21", + "p-limit": "^3.1.0" }, "devDependencies": { "@types/lodash": "^4.17.0", diff --git a/packages/btc/src/api/sendRgbppUtxos.ts b/packages/btc/src/api/sendRgbppUtxos.ts index 0bc23cc6..22a52516 100644 --- a/packages/btc/src/api/sendRgbppUtxos.ts +++ b/packages/btc/src/api/sendRgbppUtxos.ts @@ -9,12 +9,14 @@ import { InitOutput, TxAddressOutput, TxBuilder } from '../transaction/build'; import { networkTypeToConfig } from '../preset/config'; import { unpackRgbppLockArgs } from '../ckb/molecule'; import { createSendUtxosBuilder } from './sendUtxos'; +import { limitPromiseBatchSize } from '../utils'; export interface SendRgbppUtxosProps { ckbVirtualTx: CKBComponents.RawTransaction; commitment: string; tos?: string[]; paymaster?: TxAddressOutput; + needPaymaster?: boolean; ckbCollector: Collector; rgbppMinUtxoSatoshi?: number; @@ -46,12 +48,36 @@ export async function createSendRgbppUtxosBuilder(props: SendRgbppUtxosProps): P const config = networkTypeToConfig(props.source.networkType); const isCkbMainnet = props.source.networkType === NetworkType.MAINNET; + // Batch querying live cells from CkbCollector + const ckbLiveCells = await Promise.all( + ckbVirtualTx.inputs.map((ckbInput) => { + return limitPromiseBatchSize(async () => { + const ckbLiveCell = await props.ckbCollector.getLiveCell(ckbInput.previousOutput!); + const isRgbppLock = isRgbppLockCell(ckbLiveCell.output, isCkbMainnet); + const lockArgs = isRgbppLock ? unpackRgbppLockArgs(ckbLiveCell.output.lock.args) : undefined; + return { + ckbLiveCell, + isRgbppLock, + lockArgs, + }; + }); + }), + ); + + // Batch querying UTXO from BtcAssetsApi + const btcUtxos = await Promise.all( + ckbLiveCells.map(({ ckbLiveCell, isRgbppLock }) => { + if (isRgbppLock) { + const args = unpackRgbppLockArgs(ckbLiveCell.output.lock.args); + return limitPromiseBatchSize(() => props.source.getUtxo(args.btcTxid, args.outIndex, props.onlyConfirmedUtxos)); + } + return undefined; + }), + ); + // Handle and check inputs for (let i = 0; i < ckbVirtualTx.inputs.length; i++) { - const ckbInput = ckbVirtualTx.inputs[i]; - - const ckbLiveCell = await props.ckbCollector.getLiveCell(ckbInput.previousOutput!); - const isRgbppLock = isRgbppLockCell(ckbLiveCell.output, isCkbMainnet); + const { lockArgs, isRgbppLock } = ckbLiveCells[i]; // If input.lock == RgbppLock, add to inputs if: // 1. input.lock.args can be unpacked to RgbppLockArgs @@ -59,8 +85,8 @@ export async function createSendRgbppUtxosBuilder(props: SendRgbppUtxosProps): P // 3. utxo.scriptPk == addressToScriptPk(props.from) // 4. utxo is not duplicated in the inputs if (isRgbppLock) { - const args = unpackRgbppLockArgs(ckbLiveCell.output.lock.args); - const utxo = await props.source.getUtxo(args.btcTxid, args.outIndex, props.onlyConfirmedUtxos); + const args = lockArgs!; + const utxo = btcUtxos[i]; if (!utxo) { throw TxBuildError.withComment(ErrorCodes.CANNOT_FIND_UTXO, `hash: ${args.btcTxid}, index: ${args.outIndex}`); } @@ -174,8 +200,14 @@ async function getMergedBtcOutputs(btcOutputs: InitOutput[], props: SendRgbppUtx // Add paymaster output, only if paymaster address exists and needed const paymaster = defaultPaymaster ?? props.paymaster; - const isCkbTxCapacitySufficient = await checkCkbTxInputsCapacitySufficient(props.ckbVirtualTx, props.ckbCollector); - if (paymaster && !isCkbTxCapacitySufficient) { + const isNeedPaymasterOutput = await (async () => { + if (props.needPaymaster !== undefined) { + return props.needPaymaster; + } + const isInputsSufficient = await checkCkbTxInputsCapacitySufficient(props.ckbVirtualTx, props.ckbCollector); + return !isInputsSufficient; + })(); + if (paymaster && isNeedPaymasterOutput) { merged.push({ ...paymaster, fixed: true, diff --git a/packages/btc/src/query/cache.ts b/packages/btc/src/query/cache.ts index 046b2c8e..3ac3e0e0 100644 --- a/packages/btc/src/query/cache.ts +++ b/packages/btc/src/query/cache.ts @@ -2,11 +2,9 @@ import { Utxo } from '../transaction/utxo'; export class DataCache { private utxos: Map; // Map - private hasRgbppAssets: Map; // Map<`{txid}:{vout}`, HasAssets> constructor() { this.utxos = new Map(); - this.hasRgbppAssets = new Map(); } setUtxos(key: string, utxos: Utxo[]) { @@ -32,31 +30,4 @@ export class DataCache { return utxos; } - - setHasRgbppAssets(key: string, hasAssets: boolean) { - this.hasRgbppAssets.set(key, hasAssets); - } - getHasRgbppAssets(key: string): boolean | undefined { - return this.hasRgbppAssets.get(key); - } - cleanHasRgbppAssets(key: string) { - if (this.hasRgbppAssets.has(key)) { - this.hasRgbppAssets.delete(key); - } - } - async optionalCacheHasRgbppAssets(props: { - key?: string; - getter: () => Promise | boolean; - }): Promise { - if (props.key && this.hasRgbppAssets.has(props.key)) { - return this.getHasRgbppAssets(props.key) as boolean; - } - - const hasRgbppAssets = await props.getter(); - if (props.key) { - this.setHasRgbppAssets(props.key, hasRgbppAssets); - } - - return hasRgbppAssets; - } } diff --git a/packages/btc/src/query/source.ts b/packages/btc/src/query/source.ts index d7dcff0a..e368b29b 100644 --- a/packages/btc/src/query/source.ts +++ b/packages/btc/src/query/source.ts @@ -116,32 +116,24 @@ export class DataSource { satoshi: number; exceedSatoshi: number; }> { - const { - address, - targetAmount, - minUtxoSatoshi, - onlyConfirmedUtxos, - onlyNonRgbppUtxos, - noAssetsApiCache, - internalCacheKey, - allowInsufficient = false, - excludeUtxos = [], - } = props; + const allowInsufficient = props.allowInsufficient ?? false; + const excludeUtxos = props.excludeUtxos ?? []; const utxos = await this.cache.optionalCacheUtxos({ - key: internalCacheKey, + key: props.internalCacheKey, getter: () => - this.getUtxos(address, { - only_confirmed: onlyConfirmedUtxos, - min_satoshi: minUtxoSatoshi, - no_cache: noAssetsApiCache, + this.getUtxos(props.address, { + only_non_rgbpp_utxos: props.onlyNonRgbppUtxos, + only_confirmed: props.onlyConfirmedUtxos, + min_satoshi: props.minUtxoSatoshi, + no_cache: props.noAssetsApiCache, }), }); const collected = []; let collectedAmount = 0; for (const utxo of utxos) { - if (collectedAmount >= targetAmount) { + if (collectedAmount >= props.targetAmount) { break; } if (excludeUtxos.length > 0) { @@ -152,33 +144,21 @@ export class DataSource { continue; } } - if (onlyNonRgbppUtxos) { - const hasRgbppAssets = await this.cache.optionalCacheHasRgbppAssets({ - key: `${utxo.txid}:${utxo.vout}`, - getter: async () => { - const ckbRgbppAssets = await this.service.getRgbppAssetsByBtcUtxo(utxo.txid, utxo.vout); - return Array.isArray(ckbRgbppAssets) && ckbRgbppAssets.length > 0; - }, - }); - if (hasRgbppAssets) { - continue; - } - } collected.push(utxo); collectedAmount += utxo.value; } - if (!allowInsufficient && collectedAmount < targetAmount) { + if (!allowInsufficient && collectedAmount < props.targetAmount) { throw TxBuildError.withComment( ErrorCodes.INSUFFICIENT_UTXO, - `expected: ${targetAmount}, actual: ${collectedAmount}`, + `expected: ${props.targetAmount}, actual: ${collectedAmount}`, ); } return { utxos: collected, satoshi: collectedAmount, - exceedSatoshi: collectedAmount - targetAmount, + exceedSatoshi: collectedAmount - props.targetAmount, }; } diff --git a/packages/btc/src/transaction/build.ts b/packages/btc/src/transaction/build.ts index 3c54b5aa..7d34ab7b 100644 --- a/packages/btc/src/transaction/build.ts +++ b/packages/btc/src/transaction/build.ts @@ -6,6 +6,7 @@ import { NetworkType, RgbppBtcConfig } from '../preset/types'; import { AddressType, addressToScriptPublicKeyHex, getAddressType, isSupportedFromAddress } from '../address'; import { dataToOpReturnScriptPubkey, isOpReturnScriptPubkey } from './embed'; import { networkTypeToConfig } from '../preset/config'; +import { limitPromiseBatchSize } from '../utils'; import { Utxo, utxoToInput } from './utxo'; import { FeeEstimator } from './fee'; @@ -86,15 +87,19 @@ export class TxBuilder { } async validateInputs() { - for (const input of this.inputs) { - const transactionConfirmed = await this.source.isTransactionConfirmed(input.data.hash); - if (!transactionConfirmed) { - throw TxBuildError.withComment( - ErrorCodes.UNCONFIRMED_UTXO, - `hash: ${input.data.hash}, index: ${input.data.index}`, - ); - } - } + await Promise.all( + this.inputs.map(async (input) => { + return limitPromiseBatchSize(async () => { + const transactionConfirmed = await this.source.isTransactionConfirmed(input.data.hash); + if (!transactionConfirmed) { + throw TxBuildError.withComment( + ErrorCodes.UNCONFIRMED_UTXO, + `hash: ${input.data.hash}, index: ${input.data.index}`, + ); + } + }); + }), + ); } addOutput(output: InitOutput) { diff --git a/packages/btc/src/utils.ts b/packages/btc/src/utils.ts index bc63977f..22438d5d 100644 --- a/packages/btc/src/utils.ts +++ b/packages/btc/src/utils.ts @@ -1,3 +1,4 @@ +import limitPromiseConcurrency from 'p-limit'; import { bitcoin, ecc, ECPair } from './bitcoin'; import { bytes } from '@ckb-lumos/codec'; @@ -75,3 +76,15 @@ export function transactionToHex(tx: bitcoin.Transaction, withWitness?: boolean) const buffer: Buffer = tx['__toBuffer'](undefined, undefined, withWitness ?? false); return buffer.toString('hex'); } + +/** + * Limits the batch size of promises when querying with Promise.all(). + * @example + * await Promise.all([ + * limitPromiseBatchSize(() => asyncDoSomething()), + * limitPromiseBatchSize(() => asyncDoSomething()), + * limitPromiseBatchSize(() => asyncDoSomething()), + * ... + * ]); + */ +export const limitPromiseBatchSize = limitPromiseConcurrency(10); diff --git a/packages/btc/tests/DataSource.test.ts b/packages/btc/tests/DataSource.test.ts index fb001668..e2c9f90a 100644 --- a/packages/btc/tests/DataSource.test.ts +++ b/packages/btc/tests/DataSource.test.ts @@ -19,35 +19,39 @@ describe('DataSource', { retry: 3 }, () => { source.getUtxo('70b250e2a3cc7a33b47f7a4e94e41e1ee2501ce73b393d824db1dd4c872c5348', 0), ).rejects.toHaveProperty('code', ErrorCodes.UNSPENDABLE_OUTPUT); }); - it('Get UTXO[] via collectSatoshi()', async () => { - const address = 'tb1qnxdtut9vpycmpnjpp77rmx33mfxsr86dl3ce6a'; - const nonRgbppSatoshi = 2546; - const totalSatoshi = 3092; - const nonRgbppUtxo = 2; + describe('collectSatoshi()', () => { + const address = 'tb1qn5kgn70tpwsw4nuxrch8l7qa9nqn4fahxgzjg6'; + const totalSatoshi = 546 + 2000 + 1500; + const nonRgbppSatoshi = 1500; + const nonRgbppUtxo = 1; const totalUtxo = 3; - const c1 = await source.collectSatoshi({ - address, - targetAmount: totalSatoshi, - onlyNonRgbppUtxos: false, - }); - expect(c1.utxos).toHaveLength(totalUtxo); - expect(c1.satoshi).toEqual(totalSatoshi); - - const c2 = await source.collectSatoshi({ - address, - targetAmount: nonRgbppSatoshi, - onlyNonRgbppUtxos: true, - }); - expect(c2.utxos).toHaveLength(nonRgbppUtxo); - expect(c2.satoshi).toEqual(nonRgbppSatoshi); - - await expect(() => - source.collectSatoshi({ + it('onlyNonRgbppUtxos = false', async () => { + const c = await source.collectSatoshi({ address, targetAmount: totalSatoshi, + onlyNonRgbppUtxos: false, + }); + expect(c.utxos).toHaveLength(totalUtxo); + expect(c.satoshi).toEqual(totalSatoshi); + }); + it('onlyNonRgbppUtxos = true', async () => { + const c = await source.collectSatoshi({ + address, + targetAmount: nonRgbppSatoshi, onlyNonRgbppUtxos: true, - }), - ).rejects.toThrowError(); + }); + expect(c.utxos).toHaveLength(nonRgbppUtxo); + expect(c.satoshi).toEqual(nonRgbppSatoshi); + }); + it('Try onlyNonRgbppUtxos = true and targetAmount = totalSatoshi', async () => { + await expect(() => + source.collectSatoshi({ + address, + targetAmount: totalSatoshi, + onlyNonRgbppUtxos: true, + }), + ).rejects.toThrowError(); + }); }); }); diff --git a/packages/ckb/CHANGELOG.md b/packages/ckb/CHANGELOG.md index 93b550d7..1b01af94 100644 --- a/packages/ckb/CHANGELOG.md +++ b/packages/ckb/CHANGELOG.md @@ -1,5 +1,20 @@ # @rgbpp-sdk/ckb +## v0.3.0 + +### Minor Changes + +- [#197](https://github.com/ckb-cell/rgbpp-sdk/pull/197): feat: Return needPaymasterCell for RGB++ ckb cirtual tx ([@duanyytop](https://github.com/duanyytop)) + +- [#191](https://github.com/ckb-cell/rgbpp-sdk/pull/191): Dynamic fetching cell deps deployed by TypeID ([@duanyytop](https://github.com/duanyytop)) + +### Patch Changes + +- [#212](https://github.com/ckb-cell/rgbpp-sdk/pull/212): Fix the XUDT cell data unpacking logic to load only the first 16 bytes ([@ShookLyngs](https://github.com/ShookLyngs)) + +- Updated dependencies [[`4f05b1b`](https://github.com/ckb-cell/rgbpp-sdk/commit/4f05b1bba898b7acb58bdf20ae275164ad94523b)]: + - @rgbpp-sdk/service@0.3.0 + ## v0.2.0 ### Minor Changes diff --git a/packages/ckb/package.json b/packages/ckb/package.json index 0971879f..862b1aad 100644 --- a/packages/ckb/package.json +++ b/packages/ckb/package.json @@ -1,6 +1,6 @@ { "name": "@rgbpp-sdk/ckb", - "version": "0.2.0", + "version": "0.3.0", "scripts": { "test": "vitest", "build": "tsc -p tsconfig.build.json", diff --git a/packages/ckb/src/collector/index.ts b/packages/ckb/src/collector/index.ts index 032e2290..bd3ace31 100644 --- a/packages/ckb/src/collector/index.ts +++ b/packages/ckb/src/collector/index.ts @@ -4,7 +4,7 @@ import { toCamelcase } from '../utils/case-parser'; import { CollectConfig, CollectResult, CollectUdtResult, IndexerCell } from '../types/collector'; import { MIN_CAPACITY } from '../constants'; import { CapacityNotEnoughError, IndexerError, UdtAmountNotEnoughError } from '../error'; -import { isRgbppLockCellIgnoreChain, leToU128 } from '../utils'; +import { isRgbppLockCellIgnoreChain, leToU128, remove0x } from '../utils'; import { Hex } from '../types'; interface IndexerScript { @@ -148,7 +148,9 @@ export class Collector { since: '0x0', }); sumInputsCapacity = sumInputsCapacity + BigInt(cell.output.capacity); - sumAmount += leToU128(cell.outputData); + // XUDT cell.data = + // Ref: https://blog.cryptape.com/enhance-sudts-programmability-with-xudt#heading-xudt-cell + sumAmount += leToU128(remove0x(cell.outputData).slice(0, 32)); if (sumAmount >= needAmount && !isRgbppLock) { break; } @@ -159,9 +161,9 @@ export class Collector { return { inputs, sumInputsCapacity, sumAmount }; } - async getLiveCell(outPoint: CKBComponents.OutPoint): Promise { + async getLiveCell(outPoint: CKBComponents.OutPoint, withData = true): Promise { const ckb = new CKB(this.ckbNodeUrl); - const { cell } = await ckb.rpc.getLiveCell(outPoint, true); + const { cell } = await ckb.rpc.getLiveCell(outPoint, withData); return cell; } } diff --git a/packages/ckb/src/rgbpp/btc-jump-ckb.ts b/packages/ckb/src/rgbpp/btc-jump-ckb.ts index 6e3de3e4..6bafad84 100644 --- a/packages/ckb/src/rgbpp/btc-jump-ckb.ts +++ b/packages/ckb/src/rgbpp/btc-jump-ckb.ts @@ -6,6 +6,7 @@ import { calculateRgbppCellCapacity, calculateTransactionFee, deduplicateList, + fetchTypeIdCellDeps, isLockArgsSizeExceeded, isScriptEqual, isUDTTypeSupported, @@ -23,13 +24,7 @@ import { isRgbppCapacitySufficientForChange, } from '../utils/rgbpp'; import { Hex, IndexerCell } from '../types'; -import { - RGBPP_WITNESS_PLACEHOLDER, - getRgbppLockConfigDep, - getRgbppLockDep, - getSecp256k1CellDep, - getXudtDep, -} from '../constants'; +import { RGBPP_WITNESS_PLACEHOLDER, getSecp256k1CellDep } from '../constants'; import { addressToScript, getTransactionSize } from '@nervosnetwork/ckb-sdk-utils'; /** @@ -139,7 +134,7 @@ export const genBtcJumpCkbVirtualTx = async ({ outputsData.push(otherRgbppCell.outputData); } - const cellDeps = [getRgbppLockDep(isMainnet), getXudtDep(isMainnet), getRgbppLockConfigDep(isMainnet)]; + const cellDeps = await fetchTypeIdCellDeps(isMainnet, { rgbpp: true, xudt: true }); if (needPaymasterCell) { cellDeps.push(getSecp256k1CellDep(isMainnet)); } diff --git a/packages/ckb/src/rgbpp/btc-time.ts b/packages/ckb/src/rgbpp/btc-time.ts index fd0ded9c..c7beecc5 100644 --- a/packages/ckb/src/rgbpp/btc-time.ts +++ b/packages/ckb/src/rgbpp/btc-time.ts @@ -10,11 +10,8 @@ import { import { BTC_JUMP_CONFIRMATION_BLOCKS, SECP256K1_WITNESS_LOCK_SIZE, - getBtcTimeLockConfigDep, - getBtcTimeLockDep, getBtcTimeLockScript, getSecp256k1CellDep, - getXudtDep, } from '../constants'; import { BTCTimeUnlock } from '../schemas/generated/rgbpp'; import { BtcTimeCellStatusParams, BtcTimeCellsParams, Hex, SignBtcTimeCellsTxParams } from '../types'; @@ -23,6 +20,7 @@ import { btcTxIdFromBtcTimeLockArgs, calculateTransactionFee, compareInputs, + fetchTypeIdCellDeps, genBtcTimeLockArgs, lockScriptFromBtcTimeLockArgs, transformSpvProof, @@ -62,11 +60,7 @@ export const buildBtcTimeCellsSpentTx = async ({ const outputsData = sortedBtcTimeCells.map((cell) => cell.outputData); - const cellDeps: CKBComponents.CellDep[] = [ - getBtcTimeLockDep(isMainnet), - getXudtDep(isMainnet), - getBtcTimeLockConfigDep(isMainnet), - ]; + const cellDeps: CKBComponents.CellDep[] = await fetchTypeIdCellDeps(isMainnet, { btcTime: true, xudt: true }); const witnesses: Hex[] = []; diff --git a/packages/ckb/src/rgbpp/btc-transfer.ts b/packages/ckb/src/rgbpp/btc-transfer.ts index 4bc38ada..6ff45dbc 100644 --- a/packages/ckb/src/rgbpp/btc-transfer.ts +++ b/packages/ckb/src/rgbpp/btc-transfer.ts @@ -13,6 +13,7 @@ import { calculateRgbppCellCapacity, calculateTransactionFee, deduplicateList, + fetchTypeIdCellDeps, isScriptEqual, isUDTTypeSupported, u128ToLe, @@ -33,11 +34,8 @@ import { MIN_CAPACITY, RGBPP_WITNESS_PLACEHOLDER, SECP256K1_WITNESS_LOCK_SIZE, - getRgbppLockConfigDep, - getRgbppLockDep, getRgbppLockScript, getSecp256k1CellDep, - getXudtDep, } from '../constants'; import { addressToScript, @@ -172,7 +170,7 @@ export const genBtcTransferCkbVirtualTx = async ({ handleNonTargetRgbppCells(outputs.length); } - const cellDeps = [getRgbppLockDep(isMainnet), getXudtDep(isMainnet), getRgbppLockConfigDep(isMainnet)]; + const cellDeps = await fetchTypeIdCellDeps(isMainnet, { rgbpp: true, xudt: true }); if (needPaymasterCell) { cellDeps.push(getSecp256k1CellDep(isMainnet)); } @@ -287,12 +285,9 @@ export const genBtcBatchTransferCkbVirtualTx = async ({ } const cellDeps = [ - getRgbppLockDep(isMainnet), - getXudtDep(isMainnet), - getRgbppLockConfigDep(isMainnet), + ...(await fetchTypeIdCellDeps(isMainnet, { rgbpp: true, xudt: true })), getSecp256k1CellDep(isMainnet), ]; - const witnesses: Hex[] = []; const lockArgsSet: Set = new Set(); for (const cell of rgbppCells) { diff --git a/packages/ckb/src/rgbpp/ckb-jump-btc.ts b/packages/ckb/src/rgbpp/ckb-jump-btc.ts index f8ad980e..ab3f5eb5 100644 --- a/packages/ckb/src/rgbpp/ckb-jump-btc.ts +++ b/packages/ckb/src/rgbpp/ckb-jump-btc.ts @@ -6,11 +6,12 @@ import { calculateRgbppCellCapacity, calculateTransactionFee, calculateUdtCellCapacity, + fetchTypeIdCellDeps, isTypeAssetSupported, u128ToLe, } from '../utils'; import { genRgbppLockScript } from '../utils/rgbpp'; -import { MAX_FEE, MIN_CAPACITY, RGBPP_TX_WITNESS_MAX_SIZE, getXudtDep } from '../constants'; +import { MAX_FEE, MIN_CAPACITY, RGBPP_TX_WITNESS_MAX_SIZE } from '../constants'; import { addressToScript, getTransactionSize } from '@nervosnetwork/ckb-sdk-utils'; /** @@ -98,7 +99,7 @@ export const genCkbJumpBtcVirtualTx = async ({ }); outputsData.push('0x'); - const cellDeps = [getXudtDep(isMainnet)]; + const cellDeps = await fetchTypeIdCellDeps(isMainnet, { xudt: true }); const witnesses = inputs.map(() => '0x'); const ckbRawTx: CKBComponents.RawTransaction = { @@ -206,7 +207,7 @@ export const genCkbBatchJumpBtcVirtualTx = async ({ }); outputsData.push('0x'); - const cellDeps = [getXudtDep(isMainnet)]; + const cellDeps = await fetchTypeIdCellDeps(isMainnet, { xudt: true }); const witnesses = inputs.map(() => '0x'); const ckbRawTx: CKBComponents.RawTransaction = { diff --git a/packages/ckb/src/rgbpp/launch.ts b/packages/ckb/src/rgbpp/launch.ts index a851bfcd..58dc3e15 100644 --- a/packages/ckb/src/rgbpp/launch.ts +++ b/packages/ckb/src/rgbpp/launch.ts @@ -4,6 +4,7 @@ import { append0x, calculateRgbppTokenInfoCellCapacity, calculateTransactionFee, + fetchTypeIdCellDeps, generateUniqueTypeArgs, u128ToLe, } from '../utils'; @@ -19,13 +20,9 @@ import { MAX_FEE, RGBPP_TX_WITNESS_MAX_SIZE, RGBPP_WITNESS_PLACEHOLDER, - getRgbppLockDep, - getXudtDep, getXudtTypeScript, getUniqueTypeScript, - getUniqueTypeDep, UNLOCKABLE_LOCK_SCRIPT, - getRgbppLockConfigDep, } from '../constants'; import { getTransactionSize, scriptToHash } from '@nervosnetwork/ckb-sdk-utils'; @@ -80,12 +77,7 @@ export const genRgbppLaunchCkbVirtualTx = async ({ ]; const outputsData = [append0x(u128ToLe(launchAmount)), encodeRgbppTokenInfo(rgbppTokenInfo)]; - const cellDeps = [ - getRgbppLockDep(isMainnet), - getRgbppLockConfigDep(isMainnet), - getXudtDep(isMainnet), - getUniqueTypeDep(isMainnet), - ]; + const cellDeps = await fetchTypeIdCellDeps(isMainnet, { rgbpp: true, xudt: true, unique: true }); const witnesses: Hex[] = inputs.map((_, index) => (index === 0 ? RGBPP_WITNESS_PLACEHOLDER : '0x')); @@ -114,5 +106,6 @@ export const genRgbppLaunchCkbVirtualTx = async ({ return { ckbRawTx, commitment, + needPaymasterCell: false, }; }; diff --git a/packages/ckb/src/spore/cluster.ts b/packages/ckb/src/spore/cluster.ts index c23ed5e9..224b295c 100644 --- a/packages/ckb/src/spore/cluster.ts +++ b/packages/ckb/src/spore/cluster.ts @@ -1,6 +1,6 @@ import { RgbppCkbVirtualTx } from '../types/rgbpp'; import { packRawClusterData } from '@spore-sdk/core'; -import { append0x, calculateTransactionFee } from '../utils'; +import { append0x, calculateTransactionFee, fetchTypeIdCellDeps } from '../utils'; import { buildPreLockArgs, calculateCommitment, genRgbppLockScript } from '../utils/rgbpp'; import { CreateClusterCkbVirtualTxParams, Hex, SporeVirtualTxResult } from '../types'; import { @@ -8,8 +8,6 @@ import { RGBPP_WITNESS_PLACEHOLDER, getClusterTypeDep, getClusterTypeScript, - getRgbppLockConfigDep, - getRgbppLockDep, getRgbppLockScript, } from '../constants'; import { generateClusterCreateCoBuild, generateClusterId } from '../utils/spore'; @@ -62,7 +60,7 @@ export const genCreateClusterCkbVirtualTx = async ({ }, ]; const outputsData: Hex[] = [bytesToHex(packRawClusterData(clusterData))]; - const cellDeps = [getRgbppLockDep(isMainnet), getRgbppLockConfigDep(isMainnet), getClusterTypeDep(isMainnet)]; + const cellDeps = [...(await fetchTypeIdCellDeps(isMainnet, { rgbpp: true })), getClusterTypeDep(isMainnet)]; const sporeCoBuild = generateClusterCreateCoBuild(outputs[0], outputsData[0]); const witnesses = [RGBPP_WITNESS_PLACEHOLDER, sporeCoBuild]; @@ -92,5 +90,6 @@ export const genCreateClusterCkbVirtualTx = async ({ ckbRawTx, commitment, clusterId, + needPaymasterCell: false, }; }; diff --git a/packages/ckb/src/spore/leap.ts b/packages/ckb/src/spore/leap.ts index 612af062..b7edefed 100644 --- a/packages/ckb/src/spore/leap.ts +++ b/packages/ckb/src/spore/leap.ts @@ -1,5 +1,5 @@ import { BtcTimeCellsParams, RgbppCkbVirtualTx } from '../types/rgbpp'; -import { append0x, calculateTransactionFee } from '../utils'; +import { append0x, calculateTransactionFee, fetchTypeIdCellDeps } from '../utils'; import { btcTxIdFromBtcTimeLockArgs, buildSpvClientCellDep, @@ -19,10 +19,6 @@ import { BTC_JUMP_CONFIRMATION_BLOCKS, RGBPP_TX_WITNESS_MAX_SIZE, RGBPP_WITNESS_PLACEHOLDER, - getBtcTimeLockConfigDep, - getBtcTimeLockDep, - getRgbppLockConfigDep, - getRgbppLockDep, getRgbppLockScript, getSporeTypeDep, } from '../constants'; @@ -80,7 +76,7 @@ export const genLeapSporeFromBtcToCkbVirtualTx = async ({ }, ]; const outputsData: Hex[] = [sporeCell.outputData]; - const cellDeps = [getRgbppLockDep(isMainnet), getRgbppLockConfigDep(isMainnet), getSporeTypeDep(isMainnet)]; + const cellDeps = [...(await fetchTypeIdCellDeps(isMainnet, { rgbpp: true })), getSporeTypeDep(isMainnet)]; const sporeCoBuild = generateSporeTransferCoBuild([sporeCell], outputs); const witnesses = [RGBPP_WITNESS_PLACEHOLDER, sporeCoBuild]; @@ -142,9 +138,8 @@ export const buildSporeBtcTimeCellsSpentTx = async ({ const outputsData = sortedBtcTimeCells.map((cell) => cell.outputData); const cellDeps: CKBComponents.CellDep[] = [ - getBtcTimeLockDep(isMainnet), + ...(await fetchTypeIdCellDeps(isMainnet, { btcTime: true })), getSporeTypeDep(isMainnet), - getBtcTimeLockConfigDep(isMainnet), ]; const witnesses: Hex[] = []; @@ -233,7 +228,7 @@ export const genLeapSporeFromCkbToBtcRawTx = async ({ }, ]; const outputsData: Hex[] = [sporeCell.outputData]; - const cellDeps = [getRgbppLockDep(isMainnet), getRgbppLockConfigDep(isMainnet), getSporeTypeDep(isMainnet)]; + const cellDeps = [...(await fetchTypeIdCellDeps(isMainnet, { rgbpp: true })), getSporeTypeDep(isMainnet)]; const sporeCoBuild = generateSporeTransferCoBuild([sporeCell], outputs); const witnesses = [RGBPP_WITNESS_PLACEHOLDER, sporeCoBuild]; diff --git a/packages/ckb/src/spore/spore.ts b/packages/ckb/src/spore/spore.ts index 0ff01f31..d566f0bf 100644 --- a/packages/ckb/src/spore/spore.ts +++ b/packages/ckb/src/spore/spore.ts @@ -4,6 +4,7 @@ import { append0x, calculateRgbppSporeCellCapacity, calculateTransactionFee, + fetchTypeIdCellDeps, isClusterSporeTypeSupported, } from '../utils'; import { buildPreLockArgs, calculateCommitment, genRgbppLockScript } from '../utils/rgbpp'; @@ -23,8 +24,6 @@ import { RGBPP_WITNESS_PLACEHOLDER, SECP256K1_WITNESS_LOCK_SIZE, getClusterTypeDep, - getRgbppLockConfigDep, - getRgbppLockDep, getRgbppLockScript, getSecp256k1CellDep, getSporeTypeDep, @@ -117,8 +116,7 @@ export const genCreateSporeCkbVirtualTx = async ({ ]; const outputsData: Hex[] = [clusterCell.outputData, ...sporeOutputsData]; const cellDeps = [ - getRgbppLockDep(isMainnet), - getRgbppLockConfigDep(isMainnet), + ...(await fetchTypeIdCellDeps(isMainnet, { rgbpp: true })), getClusterTypeDep(isMainnet), getSporeTypeDep(isMainnet), clusterCellDep, @@ -151,6 +149,7 @@ export const genCreateSporeCkbVirtualTx = async ({ commitment, sumInputsCapacity, clusterCell, + needPaymasterCell: false, }; }; @@ -322,7 +321,7 @@ export const genTransferSporeCkbVirtualTx = async ({ }, ]; const outputsData: Hex[] = [sporeCell.outputData]; - const cellDeps = [getRgbppLockDep(isMainnet), getRgbppLockConfigDep(isMainnet), getSporeTypeDep(isMainnet)]; + const cellDeps = [...(await fetchTypeIdCellDeps(isMainnet, { rgbpp: true })), getSporeTypeDep(isMainnet)]; const sporeCoBuild = generateSporeTransferCoBuild([sporeCell], outputs); const witnesses = [RGBPP_WITNESS_PLACEHOLDER, sporeCoBuild]; diff --git a/packages/ckb/src/types/rgbpp.ts b/packages/ckb/src/types/rgbpp.ts index 0a1aac8a..4bcc77c9 100644 --- a/packages/ckb/src/types/rgbpp.ts +++ b/packages/ckb/src/types/rgbpp.ts @@ -84,6 +84,8 @@ export interface RgbppLaunchVirtualTxResult { ckbRawTx: CKBComponents.RawTransaction; // The rgbpp commitment to be inserted into BTC op_return commitment: Hex; + // The needPaymasterCell indicates whether a paymaster cell is required + needPaymasterCell: boolean; } export interface AppendWitnessesParams { diff --git a/packages/ckb/src/types/spore.ts b/packages/ckb/src/types/spore.ts index 56a5afcd..d599c591 100644 --- a/packages/ckb/src/types/spore.ts +++ b/packages/ckb/src/types/spore.ts @@ -22,6 +22,8 @@ export interface SporeVirtualTxResult { ckbRawTx: CKBComponents.RawTransaction; // The rgbpp commitment to be inserted into BTC op_return commitment: Hex; + // The needPaymasterCell indicates whether a paymaster cell is required + needPaymasterCell: boolean; // The ID of the cluster to be assigned to the spore. The cluster's ID is equivalent to the type script args of the cluster clusterId?: Hex; } @@ -49,6 +51,8 @@ export interface SporeCreateVirtualTxResult { sumInputsCapacity: Hex; // The cluster cell from ckb-indexer clusterCell: IndexerCell; + // The needPaymasterCell indicates whether a paymaster cell is required + needPaymasterCell: boolean; } export interface BuildAppendingIssuerCellTxParams { diff --git a/packages/ckb/src/utils/cell-dep.spec.ts b/packages/ckb/src/utils/cell-dep.spec.ts new file mode 100644 index 00000000..b0c9c809 --- /dev/null +++ b/packages/ckb/src/utils/cell-dep.spec.ts @@ -0,0 +1,62 @@ +import { describe, it, expect } from 'vitest'; +import { fetchTypeIdCellDeps } from './cell-dep'; +import { getBtcTimeLockDep, getRgbppLockDep, getUniqueTypeDep, getXudtDep } from '../constants'; + +describe('dynamic fetch cell dep', () => { + it('fetchTypeIdCellDeps with xudt and unique', async () => { + const isMainnet = false; + const [xudtDep, uniqueDep] = await fetchTypeIdCellDeps(isMainnet, { xudt: true, unique: true }); + expect(xudtDep.outPoint?.txHash).toBe(getXudtDep(isMainnet).outPoint?.txHash); + expect(uniqueDep.outPoint?.txHash).toBe(getUniqueTypeDep(isMainnet).outPoint?.txHash); + }); + + it('fetchTypeIdCellDeps with rgbpp and config', async () => { + const isMainnet = false; + const [rgbppDep, configDep] = await fetchTypeIdCellDeps(isMainnet, { rgbpp: true }); + expect(rgbppDep.outPoint?.txHash).toBe(getRgbppLockDep(isMainnet).outPoint?.txHash); + expect(configDep.outPoint?.index).toBe('0x1'); + }); + + it('fetchTypeIdCellDeps with rgbpp, config and xudt', async () => { + const isMainnet = false; + const [rgbppDep, configDep, xudtDep] = await fetchTypeIdCellDeps(isMainnet, { rgbpp: true, xudt: true }); + expect(rgbppDep.outPoint?.txHash).toBe(getRgbppLockDep(isMainnet).outPoint?.txHash); + expect(configDep.outPoint?.index).toBe('0x1'); + expect(xudtDep.outPoint?.txHash).toBe(getXudtDep(isMainnet).outPoint?.txHash); + }); + + it('fetchTypeIdCellDeps with btcTime and config', async () => { + const isMainnet = true; + const [btcTimeDep, configDep] = await fetchTypeIdCellDeps(isMainnet, { btcTime: true }); + expect(btcTimeDep.outPoint?.txHash).toBe(getBtcTimeLockDep(isMainnet).outPoint?.txHash); + expect(configDep.outPoint?.index).toBe('0x1'); + }); + + it('fetchTypeIdCellDeps with btcTime, config and xudt', async () => { + const isMainnet = true; + const [btcTimeDep, configDep, xudtDep] = await fetchTypeIdCellDeps(isMainnet, { btcTime: true, xudt: true }); + expect(btcTimeDep.outPoint?.txHash).toBe(getBtcTimeLockDep(isMainnet).outPoint?.txHash); + expect(configDep.outPoint?.index).toBe('0x1'); + expect(xudtDep.outPoint?.txHash).toBe(getXudtDep(isMainnet).outPoint?.txHash); + }); + + it('fetchTypeIdCellDeps with rgbpp, btcTime, xudt and unique', async () => { + const isMainnet = false; + const cellDeps = await fetchTypeIdCellDeps(isMainnet, { rgbpp: true, btcTime: true, xudt: true, unique: true }); + expect(cellDeps[0].outPoint?.txHash).toBe(getRgbppLockDep(isMainnet).outPoint?.txHash); + expect(cellDeps[1].outPoint?.index).toBe('0x1'); + + expect(cellDeps[2].outPoint?.txHash).toBe(getBtcTimeLockDep(isMainnet).outPoint?.txHash); + expect(cellDeps[3].outPoint?.index).toBe('0x1'); + + expect(cellDeps[4].outPoint?.txHash).toBe(getXudtDep(isMainnet).outPoint?.txHash); + + expect(cellDeps[5].outPoint?.txHash).toBe(getUniqueTypeDep(isMainnet).outPoint?.txHash); + }); + + it('fetchTypeIdCellDeps without cell deps', async () => { + const isMainnet = false; + const cellDeps = await fetchTypeIdCellDeps(isMainnet, {}); + expect(cellDeps.length).toBe(0); + }); +}); diff --git a/packages/ckb/src/utils/cell-dep.ts b/packages/ckb/src/utils/cell-dep.ts new file mode 100644 index 00000000..76f1e4cf --- /dev/null +++ b/packages/ckb/src/utils/cell-dep.ts @@ -0,0 +1,112 @@ +import axios from 'axios'; +import { getBtcTimeLockDep, getRgbppLockDep, getUniqueTypeDep, getXudtDep } from '../constants'; + +interface CellDepsObject { + rgbpp: { + mainnet: CKBComponents.CellDep; + testnet: CKBComponents.CellDep; + }; + btcTime: { + mainnet: CKBComponents.CellDep; + testnet: CKBComponents.CellDep; + }; + xudt: { + testnet: CKBComponents.CellDep; + }; + unique: { + testnet: CKBComponents.CellDep; + }; +} +const GITHUB_CELL_DEPS_JSON_URL = + 'https://raw.githubusercontent.com/ckb-cell/typeid-contract-cell-deps/main/deployment/cell-deps.json'; + +const CDN_GITHUB_CELL_DEPS_JSON_URL = + 'https://cdn.jsdelivr.net/gh/ckb-cell/typeid-contract-cell-deps@main/deployment/cell-deps.json'; + +const request = (url: string) => axios.get(url, { timeout: 2000 }); + +const fetchCellDepsJson = async () => { + try { + const response = await Promise.any([request(GITHUB_CELL_DEPS_JSON_URL), request(CDN_GITHUB_CELL_DEPS_JSON_URL)]); + return response.data as CellDepsObject; + } catch (error) { + // console.error('Error fetching cell deps:', error); + } +}; + +export interface CellDepsSelected { + rgbpp?: boolean; + btcTime?: boolean; + xudt?: boolean; + unique?: boolean; +} + +export const fetchTypeIdCellDeps = async ( + isMainnet: boolean, + selected: CellDepsSelected, +): Promise => { + let rgbppLockDep = getRgbppLockDep(isMainnet); + let btcTimeDep = getBtcTimeLockDep(isMainnet); + let xudtDep = getXudtDep(isMainnet); + let uniqueDep = getUniqueTypeDep(isMainnet); + + const cellDepsObj = await fetchCellDepsJson(); + if (cellDepsObj) { + rgbppLockDep = isMainnet ? cellDepsObj.rgbpp.mainnet : cellDepsObj.rgbpp.testnet; + btcTimeDep = isMainnet ? cellDepsObj.btcTime.mainnet : cellDepsObj.btcTime.testnet; + if (!isMainnet) { + xudtDep = cellDepsObj.xudt.testnet; + uniqueDep = cellDepsObj.unique.testnet; + } + } + let cellDeps: CKBComponents.CellDep[] = []; + if (selected.rgbpp) { + // RGB++ config cell is deployed together with the RGB++ lock contract + // + // contract_deployment_transaction: + // - output(index=0, data=rgbpp_code) + // - output(index=1, data=rgbpp_config) + // + cellDeps = [ + ...cellDeps, + rgbppLockDep, + { + ...rgbppLockDep, + outPoint: { + ...rgbppLockDep.outPoint, + index: '0x1', + }, + }, + ] as CKBComponents.CellDep[]; + } + + if (selected.btcTime) { + // BTC Time config cell is deployed together with the BTC Time lock contract + // + // contract_deployment_transaction: + // - output(index=0, data=rgbpp_code) + // - output(index=1, data=rgbpp_config) + // + cellDeps = [ + ...cellDeps, + btcTimeDep, + { + ...btcTimeDep, + outPoint: { + ...btcTimeDep.outPoint, + index: '0x1', + }, + }, + ] as CKBComponents.CellDep[]; + } + + if (selected.xudt) { + cellDeps = [...cellDeps, xudtDep] as CKBComponents.CellDep[]; + } + + if (selected.unique) { + cellDeps = [...cellDeps, uniqueDep] as CKBComponents.CellDep[]; + } + + return cellDeps; +}; diff --git a/packages/ckb/src/utils/index.ts b/packages/ckb/src/utils/index.ts index 4fab8d82..20dc24ce 100644 --- a/packages/ckb/src/utils/index.ts +++ b/packages/ckb/src/utils/index.ts @@ -3,3 +3,4 @@ export * from './hex'; export * from './ckb-tx'; export * from './rgbpp'; export * from './spore'; +export * from './cell-dep'; diff --git a/packages/rgbpp/CHANGELOG.md b/packages/rgbpp/CHANGELOG.md index a8a4cc28..2e521abc 100644 --- a/packages/rgbpp/CHANGELOG.md +++ b/packages/rgbpp/CHANGELOG.md @@ -1,5 +1,14 @@ # rgbpp +## v0.3.0 + +### Patch Changes + +- Updated dependencies [[`d2d963c`](https://github.com/ckb-cell/rgbpp-sdk/commit/d2d963c8f40d0316491df5bdccca4eba7a33977c), [`4c77e69`](https://github.com/ckb-cell/rgbpp-sdk/commit/4c77e69cadc8ce3d24f631c1348dcd7141fb1099), [`3d41751`](https://github.com/ckb-cell/rgbpp-sdk/commit/3d417518c0224c6cd3cc3e55123cf4a691c0a976), [`4f05b1b`](https://github.com/ckb-cell/rgbpp-sdk/commit/4f05b1bba898b7acb58bdf20ae275164ad94523b), [`d0e62e2`](https://github.com/ckb-cell/rgbpp-sdk/commit/d0e62e2be8e21f02a84753cbc0f2200c8f88f155)]: + - @rgbpp-sdk/ckb@0.3.0 + - @rgbpp-sdk/btc@0.3.0 + - @rgbpp-sdk/service@0.3.0 + ## v0.2.0 ### Minor Changes diff --git a/packages/rgbpp/package.json b/packages/rgbpp/package.json index 42a4a3dd..a78f6fdd 100644 --- a/packages/rgbpp/package.json +++ b/packages/rgbpp/package.json @@ -1,6 +1,6 @@ { "name": "rgbpp", - "version": "0.2.0", + "version": "0.3.0", "scripts": { "build": "tsc -p tsconfig.build.json", "lint": "tsc && eslint --ext .ts src/* && prettier --check 'src/*.ts'", diff --git a/packages/service/CHANGELOG.md b/packages/service/CHANGELOG.md index c8f94e79..e7736579 100644 --- a/packages/service/CHANGELOG.md +++ b/packages/service/CHANGELOG.md @@ -1,5 +1,16 @@ # @rgbpp-sdk/service +## v0.3.0 + +### Minor Changes + +- [#208](https://github.com/ckb-cell/rgbpp-sdk/pull/208): Adapt btc-assets-api#154, adding new props and return values to the /balance and /unspent APIs ([@ShookLyngs](https://github.com/ShookLyngs)) + + - Add `available_satoshi` and `total_satoshi` to the BtcAssetsApi.getBtcBalance() API + - Add `only_non_rgbpp_utxos` to the props of the BtcAssetsApi.getBtcUtxos() API + - Remove `service.getRgbppAssetsByBtcUtxo()` lines from the DataCollector.collectSatoshi() + - Remove `hasRgbppAssets` related variables/function from the DataCache + ## v0.2.0 ### Minor Changes diff --git a/packages/service/README.md b/packages/service/README.md index a6bb4898..8132bfe4 100644 --- a/packages/service/README.md +++ b/packages/service/README.md @@ -228,13 +228,18 @@ interface BtcApiBalanceParams { interface BtcApiBalance { address: string; + // @deprecated Use available_satoshi instead satoshi: number; + total_satoshi: number; + available_satoshi: number; pending_satoshi: number; + rgbpp_satoshi: number; dust_satoshi: number; utxo_count: number; } interface BtcApiUtxoParams { + only_non_rgbpp_utxos?: boolean; only_confirmed?: boolean; min_satoshi?: number; no_cache?: boolean; diff --git a/packages/service/package.json b/packages/service/package.json index f55ae9da..50c800a6 100644 --- a/packages/service/package.json +++ b/packages/service/package.json @@ -1,6 +1,6 @@ { "name": "@rgbpp-sdk/service", - "version": "0.2.0", + "version": "0.3.0", "scripts": { "test": "vitest", "build": "tsc -p tsconfig.build.json", diff --git a/packages/service/src/types/btc.ts b/packages/service/src/types/btc.ts index d5bbd031..3bc9ee00 100644 --- a/packages/service/src/types/btc.ts +++ b/packages/service/src/types/btc.ts @@ -62,13 +62,18 @@ export interface BtcApiBalanceParams { } export interface BtcApiBalance { address: string; + // @deprecated Use available_satoshi instead satoshi: number; + total_satoshi: number; + available_satoshi: number; pending_satoshi: number; + rgbpp_satoshi: number; dust_satoshi: number; utxo_count: number; } export interface BtcApiUtxoParams { + only_non_rgbpp_utxos?: boolean; only_confirmed?: boolean; min_satoshi?: number; no_cache?: boolean; diff --git a/packages/service/tests/Service.test.ts b/packages/service/tests/Service.test.ts index e2205335..a8fa09aa 100644 --- a/packages/service/tests/Service.test.ts +++ b/packages/service/tests/Service.test.ts @@ -79,6 +79,8 @@ describe( const res = await service.getBtcBalance(btcAddress); expect(res.address).toEqual(btcAddress); expect(res.satoshi).toBeTypeOf('number'); + expect(res.total_satoshi).toBeTypeOf('number'); + expect(res.available_satoshi).toBeTypeOf('number'); expect(res.pending_satoshi).toBeTypeOf('number'); expect(res.dust_satoshi).toBeTypeOf('number'); expect(res.utxo_count).toBeTypeOf('number'); @@ -89,8 +91,7 @@ describe( min_satoshi: originalBalance.satoshi + 1, }); - expect(filteredBalance.satoshi).toEqual(0); - expect(filteredBalance.dust_satoshi).toEqual(originalBalance.satoshi + originalBalance.dust_satoshi); + expect(filteredBalance.available_satoshi).toEqual(0); }); it('getBtcBalance() with no_cache', async () => { const res = await service.getBtcBalance(btcAddress, { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d58421ea..ecf567c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -165,7 +165,7 @@ importers: specifier: ^0.109.1 version: 0.109.1 rgbpp: - specifier: 0.1.0 + specifier: workspace:* version: link:../../packages/rgbpp devDependencies: '@types/dotenv': @@ -204,6 +204,9 @@ importers: lodash: specifier: ^4.17.21 version: 4.17.21 + p-limit: + specifier: ^3.1.0 + version: 3.1.0 devDependencies: '@types/lodash': specifier: ^4.17.0 @@ -4704,7 +4707,6 @@ packages: engines: {node: '>=10'} dependencies: yocto-queue: 0.1.0 - dev: true /p-limit@5.0.0: resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} @@ -6329,7 +6331,6 @@ packages: /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - dev: true /yocto-queue@1.0.0: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}