diff --git a/apps/ui/src/hooks/interaction/useSolanaPoolOperationsMutation.ts b/apps/ui/src/hooks/interaction/useSolanaPoolOperationsMutation.ts index c848f5d2f..915a51ab8 100644 --- a/apps/ui/src/hooks/interaction/useSolanaPoolOperationsMutation.ts +++ b/apps/ui/src/hooks/interaction/useSolanaPoolOperationsMutation.ts @@ -17,7 +17,6 @@ import { } from "../solana"; export const useSolanaPoolOperationsMutation = () => { - const { env } = useEnvironment(); const config = useEnvironment(selectConfig, shallow); const { pools } = config; const { data: splTokenAccounts = [] } = useUserSolanaTokenAccountsQuery(); @@ -65,7 +64,6 @@ export const useSolanaPoolOperationsMutation = () => { let inputTxId = inputState.txId; if (inputTxId === null) { inputTxId = await doSingleSolanaPoolOperation( - env, solanaClient, wallet, splTokenAccounts, @@ -97,7 +95,6 @@ export const useSolanaPoolOperationsMutation = () => { inputTx, ); const outputTxId = await doSingleSolanaPoolOperation( - env, solanaClient, wallet, splTokenAccounts, diff --git a/apps/ui/src/hooks/swim/usePoolMaths.ts b/apps/ui/src/hooks/swim/usePoolMaths.ts index 4bf1e6c08..91a13962c 100644 --- a/apps/ui/src/hooks/swim/usePoolMaths.ts +++ b/apps/ui/src/hooks/swim/usePoolMaths.ts @@ -71,13 +71,13 @@ const getPoolMath = ({ // lpFee const humanLpFee = atomicToHuman( - new Decimal(poolState.lpFee), + new Decimal(poolState.lpFee.value), poolSpec.feeDecimals, ); // governanceFee const humanGovernanceFee = atomicToHuman( - new Decimal(poolState.governanceFee), + new Decimal(poolState.governanceFee.value), poolSpec.feeDecimals, ); diff --git a/apps/ui/src/hooks/swim/usePoolStateQueries.ts b/apps/ui/src/hooks/swim/usePoolStateQueries.ts index 629a970b6..c102815d1 100644 --- a/apps/ui/src/hooks/swim/usePoolStateQueries.ts +++ b/apps/ui/src/hooks/swim/usePoolStateQueries.ts @@ -3,13 +3,11 @@ import { EVM_ECOSYSTEMS } from "@swim-io/evm"; import { SOLANA_ECOSYSTEM_ID } from "@swim-io/solana"; import type { UseQueryResult } from "react-query"; import { useQueries } from "react-query"; -import shallow from "zustand/shallow.js"; import type { PoolSpec } from "../../config"; -import { selectConfig } from "../../core/selectors"; import { useEnvironment } from "../../core/store"; import type { PoolState } from "../../models"; -import { getEvmPoolState, getSolanaPoolState } from "../../models"; +import { getLegacySolanaPoolState } from "../../models"; import { useGetEvmClient } from "../evm"; import { useSolanaClient } from "../solana"; @@ -17,8 +15,7 @@ export const usePoolStateQueries = ( poolSpecs: readonly PoolSpec[], ): readonly UseQueryResult[] => { const { env } = useEnvironment(); - const { tokens } = useEnvironment(selectConfig, shallow); - const getEvmConnection = useGetEvmClient(); + const getEvmClient = useGetEvmClient(); const solanaClient = useSolanaClient(); return useQueries( @@ -27,23 +24,21 @@ export const usePoolStateQueries = ( queryFn: async () => { const { ecosystem } = poolSpec; if (ecosystem === SOLANA_ECOSYSTEM_ID) { - return await getSolanaPoolState(solanaClient, poolSpec); + if (poolSpec.isLegacyPool) { + return await getLegacySolanaPoolState(solanaClient, poolSpec); + } + return await solanaClient.getPoolState(poolSpec.id); } if (ecosystem === APTOS_ECOSYSTEM_ID) { return null; // TODO aptos } - const evmConnection = getEvmConnection(ecosystem); + const evmClient = getEvmClient(ecosystem); const routingContractAddress = EVM_ECOSYSTEMS[ecosystem].chains[env]?.routingContractAddress ?? null; if (routingContractAddress === null) { return null; } - return await getEvmPoolState( - evmConnection, - poolSpec, - tokens, - routingContractAddress, - ); + return await evmClient.getPoolState(poolSpec.id); }, })), ) as readonly UseQueryResult[]; diff --git a/apps/ui/src/models/solana/deserializeLegacySolanaPoolState.test.ts b/apps/ui/src/models/solana/deserializeLegacySolanaPoolState.test.ts new file mode 100644 index 000000000..af4bc06e0 --- /dev/null +++ b/apps/ui/src/models/solana/deserializeLegacySolanaPoolState.test.ts @@ -0,0 +1,117 @@ +import { Buffer } from "buffer"; + +import { PublicKey } from "@solana/web3.js"; +import BN from "bn.js"; + +import type { LegacySolanaPoolState } from "./deserializeLegacySolanaPoolState"; +import { deserializeLegacySolanaPoolState } from "./deserializeLegacySolanaPoolState"; + +describe("deserializeLegacySolanaPoolState", () => { + it("deserializes a SolanaPoolState", () => { + const serialized = Buffer.from( + "00000100000000000000000000000000000000e8030000000000000000000000000000002c01000064000000990e9632b0b9f2e636feb3f0a4220f8aadf9677b451c982a4151af42e0362e8800c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61ce010e60afedb22717bd63192f54145a3f965a33bb82d2c7029eb2ce1e20826487f81d7f931ba1c5db9f5a8b2ac5e149ef9c76d9cf196615bd21163316e8c410bdd7aa20228a7bc21e67ddfe78d5d89b986d4bf5c8d5dc9d4574d81ab11e5a02012262c2067049b5c6d6a6869e7a37bbee162637f78192c3b15e8427676a422574616a65b31ff1d8f707eb279bf8a729a6644151b16d72f9694af6ae499881ed02020202000048ccc8aa094ba7b3495776e123587f2454a935671548ccdc3f4311a9febbdd18fb56a83f5d24d5e7513f96b8c24bff58e7259e92f2fd6f01162f9b0b5188d23e32bf5157ba942716dbab775cde82f881ededa5a96b325714e2bef602679dc3cd1205cdb06ade7ab0c78b50a6e7cc2dd83edfa10423348951b7ce231b6c920334bfcf845603efc68ddea00872ad53de92ed69227e373bdca1a21f78782ec87fec7b90e07d2a4fd0d055d08430d9524a755cacd7a7a531ed91705e8b823e59d820cf609300f5b15b7009876930926f1b5c4a6ecdbc035219127e8ff47ef369abbc7ef4d44674e963fe6e94097d729f1c29c382a3ca684cfd454347f97ee142959e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008d8fcd9e56d407000000000000000000", + "hex", + ); + + const expected: LegacySolanaPoolState = { + ecosystem: "solana", + bump: 0, + isPaused: false, + ampFactor: { + initialValue: { + value: new BN(1), + decimals: 0, + }, + initialTs: new BN(0), + targetValue: { + value: new BN(1000), + decimals: 0, + }, + targetTs: new BN(0), + }, + lpFee: 300, + governanceFee: 100, + lpMintKey: new PublicKey("BJUH9GJLaMSLV1E7B3SQLCy9eCfyr6zsrwGcpS2MkqR1"), + lpDecimalEqualizer: 0, + tokenMintKeys: [ + new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"), + new PublicKey("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"), + new PublicKey("A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM"), + new PublicKey("Dn4noZ5jgGfkntzcQSUZ8czkreiZ1ForXYoV2H8Dm7S1"), + new PublicKey("5RpUwQ8wtdPCZHhu6MERp2RGrpobsbZ6MH5dDHkUjs2"), + new PublicKey("8qJSyQprMC57TWKaYEmetUR3UUiTP2M3hXdcvFhkZdmv"), + ], + tokenDecimalEqualizers: [2, 2, 2, 2, 0, 0], + tokenKeys: [ + new PublicKey("5uBU2zUG8xTLA6XwwcTFWib1p7EjCBzWbiy44eVASTfV"), + new PublicKey("Hv7yPYnGs6fpN3o1NZvkima9mKDrRDJtNxf23oKLCjau"), + new PublicKey("4R6b4aibi46JzAnuA8ZWXrHAsR1oZBTZ8dqkuer3LsbS"), + new PublicKey("2DMUL42YEb4g1HAKXhUxL3Yjfgoj4VvRqKwheorfFcPV"), + new PublicKey("DukQAFyxR41nbbq2FBUDMyrtF2CRmWBREjZaTVj4u9As"), + new PublicKey("9KMH3p8cUocvQRbJfKRAStKG52xCCWNmEPsJm5gc8fzw"), + ], + governanceKey: new PublicKey( + "ExWoeFoyYwCFx2cp9PZzj4eYL5fsDEFQEpC8REsksNpb", + ), + governanceFeeKey: new PublicKey( + "9Yau6DnqYasBUKcyxQJQZqThvUnqZ32ZQuUCcC2AdT9P", + ), + preparedGovernanceKey: PublicKey.default, + governanceTransitionTs: new BN(0), + preparedLpFee: { value: 0 }, + preparedGovernanceFee: { value: 0 }, + feeTransitionTs: new BN(0), + previousDepth: new BN(2203793333522317), + }; + const decoded = deserializeLegacySolanaPoolState(6, serialized); + expect(decoded.bump).toBe(expected.bump); + expect(decoded.isPaused).toBe(expected.isPaused); + expect( + decoded.ampFactor.initialValue.value.eq( + expected.ampFactor.initialValue.value, + ), + ).toBe(true); + expect(decoded.ampFactor.initialValue.decimals).toBe( + expected.ampFactor.initialValue.decimals, + ); + expect(decoded.ampFactor.initialTs.eq(expected.ampFactor.initialTs)).toBe( + true, + ); + expect( + decoded.ampFactor.targetValue.value.eq( + expected.ampFactor.targetValue.value, + ), + ).toBe(true); + expect(decoded.ampFactor.targetValue.decimals).toBe( + expected.ampFactor.targetValue.decimals, + ); + expect(decoded.ampFactor.targetTs.eq(expected.ampFactor.targetTs)).toBe( + true, + ); + expect(decoded.lpFee).toBe(expected.lpFee); + expect(decoded.governanceFee).toBe(expected.governanceFee); + expect(decoded.lpMintKey).toStrictEqual(expected.lpMintKey); + expect(decoded.lpDecimalEqualizer).toBe(expected.lpDecimalEqualizer); + decoded.tokenMintKeys.forEach((tokenMintKey, i) => { + expect(tokenMintKey).toStrictEqual(expected.tokenMintKeys[i]); + }); + decoded.tokenDecimalEqualizers.forEach((tokenDecimalEqualizer, i) => { + expect(tokenDecimalEqualizer).toBe(expected.tokenDecimalEqualizers[i]); + }); + decoded.tokenKeys.forEach((tokenKey, i) => { + expect(tokenKey).toStrictEqual(expected.tokenKeys[i]); + }); + expect(decoded.governanceKey).toStrictEqual(expected.governanceKey); + expect(decoded.governanceFeeKey).toStrictEqual(expected.governanceFeeKey); + expect(decoded.preparedGovernanceKey).toStrictEqual( + expected.preparedGovernanceKey, + ); + expect( + decoded.governanceTransitionTs.eq(expected.governanceTransitionTs), + ).toBe(true); + expect(decoded.preparedLpFee).toBe(expected.preparedLpFee); + expect(decoded.preparedGovernanceFee).toBe(expected.preparedGovernanceFee); + expect(decoded.feeTransitionTs.eq(expected.feeTransitionTs)).toBe(true); + expect(decoded.previousDepth.eq(expected.previousDepth)).toBe(true); + }); +}); diff --git a/apps/ui/src/models/solana/deserializeLegacySolanaPoolState.ts b/apps/ui/src/models/solana/deserializeLegacySolanaPoolState.ts new file mode 100644 index 000000000..8c313277a --- /dev/null +++ b/apps/ui/src/models/solana/deserializeLegacySolanaPoolState.ts @@ -0,0 +1,65 @@ +import { + array, + bool, + i64, + publicKey, + struct, + u128, + u32, + u8, +} from "@project-serum/borsh"; +import type { Layout } from "@project-serum/borsh"; +import type { SolanaEcosystemId, SolanaPoolState } from "@swim-io/solana"; +import { SOLANA_ECOSYSTEM_ID, ampFactor } from "@swim-io/solana"; + +type U8 = (property?: string) => Layout; + +export interface LegacySolanaPoolState + extends Omit { + readonly governanceFee: number; + readonly lpFee: number; + readonly ecosystem: SolanaEcosystemId; +} + +export const solanaPool = ( + numberOfTokens: number, + property = "solanaPool", +): Layout> => + struct( + [ + u8("bump"), + bool("isPaused"), + ampFactor(), + u32("lpFee"), + u32("governanceFee"), + publicKey("lpMintKey"), + u8("lpDecimalEqualizer"), + array(publicKey(), numberOfTokens, "tokenMintKeys"), + array((u8 as U8)(), numberOfTokens, "tokenDecimalEqualizers"), + array(publicKey(), numberOfTokens, "tokenKeys"), + publicKey("governanceKey"), + publicKey("governanceFeeKey"), + publicKey("preparedGovernanceKey"), + i64("governanceTransitionTs"), + u32("preparedLpFee"), + u32("preparedGovernanceFee"), + i64("feeTransitionTs"), + u128("previousDepth"), + ], + property, + ); + +export const deserializeLegacySolanaPoolState = ( + numberOfTokens: number, + accountData: Buffer, +): LegacySolanaPoolState => { + const layout = solanaPool(numberOfTokens); + if (accountData.length !== layout.span) { + throw new Error("Incorrect account data length"); + } + const decoded = layout.decode(accountData); + return { + ...decoded, + ecosystem: SOLANA_ECOSYSTEM_ID, + }; +}; diff --git a/apps/ui/src/models/solana/index.ts b/apps/ui/src/models/solana/index.ts index a7829508c..2f79d6c02 100644 --- a/apps/ui/src/models/solana/index.ts +++ b/apps/ui/src/models/solana/index.ts @@ -1,2 +1,3 @@ +export * from "./deserializeLegacySolanaPoolState"; export * from "./findOrCreateSplTokenAccount"; export * from "./getSwimUsdBalanceChange"; diff --git a/apps/ui/src/models/swim/SwimDefiInstructor.ts b/apps/ui/src/models/swim/SwimDefiInstructor.ts index e249446fc..5bd443acb 100644 --- a/apps/ui/src/models/swim/SwimDefiInstructor.ts +++ b/apps/ui/src/models/swim/SwimDefiInstructor.ts @@ -1,7 +1,6 @@ import { TOKEN_PROGRAM_ID, createApproveInstruction } from "@solana/spl-token"; import type { AccountMeta, Transaction } from "@solana/web3.js"; import { Keypair, PublicKey, TransactionInstruction } from "@solana/web3.js"; -import type { Env } from "@swim-io/core"; import { SOLANA_ECOSYSTEM_ID, createMemoIx, @@ -35,7 +34,6 @@ import type { } from "./operation"; export class SwimDefiInstructor { - private readonly env: Env; private readonly solanaClient: SolanaClient; private readonly signer: SolanaWalletAdapter; private readonly programId: PublicKey; @@ -49,7 +47,6 @@ export class SwimDefiInstructor { private userTokenAccounts: readonly PublicKey[]; public constructor( - env: Env, solanaClient: SolanaClient, signer: SolanaWalletAdapter, swimProgramAddress: string, @@ -72,7 +69,6 @@ export class SwimDefiInstructor { "Number of user token accounts does not match number of token mints", ); } - this.env = env; this.solanaClient = solanaClient; this.signer = signer; this.programId = new PublicKey(swimProgramAddress); diff --git a/apps/ui/src/models/swim/SwimInitializer.ts b/apps/ui/src/models/swim/SwimInitializer.ts index 710273cba..219df5729 100644 --- a/apps/ui/src/models/swim/SwimInitializer.ts +++ b/apps/ui/src/models/swim/SwimInitializer.ts @@ -15,7 +15,7 @@ import { SystemProgram, TransactionInstruction, } from "@solana/web3.js"; -import { createTx, swimPool } from "@swim-io/solana"; +import { createTx } from "@swim-io/solana"; import type { DecimalBN, SolanaClient, @@ -23,6 +23,8 @@ import type { } from "@swim-io/solana"; import { chunks } from "@swim-io/utils"; +import { solanaPool } from "../solana"; + import { SwimInstruction, initInstruction } from "./instructions"; export class SwimInitializer { @@ -148,7 +150,7 @@ export class SwimInitializer { if (!this.stateAccount) { throw new Error("No state account"); } - const layout = swimPool(this.numberOfTokens); + const layout = solanaPool(this.numberOfTokens); const lamports = await this.solanaClient.connection.getMinimumBalanceForRentExemption( layout.span, diff --git a/apps/ui/src/models/swim/deserializeSwimPoolV2.ts b/apps/ui/src/models/swim/deserializeSwimPoolV2.ts deleted file mode 100644 index 23c145889..000000000 --- a/apps/ui/src/models/swim/deserializeSwimPoolV2.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { Buffer } from "buffer"; - -import type { BN, Idl } from "@project-serum/anchor"; -import { BorshAccountsCoder } from "@project-serum/anchor"; -import type { PublicKey } from "@solana/web3.js"; -import type { AmpFactor, SwimPoolState } from "@swim-io/solana"; -import { idl } from "@swim-io/solana-contracts"; - -interface Value { - readonly value: number; -} - -interface TwoPoolState { - readonly nonce: number; - readonly lpMintKey: PublicKey; - readonly lpDecimalEqualizer: number; - readonly tokenMintKeys: readonly PublicKey[]; - readonly tokenDecimalEqualizers: readonly number[]; - readonly tokenKeys: readonly PublicKey[]; - readonly isPaused: boolean; - readonly ampFactor: AmpFactor; - readonly lpFee: Value; - readonly governanceFee: Value; - readonly governanceKey: PublicKey; - readonly governanceFeeKey: PublicKey; - readonly preparedGovernanceKey: PublicKey; - readonly governanceTransitionTs: BN; - readonly preparedLpFee: Value; - readonly preparedGovernanceFee: Value; - readonly feeTransitionTs: BN; - readonly previousDepth: BN; -} - -export const deserializeSwimPoolV2 = (poolData: Buffer): SwimPoolState => { - const PoolDecoder = new BorshAccountsCoder(idl.twoPool as Idl); - const poolState: TwoPoolState = PoolDecoder.decode("TwoPool", poolData); - return { - ...poolState, - lpFee: poolState.lpFee.value, - governanceFee: poolState.governanceFee.value, - preparedLpFee: poolState.preparedLpFee.value, - preparedGovernanceFee: poolState.preparedGovernanceFee.value, - }; -}; diff --git a/apps/ui/src/models/swim/doSingleSolanaPoolOperation.ts b/apps/ui/src/models/swim/doSingleSolanaPoolOperation.ts index d77634e37..a3f0a1eef 100644 --- a/apps/ui/src/models/swim/doSingleSolanaPoolOperation.ts +++ b/apps/ui/src/models/swim/doSingleSolanaPoolOperation.ts @@ -1,4 +1,3 @@ -import type { Env } from "@swim-io/core"; import { SOLANA_ECOSYSTEM_ID, findTokenAccountForMint, @@ -23,10 +22,9 @@ import { SwimDefiInstruction } from "./instructions"; import type { Interaction } from "./interaction"; import type { OperationSpec } from "./operation"; import type { TokensByPoolId } from "./pool"; -import { getSolanaPoolState } from "./pool"; +import { getLegacySolanaPoolState } from "./pool"; export const doSingleSolanaPoolOperation = async ( - env: Env, solanaClient: SolanaClient, wallet: SolanaWalletAdapter, splTokenAccounts: readonly TokenAccount[], @@ -38,7 +36,7 @@ export const doSingleSolanaPoolOperation = async ( if (walletAddress === null) { throw new Error("Missing Solana wallet"); } - const poolState = await getSolanaPoolState(solanaClient, poolSpec); + const poolState = await getLegacySolanaPoolState(solanaClient, poolSpec); if (poolState === null) { throw new Error("Missing pool state"); } @@ -56,7 +54,6 @@ export const doSingleSolanaPoolOperation = async ( findTokenAccountForMint(mint, walletAddress, splTokenAccounts), ); const instructor = new SwimDefiInstructor( - env, solanaClient, wallet, poolSpec.contract, diff --git a/apps/ui/src/models/swim/doSingleSolanaPoolOperationV2.ts b/apps/ui/src/models/swim/doSingleSolanaPoolOperationV2.ts index faa8e64cc..d9bfb6c2b 100644 --- a/apps/ui/src/models/swim/doSingleSolanaPoolOperationV2.ts +++ b/apps/ui/src/models/swim/doSingleSolanaPoolOperationV2.ts @@ -20,7 +20,6 @@ import { getSolanaTokenDetails } from "../../config"; import { SwimDefiInstruction } from "./instructions"; import type { OperationSpec } from "./operation"; import type { TokensByPoolId } from "./pool"; -import { getSolanaPoolState } from "./pool"; const getApproveAndRevokeIxs = async ( splToken: Program, @@ -85,10 +84,7 @@ export const doSingleSolanaPoolOperationV2 = async ({ throw new Error("Invalid wallet"); } const walletAddress = walletPublicKey.toBase58(); - const poolState = await getSolanaPoolState(solanaClient, poolSpec); - if (poolState === null) { - throw new Error("Missing pool state"); - } + const poolState = await solanaClient.getPoolState(poolSpec.id); const lpTokenMintAddress = getSolanaTokenDetails(poolTokens.lpToken).address; const userLpAccount = findTokenAccountForMint( lpTokenMintAddress, @@ -98,6 +94,7 @@ export const doSingleSolanaPoolOperationV2 = async ({ const tokenMintAddresses = poolTokens.tokens.map( (token) => getSolanaTokenDetails(token).address, ); + const poolTokenAccounts = [...poolSpec.tokenAccounts.values()]; const userTokenAccounts = tokenMintAddresses.map((mint) => findTokenAccountForMint(mint, walletAddress, splTokenAccounts), ); @@ -123,9 +120,9 @@ export const doSingleSolanaPoolOperationV2 = async ({ throw new Error("Invalid user token account"); } const commonAccounts = { - poolTokenAccount0: poolState.tokenKeys[0], - poolTokenAccount1: poolState.tokenKeys[1], - lpMint: poolState.lpMintKey, + poolTokenAccount0: new PublicKey(poolTokenAccounts[0]), + poolTokenAccount1: new PublicKey(poolTokenAccounts[1]), + lpMint: new PublicKey(lpTokenMintAddress), governanceFee: poolState.governanceFeeKey, userTransferAuthority: userTransferAuthority.publicKey, userTokenAccount0: userTokenAccount0, diff --git a/apps/ui/src/models/swim/pool.test.ts b/apps/ui/src/models/swim/pool.test.ts index 221fdef2e..536b90baa 100644 --- a/apps/ui/src/models/swim/pool.test.ts +++ b/apps/ui/src/models/swim/pool.test.ts @@ -1,27 +1,17 @@ import type solana from "@solana/web3.js"; import { Env } from "@swim-io/core"; -import type { EvmClient, EvmTx } from "@swim-io/evm"; +import type { EvmTx } from "@swim-io/evm"; import { EvmEcosystemId } from "@swim-io/evm"; -import { Routing__factory } from "@swim-io/evm-contracts"; import type { SolanaTx } from "@swim-io/solana"; import { SOLANA_ECOSYSTEM_ID } from "@swim-io/solana"; -import { findOrThrow } from "@swim-io/utils"; -import Decimal from "decimal.js"; import type { ethers } from "ethers"; -import { BigNumber } from "ethers"; import { mock, mockDeep } from "jest-mock-extended"; -import type { Config, EvmPoolSpec } from "../../config"; -import { - CONFIGS, - TESTNET_POOLS_FOR_RESTRUCTURE, - TESTNET_SWIMUSD, - TESTNET_TOKENS, - TESTNET_TOKENS_FOR_RESTRUCTURE, -} from "../../config"; +import type { Config } from "../../config"; +import { CONFIGS } from "../../config"; import { parsedWormholeRedeemEvmUnlockWrappedTx } from "../../fixtures/solana/txs"; -import { getEvmPoolState, getTokensByPool, isPoolTx } from "./pool"; +import { getTokensByPool, isPoolTx } from "./pool"; describe("Pool tests", () => { describe("getTokensByPool", () => { @@ -85,68 +75,4 @@ describe("Pool tests", () => { expect(isPoolTx(contractAddress, tx)).toBe(true); }); }); - - describe("getEvmPoolState", () => { - const MOCK_STATE = { - paused: false, - balances: [ - [ - "0x4873edbb0B4b5b48A6FBe50CacB85e58D0b62ab5", - BigNumber.from("2087941202"), - ], - [ - "0x45B167CF5b14007Ca0490dCfB7C4B870Ec0C0Aa6", - BigNumber.from("2052006916"), - ], - [ - "0x996f42BdB0CB71F831C2eFB05Ac6d0d226979e5B", - BigNumber.from("2117774978"), - ], - ], - totalLpSupply: [ - "0xf3eb1180A64827A602A7e02883b7299191527073", - BigNumber.from("1968249268"), - ], - ampFactor: [BigNumber.from("1000"), 3], - lpFee: [BigNumber.from("300"), 6], - governanceFee: [BigNumber.from("100"), 6], - }; - - beforeEach(() => { - Routing__factory.connect = jest.fn().mockReturnValue({ - getPoolStates: () => Promise.resolve([MOCK_STATE]), - }); - }); - - it("should return EVM pool state in human decimal", async () => { - const ethereumPool = findOrThrow( - TESTNET_POOLS_FOR_RESTRUCTURE, - (poolSpec) => poolSpec.id === "testnet-ethereum-usdc-usdt", - ); - const tokens = [ - ...TESTNET_TOKENS, - TESTNET_SWIMUSD, - ...TESTNET_TOKENS_FOR_RESTRUCTURE, - ]; - const state = await getEvmPoolState( - {} as EvmClient, - ethereumPool as EvmPoolSpec, - tokens, - "MOCK_ADDRESS", - ); - expect(state).toEqual({ - ampFactor: new Decimal("1"), - balances: [ - new Decimal("2087.941202"), - new Decimal("2052.006916"), - new Decimal("2117.774978"), - ], - ecosystem: "ethereum", - governanceFee: new Decimal("0.0001"), - isPaused: false, - lpFee: new Decimal("0.0003"), - totalLpSupply: new Decimal("1968.249268"), - }); - }); - }); }); diff --git a/apps/ui/src/models/swim/pool.ts b/apps/ui/src/models/swim/pool.ts index 636fd6ca6..2d7407c23 100644 --- a/apps/ui/src/models/swim/pool.ts +++ b/apps/ui/src/models/swim/pool.ts @@ -1,47 +1,33 @@ import { PublicKey } from "@solana/web3.js"; -import type { EvmClient, EvmEcosystemId } from "@swim-io/evm"; +import type { PoolState as BasePoolState } from "@swim-io/core"; +import type { EvmEcosystemId } from "@swim-io/evm"; import { isEvmEcosystemId } from "@swim-io/evm"; -import { Routing__factory } from "@swim-io/evm-contracts"; -import { - SOLANA_ECOSYSTEM_ID, - deserializeSwimPool, - isSolanaTx, -} from "@swim-io/solana"; +import { SOLANA_ECOSYSTEM_ID, isSolanaTx } from "@swim-io/solana"; import type { + SolanaPoolState as BaseSolanaPoolState, SolanaClient, SolanaEcosystemId, SolanaTx, - SwimPoolState, } from "@swim-io/solana"; import type { ReadonlyRecord } from "@swim-io/utils"; -import { atomicToHuman, findOrThrow } from "@swim-io/utils"; -import Decimal from "decimal.js"; +import { findOrThrow } from "@swim-io/utils"; -import { bnOrBigNumberToDecimal } from "../../amounts"; import type { Config, - EvmPoolSpec, PoolSpec, SolanaPoolSpec, TokenConfig, } from "../../config"; -import { getTokenDetailsForEcosystem } from "../../config"; import type { Tx } from "../crossEcosystem"; +import { deserializeLegacySolanaPoolState } from "../solana"; -import { deserializeSwimPoolV2 } from "./deserializeSwimPoolV2"; - -export interface SolanaPoolState extends SwimPoolState { +export interface SolanaPoolState + extends Omit { readonly ecosystem: SolanaEcosystemId; } -export interface EvmPoolState { +export interface EvmPoolState extends BasePoolState { readonly ecosystem: EvmEcosystemId; - readonly isPaused: boolean; - readonly balances: readonly Decimal[]; - readonly totalLpSupply: Decimal; - readonly ampFactor: Decimal; - readonly lpFee: Decimal; - readonly governanceFee: Decimal; } export type PoolState = SolanaPoolState | EvmPoolState; @@ -84,10 +70,13 @@ export const isPoolTx = ( export const isSolanaPool = (pool: PoolSpec): pool is SolanaPoolSpec => pool.ecosystem === SOLANA_ECOSYSTEM_ID; -export const getSolanaPoolState = async ( +export const getLegacySolanaPoolState = async ( solanaClient: SolanaClient, poolSpec: SolanaPoolSpec, ): Promise => { + if (!poolSpec.isLegacyPool) { + throw new Error("Invalid pool version"); + } const numberOfTokens = poolSpec.tokens.length; const accountInfo = await solanaClient.connection.getAccountInfo( new PublicKey(poolSpec.address), @@ -95,60 +84,14 @@ export const getSolanaPoolState = async ( if (accountInfo === null) { return null; } - const swimPool = poolSpec.isLegacyPool - ? deserializeSwimPool(numberOfTokens, accountInfo.data) - : deserializeSwimPoolV2(accountInfo.data); - - return { - ...swimPool, - ecosystem: SOLANA_ECOSYSTEM_ID, - }; -}; - -export const getEvmPoolState = async ( - evmClient: EvmClient, - poolSpec: EvmPoolSpec, - tokens: readonly TokenConfig[], - routingContractAddress: string, -): Promise => { - const { ecosystem, address } = poolSpec; - const contract = Routing__factory.connect( - routingContractAddress, - evmClient.provider, - ); - const lpToken = findOrThrow(tokens, ({ id }) => id === poolSpec.lpToken); - const poolTokens = poolSpec.tokens.map((tokenId) => - findOrThrow(tokens, ({ id }) => id === tokenId), + const deserialized = deserializeLegacySolanaPoolState( + numberOfTokens, + accountInfo.data, ); - const lpTokenDetails = getTokenDetailsForEcosystem(lpToken, ecosystem); - if (lpTokenDetails === null) { - throw new Error("Token details not found"); - } - const [state] = await contract.getPoolStates([address]); return { - isPaused: state.paused, - ecosystem, - balances: poolTokens.map((token, i) => { - const tokenDetails = getTokenDetailsForEcosystem(token, ecosystem); - if (tokenDetails === null) { - throw new Error("Token details not found"); - } - return atomicToHuman( - new Decimal(state.balances[i][1].toString()), - tokenDetails.decimals, - ); - }), - totalLpSupply: atomicToHuman( - new Decimal(state.totalLpSupply[1].toString()), - lpTokenDetails.decimals, - ), - ampFactor: bnOrBigNumberToDecimal(state.ampFactor[0]).div( - 10 ** state.ampFactor[1], - ), - lpFee: bnOrBigNumberToDecimal(state.lpFee[0]).div(10 ** state.lpFee[1]), - governanceFee: bnOrBigNumberToDecimal(state.governanceFee[0]).div( - 10 ** state.governanceFee[1], - ), + ...deserialized, + governanceFee: { value: deserialized.governanceFee }, + lpFee: { value: deserialized.lpFee }, }; };