From a3799fefb048af2f7a64eea64a6fb1dd4b6cfb00 Mon Sep 17 00:00:00 2001 From: felix11 Date: Tue, 4 Aug 2026 16:30:25 +0400 Subject: [PATCH] chore(admin): move admin reads to gRPC and drop the JSON-RPC client --- src/admin/alphaVault.ts | 21 +++++++++--------- src/admin/rebalanceCap.ts | 11 +++++----- src/admin/slushAdmin.ts | 40 ++++++++++------------------------- src/core/index.ts | 1 + src/core/types.ts | 2 ++ src/models/blockchain.ts | 21 +++--------------- src/models/strategyContext.ts | 4 ++-- 7 files changed, 35 insertions(+), 65 deletions(-) diff --git a/src/admin/alphaVault.ts b/src/admin/alphaVault.ts index 7a1ce87..2a84933 100644 --- a/src/admin/alphaVault.ts +++ b/src/admin/alphaVault.ts @@ -4,7 +4,6 @@ */ import { Transaction } from '@mysten/sui/transactions'; -import { SuiMoveObject } from '@mysten/sui/jsonRpc'; import { StrategyContext } from '../models/strategyContext.js'; import { AlphaVaultPoolLabel } from '../strategies/alphaVault.js'; import { VERSIONS } from '../utils/constants.js'; @@ -57,19 +56,21 @@ export async function getWithdrawRequestsAndUnsuppliedAmount( context: StrategyContext, ): Promise { const label = await getAlphaLabel(context); - const pool = await context.blockchain.pythSuiClient.getObject({ - id: label.poolId, - options: { showContent: true }, + const { object } = await context.blockchain.suiGrpcClient.core.getObject({ + objectId: label.poolId, + include: { json: true }, }); - if (!pool.data?.content) throw new Error('Alpha pool data not found'); + const fields = object?.json as Record | undefined; + if (!fields) throw new Error('Alpha pool data not found'); - const fields = (pool.data.content as SuiMoveObject).fields as Record; const unsuppliedAmount = String(fields.unsupplied_balance ?? '0'); - const rawRequests = (fields.withdraw_requests as any)?.fields?.contents ?? []; + const withdrawRequestsField = fields.withdraw_requests as + | { contents?: { key: string; value: { leftover_amount: string } }[] } + | undefined; - const withdrawRequests = rawRequests.map((entry: any) => ({ - withdrawRequestAmount: entry.fields.value.fields.leftover_amount.toString(), - settleRequestTime: entry.fields.key.toString(), + const withdrawRequests = (withdrawRequestsField?.contents ?? []).map((entry) => ({ + withdrawRequestAmount: String(entry.value.leftover_amount), + settleRequestTime: String(entry.key), })); return { unsuppliedAmount, withdrawRequests }; diff --git a/src/admin/rebalanceCap.ts b/src/admin/rebalanceCap.ts index f54419d..d378eed 100644 --- a/src/admin/rebalanceCap.ts +++ b/src/admin/rebalanceCap.ts @@ -8,17 +8,16 @@ import { ADMIN } from '../utils/constants.js'; /** * Find the RebalanceCap object owned by `address` and return its object ID. * Throws if no cap is found (wallet doesn't have permission to rebalance). - * Uses JSON-RPC via `context.blockchain.pythSuiClient`. */ export async function getRebalanceCap(address: string, context: StrategyContext): Promise { - const rpc = context.blockchain.pythSuiClient; const rebalanceCapType = `${ADMIN.ALPHA_FIRST_PACKAGE_ID}::distributor::RebalanceCap`; - const data = await rpc.getOwnedObjects({ + const { objects } = await context.blockchain.suiGrpcClient.core.listOwnedObjects({ owner: address, - filter: { StructType: rebalanceCapType }, + type: rebalanceCapType, }); - if (!data.data[0]?.data) { + const objectId = objects[0]?.objectId; + if (!objectId) { throw new Error('no rebalance cap found'); } - return data.data[0].data.objectId; + return objectId; } diff --git a/src/admin/slushAdmin.ts b/src/admin/slushAdmin.ts index 07b9a6a..1607969 100644 --- a/src/admin/slushAdmin.ts +++ b/src/admin/slushAdmin.ts @@ -3,7 +3,6 @@ */ import { Transaction } from '@mysten/sui/transactions'; -import { SuiMoveObject, type SuiObjectResponse } from '@mysten/sui/jsonRpc'; import { Decimal } from 'decimal.js'; import { StrategyContext } from '../models/strategyContext.js'; import { @@ -51,21 +50,17 @@ function numberTypeToHuman(raw: string, tokenDecimals: number): number { export async function getWalLockedRewardInfo( context: StrategyContext, ): Promise { - const pool = await context.blockchain.pythSuiClient.getObject({ - id: ADMIN.ALPHA_SLUSH_WAL_LOOP_POOL_ID, - options: { showContent: true }, + const { object } = await context.blockchain.suiGrpcClient.core.getObject({ + objectId: ADMIN.ALPHA_SLUSH_WAL_LOOP_POOL_ID, + include: { json: true }, }); - if (!pool.data?.content || pool.data.content.dataType !== 'moveObject') { + const poolFields = object?.json as Record | undefined; + if (!poolFields) { throw new Error('WAL locked pool object not found on chain.'); } - const content = pool.data.content as SuiMoveObject; - const poolFields = content.fields as Record; - const externalRewardsInfo = poolFields.external_rewards_info as Record; - const rewardsInfo = - (externalRewardsInfo?.fields as Record) ?? externalRewardsInfo ?? null; - + const rewardsInfo = poolFields.external_rewards_info as Record | undefined; if (!rewardsInfo) { throw new Error('external_rewards_info field not found in pool object.'); } @@ -73,10 +68,8 @@ export async function getWalLockedRewardInfo( const endTimeMs = Number((rewardsInfo.end_time as string | undefined) ?? 0); if (endTimeMs === 0) return null; - const rewardPerMsField = rewardsInfo.reward_per_ms as Record; - const rewardPerMsFields = rewardPerMsField?.fields as Record | undefined; - const rewardPerMsRaw: string = - ((rewardPerMsFields?.value ?? rewardPerMsField?.value) as string | undefined) ?? '0'; + const rewardPerMs = rewardsInfo.reward_per_ms as Record | undefined; + const rewardPerMsRaw: string = (rewardPerMs?.value as string | undefined) ?? '0'; return { rewardPerMsHuman: numberTypeToHuman(rewardPerMsRaw, WAL_DECIMALS), @@ -103,24 +96,13 @@ export async function addExternalRewardsWalLockedTxb( endTimeMs: number, context: StrategyContext, ): Promise { - const rpc = context.blockchain.pythSuiClient; - // Find AdminCap owned by this wallet - const ownedCaps = await rpc.getOwnedObjects({ + const { objects: ownedCaps } = await context.blockchain.suiGrpcClient.core.listOwnedObjects({ owner: address, - filter: { - MoveModule: { - package: ADMIN.ALPHA_SLUSH_FIRST_PACKAGE_ID, - module: 'alphalend_slush_pool', - }, - }, - options: { showType: true }, + type: `${ADMIN.ALPHA_SLUSH_FIRST_PACKAGE_ID}::alphalend_slush_pool::AdminCap`, }); - const adminCapObject = ownedCaps.data.find((obj: SuiObjectResponse) => - obj.data?.type?.includes('::alphalend_slush_pool::AdminCap'), - ); - const adminCapId = adminCapObject?.data?.objectId; + const adminCapId = ownedCaps[0]?.objectId; if (!adminCapId) { throw new Error( `No AdminCap found for address ${address}. Ensure this wallet owns the locked loop AdminCap.`, diff --git a/src/core/index.ts b/src/core/index.ts index f5d55c1..8296f3f 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -74,6 +74,7 @@ export class AlphaFiSDK { config.network, config.graphqlUrl, config.apiBaseUrl, + config.grpcUrl, ); this.protocol = new Protocol(this.strategyContext); this.portfolio = new Portfolio(this.protocol, this.strategyContext); diff --git a/src/core/types.ts b/src/core/types.ts index c24ee59..5f19ed1 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -16,6 +16,8 @@ export interface AlphaFiSDKConfig { graphqlUrl?: string; /** Base URL for the AlphaFi API (defaults to 'https://api.alphafi.xyz') */ apiBaseUrl?: string; + /** Optional Sui gRPC endpoint override for object reads */ + grpcUrl?: string; } /** diff --git a/src/models/blockchain.ts b/src/models/blockchain.ts index a351ffa..6fc4b00 100644 --- a/src/models/blockchain.ts +++ b/src/models/blockchain.ts @@ -3,13 +3,10 @@ * * Reads and transaction simulation go through GraphQL (`gqlClient`), using * `gqlClient.core.simulateTransaction` for simulation (server-side build + gas - * resolution). A JSON-RPC client (`pythSuiClient`) is retained for the admin - * helpers that still need JSON-RPC object reads. A gRPC (v2 Core) client - * (`suiGrpcClient`) is used where a Core-capable client is required (e.g. the - * Navi reward `devInspect`). + * resolution). A gRPC (v2 Core) client (`suiGrpcClient`) serves object reads + * and anything needing a Core-capable client (e.g. the Navi reward `devInspect`). */ -import { SuiJsonRpcClient } from '@mysten/sui/jsonRpc'; import { SuiGraphQLClient } from '@mysten/sui/graphql'; import { SuiGrpcClient } from '@mysten/sui/grpc'; import { graphql } from '@mysten/sui/graphql/schema'; @@ -18,7 +15,6 @@ import { Network } from '@alphafi/alphalend-sdk'; export type BlockchainOptions = { network: Network; - pythSuiClient?: SuiJsonRpcClient; graphqlUrl?: string; grpcUrl?: string; }; @@ -27,9 +23,7 @@ export class Blockchain { network: Network; graphqlUrl: string; gqlClient: SuiGraphQLClient; - /** Retained for admin helpers that still need JSON-RPC object reads. */ - pythSuiClient: SuiJsonRpcClient; - /** gRPC (v2 Core) client for reads needing a Core-capable client (e.g. Navi reward `devInspect`). */ + /** gRPC (v2 Core) client for object reads and anything needing a Core-capable client. */ suiGrpcClient: SuiGrpcClient; constructor(options: BlockchainOptions) { @@ -39,15 +33,6 @@ export class Blockchain { (options.network === 'testnet' ? 'https://graphql.testnet.sui.io/graphql' : 'https://graphql.mainnet.sui.io/graphql'); - this.pythSuiClient = - options.pythSuiClient ?? - new SuiJsonRpcClient({ - url: - options.network === 'testnet' - ? 'https://fullnode.testnet.sui.io/' - : 'https://alphalen-suimain-ef6f.mainnet.sui.rpcpool.com/', - network: options.network === 'testnet' ? 'testnet' : 'mainnet', - }); this.gqlClient = new SuiGraphQLClient({ url: this.graphqlUrl, network: options.network === 'testnet' ? 'testnet' : 'mainnet', diff --git a/src/models/strategyContext.ts b/src/models/strategyContext.ts index 00d4db8..5202e7f 100644 --- a/src/models/strategyContext.ts +++ b/src/models/strategyContext.ts @@ -53,9 +53,9 @@ export class StrategyContext { private slushPositionsCache: Cache>; private alphaFiPositionsCache: Cache>; - constructor(network: Network, graphqlUrl?: string, apiBaseUrl?: string) { + constructor(network: Network, graphqlUrl?: string, apiBaseUrl?: string, grpcUrl?: string) { this.apiBaseUrl = apiBaseUrl ?? DEFAULT_API_BASE_URL; - this.blockchain = new Blockchain({ network, graphqlUrl }); + this.blockchain = new Blockchain({ network, graphqlUrl, grpcUrl }); this.coinInfoProvider = new CoinInfoProvider(); this.alphalendClient = new AlphalendClient(network, graphqlUrl);