From 5ac241d04332209cb24f4802fd317ae71baf2841 Mon Sep 17 00:00:00 2001 From: Shrom Date: Wed, 22 Apr 2026 20:30:06 +0530 Subject: [PATCH 1/2] structure change for slush single asset loop strategy. note: strategy name will be corrected in subsequent prs --- scripts/testRun.ts | 16 +- src/models/strategyContext.ts | 3 +- src/strategies/slushSingleAssetLooping.ts | 182 ++++++++++++++-------- src/strategies/strategy.ts | 5 +- src/utils/testing-pools.ts | 18 ++- 5 files changed, 148 insertions(+), 76 deletions(-) diff --git a/scripts/testRun.ts b/scripts/testRun.ts index 7be43e6..274b838 100644 --- a/scripts/testRun.ts +++ b/scripts/testRun.ts @@ -176,7 +176,7 @@ async function poolsData() { network: 'mainnet', }); const data = await sdk.getSinglePoolData( - '0xccc08b2e42a88002b4bd505e7e0b5bed17079d4cafc2ccbe82da0172d5291867', + '0x0bca47c53d57d203d19611af98a4e723c52cbf1bc58312360bfb5dcba0286de9', ); console.log('data', data); } @@ -188,7 +188,7 @@ async function portfolioData() { }); const data = await sdk.getUserSinglePoolBalance( address, - '0xccc08b2e42a88002b4bd505e7e0b5bed17079d4cafc2ccbe82da0172d5291867', + '0x0bca47c53d57d203d19611af98a4e723c52cbf1bc58312360bfb5dcba0286de9', ); console.log('user data', data); } @@ -232,7 +232,7 @@ async function claimSlushWithdraw() { const sdk = new AlphaFiSDK({ suiClient: suiClient, network: 'mainnet' }); const tx = await sdk.claimWithdrawSlush({ poolId: '0x0bca47c53d57d203d19611af98a4e723c52cbf1bc58312360bfb5dcba0286de9', - withdrawRequestId: '0xd7c583c1a6b2849ed2a8164747cb4dda02b3bd56ef1f76cc0cfbd3301a9a1c7f', + withdrawRequestId: '0xdd9adca11bea0925ad8e1eaec8f68896087c12c6449740f356141ec9bfd52a58', address, }); tx.setGasBudget(2e8); @@ -244,12 +244,12 @@ async function cancelSlushWithdraw() { const sdk = new AlphaFiSDK({ suiClient: suiClient, network: 'mainnet' }); const tx = await sdk.cancelWithdrawSlush({ poolId: '0x0bca47c53d57d203d19611af98a4e723c52cbf1bc58312360bfb5dcba0286de9', - withdrawRequestId: '0xd7c583c1a6b2849ed2a8164747cb4dda02b3bd56ef1f76cc0cfbd3301a9a1c7f', + withdrawRequestId: '0xdd9adca11bea0925ad8e1eaec8f68896087c12c6449740f356141ec9bfd52a58', address, }); tx.setGasBudget(2e8); - // dryRunTransactionBlock(tx); - executeTransactionBlock(tx); + dryRunTransactionBlock(tx); + // executeTransactionBlock(tx); } async function claimAirdrop() { const { address, keypair, suiClient } = getExecStuff(); @@ -260,9 +260,9 @@ async function claimAirdrop() { // executeTransactionBlock(tx); } // claimAirdrop(); -withdraw(); +// withdraw(); // poolsData(); // portfolioData(); // claimSlushWithdraw(); -// deposit(); +deposit(); // cancelSlushWithdraw(); diff --git a/src/models/strategyContext.ts b/src/models/strategyContext.ts index a9e53a8..8fde950 100644 --- a/src/models/strategyContext.ts +++ b/src/models/strategyContext.ts @@ -266,7 +266,8 @@ export class StrategyContext { packageId: d.package_id, strategyType: strategyType, parentProtocol: d.parent_protocol, - asset: d.asset, + assetA: d.asset_a, + assetB: d.asset_b, events: { autocompoundEventType: d.events?.xtoken_ratio_change_event_type, }, diff --git a/src/strategies/slushSingleAssetLooping.ts b/src/strategies/slushSingleAssetLooping.ts index b6ae6f3..c82b5f5 100644 --- a/src/strategies/slushSingleAssetLooping.ts +++ b/src/strategies/slushSingleAssetLooping.ts @@ -95,7 +95,7 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< return { poolId: this.poolLabel.poolId, strategyType: this.poolLabel.strategyType, - coinType: this.poolLabel.asset.type, + coinType: this.poolLabel.assetA.type, poolName: this.poolLabel.poolName, apr, tvl: { @@ -109,7 +109,7 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< * Calculate total value locked using current asset price */ async getTvl(): Promise { - const coinType = this.poolLabel.asset.type; + const coinType = this.poolLabel.assetA.type; const [price, decimals] = await Promise.all([ this.context.getCoinPrice(coinType), this.context.getCoinDecimals(coinType), @@ -130,8 +130,8 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< throw new Error(`Unsupported parent protocol for SlushSingleAssetLooping: ${protocol}`); } const [tokenAmount, price] = await Promise.all([ - this.context.getAlphaLendTvl(this.poolLabel.asset.type), - this.context.getCoinPrice(this.poolLabel.asset.type), + this.context.getAlphaLendTvl(this.poolLabel.assetA.type), + this.context.getCoinPrice(this.poolLabel.assetA.type), ]); return { tokenAmount, usdValue: tokenAmount.mul(price) }; } @@ -141,8 +141,8 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< */ async getBalance(_userAddress: string): Promise { const [price, decimals] = await Promise.all([ - this.context.getCoinPrice(this.poolLabel.asset.type), - this.context.getCoinDecimals(this.poolLabel.asset.type), + this.context.getCoinPrice(this.poolLabel.assetA.type), + this.context.getCoinDecimals(this.poolLabel.assetA.type), ]); if (this.receiptObjects.length === 0 || this.receiptObjects[0].xTokens === '0') { const withdrawals = await this.getWithdrawalsStatus(); @@ -171,7 +171,7 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< async getWithdrawalsStatus(): Promise { if (this.receiptObjects.length === 0) return []; - const decimals = await this.context.getCoinDecimals(this.poolLabel.asset.type); + const decimals = await this.context.getCoinDecimals(this.poolLabel.assetA.type); const currentTime = Date.now(); return this.receiptObjects[0].withdrawRequests.map((req) => { @@ -194,7 +194,7 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< parsePoolObject(response: any): SlushSingleAssetLoopingPoolObject { return this.safeParseObject(() => { const fields = this.extractFields(response); - + const marketId = this.getStringField(fields.investor, 'market_id'); return { id: this.getStringField(fields, 'id'), xTokenSupply: @@ -207,7 +207,8 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< positionsTableId: this.getNestedField(fields, 'positions.id'), investor: { id: this.getStringField(fields.investor, 'id'), - marketId: this.getStringField(fields.investor, 'market_id'), + marketId, + borrowMarketId: this.getStringField(fields.investor, 'borrow_market_id', marketId), positionCap: { clientAddress: this.getNestedField( fields, @@ -293,26 +294,32 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< async deposit(tx: Transaction, options: DepositOptions) { const alphalendClient = new AlphalendClient('mainnet', this.context.blockchain.suiClient); - await alphalendClient.updatePrices(tx, [this.poolLabel.asset.type]); + await alphalendClient.updatePrices(tx, [this.poolLabel.assetA.type]); await this.collectAndSwapRewards(tx); // Get coin object const depositCoin = await this.context.blockchain.getCoinObject( tx, - this.poolLabel.asset.type, + this.poolLabel.assetA.type, options.address, BigInt(options.amount), ); const positionCaps = await this.context.getSlushPositionCaps(options.address); - const target = `${this.poolLabel.packageId}::alphalend_slush_locked_loop_pool::user_deposit`; + let target = `${this.poolLabel.packageId}::alphalend_slush_locked_loop_pool::user_deposit`; + let typeArguments = [this.poolLabel.assetA.type]; + const isSingle = this.poolLabel.assetA.type === this.poolLabel.assetB.type; + if (!isSingle) { + target = `${this.poolLabel.packageId}::alphalend_slush_loop_pool::user_deposit`; + typeArguments.push(this.poolLabel.assetB.type); + } if (positionCaps.length === 0) { const positionCap: TransactionResult = this.createPositionCap(tx); tx.moveCall({ target, - typeArguments: [this.poolLabel.asset.type], + typeArguments, arguments: [ tx.object(VERSIONS.SLUSH), positionCap, @@ -326,7 +333,7 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< } else { tx.moveCall({ target, - typeArguments: [this.poolLabel.asset.type], + typeArguments, arguments: [ tx.object(VERSIONS.SLUSH), tx.object(positionCaps[0].id), @@ -345,7 +352,7 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< } const alphalendClient = new AlphalendClient('mainnet', this.context.blockchain.suiClient); - await alphalendClient.updatePrices(tx, [this.poolLabel.asset.type]); + await alphalendClient.updatePrices(tx, [this.poolLabel.assetA.type]); await this.collectAndSwapRewards(tx); @@ -355,68 +362,109 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< } const positionCaps = await this.context.getSlushPositionCaps(options.address); - const target = `${this.poolLabel.packageId}::alphalend_slush_locked_loop_pool::user_initiate_withdraw`; - + let target = `${this.poolLabel.packageId}::alphalend_slush_locked_loop_pool::user_initiate_withdraw`; + let typeArguments = [this.poolLabel.assetA.type]; + let args = [ + tx.object(VERSIONS.SLUSH), + tx.object(positionCaps[0].id), + tx.object(this.poolLabel.poolId), + tx.pure.u64(xTokenAmount), + tx.object(ALPHALEND_LENDING_PROTOCOL_ID), + ]; + const isSingle = this.poolLabel.assetA.type === this.poolLabel.assetB.type; + if (!isSingle) { + target = `${this.poolLabel.packageId}::alphalend_slush_loop_pool::user_initiate_withdraw`; + typeArguments.push(this.poolLabel.assetB.type); + args.push( + tx.object( + await this.context.getPoolIdByTypesAndProtocol( + this.poolLabel.assetA.type, + this.poolLabel.assetB.type, + 'bluefin', + true, + ), + ), + ); + args.push(tx.object(GLOBAL_CONFIGS.BLUEFIN)); + } + args.push(tx.object(CLOCK_PACKAGE_ID)); tx.moveCall({ target, - typeArguments: [this.poolLabel.asset.type], - arguments: [ - tx.object(VERSIONS.SLUSH), - tx.object(positionCaps[0].id), - tx.object(this.poolLabel.poolId), - tx.pure.u64(xTokenAmount), - tx.object(ALPHALEND_LENDING_PROTOCOL_ID), - tx.object(CLOCK_PACKAGE_ID), - ], + typeArguments, + arguments: args, }); } async claimWithdraw(tx: Transaction, withdrawRequestId: string, address: string) { const alphalendClient = new AlphalendClient('mainnet', this.context.blockchain.suiClient); - await alphalendClient.updatePrices(tx, [this.poolLabel.asset.type]); + await alphalendClient.updatePrices(tx, [this.poolLabel.assetA.type]); await this.collectAndSwapRewards(tx); const positionCaps = await this.context.getSlushPositionCaps(address); if (positionCaps.length === 0) { throw new Error('No position cap found for claim'); } - - const target = `${this.poolLabel.packageId}::alphalend_slush_locked_loop_pool::user_claim_withdraw`; + let target = `${this.poolLabel.packageId}::alphalend_slush_locked_loop_pool::user_claim_withdraw`; + let typeArguments = [this.poolLabel.assetA.type]; + let args = [ + tx.object(VERSIONS.SLUSH), + tx.object(positionCaps[0].id), + tx.object(this.poolLabel.poolId), + tx.pure.id(withdrawRequestId), + tx.object(ALPHALEND_LENDING_PROTOCOL_ID), + ]; + const isSingle = this.poolLabel.assetA.type === this.poolLabel.assetB.type; + if (!isSingle) { + target = `${this.poolLabel.packageId}::alphalend_slush_loop_pool::user_claim_withdraw`; + typeArguments.push(this.poolLabel.assetB.type); + args.push( + tx.object( + await this.context.getPoolIdByTypesAndProtocol( + this.poolLabel.assetA.type, + this.poolLabel.assetB.type, + 'bluefin', + true, + ), + ), + ); + args.push(tx.object(GLOBAL_CONFIGS.BLUEFIN)); + } + args.push(tx.object(CLOCK_PACKAGE_ID)); const coin = tx.moveCall({ target, - typeArguments: [this.poolLabel.asset.type], - arguments: [ - tx.object(VERSIONS.SLUSH), - tx.object(positionCaps[0].id), - tx.object(this.poolLabel.poolId), - tx.pure.id(withdrawRequestId), - tx.object(ALPHALEND_LENDING_PROTOCOL_ID), - tx.object(CLOCK_PACKAGE_ID), - ], + typeArguments, + arguments: args, }); tx.transferObjects([coin], address); } async cancelWithdraw(tx: Transaction, withdrawRequestId: string, address: string) { const alphalendClient = new AlphalendClient('mainnet', this.context.blockchain.suiClient); - await alphalendClient.updatePrices(tx, [this.poolLabel.asset.type]); + await alphalendClient.updatePrices(tx, [this.poolLabel.assetA.type]); await this.collectAndSwapRewards(tx); const positionCaps = await this.context.getSlushPositionCaps(address); if (positionCaps.length === 0) { throw new Error('No position cap found for cancellation'); } - const target = `${this.poolLabel.packageId}::alphalend_slush_locked_loop_pool::user_cancel_withdraw`; + let target = `${this.poolLabel.packageId}::alphalend_slush_locked_loop_pool::user_cancel_withdraw`; + let typeArguments = [this.poolLabel.assetA.type]; + let args = [ + tx.object(VERSIONS.SLUSH), + tx.object(positionCaps[0].id), + tx.object(this.poolLabel.poolId), + tx.pure.id(withdrawRequestId), + tx.object(ALPHALEND_LENDING_PROTOCOL_ID), + ]; + const isSingle = this.poolLabel.assetA.type === this.poolLabel.assetB.type; + if (!isSingle) { + target = `${this.poolLabel.packageId}::alphalend_slush_loop_pool::user_cancel_withdraw`; + typeArguments.push(this.poolLabel.assetB.type); + } + args.push(tx.object(CLOCK_PACKAGE_ID)); tx.moveCall({ target, - typeArguments: [this.poolLabel.asset.type], - arguments: [ - tx.object(VERSIONS.SLUSH), - tx.object(positionCaps[0].id), - tx.object(this.poolLabel.poolId), - tx.pure.id(withdrawRequestId), - tx.object(ALPHALEND_LENDING_PROTOCOL_ID), - tx.object(CLOCK_PACKAGE_ID), - ], + typeArguments, + arguments: args, }); } @@ -481,14 +529,20 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< console.log('no rewards for pool id: ', this.poolLabel.poolId); return; } - + let target = `${this.poolLabel.packageId}::alphalend_slush_locked_loop_pool::collect_reward_and_swap_bluefin`; + let typeArgs = [this.poolLabel.assetA.type]; + const isSingle = this.poolLabel.assetA.type === this.poolLabel.assetB.type; + if (!isSingle) { + target = `${this.poolLabel.packageId}::alphalend_slush_loop_pool::collect_reward_and_swap_bluefin`; + typeArgs.push(this.poolLabel.assetB.type); + } // Loop through rewards and collect/swap each one for (const x of rewards) { if (x.coinType == alphaCoin.coinType) { // ALPHA -> stSUI -> SUI tx.moveCall({ - target: `${this.poolLabel.packageId}::alphalend_slush_locked_loop_pool::collect_reward_and_swap_bluefin`, - typeArguments: [this.poolLabel.asset.type, alphaCoin.coinType, stsuiCoin.coinType], + target, + typeArguments: [...typeArgs, alphaCoin.coinType, stsuiCoin.coinType], arguments: [ tx.object(VERSIONS.SLUSH), tx.object(this.poolLabel.poolId), @@ -504,8 +558,8 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< ], }); tx.moveCall({ - target: `${this.poolLabel.packageId}::alphalend_slush_locked_loop_pool::collect_reward_and_swap_bluefin`, - typeArguments: [this.poolLabel.asset.type, stsuiCoin.coinType, suiCoin.coinType], + target, + typeArguments: [...typeArgs, stsuiCoin.coinType, suiCoin.coinType], arguments: [ tx.object(VERSIONS.SLUSH), tx.object(this.poolLabel.poolId), @@ -518,11 +572,11 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< tx.object(CLOCK_PACKAGE_ID), ], }); - } else if (coinTypes.includes(x.coinType) && x.coinType != this.poolLabel.asset.type) { + } else if (coinTypes.includes(x.coinType) && x.coinType != this.poolLabel.assetA.type) { // Other rewards -> SUI tx.moveCall({ - target: `${this.poolLabel.packageId}::alphalend_slush_locked_loop_pool::collect_reward_and_swap_bluefin`, - typeArguments: [this.poolLabel.asset.type, x.coinType, suiCoin.coinType], + target, + typeArguments: [...typeArgs, x.coinType, suiCoin.coinType], arguments: [ tx.object(VERSIONS.SLUSH), tx.object(this.poolLabel.poolId), @@ -546,11 +600,11 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< // After collecting all rewards, handle base asset conversion // If pool asset is USDSUI, convert SUI -> USDC, then USDC -> USDSUI - if (this.poolLabel.asset.name === 'USDSUI') { + if (this.poolLabel.assetA.name === 'USDSUI') { // SUI -> USDC tx.moveCall({ - target: `${this.poolLabel.packageId}::alphalend_slush_locked_loop_pool::collect_reward_and_swap_bluefin`, - typeArguments: [this.poolLabel.asset.type, suiCoin.coinType, usdcCoin.coinType], + target, + typeArguments: [...typeArgs, suiCoin.coinType, usdcCoin.coinType], arguments: [ tx.object(VERSIONS.SLUSH), tx.object(this.poolLabel.poolId), @@ -565,8 +619,8 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< }); // USDC -> USDSUI (base asset) tx.moveCall({ - target: `${this.poolLabel.packageId}::alphalend_slush_locked_loop_pool::collect_reward_and_swap_bluefin`, - typeArguments: [this.poolLabel.asset.type, this.poolLabel.asset.type, usdcCoin.coinType], + target, + typeArguments: [...typeArgs, this.poolLabel.assetA.type, usdcCoin.coinType], arguments: [ tx.object(VERSIONS.SLUSH), tx.object(this.poolLabel.poolId), @@ -595,6 +649,7 @@ export interface SlushSingleAssetLoopingPoolObject { investor: { id: string; marketId: string; + borrowMarketId: string; positionCap: { clientAddress: string; id: string; @@ -638,7 +693,8 @@ export interface SlushSingleAssetLoopingPoolLabel { packageId: string; strategyType: 'SlushSingleAssetLooping'; parentProtocol: ProtocolType; - asset: StringMap; + assetA: StringMap; + assetB: StringMap; events: { autocompoundEventType: string; }; diff --git a/src/strategies/strategy.ts b/src/strategies/strategy.ts index 849f35b..852831a 100644 --- a/src/strategies/strategy.ts +++ b/src/strategies/strategy.ts @@ -323,7 +323,7 @@ export abstract class BaseStrategy Date: Thu, 23 Apr 2026 19:37:33 +0530 Subject: [PATCH 2/2] reversed pool handling --- src/strategies/slushSingleAssetLooping.ts | 79 +++++++++++++++++------ 1 file changed, 61 insertions(+), 18 deletions(-) diff --git a/src/strategies/slushSingleAssetLooping.ts b/src/strategies/slushSingleAssetLooping.ts index c82b5f5..261f75a 100644 --- a/src/strategies/slushSingleAssetLooping.ts +++ b/src/strategies/slushSingleAssetLooping.ts @@ -209,6 +209,7 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< id: this.getStringField(fields.investor, 'id'), marketId, borrowMarketId: this.getStringField(fields.investor, 'borrow_market_id', marketId), + isReversedForSwap: this.getBooleanField(fields.investor, 'is_reversed_for_swap', false), positionCap: { clientAddress: this.getNestedField( fields, @@ -313,6 +314,13 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< if (!isSingle) { target = `${this.poolLabel.packageId}::alphalend_slush_loop_pool::user_deposit`; typeArguments.push(this.poolLabel.assetB.type); + if (this.poolObject.investor.isReversedForSwap) { + typeArguments.push(this.poolLabel.assetB.type); + typeArguments.push(this.poolLabel.assetA.type); + } else { + typeArguments.push(this.poolLabel.assetA.type); + typeArguments.push(this.poolLabel.assetB.type); + } } if (positionCaps.length === 0) { @@ -375,16 +383,33 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< if (!isSingle) { target = `${this.poolLabel.packageId}::alphalend_slush_loop_pool::user_initiate_withdraw`; typeArguments.push(this.poolLabel.assetB.type); - args.push( - tx.object( - await this.context.getPoolIdByTypesAndProtocol( - this.poolLabel.assetA.type, - this.poolLabel.assetB.type, - 'bluefin', - true, + if (this.poolObject.investor.isReversedForSwap) { + typeArguments.push(this.poolLabel.assetB.type); + typeArguments.push(this.poolLabel.assetA.type); + args.push( + tx.object( + await this.context.getPoolIdByTypesAndProtocol( + this.poolLabel.assetB.type, + this.poolLabel.assetA.type, + 'bluefin', + true, + ), ), - ), - ); + ); + } else { + typeArguments.push(this.poolLabel.assetA.type); + typeArguments.push(this.poolLabel.assetB.type); + args.push( + tx.object( + await this.context.getPoolIdByTypesAndProtocol( + this.poolLabel.assetA.type, + this.poolLabel.assetB.type, + 'bluefin', + true, + ), + ), + ); + } args.push(tx.object(GLOBAL_CONFIGS.BLUEFIN)); } args.push(tx.object(CLOCK_PACKAGE_ID)); @@ -416,16 +441,33 @@ export class SlushSingleAssetLoopingStrategy extends BaseStrategy< if (!isSingle) { target = `${this.poolLabel.packageId}::alphalend_slush_loop_pool::user_claim_withdraw`; typeArguments.push(this.poolLabel.assetB.type); - args.push( - tx.object( - await this.context.getPoolIdByTypesAndProtocol( - this.poolLabel.assetA.type, - this.poolLabel.assetB.type, - 'bluefin', - true, + if (this.poolObject.investor.isReversedForSwap) { + typeArguments.push(this.poolLabel.assetB.type); + typeArguments.push(this.poolLabel.assetA.type); + args.push( + tx.object( + await this.context.getPoolIdByTypesAndProtocol( + this.poolLabel.assetB.type, + this.poolLabel.assetA.type, + 'bluefin', + true, + ), + ), + ); + } else { + typeArguments.push(this.poolLabel.assetA.type); + typeArguments.push(this.poolLabel.assetB.type); + args.push( + tx.object( + await this.context.getPoolIdByTypesAndProtocol( + this.poolLabel.assetA.type, + this.poolLabel.assetB.type, + 'bluefin', + true, + ), ), - ), - ); + ); + } args.push(tx.object(GLOBAL_CONFIGS.BLUEFIN)); } args.push(tx.object(CLOCK_PACKAGE_ID)); @@ -650,6 +692,7 @@ export interface SlushSingleAssetLoopingPoolObject { id: string; marketId: string; borrowMarketId: string; + isReversedForSwap: boolean; positionCap: { clientAddress: string; id: string;