diff --git a/README.md b/README.md index 3840fff..164da8d 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,13 @@ npm install @bucket-protocol/sdk import { BucketClient } from '@bucket-protocol/sdk'; import { SuiGrpcClient } from '@mysten/sui/grpc'; -// Create client with config fetched from chain (recommended) +// Recommended: Create client with config fetched from chain (waits for config to be ready) const client = await BucketClient.initialize({ network: 'mainnet' }); +// Alternative: Sync constructor (config loads in background; config-dependent methods are async) +// const client = new BucketClient({ network: 'mainnet' }); +// Then use await client.getConfig(), await client.getAllCollateralTypes(), etc. + // With custom SuiGrpcClient const customSuiClient = new SuiGrpcClient({ network: 'mainnet', @@ -35,8 +39,8 @@ const client = await BucketClient.initialize({ ### Basic Queries ```typescript -// Get all supported collateral types -const collateralTypes = client.getAllCollateralTypes(); +// Get all supported collateral types (async when using sync constructor) +const collateralTypes = await client.getAllCollateralTypes(); console.log('Supported collaterals:', collateralTypes); // Example: ['0x2::sui::SUI', '0x...::btc::BTC', ...] @@ -117,7 +121,7 @@ await client.buildManagePositionTransaction(tx, { // Close entire position (repay all debt, withdraw all collateral) const tx = new Transaction(); const address = keypair.getPublicKey().toSuiAddress(); -client.buildClosePositionTransaction(tx, { +await client.buildClosePositionTransaction(tx, { coinType: SUI_TYPE_ARG, address, }); @@ -239,7 +243,7 @@ const usdbAmount = 100 * 10 ** 6; // 100 USDB const SUSDB_TYPE = '0x38f61c75fa8407140294c84167dd57684580b55c3066883b48dedc344b1cde1e::susdb::SUSDB'; const userAddress = keypair.getPublicKey().toSuiAddress(); -client.buildDepositToSavingPoolTransaction(tx, { +await client.buildDepositToSavingPoolTransaction(tx, { lpType: SUSDB_TYPE, address: userAddress, depositCoinOrAmount: usdbAmount, // Can also pass a coin object @@ -260,7 +264,7 @@ const tx = new Transaction(); // Withdraw 50 LP tokens worth of USDB const lpAmount = 50 * 10 ** 6; // 50 SUSDB (LP tokens) -const usdbCoin = client.buildWithdrawFromSavingPoolTransaction(tx, { +const usdbCoin = await client.buildWithdrawFromSavingPoolTransaction(tx, { lpType: SUSDB_TYPE, amount: lpAmount, }); @@ -281,7 +285,7 @@ const result = await client.getSuiClient().signAndExecuteTransaction({ const tx = new Transaction(); // Claim all available rewards -const rewardsRecord = client.buildClaimSavingRewardsTransaction(tx, { +const rewardsRecord = await client.buildClaimSavingRewardsTransaction(tx, { lpType: SUSDB_TYPE, }); @@ -321,14 +325,14 @@ const tx = new Transaction(); // Deposit zero to distribute interest const zeroUsdb = tx.splitCoins(tx.gas, [0]); // Zero coin -client.buildDepositToSavingPoolTransaction(tx, { +await client.buildDepositToSavingPoolTransaction(tx, { lpType: SUSDB_TYPE, address: userAddress, depositCoinOrAmount: zeroUsdb, }); // Withdraw zero to claim accumulated interest -const accruedUsdb = client.buildWithdrawFromSavingPoolTransaction(tx, { +const accruedUsdb = await client.buildWithdrawFromSavingPoolTransaction(tx, { lpType: SUSDB_TYPE, amount: 0, }); @@ -347,7 +351,7 @@ const tx = new Transaction(); // Flash mint 1000 USDB const amount = 1000 * 10 ** 6; // 1000 USDB -const [usdbCoin, flashReceipt] = client.flashMint(tx, { amount }); +const [usdbCoin, flashReceipt] = await client.flashMint(tx, { amount }); // Use USDB for operations... // (e.g., arbitrage, liquidation, swaps) @@ -360,7 +364,7 @@ const totalRepayment = amount + feeAmount; // ... // Burn to repay flash loan -client.flashBurn(tx, { usdbCoin, flashMintReceipt: flashReceipt }); +await client.flashBurn(tx, { usdbCoin, flashMintReceipt: flashReceipt }); ``` ### 7. Integration Patterns @@ -378,7 +382,7 @@ const usdbCoin = await client.buildPSMSwapInTransaction(tx, { }); // Step 2: Deposit USDB to saving pool -client.buildDepositToSavingPoolTransaction(tx, { +await client.buildDepositToSavingPoolTransaction(tx, { lpType: SUSDB_TYPE, address: userAddress, depositCoinOrAmount: usdbCoin, @@ -398,7 +402,7 @@ const tx = new Transaction(); // Step 1: Flash mint USDB const amount = 1000 * 10 ** 6; // 1000 USDB -const [usdbCoin, flashReceipt] = client.flashMint(tx, { amount }); +const [usdbCoin, flashReceipt] = await client.flashMint(tx, { amount }); // Step 2: Use USDB for operations... // (e.g., arbitrage, liquidation, etc.) @@ -412,7 +416,7 @@ const feeUsdbCoin = await client.buildPSMSwapInTransaction(tx, { // Step 4: Repay flash loan tx.mergeCoins(usdbCoin, [feeUsdbCoin]); -client.flashBurn(tx, { usdbCoin, flashMintReceipt: flashReceipt }); +await client.flashBurn(tx, { usdbCoin, flashMintReceipt: flashReceipt }); tx.setSender(userAddress); const result = await client.getSuiClient().signAndExecuteTransaction({ @@ -437,7 +441,7 @@ const usdbCoin = await client.buildPSMSwapInTransaction(tx, { }); // Deposit to saving pool with partner account -client.buildDepositToSavingPoolTransaction(tx, { +await client.buildDepositToSavingPoolTransaction(tx, { accountObjectOrId: partnerAccountId, lpType: SUSDB_TYPE, address: userAddress, @@ -508,7 +512,7 @@ const allPrices = await client.getAllOraclePrices(); ```typescript // Create a price collector (within a transaction) const tx = new Transaction(); -const collector = client.newPriceCollector(tx, { coinType: SUI_TYPE_ARG }); +const collector = await client.newPriceCollector(tx, { coinType: SUI_TYPE_ARG }); ``` ## Advanced Usage @@ -524,26 +528,26 @@ const coinTypes = [SUI_TYPE_ARG]; const [priceResult] = await client.aggregatePrices(tx, { coinTypes }); // Create debtor request -const debtorReq = client.debtorRequest(tx, { +const debtorReq = await client.debtorRequest(tx, { coinType: SUI_TYPE_ARG, borrowAmount: 1 * 10 ** 6, }); // Check update position request -const updateRequest = client.checkUpdatePositionRequest(tx, { +const updateRequest = await client.checkUpdatePositionRequest(tx, { coinType: SUI_TYPE_ARG, request: debtorReq, }); // Update position -const [collCoin, usdbCoin, response] = client.updatePosition(tx, { +const [collCoin, usdbCoin, response] = await client.updatePosition(tx, { coinType: SUI_TYPE_ARG, updateRequest, priceResult, }); // Check response -client.checkUpdatePositionResponse(tx, { +await client.checkUpdatePositionResponse(tx, { coinType: SUI_TYPE_ARG, response, }); @@ -743,10 +747,10 @@ The new version separates price aggregator configuration from vault configuratio ```typescript // Get aggregator information -const aggInfo = client.getAggregatorObjectInfo({ coinType: SUI_TYPE_ARG }); +const aggInfo = await client.getAggregatorObjectInfo({ coinType: SUI_TYPE_ARG }); // Get vault information -const vaultInfo = client.getVaultObjectInfo({ coinType: SUI_TYPE_ARG }); +const vaultInfo = await client.getVaultObjectInfo({ coinType: SUI_TYPE_ARG }); ``` ### Enhanced Price Aggregation @@ -770,7 +774,7 @@ const priceResults = await client.aggregatePrices(tx, { ```typescript // Get USDB token type -const usdbType = client.getUsdbCoinType(); +const usdbType = await client.getUsdbCoinType(); console.log('USDB Type:', usdbType); ``` @@ -780,10 +784,10 @@ console.log('USDB Type:', usdbType); const tx = new Transaction(); // Create account request for EOA (Externally Owned Account) -const accountRequest = client.newAccountRequest(tx, {}); +const accountRequest = await client.newAccountRequest(tx, {}); // Create account request with account object -const accountRequest = client.newAccountRequest(tx, { +const accountRequest = await client.newAccountRequest(tx, { accountObjectOrId: '0x...account-object-id', }); ``` @@ -817,26 +821,31 @@ For complete usage examples, refer to the [test/e2e/](./test/e2e/) directory (e. **Recommended: `BucketClient.initialize()` (async factory)** -Fetches config from chain and returns a ready-to-use client: +Creates a client and waits for config to be ready. Config is fetched from chain when not provided: ```typescript const client = await BucketClient.initialize({ suiClient, // Optional: custom SuiGrpcClient network, // Optional: 'mainnet' | 'testnet' (default: 'mainnet') + configObjectId, // Optional: override entry config object ID + config, // Optional: pre-built config (skips chain fetch) configOverrides, // Optional: e.g. { PRICE_SERVICE_ENDPOINT: 'https://...' } }); ``` -**Advanced: Constructor (requires `config`)** +**Alternative: Sync constructor** -For custom config (e.g. caching, testing, or pre-built config): +For dependency injection or when you need sync instantiation. Config loads in background; methods that use config (`getConfig`, `getAllCollateralTypes`, `buildClosePositionTransaction`, etc.) are async: ```typescript const client = new BucketClient({ suiClient?: SuiGrpcClient; network?: Network; - config: ConfigType; // Required — use convertOnchainConfig() if building from chain data + configObjectId?: string; + config?: ConfigType; // Optional: pre-built config + configOverrides?: Partial; }); +// Use await client.getConfig() before calling config-dependent methods ``` ### Transaction Options @@ -932,7 +941,7 @@ console.log('Available balance:', coins); ```typescript // Use supported collateral types -const supportedTypes = client.getAllCollateralTypes(); +const supportedTypes = await client.getAllCollateralTypes(); console.log('Supported types:', supportedTypes); ``` @@ -940,7 +949,7 @@ console.log('Supported types:', supportedTypes); ```typescript // Ensure the coin type has a valid price feed -const aggInfo = client.getAggregatorObjectInfo({ coinType: SUI_TYPE_ARG }); +const aggInfo = await client.getAggregatorObjectInfo({ coinType: SUI_TYPE_ARG }); console.log('Pyth Price ID:', aggInfo.pythPriceId); ``` diff --git a/src/client.ts b/src/client.ts index ba748f8..6b5576b 100644 --- a/src/client.ts +++ b/src/client.ts @@ -70,48 +70,64 @@ export class BucketClient { * @description a TypeScript wrapper over Bucket V2 client. * Use `BucketClient.initialize()` to create a client. Config is required. */ - private config: ConfigType; - private configOverrides?: Partial; private configObjectId?: string; + private configParam?: ConfigType; + private configOverrides?: Partial; + private config?: ConfigType; + private configLoadingPromise?: Promise; private suiClient: SuiGrpcClient; private network: Network; private pythCache = new PythCache(); + /** + * @description Creates a BucketClient with config fetched from on-chain. + * + * @param configObjectId - Optional. Override the default entry config object ID (e.g. for testing). + * When provided, refreshConfig() will use the same source. + * @param config - Optional. Pre-built config for testing; skips chain fetch when provided. + * When config is provided, configOverrides are not applied to the initial config; they are + * stored for use on refreshConfig() only. + * @param configOverrides - Optional overrides (e.g. PRICE_SERVICE_ENDPOINT). Applied when + * fetching from chain; when config is provided, used only after refreshConfig(). + */ constructor({ suiClient, network = 'mainnet', - config, + configObjectId, + config: configParam, + configOverrides, }: { suiClient?: SuiGrpcClient; network?: Network; - config: ConfigType; + configObjectId?: string; + config?: ConfigType; + configOverrides?: Partial; }) { - if (!config) { - throw new Error('BucketClient requires config. Use BucketClient.initialize() or pass config to the constructor.'); - } - this.config = config; - this.network = network; const rpcUrl = NETWORK_RPC_URLS[network] ?? NETWORK_RPC_URLS['mainnet']!; + + this.network = network; this.suiClient = suiClient ?? new SuiGrpcClient({ network, baseUrl: rpcUrl }); + + this.configParam = configParam; + this.configObjectId = configObjectId; + this.configOverrides = configOverrides; + + this.configLoadingPromise = this.loadConfig(); } /** - * @description Creates a BucketClient with config fetched from on-chain. - * This is the only way to create a client when not passing a pre-built config. + * @description Creates a BucketClient and waits for config to be ready. + * Prefer this over `new BucketClient(...)` when you need config to be available immediately. * - * @param configObjectId - Optional. Override the default entry config object ID (e.g. for testing). - * When provided, refreshConfig() will use the same source. + * @param configObjectId - Optional. Override the default entry config object ID. * @param config - Optional. Pre-built config for testing; skips chain fetch when provided. - * When config is provided, configOverrides are not applied to the initial config; they are - * stored for use on refreshConfig() only. - * @param configOverrides - Optional overrides (e.g. PRICE_SERVICE_ENDPOINT). Applied when - * fetching from chain; when config is provided, used only after refreshConfig(). + * @param configOverrides - Optional overrides (e.g. PRICE_SERVICE_ENDPOINT). */ static async initialize({ suiClient, network = 'mainnet', configObjectId, - config: configParam, + config, configOverrides, }: { suiClient?: SuiGrpcClient; @@ -120,22 +136,36 @@ export class BucketClient { config?: ConfigType; configOverrides?: Partial; } = {}): Promise { - const rpcUrl = NETWORK_RPC_URLS[network] ?? NETWORK_RPC_URLS['mainnet']!; - const client = suiClient ?? new SuiGrpcClient({ network, baseUrl: rpcUrl }); + const bc = new BucketClient({ suiClient, network, configObjectId, config, configOverrides }); + await bc.getConfig(); + return bc; + } - let config: ConfigType; - if (configParam) { - config = await enrichSharedObjectRefs(configParam, client); + private async loadConfig(): Promise { + if (this.configParam) { + this.config = await enrichSharedObjectRefs(this.configParam, this.suiClient); } else { - const onchainConfig = await queryAllConfig(client, network, configObjectId); - config = convertOnchainConfig(onchainConfig, configOverrides); - config = await enrichSharedObjectRefs(config, client); + await this.fetchConfig(); } + } - const bc = new BucketClient({ suiClient: client, network, config }); - bc.configOverrides = configOverrides; - bc.configObjectId = configObjectId; - return bc; + /** + * @description Re-fetch config from on-chain and update this client's config. + * When overrides is omitted, preserves configOverrides from initialize(). + * Uses the same config source (configObjectId) as initialize() when one was provided. + */ + async refreshConfig(overrides?: Partial): Promise { + await this.configLoadingPromise; + + this.configLoadingPromise = this.fetchConfig(overrides); + + return this.configLoadingPromise; + } + + private async fetchConfig(overrides?: Partial): Promise { + const onchainConfig = await queryAllConfig(this.suiClient, this.network, this.configObjectId); + const config = convertOnchainConfig(onchainConfig, overrides ?? this.configOverrides); + this.config = await enrichSharedObjectRefs(config, this.suiClient); } /* ----- Getter ----- */ @@ -150,25 +180,17 @@ export class BucketClient { /** * @description */ - getConfig(): ConfigType { - return this.config; - } + async getConfig(): Promise { + await this.configLoadingPromise; - /** - * @description Re-fetch config from on-chain and update this client's config. - * When overrides is omitted, preserves configOverrides from initialize(). - * Uses the same config source (configObjectId) as initialize() when one was provided. - */ - async refreshConfig(overrides?: Partial): Promise { - const onchainConfig = await queryAllConfig(this.suiClient, this.network, this.configObjectId); - const config = convertOnchainConfig(onchainConfig, overrides ?? this.configOverrides); - this.config = await enrichSharedObjectRefs(config, this.suiClient); + return this.config!; } /** * @description Get a price config info entry matching the given derivative kind. */ - private getPriceConfigInfo(derivativeKind: DerivativeKind): PriceConfigInfo { + private async getPriceConfigInfo(derivativeKind: DerivativeKind): Promise { + const config = await this.getConfig(); const variantMap: Partial> = { sCoin: 'SCOIN', gCoin: 'GCOIN', @@ -178,25 +200,27 @@ export class BucketClient { if (!targetVariant) { throw new Error(`No PriceConfigInfo mapping for derivativeKind "${derivativeKind}".`); } - for (const info of Object.values(this.config.PRICE_OBJS)) { + for (const info of Object.values(config.PRICE_OBJS)) { if (targetVariant in info) return info; } throw new Error(`PriceConfigInfo for derivativeKind "${derivativeKind}" not found in PRICE_OBJS.`); } - getUsdbCoinType(): string { - return `${this.config.ORIGINAL_USDB_PACKAGE_ID}::usdb::USDB`; + async getUsdbCoinType(): Promise { + const config = await this.getConfig(); + return `${config.ORIGINAL_USDB_PACKAGE_ID}::usdb::USDB`; } /** * @description Get all Oracle coin types that have a valid Pyth price (basic or derivative with priceable underlying). */ - getAllOracleCoinTypes(): string[] { + async getAllOracleCoinTypes(): Promise { + const config = await this.getConfig(); const isPriceable = (coinType: string, visiting: Set): boolean => { const normalized = normalizeStructTag(coinType); if (visiting.has(normalized)) return false; visiting.add(normalized); - const aggregatorInfo = this.config.AGGREGATOR_OBJS[normalized]; + const aggregatorInfo = config.AGGREGATOR_OBJS[normalized]; if (!aggregatorInfo) return false; if ('Pyth' in aggregatorInfo && aggregatorInfo.Pyth) { return isValidPythPriceId(aggregatorInfo.Pyth.pythPriceId); @@ -206,18 +230,20 @@ export class BucketClient { } return false; }; - return Object.keys(this.config.AGGREGATOR_OBJS).filter((coinType) => isPriceable(coinType, new Set())); + return Object.keys(config.AGGREGATOR_OBJS).filter((coinType) => isPriceable(coinType, new Set())); } /** * @description Get all CDP collateral types */ - getAllCollateralTypes(): string[] { - return Object.keys(this.config.VAULT_OBJS); + async getAllCollateralTypes(): Promise { + const config = await this.getConfig(); + return Object.keys(config.VAULT_OBJS); } - getAggregatorObjectInfo({ coinType }: { coinType: string }): AggregatorObjectInfo { - const aggregatorInfo = this.config.AGGREGATOR_OBJS[normalizeStructTag(coinType)]; + async getAggregatorObjectInfo({ coinType }: { coinType: string }): Promise { + const config = await this.getConfig(); + const aggregatorInfo = config.AGGREGATOR_OBJS[normalizeStructTag(coinType)]; if (!aggregatorInfo) { throw new Error('Unsupported coin type'); @@ -225,8 +251,9 @@ export class BucketClient { return aggregatorInfo; } - getVaultObjectInfo({ coinType }: { coinType: string }): VaultObjectInfo { - const vaultInfo = this.config.VAULT_OBJS[normalizeStructTag(coinType)]; + async getVaultObjectInfo({ coinType }: { coinType: string }): Promise { + const config = await this.getConfig(); + const vaultInfo = config.VAULT_OBJS[normalizeStructTag(coinType)]; if (!vaultInfo) { throw new Error('Unsupported collateral type'); @@ -234,8 +261,9 @@ export class BucketClient { return vaultInfo; } - getSavingPoolObjectInfo({ lpType }: { lpType: string }): SavingPoolObjectInfo { - const savingPoolInfo = this.config.SAVING_POOL_OBJS[normalizeStructTag(lpType)]; + async getSavingPoolObjectInfo({ lpType }: { lpType: string }): Promise { + const config = await this.getConfig(); + const savingPoolInfo = config.SAVING_POOL_OBJS[normalizeStructTag(lpType)]; if (!savingPoolInfo) { throw new Error('Unsupported coin type'); @@ -243,8 +271,9 @@ export class BucketClient { return savingPoolInfo; } - getPsmPoolObjectInfo({ coinType }: { coinType: string }): PsmPoolObjectInfo { - const psmPoolInfo = this.config.PSM_POOL_OBJS[normalizeStructTag(coinType)]; + async getPsmPoolObjectInfo({ coinType }: { coinType: string }): Promise { + const config = await this.getConfig(); + const psmPoolInfo = config.PSM_POOL_OBJS[normalizeStructTag(coinType)]; if (!psmPoolInfo) { throw new Error('Unsupported coin type'); @@ -260,7 +289,7 @@ export class BucketClient { * &mut which cannot be passed between commands; SDK v2 validates this and rejects). */ async getUsdbSupply(): Promise { - const coinType = this.getUsdbCoinType(); + const coinType = await this.getUsdbCoinType(); const { response } = await this.suiClient.stateService.getCoinInfo({ coinType }); const supply = response.treasury?.totalSupply; if (supply === null || supply === undefined) { @@ -304,7 +333,7 @@ export class BucketClient { * @description */ async getAllOraclePrices(): Promise> { - const coinTypes = this.getAllOracleCoinTypes(); + const coinTypes = await this.getAllOracleCoinTypes(); return this.getOraclePrices({ coinTypes }); } @@ -313,7 +342,7 @@ export class BucketClient { * @description */ async getBorrowRewardFlowRate({ coinType }: { coinType: string }): Promise> { - const vault = this.getVaultObjectInfo({ coinType }); + const vault = await this.getVaultObjectInfo({ coinType }); if (!vault.rewarders) { return {}; @@ -345,8 +374,9 @@ export class BucketClient { * @description Get all vault objects */ async getAllVaultObjects(): Promise> { - const vaultObjectIds = Object.values(this.config.VAULT_OBJS).map((v) => v.vault.objectId); - const allCollateralTypes = this.getAllCollateralTypes(); + const config = await this.getConfig(); + const vaultObjectIds = Object.values(config.VAULT_OBJS).map((v) => v.vault.objectId); + const allCollateralTypes = await this.getAllCollateralTypes(); const res = await this.suiClient.getObjects({ objectIds: vaultObjectIds, @@ -357,7 +387,7 @@ export class BucketClient { const rewardFlowRates = await Promise.all( allCollateralTypes.map((coinType) => this.getBorrowRewardFlowRate({ coinType })), ); - return Object.keys(this.config.VAULT_OBJS).reduce( + return Object.keys(config.VAULT_OBJS).reduce( (result, collateralType, index) => { const object = res.objects[index]; const flowRates = rewardFlowRates[index]; @@ -397,7 +427,7 @@ export class BucketClient { * @description */ async getSavingPoolRewardFlowRate({ lpType }: { lpType: string }): Promise> { - const pool = this.getSavingPoolObjectInfo({ lpType }); + const pool = await this.getSavingPoolObjectInfo({ lpType }); if (!pool.reward) { return {}; @@ -431,8 +461,9 @@ export class BucketClient { * @description Get all Saving pool objects */ async getAllSavingPoolObjects(): Promise> { - const lpTypes = Object.keys(this.config.SAVING_POOL_OBJS); - const poolObjectIds = Object.values(this.config.SAVING_POOL_OBJS).map((v) => v.pool.objectId); + const config = await this.getConfig(); + const lpTypes = Object.keys(config.SAVING_POOL_OBJS); + const poolObjectIds = Object.values(config.SAVING_POOL_OBJS).map((v) => v.pool.objectId); const res = await this.suiClient.getObjects({ objectIds: poolObjectIds, @@ -442,7 +473,7 @@ export class BucketClient { }); const rewardFlowRates = await Promise.all(lpTypes.map((lpType) => this.getSavingPoolRewardFlowRate({ lpType }))); - return Object.keys(this.config.SAVING_POOL_OBJS).reduce( + return Object.keys(config.SAVING_POOL_OBJS).reduce( (result, lpType, index) => { const object = res.objects[index]; const flowRates = rewardFlowRates[index]; @@ -479,7 +510,8 @@ export class BucketClient { * @description Get all PSM pool objects */ async getAllPsmPoolObjects(): Promise> { - const poolObjectIds = Object.values(this.config.PSM_POOL_OBJS).map((v) => v.pool.objectId); + const config = await this.getConfig(); + const poolObjectIds = Object.values(config.PSM_POOL_OBJS).map((v) => v.pool.objectId); const res = await this.suiClient.getObjects({ objectIds: poolObjectIds, @@ -487,7 +519,7 @@ export class BucketClient { content: true, }, }); - return Object.keys(this.config.PSM_POOL_OBJS).reduce( + return Object.keys(config.PSM_POOL_OBJS).reduce( (result, coinType, index) => { const object = res.objects[index]; @@ -526,8 +558,9 @@ export class BucketClient { * @description */ async getFlashMintInfo(): Promise { + const config = await this.getConfig(); const data = await this.suiClient.getObject({ - objectId: this.config.FLASH_GLOBAL_CONFIG_OBJ.objectId, + objectId: config.FLASH_GLOBAL_CONFIG_OBJ.objectId, include: { content: true, }, @@ -561,13 +594,14 @@ export class BucketClient { pageSize?: number; cursor?: string | null; }): Promise { + const config = await this.getConfig(); const tx = new Transaction(); tx.moveCall({ - target: `${this.config.CDP_PACKAGE_ID}::vault::get_positions`, + target: `${config.CDP_PACKAGE_ID}::vault::get_positions`, typeArguments: [coinType], arguments: [ - this.vault(tx, { coinType }), + await this.vault(tx, { coinType }), tx.object.clock(), tx.pure.option('address', cursor), tx.pure.u64(pageSize), @@ -602,9 +636,10 @@ export class BucketClient { * @description */ async getUserAccounts({ address }: { address: string }) { + const config = await this.getConfig(); const accountRes = await this.suiClient.listOwnedObjects({ owner: address, - type: `${this.config.ORIGINAL_FRAMEWORK_PACKAGE_ID}::account::Account`, + type: `${config.ORIGINAL_FRAMEWORK_PACKAGE_ID}::account::Account`, include: { content: true, }, @@ -629,17 +664,18 @@ export class BucketClient { accountId?: string; coinTypes: string[]; }): Promise>> { + const config = await this.getConfig(); const tx = new Transaction(); - coinTypes.forEach((coinType) => { - const vaultInfo = this.getVaultObjectInfo({ coinType }); + for (const coinType of coinTypes) { + const vaultInfo = await this.getVaultObjectInfo({ coinType }); if (!vaultInfo.rewarders) { - return; + continue; } vaultInfo.rewarders.forEach((rewarder) => { tx.moveCall({ - target: `${this.config.BORROW_INCENTIVE_PACKAGE_ID}::borrow_incentive::realtime_reward_amount`, + target: `${config.BORROW_INCENTIVE_PACKAGE_ID}::borrow_incentive::realtime_reward_amount`, typeArguments: [coinType, rewarder.reward_type], arguments: [ tx.object(rewarder.rewarder_id), @@ -649,51 +685,51 @@ 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 {}; } - return coinTypes.reduce((result, coinType) => { - const vaultInfo = this.getVaultObjectInfo({ coinType }); + const result: Record> = {}; + for (const coinType of coinTypes) { + const vaultInfo = await this.getVaultObjectInfo({ coinType }); if (!vaultInfo.rewarders?.length) { - return result; + continue; } const responses = res.commandResults!.splice(0, vaultInfo.rewarders.length); - return { - ...result, - [coinType]: vaultInfo.rewarders.reduce((result, rewarder, index) => { - const resItem = responses[index]?.returnValues; - if (!resItem) { - return result; - } - const realtimeReward = bcs.u64().parse(resItem[0].bcs); - - return { ...result, [rewarder.reward_type]: BigInt(realtimeReward) }; - }, {}), - }; - }, {}); + result[coinType] = vaultInfo.rewarders.reduce((acc, rewarder, index) => { + const resItem = responses[index]?.returnValues; + if (!resItem) { + return acc; + } + const realtimeReward = bcs.u64().parse(resItem[0].bcs); + + return { ...acc, [rewarder.reward_type]: BigInt(realtimeReward) }; + }, {}); + } + return result; } /** * @description Get position data given account (can be wallet address or Account object ID) */ async getAccountPositions({ address, accountId }: { address: string; accountId?: string }): Promise { + const config = await this.getConfig(); const tx = new Transaction(); - const allCollateralTypes = this.getAllCollateralTypes(); + const allCollateralTypes = await this.getAllCollateralTypes(); - allCollateralTypes.forEach((coinType) => { - const vaultInfo = this.getVaultObjectInfo({ coinType }); + for (const coinType of allCollateralTypes) { + const vaultInfo = await this.getVaultObjectInfo({ coinType }); tx.moveCall({ - target: `${this.config.CDP_PACKAGE_ID}::vault::try_get_position_data`, + target: `${config.CDP_PACKAGE_ID}::vault::try_get_position_data`, typeArguments: [coinType], arguments: [tx.sharedObjectRef(vaultInfo.vault), tx.pure.address(accountId ?? address), tx.object.clock()], }); - }); + } tx.setSender(DUMMY_ADDRESS); const res = await this.suiClient.simulateTransaction({ transaction: tx, include: { commandResults: true } }); if (res.$kind === 'FailedTransaction' || !res.commandResults) { @@ -750,17 +786,18 @@ export class BucketClient { accountId?: string; lpTypes: string[]; }): Promise>> { + const config = await this.getConfig(); const tx = new Transaction(); - lpTypes.forEach((lpType) => { - const poolInfo = this.getSavingPoolObjectInfo({ lpType }); + for (const lpType of lpTypes) { + const poolInfo = await this.getSavingPoolObjectInfo({ lpType }); if (!poolInfo.reward) { - return; + continue; } poolInfo.reward.reward_types.forEach((rewardType) => { tx.moveCall({ - target: `${this.config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::get_realtime_reward_amount`, + target: `${config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::get_realtime_reward_amount`, typeArguments: [lpType, rewardType], arguments: [ tx.sharedObjectRef(poolInfo.reward!.reward_manager), @@ -770,55 +807,55 @@ 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 {}; } - return lpTypes.reduce((result, lpType) => { - const poolInfo = this.getSavingPoolObjectInfo({ lpType }); + const result: Record> = {}; + for (const lpType of lpTypes) { + const poolInfo = await this.getSavingPoolObjectInfo({ lpType }); if (!poolInfo.reward?.reward_types?.length) { - return result; + continue; } const responses = res.commandResults!.splice(0, poolInfo.reward.reward_types.length); - return { - ...result, - [lpType]: poolInfo.reward.reward_types.reduce((result, rewardType, index) => { - const amountRes = responses[index]?.returnValues; - if (!amountRes) { - return result; - } - const realtimeReward = bcs.u64().parse(amountRes[0].bcs); - - return { ...result, [rewardType]: BigInt(realtimeReward) }; - }, {}), - }; - }, {}); + result[lpType] = poolInfo.reward.reward_types.reduce((acc, rewardType, index) => { + const amountRes = responses[index]?.returnValues; + if (!amountRes) { + return acc; + } + const realtimeReward = bcs.u64().parse(amountRes[0].bcs); + + return { ...acc, [rewardType]: BigInt(realtimeReward) }; + }, {}); + } + return result; } /** * @description */ async getAccountSavings({ address, accountId }: { address: string; accountId?: string }): Promise { - const lpTypes = Object.keys(this.config.SAVING_POOL_OBJS); + const config = await this.getConfig(); + const lpTypes = Object.keys(config.SAVING_POOL_OBJS); const tx = new Transaction(); - lpTypes.map((lpType) => { + for (const lpType of lpTypes) { tx.moveCall({ - target: `${this.config.SAVING_PACKAGE_ID}::saving::lp_token_value_of`, + target: `${config.SAVING_PACKAGE_ID}::saving::lp_token_value_of`, typeArguments: [lpType], - arguments: [this.savingPoolObj(tx, { lpType }), tx.pure.address(accountId ?? address), tx.object.clock()], + arguments: [await this.savingPoolObj(tx, { lpType }), tx.pure.address(accountId ?? address), tx.object.clock()], }); tx.moveCall({ - target: `${this.config.SAVING_PACKAGE_ID}::saving::lp_balance_of`, + target: `${config.SAVING_PACKAGE_ID}::saving::lp_balance_of`, typeArguments: [lpType], - arguments: [this.savingPoolObj(tx, { lpType }), tx.pure.address(accountId ?? address)], + arguments: [await this.savingPoolObj(tx, { lpType }), tx.pure.address(accountId ?? address)], }); - }); + } tx.setSender(DUMMY_ADDRESS); const res = await this.suiClient.simulateTransaction({ transaction: tx, include: { commandResults: true } }); const savingRewards = await this.getAccountSavingPoolRewards({ lpTypes, address, accountId }); @@ -866,36 +903,40 @@ export class BucketClient { /** * @description */ - treasury(tx: Transaction) { - return tx.sharedObjectRef(this.config.TREASURY_OBJ); + async treasury(tx: Transaction) { + const config = await this.getConfig(); + return tx.sharedObjectRef(config.TREASURY_OBJ); } /** * @description */ - vaultRewarderRegistry(tx: Transaction) { - return tx.sharedObjectRef(this.config.VAULT_REWARDER_REGISTRY); + async vaultRewarderRegistry(tx: Transaction) { + const config = await this.getConfig(); + return tx.sharedObjectRef(config.VAULT_REWARDER_REGISTRY); } /** * @description */ - savingPoolGlobalConfig(tx: Transaction) { - return tx.sharedObjectRef(this.config.SAVING_POOL_INCENTIVE_GLOBAL_CONFIG_OBJ); + async savingPoolGlobalConfig(tx: Transaction) { + const config = await this.getConfig(); + return tx.sharedObjectRef(config.SAVING_POOL_INCENTIVE_GLOBAL_CONFIG_OBJ); } /** * @description */ - flashGlobalConfig(tx: Transaction) { - return tx.sharedObjectRef(this.config.FLASH_GLOBAL_CONFIG_OBJ); + async flashGlobalConfig(tx: Transaction) { + const config = await this.getConfig(); + return tx.sharedObjectRef(config.FLASH_GLOBAL_CONFIG_OBJ); } /** * @description */ - aggregator(tx: Transaction, { coinType }: { coinType: string }) { - const aggregatorInfo = this.getAggregatorObjectInfo({ coinType }); + async aggregator(tx: Transaction, { coinType }: { coinType: string }) { + const aggregatorInfo = await this.getAggregatorObjectInfo({ coinType }); const agg = 'Pyth' in aggregatorInfo ? aggregatorInfo.Pyth : aggregatorInfo.DerivativeInfo; return tx.sharedObjectRef(agg!.priceAggregator); @@ -904,8 +945,8 @@ export class BucketClient { /** * @description */ - vault(tx: Transaction, { coinType }: { coinType: string }) { - const vaultInfo = this.getVaultObjectInfo({ coinType }); + async vault(tx: Transaction, { coinType }: { coinType: string }) { + const vaultInfo = await this.getVaultObjectInfo({ coinType }); return tx.sharedObjectRef(vaultInfo.vault); } @@ -913,8 +954,8 @@ export class BucketClient { /** * @description */ - savingPoolObj(tx: Transaction, { lpType }: { lpType: string }) { - const savingPoolInfo = this.getSavingPoolObjectInfo({ lpType }); + async savingPoolObj(tx: Transaction, { lpType }: { lpType: string }) { + const savingPoolInfo = await this.getSavingPoolObjectInfo({ lpType }); return tx.sharedObjectRef(savingPoolInfo.pool); } @@ -922,13 +963,13 @@ export class BucketClient { /** * @description */ - psmPoolObj(tx: Transaction, { coinType }: { coinType: string }) { - const psmPoolInfo = this.getPsmPoolObjectInfo({ coinType }); + async psmPoolObj(tx: Transaction, { coinType }: { coinType: string }) { + const psmPoolInfo = await this.getPsmPoolObjectInfo({ coinType }); return tx.sharedObjectRef(psmPoolInfo.pool); } - accountAddress( + async accountAddress( tx: Transaction, { address, @@ -937,12 +978,13 @@ export class BucketClient { address: string; accountObjectOrId?: string | TransactionArgument; }, - ): TransactionArgument { + ): Promise { + const config = await this.getConfig(); if (accountObjectOrId) { return typeof accountObjectOrId === 'string' ? tx.pure.address(accountObjectOrId) : tx.moveCall({ - target: `${this.config.FRAMEWORK_PACKAGE_ID}::account::account_address`, + target: `${config.FRAMEWORK_PACKAGE_ID}::account::account_address`, arguments: [accountObjectOrId], }); } @@ -954,17 +996,18 @@ export class BucketClient { * @param accountObjectOrId (optional): Account object or EOA if undefined * @return AccountRequest */ - newAccountRequest( + async newAccountRequest( tx: Transaction, { accountObjectOrId }: { accountObjectOrId?: string | TransactionArgument }, - ): TransactionResult { + ): Promise { + const config = await this.getConfig(); return accountObjectOrId ? tx.moveCall({ - target: `${this.config.FRAMEWORK_PACKAGE_ID}::account::request_with_account`, + target: `${config.FRAMEWORK_PACKAGE_ID}::account::request_with_account`, arguments: [typeof accountObjectOrId === 'string' ? tx.object(accountObjectOrId) : accountObjectOrId], }) : tx.moveCall({ - target: `${this.config.FRAMEWORK_PACKAGE_ID}::account::request`, + target: `${config.FRAMEWORK_PACKAGE_ID}::account::request`, }); } @@ -973,9 +1016,10 @@ export class BucketClient { * @param collateral coin type, e.g "0x2::sui::SUI" * @return PriceCollector */ - newPriceCollector(tx: Transaction, { coinType }: { coinType: string }): TransactionResult { + async newPriceCollector(tx: Transaction, { coinType }: { coinType: string }): Promise { + const config = await this.getConfig(); return tx.moveCall({ - target: `${this.config.ORACLE_PACKAGE_ID}::collector::new`, + target: `${config.ORACLE_PACKAGE_ID}::collector::new`, typeArguments: [coinType], }); } @@ -986,60 +1030,65 @@ export class BucketClient { * @return [PriceResult] */ async aggregateBasicPrices(tx: Transaction, { coinTypes }: { coinTypes: string[] }): Promise { + const config = await this.getConfig(); if (!coinTypes.length) { return []; } - const pythPriceIds = coinTypes.map((coinType) => { - const aggregator = this.getAggregatorObjectInfo({ coinType }); + const pythPriceIds: string[] = []; + for (const coinType of coinTypes) { + const aggregator = await this.getAggregatorObjectInfo({ coinType }); if (!('Pyth' in aggregator) || !aggregator.Pyth) { throw new Error(`${coinType} has no basic price`); } - return aggregator.Pyth.pythPriceId; - }); - const updateData = await fetchPriceFeedsUpdateDataFromHermes(this.config.PRICE_SERVICE_ENDPOINT, pythPriceIds); + pythPriceIds.push(aggregator.Pyth.pythPriceId); + } + const updateData = await fetchPriceFeedsUpdateDataFromHermes(config.PRICE_SERVICE_ENDPOINT, pythPriceIds); const priceInfoObjIds = await buildPythPriceUpdateCalls( tx, this.suiClient, { - pythStateId: this.config.PYTH_STATE_ID, - wormholeStateId: this.config.WORMHOLE_STATE_ID, + pythStateId: config.PYTH_STATE_ID, + wormholeStateId: config.WORMHOLE_STATE_ID, }, updateData, pythPriceIds, this.pythCache, ); - return coinTypes.map((coinType, index) => { - const collector = this.newPriceCollector(tx, { coinType }); - const aggInfo = this.getAggregatorObjectInfo({ coinType }); + const results: TransactionResult[] = []; + for (let index = 0; index < coinTypes.length; index++) { + const coinType = coinTypes[index]; + const collector = await this.newPriceCollector(tx, { coinType }); + const aggInfo = await this.getAggregatorObjectInfo({ coinType }); const agg = 'Pyth' in aggInfo ? aggInfo.Pyth : aggInfo.DerivativeInfo; if (!agg) throw new Error(`${coinType} has no aggregator info`); tx.moveCall({ - target: `${this.config.PYTH_RULE_PACKAGE_ID}::pyth_rule::feed`, + target: `${config.PYTH_RULE_PACKAGE_ID}::pyth_rule::feed`, typeArguments: [coinType], arguments: [ collector, - tx.sharedObjectRef(this.config.PYTH_RULE_CONFIG_OBJ), + tx.sharedObjectRef(config.PYTH_RULE_CONFIG_OBJ), tx.object.clock(), - tx.object(this.config.PYTH_STATE_ID), + tx.object(config.PYTH_STATE_ID), tx.object(priceInfoObjIds[index]!), ], }); const priceResult = tx.moveCall({ - target: `${this.config.ORACLE_PACKAGE_ID}::aggregator::aggregate`, + target: `${config.ORACLE_PACKAGE_ID}::aggregator::aggregate`, typeArguments: [coinType], arguments: [tx.sharedObjectRef(agg.priceAggregator), collector], }); - return priceResult; - }); + results.push(priceResult); + } + return results; } /** * @description */ - private getDerivativePrice( + private async getDerivativePrice( tx: Transaction, { coinType, @@ -1048,14 +1097,15 @@ export class BucketClient { coinType: string; underlyingPriceResult: TransactionArgument; }, - ): TransactionResult { - const aggregator = this.getAggregatorObjectInfo({ coinType }); + ): Promise { + const config = await this.getConfig(); + const aggregator = await this.getAggregatorObjectInfo({ coinType }); if (!('DerivativeInfo' in aggregator) || !aggregator.DerivativeInfo) { throw new Error(`${coinType} has no derivative info`); } const { priceAggregator, underlying_coin_type, derivative_kind } = aggregator.DerivativeInfo; - const collector = this.newPriceCollector(tx, { coinType }); + const collector = await this.newPriceCollector(tx, { coinType }); // Map on-chain derivative_kind (Scallop, GCoin, Unihouse, BFBTC) to SDK DerivativeKind const dk: DerivativeKind = derivative_kind === 'Scallop' || derivative_kind === 'sCoin' @@ -1067,7 +1117,7 @@ export class BucketClient { : derivative_kind === 'TLP' ? 'TLP' : 'sCoin'; // fallback - const priceConfig = this.getPriceConfigInfo(dk); + const priceConfig = await this.getPriceConfigInfo(dk); switch (derivative_kind) { case 'sCoin': @@ -1122,7 +1172,7 @@ export class BucketClient { throw new Error(`Unsupported derivative kind: ${derivative_kind}`); } return tx.moveCall({ - target: `${this.config.ORACLE_PACKAGE_ID}::aggregator::aggregate`, + target: `${config.ORACLE_PACKAGE_ID}::aggregator::aggregate`, typeArguments: [coinType], arguments: [tx.sharedObjectRef(priceAggregator), collector], }); @@ -1132,34 +1182,36 @@ export class BucketClient { * @description */ async aggregatePrices(tx: Transaction, { coinTypes }: { coinTypes: string[] }): Promise { - const allBasicCoinTypes = Array.from( - new Set( - coinTypes.map((coinType) => { - const aggregator = this.getAggregatorObjectInfo({ coinType }); - - return 'Pyth' in aggregator ? coinType : aggregator.DerivativeInfo.underlying_coin_type; - }), - ), - ); + const allBasicCoinTypes: string[] = []; + const seen = new Set(); + for (const coinType of coinTypes) { + const aggregator = await this.getAggregatorObjectInfo({ coinType }); + const basicType = 'Pyth' in aggregator ? coinType : aggregator.DerivativeInfo.underlying_coin_type; + + if (!seen.has(basicType)) { + seen.add(basicType); + allBasicCoinTypes.push(basicType); + } + } const basicPriceResults = await this.aggregateBasicPrices(tx, { coinTypes: allBasicCoinTypes }); - const priceResults = Object.fromEntries( + const priceResults: Record = Object.fromEntries( allBasicCoinTypes.map((coinType, index) => [coinType, basicPriceResults[index]]), ); // deal with exchange rate - coinTypes.forEach((coinType) => { - const aggregator = this.getAggregatorObjectInfo({ coinType }); + for (const coinType of coinTypes) { + const aggregator = await this.getAggregatorObjectInfo({ coinType }); if (!('DerivativeInfo' in aggregator)) { - return; + continue; } const { underlying_coin_type: underlyingCoinType } = aggregator.DerivativeInfo!; - priceResults[coinType] = this.getDerivativePrice(tx, { + priceResults[coinType] = await this.getDerivativePrice(tx, { coinType, underlyingPriceResult: priceResults[underlyingCoinType], }); - }); + } return coinTypes.map((coinType) => priceResults[coinType]); } @@ -1173,14 +1225,14 @@ export class BucketClient { * @param accountObjectOrId (optional): account object id or transaction argument * @returns UpdateRequest */ - debtorRequest( + async debtorRequest( tx: Transaction, { accountObjectOrId, coinType, depositCoin = getZeroCoin(tx, { coinType: coinType }), borrowAmount = 0, - repayCoin = getZeroCoin(tx, { coinType: this.getUsdbCoinType() }), + repayCoin, withdrawAmount = 0, }: { accountObjectOrId?: string | TransactionArgument; @@ -1190,19 +1242,22 @@ export class BucketClient { repayCoin?: TransactionArgument; withdrawAmount?: number | TransactionArgument; }, - ): TransactionResult { - const accountReq = this.newAccountRequest(tx, { accountObjectOrId }); + ): Promise { + const config = await this.getConfig(); + const usdbCoinType = await this.getUsdbCoinType(); + const resolvedRepayCoin = repayCoin ?? getZeroCoin(tx, { coinType: usdbCoinType }); + const accountReq = await this.newAccountRequest(tx, { accountObjectOrId }); return tx.moveCall({ - target: `${this.config.CDP_PACKAGE_ID}::vault::debtor_request`, + target: `${config.CDP_PACKAGE_ID}::vault::debtor_request`, typeArguments: [coinType], arguments: [ - this.vault(tx, { coinType }), + await this.vault(tx, { coinType }), accountReq, - this.treasury(tx), + await this.treasury(tx), depositCoin, typeof borrowAmount === 'number' ? tx.pure.u64(borrowAmount) : borrowAmount, - repayCoin, + resolvedRepayCoin, typeof withdrawAmount === 'number' ? tx.pure.u64(withdrawAmount) : withdrawAmount, ], }); @@ -1213,34 +1268,35 @@ export class BucketClient { * @param coinType: collateral coin type , e.g "0x2::sui::SUI" * @param response: UpdateResponse generated by update_position */ - checkUpdatePositionRequest( + async checkUpdatePositionRequest( tx: Transaction, { coinType, request }: { coinType: string; request: TransactionArgument }, - ): TransactionResult { - const vaultObj = this.vault(tx, { coinType }); - const rewarders = this.getVaultObjectInfo({ coinType }).rewarders; - const registryObj = tx.sharedObjectRef(this.config.VAULT_REWARDER_REGISTRY); + ): Promise { + const config = await this.getConfig(); + const vaultObj = await this.vault(tx, { coinType }); + const rewarders = (await this.getVaultObjectInfo({ coinType })).rewarders; + const registryObj = tx.sharedObjectRef(config.VAULT_REWARDER_REGISTRY); const checker = tx.moveCall({ - target: `${this.config.BORROW_INCENTIVE_PACKAGE_ID}::borrow_incentive::new_checker`, + target: `${config.BORROW_INCENTIVE_PACKAGE_ID}::borrow_incentive::new_checker`, typeArguments: [coinType], arguments: [registryObj, request], }); (rewarders ?? []).map((rewarder) => { tx.moveCall({ - target: `${this.config.BORROW_INCENTIVE_PACKAGE_ID}::borrow_incentive::update`, + target: `${config.BORROW_INCENTIVE_PACKAGE_ID}::borrow_incentive::update`, typeArguments: [coinType, rewarder.reward_type], arguments: [registryObj, checker, vaultObj, tx.object(rewarder.rewarder_id), tx.object.clock()], }); }); const updateRequest = tx.moveCall({ - target: `${this.config.BORROW_INCENTIVE_PACKAGE_ID}::borrow_incentive::destroy_checker`, + target: `${config.BORROW_INCENTIVE_PACKAGE_ID}::borrow_incentive::destroy_checker`, typeArguments: [coinType], arguments: [registryObj, checker], }); tx.moveCall({ - target: `${this.config.BLACKLIST_PACKAGE_ID}::blacklist_rule::check`, + target: `${config.BLACKLIST_PACKAGE_ID}::blacklist_rule::check`, typeArguments: [coinType], - arguments: [tx.sharedObjectRef(this.config.BLACKLIST_OBJ), updateRequest], + arguments: [tx.sharedObjectRef(config.BLACKLIST_OBJ), updateRequest], }); return updateRequest; } @@ -1252,7 +1308,7 @@ export class BucketClient { * @param priceResult: price result, see this.aggregatePrice * @returns [Coin, COIN, UpdateResponse] */ - updatePosition( + async updatePosition( tx: Transaction, { coinType, @@ -1263,8 +1319,9 @@ export class BucketClient { updateRequest: TransactionArgument; priceResult?: TransactionArgument; }, - ): TransactionNestedResult[] { - const priceResultType = `${this.config.ORIGINAL_ORACLE_PACKAGE_ID}::result::PriceResult<${coinType}>`; + ): Promise { + const config = await this.getConfig(); + const priceResultType = `${config.ORIGINAL_ORACLE_PACKAGE_ID}::result::PriceResult<${coinType}>`; const priceResultOpt = priceResult ? tx.moveCall({ target: `0x1::option::some`, @@ -1276,9 +1333,15 @@ export class BucketClient { typeArguments: [priceResultType], }); const [inputCoin, usdbCoin, response] = tx.moveCall({ - target: `${this.config.CDP_PACKAGE_ID}::vault::update_position`, + target: `${config.CDP_PACKAGE_ID}::vault::update_position`, typeArguments: [coinType], - arguments: [this.vault(tx, { coinType }), this.treasury(tx), tx.object.clock(), priceResultOpt, updateRequest], + arguments: [ + await this.vault(tx, { coinType }), + await this.treasury(tx), + tx.object.clock(), + priceResultOpt, + updateRequest, + ], }); return [inputCoin, usdbCoin, response]; } @@ -1288,21 +1351,22 @@ export class BucketClient { * @param coinType: collateral coin type , e.g "0x2::sui::SUI" * @param response: UpdateResponse generated by update_position */ - checkUpdatePositionResponse( + async checkUpdatePositionResponse( tx: Transaction, { coinType, response }: { coinType: string; response: TransactionArgument }, - ): void { + ): Promise { + const config = await this.getConfig(); tx.moveCall({ - target: `${this.config.CDP_PACKAGE_ID}::vault::destroy_response`, + target: `${config.CDP_PACKAGE_ID}::vault::destroy_response`, typeArguments: [coinType], - arguments: [this.vault(tx, { coinType }), this.treasury(tx), response], + arguments: [await this.vault(tx, { coinType }), await this.treasury(tx), response], }); } /** * @description */ - savingPoolDeposit( + async savingPoolDeposit( tx: Transaction, { address, @@ -1315,14 +1379,15 @@ export class BucketClient { lpType: string; usdbCoin: TransactionArgument; }, - ): TransactionResult { + ): Promise { + const config = await this.getConfig(); const depositResponse = tx.moveCall({ - target: `${this.config.SAVING_PACKAGE_ID}::saving::deposit`, + target: `${config.SAVING_PACKAGE_ID}::saving::deposit`, typeArguments: [lpType], arguments: [ - this.savingPoolObj(tx, { lpType }), - this.treasury(tx), - this.accountAddress(tx, { address, accountObjectOrId }), + await this.savingPoolObj(tx, { lpType }), + await this.treasury(tx), + await this.accountAddress(tx, { address, accountObjectOrId }), usdbCoin, tx.object.clock(), ], @@ -1333,7 +1398,7 @@ export class BucketClient { /** * @description */ - private updateSavingPoolIncentiveDepositAction( + private async updateSavingPoolIncentiveDepositAction( tx: Transaction, { lpType, @@ -1342,28 +1407,29 @@ export class BucketClient { lpType: string; depositResponse: TransactionArgument; }, - ): TransactionResult { - const savingPool = this.getSavingPoolObjectInfo({ lpType }); + ): Promise { + const config = await this.getConfig(); + const savingPool = await this.getSavingPoolObjectInfo({ lpType }); if (!savingPool.reward) { throw new Error(`No rewards for ${lpType}`); } const depositChecker = tx.moveCall({ - target: `${this.config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::new_checker_for_deposit_action`, + target: `${config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::new_checker_for_deposit_action`, typeArguments: [lpType], arguments: [ tx.sharedObjectRef(savingPool.reward.reward_manager), - this.savingPoolGlobalConfig(tx), + tx.sharedObjectRef(config.SAVING_POOL_INCENTIVE_GLOBAL_CONFIG_OBJ), depositResponse, ], }); savingPool.reward.reward_types.forEach((rewardType) => { tx.moveCall({ - target: `${this.config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::update_deposit_action`, + target: `${config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::update_deposit_action`, typeArguments: [lpType, rewardType], arguments: [ depositChecker, - this.savingPoolGlobalConfig(tx), + tx.sharedObjectRef(config.SAVING_POOL_INCENTIVE_GLOBAL_CONFIG_OBJ), tx.sharedObjectRef(savingPool.reward!.reward_manager), tx.sharedObjectRef(savingPool.pool), tx.object.clock(), @@ -1371,9 +1437,9 @@ export class BucketClient { }); }); const finalResponse = tx.moveCall({ - target: `${this.config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::destroy_deposit_checker`, + target: `${config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::destroy_deposit_checker`, typeArguments: [lpType], - arguments: [depositChecker, this.savingPoolGlobalConfig(tx)], + arguments: [depositChecker, tx.sharedObjectRef(config.SAVING_POOL_INCENTIVE_GLOBAL_CONFIG_OBJ)], }); return finalResponse; } @@ -1381,7 +1447,7 @@ export class BucketClient { /** * @description */ - checkDepositResponse( + async checkDepositResponse( tx: Transaction, { lpType, @@ -1390,14 +1456,15 @@ export class BucketClient { lpType: string; depositResponse: TransactionArgument; }, - ): void { + ): Promise { + const config = await this.getConfig(); tx.moveCall({ - target: `${this.config.SAVING_PACKAGE_ID}::saving::check_deposit_response`, + target: `${config.SAVING_PACKAGE_ID}::saving::check_deposit_response`, typeArguments: [lpType], arguments: [ - this.updateSavingPoolIncentiveDepositAction(tx, { lpType, depositResponse }), - this.savingPoolObj(tx, { lpType }), - this.treasury(tx), + await this.updateSavingPoolIncentiveDepositAction(tx, { lpType, depositResponse }), + await this.savingPoolObj(tx, { lpType }), + await this.treasury(tx), ], }); } @@ -1405,7 +1472,7 @@ export class BucketClient { /** * @description */ - savingPoolWithdraw( + async savingPoolWithdraw( tx: Transaction, { accountObjectOrId, @@ -1416,15 +1483,16 @@ export class BucketClient { lpType: string; amount: number; }, - ): [TransactionNestedResult, TransactionNestedResult] { - const accountReq = this.newAccountRequest(tx, { accountObjectOrId }); + ): Promise<[TransactionNestedResult, TransactionNestedResult]> { + const config = await this.getConfig(); + const accountReq = await this.newAccountRequest(tx, { accountObjectOrId }); const [usdbCoin, withdrawResponse] = tx.moveCall({ - target: `${this.config.SAVING_PACKAGE_ID}::saving::withdraw`, + target: `${config.SAVING_PACKAGE_ID}::saving::withdraw`, typeArguments: [lpType], arguments: [ - this.savingPoolObj(tx, { lpType }), - this.treasury(tx), + await this.savingPoolObj(tx, { lpType }), + await this.treasury(tx), accountReq, tx.pure.u64(amount), tx.object.clock(), @@ -1436,7 +1504,7 @@ export class BucketClient { /** * @description */ - private updateSavingPoolIncentiveWithdrawAction( + private async updateSavingPoolIncentiveWithdrawAction( tx: Transaction, { lpType, @@ -1445,28 +1513,29 @@ export class BucketClient { lpType: string; withdrawResponse: TransactionArgument; }, - ): TransactionResult { - const savingPool = this.getSavingPoolObjectInfo({ lpType }); + ): Promise { + const config = await this.getConfig(); + const savingPool = await this.getSavingPoolObjectInfo({ lpType }); if (!savingPool.reward) { throw new Error(`No rewards for ${lpType}`); } const withdrawChecker = tx.moveCall({ - target: `${this.config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::new_checker_for_withdraw_action`, + target: `${config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::new_checker_for_withdraw_action`, typeArguments: [lpType], arguments: [ tx.sharedObjectRef(savingPool.reward.reward_manager), - this.savingPoolGlobalConfig(tx), + tx.sharedObjectRef(config.SAVING_POOL_INCENTIVE_GLOBAL_CONFIG_OBJ), withdrawResponse, ], }); savingPool.reward.reward_types.forEach((rewardType) => { tx.moveCall({ - target: `${this.config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::update_withdraw_action`, + target: `${config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::update_withdraw_action`, typeArguments: [lpType, rewardType], arguments: [ withdrawChecker, - this.savingPoolGlobalConfig(tx), + tx.sharedObjectRef(config.SAVING_POOL_INCENTIVE_GLOBAL_CONFIG_OBJ), tx.sharedObjectRef(savingPool.reward!.reward_manager), tx.sharedObjectRef(savingPool.pool), tx.object.clock(), @@ -1474,9 +1543,9 @@ export class BucketClient { }); }); const finalResponse = tx.moveCall({ - target: `${this.config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::destroy_withdraw_checker`, + target: `${config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::destroy_withdraw_checker`, typeArguments: [lpType], - arguments: [withdrawChecker, this.savingPoolGlobalConfig(tx)], + arguments: [withdrawChecker, tx.sharedObjectRef(config.SAVING_POOL_INCENTIVE_GLOBAL_CONFIG_OBJ)], }); return finalResponse; } @@ -1484,7 +1553,7 @@ export class BucketClient { /** * @description */ - checkWithdrawResponse( + async checkWithdrawResponse( tx: Transaction, { lpType, @@ -1493,14 +1562,15 @@ export class BucketClient { lpType: string; withdrawResponse: TransactionArgument; }, - ): void { + ): Promise { + const config = await this.getConfig(); tx.moveCall({ - target: `${this.config.SAVING_PACKAGE_ID}::saving::check_withdraw_response`, + target: `${config.SAVING_PACKAGE_ID}::saving::check_withdraw_response`, typeArguments: [lpType], arguments: [ - this.updateSavingPoolIncentiveWithdrawAction(tx, { lpType, withdrawResponse }), - this.savingPoolObj(tx, { lpType }), - this.treasury(tx), + await this.updateSavingPoolIncentiveWithdrawAction(tx, { lpType, withdrawResponse }), + await this.savingPoolObj(tx, { lpType }), + await this.treasury(tx), ], }); } @@ -1508,7 +1578,7 @@ export class BucketClient { /** * @description */ - claimPoolIncentive( + async claimPoolIncentive( tx: Transaction, { accountObjectOrId, @@ -1519,20 +1589,21 @@ export class BucketClient { lpType: string; rewardType: string; }, - ): TransactionResult { - const savingPool = this.getSavingPoolObjectInfo({ lpType }); + ): Promise { + const config = await this.getConfig(); + const savingPool = await this.getSavingPoolObjectInfo({ lpType }); if (!savingPool.reward) { return getZeroCoin(tx, { coinType: rewardType }); } - const accountReq = this.newAccountRequest(tx, { accountObjectOrId }); + const accountReq = await this.newAccountRequest(tx, { accountObjectOrId }); const rewardCoin = tx.moveCall({ - target: `${this.config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::claim`, + target: `${config.SAVING_INCENTIVE_PACKAGE_ID}::saving_incentive::claim`, typeArguments: [lpType, rewardType], arguments: [ tx.sharedObjectRef(savingPool.reward.reward_manager), - this.savingPoolGlobalConfig(tx), + await this.savingPoolGlobalConfig(tx), tx.sharedObjectRef(savingPool.pool), accountReq, tx.object.clock(), @@ -1544,7 +1615,7 @@ export class BucketClient { /** * @description */ - psmSwapIn( + async psmSwapIn( tx: Transaction, { accountObjectOrId, @@ -1557,15 +1628,16 @@ export class BucketClient { priceResult: TransactionArgument; inputCoin: TransactionArgument; }, - ): TransactionResult { + ): Promise { + const config = await this.getConfig(); const partner = tx.object.option({ - type: `${this.config.FRAMEWORK_PACKAGE_ID}::account::AccountRequest`, - value: this.newAccountRequest(tx, { accountObjectOrId }), + type: `${config.FRAMEWORK_PACKAGE_ID}::account::AccountRequest`, + value: await this.newAccountRequest(tx, { accountObjectOrId }), }); const usdbCoin = tx.moveCall({ - target: `${this.config.PSM_PACKAGE_ID}::pool::swap_in`, + target: `${config.PSM_PACKAGE_ID}::pool::swap_in`, typeArguments: [coinType], - arguments: [this.psmPoolObj(tx, { coinType }), this.treasury(tx), priceResult, inputCoin, partner], + arguments: [await this.psmPoolObj(tx, { coinType }), await this.treasury(tx), priceResult, inputCoin, partner], }); return usdbCoin; } @@ -1573,7 +1645,7 @@ export class BucketClient { /** * @description */ - psmSwapOut( + async psmSwapOut( tx: Transaction, { accountObjectOrId, @@ -1586,15 +1658,16 @@ export class BucketClient { priceResult: TransactionArgument; usdbCoin: TransactionArgument; }, - ): TransactionResult { + ): Promise { + const config = await this.getConfig(); const partner = tx.object.option({ - type: `${this.config.FRAMEWORK_PACKAGE_ID}::account::AccountRequest`, - value: this.newAccountRequest(tx, { accountObjectOrId }), + type: `${config.FRAMEWORK_PACKAGE_ID}::account::AccountRequest`, + value: await this.newAccountRequest(tx, { accountObjectOrId }), }); const inputCoin = tx.moveCall({ - target: `${this.config.PSM_PACKAGE_ID}::pool::swap_out`, + target: `${config.PSM_PACKAGE_ID}::pool::swap_out`, typeArguments: [coinType], - arguments: [this.psmPoolObj(tx, { coinType }), this.treasury(tx), priceResult, usdbCoin, partner], + arguments: [await this.psmPoolObj(tx, { coinType }), await this.treasury(tx), priceResult, usdbCoin, partner], }); return inputCoin; } @@ -1602,7 +1675,7 @@ export class BucketClient { /** * @description */ - flashMint( + async flashMint( tx: Transaction, { accountObjectOrId, @@ -1611,16 +1684,17 @@ export class BucketClient { accountObjectOrId?: string | TransactionArgument; amount: number | TransactionArgument; }, - ): [TransactionNestedResult, TransactionNestedResult] { + ): Promise<[TransactionNestedResult, TransactionNestedResult]> { + const config = await this.getConfig(); const partner = tx.object.option({ - type: `${this.config.FRAMEWORK_PACKAGE_ID}::account::AccountRequest`, - value: this.newAccountRequest(tx, { accountObjectOrId }), + type: `${config.FRAMEWORK_PACKAGE_ID}::account::AccountRequest`, + value: await this.newAccountRequest(tx, { accountObjectOrId }), }); const [usdbCoin, flashMintReceipt] = tx.moveCall({ - target: `${this.config.FLASH_PACKAGE_ID}::config::flash_mint`, + target: `${config.FLASH_PACKAGE_ID}::config::flash_mint`, arguments: [ - this.flashGlobalConfig(tx), - this.treasury(tx), + await this.flashGlobalConfig(tx), + await this.treasury(tx), partner, typeof amount === 'number' ? tx.pure.u64(amount) : amount, ], @@ -1631,7 +1705,7 @@ export class BucketClient { /** * @description */ - flashBurn( + async flashBurn( tx: Transaction, { usdbCoin, @@ -1640,10 +1714,11 @@ export class BucketClient { usdbCoin: TransactionArgument; flashMintReceipt: TransactionArgument; }, - ): void { + ): Promise { + const config = await this.getConfig(); tx.moveCall({ - target: `${this.config.FLASH_PACKAGE_ID}::config::flash_burn`, - arguments: [this.flashGlobalConfig(tx), this.treasury(tx), usdbCoin, flashMintReceipt], + target: `${config.FLASH_PACKAGE_ID}::config::flash_burn`, + arguments: [await this.flashGlobalConfig(tx), await this.treasury(tx), usdbCoin, flashMintReceipt], }); } @@ -1678,16 +1753,17 @@ export class BucketClient { withdrawAmount?: number | TransactionArgument; }, ): Promise { + const usdbCoinType = await this.getUsdbCoinType(); const depositCoin = typeof depositCoinOrAmount === 'number' ? coinWithBalance({ balance: depositCoinOrAmount, type: coinType }) : depositCoinOrAmount; const repayCoin = typeof repayCoinOrAmount === 'number' - ? coinWithBalance({ balance: repayCoinOrAmount, type: this.getUsdbCoinType() }) + ? coinWithBalance({ balance: repayCoinOrAmount, type: usdbCoinType }) : repayCoinOrAmount; - const debtorRequest = this.debtorRequest(tx, { + const debtorRequest = await this.debtorRequest(tx, { accountObjectOrId, coinType, depositCoin, @@ -1695,22 +1771,22 @@ export class BucketClient { repayCoin, withdrawAmount, }); - const updateRequest = this.checkUpdatePositionRequest(tx, { coinType, request: debtorRequest }); + const updateRequest = await this.checkUpdatePositionRequest(tx, { coinType, request: debtorRequest }); const [priceResult] = borrowAmount || withdrawAmount ? await this.aggregatePrices(tx, { coinTypes: [coinType] }) : []; - const [collateralCoin, usdbCoin, response] = this.updatePosition(tx, { + const [collateralCoin, usdbCoin, response] = await this.updatePosition(tx, { coinType, updateRequest, priceResult, }); - this.checkUpdatePositionResponse(tx, { coinType, response }); + await this.checkUpdatePositionResponse(tx, { coinType, response }); if (withdrawAmount === 0) { destroyZeroCoin(tx, { coinType: coinType, coin: collateralCoin }); } if (borrowAmount === 0) { - destroyZeroCoin(tx, { coinType: this.getUsdbCoinType(), coin: usdbCoin }); + destroyZeroCoin(tx, { coinType: usdbCoinType, coin: usdbCoin }); } return [collateralCoin, usdbCoin]; } @@ -1722,7 +1798,7 @@ export class BucketClient { * @param recipient (optional): the recipient of the output coins * @returns Transaction */ - buildClosePositionTransaction( + async buildClosePositionTransaction( tx: Transaction, { address, @@ -1735,34 +1811,36 @@ export class BucketClient { coinType: string; repayCoin?: TransactionObjectArgument; }, - ): [TransactionNestedResult, TransactionObjectArgument | undefined] { + ): Promise<[TransactionNestedResult, TransactionObjectArgument | undefined]> { + const config = await this.getConfig(); + const usdbCoinType = await this.getUsdbCoinType(); const [collateralAmount, debtAmount] = tx.moveCall({ - target: `${this.config.CDP_PACKAGE_ID}::vault::get_position_data`, + target: `${config.CDP_PACKAGE_ID}::vault::get_position_data`, typeArguments: [coinType], arguments: [ - this.vault(tx, { coinType }), - this.accountAddress(tx, { address, accountObjectOrId }), + await this.vault(tx, { coinType }), + await this.accountAddress(tx, { address, accountObjectOrId }), tx.object.clock(), ], }); const splittedRepayCoin = repayCoin ? tx.splitCoins(repayCoin, [debtAmount])[0] - : coinWithBalance({ balance: debtAmount, type: this.getUsdbCoinType() }); + : coinWithBalance({ balance: debtAmount, type: usdbCoinType }); - const debtorRequest = this.debtorRequest(tx, { + const debtorRequest = await this.debtorRequest(tx, { accountObjectOrId, coinType, repayCoin: splittedRepayCoin, withdrawAmount: collateralAmount, }); - const updateRequest = this.checkUpdatePositionRequest(tx, { coinType, request: debtorRequest }); - const [collateralCoin, usdbCoin, response] = this.updatePosition(tx, { + const updateRequest = await this.checkUpdatePositionRequest(tx, { coinType, request: debtorRequest }); + const [collateralCoin, usdbCoin, response] = await this.updatePosition(tx, { coinType, updateRequest, }); - this.checkUpdatePositionResponse(tx, { coinType, response }); + await this.checkUpdatePositionResponse(tx, { coinType, response }); - destroyZeroCoin(tx, { coinType: this.getUsdbCoinType(), coin: usdbCoin }); + destroyZeroCoin(tx, { coinType: usdbCoinType, coin: usdbCoin }); return [collateralCoin, repayCoin]; } @@ -1773,7 +1851,7 @@ export class BucketClient { * @param coinType: collateral coin type , e.g "0x2::sui::SUI" * @returns Transaction[] if has rewards else undefined */ - buildClaimBorrowRewardsTransaction( + async buildClaimBorrowRewardsTransaction( tx: Transaction, { accountObjectOrId, @@ -1782,37 +1860,36 @@ export class BucketClient { accountObjectOrId?: string | TransactionArgument; coinType: string; }, - ): Record { - const vaultObj = this.vault(tx, { coinType }); - const rewarders = this.getVaultObjectInfo({ coinType }).rewarders; - const registryObj = tx.sharedObjectRef(this.config.VAULT_REWARDER_REGISTRY); + ): Promise> { + const config = await this.getConfig(); + const vaultObj = await this.vault(tx, { coinType }); + const rewarders = (await this.getVaultObjectInfo({ coinType })).rewarders; + const registryObj = tx.sharedObjectRef(config.VAULT_REWARDER_REGISTRY); if (!rewarders) { return {}; } - return rewarders.reduce( - (result, { reward_type, rewarder_id }) => ({ - ...result, - [reward_type]: tx.moveCall({ - target: `${this.config.BORROW_INCENTIVE_PACKAGE_ID}::borrow_incentive::claim`, - typeArguments: [coinType, reward_type], - arguments: [ - registryObj, - tx.object(rewarder_id), - vaultObj, - this.newAccountRequest(tx, { accountObjectOrId }), - tx.object.clock(), - ], - }), - }), - {}, - ); + const result: Record = {}; + for (const { reward_type, rewarder_id } of rewarders) { + result[reward_type] = tx.moveCall({ + target: `${config.BORROW_INCENTIVE_PACKAGE_ID}::borrow_incentive::claim`, + typeArguments: [coinType, reward_type], + arguments: [ + registryObj, + tx.object(rewarder_id), + vaultObj, + await this.newAccountRequest(tx, { accountObjectOrId }), + tx.object.clock(), + ], + }); + } + return result; } /** * @description */ - buildDepositToSavingPoolTransaction( + async buildDepositToSavingPoolTransaction( tx: Transaction, { address, @@ -1825,26 +1902,27 @@ export class BucketClient { lpType: string; depositCoinOrAmount: number | TransactionArgument; }, - ): void { + ): Promise { + const usdbCoinType = await this.getUsdbCoinType(); const depositCoin = typeof depositCoinOrAmount === 'number' - ? coinWithBalance({ balance: depositCoinOrAmount, type: this.getUsdbCoinType() }) + ? coinWithBalance({ balance: depositCoinOrAmount, type: usdbCoinType }) : depositCoinOrAmount; - const depositResponse = this.savingPoolDeposit(tx, { + const depositResponse = await this.savingPoolDeposit(tx, { address, accountObjectOrId, lpType, usdbCoin: depositCoin, }); - this.checkDepositResponse(tx, { lpType, depositResponse }); + await this.checkDepositResponse(tx, { lpType, depositResponse }); } /** * @description */ - buildWithdrawFromSavingPoolTransaction( + async buildWithdrawFromSavingPoolTransaction( tx: Transaction, { accountObjectOrId, @@ -1855,14 +1933,14 @@ export class BucketClient { lpType: string; amount: number; }, - ): TransactionNestedResult { - const [usdbCoin, withdrawResponse] = this.savingPoolWithdraw(tx, { + ): Promise { + const [usdbCoin, withdrawResponse] = await this.savingPoolWithdraw(tx, { accountObjectOrId, lpType, amount, }); - this.checkWithdrawResponse(tx, { lpType, withdrawResponse }); + await this.checkWithdrawResponse(tx, { lpType, withdrawResponse }); return usdbCoin; } @@ -1870,7 +1948,7 @@ export class BucketClient { /** * @description */ - buildClaimSavingRewardsTransaction( + async buildClaimSavingRewardsTransaction( tx: Transaction, { accountObjectOrId, @@ -1879,23 +1957,21 @@ export class BucketClient { accountObjectOrId?: string | TransactionArgument; lpType: string; }, - ): Record { - const savingPool = this.getSavingPoolObjectInfo({ lpType }); + ): Promise> { + const savingPool = await this.getSavingPoolObjectInfo({ lpType }); if (!savingPool.reward) { return {}; } - return savingPool.reward.reward_types.reduce( - (result, rewardType) => ({ - ...result, - [rewardType]: this.claimPoolIncentive(tx, { - accountObjectOrId, - lpType, - rewardType, - }), - }), - {}, - ); + const result: Record = {}; + for (const rewardType of savingPool.reward.reward_types) { + result[rewardType] = await this.claimPoolIncentive(tx, { + accountObjectOrId, + lpType, + rewardType, + }); + } + return result; } /** @@ -1938,9 +2014,10 @@ export class BucketClient { usdbCoinOrAmount: number | TransactionArgument; }, ): Promise { + const usdbCoinType = await this.getUsdbCoinType(); const usdbCoin = typeof usdbCoinOrAmount === 'number' - ? coinWithBalance({ balance: usdbCoinOrAmount, type: this.getUsdbCoinType() }) + ? coinWithBalance({ balance: usdbCoinOrAmount, type: usdbCoinType }) : usdbCoinOrAmount; const [priceResult] = await this.aggregatePrices(tx, { coinTypes: [coinType] }); diff --git a/test/e2e/all-query-outputs.test.ts b/test/e2e/all-query-outputs.test.ts index 936dd6a..d32be07 100644 --- a/test/e2e/all-query-outputs.test.ts +++ b/test/e2e/all-query-outputs.test.ts @@ -126,12 +126,12 @@ describe('E2E All query outputs (report)', () => { it( 'collects all query API outputs', async () => { - const config = bucketClient.getConfig(); + const config = await bucketClient.getConfig(); // Assert required config — fail fast, no silent fallback const psmPoolKeys = Object.keys(config.PSM_POOL_OBJS ?? {}); const savingPoolKeys = Object.keys(config.SAVING_POOL_OBJS ?? {}); - const collateralTypes = bucketClient.getAllCollateralTypes(); + const collateralTypes = await bucketClient.getAllCollateralTypes(); expect(psmPoolKeys.length, 'PSM_POOL_OBJS must have at least one pool').toBeGreaterThan(0); expect(savingPoolKeys.length, 'SAVING_POOL_OBJS must have at least one pool').toBeGreaterThan(0); expect(collateralTypes.length, 'Config must have at least one collateral type').toBeGreaterThan(0); @@ -147,24 +147,16 @@ describe('E2E All query outputs (report)', () => { .slice(0, 3); const hasVaultWithRewarders = coinTypeWithRewarders.length > 0; - const coinTypes = bucketClient.getAllOracleCoinTypes().slice(0, 2); + const coinTypes = (await bucketClient.getAllOracleCoinTypes()).slice(0, 2); - // Sync config-derived - await run('getUsdbCoinType', () => Promise.resolve(getUsdbCoinType())); - await run('getAllOracleCoinTypes', () => Promise.resolve(bucketClient.getAllOracleCoinTypes())); - await run('getAllCollateralTypes', () => Promise.resolve(bucketClient.getAllCollateralTypes())); - await run('getAggregatorObjectInfo', () => - Promise.resolve(bucketClient.getAggregatorObjectInfo({ coinType: firstCoinType })), - ); - await run('getVaultObjectInfo', () => - Promise.resolve(bucketClient.getVaultObjectInfo({ coinType: firstCoinType })), - ); - await run('getSavingPoolObjectInfo', () => - Promise.resolve(bucketClient.getSavingPoolObjectInfo({ lpType: firstLpType })), - ); - await run('getPsmPoolObjectInfo', () => - Promise.resolve(bucketClient.getPsmPoolObjectInfo({ coinType: firstPsmCoinType })), - ); + // Async config-derived + await run('getUsdbCoinType', () => bucketClient.getUsdbCoinType()); + await run('getAllOracleCoinTypes', () => bucketClient.getAllOracleCoinTypes()); + await run('getAllCollateralTypes', () => bucketClient.getAllCollateralTypes()); + await run('getAggregatorObjectInfo', () => bucketClient.getAggregatorObjectInfo({ coinType: firstCoinType })); + await run('getVaultObjectInfo', () => bucketClient.getVaultObjectInfo({ coinType: firstCoinType })); + await run('getSavingPoolObjectInfo', () => bucketClient.getSavingPoolObjectInfo({ lpType: firstLpType })); + await run('getPsmPoolObjectInfo', () => bucketClient.getPsmPoolObjectInfo({ coinType: firstPsmCoinType })); // Client / config await run('getSuiClient', () => @@ -172,7 +164,7 @@ describe('E2E All query outputs (report)', () => { ); await run('refreshConfig', () => bucketClient.refreshConfig()); - await run('getConfig', () => Promise.resolve(bucketClient.getConfig())); + await run('getConfig', () => bucketClient.getConfig()); // Utils (exported from @bucket-protocol/sdk) await run('queryAllConfig', () => queryAllConfig(suiClient, network)); @@ -191,7 +183,7 @@ describe('E2E All query outputs (report)', () => { }); await run('coinWithBalance', async () => { const tx = txWithSender(); - const fn = coinWithBalance({ type: getUsdbCoinType(), balance: 1n * 10n ** 6n }); + const fn = coinWithBalance({ type: await getUsdbCoinType(), balance: 1n * 10n ** 6n }); fn(tx); return { type: 'TransactionResult', built: true }; }); @@ -228,7 +220,7 @@ describe('E2E All query outputs (report)', () => { await run('getUserAccounts', () => bucketClient.getUserAccounts({ address: testAccount })); // getAccountBorrowRewards returns {} when vaults have no rewarders; use coinTypes with rewarders const coinTypesWithRewarders = ( - Object.entries(bucketClient.getConfig().VAULT_OBJS ?? {}) as [string, VaultObjectInfo][] + Object.entries((await bucketClient.getConfig()).VAULT_OBJS ?? {}) as [string, VaultObjectInfo][] ) .filter(([, v]) => v.rewarders && v.rewarders.length > 0) .map(([k]) => k) @@ -306,7 +298,7 @@ describe('E2E All query outputs (report)', () => { }; const depositAmount = await depositAmountForBorrowUsd(1); - const usdbType = getUsdbCoinType(); + const usdbType = await getUsdbCoinType(); await captureBuild('buildManagePositionTransaction', async () => { const tx = txWithSender(); @@ -339,7 +331,7 @@ describe('E2E All query outputs (report)', () => { const tx = txWithSender(); const amt = 1n * 10n ** 6n; const coin = coinWithBalance({ type: usdbType, balance: amt }); - bucketClient.buildDepositToSavingPoolTransaction(tx, { + await bucketClient.buildDepositToSavingPoolTransaction(tx, { lpType: firstLpType, address: testAccount, depositCoinOrAmount: coin, @@ -385,10 +377,13 @@ describe('E2E All query outputs (report)', () => { }); // aggregateBasicPrices — lower-level Hermes price aggregation (Pyth only, no derivatives) - const basicCoinTypes = bucketClient.getAllOracleCoinTypes().filter((ct) => { - const agg = bucketClient.getAggregatorObjectInfo({ coinType: ct }); - return 'Pyth' in agg && agg.Pyth; - }); + const allOracleCoinTypes = await bucketClient.getAllOracleCoinTypes(); + const aggregatorInfos = await Promise.all( + allOracleCoinTypes.map((ct) => bucketClient.getAggregatorObjectInfo({ coinType: ct })), + ); + const basicCoinTypes = allOracleCoinTypes.filter( + (_, i) => 'Pyth' in aggregatorInfos[i]! && aggregatorInfos[i]!.Pyth, + ); await captureBuild('aggregateBasicPrices', async () => { const tx = txWithSender(); return bucketClient.aggregateBasicPrices(tx, { @@ -407,7 +402,7 @@ describe('E2E All query outputs (report)', () => { const tx = txWithSender(); const amount = 1_000 * 10 ** 6; const feeAmount = Math.ceil(amount * 0.0005); - const [usdbCoin, flashMintReceipt] = bucketClient.flashMint(tx, { amount }); + const [usdbCoin, flashMintReceipt] = await bucketClient.flashMint(tx, { amount }); const feeCollateralCoin = coinWithBalance({ type: usdcCoinType, balance: feeAmount, @@ -417,7 +412,7 @@ describe('E2E All query outputs (report)', () => { inputCoinOrAmount: feeCollateralCoin, }); tx.mergeCoins(usdbCoin, [feeUsdbCoin]); - bucketClient.flashBurn(tx, { usdbCoin, flashMintReceipt }); + await bucketClient.flashBurn(tx, { usdbCoin, flashMintReceipt }); return undefined as void; }); @@ -448,20 +443,20 @@ describe('E2E All query outputs (report)', () => { const [priceResult] = await bucketClient.aggregatePrices(tx, { coinTypes: [firstCoinType], }); - const debtorReq = bucketClient.debtorRequest(tx, { + const debtorReq = await bucketClient.debtorRequest(tx, { coinType: firstCoinType, borrowAmount: 1 * 10 ** 6, }); - const updateRequest = bucketClient.checkUpdatePositionRequest(tx, { + const updateRequest = await bucketClient.checkUpdatePositionRequest(tx, { coinType: firstCoinType, request: debtorReq, }); - const [, , response] = bucketClient.updatePosition(tx, { + const [, , response] = await bucketClient.updatePosition(tx, { coinType: firstCoinType, updateRequest, priceResult, }); - bucketClient.checkUpdatePositionResponse(tx, { + await bucketClient.checkUpdatePositionResponse(tx, { coinType: firstCoinType, response, }); diff --git a/test/e2e/cdp.test.ts b/test/e2e/cdp.test.ts index 0b1e218..1dad5ff 100644 --- a/test/e2e/cdp.test.ts +++ b/test/e2e/cdp.test.ts @@ -131,7 +131,7 @@ describe('E2E CDP', () => { 'buildClosePositionTransaction dry run succeeds when account has SUI position', async () => { const tx = txWithSender(); - const [collateralCoin, repayCoin] = bucketClient.buildClosePositionTransaction(tx, { + const [collateralCoin, repayCoin] = await bucketClient.buildClosePositionTransaction(tx, { address: testAccount, coinType: SUI_TYPE_ARG, }); @@ -146,7 +146,7 @@ describe('E2E CDP', () => { 'newAccountRequest creates account request for EOA', async () => { const tx = txWithSender(); - const accountReq = bucketClient.newAccountRequest(tx, {}); + const accountReq = await bucketClient.newAccountRequest(tx, {}); expect(accountReq).toBeDefined(); await assertDryRunSucceeds(tx); }, @@ -163,7 +163,7 @@ describe('E2E CDP', () => { type: SUI_TYPE_ARG, balance: depositAmount, }); - const request = bucketClient.debtorRequest(tx, { + const request = await bucketClient.debtorRequest(tx, { coinType: SUI_TYPE_ARG, depositCoin, borrowAmount, @@ -187,21 +187,21 @@ describe('E2E CDP', () => { type: SUI_TYPE_ARG, balance: depositAmount, }); - const debtorReq = bucketClient.debtorRequest(tx, { + const debtorReq = await bucketClient.debtorRequest(tx, { coinType: SUI_TYPE_ARG, depositCoin, borrowAmount, }); - const updateRequest = bucketClient.checkUpdatePositionRequest(tx, { + const updateRequest = await bucketClient.checkUpdatePositionRequest(tx, { coinType: SUI_TYPE_ARG, request: debtorReq, }); - const [collateralCoin, usdbCoin, response] = bucketClient.updatePosition(tx, { + const [collateralCoin, usdbCoin, response] = await bucketClient.updatePosition(tx, { coinType: SUI_TYPE_ARG, updateRequest, priceResult, }); - bucketClient.checkUpdatePositionResponse(tx, { + await bucketClient.checkUpdatePositionResponse(tx, { coinType: SUI_TYPE_ARG, response, }); diff --git a/test/e2e/config.test.ts b/test/e2e/config.test.ts index d4d4b5d..2735bb6 100644 --- a/test/e2e/config.test.ts +++ b/test/e2e/config.test.ts @@ -18,7 +18,7 @@ describe('E2E Config & metadata', () => { it( 'getConfig returns config with package IDs and object refs', async () => { - const config = bucketClient.getConfig(); + const config = await bucketClient.getConfig(); expect(config).toBeDefined(); expect(config).toHaveProperty('ORIGINAL_USDB_PACKAGE_ID'); expect(config).toHaveProperty('TREASURY_OBJ'); @@ -32,7 +32,7 @@ describe('E2E Config & metadata', () => { 'usdbCoinType returns USDB metadata (6 decimals)', async () => { const { coinMetadata: usdbMetadata } = await suiClient.getCoinMetadata({ - coinType: getUsdbCoinType(), + coinType: await getUsdbCoinType(), }); expect(usdbMetadata?.decimals).toBe(6); expect(usdbMetadata?.symbol).toBe('USDB'); diff --git a/test/e2e/flash.test.ts b/test/e2e/flash.test.ts index 5bada4e..cf965f5 100644 --- a/test/e2e/flash.test.ts +++ b/test/e2e/flash.test.ts @@ -23,14 +23,14 @@ describe('E2E Flash', () => { const tx = txWithSender(); const amount = 1_000 * 10 ** 6; const feeAmount = amount * 0.0005; - const [usdbCoin, flashMintReceipt] = bucketClient.flashMint(tx, { amount }); + const [usdbCoin, flashMintReceipt] = await bucketClient.flashMint(tx, { amount }); const feeCollateralCoin = coinWithBalance({ type: usdcCoinType, balance: feeAmount }); const feeUsdbCoin = await bucketClient.buildPSMSwapInTransaction(tx, { coinType: usdcCoinType, inputCoinOrAmount: feeCollateralCoin, }); tx.mergeCoins(usdbCoin, [feeUsdbCoin]); - bucketClient.flashBurn(tx, { usdbCoin, flashMintReceipt }); + await bucketClient.flashBurn(tx, { usdbCoin, flashMintReceipt }); await assertDryRunSucceeds(tx); }, MAINNET_TIMEOUT_MS, diff --git a/test/e2e/helpers/setup.ts b/test/e2e/helpers/setup.ts index 0868eb9..47406c8 100644 --- a/test/e2e/helpers/setup.ts +++ b/test/e2e/helpers/setup.ts @@ -46,7 +46,7 @@ export async function ensureBucketClient(): Promise { return bucketClient; } /** Use after ensureBucketClient() has run. */ -export function getUsdbCoinType(): string { +export async function getUsdbCoinType(): Promise { return bucketClient.getUsdbCoinType(); } diff --git a/test/e2e/object-info.test.ts b/test/e2e/object-info.test.ts index 110ffb9..7ad0121 100644 --- a/test/e2e/object-info.test.ts +++ b/test/e2e/object-info.test.ts @@ -18,8 +18,8 @@ describe('E2E Object info helpers', () => { it( 'getAggregatorObjectInfo returns aggregator for SUI', - () => { - const info = bucketClient.getAggregatorObjectInfo({ coinType: SUI_TYPE_ARG }); + async () => { + const info = await bucketClient.getAggregatorObjectInfo({ coinType: SUI_TYPE_ARG }); const agg = 'Pyth' in info ? info.Pyth : info.DerivativeInfo; expect(agg).toBeDefined(); expect(agg!.priceAggregator).toHaveProperty('objectId'); @@ -29,8 +29,8 @@ describe('E2E Object info helpers', () => { it( 'getAggregatorObjectInfo throws for unsupported coin type', - () => { - expect(() => bucketClient.getAggregatorObjectInfo({ coinType: '0x1::invalid::INVALID' })).toThrow( + async () => { + await expect(bucketClient.getAggregatorObjectInfo({ coinType: '0x1::invalid::INVALID' })).rejects.toThrow( 'Unsupported coin type', ); }, @@ -39,8 +39,8 @@ describe('E2E Object info helpers', () => { it( 'getSavingPoolObjectInfo returns pool info for SUSDB', - () => { - const info = bucketClient.getSavingPoolObjectInfo({ lpType: susdbLpType }); + async () => { + const info = await bucketClient.getSavingPoolObjectInfo({ lpType: susdbLpType }); expect(info).toHaveProperty('pool'); expect(info.pool).toHaveProperty('objectId'); }, @@ -49,8 +49,8 @@ describe('E2E Object info helpers', () => { it( 'getSavingPoolObjectInfo throws for unsupported lp type', - () => { - expect(() => bucketClient.getSavingPoolObjectInfo({ lpType: '0x1::invalid::INVALID' })).toThrow( + async () => { + await expect(bucketClient.getSavingPoolObjectInfo({ lpType: '0x1::invalid::INVALID' })).rejects.toThrow( 'Unsupported coin type', ); }, @@ -59,8 +59,8 @@ describe('E2E Object info helpers', () => { it( 'getPsmPoolObjectInfo returns pool info for USDC', - () => { - const info = bucketClient.getPsmPoolObjectInfo({ coinType: usdcCoinType }); + async () => { + const info = await bucketClient.getPsmPoolObjectInfo({ coinType: usdcCoinType }); expect(info).toHaveProperty('pool'); expect(info.pool).toHaveProperty('objectId'); }, @@ -69,8 +69,8 @@ describe('E2E Object info helpers', () => { it( 'getPsmPoolObjectInfo throws for unsupported coin type', - () => { - expect(() => bucketClient.getPsmPoolObjectInfo({ coinType: '0x1::invalid::INVALID' })).toThrow( + async () => { + await expect(bucketClient.getPsmPoolObjectInfo({ coinType: '0x1::invalid::INVALID' })).rejects.toThrow( 'Unsupported coin type', ); }, @@ -79,8 +79,8 @@ describe('E2E Object info helpers', () => { it( 'getVaultObjectInfo throws for unsupported collateral type', - () => { - expect(() => bucketClient.getVaultObjectInfo({ coinType: '0x1::invalid::INVALID' })).toThrow( + async () => { + await expect(bucketClient.getVaultObjectInfo({ coinType: '0x1::invalid::INVALID' })).rejects.toThrow( 'Unsupported collateral type', ); }, diff --git a/test/e2e/onchain-config.test.ts b/test/e2e/onchain-config.test.ts index 112691a..122d25b 100644 --- a/test/e2e/onchain-config.test.ts +++ b/test/e2e/onchain-config.test.ts @@ -57,7 +57,7 @@ describe('On-chain config (testnet)', () => { it( 'getConfig returns a populated ConfigType', async () => { - const config = bucketClient.getConfig(); + const config = await bucketClient.getConfig(); expect(config).toBeDefined(); expect(config.ORIGINAL_FRAMEWORK_PACKAGE_ID).toBeTruthy(); expect(config.ORIGINAL_USDB_PACKAGE_ID).toBeTruthy(); @@ -70,8 +70,8 @@ describe('On-chain config (testnet)', () => { TIMEOUT_MS, ); - it('getUsdbCoinType returns a valid coin type string', () => { - const usdbType = bucketClient.getUsdbCoinType(); + it('getUsdbCoinType returns a valid coin type string', async () => { + const usdbType = await bucketClient.getUsdbCoinType(); expect(usdbType).toContain('::usdb::USDB'); expect(usdbType.startsWith('0x')).toBe(true); }); @@ -79,8 +79,8 @@ describe('On-chain config (testnet)', () => { it( 'getUsdbCoinType uses ORIGINAL_USDB_PACKAGE_ID; resolveType yields current type when upgraded', async () => { - const config = bucketClient.getConfig(); - const rawType = bucketClient.getUsdbCoinType(); + const config = await bucketClient.getConfig(); + const rawType = await bucketClient.getUsdbCoinType(); const rawPkgId = rawType.split('::')[0]; expect(rawPkgId).toBe(config.ORIGINAL_USDB_PACKAGE_ID); @@ -112,8 +112,8 @@ describe('On-chain price config (mainnet)', () => { 'getUsdbCoinType uses ORIGINAL_USDB_PACKAGE_ID; resolveType yields current type when upgraded (mainnet)', async () => { const bucketClient = await BucketClient.initialize({ suiClient: mainnetSuiClient, network: 'mainnet' }); - const config = bucketClient.getConfig(); - const rawType = bucketClient.getUsdbCoinType(); + const config = await bucketClient.getConfig(); + const rawType = await bucketClient.getUsdbCoinType(); const rawPkgId = rawType.split('::')[0]; expect(rawPkgId).toBe(config.ORIGINAL_USDB_PACKAGE_ID); @@ -155,7 +155,7 @@ describe('On-chain price config (mainnet)', () => { 'PRICE_OBJS contains required derivative price variants', async () => { const client = await BucketClient.initialize({ suiClient: mainnetSuiClient, network: 'mainnet' }); - const config = client.getConfig(); + const config = await client.getConfig(); expect(config).toBeDefined(); const priceObjs = config.PRICE_OBJS; @@ -178,7 +178,7 @@ describe('On-chain price config (mainnet)', () => { 'PRICE_OBJS entries have valid PriceConfigInfo shape', async () => { const client = await BucketClient.initialize({ suiClient: mainnetSuiClient, network: 'mainnet' }); - const config = client.getConfig(); + const config = await client.getConfig(); const priceObjs = config.PRICE_OBJS; for (const [key, info] of Object.entries(priceObjs) as [string, PriceConfigInfo][]) { diff --git a/test/e2e/psm.test.ts b/test/e2e/psm.test.ts index 09704d8..21659ee 100644 --- a/test/e2e/psm.test.ts +++ b/test/e2e/psm.test.ts @@ -34,7 +34,7 @@ describe('E2E PSM', () => { const balanceChanges = ( dryrunRes as { Transaction?: { balanceChanges?: { coinType: string; amount: string }[] } } ).Transaction!.balanceChanges!; - const usdbType = getUsdbCoinType(); + const usdbType = await getUsdbCoinType(); expect(balanceChanges.find((c) => c.coinType === usdcCoinType)?.amount).toBe('-1000000'); expect(balanceChanges.find((c) => c.coinType === usdbType)?.amount).toBe('1000000'); }, @@ -46,7 +46,8 @@ describe('E2E PSM', () => { async () => { const tx = txWithSender(); const amount = 1 * 10 ** 6; - const usdbCoin = coinWithBalance({ type: getUsdbCoinType(), balance: amount }); + const usdbType = await getUsdbCoinType(); + const usdbCoin = coinWithBalance({ type: usdbType, balance: amount }); const usdcCoin = await bucketClient.buildPSMSwapOutTransaction(tx, { coinType: usdcCoinType, usdbCoinOrAmount: usdbCoin, @@ -62,7 +63,8 @@ describe('E2E PSM', () => { async () => { const tx = txWithSender(); const amount = 1n * 10n ** 6n; - const usdbCoin = coinWithBalance({ type: getUsdbCoinType(), balance: amount }); + const usdbType = await getUsdbCoinType(); + const usdbCoin = coinWithBalance({ type: usdbType, balance: amount }); const usdcCoin = await bucketClient.buildPSMSwapOutTransaction(tx, { coinType: usdcCoinType, usdbCoinOrAmount: usdbCoin, diff --git a/test/e2e/rewards.test.ts b/test/e2e/rewards.test.ts index 9e07026..798799c 100644 --- a/test/e2e/rewards.test.ts +++ b/test/e2e/rewards.test.ts @@ -22,7 +22,7 @@ describe('E2E Rewards', () => { 'buildClaimBorrowRewardsTransaction dry run succeeds and has reward balance changes', async () => { const tx = txWithSender(); - const collateralTypes = bucketClient.getAllCollateralTypes(); + const collateralTypes = await bucketClient.getAllCollateralTypes(); const result: Record = {}; for (const coinType of collateralTypes) { const rewardsRecord = await bucketClient.buildClaimBorrowRewardsTransaction(tx, { coinType }); diff --git a/test/e2e/saving.test.ts b/test/e2e/saving.test.ts index 9f54f15..d98fe35 100644 --- a/test/e2e/saving.test.ts +++ b/test/e2e/saving.test.ts @@ -32,7 +32,7 @@ describe('E2E Saving', () => { coinType: usdcCoinType, inputCoinOrAmount: usdcCoin, }); - bucketClient.buildDepositToSavingPoolTransaction(tx, { + await bucketClient.buildDepositToSavingPoolTransaction(tx, { lpType: susdbLpType, address: testAccount, depositCoinOrAmount: usdbCoin, @@ -47,8 +47,9 @@ describe('E2E Saving', () => { async () => { const tx = txWithSender(); const amount = TEST_AMOUNT_USDB; - const usdbCoin = coinWithBalance({ type: getUsdbCoinType(), balance: amount }); - bucketClient.buildDepositToSavingPoolTransaction(tx, { + const usdbType = await getUsdbCoinType(); + const usdbCoin = coinWithBalance({ type: usdbType, balance: amount }); + await bucketClient.buildDepositToSavingPoolTransaction(tx, { lpType: susdbLpType, address: testAccount, depositCoinOrAmount: usdbCoin, @@ -63,7 +64,7 @@ describe('E2E Saving', () => { async () => { const tx = txWithSender(); const amount = TEST_AMOUNT_USDB; - const usdbCoin = bucketClient.buildWithdrawFromSavingPoolTransaction(tx, { + const usdbCoin = await bucketClient.buildWithdrawFromSavingPoolTransaction(tx, { lpType: susdbLpType, amount, }); @@ -77,7 +78,7 @@ describe('E2E Saving', () => { 'claim from saving pool', async () => { const tx = txWithSender(); - const rewardsRecord = bucketClient.buildClaimSavingRewardsTransaction(tx, { + const rewardsRecord = await bucketClient.buildClaimSavingRewardsTransaction(tx, { lpType: susdbLpType, }); tx.transferObjects(Object.values(rewardsRecord), testAccount); @@ -90,14 +91,14 @@ describe('E2E Saving', () => { 'deposit/withdraw zero to saving pool', async () => { const tx = txWithSender(); - const usdbType = getUsdbCoinType(); + const usdbType = await getUsdbCoinType(); const zeroUsdbCoin = getZeroCoin(tx, { coinType: usdbType }); - bucketClient.buildDepositToSavingPoolTransaction(tx, { + await bucketClient.buildDepositToSavingPoolTransaction(tx, { lpType: susdbLpType, address: testAccount, depositCoinOrAmount: zeroUsdbCoin, }); - const usdbOut = bucketClient.buildWithdrawFromSavingPoolTransaction(tx, { + const usdbOut = await bucketClient.buildWithdrawFromSavingPoolTransaction(tx, { lpType: susdbLpType, amount: 0, }); @@ -111,14 +112,14 @@ describe('E2E Saving', () => { 'deposit zero via coinWithBalance(balance:0) exercises resolver zero-coin path', async () => { const tx = txWithSender(); - const usdbType = getUsdbCoinType(); + const usdbType = await getUsdbCoinType(); const zeroUsdbCoin = coinWithBalance({ type: usdbType, balance: 0 }); - bucketClient.buildDepositToSavingPoolTransaction(tx, { + await bucketClient.buildDepositToSavingPoolTransaction(tx, { lpType: susdbLpType, address: testAccount, depositCoinOrAmount: zeroUsdbCoin, }); - const usdbOut = bucketClient.buildWithdrawFromSavingPoolTransaction(tx, { + const usdbOut = await bucketClient.buildWithdrawFromSavingPoolTransaction(tx, { lpType: susdbLpType, amount: 0, }); diff --git a/test/e2e/savings-rewards.test.ts b/test/e2e/savings-rewards.test.ts index 9f6885e..d8e3146 100644 --- a/test/e2e/savings-rewards.test.ts +++ b/test/e2e/savings-rewards.test.ts @@ -75,7 +75,7 @@ describe('E2E Savings & rewards', () => { it( 'getAccountBorrowRewards with multiple collateral types exercises vault iteration', async () => { - const coinTypes = bucketClient.getAllCollateralTypes().slice(0, 3); + const coinTypes = (await bucketClient.getAllCollateralTypes()).slice(0, 3); expect(coinTypes.length).toBeGreaterThan(0); const rewards = await bucketClient.getAccountBorrowRewards({ address: testAccount, diff --git a/test/e2e/vaults.test.ts b/test/e2e/vaults.test.ts index 1ef2b04..f42f117 100644 --- a/test/e2e/vaults.test.ts +++ b/test/e2e/vaults.test.ts @@ -12,7 +12,7 @@ describe('E2E Vaults', () => { 'getAllVaultObjects: one entry per collateral type, all with required VaultInfo fields', async () => { const allVaults = await bucketClient.getAllVaultObjects(); - const collateralTypes = bucketClient.getAllCollateralTypes(); + const collateralTypes = await bucketClient.getAllCollateralTypes(); expect(Object.keys(allVaults).length).toBe(collateralTypes.length); for (const [coinType, vault] of Object.entries(allVaults)) { expect(collateralTypes).toContain(coinType); @@ -53,7 +53,7 @@ describe('E2E Vaults', () => { it( 'getVaultObjectInfo returns config for known collateral (SUI)', async () => { - const info = bucketClient.getVaultObjectInfo({ coinType: SUI_TYPE_ARG }); + const info = await bucketClient.getVaultObjectInfo({ coinType: SUI_TYPE_ARG }); expect(info).toHaveProperty('vault'); expect(info.vault).toHaveProperty('objectId'); expect(typeof info.vault.objectId).toBe('string'); diff --git a/test/unit/client.test.ts b/test/unit/client.test.ts index 07d3fe5..e336d26 100644 --- a/test/unit/client.test.ts +++ b/test/unit/client.test.ts @@ -83,7 +83,7 @@ describe('unit/client', () => { expect.anything(), expect.objectContaining({ PRICE_SERVICE_ENDPOINT: 'https://custom.hermes.test' }), ); - const config = client.getConfig(); + const config = await client.getConfig(); expect(config.PRICE_SERVICE_ENDPOINT).toBe('https://custom.hermes.test'); }); @@ -136,7 +136,7 @@ describe('unit/client', () => { expect.anything(), expect.objectContaining({ PRICE_SERVICE_ENDPOINT: 'https://explicit.override.test' }), ); - const config = client.getConfig(); + const config = await client.getConfig(); expect(config.PRICE_SERVICE_ENDPOINT).toBe('https://explicit.override.test'); }); @@ -162,36 +162,14 @@ describe('unit/client', () => { }); describe('constructor requires config', () => { - it('throws when config is undefined', () => { - expect( - () => - new BucketClient({ - suiClient: asSuiClient({}), - network: 'mainnet', - config: undefined as unknown as ConfigType, - }), - ).toThrow('BucketClient requires config'); - }); - - it('throws when config is null', () => { - expect( - () => - new BucketClient({ - suiClient: asSuiClient({}), - network: 'mainnet', - config: null as unknown as ConfigType, - }), - ).toThrow('BucketClient requires config'); - }); - - it('accepts custom config for testing', () => { + it('accepts custom config for testing', async () => { const custom = minimalConfig({ PRICE_SERVICE_ENDPOINT: 'https://custom.test' }); const client = new BucketClient({ suiClient: asSuiClient({ getObjects: vi.fn() }), network: 'mainnet', config: custom, }); - const config = client.getConfig(); + const config = await client.getConfig(); expect(config).toBeDefined(); expect(config.PRICE_SERVICE_ENDPOINT).toBe('https://custom.test'); }); @@ -210,7 +188,7 @@ describe('unit/client', () => { }); expect(querySpy).not.toHaveBeenCalled(); - expect(client.getConfig().PRICE_SERVICE_ENDPOINT).toBe('https://prebuilt.test'); + expect((await client.getConfig()).PRICE_SERVICE_ENDPOINT).toBe('https://prebuilt.test'); }); it('uses provided config as-is (configOverrides ignored when config is passed)', async () => { @@ -225,7 +203,7 @@ describe('unit/client', () => { }); // When config is passed, configOverrides is not applied (only used with chain fetch) - expect(client.getConfig().PRICE_SERVICE_ENDPOINT).toBe('https://base.test'); + expect((await client.getConfig()).PRICE_SERVICE_ENDPOINT).toBe('https://base.test'); }); }); @@ -293,7 +271,7 @@ describe('unit/client', () => { suiClient: asSuiClient({ getObjects: vi.fn() }), network: 'mainnet', }); - expect(() => client.getAggregatorObjectInfo({ coinType: '0xunknown::coin::UNKNOWN' })).toThrow( + await expect(client.getAggregatorObjectInfo({ coinType: '0xunknown::coin::UNKNOWN' })).rejects.toThrow( 'Unsupported coin type', ); }); @@ -309,7 +287,7 @@ describe('unit/client', () => { suiClient: asSuiClient({ getObjects: vi.fn() }), network: 'mainnet', }); - expect(() => client.getVaultObjectInfo({ coinType: '0xunknown::coin::UNKNOWN' })).toThrow( + await expect(client.getVaultObjectInfo({ coinType: '0xunknown::coin::UNKNOWN' })).rejects.toThrow( 'Unsupported collateral type', ); }); @@ -325,7 +303,7 @@ describe('unit/client', () => { suiClient: asSuiClient({ getObjects: vi.fn() }), network: 'mainnet', }); - expect(() => client.getSavingPoolObjectInfo({ lpType: '0xunknown::lp::UNKNOWN' })).toThrow( + await expect(client.getSavingPoolObjectInfo({ lpType: '0xunknown::lp::UNKNOWN' })).rejects.toThrow( 'Unsupported coin type', ); }); @@ -341,7 +319,9 @@ describe('unit/client', () => { suiClient: asSuiClient({ getObjects: vi.fn() }), network: 'mainnet', }); - expect(() => client.getPsmPoolObjectInfo({ coinType: '0xunknown::usdc::USDC' })).toThrow('Unsupported coin type'); + await expect(client.getPsmPoolObjectInfo({ coinType: '0xunknown::usdc::USDC' })).rejects.toThrow( + 'Unsupported coin type', + ); }); }); @@ -357,7 +337,7 @@ describe('unit/client', () => { }); const { Transaction } = await import('@mysten/sui/transactions'); const tx = new Transaction(); - const result = client.accountAddress(tx, { + const result = await client.accountAddress(tx, { address: '0xdead', accountObjectOrId: validAddress, }); @@ -376,7 +356,7 @@ describe('unit/client', () => { const { Transaction } = await import('@mysten/sui/transactions'); const tx = new Transaction(); const objRef = tx.pure.address(validAddress); - const result = client.accountAddress(tx, { + const result = await client.accountAddress(tx, { address: '0xdead', accountObjectOrId: objRef, }); @@ -394,7 +374,7 @@ describe('unit/client', () => { }); const { Transaction } = await import('@mysten/sui/transactions'); const tx = new Transaction(); - const result = client.newAccountRequest(tx, { accountObjectOrId: validAddress }); + const result = await client.newAccountRequest(tx, { accountObjectOrId: validAddress }); expect(result).toBeDefined(); const data = tx.getData() as { commands: unknown[] }; const moveCall = data.commands.find((c: unknown) => (c as { MoveCall?: unknown }).MoveCall) as { @@ -413,7 +393,7 @@ describe('unit/client', () => { const { Transaction } = await import('@mysten/sui/transactions'); const tx = new Transaction(); const objRef = tx.pure.address(validAddress); - const result = client.newAccountRequest(tx, { accountObjectOrId: objRef }); + const result = await client.newAccountRequest(tx, { accountObjectOrId: objRef }); expect(result).toBeDefined(); const data = tx.getData() as { commands: unknown[] }; const moveCall = data.commands.find((c: unknown) => (c as { MoveCall?: unknown }).MoveCall) as {