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

Get token list for self-sell task on Gnosis Chain #82

Merged
merged 2 commits into from
Apr 16, 2024
Merged
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
15 changes: 5 additions & 10 deletions src/tasks/selfSell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -970,16 +970,11 @@ const setupSelfSellTask: () => void = () =>
}

if (tokens == undefined || tokens.length === 0) {
if (chainId === 1) {
tokens = await getTokensWithBalanceAbove(
minValue,
settlementDeployment.address,
);
} else {
throw new Error(
"Automatic token list generation is only supported on mainnet",
);
}
tokens = await getTokensWithBalanceAbove({
chainId,
settlementContract: settlementDeployment.address,
minValueUsd: parseInt(minValue),
});
}
// Exclude the toToken if needed, as we can not sell it for itself (buyToken is not allowed to equal sellToken)
tokens = tokens.filter(
Expand Down
72 changes: 67 additions & 5 deletions src/tasks/withdraw/token_balances.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,35 @@
import axios from "axios";

interface AddressInfoResponse {
export interface GetTokensInput {
minValueUsd: number;
settlementContract: string;
chainId: number;
}

export async function getTokensWithBalanceAbove({
minValueUsd,
settlementContract,
chainId,
}: GetTokensInput): Promise<string[]> {
switch (chainId) {
case 1:
return await getMainnetTokensWithBalanceAbove(
minValueUsd,
settlementContract,
);
case 100:
return await getGnosisChainTokensWithBalanceAbove(
minValueUsd,
settlementContract,
);
default:
throw new Error(
`Automatic token list generation is not supported on chain with id ${chainId}`,
);
}
}

interface EthplorerAddressInfoResponse {
tokens: {
tokenInfo: {
address: string;
Expand All @@ -13,8 +42,8 @@ interface AddressInfoResponse {
}[];
}

export async function getTokensWithBalanceAbove(
minValueUsd: string,
export async function getMainnetTokensWithBalanceAbove(
minValueUsd: number,
settlementContract: string,
): Promise<string[]> {
const response = await axios.get(
Expand All @@ -24,14 +53,47 @@ export async function getTokensWithBalanceAbove(
throw new Error(`Error getting tokens from ETHplorer ${response}`);
}
const result = [];
const data = response.data as AddressInfoResponse;
const data = response.data as EthplorerAddressInfoResponse;
for (const token of data.tokens) {
const tokenUsdValue =
token.tokenInfo.price.rate *
(token.balance / Math.pow(10, token.tokenInfo.decimals));
if (tokenUsdValue > parseInt(minValueUsd)) {
if (tokenUsdValue > minValueUsd) {
result.push(token.tokenInfo.address);
}
}
return result;
}

type BlockscoutAddressInfoResponse = BlockscoutSingleTokenInfo[];
interface BlockscoutSingleTokenInfo {
token: {
address: string;
exchange_rate: string;
decimals: string;
};
value: string;
}

export async function getGnosisChainTokensWithBalanceAbove(
minValueUsd: number,
settlementContract: string,
): Promise<string[]> {
const response = await axios.get(
`https://gnosis.blockscout.com/api/v2/addresses/${settlementContract}/token-balances`,
);
if (response.status !== 200) {
throw new Error(`Error getting tokens from ETHplorer ${response}`);
fedgiac marked this conversation as resolved.
Show resolved Hide resolved
}
const result = [];
const data = response.data as BlockscoutAddressInfoResponse;
for (const { value, token } of data) {
const tokenUsdValue =
parseFloat(token.exchange_rate) *
(parseInt(value) / Math.pow(10, parseInt(token.decimals)));
Comment on lines +92 to +93
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parsing the value and 10**decimals can both overflow the safe integer number range so the numbers would get converted to floats resulting in rounding issues.
Could we use BigInts here instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I can avoid a conversion to float anywhere here. I don't know the precision of exchange_rate, so the best I can do is saying it's a float, which still offers some decent precision. Once this is a float, everything will need to be converted to float and so parsing as BigInt doesn't really help for precision. At first I tried to replace everything with Number(BigInt(value) / 10n ** BigInt(token.decimals)), which I realized was just going to create much worse issues!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. Unless we have something like a BigRational indeed the best option would be to use floats here. Doesn't look like ethers has something like that.
Also IIRC we end up getting quotes for all these tokens which should be more accurate anyway so the worst thing that could happen is that some of the tokens should get drained but doesn't. But then it will probably be drained the next time if the buffers keep accumulating that token.

if (tokenUsdValue > minValueUsd) {
result.push(token.address);
}
}
return result;
}
Loading