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
53 changes: 22 additions & 31 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,26 +164,15 @@ export class BucketClient {
/* ----- Queries ----- */

/**
* @description
* @description Get total USDB supply via RPC.
* Uses stateService.getCoinInfo (avoids PTB reference limitation: borrow_cap_mut returns
* &mut which cannot be passed between commands; SDK v2 validates this and rejects).
*/
async getUsdbSupply(): Promise<bigint> {
const tx = new Transaction();

const treasury = tx.moveCall({
target: `${this.config.USDB_PACKAGE_ID}::usdb::borrow_cap_mut`,
arguments: [this.treasury(tx)],
});
tx.moveCall({
target: `0x2::coin::total_supply`,
typeArguments: [this.getUsdbCoinType()],
arguments: [treasury],
});
tx.setSender(DUMMY_ADDRESS);
const res = await this.suiClient.simulateTransaction({ transaction: tx, include: { commandResults: true } });
if (res.$kind === 'FailedTransaction' || !res.commandResults?.[1]?.returnValues) {
return 0n;
}
return BigInt(bcs.u64().parse(res.commandResults![1].returnValues[0].bcs));
const coinType = this.getUsdbCoinType();
const { response } = await this.suiClient.stateService.getCoinInfo({ coinType });
const supply = response.treasury?.totalSupply;
return supply !== undefined && supply !== null ? BigInt(supply) : 0n;
}

/**
Expand Down Expand Up @@ -668,16 +657,11 @@ export class BucketClient {
return;
}
poolInfo.reward.rewardTypes.forEach((rewardType) => {
const rewarder = tx.moveCall({
target: `${this.config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::get_rewarder`,
typeArguments: [lpType, rewardType],
arguments: [tx.sharedObjectRef(poolInfo.reward!.rewardManager)],
});
tx.moveCall({
target: `${this.config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::realtime_reward_amount`,
target: `${this.config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::get_realtime_reward_amount`,
typeArguments: [lpType, rewardType],
arguments: [
rewarder,
tx.sharedObjectRef(poolInfo.reward!.rewardManager),
tx.sharedObjectRef(poolInfo.pool),
tx.pure.address(accountId ?? address),
tx.object.clock(),
Expand All @@ -687,24 +671,31 @@ export class BucketClient {
});
tx.setSender(DUMMY_ADDRESS);
const res = await this.suiClient.simulateTransaction({ transaction: tx, include: { commandResults: true } });
if (res.$kind === 'FailedTransaction' || !res.commandResults) {
return {};
if (res.$kind === 'FailedTransaction') {
const err = (res as { FailedTransaction?: { status?: { error?: unknown } } }).FailedTransaction?.status?.error;
throw Object.assign(new Error('Failed to fetch account saving pool rewards'), { cause: err ?? res });
}
if (!res.commandResults) {
throw new Error('Failed to fetch account saving pool rewards');
}
return lpTypes.reduce((result, lpType) => {
const poolInfo = this.getSavingPoolObjectInfo({ lpType });

if (!poolInfo.reward?.rewardTypes?.length) {
return result;
}
const responses = res.commandResults!.splice(0, 2 * poolInfo.reward.rewardTypes.length);
const responses = res.commandResults!.splice(0, poolInfo.reward.rewardTypes.length);

return {
...result,
[lpType]: poolInfo.reward.rewardTypes.reduce((result, rewardType, index) => {
if (!responses[index]?.returnValues) {
return result;
const amountRes = responses[index]?.returnValues;
if (!amountRes) {
throw new Error(
`Failed to fetch account saving pool rewards: missing result for ${lpType} reward ${rewardType}`,
);
}
const realtimeReward = bcs.u64().parse(responses[index + 1].returnValues[0].bcs);
const realtimeReward = bcs.u64().parse(amountRes[0].bcs);

return { ...result, [rewardType]: BigInt(realtimeReward) };
}, {}),
Expand Down
2 changes: 1 addition & 1 deletion src/consts/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const CONFIG: Record<Network, ConfigType> = {
PSM_PACKAGE_ID: '0x8a46afe6ab0b972e2ee3923f5669104ae9465c12b84cd5e78ea1ff6404e4bfaf',
FLASH_PACKAGE_ID: '0xca00bbb7ecfe11dbb527efab6145b358f2a95aa11a8aae9b9f475176430cf42a',
SAVING_PACKAGE_ID: '0x5b60c3cdbb9ee31b856f7f8a6d4d6c8b91e3c0036a138fb9927529b05fd00373',
SAVING_INCENTIVE_PACKAGE_ID: '0x39692320d6fc01c27315a7972ed4717e4fd32eed43531ad6f55fd7f24b74e207',
SAVING_INCENTIVE_PACKAGE_ID: '0xd419027a4b92b1916c62c08da27520576bcf34d0a8c40884491dca2f5d992719',
BORROW_INCENTIVE_PACKAGE_ID: '0xef9719a3392d3742ae09c42ebf01030d88b1a75651430932be1eb017b79fb372',
BLACKLIST_PACKAGE_ID: '0x6d7b711a5e614f99f87fdbfbf915fea0fa96af769130ac226c3cbcf07efb90f3',

Expand Down
54 changes: 48 additions & 6 deletions test/e2e/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,39 @@ import { coinWithBalance, destroyZeroCoin, getZeroCoin } from '../../src/utils/t
const MAINNET_TIMEOUT_MS = 20_000;
const network = 'mainnet';
const testAccount = '0x7a718956581fbe4a568d135fef5161024e74af87a073a1489e57ebef53744652';
const suiClient = new SuiGrpcClient({ network, baseUrl: 'https://fullnode.mainnet.sui.io:443' });
// Use SUI_GRPC_URL for custom RPC (avoids public mainnet rate limits)
const rpcUrl = process.env.SUI_GRPC_URL ?? 'https://fullnode.mainnet.sui.io:443';
const suiClient = new SuiGrpcClient({ network, baseUrl: rpcUrl });
const bucketClient = new BucketClient({ suiClient, network });
const usdbCoinType = bucketClient.getUsdbCoinType();
const usdcCoinType = '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC';
const susdbLpType = '0x38f61c75fa8407140294c84167dd57684580b55c3066883b48dedc344b1cde1e::susdb::SUSDB';

describe('Interacting with Bucket Client on mainnet', () => {
describe('Oracle', () => {
it(
'getOraclePrices returns positive numbers for SUI (validates oracle flow)',
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

這部分不測全部了嗎?

Copy link
Copy Markdown
Contributor Author

@do0x0ob do0x0ob Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

先前因為避開 sCoin 升級問題修改,已經在 commit 382a46e
中加回,測試通過
image

async () => {
const prices = await bucketClient.getOraclePrices({ coinTypes: [SUI_TYPE_ARG] });
expect(Object.keys(prices).length).toBeGreaterThan(0);
expect(prices[SUI_TYPE_ARG]).toBeDefined();
expect(typeof prices[SUI_TYPE_ARG]).toBe('number');
expect(prices[SUI_TYPE_ARG]).toBeGreaterThan(0);
},
MAINNET_TIMEOUT_MS,
);

it(
'getAllOraclePrices returns positive numbers for all oracle coin types',
async () => {
const prices = await bucketClient.getAllOraclePrices();
const coinTypes = bucketClient.getAllOracleCoinTypes();
expect(Object.keys(prices).length).toBeGreaterThan(0);
expect(coinTypes.length).toBeGreaterThan(0);
expect(Object.keys(prices).length).toBe(coinTypes.length);
for (const coinType of coinTypes) {
const price = prices[coinType];
expect(price).toBeDefined();
expect(typeof price).toBe('number');
expect(price).toBeGreaterThan(0);
expect(prices[coinType]).toBeDefined();
expect(typeof prices[coinType]).toBe('number');
expect(prices[coinType]).toBeGreaterThan(0);
}
},
MAINNET_TIMEOUT_MS,
Expand All @@ -49,6 +63,16 @@ describe('Interacting with Bucket Client on mainnet', () => {
MAINNET_TIMEOUT_MS,
);

it(
'getUsdbSupply returns positive bigint (mainnet has circulating USDB)',
async () => {
const supply = await bucketClient.getUsdbSupply();
expect(typeof supply).toBe('bigint');
expect(supply).toBeGreaterThan(0n);
},
MAINNET_TIMEOUT_MS,
);

it(
'getFlashMintInfo returns feeRate and partner config',
async () => {
Expand Down Expand Up @@ -76,6 +100,24 @@ describe('Interacting with Bucket Client on mainnet', () => {
});

describe('Rewards', () => {
it(
'getAccountSavingPoolRewards returns record for SUSDB (validates SAVING_INCENTIVE_PACKAGE_ID)',
async () => {
const rewards = await bucketClient.getAccountSavingPoolRewards({
address: testAccount,
lpTypes: [susdbLpType],
});
expect(typeof rewards).toBe('object');
expect(rewards).toHaveProperty(susdbLpType);
const susdbRewards = rewards[susdbLpType]!;
for (const [, amount] of Object.entries(susdbRewards)) {
expect(typeof amount).toBe('bigint');
expect(amount).toBeGreaterThanOrEqual(0n);
}
},
MAINNET_TIMEOUT_MS,
);

it(
'buildClaimBorrowRewardsTransaction dry run succeeds and has reward balance changes',
async () => {
Expand Down
Loading