feat: integrate luxnet SDK for P-chain API calls

Replace hand-rolled JSON-RPC fetch client with [email protected].0 SDK.
P-chain hooks now use PlatformVMAPI methods directly.
This commit is contained in:
2026-02-27 10:55:58 -08:00
parent 126693f20d
commit df89ce82e7
9 changed files with 908 additions and 123 deletions
+34
View File
@@ -0,0 +1,34 @@
import Lux from 'luxnet';
import { getEnvValue } from 'configs/app/utils';
const CCHAIN_RPC_PATTERN = /\/ext\/bc\/C\/rpc$/;
const HTTPS_PORT = 443;
const HTTP_PORT = 80;
function parseRpcUrl(): { host: string; port: number; protocol: string } {
const rpcUrl = getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL');
if (!rpcUrl) {
throw new Error('NEXT_PUBLIC_NETWORK_RPC_URL is not configured');
}
const base = rpcUrl.replace(CCHAIN_RPC_PATTERN, '');
const url = new URL(base);
const defaultPort = url.protocol === 'https:' ? HTTPS_PORT : HTTP_PORT;
return {
host: url.hostname,
port: url.port ? Number(url.port) : defaultPort,
protocol: url.protocol.replace(':', ''),
};
}
let instance: Lux | undefined;
export function getLux(): Lux {
if (!instance) {
const { host, port, protocol } = parseRpcUrl();
instance = new Lux(host, port, protocol);
}
return instance;
}
+8 -68
View File
@@ -1,73 +1,13 @@
// Low-level P-chain JSON-RPC client.
// Derives the P-chain URL from NEXT_PUBLIC_NETWORK_RPC_URL by replacing the
// C-chain path (/ext/bc/C/rpc) with the P-chain path (/ext/bc/P).
import { getLux } from 'lib/api/luxnet/instance';
import type { JsonRpcRequest, JsonRpcResponse } from './types';
import { getEnvValue } from 'configs/app/utils';
const PCHAIN_PATH = '/ext/bc/P';
const CCHAIN_PATH_PATTERN = /\/ext\/bc\/C\/rpc$/;
let requestId = 0;
function getPChainUrl(): string {
const rpcUrl = getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL');
if (!rpcUrl) {
throw new Error('NEXT_PUBLIC_NETWORK_RPC_URL is not configured');
}
// Replace C-chain path with P-chain path
if (CCHAIN_PATH_PATTERN.test(rpcUrl)) {
return rpcUrl.replace(CCHAIN_PATH_PATTERN, PCHAIN_PATH);
}
// Fallback: strip trailing slash and append P-chain path
const base = rpcUrl.replace(/\/+$/, '');
try {
const url = new URL(base);
return `${ url.origin }${ PCHAIN_PATH }`;
} catch {
throw new Error(`Invalid NEXT_PUBLIC_NETWORK_RPC_URL: ${ rpcUrl }`);
}
export function getPChain() {
return getLux().PChain();
}
export async function pchainRpc<TResult>(
method: string,
params?: Record<string, unknown>,
): Promise<TResult> {
const url = getPChainUrl();
requestId += 1;
const body: JsonRpcRequest = {
jsonrpc: '2.0',
id: requestId,
method,
...(params !== undefined ? { params } : {}),
};
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error(`P-chain RPC error: HTTP ${ response.status }`);
}
const json = await response.json() as JsonRpcResponse<TResult>;
if (json.error) {
throw new Error(`P-chain RPC error: ${ json.error.message } (code ${ json.error.code })`);
}
if (json.result === undefined) {
throw new Error('P-chain RPC error: missing result in response');
}
return json.result;
export function getInfo() {
return getLux().Info();
}
export { getPChainUrl };
export function getHealth() {
return getLux().Health();
}
+1 -4
View File
@@ -1,6 +1,6 @@
// P-chain API hooks and types.
export { pchainRpc, getPChainUrl } from './client';
export { getPChain, getInfo, getHealth } from './client';
export { useCurrentValidators } from './useCurrentValidators';
export { useBlockchains } from './useBlockchains';
export { useSubnets } from './useSubnets';
@@ -15,9 +15,6 @@ export type {
GetBlockchainsResponse,
GetSubnetsResponse,
ValidatorStats,
JsonRpcRequest,
JsonRpcResponse,
JsonRpcError,
} from './types';
export type { UseCurrentValidatorsResult } from './useCurrentValidators';
-22
View File
@@ -67,25 +67,3 @@ export interface ValidatorStats {
readonly totalDelegatedStake: bigint;
readonly averageUptime: number;
}
// JSON-RPC envelope types
export interface JsonRpcRequest {
readonly jsonrpc: '2.0';
readonly id: number;
readonly method: string;
readonly params?: Record<string, unknown>;
}
export interface JsonRpcResponse<TResult> {
readonly jsonrpc: '2.0';
readonly id: number;
readonly result?: TResult;
readonly error?: JsonRpcError;
}
export interface JsonRpcError {
readonly code: number;
readonly message: string;
readonly data?: unknown;
}
+4 -11
View File
@@ -4,24 +4,17 @@
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import type {
GetBlockchainsResponse,
PChainBlockchain,
} from './types';
import type { PChainBlockchain } from './types';
import { pchainRpc } from './client';
import { getPChain } from './client';
const BLOCKCHAINS_STALE_TIME_MS = 300_000;
const BLOCKCHAINS_QUERY_KEY = 'pchain:blockchains' as const;
const EMPTY_BLOCKCHAINS: ReadonlyArray<PChainBlockchain> = [];
async function fetchBlockchains(): Promise<ReadonlyArray<PChainBlockchain>> {
const response = await pchainRpc<GetBlockchainsResponse>(
'platform.getBlockchains',
{},
);
return response.blockchains;
const blockchains = await getPChain().getBlockchains();
return blockchains as unknown as ReadonlyArray<PChainBlockchain>;
}
export function useBlockchains() {
+3 -7
View File
@@ -10,7 +10,7 @@ import type {
ValidatorStats,
} from './types';
import { pchainRpc } from './client';
import { getPChain } from './client';
const VALIDATORS_STALE_TIME_MS = 60_000;
const VALIDATORS_QUERY_KEY = 'pchain:currentValidators' as const;
@@ -63,12 +63,8 @@ export interface UseCurrentValidatorsResult {
}
async function fetchCurrentValidators(): Promise<UseCurrentValidatorsResult> {
const response = await pchainRpc<GetCurrentValidatorsResponse>(
'platform.getCurrentValidators',
{},
);
const validators = response.validators;
const result = await getPChain().getCurrentValidators() as GetCurrentValidatorsResponse;
const validators = result.validators;
const stats = computeValidatorStats(validators);
return { validators, stats };
+4 -11
View File
@@ -4,24 +4,17 @@
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import type {
GetSubnetsResponse,
PChainSubnet,
} from './types';
import type { PChainSubnet } from './types';
import { pchainRpc } from './client';
import { getPChain } from './client';
const SUBNETS_STALE_TIME_MS = 300_000;
const SUBNETS_QUERY_KEY = 'pchain:subnets' as const;
const EMPTY_SUBNETS: ReadonlyArray<PChainSubnet> = [];
async function fetchSubnets(): Promise<ReadonlyArray<PChainSubnet>> {
const response = await pchainRpc<GetSubnetsResponse>(
'platform.getSubnets',
{},
);
return response.subnets;
const subnets = await getPChain().getSubnets();
return subnets as unknown as ReadonlyArray<PChainSubnet>;
}
export function useSubnets() {
+1
View File
@@ -98,6 +98,7 @@
"graphql": "^16.8.1",
"graphql-ws": "^5.11.3",
"js-cookie": "^3.0.1",
"luxnet": "3.16.0",
"magic-bytes.js": "1.8.0",
"mixpanel-browser": "2.67.0",
"monaco-editor": "^0.34.1",
+853
View File
File diff suppressed because it is too large Load Diff