Skip to content

Add USDI to compatible xUDT list #297

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 21, 2025
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
8 changes: 8 additions & 0 deletions .changeset/sweet-cheetahs-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@rgbpp-sdk/ckb': minor
---

Add USDI to compatible xUDT list and remove jsdelivr CDN because of cache
- Fetch and cache compatible xUDT list from Vercel or GitHub server
- Use local static compatible xUDT list when the cache is empty
- Remove jsdelivr CDN because CDN cache time is too long, causing UTXO Airdrop cellDeps to become outdated
2 changes: 2 additions & 0 deletions examples/rgbpp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

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

> [!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.
Expand Down
9 changes: 8 additions & 1 deletion examples/rgbpp/xudt/compatible-xudt/1-ckb-leap-btc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { serializeScript } from '@nervosnetwork/ckb-sdk-utils';
import { genCkbJumpBtcVirtualTx } from 'rgbpp';
import { getSecp256k1CellDep, buildRgbppLockArgs } from 'rgbpp/ckb';
import { getSecp256k1CellDep, buildRgbppLockArgs, CompatibleXUDTRegistry } from 'rgbpp/ckb';
import { CKB_PRIVATE_KEY, isMainnet, collector, ckbAddress, BTC_TESTNET_TYPE } from '../../env';

interface LeapToBtcParams {
Expand All @@ -18,6 +18,13 @@ const leapRusdFromCkbToBtc = async ({
}: LeapToBtcParams) => {
const toRgbppLockArgs = buildRgbppLockArgs(outIndex, btcTxId);

// Refresh the cache by fetching the latest compatible xUDT list from the specified URL.
// The default URL is:
// https://raw.githubusercontent.com/utxostack/typeid-contract-cell-deps/main/compatible-udt.json
// You can set your own trusted URL to fetch the compatible xUDT list.
// await CompatibleXUDTRegistry.refreshCache("https://your-own-trusted-compatible-xudt-url");
await CompatibleXUDTRegistry.refreshCache();

const ckbRawTx = await genCkbJumpBtcVirtualTx({
collector,
fromCkbAddress: ckbAddress,
Expand Down
9 changes: 8 additions & 1 deletion examples/rgbpp/xudt/compatible-xudt/2-btc-transfer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { buildRgbppLockArgs } from 'rgbpp/ckb';
import { buildRgbppLockArgs, CompatibleXUDTRegistry } from 'rgbpp/ckb';
import { buildRgbppTransferTx } from 'rgbpp';
import { isMainnet, collector, btcService, btcAccount, btcDataSource, BTC_TESTNET_TYPE } from '../../env';
import { saveCkbVirtualTxResult } from '../../shared/utils';
Expand All @@ -18,6 +18,13 @@ const transferRusdOnBtc = async ({
compatibleXudtTypeScript,
transferAmount,
}: RgbppTransferParams) => {
// Refresh the cache by fetching the latest compatible xUDT list from the specified URL.
// The default URL is:
// https://raw.githubusercontent.com/utxostack/typeid-contract-cell-deps/main/compatible-udt.json
// You can set your own trusted URL to fetch the compatible xUDT list.
// await CompatibleXUDTRegistry.refreshCache("https://your-own-trusted-compatible-xudt-url");
await CompatibleXUDTRegistry.refreshCache();

const { ckbVirtualTxResult, btcPsbtHex } = await buildRgbppTransferTx({
ckb: {
collector,
Expand Down
9 changes: 8 additions & 1 deletion examples/rgbpp/xudt/compatible-xudt/3-btc-leap-ckb.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { buildRgbppLockArgs } from 'rgbpp/ckb';
import { buildRgbppLockArgs, CompatibleXUDTRegistry } from 'rgbpp/ckb';
import { serializeScript } from '@nervosnetwork/ckb-sdk-utils';
import { genBtcJumpCkbVirtualTx, sendRgbppUtxos } from 'rgbpp';
import { isMainnet, collector, btcService, btcDataSource, btcAccount, BTC_TESTNET_TYPE } from '../../env';
Expand All @@ -18,6 +18,13 @@ const leapRusdFromBtcToCKB = async ({
compatibleXudtTypeScript,
transferAmount,
}: LeapToCkbParams) => {
// Refresh the cache by fetching the latest compatible xUDT list from the specified URL.
// The default URL is:
// https://raw.githubusercontent.com/utxostack/typeid-contract-cell-deps/main/compatible-udt.json
// You can set your own trusted URL to fetch the compatible xUDT list.
// await CompatibleXUDTRegistry.refreshCache("https://your-own-trusted-compatible-xudt-url");
await CompatibleXUDTRegistry.refreshCache();

const ckbVirtualTxResult = await genBtcJumpCkbVirtualTx({
collector,
rgbppLockArgsList,
Expand Down
78 changes: 44 additions & 34 deletions examples/rgbpp/xudt/compatible-xudt/assets-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,58 @@ import { serializeScript } from '@nervosnetwork/ckb-sdk-utils';
import { btcService } from '../../env';

(async () => {
const assets = await btcService.getRgbppAssetsByBtcAddress('tb1qvt7p9g6mw70sealdewtfp0sekquxuru6j3gwmt', {
type_script: serializeScript({
codeHash: '0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a',
hashType: 'type',
args: '0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b',
}),
});
console.log('RUSD Assets: ', JSON.stringify(assets));
// const assets = await btcService.getRgbppAssetsByBtcAddress('tb1qvt7p9g6mw70sealdewtfp0sekquxuru6j3gwmt', {
// type_script: serializeScript({
// codeHash: '0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a',
// hashType: 'type',
// args: '0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b',
// }),
// });
// console.log('RUSD Assets: ', JSON.stringify(assets));

const activities = await btcService.getRgbppActivityByBtcAddress('tb1qvt7p9g6mw70sealdewtfp0sekquxuru6j3gwmt', {
type_script: serializeScript({
codeHash: '0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a',
hashType: 'type',
args: '0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b',
}),
});
console.log('RUSD Activities: ', JSON.stringify(activities));
// const activities = await btcService.getRgbppActivityByBtcAddress('tb1qvt7p9g6mw70sealdewtfp0sekquxuru6j3gwmt', {
// type_script: serializeScript({
// codeHash: '0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a',
// hashType: 'type',
// args: '0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b',
// }),
// });
// console.log('RUSD Activities: ', JSON.stringify(activities));

const info = await btcService.getRgbppAssetInfoByTypeScript(
serializeScript({
codeHash: '0x25c29dc317811a6f6f3985a7a9ebc4838bd388d19d0feeecf0bcd60f6c0975bb',
hashType: 'type',
args: '0x661cfbe2124b3e79e50e505c406be5b2dcf9da15d8654b749ec536fa4c2eaaae',
}),
);
console.log('Standard xUDT info: ', JSON.stringify(info));
// const balance = await btcService.getRgbppBalanceByBtcAddress('tb1qvt7p9g6mw70sealdewtfp0sekquxuru6j3gwmt', {
// type_script: serializeScript({
// codeHash: '0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a',
// hashType: 'type',
// args: '0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b',
// }),
// no_cache: true,
// });
// console.log('RUSD balance from btc-assets-api: ', JSON.stringify(balance));

// const info = await btcService.getRgbppAssetInfoByTypeScript(
// serializeScript({
// codeHash: '0x25c29dc317811a6f6f3985a7a9ebc4838bd388d19d0feeecf0bcd60f6c0975bb',
// hashType: 'type',
// args: '0x661cfbe2124b3e79e50e505c406be5b2dcf9da15d8654b749ec536fa4c2eaaae',
// }),
// );
// console.log('Standard xUDT info: ', JSON.stringify(info));

const rusdInfo = await btcService.getRgbppAssetInfoByTypeScript(
serializeScript({
codeHash: '0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a',
codeHash: '0xcc9dc33ef234e14bc788c43a4848556a5fb16401a04662fc55db9bb201987037',
hashType: 'type',
args: '0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b',
args: '0x71fd1985b2971a9903e4d8ed0d59e6710166985217ca0681437883837b86162f',
}),
);
console.log('RUSD xUDT info: ', JSON.stringify(rusdInfo));

const utxoAirdropInfo = await btcService.getRgbppAssetInfoByTypeScript(
serializeScript({
codeHash: '0xf5da9003e31fa9301a3915fe304de9bdb80524b5f0d8fc325fb699317998ee7a',
hashType: 'type',
args: '0xa63d308c04b4c075eb1d7d5cac891cf20276e3ddb2ec855fc981c88d8134dbe2',
}),
);
console.log('UTXO Airdrop xUDT info: ', JSON.stringify(utxoAirdropInfo));
// const utxoAirdropInfo = await btcService.getRgbppAssetInfoByTypeScript(
// serializeScript({
// codeHash: '0xf5da9003e31fa9301a3915fe304de9bdb80524b5f0d8fc325fb699317998ee7a',
// hashType: 'type',
// args: '0xa63d308c04b4c075eb1d7d5cac891cf20276e3ddb2ec855fc981c88d8134dbe2',
// }),
// );
// console.log('UTXO Airdrop xUDT info: ', JSON.stringify(utxoAirdropInfo));
})();
50 changes: 28 additions & 22 deletions packages/ckb/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,6 @@ const TestnetInfo = {
depType: 'code',
} as CKBComponents.CellDep,

CompatibleXUDTTypeScripts: [
// RUSD
{
codeHash: '0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a',
hashType: 'type',
args: '',
},
] as CKBComponents.Script[],

UniqueTypeScript: {
codeHash: '0x8e341bcfec6393dcd41e635733ff2dca00a6af546949f70c57a706c0f344df8b',
hashType: 'type',
Expand Down Expand Up @@ -157,7 +148,7 @@ const TestnetInfo = {
} as CKBComponents.Script,

UtxoAirdropBadgeTypeDep: {
outPoint: { txHash: '0xfa0a6821293cc1ef4ee67a900862208e27f67b98237c9b13bf93c84607c5cd33', index: '0x2' },
outPoint: { txHash: '0xbbbb73972ac260a0f7204bea707288c3970688fe8714c3246a5e9a538168a42a', index: '0x0' },
depType: 'code',
} as CKBComponents.CellDep,

Expand Down Expand Up @@ -223,15 +214,6 @@ const MainnetInfo = {
depType: 'code',
} as CKBComponents.CellDep,

CompatibleXUDTTypeScripts: [
// RUSD
{
codeHash: '0x26a33e0815888a4a0614a0b7d09fa951e0993ff21e55905510104a0b1312032b',
hashType: 'type',
args: '',
},
] as CKBComponents.Script[],

UniqueTypeScript: {
codeHash: '0x2c8c11c985da60b0a330c61a85507416d6382c130ba67f0c47ab071e00aec628',
hashType: 'data1',
Expand Down Expand Up @@ -294,6 +276,33 @@ const MainnetInfo = {
} as CKBComponents.Script,
};

export const COMPATIBLE_XUDT_TYPE_SCRIPTS: CKBComponents.Script[] = [
// RUSD Mainnet
{
codeHash: '0x26a33e0815888a4a0614a0b7d09fa951e0993ff21e55905510104a0b1312032b',
hashType: 'type',
args: '',
},
// RUSD Testnet
{
codeHash: '0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a',
hashType: 'type',
args: '',
},
// USDI Mainnet
{
codeHash: '0xbfa35a9c38a676682b65ade8f02be164d48632281477e36f8dc2f41f79e56bfc',
hashType: 'type',
args: '',
},
// USDI Testnet
{
codeHash: '0xcc9dc33ef234e14bc788c43a4848556a5fb16401a04662fc55db9bb201987037',
hashType: 'type',
args: '',
},
];

export const UNLOCKABLE_LOCK_SCRIPT = {
codeHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
hashType: 'data',
Expand Down Expand Up @@ -363,9 +372,6 @@ export const getSporeTypeScript = (isMainnet: boolean) =>
export const getSporeTypeDep = (isMainnet: boolean) =>
isMainnet ? MainnetInfo.SporeTypeDep : TestnetInfo.SporeTypeDep;

export const getCompatibleXudtTypeScripts = (isMainnet: boolean) =>
isMainnet ? MainnetInfo.CompatibleXUDTTypeScripts : TestnetInfo.CompatibleXUDTTypeScripts;

export const getUtxoAirdropBadgeTypeScript = (isMainnet: boolean) =>
isMainnet ? MainnetInfo.UtxoAirdropBadgeTypeScript : TestnetInfo.UtxoAirdropBadgeTypeScript;
export const getUtxoAirdropBadgeTypeDep = (isMainnet: boolean) =>
Expand Down
2 changes: 1 addition & 1 deletion packages/ckb/src/rgbpp/btc-time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const buildBtcTimeCellsSpentTx = async ({

const hasStandardUDT = outputs.some((output) => isStandardUDTTypeSupported(output.type!, isMainnet));
const compatibleXudtCodeHashes = outputs
.filter((output) => isCompatibleUDTTypesSupported(output.type!, isMainnet))
.filter((output) => isCompatibleUDTTypesSupported(output.type!))
.map((output) => output.type!.codeHash);
const cellDeps = await fetchTypeIdCellDeps(
isMainnet,
Expand Down
27 changes: 24 additions & 3 deletions packages/ckb/src/utils/cell-dep.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { fetchTypeIdCellDeps } from './cell-dep';
import { CompatibleXUDTRegistry, fetchTypeIdCellDeps } from './cell-dep';
import { getBtcTimeLockDep, getRgbppLockDep, getUniqueTypeDep, getXudtDep } from '../constants';

describe('dynamic fetch cell dep', () => {
Expand Down Expand Up @@ -109,8 +109,29 @@ describe('dynamic fetch cell dep', () => {
const cellDeps = await fetchTypeIdCellDeps(isMainnet, {
utxoAirdropBadge: true,
});
expect(cellDeps[0].outPoint?.txHash).toBe('0xfa0a6821293cc1ef4ee67a900862208e27f67b98237c9b13bf93c84607c5cd33');
expect(cellDeps[0].outPoint?.index).toBe('0x2');
expect(cellDeps[0].outPoint?.txHash).toBe('0xbbbb73972ac260a0f7204bea707288c3970688fe8714c3246a5e9a538168a42a');
expect(cellDeps[0].outPoint?.index).toBe('0x0');
},
{ timeout: 10000 },
);

it(
'CompatibleXUDTRegistry.getCompatibleTokens',
async () => {
const scripts = CompatibleXUDTRegistry.getCompatibleTokens();
expect(scripts.length > 0).toBe(true);
// RUSD Mainnet
expect(scripts[0].codeHash).toBe('0x26a33e0815888a4a0614a0b7d09fa951e0993ff21e55905510104a0b1312032b');
// USDI Testnet
expect(scripts[3].codeHash).toBe('0xcc9dc33ef234e14bc788c43a4848556a5fb16401a04662fc55db9bb201987037');

await CompatibleXUDTRegistry.refreshCache();
const latestScripts = CompatibleXUDTRegistry.getCompatibleTokens();
expect(latestScripts.length > 0).toBe(true);
// RUSD Testnet
expect(latestScripts[0].codeHash).toBe('0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a');
// USDI Mainnet
expect(latestScripts[3].codeHash).toBe('0xbfa35a9c38a676682b65ade8f02be164d48632281477e36f8dc2f41f79e56bfc');
},
{ timeout: 10000 },
);
Expand Down
70 changes: 60 additions & 10 deletions packages/ckb/src/utils/cell-dep.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import axios from 'axios';
import {
COMPATIBLE_XUDT_TYPE_SCRIPTS,
getBtcTimeLockDep,
getRgbppLockDep,
getUniqueTypeDep,
Expand Down Expand Up @@ -36,11 +37,6 @@ export interface CellDepsObject {
const GITHUB_CELL_DEPS_JSON_URL =
'https://raw.githubusercontent.com/utxostack/typeid-contract-cell-deps/main/deployment/cell-deps.json';

// If the CDN has cache issue, please clear the cache by visiting
// https://www.jsdelivr.com/tools/purge?path=/gh/utxostack/typeid-contract-cell-deps@main
const CDN_GITHUB_CELL_DEPS_JSON_URL =
'https://cdn.jsdelivr.net/gh/utxostack/typeid-contract-cell-deps@main/deployment/cell-deps.json';

const VERCEL_CELL_DEPS_JSON_STATIC_URL = 'https://typeid-contract-cell-deps.vercel.app/deployment/cell-deps.json';

const VERCEL_SERVER_CELL_DEPS_JSON_URL = 'https://typeid-contract-cell-deps.vercel.app/api/cell-deps';
Expand All @@ -49,11 +45,7 @@ const request = (url: string) => axios.get(url, { timeout: 10000 });

const fetchCellDepsJsonFromStaticSource = async () => {
try {
const response = await Promise.any([
request(CDN_GITHUB_CELL_DEPS_JSON_URL),
request(GITHUB_CELL_DEPS_JSON_URL),
request(VERCEL_CELL_DEPS_JSON_STATIC_URL),
]);
const response = await Promise.any([request(VERCEL_CELL_DEPS_JSON_STATIC_URL), request(GITHUB_CELL_DEPS_JSON_URL)]);
return response.data as CellDepsObject;
} catch (error) {
// for (const e of error.errors) {
Expand Down Expand Up @@ -210,3 +202,61 @@ export const fetchTypeIdCellDeps = async (

return cellDeps;
};

const VERCEL_STATIC_COMPATIBLE_XUDT_URL = 'https://typeid-contract-cell-deps.vercel.app/compatible-udt.json';
const GITHUB_STATIC_COMPATIBLE_XUDT_URL =
'https://raw.githubusercontent.com/utxostack/typeid-contract-cell-deps/main/compatible-udt.json';

/**
* The `CompatibleXUDTRegistry` class is responsible for managing a cache of compatible XUDT (eXtensible User-Defined Token) scripts.
* It fetches and caches the compatible tokens from specified URLs and refreshes the cache periodically.
*/
export class CompatibleXUDTRegistry {
private static cache: CKBComponents.Script[] = [];
private static lastFetchTime: number = 0;
private static CACHE_DURATION = 3 * 60 * 1000; // 3 minutes ([about 24 CKB blocks](https://docs-old.nervos.org/docs/essays/tx-confirmation))
private static xudtUrl = VERCEL_STATIC_COMPATIBLE_XUDT_URL;

// If you want to get the latest compatible xUDT list, CompatibleXUDTRegistry.refreshCache should be called first
static getCompatibleTokens(): CKBComponents.Script[] {
const now = Date.now();
if (this.cache.length === 0 || now - this.lastFetchTime > this.CACHE_DURATION) {
this.refreshCache(this.xudtUrl);
}
return this.cache.length > 0 ? this.cache : COMPATIBLE_XUDT_TYPE_SCRIPTS;
}

/**
* Refreshes the cache by fetching data from the provided URL or a default URL.
*
* This method attempts to fetch data from the provided URL or a default URL
* using `Promise.any` to handle multiple potential sources. If the fetch is
* successful, it updates the cache with the fetched data and sets the last
* fetch time to the current timestamp.
*
* @param url - An optional URL to fetch data from. If not provided, a default
* URL (`VERCEL_CELL_DEPS_JSON_STATIC_URL`) will be used.
* @returns A promise that resolves when the cache has been refreshed.
*/
static async refreshCache(url?: string): Promise<void> {
this.xudtUrl = url ?? VERCEL_STATIC_COMPATIBLE_XUDT_URL;
const isExternal = url !== VERCEL_STATIC_COMPATIBLE_XUDT_URL && url !== GITHUB_STATIC_COMPATIBLE_XUDT_URL;
try {
const response = await (isExternal
? request(this.xudtUrl)
: Promise.any([request(this.xudtUrl), request(GITHUB_STATIC_COMPATIBLE_XUDT_URL)]));
if (response && response.data) {
const xudtList = response.data as { codeHash: string }[];
this.cache = xudtList.map((xudt) => {
return {
codeHash: xudt.codeHash,
hashType: 'type',
} as CKBComponents.Script;
});
}
this.lastFetchTime = Date.now();
} catch (error) {
// console.error(error)
}
}
}
Loading
Loading