Skip to content

Commit

Permalink
feat: add infinex adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
sneedinfinex committed Jul 29, 2024
1 parent 8670651 commit 3e67333
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 0 deletions.
20 changes: 20 additions & 0 deletions projects/infinex/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const http = require("../helper/http");

const INFINEX_API_BASE_URL = "https://api.app.infinex.xyz"

async function getAccountAddresses() {
return http.get(`${INFINEX_API_BASE_URL}/public/accounts`);
}

async function getChainAssets(chain) {
const assetsJson = await http.get(`${INFINEX_API_BASE_URL}/public/chainAssets`);

return assetsJson
.filter(i => i.chain === chain && i.address !== 'native')
.map(v => v.address);
}

module.exports = {
getAccountAddresses,
getChainAssets
}
39 changes: 39 additions & 0 deletions projects/infinex/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const { sumTokens2: sumTokens2Evm } = require('../helper/unwrapLPs')
const { sumTokens2Batched: sumTokens2Solana } = require('./solana');
const { getAccountAddresses, getChainAssets } = require('./api');

async function tvlEvm(api) {
const tokens = await getChainAssets(api.chain);
const owners = (await getAccountAddresses()).map(address => address.evm).filter(address => !!address);

return sumTokens2Evm({ owners, tokens, api, sumChunkSize: 30000 });
}

async function tvlSolana(api) {
const tokens = await getChainAssets(api.chain);
const owners = (await getAccountAddresses()).map(address => address.solana).filter(address => !!address);

return sumTokens2Solana({ owners, tokens });
}

module.exports = {
methodology: `Sums native + ERC-20 + SPL token balances of all Infinex accounts`,
base: {
tvl: tvlEvm
},
arbitrum: {
tvl: tvlEvm
},
optimism: {
tvl: tvlEvm
},
polygon: {
tvl: tvlEvm
},
ethereum: {
tvl: tvlEvm
},
solana: {
tvl: tvlSolana
},
}
141 changes: 141 additions & 0 deletions projects/infinex/solana.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
const ADDRESSES = require('../helper/coreAssets.json')
const axios = require("axios");
const { sleep } = require('../helper/utils');
const { blacklistedTokens_default, endpoint, transformBalances } = require('../helper/solana');
const { log } = require('@defillama/sdk');
const { sliceIntoChunks } = require('@defillama/sdk/build/util');
const { PublicKey } = require('@solana/web3.js');

// Address of the SPL Token program
const TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
const TOKEN_PROGRAM_ID_BUFFER = TOKEN_PROGRAM_ID.toBuffer();

// Address of the SPL Associated Token Account program
const ASSOCIATED_TOKEN_PROGRAM_ID = new PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL');

async function sumTokens2Batched(
{ tokens, owners }
) {
log("Calculating solana token accounts...")
const tokenAccountsWithMints = tokens
.filter((token) => !blacklistedTokens_default.includes(token))
// this is synchronous and blocks propotional to the number of accounts
.flatMap(token => owners.map(owner => [token, getAssociatedTokenAccountAddress(owner, token)]))
const tokenAccounts = tokenAccountsWithMints.map(([_, tokenAccount]) => tokenAccount);

const solBalances = await getSolBalances(owners);
const splBalances = await getTokenAccountBalances(tokenAccounts);

const solTokenBalances = solBalances.flatMap(balance => balance ? [[ADDRESSES.solana.SOL, balance]] : []);
const splTokenBalances = splBalances.flatMap((balance, idx) => balance ? [[tokenAccountsWithMints[idx][0], balance]] : [])

const tokenBalances = [...solTokenBalances, ...splTokenBalances].reduce((acc, [mint, value]) => {
return (value ?? 0n) > 0n
? {
...acc,
[mint]: (acc[mint] ?? 0n) + value
}
: acc
}, {});

return transformBalances({ tokenBalances });
}

async function getTokenAccountBalances(
tokenAccountAddresses
) {
const data = await getMultipleAccountsBatched(
tokenAccountAddresses,
// get _just_ the account balance
{ length: 64, offset: 64 }
);

return data
.map(value => value ? BigInt(value.data.parsed.info.tokenAmount.amount) : null)
}

async function getSolBalances(
owners
) {
const data = await getMultipleAccountsBatched(
owners,
);

return data.map(value => value ? BigInt(value.lamports) : null);
}

function getAssociatedTokenAccountAddress(
owner,
token
) {
return PublicKey.findProgramAddressSync(
[
new PublicKey(owner).toBuffer(),
TOKEN_PROGRAM_ID_BUFFER,
new PublicKey(token).toBuffer(),
],
ASSOCIATED_TOKEN_PROGRAM_ID
)[0].toBase58();
}

async function getMultipleAccountsBatched(
addresses,
dataSlice
) {
// this lets us query for 100 * 50 * 1 (5000k) accounts at a time
const rpcMethodChunks = sliceIntoChunks(addresses, 100);
const httpRequestChunks = sliceIntoChunks(rpcMethodChunks, 50);
const promiseChunks = sliceIntoChunks(httpRequestChunks, 1);

const data = [];
for (const promiseChunk of promiseChunks) {
const result = (
await Promise.all(
promiseChunk.map(httpRequestChunk =>
solanaRpcRetry({
body: httpRequestChunk.map(rpcMethodChunk => ({
jsonrpc: "2.0",
id: 1,
method: "getMultipleAccounts",
params: [rpcMethodChunk, {
dataSlice,
encoding: "jsonParsed"
}],
}))
})
)
)).flat();

result
.forEach(item => data.push(...item.result.value))

await sleep(500);
}

return data;
}

async function solanaRpcRetry(
{
body,
maxRetries = 5,
retryCount = 0,
}
) {
try {
const response = await axios.post(endpoint, body);
return response.data;
} catch (err) {
if (err?.response?.status === 429 && retryCount < maxRetries) {
log(`Hit Solana RPC rate limit. Retrying...`);
await sleep(2 ** retryCount * 1000);
return solanaRpcRetry({ body, maxRetries, retryCount: retryCount + 1 });
}

throw err;
}
}

module.exports = {
sumTokens2Batched
}

0 comments on commit 3e67333

Please sign in to comment.