Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

token pair support router #3397

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import type { BalanceInfoView, AnnotatedMoveStructView } from '@roochnetwork/rooch-sdk';

import { useState, useEffect } from 'react';
import {
useRoochClient,
useCurrentAddress,
useRoochClientQuery,
} from '@roochnetwork/rooch-sdk-kit';

import { useNetworkVariable } from 'src/hooks/use-networks';
import { TokenGraph } from "../util/token-graph";

type TokenPairType = {
x: BalanceInfoView;
y: BalanceInfoView[];
};

export function useTokenPairRouter() {
const client = useRoochClient();
const currentAddress = useCurrentAddress();
const dex = useNetworkVariable('dex');
const [tokenGraph, setTokenGraph] = useState<TokenGraph>(new TokenGraph());
const [tokenPairsMap, setTokenPairsMap] = useState<Map<string, TokenPairType>>(new Map());
const [tokenInfo, setTokenInfo] = useState<Map<string, TokenPairType>>(new Map());

const { data: tokenPairs, isPending } = useRoochClientQuery('queryObjectStates', {
filter: {
object_type: `${dex.address}::swap::TokenPair`,
},
limit: '200',
});

useEffect(() => {
if (!tokenPairs || !client || !currentAddress) {
return;
}

const parseType = (coin: AnnotatedMoveStructView) => {
const xType = coin.type.replace('0x2::object::Object<0x3::coin_store::CoinStore<', '');
return xType.replace('>>', '');
};

const fetchInfo = async () => {
console.log('fetchInfo');
const infos = tokenPairs.data?.map(async (item) => {
const xView = item.decoded_value!.value.balance_x as AnnotatedMoveStructView;
const xType = parseType(xView);
const yView = item.decoded_value!.value.balance_y as AnnotatedMoveStructView;
const yType = parseType(yView);

const [xResult, yResult] = await Promise.all([
client.getBalance({ owner: currentAddress!.toStr(), coinType: xType }),
client.getBalance({ owner: currentAddress!.toStr(), coinType: yType }),
])

return {
x: xResult,
y: yResult,
}
})

await Promise.all(infos).then((result) => {
const _pairMap = new Map<string, BalanceInfoView>();
const _tokenGraph = new TokenGraph()
const _tokenPairsMap = new Map<string, TokenPairType>();

result.forEach((item) => {
_tokenGraph.addPair([item.x.coin_type, item.y.coin_type]);
_pairMap.set(item.x.coin_type, item.x);
_pairMap.set(item.y.coin_type, item.y);
});

// skip path > 2
const allPairs = _tokenGraph.findAllPairs().filter((item) => item.length <= 4)

allPairs.forEach((item) => {
// insert
const x = _pairMap.get(item[0])!;
const y = _pairMap.get(item[1])!;
if (!_tokenPairsMap.has(x.coin_type)) {
_tokenPairsMap.set(x.coin_type, {
x,
y:[y],
});
} else {
_tokenPairsMap.get(x.coin_type)?.y.push(y);
}

// reverse
const key1 = y.coin_type;
if (!_tokenPairsMap.has(key1)) {
_tokenPairsMap.set(key1, {
x: y,
y: [x],
});
} else {
_tokenPairsMap.get(key1)?.y.push(x);
}
})

setTokenInfo(_tokenPairsMap);
setTokenGraph(_tokenGraph);
setTokenPairsMap(_tokenPairsMap);
});
}

fetchInfo();
}, [tokenPairs, client, currentAddress, setTokenGraph, setTokenInfo, setTokenPairsMap]);

return {
tokenGraph,
tokenPairsMap,
tokenInfo,
isPending: isPending && tokenInfo.size > 0,
};
}
24 changes: 16 additions & 8 deletions infra/rooch-portal-v2/src/sections/trade/swap-v2/view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { toast } from 'src/components/snackbar';

import SwapConfirmModal from './confirm-modal';
import { useTokenPair } from '../hooks/use-token-pair';
import { useTokenPairRouter } from "../hooks/use-token-pair-router";

const normalizeCoinIconUrl = (onChainCoinIconUrl?: string | null) =>
`data:image/svg+xml;utf8,${encodeURIComponent(onChainCoinIconUrl || '')}`;
Expand All @@ -42,39 +43,46 @@ export default function SwapView() {

const { tokenPairs } = useTokenPair();


const { tokenPairsMap, tokenGraph } = useTokenPairRouter();

tokenGraph.findAllPairs()
console.log(tokenPairs)
console.log(tokenPairsMap)

const availableFromCoins = useMemo(
() =>
Array.from(tokenPairs.values()).map((i) => ({
Array.from(tokenPairsMap.values()).map((i) => ({
...i.x,
icon_url: normalizeCoinIconUrl(i.x.icon_url),
amount: '0',
})),
[tokenPairs]
[tokenPairsMap]
);

const availableToCoins = useMemo(() => {
if (!tokenPairs) {
if (!tokenPairsMap) {
return [];
}
if (!fromCoinType) {
return Array.from(tokenPairs.values()).map((i) => ({
return Array.from(tokenPairsMap.values()).map((i) => ({
...i.x,
icon_url: normalizeCoinIconUrl(i.x.icon_url),
amount: '0',
}));
}
return (
tokenPairs.get(fromCoinType as string)?.y.map((i) => ({
tokenPairsMap.get(fromCoinType as string)?.y.map((i) => ({
...i,
icon_url: normalizeCoinIconUrl(i.icon_url),
amount: '0',
})) || []
);
}, [fromCoinType, tokenPairs]);
}, [fromCoinType, tokenPairsMap]);

const fromCoinInfo = useMemo(
() => tokenPairs.get(fromCoinType as string)?.x,
[tokenPairs, fromCoinType]
() => tokenPairsMap.get(fromCoinType as string)?.x,
[tokenPairsMap, fromCoinType]
);

const toCoinInfo = useMemo(
Expand Down
94 changes: 94 additions & 0 deletions infra/rooch-portal-v2/src/sections/trade/util/token-graph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
export class TokenGraph {
private adjacencyList: Map<string, Set<string>>;

constructor() {
this.adjacencyList = new Map();
}

addPair(pair: [string, string]): void {
const [currency1, currency2] = pair;

if (!this.adjacencyList.has(currency1)) {
this.adjacencyList.set(currency1, new Set());
}
if (!this.adjacencyList.has(currency2)) {
this.adjacencyList.set(currency2, new Set());
}

this.adjacencyList.get(currency1)!.add(currency2);
this.adjacencyList.get(currency2)!.add(currency1);
}

findAllPairs(): [string, string][] {
const allCurrencies = Array.from(this.adjacencyList.keys());
const pairs: Set<string> = new Set();

for (let i = 0; i < allCurrencies.length; i+=1) {
for (let j = i + 1; j < allCurrencies.length; j+=1) {
const currency1 = allCurrencies[i];
const currency2 = allCurrencies[j];

if (this.hasPath(currency1, currency2)) {
const pair = [currency1, currency2].sort().join('-');
pairs.add(pair);
}
}
}

return Array.from(pairs).map(pair => pair.split('-') as [string, string]);
}

findPath(start: string, end: string): string[] | null {
if (!this.adjacencyList.has(start) || !this.adjacencyList.has(end)) {
return null;
}

const queue: string[][] = [[start]];
const visited: Set<string> = new Set();

while (queue.length > 0) {
const path = queue.shift()!;
const node = path[path.length - 1];

if (node === end) {
return path;
}

if (!visited.has(node)) {
visited.add(node);
const neighbors = this.adjacencyList.get(node)!;

neighbors.forEach((neighbor) => {
const newPath = [...path, neighbor];
queue.push(newPath);
})
}
}

return null; // No path found
}

private hasPath(start: string, end: string): boolean {
const visited = new Set<string>();
const queue: string[] = [start];

while (queue.length > 0) {
const node = queue.shift()!;
if (node === end) {
return true;
}

if (!visited.has(node)) {
visited.add(node);
const neighbors = this.adjacencyList.get(node) || [];
neighbors.forEach((neighbor) => {
if (!visited.has(neighbor)) {
queue.push(neighbor);
}
})
}
}

return false;
}
}
Loading