From 9344332788ffba7b4d914ca38702d409dfd88b2e Mon Sep 17 00:00:00 2001 From: Jon Michael Aanes Date: Sun, 9 Jun 2024 01:09:54 +0200 Subject: [PATCH] Basic state loading --- ...tySwapContract.ts => LiquiditySwapLock.ts} | 4 +- src/main/swap/Main.ts | 174 +++++++++++++++--- src/main/swap/index.html | 40 ++-- 3 files changed, 169 insertions(+), 49 deletions(-) rename src/main/abi/{LiquiditySwapContract.ts => LiquiditySwapLock.ts} (99%) diff --git a/src/main/abi/LiquiditySwapContract.ts b/src/main/abi/LiquiditySwapLock.ts similarity index 99% rename from src/main/abi/LiquiditySwapContract.ts rename to src/main/abi/LiquiditySwapLock.ts index c003f0f..c825c75 100644 --- a/src/main/abi/LiquiditySwapContract.ts +++ b/src/main/abi/LiquiditySwapLock.ts @@ -102,7 +102,7 @@ export function deserializeLiquiditySwapContractState(state: StateBytes): Liquid return fromScValueLiquiditySwapContractState(scValue); } -export type Token = +export type Token = | TokenTokenA | TokenTokenB | TokenLiquidityToken; @@ -247,7 +247,7 @@ function fromScValueTokensInOut(structValue: ScValueStruct): TokensInOut { }; } -export type Permission = +export type Permission = | PermissionAnybody | PermissionSpecific; diff --git a/src/main/swap/Main.ts b/src/main/swap/Main.ts index adc1e11..6fd8263 100644 --- a/src/main/swap/Main.ts +++ b/src/main/swap/Main.ts @@ -3,17 +3,31 @@ */ import BN from "bn.js"; +import { LittleEndianByteInput } from "@secata-public/bitmanipulation-ts"; import { BlockchainAddress, StateBytes } from "@partisiablockchain/abi-client"; import { TransactionFailedError } from "../client/TransactionApi"; import { PutTransactionWasSuccessful } from "../client/TransactionData"; import { ShardedClient } from "../client/ShardedClient"; import { RouterState, deserializeRouterState } from "../abi/SwapRouter"; -import { LiquiditySwapContractState, deserializeLiquiditySwapContractState } from "../abi/LiquiditySwapContract"; -import { TokenState, deserializeTokenState } from "../abi/TokenV1"; +import { TokenBalance, LiquiditySwapContractState, deserializeLiquiditySwapContractState } from "../abi/LiquiditySwapLock"; +import { TokenState as TokenStateV1, deserializeTokenState as deserializeTokenStateV1 } from "../abi/TokenV1"; +import { TokenState as TokenStateV2, deserializeTokenState as deserializeTokenStateV2 } from "../abi/TokenV2"; import { NETWORK } from "../constant"; +import { AvlClient } from "../client/AvlClient"; + +// UI constants +const TOKEN_LIST = document.querySelector("#token-list"); +const EXCHANGE_RATE_LIST = document.querySelector("#rate-list"); + + +// Logic + +type ContractType = "token_v1" | "token_v2" | "swap_lock_v1" interface ContractState { - latest_state: TokenState | LiquiditySwapContractState | null; + latest_state: TokenStateV1 | TokenStateV2 | null; + swaps: BlockchainAddress[] | null, + type: ContractType | null, } const ROUTERS: BlockchainAddress[] = [BlockchainAddress.fromString("02f8eb18e09dfe6797880c952527747202560338bf")]; @@ -25,26 +39,132 @@ const TOKENS: Record = {}; const SWAPS: Record = {}; const SHARDED_CLIENT: ShardedClient = new ShardedClient(NETWORK.node_base_url, NETWORK.network_shards); +const AVL_CLIENT:AvlClient = new AvlClient(NETWORK .node_base_url, NETWORK.network_shards);; interface RawContractData { state: { data: string }; } -function get_contract_state(contractAddress: BlockchainAddress, deserialize: (state_bytes: StateBytes) => T): Promise { - return SHARDED_CLIENT - .getContractData(contractAddress.asString()) - .then((contract) => { - if (contract == null) { - throw new Error("Could not find data for contract"); - } +async function get_contract_state(contractAddress: BlockchainAddress, deserialize: (state_bytes: StateBytes) => T): Promise { + const contract = await SHARDED_CLIENT.getContractData(contractAddress.asString()); - // Reads the state of the contract - const stateBuffer = Buffer.from(contract.serializedContract.state.data, "base64"); + if (contract == null) { + throw new Error("Could not find data for contract"); + } - return deserialize({ state: stateBuffer }); - }); + // Reads the state of the contract + const stateBuffer = Buffer.from(contract.serializedContract.state.data, "base64"); + + return deserialize({ state: stateBuffer }); } +function address_to_url(address: BlockchainAddress): string { + return `${NETWORK.browser_base_url}/contracts/${address.asString()}`; +} + +function ui_add_token(tokenState: TokenStateV1 | TokenStateV2, address: BlockchainAddress) { + const spanFrom = document.createElement("div"); + spanFrom.innerHTML = ` +
+ +
+ `; + const spanTo = document.createElement("div"); + spanTo.innerHTML = ` +
+ +
+ `; + const spanSymbol= document.createElement("a"); + spanSymbol.innerText = tokenState.symbol; + spanSymbol.title = tokenState.name; + spanSymbol.href = address_to_url(address); + const spanAmount = document.createElement("div"); + spanAmount.innerText = "Login to view"; + TOKEN_LIST.append(spanFrom); + TOKEN_LIST.append(spanTo); + TOKEN_LIST.append(spanSymbol); + TOKEN_LIST.append(spanAmount); +} + +async function get_current_liquidity(swapAddress: BlockchainAddress): Promise { + const SWAP_CONTRACT_BALANCES_TREE_ID = 0; + const dataBuffer = await AVL_CLIENT.getContractStateAvlValue( + swapAddress.asString(), + SWAP_CONTRACT_BALANCES_TREE_ID, + swapAddress.asBuffer(), + ); + + if (dataBuffer === undefined) { + throw new Error("Contract was not correctly initialized"); + } + + const reader = new LittleEndianByteInput(dataBuffer); + + return { + aTokens: reader.readUnsignedBigInteger(16), + bTokens: reader.readUnsignedBigInteger(16), + liquidityTokens: reader.readUnsignedBigInteger(16), + } +} + +const RATE_DECIMALS = new BN(10000); + +async function get_current_exchange_rate(swapAddress: BlockchainAddress): Promise { + const liquidity: TokenBalance = await get_current_liquidity(swapAddress); + const result = liquidity.aTokens.mul(RATE_DECIMALS).div(liquidity.bTokens); + console.log(`${liquidity.aTokens}.mul(${RATE_DECIMALS}).div(${liquidity.bTokens}) = ${result}`); + return result; +} + +function ui_add_swap(swapAddress: BlockchainAddress) { + const rateElement = document.createElement("li"); + rateElement.id = "C"+swapAddress.asString(); + rateElement.innerText = swapAddress.asString(); + EXCHANGE_RATE_LIST.append(rateElement); +} + +async function ui_update_swap(swapState: LiquiditySwapContractState, swapAddress: BlockchainAddress) { + // Calculate rate + + const infoA = TOKENS[swapState.tokenBalances.tokenAAddress.asString()]; + const infoB = TOKENS[swapState.tokenBalances.tokenBAddress.asString()]; + + const symbolA = infoA.latest_state == null ? "???" : infoA.latest_state.symbol; + const symbolB = infoB.latest_state == null ? "???" : infoB.latest_state.symbol; + + const rateBsForA: BN = await get_current_exchange_rate(swapAddress); + + // + const rateElement = document.querySelector("#C"+swapAddress.asString()); + // TODO: Liquidity information + rateElement.innerHTML = `${RATE_DECIMALS} ${symbolA} = ${rateBsForA} ${symbolB} (Contract)`; +} + +function update_swap_contract_info(){ + for (const swapAddressStr in SWAPS) { + const swapAddress = BlockchainAddress.fromString(swapAddressStr); + get_contract_state(swapAddress, deserializeLiquiditySwapContractState).then(state => ui_update_swap(state, swapAddress)); + } +} + +async function get_token_state(tokenAddress: BlockchainAddress): Promise { + try { + return await get_contract_state(tokenAddress, deserializeTokenStateV1); + } catch { + // Pass + } + + return await get_contract_state(tokenAddress, deserializeTokenStateV2); +} function setup() { for (let router of ROUTERS) { @@ -52,16 +172,24 @@ function setup() { get_contract_state(router, deserializeRouterState).then((state) => { console.log(state); - for (let swapInfo of state.swapContracts) { - SWAPS[swapInfo.swapAddress.asString()] = { latest_state: null }; - TOKENS[swapInfo.tokenAAddress.asString()] = { latest_state: null }; - TOKENS[swapInfo.tokenBAddress.asString()] = { latest_state: null }; - - // TODO: Deduplicate tokens - get_contract_state(swapInfo.swapAddress, deserializeLiquiditySwapContractState).then(console.log); - get_contract_state(swapInfo.tokenAAddress, deserializeTokenState).then(console.log); - get_contract_state(swapInfo.tokenBAddress, deserializeTokenState).then(console.log); + for (const swapInfo of state.swapContracts) { + TOKENS[swapInfo.tokenAAddress.asString()] = { latest_state: null, swaps: null, type: null }; + TOKENS[swapInfo.tokenBAddress.asString()] = { latest_state: null, swaps: null, type: null }; + SWAPS[swapInfo.swapAddress.asString()] = { latest_state: null, swaps: [swapInfo.tokenAAddress, swapInfo.tokenBAddress], type: null }; + ui_add_swap(swapInfo.swapAddress); } + + for (const tokenAddressStr in TOKENS) { + const tokenAddress = BlockchainAddress.fromString(tokenAddressStr); + get_token_state(tokenAddress).then(state => { + TOKENS[tokenAddress.asString()].type = "token_v1"; + TOKENS[tokenAddress.asString()].latest_state = state; + ui_add_token(state, tokenAddress); + }); + } + + update_swap_contract_info(); + setInterval(update_swap_contract_info, 30*1000); }); } } diff --git a/src/main/swap/index.html b/src/main/swap/index.html index d212628..7cf2fff 100644 --- a/src/main/swap/index.html +++ b/src/main/swap/index.html @@ -47,6 +47,14 @@ transform: rotate(360deg); } } + + #token-list { + display: grid; + grid-template-columns: auto auto auto 1fr; + grid-gap: 1em; + max-width: 400px; + } +

Token Overview

- -
- -
-
- -
- -
-
- - - - - Owned - +
+
+
Symbol
+
Owned Amount
+
+ +

Exchange Rates

+