Skip to content
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
21 changes: 11 additions & 10 deletions src/admin/alphaVault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -57,19 +56,21 @@ export async function getWithdrawRequestsAndUnsuppliedAmount(
context: StrategyContext,
): Promise<WithdrawRequestsAndUnsuppliedAmount> {
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<string, unknown> | undefined;
if (!fields) throw new Error('Alpha pool data not found');

const fields = (pool.data.content as SuiMoveObject).fields as Record<string, unknown>;
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 };
Expand Down
11 changes: 5 additions & 6 deletions src/admin/rebalanceCap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
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;
}
40 changes: 11 additions & 29 deletions src/admin/slushAdmin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -51,32 +50,26 @@ function numberTypeToHuman(raw: string, tokenDecimals: number): number {
export async function getWalLockedRewardInfo(
context: StrategyContext,
): Promise<WalLockedRewardInfo | null> {
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<string, unknown> | 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<string, unknown>;
const externalRewardsInfo = poolFields.external_rewards_info as Record<string, unknown>;
const rewardsInfo =
(externalRewardsInfo?.fields as Record<string, unknown>) ?? externalRewardsInfo ?? null;

const rewardsInfo = poolFields.external_rewards_info as Record<string, unknown> | undefined;
if (!rewardsInfo) {
throw new Error('external_rewards_info field not found in pool object.');
}

const endTimeMs = Number((rewardsInfo.end_time as string | undefined) ?? 0);
if (endTimeMs === 0) return null;

const rewardPerMsField = rewardsInfo.reward_per_ms as Record<string, unknown>;
const rewardPerMsFields = rewardPerMsField?.fields as Record<string, unknown> | undefined;
const rewardPerMsRaw: string =
((rewardPerMsFields?.value ?? rewardPerMsField?.value) as string | undefined) ?? '0';
const rewardPerMs = rewardsInfo.reward_per_ms as Record<string, unknown> | undefined;
const rewardPerMsRaw: string = (rewardPerMs?.value as string | undefined) ?? '0';

return {
rewardPerMsHuman: numberTypeToHuman(rewardPerMsRaw, WAL_DECIMALS),
Expand All @@ -103,24 +96,13 @@ export async function addExternalRewardsWalLockedTxb(
endTimeMs: number,
context: StrategyContext,
): Promise<void> {
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.`,
Expand Down
1 change: 1 addition & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down
21 changes: 3 additions & 18 deletions src/models/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -18,7 +15,6 @@ import { Network } from '@alphafi/alphalend-sdk';

export type BlockchainOptions = {
network: Network;
pythSuiClient?: SuiJsonRpcClient;
graphqlUrl?: string;
grpcUrl?: string;
};
Expand All @@ -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) {
Expand All @@ -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',
Expand Down
4 changes: 2 additions & 2 deletions src/models/strategyContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ export class StrategyContext {
private slushPositionsCache: Cache<string, Map<string, any[]>>;
private alphaFiPositionsCache: Cache<string, Map<string, any[]>>;

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);

Expand Down
Loading