From 952c6831004ac736170ff7061447097334feb325 Mon Sep 17 00:00:00 2001 From: SamuelQZQ Date: Sun, 7 Apr 2024 01:52:16 +0800 Subject: [PATCH] remove sentio --- .changeset/lovely-cooks-allow.md | 5 ++ bun.lockb | Bin 202644 -> 203310 bytes examples/examples.ts | 5 +- package.json | 12 ++-- src/PoolDataClient.ts | 113 ++++++++++++++++++++++++++----- src/ThalaswapRouter.ts | 47 ++----------- src/constants.ts | 2 + src/router.ts | 12 ++-- src/types.ts | 13 +++- src/utils.ts | 60 ++++++++++++++++ test/test-pools.json | 84 +++++++++++++++-------- 11 files changed, 250 insertions(+), 103 deletions(-) create mode 100644 .changeset/lovely-cooks-allow.md create mode 100644 src/constants.ts create mode 100644 src/utils.ts diff --git a/.changeset/lovely-cooks-allow.md b/.changeset/lovely-cooks-allow.md new file mode 100644 index 0000000..df0756a --- /dev/null +++ b/.changeset/lovely-cooks-allow.md @@ -0,0 +1,5 @@ +--- +"@thalalabs/router-sdk": major +--- + +Fetch data from aptos sdk directly, instead of depends on external indexer diff --git a/bun.lockb b/bun.lockb index 9f3e9cf0445054b771732707741f27d9f3f0e1ef..d8402aec41b31413f39086c753529a2e3628eb00 100755 GIT binary patch delta 40673 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≷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 E3yl*PbN~PV delta 40562 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 diff --git a/examples/examples.ts b/examples/examples.ts index aa77810..85bfe75 100644 --- a/examples/examples.ts +++ b/examples/examples.ts @@ -21,10 +21,7 @@ const account = Account.fromPrivateKey({ privateKey: new Ed25519PrivateKey(privateKey), }); -// `testnet-example-pools.json` contains some testnet pools. The data is not up-to-date. -const router = new ThalaswapRouter( - "https://raw.githubusercontent.com/ThalaLabs/thala-router/main/examples/testnet-example-pools.json", -); +const router = new ThalaswapRouter("https://fullnode.mainnet.aptoslabs.com/v1"); // Example 1: Exact input. 1 hop async function example1() { diff --git a/package.json b/package.json index 84d1985..357c820 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "1.2.0", "main": "dist/index.js", "types": "dist/index.d.ts", + "sideEffects": false, "scripts": { "typecheck": "bun run tsc --noEmit", "build": "bun run tsup ./src/index.ts --dts", @@ -32,15 +33,18 @@ "prettier": "3.1.0", "tsup": "^7.2.0", "typescript": "^5.2.2", - "@aptos-labs/ts-sdk": "^1.2.0", - "@thalalabs/surf": "^1.1.0" + "@aptos-labs/ts-sdk": "^1.11.0", + "@thalalabs/surf": "^1.3.1", + "@types/lodash": "^4.17.0" }, "license": "MIT", "dependencies": { - "axios": "^1.5.1" + "bignumber.js": "^9.1.2", + "lodash": "^4.17.21" }, "peerDependencies": { - "@thalalabs/surf": "^1.1.0" + "@aptos-labs/ts-sdk": "^1.11.0", + "@thalalabs/surf": "^1.3.1" }, "lint-staged": { "*.{js,jsx,ts,tsx}": [ diff --git a/src/PoolDataClient.ts b/src/PoolDataClient.ts index ed24c69..28d626a 100644 --- a/src/PoolDataClient.ts +++ b/src/PoolDataClient.ts @@ -1,15 +1,24 @@ -import axios from "axios"; -import { Coin, PoolData, RawPool, RawPoolData } from "./types"; +import { Coin, Pool, PoolData } from "./types"; +import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; +import { THALASWAP_RESOURCE_ACCOUNT_ADDRESS } from "./constants"; +import { uniq } from "lodash"; +import { parsePoolMetadata, scaleDown } from "./utils"; class PoolDataClient { private poolData: PoolData | null = null; private lastUpdated: number = 0; private expiry = 10000; // 10 seconds private retryLimit = 3; - private URL = ""; + private client: Aptos; + private coins: Coin[] = []; - constructor(dataURL: string) { - this.URL = dataURL; + constructor(aptosRpc: string) { + this.client = new Aptos( + new AptosConfig({ + network: Network.MAINNET, + fullnode: aptosRpc, + }), + ); } async getPoolData(): Promise { @@ -17,23 +26,91 @@ class PoolDataClient { if (!this.poolData || currentTime - this.lastUpdated > this.expiry) { for (let i = 0; i < this.retryLimit; i++) { try { - const response = await axios.get(this.URL); - - // Convert the indices in the pools to the actual coin addresses - const coins = response.data.coins as Coin[]; - const pools = response.data.pools.map((pool: RawPool) => { - return { - ...pool, - asset0: coins[pool.asset0], - asset1: coins[pool.asset1], - asset2: pool.asset2 ? coins[pool.asset2] : undefined, - asset3: pool.asset3 ? coins[pool.asset3] : undefined, + const resources = ( + await this.client.getAccountResources({ + accountAddress: THALASWAP_RESOURCE_ACCOUNT_ADDRESS, + }) + ).filter( + (r) => + r.type.startsWith( + `${THALASWAP_RESOURCE_ACCOUNT_ADDRESS}::weighted_pool::WeightedPool<`, + ) || + r.type.startsWith( + `${THALASWAP_RESOURCE_ACCOUNT_ADDRESS}::stable_pool::StablePool<`, + ), + ) as { + type: string; + data: { + asset_0: { value: string }; + asset_1: { value: string }; + asset_2: { value: string }; + asset_3: { value: string }; + amp_factor?: string; }; - }); + }[]; + + const allCoinAddress = uniq( + resources.reduce((acc, resource) => { + const metadata = parsePoolMetadata(resource.type); + metadata.coinAddresses.forEach((coin) => { + coin && acc.push(coin); + }); + return acc; + }, [] as string[]), + ); + + await Promise.all( + allCoinAddress.map(async (address) => { + if (this.coins.find((c) => c.address === address)) return; + const coin = { + address, + decimals: ( + await this.client.view({ + payload: { + function: "0x1::coin::decimals", + functionArguments: [], + typeArguments: [ + address as `${string}::${string}::${string}`, + ], + }, + }) + )[0] as number, + }; + this.coins.push(coin); + }), + ); + + const pools = resources.reduce((acc, resource) => { + const metadata = parsePoolMetadata(resource.type); + const [coin0, coin1, coin2, coin3] = metadata.coinAddresses.map( + (addr) => this.coins.find((c) => c.address === addr), + ); + + acc.push({ + weights: metadata.weights.map((w) => Number(w) / 100), + poolType: metadata.poolType, + amp: resource.data.amp_factor + ? Number(resource.data.amp_factor) + : undefined, + asset0: coin0!, + asset1: coin1!, + asset2: coin2, + asset3: coin3, + balance0: scaleDown(resource.data.asset_0.value, coin0!.decimals), + balance1: scaleDown(resource.data.asset_1.value, coin1!.decimals), + balance2: coin2 + ? scaleDown(resource.data.asset_2.value, coin2.decimals) + : undefined, + balance3: coin3 + ? scaleDown(resource.data.asset_3.value, coin3.decimals) + : undefined, + }); + return acc; + }, [] as Pool[]); this.poolData = { pools, - coins, + coins: this.coins, }; this.lastUpdated = currentTime; return this.poolData; diff --git a/src/ThalaswapRouter.ts b/src/ThalaswapRouter.ts index 20b772c..d7a91a2 100644 --- a/src/ThalaswapRouter.ts +++ b/src/ThalaswapRouter.ts @@ -32,7 +32,7 @@ const encodePoolType = ( pool: LiquidityPool, extendStableArgs: boolean, ): string[] => { - if (pool.poolType === "stable_pool") { + if (pool.poolType === "Stable") { const typeArgs = NULL_4.map((nullType, i) => i < pool.coinAddresses.length ? pool.coinAddresses[i] : nullType, ); @@ -80,34 +80,6 @@ class ThalaswapRouter { this.graph = await this.buildGraph(pools); } - parseWeightsFromWeightedPoolName(poolName: string): number[] { - const weights: number[] = []; - - const tokenWeightPairs = poolName.split(":"); - - // Iterate over each token-weight pair except the first one (which is just 'W' or 'S') - for (const pair of tokenWeightPairs.slice(1)) { - const parts = pair.split("-"); - if (parts.length === 2) { - const weight = parseInt(parts[1], 10); - if (!isNaN(weight)) { - weights.push(weight / 100); - } else { - throw new Error("Invalid weight in pool name"); - } - } else { - throw new Error("Invalid token-weight pair in pool name: " + poolName); - } - } - - return weights; - } - - parseAmpFactorFromStablePoolName(poolName: string): number { - const parts = poolName.split(":"); - return parseInt(parts[1]); - } - async buildGraph(pools: Pool[]): Promise { const tokens: Set = new Set(); const graph: Graph = {}; @@ -122,26 +94,19 @@ class ThalaswapRouter { .filter((b, i) => assets[i]) .map((b) => pool[b as BalanceIndex] as number); - const poolType = pool.name[0] === "S" ? "stable_pool" : "weighted_pool"; const swapFee = - poolType === "stable_pool" + pool.poolType === "Stable" ? DEFAULT_SWAP_FEE_STABLE : DEFAULT_SWAP_FEE_WEIGHTED; - const weights = - poolType === "weighted_pool" - ? this.parseWeightsFromWeightedPoolName(pool.name) - : undefined; + const weights = pool.poolType === "Weighted" ? pool.weights! : undefined; - const amp = - poolType === "stable_pool" - ? this.parseAmpFactorFromStablePoolName(pool.name) - : undefined; + const amp = pool.poolType === "Stable" ? pool.amp! : undefined; const convertedPool: LiquidityPool = { coinAddresses: assets.map((a) => a.address), balances, - poolType, + poolType: pool.poolType, swapFee, weights, amp, @@ -258,7 +223,7 @@ class ThalaswapRouter { const functionName = route.type === "exact_input" ? "swap_exact_in" : "swap_exact_out"; const abi = - path.pool.poolType === "stable_pool" + path.pool.poolType === "Stable" ? STABLE_POOL_SCRIPTS_ABI : WEIGHTED_POOL_SCRIPTS_ABI; const typeArgs = encodePoolType(path.pool, false).concat([ diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..dc9252b --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,2 @@ +export const THALASWAP_RESOURCE_ACCOUNT_ADDRESS = + "0x48271d39d0b05bd6efca2278f22277d6fcc375504f9839fd73f74ace240861af" as const; diff --git a/src/router.ts b/src/router.ts index 33387a1..4d23c6a 100644 --- a/src/router.ts +++ b/src/router.ts @@ -22,7 +22,7 @@ function calcOutGivenIn( toIndex: number, ): number { const { poolType, balances, swapFee, weights, amp } = pool; - if (poolType === "stable_pool") { + if (poolType === "Stable") { return calcOutGivenInStable( amountIn, fromIndex, @@ -31,7 +31,7 @@ function calcOutGivenIn( amp as number, swapFee, ); - } else if (poolType === "weighted_pool") { + } else if (poolType === "Weighted") { const weightFrom = weights![fromIndex]; const weightTo = weights![toIndex]; return calcOutGivenInWeighted( @@ -58,7 +58,7 @@ function calcInGivenOut( throw new Error("Insufficient balance"); } - if (poolType === "stable_pool") { + if (poolType === "Stable") { return calcInGivenOutStable( amountOut, fromIndex, @@ -67,7 +67,7 @@ function calcInGivenOut( amp as number, swapFee, ); - } else if (poolType === "weighted_pool") { + } else if (poolType === "Weighted") { return calcInGivenOutWeighted( balances[fromIndex], weights![fromIndex], @@ -90,7 +90,7 @@ function calcPriceImpactPercentage( toIndex: number, ): number { const { poolType, balances, weights, amp } = pool; - if (poolType === "stable_pool") { + if (poolType === "Stable") { return calcPriceImpactPercentageStable( amountIn, amountOut, @@ -99,7 +99,7 @@ function calcPriceImpactPercentage( balances, amp as number, ); - } else if (poolType === "weighted_pool") { + } else if (poolType === "Weighted") { return calcPriceImpactPercentageWeighted( amountIn, amountOut, diff --git a/src/types.ts b/src/types.ts index 076e4eb..7151ef1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -33,11 +33,12 @@ type Coin = { }; type PoolBase = { - name: string; + poolType: "Weighted" | "Stable"; balance0: number; balance1: number; balance2?: number; balance3?: number; + weights: number[]; amp?: number; }; type RawPool = PoolBase & { @@ -65,7 +66,7 @@ type PoolData = { }; type RouteType = "exact_input" | "exact_output"; -type PoolType = "stable_pool" | "weighted_pool"; +type PoolType = "Stable" | "Weighted"; type Graph = Record; type Distances = Record>; type Predecessors = Record< @@ -93,3 +94,11 @@ export type { PoolData, Pool, }; + +export type LiquidityPoolMetadata = { + type: string; + poolType: PoolType; + numCoins: number; + coinAddresses: string[]; + weights: number[]; +}; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..658351e --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,60 @@ +import BigNumber from "bignumber.js"; +import { THALASWAP_RESOURCE_ACCOUNT_ADDRESS } from "./constants"; +import { LiquidityPoolMetadata } from "./types"; + +export const BN_TEN = new BigNumber(10); + +export function scaleDown(v: number | string, decimals: number): number { + return new BigNumber(v) + .dividedBy(BN_TEN.exponentiatedBy(decimals)) + .toNumber(); +} + +export function parsePoolMetadata(poolType: string): LiquidityPoolMetadata { + const [liquidityPoolType, poolTypeArgs] = parseLiquidityPoolType(poolType); + + // if first n coins are not dummycoin, then numCoins = n + const nullIndex = poolTypeArgs + .slice(0, 4) + .findIndex((e) => NULL_PATTERN.test(e)); + const numCoins = nullIndex === -1 ? 4 : nullIndex; + + const coinAddresses = poolTypeArgs.slice(0, numCoins); + const weights = + liquidityPoolType === "Weighted" + ? poolTypeArgs + .slice(4, 4 + numCoins) + .map((weight) => parseInt(weight.match(/.*::Weight_(\d+)/)![1])) + : []; // stable pool has no weights, so return empty array + return { + type: poolType, + poolType: liquidityPoolType, + numCoins, + coinAddresses, + weights, + }; +} + +export function parseLiquidityPoolType( + poolType: string, +): ["Weighted" | "Stable", string[]] { + const matchWeightedPool = poolType.match(WEIGHTED_POOL_PATTERN); + if (matchWeightedPool) { + return ["Weighted", matchWeightedPool[1].split(",").map((e) => e.trim())]; + } + const matchStablePool = poolType.match(STABLE_POOL_PATTERN); + if (matchStablePool) { + return ["Stable", matchStablePool[1].split(",").map((e) => e.trim())]; + } + throw new Error(`Invalid poolType: ${poolType}`); +} + +const WEIGHTED_POOL_PATTERN = new RegExp( + `${THALASWAP_RESOURCE_ACCOUNT_ADDRESS}::weighted_pool::WeightedPool<(.*)>`, +); +const STABLE_POOL_PATTERN = new RegExp( + `${THALASWAP_RESOURCE_ACCOUNT_ADDRESS}::stable_pool::StablePool<(.*)>`, +); +const NULL_PATTERN = new RegExp( + `${THALASWAP_RESOURCE_ACCOUNT_ADDRESS}::base_pool::Null`, +); diff --git a/test/test-pools.json b/test/test-pools.json index 810fcbb..194e534 100644 --- a/test/test-pools.json +++ b/test/test-pools.json @@ -55,196 +55,224 @@ ], "pools": [ { - "name": "S:100:APT:tAPT", + "poolType": "Stable", + "amp": 100, "asset0": 2, "asset1": 9, "balance0": 13008.29255679, "balance1": 7089.59780576 }, { - "name": "W:zUSDC-50:APT-50", + "weights": [0.5, 0.5], + "poolType": "Weighted", "asset0": 4, "asset1": 2, "balance0": 2056.163873, "balance1": 343.08755821 }, { - "name": "S:100:USDC:zUSDC", + "poolType": "Stable", + "amp": 100, "asset0": 8, "asset1": 4, "balance0": 116253605.3264, "balance1": 147928165.8321 }, { - "name": "W:wTBT-50:MOD-50", + "weights": [0.5, 0.5], + "poolType": "Weighted", "asset0": 11, "asset1": 0, "balance0": 34979.5108242, "balance1": 35724.6883114 }, { - "name": "W:CAKE-50:APT-50", + "weights": [0.5, 0.5], + "poolType": "Weighted", "asset0": 10, "asset1": 2, "balance0": 72.51205774, "balance1": 13.8509258 }, { - "name": "W:zWETH-50:APT-50", + "weights": [0.5, 0.5], + "poolType": "Weighted", "asset0": 3, "asset1": 2, "balance0": 0.004836, "balance1": 1.32766579 }, { - "name": "W:MOD-50:APT-50", + "weights": [0.5, 0.5], + "poolType": "Weighted", "asset0": 0, "asset1": 2, "balance0": 362930.20489986, "balance1": 60585.67918414 }, { - "name": "W:MOD-20:THL-80", + "weights": [0.2, 0.8], + "poolType": "Weighted", "asset0": 0, "asset1": 1, "balance0": 248734.03692406, "balance1": 6879670.48528901 }, { - "name": "S:100:zUSDT:APT", + "poolType": "Stable", + "amp": 100, "asset0": 5, "asset1": 2, "balance0": 270.645707, "balance1": 3.7526662 }, { - "name": "S:100:zUSDC:APT", + "poolType": "Stable", + "amp": 100, "asset0": 4, "asset1": 2, "balance0": 28.135422, "balance1": 0.99660011 }, { - "name": "W:zUSDC-50:zWETH-50", + "weights": [0.5, 0.5], + "poolType": "Weighted", "asset0": 4, "asset1": 3, "balance0": 298505.588373, "balance1": 178.617787 }, { - "name": "S:100:MOD:zUSDC", + "poolType": "Stable", + "amp": 100, "asset0": 0, "asset1": 4, "balance0": 2156392.68306257, "balance1": 1718685.730877 }, { - "name": "W:zUSDT-50:APT-50", + "weights": [0.5, 0.5], + "poolType": "Weighted", "asset0": 5, "asset1": 2, "balance0": 85.842312, "balance1": 14.31721868 }, { - "name": "W:USDC-50:MOD-50", + "weights": [0.5, 0.5], + "poolType": "Weighted", "asset0": 8, "asset1": 0, "balance0": 128039.615342, "balance1": 128142.51454857 }, { - "name": "S:100:MOD:APT", + "poolType": "Stable", + "amp": 100, "asset0": 0, "asset1": 2, "balance0": 4.02854082, "balance1": 0.14138684 }, { - "name": "W:USDC-50:zUSDT-50", + "weights": [0.5, 0.5], + "poolType": "Weighted", "asset0": 8, "asset1": 5, "balance0": 1.001998, "balance1": 0.99801 }, { - "name": "S:100:MOD:zUSDT", + "poolType": "Stable", + "amp": 100, "asset0": 0, "asset1": 5, "balance0": 4.94143188, "balance1": 5.141924 }, { - "name": "S:100:zWETH:APT", + "poolType": "Stable", + "amp": 100, "asset0": 3, "asset1": 2, "balance0": 0.023479, "balance1": 14.64680542 }, { - "name": "W:USDC-50:APT-50", + "weights": [0.5, 0.5], + "poolType": "Weighted", "asset0": 8, "asset1": 2, "balance0": 24.579756, "balance1": 4.12285236 }, { - "name": "S:100:USDC:MOD", + "poolType": "Stable", + "amp": 100, "asset0": 8, "asset1": 0, "balance0": 0.571745, "balance1": 0.52156903 }, { - "name": "W:zUSDC-50:zWBTC-50", + "weights": [0.5, 0.5], + "poolType": "Weighted", "asset0": 4, "asset1": 6, "balance0": 222295.82288, "balance1": 7.312754 }, { - "name": "W:zUSDC-20:APT-80", + "weights": [0.2, 0.8], + "poolType": "Weighted", "asset0": 4, "asset1": 2, "balance0": 25.909589, "balance1": 17.38773534 }, { - "name": "W:THL-50:zUSDT-50", + "weights": [0.5, 0.5], + "poolType": "Weighted", "asset0": 1, "asset1": 5, "balance0": 26.2370464, "balance1": 3.833277 }, { - "name": "W:THL-50:APT-50", + "weights": [0.5, 0.5], + "poolType": "Weighted", "asset0": 1, "asset1": 2, "balance0": 35654.3460136, "balance1": 859.43733753 }, { - "name": "S:100:zUSDC:zUSDT", + "poolType": "Stable", + "amp": 100, "asset0": 4, "asset1": 5, "balance0": 1345237.470138, "balance1": 1164406.178002 }, { - "name": "W:THL-50:zUSDC-50", + "weights": [0.5, 0.5], + "poolType": "Weighted", "asset0": 1, "asset1": 4, "balance0": 92.37699208, "balance1": 13.095411 }, { - "name": "W:THL-55:APT-45", + "weights": [0.55, 0.45], + "poolType": "Weighted", "asset0": 1, "asset1": 2, "balance0": 204.13207378, "balance1": 3.98294437 }, { - "name": "S:100:THL:zUSDC", + "poolType": "Stable", + "amp": 100, "asset0": 1, "asset1": 4, "balance0": 360.03921448,