From 343fa35347fcc63e0587a7971055cec50a22a3d7 Mon Sep 17 00:00:00 2001 From: Nico Miicro Date: Fri, 21 Oct 2022 14:56:34 +0300 Subject: [PATCH 01/10] feat(core): Add PoolState interface to core --- packages/core/package.json | 7 ++++--- packages/core/src/pool.ts | 11 +++++++++++ yarn.lock | 1 + 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 99902856f..2b8e9c5cf 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -32,8 +32,7 @@ }, "dependencies": { "@swim-io/token-projects": "workspace:^", - "@swim-io/utils": "workspace:^", - "decimal.js": "^10.3.1" + "@swim-io/utils": "workspace:^" }, "devDependencies": { "@certusone/wormhole-sdk": "^0.6.2", @@ -42,6 +41,7 @@ "@types/jest": "^28.1.3", "@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/parser": "^5.38.1", + "decimal.js": "^10.3.1", "eslint": "^8.18.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-deprecation": "^1.3.2", @@ -56,6 +56,7 @@ "typescript": "~4.8.4" }, "peerDependencies": { - "@certusone/wormhole-sdk": "^0.6.2" + "@certusone/wormhole-sdk": "^0.6.2", + "decimal.js": "^10.3.1" } } diff --git a/packages/core/src/pool.ts b/packages/core/src/pool.ts index 8cce3f985..3a44cf296 100644 --- a/packages/core/src/pool.ts +++ b/packages/core/src/pool.ts @@ -1,3 +1,5 @@ +import type { Decimal } from "decimal.js"; + /** Ecosystem-neutral configuration object for a Swim liquidity pool */ export interface PoolConfig { readonly id: string; @@ -12,3 +14,12 @@ export interface PoolConfig { readonly isLegacyPool?: boolean; readonly isDisabled?: boolean; } + +export interface PoolState { + readonly isPaused: boolean; + readonly ampFactor: Decimal; + readonly lpFee: Decimal; + readonly governanceFee: Decimal; + readonly balances: readonly Decimal[]; + readonly totalLpSupply: Decimal; +} diff --git a/yarn.lock b/yarn.lock index f0c822356..c1dbef74f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7275,6 +7275,7 @@ __metadata: typescript: ~4.8.4 peerDependencies: "@certusone/wormhole-sdk": ^0.6.2 + decimal.js: ^10.3.1 languageName: unknown linkType: soft From a4d8dae4e64c9331a721492b379f671b0cd900c0 Mon Sep 17 00:00:00 2001 From: Nico Miicro Date: Fri, 21 Oct 2022 16:07:14 +0300 Subject: [PATCH 02/10] feat(core): Add getPoolState to client interface --- packages/aptos/src/client.ts | 8 +++++++- packages/core/src/client.ts | 3 +++ packages/evm/src/client.ts | 7 +++++++ packages/solana/src/client.ts | 7 +++++++ 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/aptos/src/client.ts b/packages/aptos/src/client.ts index 21901bc83..26a110072 100644 --- a/packages/aptos/src/client.ts +++ b/packages/aptos/src/client.ts @@ -1,4 +1,4 @@ -import type { TokenDetails } from "@swim-io/core"; +import type { PoolState, TokenDetails } from "@swim-io/core"; import { Client } from "@swim-io/core"; import { atomicToHuman } from "@swim-io/utils"; import type { Types } from "aptos"; @@ -123,4 +123,10 @@ export class AptosClient extends Client< public completeWormholeTransfer(): Promise { throw new Error("Not implemented"); } + + public getPoolState(poolId: string): Promise { + throw new Error( + `getPoolState not implemented. Called with poolId: ${poolId}`, + ); + } } diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index dd3bfabcb..5a8a60ef3 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -3,6 +3,7 @@ import type { TokenProjectId } from "@swim-io/token-projects"; import type Decimal from "decimal.js"; import type { ChainConfig } from "./chain"; +import type { PoolState } from "./pool"; import type { TokenDetails } from "./token"; import type { Tx } from "./tx"; @@ -66,4 +67,6 @@ export abstract class Client< public abstract completeWormholeTransfer( params: CompleteWormholeTransferParams, ): Promise; + + public abstract getPoolState(poolId: string): Promise; } diff --git a/packages/evm/src/client.ts b/packages/evm/src/client.ts index 10566f473..6324fb0c0 100644 --- a/packages/evm/src/client.ts +++ b/packages/evm/src/client.ts @@ -8,6 +8,7 @@ import { Client, getTokenDetails } from "@swim-io/core"; import type { CompleteWormholeTransferParams, InitiateWormholeTransferParams, + PoolState, TokenDetails, } from "@swim-io/core"; import { ERC20__factory } from "@swim-io/evm-contracts"; @@ -278,6 +279,12 @@ export class EvmClient extends Client< return approvalResponses; } + public getPoolState(poolId: string): Promise { + throw new Error( + `getPoolState not implemented. Called with poolId: ${poolId}`, + ); + } + private async getTxReceipt( txIdOrResponse: string | TransactionResponse, ): Promise { diff --git a/packages/solana/src/client.ts b/packages/solana/src/client.ts index 8337acf84..0cff8d344 100644 --- a/packages/solana/src/client.ts +++ b/packages/solana/src/client.ts @@ -25,6 +25,7 @@ import { import type { CompleteWormholeTransferParams, InitiateWormholeTransferParams, + PoolState, TokenDetails, } from "@swim-io/core"; import { Client, getTokenDetails } from "@swim-io/core"; @@ -424,6 +425,12 @@ export class SolanaClient extends Client< ); } + public getPoolState(poolId: string): Promise { + throw new Error( + `getPoolState not implemented. Called with poolId: ${poolId}`, + ); + } + private incrementRpcProvider() { if ( this.endpoints.length === 1 && From cf1e61d4e61cb55e9aa792e8f21f0aebc7329ade Mon Sep 17 00:00:00 2001 From: Nico Miicro Date: Mon, 24 Oct 2022 15:51:05 +0300 Subject: [PATCH 03/10] feat(aptos): implement client.getPoolState --- packages/aptos/package.json | 1 + packages/aptos/src/client.ts | 92 ++++++++++++++++++++++++++++++-- packages/aptos/src/liquidswap.ts | 21 ++++++++ packages/aptos/src/protocol.ts | 5 ++ yarn.lock | 36 ++++++++----- 5 files changed, 139 insertions(+), 16 deletions(-) create mode 100644 packages/aptos/src/liquidswap.ts diff --git a/packages/aptos/package.json b/packages/aptos/package.json index 23d84d9e3..d6daee012 100644 --- a/packages/aptos/package.json +++ b/packages/aptos/package.json @@ -31,6 +31,7 @@ "prepare": "yarn verify && yarn build" }, "dependencies": { + "@pontem/liquidswap-sdk": "^0.0.1", "@swim-io/core": "workspace:^", "@swim-io/token-projects": "workspace:^", "@swim-io/utils": "workspace:^", diff --git a/packages/aptos/src/client.ts b/packages/aptos/src/client.ts index 26a110072..0c07a9e7d 100644 --- a/packages/aptos/src/client.ts +++ b/packages/aptos/src/client.ts @@ -1,6 +1,7 @@ +import { isSortedSymbols } from "@pontem/liquidswap-sdk/dist/tsc/utils/contracts.js"; import type { PoolState, TokenDetails } from "@swim-io/core"; import { Client } from "@swim-io/core"; -import { atomicToHuman } from "@swim-io/utils"; +import { atomicToHuman, findOrThrow } from "@swim-io/utils"; import type { Types } from "aptos"; import { APTOS_COIN, @@ -10,6 +11,8 @@ import { } from "aptos"; import Decimal from "decimal.js"; +import type { PoolResource } from "./liquidswap"; +import { DAO_FEE_DECIMALS, FEE_DECIMALS } from "./liquidswap"; import type { AptosChainConfig, AptosEcosystemId, AptosTx } from "./protocol"; import { APTOS_ECOSYSTEM_ID } from "./protocol"; import type { AptosWalletAdapter } from "./walletAdapters"; @@ -20,6 +23,25 @@ interface GasScheduleV2 { readonly value: string; }[]; } +interface CoinInfoResource { + readonly decimals: number; + readonly name: string; + readonly supply: { + readonly vec: readonly [ + { + readonly integer: { + readonly vec: readonly [ + { + readonly limit: string; + readonly value: string; + }, + ]; + }; + }, + ]; + }; + readonly symbol: string; +} export interface AptosClientOptions { readonly endpoint: string; @@ -124,9 +146,71 @@ export class AptosClient extends Client< throw new Error("Not implemented"); } - public getPoolState(poolId: string): Promise { - throw new Error( - `getPoolState not implemented. Called with poolId: ${poolId}`, + public async getPoolState(poolId: string): Promise { + const { address, owner, lpTokenId, tokenIds } = findOrThrow( + this.chainConfig.pools, + (poolConfig) => poolConfig.id === poolId, + ); + const lpToken = findOrThrow( + this.chainConfig.tokens, + (token) => token.id === lpTokenId, ); + const [tokenX, tokenY]: readonly TokenDetails[] = tokenIds.map( + (tokenId) => { + if (/swimusd$/.test(tokenId)) return this.chainConfig.swimUsdDetails; // TODO is there a better way to do this? + + return findOrThrow( + this.chainConfig.tokens, + (token) => token.id === tokenId, + ).nativeDetails; + }, + ); + + const [lpTokenSupplyResponse, poolBalancesResponse] = await Promise.all([ + this.sdkClient.getAccountResource( + owner, + getCoinInfoType(lpToken.nativeDetails.address), + ), + this.sdkClient.getAccountResource(owner, address), + ]); + + const totalLpSupply = getCoinInfoSupply( + lpTokenSupplyResponse.data as CoinInfoResource, + ); + const pool = poolBalancesResponse.data as PoolResource; + const balances = getBalances(pool, [tokenX, tokenY]); + + return { + isPaused: false, // TODO ? https://exsphere.slack.com/archives/C03SQTXMFT9/p1666390318707959?thread_ts=1666352227.333479&cid=C03SQTXMFT9 + ampFactor: new Decimal("1"), // no ampFactor for liquidswap pool ? + lpFee: atomicToHuman(new Decimal(pool.fee), FEE_DECIMALS), + governanceFee: atomicToHuman(new Decimal(pool.dao_fee), DAO_FEE_DECIMALS), + balances, + totalLpSupply, + }; } } + +const getCoinInfoType = (address: string) => `0x1::coin::CoinInfo<${address}>`; + +const getCoinInfoSupply = (coinInfo: CoinInfoResource): Decimal => { + return atomicToHuman( + new Decimal(coinInfo.supply.vec[0].integer.vec[0].value), + coinInfo.decimals, + ); +}; + +const getBalances = ( + pool: PoolResource, + tokens: readonly [TokenDetails, TokenDetails], +): PoolState["balances"] => { + const reserves = [pool.coin_x_reserve.value, pool.coin_y_reserve.value]; + const atomicBalances = isSortedSymbols(tokens[0].address, tokens[1].address) + ? reserves + : [...reserves].reverse(); + + return [ + atomicToHuman(new Decimal(atomicBalances[0]), tokens[0].decimals), + atomicToHuman(new Decimal(atomicBalances[1]), tokens[1].decimals), + ]; +}; diff --git a/packages/aptos/src/liquidswap.ts b/packages/aptos/src/liquidswap.ts new file mode 100644 index 000000000..636d5aa7b --- /dev/null +++ b/packages/aptos/src/liquidswap.ts @@ -0,0 +1,21 @@ +export interface PoolResource { + readonly coin_x_reserve: { + readonly value: string; + }; + + readonly coin_y_reserve: { + readonly value: string; + }; + + readonly dao_fee: string; + readonly fee: string; + readonly last_block_timestamp: string; + readonly last_price_x_cumulative: string; + readonly last_price_y_cumulative: string; + readonly x_scale: string; + readonly y_scale: string; +} + +// From https://github.com/pontem-network/liquidswap/blob/b89aed9adb96c0b083134765f75be814418be670/sources/swap/liquidity_pool.move#L70-L74 +export const FEE_DECIMALS = 4; +export const DAO_FEE_DECIMALS = 2; diff --git a/packages/aptos/src/protocol.ts b/packages/aptos/src/protocol.ts index cc552c1dc..d97e050a7 100644 --- a/packages/aptos/src/protocol.ts +++ b/packages/aptos/src/protocol.ts @@ -3,6 +3,7 @@ import type { EcosystemConfig, Env, PoolConfig, + PoolState, Tx, } from "@swim-io/core"; import type { ReadonlyRecord } from "@swim-io/utils"; @@ -19,6 +20,10 @@ export interface AptosPoolConfig extends PoolConfig { readonly owner: Types.Address; } +export interface AptosPoolState extends PoolState { + readonly ecosystem: AptosEcosystemId; +} + export interface AptosChainConfig extends ChainConfig { readonly pools: readonly AptosPoolConfig[]; } diff --git a/yarn.lock b/yarn.lock index c1dbef74f..54b97ec15 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5014,6 +5014,17 @@ __metadata: languageName: node linkType: hard +"@pontem/liquidswap-sdk@npm:^0.0.1": + version: 0.0.1 + resolution: "@pontem/liquidswap-sdk@npm:0.0.1" + dependencies: + aptos: ^1.2.0 + axios: ^0.27.2 + decimal.js: ^10.3.1 + checksum: 559a9f6e965fd068b7e497df3057a4e808ecec7f9fad1c389679bfc0ec5e62edf92bae2ba9962ab2ffa23ae2cebd0c6d14628b3758b30ccf015684152fecfaf5 + languageName: node + linkType: hard + "@project-serum/anchor@npm:^0.22.1": version: 0.22.1 resolution: "@project-serum/anchor@npm:0.22.1" @@ -7160,6 +7171,7 @@ __metadata: version: 0.0.0-use.local resolution: "@swim-io/aptos@workspace:packages/aptos" dependencies: + "@pontem/liquidswap-sdk": ^0.0.1 "@swim-io/core": "workspace:^" "@swim-io/eslint-config": "workspace:^" "@swim-io/token-projects": "workspace:^" @@ -7911,7 +7923,7 @@ __metadata: "@storybook/node-logger": ^6.5.10 "@storybook/react": ^6.5.10 "@swim-io/aptos": "workspace:^" - "@swim-io/core": ^0.39.0 + "@swim-io/core": "workspace:^" "@swim-io/eslint-config": "workspace:^" "@swim-io/evm": ^0.39.0 "@swim-io/evm-contracts": ^0.39.0 @@ -10605,30 +10617,30 @@ __metadata: languageName: node linkType: hard -"aptos@npm:^1.3.13": - version: 1.3.13 - resolution: "aptos@npm:1.3.13" +"aptos@npm:^1.2.0, aptos@npm:^1.3.16": + version: 1.3.16 + resolution: "aptos@npm:1.3.16" dependencies: - "@noble/hashes": 1.1.2 + "@noble/hashes": 1.1.3 "@scure/bip39": 1.1.0 axios: 0.27.2 form-data: 4.0.0 - js-sha3: 0.8.0 tweetnacl: 1.0.3 - checksum: 128e63b57c4e49230472d47cd3f23fc8a0789b182c312c1188bb3fb75cedd09adf1c175ecfe442b2ba4a0ea48964434121311ef2fcbf4469587c794e15027f70 + checksum: 7c16e5f588ee81ec727102d71b62e779759c0e871b30a3ba8b22e8f2fcf5534fbd54e08e172a24252f1bd49e6fecd1a1b277517d518e24f593647f47d5597fa3 languageName: node linkType: hard -"aptos@npm:^1.3.16": - version: 1.3.16 - resolution: "aptos@npm:1.3.16" +"aptos@npm:^1.3.13": + version: 1.3.13 + resolution: "aptos@npm:1.3.13" dependencies: - "@noble/hashes": 1.1.3 + "@noble/hashes": 1.1.2 "@scure/bip39": 1.1.0 axios: 0.27.2 form-data: 4.0.0 + js-sha3: 0.8.0 tweetnacl: 1.0.3 - checksum: 7c16e5f588ee81ec727102d71b62e779759c0e871b30a3ba8b22e8f2fcf5534fbd54e08e172a24252f1bd49e6fecd1a1b277517d518e24f593647f47d5597fa3 + checksum: 128e63b57c4e49230472d47cd3f23fc8a0789b182c312c1188bb3fb75cedd09adf1c175ecfe442b2ba4a0ea48964434121311ef2fcbf4469587c794e15027f70 languageName: node linkType: hard From bcb6fcd8f64f4a86f49514adbe3067800f9bd17b Mon Sep 17 00:00:00 2001 From: Nico Miicro Date: Mon, 24 Oct 2022 15:52:25 +0300 Subject: [PATCH 04/10] feat(ui): add aptos pool state queries --- apps/ui/package.json | 2 +- apps/ui/src/hooks/swim/usePoolMaths.ts | 6 ++++- apps/ui/src/hooks/swim/usePoolStateQueries.ts | 9 ++++++- apps/ui/src/hooks/swim/usePools.ts | 22 +++++++++++++++- apps/ui/src/hooks/swim/useUserLpBalances.ts | 15 ++++++++++- apps/ui/src/models/swim/pool.ts | 9 ++++++- apps/ui/src/pages/PoolPage.tsx | 25 +++++++++++++++++-- 7 files changed, 80 insertions(+), 8 deletions(-) diff --git a/apps/ui/package.json b/apps/ui/package.json index be5a4b78e..cb9b9e90c 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -51,7 +51,7 @@ "@solana/spl-token": "^0.3.5", "@solana/web3.js": "^1.62.0", "@swim-io/aptos": "workspace:^", - "@swim-io/core": "^0.39.0", + "@swim-io/core": "workspace:^", "@swim-io/evm": "^0.39.0", "@swim-io/evm-contracts": "^0.39.0", "@swim-io/pool-math": "^0.39.0", diff --git a/apps/ui/src/hooks/swim/usePoolMaths.ts b/apps/ui/src/hooks/swim/usePoolMaths.ts index 4bf1e6c08..343aafc17 100644 --- a/apps/ui/src/hooks/swim/usePoolMaths.ts +++ b/apps/ui/src/hooks/swim/usePoolMaths.ts @@ -7,7 +7,7 @@ import { bnOrBigNumberToDecimal } from "../../amounts"; import { getTokenDetailsForEcosystem } from "../../config"; import { selectConfig } from "../../core/selectors"; import { useEnvironment } from "../../core/store"; -import { Amount, isEvmPoolState } from "../../models"; +import { Amount, isAptosPoolState, isEvmPoolState } from "../../models"; import type { PoolData } from "./usePools"; import { usePools } from "./usePools"; @@ -47,6 +47,10 @@ const getPoolMath = ({ ); } + if (isAptosPoolState(poolState)) { + return null; // TODO aptos + } + if ( poolLpMint === null || poolTokenAccounts === null || diff --git a/apps/ui/src/hooks/swim/usePoolStateQueries.ts b/apps/ui/src/hooks/swim/usePoolStateQueries.ts index 629a970b6..71fdeb5a3 100644 --- a/apps/ui/src/hooks/swim/usePoolStateQueries.ts +++ b/apps/ui/src/hooks/swim/usePoolStateQueries.ts @@ -10,6 +10,7 @@ import { selectConfig } from "../../core/selectors"; import { useEnvironment } from "../../core/store"; import type { PoolState } from "../../models"; import { getEvmPoolState, getSolanaPoolState } from "../../models"; +import { useAptosClient } from "../aptos"; import { useGetEvmClient } from "../evm"; import { useSolanaClient } from "../solana"; @@ -18,6 +19,7 @@ export const usePoolStateQueries = ( ): readonly UseQueryResult[] => { const { env } = useEnvironment(); const { tokens } = useEnvironment(selectConfig, shallow); + const aptosClient = useAptosClient(); const getEvmConnection = useGetEvmClient(); const solanaClient = useSolanaClient(); @@ -30,7 +32,12 @@ export const usePoolStateQueries = ( return await getSolanaPoolState(solanaClient, poolSpec); } if (ecosystem === APTOS_ECOSYSTEM_ID) { - return null; // TODO aptos + const poolState = await aptosClient.getPoolState(poolSpec.id); + + return { + ...poolState, + ecosystem: APTOS_ECOSYSTEM_ID, + }; } const evmConnection = getEvmConnection(ecosystem); const routingContractAddress = diff --git a/apps/ui/src/hooks/swim/usePools.ts b/apps/ui/src/hooks/swim/usePools.ts index 9d4e97ff9..255a1a6da 100644 --- a/apps/ui/src/hooks/swim/usePools.ts +++ b/apps/ui/src/hooks/swim/usePools.ts @@ -9,7 +9,11 @@ import { getSolanaTokenDetails } from "../../config"; import { selectConfig } from "../../core/selectors"; import { useEnvironment } from "../../core/store"; import type { PoolState } from "../../models"; -import { getPoolUsdValue, isEvmPoolState } from "../../models"; +import { + getPoolUsdValue, + isAptosPoolState, + isEvmPoolState, +} from "../../models"; import { useSolanaLiquidityQueries, useSolanaWallet, @@ -73,6 +77,22 @@ const constructPool = ( }; } + if (isAptosPoolState(poolState)) { + return { + spec: poolSpec, + nativeEcosystems, + lpToken, + tokens, + state: poolState, + poolUsdValue: Decimal.sum(...poolState.balances), + isPoolPaused: poolState.isPaused, + // solana based attributes + poolLpMint: null, + poolTokenAccounts: null, + userLpTokenAccount: null, + }; + } + if (isEvmPoolState(poolState)) { return { spec: poolSpec, diff --git a/apps/ui/src/hooks/swim/useUserLpBalances.ts b/apps/ui/src/hooks/swim/useUserLpBalances.ts index 9cb8799d4..321047c82 100644 --- a/apps/ui/src/hooks/swim/useUserLpBalances.ts +++ b/apps/ui/src/hooks/swim/useUserLpBalances.ts @@ -6,6 +6,7 @@ import { SOLANA_ECOSYSTEM_ID } from "@swim-io/solana"; import type { EcosystemId, TokenConfig } from "../../config"; import { getTokenDetailsForEcosystem } from "../../config"; import { Amount } from "../../models"; +import { useAptosTokenBalanceQuery } from "../aptos"; import { useErc20BalanceQuery } from "../evm"; export const useUserLpBalances = ( @@ -45,8 +46,20 @@ export const useUserLpBalances = ( ? Amount.fromHuman(lpTokenConfig, userLpBalanceBnbHuman) : null; + // aptos + const aptosTokenDetails = + getTokenDetailsForEcosystem(lpTokenConfig, APTOS_ECOSYSTEM_ID) ?? null; + const { data: userLpBalanceAptosHuman = null } = useAptosTokenBalanceQuery( + aptosTokenDetails, + { enabled: !!aptosTokenDetails }, + ); + const userLpBalanceAptos = + aptosTokenDetails && userLpBalanceAptosHuman + ? Amount.fromHuman(lpTokenConfig, userLpBalanceAptosHuman) + : null; + return { - [APTOS_ECOSYSTEM_ID]: null, + [APTOS_ECOSYSTEM_ID]: userLpBalanceAptos, [SOLANA_ECOSYSTEM_ID]: userLpBalanceSolana, [EvmEcosystemId.Ethereum]: userLpBalanceEthereum, [EvmEcosystemId.Bnb]: userLpBalanceBnb, diff --git a/apps/ui/src/models/swim/pool.ts b/apps/ui/src/models/swim/pool.ts index 8e570657b..285ffe414 100644 --- a/apps/ui/src/models/swim/pool.ts +++ b/apps/ui/src/models/swim/pool.ts @@ -1,4 +1,6 @@ import { PublicKey } from "@solana/web3.js"; +import type { AptosPoolState } from "@swim-io/aptos"; +import { APTOS_ECOSYSTEM_ID } from "@swim-io/aptos"; import type { EvmClient, EvmEcosystemId } from "@swim-io/evm"; import { isEvmEcosystemId } from "@swim-io/evm"; import { Routing__factory } from "@swim-io/evm-contracts"; @@ -44,7 +46,7 @@ export interface EvmPoolState { readonly governanceFee: Decimal; } -export type PoolState = SolanaPoolState | EvmPoolState; +export type PoolState = AptosPoolState | SolanaPoolState | EvmPoolState; export type TokensByPoolId = ReadonlyRecord< string, // Pool ID @@ -157,6 +159,11 @@ export const isEvmPoolState = ( ): poolState is EvmPoolState => poolState !== null && isEvmEcosystemId(poolState.ecosystem); +export const isAptosPoolState = ( + poolState: PoolState | null, +): poolState is AptosPoolState => + poolState !== null && poolState.ecosystem === APTOS_ECOSYSTEM_ID; + export const isSolanaPoolState = ( poolState: PoolState, ): poolState is SolanaPoolState => poolState.ecosystem === SOLANA_ECOSYSTEM_ID; diff --git a/apps/ui/src/pages/PoolPage.tsx b/apps/ui/src/pages/PoolPage.tsx index e79b0b49f..5db1e6578 100644 --- a/apps/ui/src/pages/PoolPage.tsx +++ b/apps/ui/src/pages/PoolPage.tsx @@ -47,7 +47,7 @@ import { } from "../hooks"; import BNB_SVG from "../images/ecosystems/bnb.svg"; import ETHEREUM_SVG from "../images/ecosystems/ethereum.svg"; -import { isEvmPoolState } from "../models"; +import { isAptosPoolState, isEvmPoolState } from "../models"; import "./PoolPage.scss"; @@ -135,7 +135,27 @@ export const PoolPageInner = ({ ); const reserveStats = tokens.map((tokenConfig, i) => { - const solanaDetails = getSolanaTokenDetails(tokenConfig); + // loading state + if (poolState === null) { + return { + title: , + description: "-", + key: tokenConfig.id, + }; + } + + if (isAptosPoolState(poolState)) { + return { + title: ( + + ), + description: atomicToHumanString( + new Decimal(poolState.balances[i].toString()), + 2, + ), + key: tokenConfig.id, + }; + } if (isEvmPoolState(poolState)) { return { @@ -150,6 +170,7 @@ export const PoolPageInner = ({ }; } + const solanaDetails = getSolanaTokenDetails(tokenConfig); const poolTokenAccount = poolTokenAccounts?.find( (account) => From 634b35e66edca7ce3fcc2a097486ef15787b6e3e Mon Sep 17 00:00:00 2001 From: Nico Miicro Date: Tue, 25 Oct 2022 12:09:28 +0300 Subject: [PATCH 05/10] fix(aptos): use proper resource type in getTokenBalance --- packages/aptos/src/client.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/aptos/src/client.ts b/packages/aptos/src/client.ts index 0c07a9e7d..718ba5522 100644 --- a/packages/aptos/src/client.ts +++ b/packages/aptos/src/client.ts @@ -86,7 +86,7 @@ export class AptosClient extends Client< try { const resource = (await this.sdkClient.getAccountResource( account.address(), - tokenDetails.address, + getCoinStoreType(tokenDetails.address), )) as CoinResource; return atomicToHuman( new Decimal(resource.data.coin.value), @@ -191,6 +191,8 @@ export class AptosClient extends Client< } } +const getCoinStoreType = (address: string) => + `0x1::coin::CoinStore<${address}>`; const getCoinInfoType = (address: string) => `0x1::coin::CoinInfo<${address}>`; const getCoinInfoSupply = (coinInfo: CoinInfoResource): Decimal => { From dfcba149b780a2b51245b2868d5fdf2a3a059e36 Mon Sep 17 00:00:00 2001 From: Nico Miicro Date: Tue, 25 Oct 2022 14:28:53 +0300 Subject: [PATCH 06/10] refactor(ui): define local AptosPoolState --- apps/ui/src/models/swim/pool.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/ui/src/models/swim/pool.ts b/apps/ui/src/models/swim/pool.ts index 38a11367d..4d675af85 100644 --- a/apps/ui/src/models/swim/pool.ts +++ b/apps/ui/src/models/swim/pool.ts @@ -1,6 +1,7 @@ import { PublicKey } from "@solana/web3.js"; -import type { AptosPoolState } from "@swim-io/aptos"; +import type { AptosEcosystemId } from "@swim-io/aptos"; import { APTOS_ECOSYSTEM_ID } from "@swim-io/aptos"; +import type { PoolState as CorePoolState } from "@swim-io/core"; import type { EvmClient, EvmEcosystemId } from "@swim-io/evm"; import { isEvmEcosystemId } from "@swim-io/evm"; import { Routing__factory } from "@swim-io/evm-contracts"; @@ -46,6 +47,10 @@ export interface EvmPoolState { readonly governanceFee: Decimal; } +export interface AptosPoolState extends CorePoolState { + readonly ecosystem: AptosEcosystemId; +} + export type PoolState = AptosPoolState | SolanaPoolState | EvmPoolState; export type TokensByPoolId = ReadonlyRecord< From cdf14224a969cc968bbc37a36909b426669897f8 Mon Sep 17 00:00:00 2001 From: Nico Miicro Date: Tue, 25 Oct 2022 14:29:09 +0300 Subject: [PATCH 07/10] refactor(aptos): remove AptosPoolState --- packages/aptos/src/protocol.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/aptos/src/protocol.ts b/packages/aptos/src/protocol.ts index 49a830669..6fae53bfb 100644 --- a/packages/aptos/src/protocol.ts +++ b/packages/aptos/src/protocol.ts @@ -20,10 +20,6 @@ export interface AptosPoolConfig extends PoolConfig { readonly owner: Types.Address; } -export interface AptosPoolState extends PoolState { - readonly ecosystem: AptosEcosystemId; -} - export interface AptosChainConfig extends ChainConfig { readonly pools: readonly AptosPoolConfig[]; } From 8b3c2628db2ef76c18d93d9109d8fd307b8c2f39 Mon Sep 17 00:00:00 2001 From: Nico Miicro Date: Tue, 25 Oct 2022 16:30:34 +0300 Subject: [PATCH 08/10] refactor(aptos): tidy up utils and types --- packages/aptos/src/client.ts | 76 ++++---------------------- packages/aptos/src/liquidswap.ts | 21 ------- packages/aptos/src/liquidswap/const.ts | 3 + packages/aptos/src/liquidswap/index.ts | 3 + packages/aptos/src/liquidswap/types.ts | 20 +++++++ packages/aptos/src/liquidswap/utils.ts | 24 ++++++++ packages/aptos/src/protocol.ts | 1 - packages/aptos/src/types.ts | 36 ++++++++++++ packages/aptos/src/utils.ts | 17 ++++++ 9 files changed, 114 insertions(+), 87 deletions(-) delete mode 100644 packages/aptos/src/liquidswap.ts create mode 100644 packages/aptos/src/liquidswap/const.ts create mode 100644 packages/aptos/src/liquidswap/index.ts create mode 100644 packages/aptos/src/liquidswap/types.ts create mode 100644 packages/aptos/src/liquidswap/utils.ts create mode 100644 packages/aptos/src/types.ts create mode 100644 packages/aptos/src/utils.ts diff --git a/packages/aptos/src/client.ts b/packages/aptos/src/client.ts index bb47b96b8..dee9254f3 100644 --- a/packages/aptos/src/client.ts +++ b/packages/aptos/src/client.ts @@ -1,8 +1,6 @@ -import { isSortedSymbols } from "@pontem/liquidswap-sdk/dist/tsc/utils/contracts.js"; import type { PoolState, TokenDetails, TxGeneratorResult } from "@swim-io/core"; import { Client } from "@swim-io/core"; import { atomicToHuman, findOrThrow } from "@swim-io/utils"; -import type { Types } from "aptos"; import { APTOS_COIN, ApiError, @@ -11,8 +9,8 @@ import { } from "aptos"; import Decimal from "decimal.js"; +import { DAO_FEE_DECIMALS, FEE_DECIMALS, getBalances } from "./liquidswap"; import type { PoolResource } from "./liquidswap"; -import { DAO_FEE_DECIMALS, FEE_DECIMALS } from "./liquidswap"; import type { AptosChainConfig, AptosEcosystemId, @@ -20,43 +18,14 @@ import type { AptosTxType, } from "./protocol"; import { APTOS_ECOSYSTEM_ID } from "./protocol"; +import type { CoinInfoResource, CoinResource, GasScheduleV2 } from "./types"; +import { getCoinInfoSupply, getCoinInfoType, getCoinStoreType } from "./utils"; import type { AptosWalletAdapter } from "./walletAdapters"; -interface GasScheduleV2 { - readonly entries: readonly { - readonly key: string; - readonly value: string; - }[]; -} -interface CoinInfoResource { - readonly decimals: number; - readonly name: string; - readonly supply: { - readonly vec: readonly [ - { - readonly integer: { - readonly vec: readonly [ - { - readonly limit: string; - readonly value: string; - }, - ]; - }; - }, - ]; - }; - readonly symbol: string; -} - export interface AptosClientOptions { readonly endpoint: string; } -interface CoinResource { - readonly data: { readonly coin: { readonly value: string } }; - readonly type: Types.MoveStructTag; -} - export class AptosClient extends Client< AptosEcosystemId, AptosChainConfig, @@ -185,7 +154,7 @@ export class AptosClient extends Client< }, ); - const [lpTokenSupplyResponse, poolBalancesResponse] = await Promise.all([ + const [lpTokenSupplyResponse, poolResponse] = await Promise.all([ this.sdkClient.getAccountResource( owner, getCoinInfoType(lpToken.nativeDetails.address), @@ -194,44 +163,21 @@ export class AptosClient extends Client< ]); const totalLpSupply = getCoinInfoSupply( - lpTokenSupplyResponse.data as CoinInfoResource, + lpTokenSupplyResponse as CoinInfoResource, ); - const pool = poolBalancesResponse.data as PoolResource; + const pool = poolResponse as PoolResource; const balances = getBalances(pool, [tokenX, tokenY]); return { isPaused: false, // TODO ? https://exsphere.slack.com/archives/C03SQTXMFT9/p1666390318707959?thread_ts=1666352227.333479&cid=C03SQTXMFT9 ampFactor: new Decimal("1"), // no ampFactor for liquidswap pool ? - lpFee: atomicToHuman(new Decimal(pool.fee), FEE_DECIMALS), - governanceFee: atomicToHuman(new Decimal(pool.dao_fee), DAO_FEE_DECIMALS), + lpFee: atomicToHuman(new Decimal(pool.data.fee), FEE_DECIMALS), + governanceFee: atomicToHuman( + new Decimal(pool.data.dao_fee), + DAO_FEE_DECIMALS, + ), balances, totalLpSupply, }; } } - -const getCoinStoreType = (address: string) => - `0x1::coin::CoinStore<${address}>`; -const getCoinInfoType = (address: string) => `0x1::coin::CoinInfo<${address}>`; - -const getCoinInfoSupply = (coinInfo: CoinInfoResource): Decimal => { - return atomicToHuman( - new Decimal(coinInfo.supply.vec[0].integer.vec[0].value), - coinInfo.decimals, - ); -}; - -const getBalances = ( - pool: PoolResource, - tokens: readonly [TokenDetails, TokenDetails], -): PoolState["balances"] => { - const reserves = [pool.coin_x_reserve.value, pool.coin_y_reserve.value]; - const atomicBalances = isSortedSymbols(tokens[0].address, tokens[1].address) - ? reserves - : [...reserves].reverse(); - - return [ - atomicToHuman(new Decimal(atomicBalances[0]), tokens[0].decimals), - atomicToHuman(new Decimal(atomicBalances[1]), tokens[1].decimals), - ]; -}; diff --git a/packages/aptos/src/liquidswap.ts b/packages/aptos/src/liquidswap.ts deleted file mode 100644 index 636d5aa7b..000000000 --- a/packages/aptos/src/liquidswap.ts +++ /dev/null @@ -1,21 +0,0 @@ -export interface PoolResource { - readonly coin_x_reserve: { - readonly value: string; - }; - - readonly coin_y_reserve: { - readonly value: string; - }; - - readonly dao_fee: string; - readonly fee: string; - readonly last_block_timestamp: string; - readonly last_price_x_cumulative: string; - readonly last_price_y_cumulative: string; - readonly x_scale: string; - readonly y_scale: string; -} - -// From https://github.com/pontem-network/liquidswap/blob/b89aed9adb96c0b083134765f75be814418be670/sources/swap/liquidity_pool.move#L70-L74 -export const FEE_DECIMALS = 4; -export const DAO_FEE_DECIMALS = 2; diff --git a/packages/aptos/src/liquidswap/const.ts b/packages/aptos/src/liquidswap/const.ts new file mode 100644 index 000000000..f01595dc7 --- /dev/null +++ b/packages/aptos/src/liquidswap/const.ts @@ -0,0 +1,3 @@ +// From https://github.com/pontem-network/liquidswap/blob/b89aed9adb96c0b083134765f75be814418be670/sources/swap/liquidity_pool.move#L70-L74 +export const FEE_DECIMALS = 4; +export const DAO_FEE_DECIMALS = 2; diff --git a/packages/aptos/src/liquidswap/index.ts b/packages/aptos/src/liquidswap/index.ts new file mode 100644 index 000000000..5134ddae0 --- /dev/null +++ b/packages/aptos/src/liquidswap/index.ts @@ -0,0 +1,3 @@ +export * from "./const"; +export * from "./types"; +export * from "./utils"; diff --git a/packages/aptos/src/liquidswap/types.ts b/packages/aptos/src/liquidswap/types.ts new file mode 100644 index 000000000..ca7a067fd --- /dev/null +++ b/packages/aptos/src/liquidswap/types.ts @@ -0,0 +1,20 @@ +import type { Types } from "aptos"; + +export interface PoolResource { + readonly data: { + readonly coin_x_reserve: { + readonly value: string; + }; + readonly coin_y_reserve: { + readonly value: string; + }; + readonly dao_fee: string; + readonly fee: string; + readonly last_block_timestamp: string; + readonly last_price_x_cumulative: string; + readonly last_price_y_cumulative: string; + readonly x_scale: string; + readonly y_scale: string; + }; + readonly type: Types.MoveStructTag; +} diff --git a/packages/aptos/src/liquidswap/utils.ts b/packages/aptos/src/liquidswap/utils.ts new file mode 100644 index 000000000..768b6cf40 --- /dev/null +++ b/packages/aptos/src/liquidswap/utils.ts @@ -0,0 +1,24 @@ +import { isSortedSymbols } from "@pontem/liquidswap-sdk/dist/tsc/utils/contracts.js"; +import type { PoolState, TokenDetails } from "@swim-io/core"; +import { atomicToHuman } from "@swim-io/utils"; +import { Decimal } from "decimal.js"; + +import type { PoolResource } from "./types"; + +export const getBalances = ( + pool: PoolResource, + tokens: readonly [TokenDetails, TokenDetails], +): PoolState["balances"] => { + const reserves = [ + pool.data.coin_x_reserve.value, + pool.data.coin_y_reserve.value, + ]; + const atomicBalances = isSortedSymbols(tokens[0].address, tokens[1].address) + ? reserves + : [...reserves].reverse(); + + return [ + atomicToHuman(new Decimal(atomicBalances[0]), tokens[0].decimals), + atomicToHuman(new Decimal(atomicBalances[1]), tokens[1].decimals), + ]; +}; diff --git a/packages/aptos/src/protocol.ts b/packages/aptos/src/protocol.ts index 6fae53bfb..1ae5aa779 100644 --- a/packages/aptos/src/protocol.ts +++ b/packages/aptos/src/protocol.ts @@ -3,7 +3,6 @@ import type { EcosystemConfig, Env, PoolConfig, - PoolState, Tx, } from "@swim-io/core"; import type { ReadonlyRecord } from "@swim-io/utils"; diff --git a/packages/aptos/src/types.ts b/packages/aptos/src/types.ts new file mode 100644 index 000000000..607cdabf2 --- /dev/null +++ b/packages/aptos/src/types.ts @@ -0,0 +1,36 @@ +import type { Types } from "aptos"; + +export interface GasScheduleV2 { + readonly entries: readonly { + readonly key: string; + readonly value: string; + }[]; +} + +export interface CoinInfoResource { + readonly data: { + readonly decimals: number; + readonly name: string; + readonly supply: { + readonly vec: readonly [ + { + readonly integer: { + readonly vec: readonly [ + { + readonly limit: string; + readonly value: string; + }, + ]; + }; + }, + ]; + }; + readonly symbol: string; + }; + readonly type: Types.MoveStructTag; +} + +export interface CoinResource { + readonly data: { readonly coin: { readonly value: string } }; + readonly type: Types.MoveStructTag; +} diff --git a/packages/aptos/src/utils.ts b/packages/aptos/src/utils.ts new file mode 100644 index 000000000..62db1eada --- /dev/null +++ b/packages/aptos/src/utils.ts @@ -0,0 +1,17 @@ +import { atomicToHuman } from "@swim-io/utils"; +import { Decimal } from "decimal.js"; + +import type { CoinInfoResource } from "./types"; + +export const getCoinInfoType = (address: string) => + `0x1::coin::CoinInfo<${address}>`; + +export const getCoinStoreType = (address: string) => + `0x1::coin::CoinStore<${address}>`; + +export const getCoinInfoSupply = (coinInfo: CoinInfoResource): Decimal => { + return atomicToHuman( + new Decimal(coinInfo.data.supply.vec[0].integer.vec[0].value), + coinInfo.data.decimals, + ); +}; From 4f3fd6026b529bcc206430db54073d2e8d70f127 Mon Sep 17 00:00:00 2001 From: Nico Miicro Date: Tue, 25 Oct 2022 22:38:31 +0300 Subject: [PATCH 09/10] refactor(aptos): rename const to constants --- packages/aptos/src/liquidswap/{const.ts => constants.ts} | 0 packages/aptos/src/liquidswap/index.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/aptos/src/liquidswap/{const.ts => constants.ts} (100%) diff --git a/packages/aptos/src/liquidswap/const.ts b/packages/aptos/src/liquidswap/constants.ts similarity index 100% rename from packages/aptos/src/liquidswap/const.ts rename to packages/aptos/src/liquidswap/constants.ts diff --git a/packages/aptos/src/liquidswap/index.ts b/packages/aptos/src/liquidswap/index.ts index 5134ddae0..227a133b3 100644 --- a/packages/aptos/src/liquidswap/index.ts +++ b/packages/aptos/src/liquidswap/index.ts @@ -1,3 +1,3 @@ -export * from "./const"; +export * from "./constants"; export * from "./types"; export * from "./utils"; From c7156f1ed9100f911307236e4abaa5cc5562d8ba Mon Sep 17 00:00:00 2001 From: Nico Miicro Date: Tue, 25 Oct 2022 22:39:59 +0300 Subject: [PATCH 10/10] refactor(aptos): rename getBalances to getPoolBalances --- packages/aptos/src/client.ts | 4 ++-- packages/aptos/src/liquidswap/utils.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/aptos/src/client.ts b/packages/aptos/src/client.ts index dee9254f3..c94db0b16 100644 --- a/packages/aptos/src/client.ts +++ b/packages/aptos/src/client.ts @@ -9,7 +9,7 @@ import { } from "aptos"; import Decimal from "decimal.js"; -import { DAO_FEE_DECIMALS, FEE_DECIMALS, getBalances } from "./liquidswap"; +import { DAO_FEE_DECIMALS, FEE_DECIMALS, getPoolBalances } from "./liquidswap"; import type { PoolResource } from "./liquidswap"; import type { AptosChainConfig, @@ -166,7 +166,7 @@ export class AptosClient extends Client< lpTokenSupplyResponse as CoinInfoResource, ); const pool = poolResponse as PoolResource; - const balances = getBalances(pool, [tokenX, tokenY]); + const balances = getPoolBalances(pool, [tokenX, tokenY]); return { isPaused: false, // TODO ? https://exsphere.slack.com/archives/C03SQTXMFT9/p1666390318707959?thread_ts=1666352227.333479&cid=C03SQTXMFT9 diff --git a/packages/aptos/src/liquidswap/utils.ts b/packages/aptos/src/liquidswap/utils.ts index 768b6cf40..8299e4938 100644 --- a/packages/aptos/src/liquidswap/utils.ts +++ b/packages/aptos/src/liquidswap/utils.ts @@ -5,7 +5,7 @@ import { Decimal } from "decimal.js"; import type { PoolResource } from "./types"; -export const getBalances = ( +export const getPoolBalances = ( pool: PoolResource, tokens: readonly [TokenDetails, TokenDetails], ): PoolState["balances"] => {