Skip to content
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

feat(rgbpp): calculate the pending rgbpp balance #155

Merged
merged 11 commits into from
Jun 3, 2024
2 changes: 1 addition & 1 deletion devbox.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"packages": [
"nodejs@latest",
"nodejs@20",
"nodePackages.pnpm@latest",
"redis@latest"
]
Expand Down
87 changes: 66 additions & 21 deletions devbox.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,108 @@
"lockfile_version": "1",
"packages": {
"nodePackages.pnpm@latest": {
"last_modified": "2024-03-08T13:51:52Z",
"resolved": "github:NixOS/nixpkgs/a343533bccc62400e8a9560423486a3b6c11a23b#nodePackages.pnpm",
"last_modified": "2024-05-22T06:18:38Z",
"resolved": "github:NixOS/nixpkgs/3f316d2a50699a78afe5e77ca486ad553169061e#nodePackages.pnpm",
"source": "devbox-search",
"version": "8.15.3",
"version": "8.15.5",
"systems": {
"aarch64-darwin": {
"store_path": "/nix/store/m9p6q42njy1k33fdzf2axqyhy8vh4vn5-pnpm-8.15.3"
"store_path": "/nix/store/98b2ljlansqmkpkd5pqfgwhcwr5kvsha-pnpm-8.15.5"
},
"aarch64-linux": {
"store_path": "/nix/store/war7jm6ka7afc2v8a78hlq8v9m4pg38m-pnpm-8.15.3"
"store_path": "/nix/store/b0h4xyqwdk2zhyhw42bprjnj2i518sxn-pnpm-8.15.5"
},
"x86_64-darwin": {
"store_path": "/nix/store/3w3bs2cdqx7sivcldcf6drwrhilv511m-pnpm-8.15.3"
"store_path": "/nix/store/7xyc072k4g6430vijvylidqacjxy5dbb-pnpm-8.15.5"
},
"x86_64-linux": {
"store_path": "/nix/store/hcq09j80njlfghy65qmwhn5nq20nk8kl-pnpm-8.15.3"
"store_path": "/nix/store/77wblnm5dnmgnan3695j3mk4r7j75s5j-pnpm-8.15.5"
}
}
},
"nodejs@latest": {
"last_modified": "2024-03-09T07:11:56Z",
"resolved": "github:NixOS/nixpkgs/0e7f98a5f30166cbed344569426850b21e4091d4#nodejs_21",
"nodejs@20": {
"last_modified": "2024-05-22T06:18:38Z",
"plugin_version": "0.0.2",
"resolved": "github:NixOS/nixpkgs/3f316d2a50699a78afe5e77ca486ad553169061e#nodejs_20",
"source": "devbox-search",
"version": "21.7.1",
"version": "20.12.2",
"systems": {
"aarch64-darwin": {
"store_path": "/nix/store/mh1db6rni4mlcr473sh3fy6jg2m38jvg-nodejs-21.7.1"
"outputs": [
{
"name": "out",
"path": "/nix/store/bzzs4kvjyvjjhs3rj08vqpvvzmfggvbv-nodejs-20.12.2",
"default": true
},
{
"name": "libv8",
"path": "/nix/store/c56874bxzncqwy58kif6wfnzy017v1sl-nodejs-20.12.2-libv8"
}
],
"store_path": "/nix/store/bzzs4kvjyvjjhs3rj08vqpvvzmfggvbv-nodejs-20.12.2"
},
"aarch64-linux": {
"store_path": "/nix/store/ziyb3ny5f9hcwp0lsb9gwdv1pqsn03x1-nodejs-21.7.1"
"outputs": [
{
"name": "out",
"path": "/nix/store/y50zafzgnnkrj4hvmk23icv2ggvys8r9-nodejs-20.12.2",
"default": true
},
{
"name": "libv8",
"path": "/nix/store/vc7y8h3c8pwbh4zbvjcyfqrd3fhdjhw6-nodejs-20.12.2-libv8"
}
],
"store_path": "/nix/store/y50zafzgnnkrj4hvmk23icv2ggvys8r9-nodejs-20.12.2"
},
"x86_64-darwin": {
"store_path": "/nix/store/0zaa3aarbsj38g62ihv94gz2hgvgh6bc-nodejs-21.7.1"
"outputs": [
{
"name": "out",
"path": "/nix/store/l53svh1nfrcb83qbqvrrkangrcl1rr25-nodejs-20.12.2",
"default": true
},
{
"name": "libv8",
"path": "/nix/store/q71hh22bfqjygd34gq16dv4dwfc33378-nodejs-20.12.2-libv8"
}
],
"store_path": "/nix/store/l53svh1nfrcb83qbqvrrkangrcl1rr25-nodejs-20.12.2"
},
"x86_64-linux": {
"store_path": "/nix/store/4arhczh30ychpx6h2wpj5nhx39wm0nla-nodejs-21.7.1"
"outputs": [
{
"name": "out",
"path": "/nix/store/6g9n96qf1yx139xklnmy3v4xhjvjgsji-nodejs-20.12.2",
"default": true
},
{
"name": "libv8",
"path": "/nix/store/s7b0dqga0311mvq48mirnlm0p3dr4gm3-nodejs-20.12.2-libv8"
}
],
"store_path": "/nix/store/6g9n96qf1yx139xklnmy3v4xhjvjgsji-nodejs-20.12.2"
}
}
},
"redis@latest": {
"last_modified": "2024-03-08T13:51:52Z",
"last_modified": "2024-05-22T06:18:38Z",
"plugin_version": "0.0.2",
"resolved": "github:NixOS/nixpkgs/a343533bccc62400e8a9560423486a3b6c11a23b#redis",
"resolved": "github:NixOS/nixpkgs/3f316d2a50699a78afe5e77ca486ad553169061e#redis",
"source": "devbox-search",
"version": "7.2.4",
"systems": {
"aarch64-darwin": {
"store_path": "/nix/store/700kyznxcmlkqqabhwa64vmyg4aj6igj-redis-7.2.4"
"store_path": "/nix/store/f1hmasfrq0s1yag27kmdwv97cjxv85rm-redis-7.2.4"
},
"aarch64-linux": {
"store_path": "/nix/store/xlc976dh4nb2aa0gzs0jfgld4cc3x8si-redis-7.2.4"
"store_path": "/nix/store/5ay0vkvk8l2n8fgxnmxdci1y1ldafnx8-redis-7.2.4"
},
"x86_64-darwin": {
"store_path": "/nix/store/5v58fadclljqa2fmwq281bx5wma9cslb-redis-7.2.4"
"store_path": "/nix/store/5l40xjgzqsd56dx5zfwr2v6sfpk12lkl-redis-7.2.4"
},
"x86_64-linux": {
"store_path": "/nix/store/zp1lapdicjxnjhiz8l6j2j4nn6sa9fg5-redis-7.2.4"
"store_path": "/nix/store/xlvzg81dgimxfjxpxwr2w3q1ca3l5lwa-redis-7.2.4"
}
}
}
Expand Down
106 changes: 60 additions & 46 deletions src/routes/rgbpp/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import { ZodTypeProvider } from 'fastify-type-provider-zod';
import { Cell, Script, XUDTBalance } from './types';
import { blockchain } from '@ckb-lumos/base';
import z from 'zod';
import { serializeScript } from '@nervosnetwork/ckb-sdk-utils';
import { Env } from '../../env';
import { getXudtTypeScript, isTypeAssetSupported, leToU128 } from '@rgbpp-sdk/ckb';
import { getXudtTypeScript, isScriptEqual, isTypeAssetSupported } from '@rgbpp-sdk/ckb';
import { BI } from '@ckb-lumos/lumos';
import { computeScriptHash } from '@ckb-lumos/lumos/utils';
import { UTXO } from '../../services/bitcoin/schema';

const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodTypeProvider> = (fastify, _, done) => {
const env: Env = fastify.container.resolve('env');
Expand Down Expand Up @@ -39,14 +38,20 @@ const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodType
}

/**
* Get RGB++ assets by btc address
* Get UTXOs by btc address
*/
async function getRgbppAssetsCells(btc_address: string, typeScript?: Script, no_cache?: string) {
async function getUxtos(btc_address: string, no_cache?: string) {
const utxos = await fastify.utxoSyncer.getUtxosByAddress(btc_address, no_cache === 'true');
if (env.UTXO_SYNC_DATA_CACHE_ENABLE) {
await fastify.utxoSyncer.enqueueSyncJob(btc_address);
}
return utxos;
}

/**
* Get RGB++ assets by btc address
*/
async function getRgbppAssetsCells(btc_address: string, utxos: UTXO[], no_cache?: string) {
const rgbppUtxoCellsPairs = await fastify.rgbppCollector.getRgbppUtxoCellsPairs(
btc_address,
utxos,
Expand All @@ -56,23 +61,26 @@ const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodType
await fastify.rgbppCollector.enqueueCollectJob(btc_address, utxos);
}
const cells = rgbppUtxoCellsPairs.map((pair) => pair.cells).flat();

if (typeScript) {
return cells.filter((cell) => {
if (!cell.cellOutput.type) {
return false;
}
// if typeScript.args is empty, only compare codeHash and hashType
if (!typeScript.args) {
const script = { ...cell.cellOutput.type, args: '' };
return serializeScript(script) === serializeScript(typeScript);
}
return serializeScript(cell.cellOutput.type) === serializeScript(typeScript);
});
}
return cells;
}

/**
* Filter cells by type script
*/
async function filterCellsByTypeScript(cells: Cell[], typeScript: Script) {
return cells.filter((cell) => {
if (!cell.cellOutput.type) {
return false;
}
// if typeScript.args is empty, only compare codeHash and hashType
if (!typeScript.args) {
const script = { ...cell.cellOutput.type, args: '' };
return isScriptEqual(script, typeScript);
}
return isScriptEqual(cell.cellOutput.type, typeScript);
});
}

fastify.get(
'/:btc_address/assets',
{
Expand Down Expand Up @@ -107,8 +115,10 @@ const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodType
async (request) => {
const { btc_address } = request.params;
const { no_cache } = request.query;
const utxos = await getUxtos(btc_address, no_cache);
const cells = await getRgbppAssetsCells(btc_address, utxos, no_cache);
const typeScript = getTypeScript(request);
return getRgbppAssetsCells(btc_address, typeScript, no_cache);
return typeScript ? filterCellsByTypeScript(cells, typeScript) : cells;
},
);

Expand Down Expand Up @@ -154,38 +164,42 @@ const addressRoutes: FastifyPluginCallback<Record<never, never>, Server, ZodType
if (!typeScript || !isTypeAssetSupported(typeScript, env.NETWORK === 'mainnet')) {
throw fastify.httpErrors.badRequest('Unsupported type asset');
}
const cells = await getRgbppAssetsCells(btc_address, typeScript, no_cache);

const scripts = fastify.ckb.getScripts();
if (serializeScript({ ...typeScript, args: '' }) !== serializeScript(scripts.XUDT)) {
if (!isScriptEqual({ ...typeScript, args: '' }, scripts.XUDT)) {
throw fastify.httpErrors.badRequest('Unsupported type asset');
}

const infoCellDataMap = new Map();
const allInfoCellTxs = await fastify.ckb.getAllInfoCellTxs();
const utxos = await getUxtos(btc_address, no_cache);
const xudtBalances: Record<string, XUDTBalance> = {};

for await (const cell of cells) {
const type = cell.cellOutput.type!;
const typeHash = computeScriptHash(type);
if (!infoCellDataMap.has(typeHash)) {
const infoCellData = fastify.ckb.getInfoCellData(allInfoCellTxs, type);
infoCellDataMap.set(typeHash, infoCellData);
}
const infoCellData = infoCellDataMap.get(typeHash);
const amount = BI.from(leToU128(cell.data)).toHexString();
if (infoCellData) {
if (!xudtBalances[typeHash]) {
xudtBalances[typeHash] = {
...infoCellData,
typeHash,
amount,
};
} else {
xudtBalances[typeHash].amount = BI.from(xudtBalances[typeHash].amount).add(BI.from(amount)).toHexString();
}
}
}
let cells = await getRgbppAssetsCells(btc_address, utxos, no_cache);
cells = typeScript ? await filterCellsByTypeScript(cells, typeScript) : cells;
const availableXudtBalances = await fastify.rgbppCollector.getRgbppBalanceByCells(cells);
Object.keys(availableXudtBalances).forEach((key) => {
const { amount, ...xudtInfo } = availableXudtBalances[key];
xudtBalances[key] = {
...xudtInfo,
total_amount: amount,
available_amount: amount,
pending_amount: '0x0',
};
});

const pendingUtxos = utxos.filter((utxo) => !utxo.status.confirmed);
const pendingTxids = Array.from(new Set(pendingUtxos.map((utxo) => utxo.txid)));
ShookLyngs marked this conversation as resolved.
Show resolved Hide resolved
let pendingOutputCells = await fastify.transactionProcessor.getPendingOuputCellsByTxids(pendingTxids);
pendingOutputCells = typeScript
? await filterCellsByTypeScript(pendingOutputCells, typeScript)
: pendingOutputCells;
const pendingXudtBalances = await fastify.rgbppCollector.getRgbppBalanceByCells(pendingOutputCells);
Object.values(pendingXudtBalances).forEach(({ amount, typeHash }) => {
xudtBalances[typeHash].pending_amount = BI.from(xudtBalances[typeHash].pending_amount)
Flouse marked this conversation as resolved.
Show resolved Hide resolved
.add(BI.from(amount))
.toHexString();
xudtBalances[typeHash].total_amount = BI.from(xudtBalances[typeHash].total_amount)
.add(BI.from(amount))
.toHexString();
});

return {
address: btc_address,
Expand Down
4 changes: 3 additions & 1 deletion src/routes/rgbpp/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ export const XUDTBalance = z.object({
name: z.string(),
decimal: z.number(),
symbol: z.string(),
amount: z.string(),
total_amount: z.string(),
available_amount: z.string(),
pending_amount: z.string(),
typeHash: z.string(),
});
export type XUDTBalance = z.infer<typeof XUDTBalance>;
8 changes: 4 additions & 4 deletions src/services/base/data-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,18 @@ export default class DataCache<T> {
this.expire = options.expire;
}

public async set(btcAddress: string, data: unknown) {
public async set(id: string, data: unknown) {
const parsed = this.schema.safeParse(data);
if (!parsed.success) {
throw new DataCacheError(parsed.error.message);
}
const key = `data-cache:${this.prefix}:${btcAddress}`;
const key = `data-cache:${this.prefix}:${id}`;
await this.redis.set(key, JSON.stringify(parsed.data), 'PX', this.expire);
return parsed.data;
}

public async get(btcAddress: string): Promise<T | null> {
const key = `data-cache:${this.prefix}:${btcAddress}`;
public async get(id: string): Promise<T | null> {
const key = `data-cache:${this.prefix}:${id}`;
const data = await this.redis.get(key);
if (data) {
const parsed = this.schema.safeParse(JSON.parse(data));
Expand Down
Loading
Loading