diff --git a/README.md b/README.md index 7679fd5..98c3ab7 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ Program Flow: * [X] Download token contract state to determine contract types and basic info. * [X] Download swap contract state to determine contract types. * [X] Continually download state of swap contracts, to ensure liquidity and pricing information is up to date. - * [ ] Construct swap graph. - Logging in: * [X] User presses relevant login button. @@ -19,7 +18,7 @@ Program Flow: - Trading (Write!): * [X] User selects tokens and amount to move between. - * [ ] Compute shortest route; dijkstra's. (Can use one, or several weight algorithms: Liquidity, price, etc.) + * [X] Compute shortest route; dijkstra's. (Can use one, or several weight algorithms: Liquidity, price, etc.) * [ ] Present route, fees, gas usage, etc. * [ ] User presses TRADE! * [ ] Sends transactions: _Approve to router_ and _route_ with the given route. diff --git a/package.json b/package.json index 899eee3..3cedae3 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,9 @@ "hash.js": "^1.1.7", "partisia-blockchain-applications-sdk": "^0.1.2", "stream-browserify": "^3.0.0", - "bip32-path": "^0.4.2" + "bip32-path": "^0.4.2", + "graphology": "^0.25.4", + "graphology-shortest-path": "2" }, "devDependencies": { "@babel/plugin-transform-runtime": "^7.19.6", diff --git a/src/main/swap/Main.ts b/src/main/swap/Main.ts index 892fb57..31ad8ea 100644 --- a/src/main/swap/Main.ts +++ b/src/main/swap/Main.ts @@ -19,6 +19,8 @@ import { NETWORK } from "../constant"; import { AvlClient } from "../client/AvlClient"; import {TokenV1Contract } from "../token/contract/TokenV1Contract"; import {TokenV2Contract } from "../token/contract/TokenV2Contract"; +import UndirectedGraph from 'graphology'; +import dijkstra from 'graphology-shortest-path/dijkstra'; // UI constants const TOKEN_LIST = document.querySelector("#token-list"); @@ -31,15 +33,22 @@ type ContractType = "token_v1" | "token_v2" | "swap_lock_v1" interface ContractState { address: BlockchainAddress, - latest_state: TokenStateV1 | TokenStateV2 | null; - swaps: BlockchainAddress[] | null, type: ContractType | null, + latest_state: TokenStateV1 | TokenStateV2 | null; +} + +interface TokenContractState extends ContractState { +} + +interface SwapContractState extends ContractState { + swapTokens: BlockchainAddress[], } interface Route { from: BlockchainAddress | null, to: BlockchainAddress | null, - swaps: BlockchainAddress[], + tokenContracts: string[], + swapContracts: string[], user: BlockchainAddress | null, } @@ -47,11 +56,11 @@ const ROUTERS: BlockchainAddress[] = [BlockchainAddress.fromString("02f8eb18e09d type BlockchainAddressKey = string; -const TOKENS: Record = {}; +const TOKENS: Record = {}; -const SWAPS: Record = {}; +const SWAPS: Record = {}; -const CURRENT_ROUTE: Route = { from: null, to: null, swaps: [], user: null }; +const CURRENT_ROUTE: Route = { from: null, to: null, tokenContracts: [], swapContracts: [], user: null }; 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);; @@ -78,7 +87,6 @@ function address_to_url(address: BlockchainAddress): string { } function setInnerText(query: string, text: string) { - console.log(query, text); for (const a of document.querySelectorAll(query)) { (a).innerText = text; } @@ -97,18 +105,43 @@ function ui_show_route() { setInnerText("[data-input-token]", symbolFrom); setInnerText("[data-output-token]", symbolTo); + + setInnerText("[data-route-list]", ""); + (document.querySelector("[data-route-list]")).innerHTML = CURRENT_ROUTE.swapContracts.map(s => `
${s}
`).join(""); +} + +function get_swap_graph(): UndirectedGraph { + const graph = new UndirectedGraph(); + for (const addrStr in TOKENS) { + graph.addNode(addrStr); + } + for (const addrStr in SWAPS) { + const swap = SWAPS[addrStr]; + graph.addEdgeWithKey(addrStr, swap.swapTokens[0].asString(), swap.swapTokens[1].asString(), { liquidity: 1000 }); + } + return graph; +} + +function node_path_to_edge_path(graph: UndirectedGraph, nodePath: string[]): string[] { + const edgeRoute = []; + for (let idx = 0; idx < nodePath.length - 1; idx++) { + edgeRoute.push(graph.edges(nodePath[idx], nodePath[idx+1])[0]); + } + + return edgeRoute; } function reroute() { - console.log("Rerouting", CURRENT_ROUTE); - // Do not route if from or to are not assigned yet. if (CURRENT_ROUTE.from == null || CURRENT_ROUTE.to == null) { return; } // Perform routing. - // TODO + const graph = get_swap_graph(); + + CURRENT_ROUTE.tokenContracts = dijkstra.bidirectional(graph, CURRENT_ROUTE.from.asString(), CURRENT_ROUTE.to.asString(), (_, attr) => attr.liquidity); + CURRENT_ROUTE.swapContracts = node_path_to_edge_path(graph, CURRENT_ROUTE.tokenContracts); ui_show_route(); } @@ -232,7 +265,6 @@ async function get_token_state(tokenAddress: BlockchainAddress): Promise<[TokenS } async function get_token_balance(info: ContractState, account: BlockchainAddress): Promise { - console.log(info); if (info.type == "token_v2") { return await new TokenV2Contract(SHARDED_CLIENT, undefined).tokenBalance(info.address, account); } else { @@ -263,14 +295,11 @@ function setup() { // Setup routes for (let router of ROUTERS) { - console.log(router); get_contract_state(router, deserializeRouterState).then((state) => { - console.log(state); - for (const swapInfo of state.swapContracts) { - TOKENS[swapInfo.tokenAAddress.asString()] = { latest_state: null, swaps: null, type: null, address: swapInfo.tokenAAddress }; - TOKENS[swapInfo.tokenBAddress.asString()] = { latest_state: null, swaps: null, type: null, address: swapInfo.tokenBAddress }; - SWAPS[swapInfo.swapAddress.asString()] = { latest_state: null, swaps: [swapInfo.tokenAAddress, swapInfo.tokenBAddress], type: null, address: swapInfo.swapAddress }; + TOKENS[swapInfo.tokenAAddress.asString()] = { latest_state: null, type: null, address: swapInfo.tokenAAddress }; + TOKENS[swapInfo.tokenBAddress.asString()] = { latest_state: null, type: null, address: swapInfo.tokenBAddress }; + SWAPS[swapInfo.swapAddress.asString()] = { latest_state: null, swapTokens: [swapInfo.tokenAAddress, swapInfo.tokenBAddress], type: null, address: swapInfo.swapAddress }; ui_add_swap(swapInfo.swapAddress); } diff --git a/src/main/swap/index.html b/src/main/swap/index.html index 9f1e7ba..3201db7 100644 --- a/src/main/swap/index.html +++ b/src/main/swap/index.html @@ -106,7 +106,7 @@ type="submit" value="Exchange!" /> -
+
1000 A to 3000 B (0.3% fee)
3000 B to 9000 C (0.3% fee)