Skip to content

Commit

Permalink
Merge pull request #8529 from DefiLlama/depositedvaults
Browse files Browse the repository at this point in the history
Deposited Vaults
  • Loading branch information
waynebruce0x authored Nov 11, 2024
2 parents 1ef582a + 5a18dd5 commit d2417ed
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 0 deletions.
8 changes: 8 additions & 0 deletions defi/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,14 @@ functions:
- http:
path: chain-assets/historical-flows/{chain}/{period}
method: get
depositedContracts:
handler: src/getDepositedContracts.default
timeout: 30
memorySize: 1000
events:
- http:
path: deposited-contracts//{addresses}/{threshold}
method: get

resources:
# CORS for api gateway errors
Expand Down
4 changes: 4 additions & 0 deletions defi/src/depositedContracts/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const explorers: { [id: number]: string } = {
1: "https://etherscan.io/tx/",
10: "https://optimistic.etherscan.io/tx/",
};
168 changes: 168 additions & 0 deletions defi/src/depositedContracts/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import PromisePool from "@supercharge/promise-pool";
import { CoinData, RawDeposits, ReadableDeposit } from "./types";
import { explorers } from "./constants";
import providers from "@defillama/sdk/build/providers.json";

const chainIdMap: { [id: number]: string } = {};
Object.keys(providers).map((c: string) => {
chainIdMap[providers[c as keyof typeof providers].chainId] = c;
});

async function get(
endpoint: string,
params?: { query?: { [param: string]: string }; body?: BodyInit; method?: "get" | "post" }
) {
const { query, body, method } = params ?? {};
let url = endpoint;
if (query) {
url += "?";
Object.keys(query).map((p) => (url += `${p}=${query[p]}&`));
}
const res = await fetch(url, { method, body });
return await res.json();
}
export async function fetchTransfers(addresses: string): Promise<{ [chain: string]: any[] }> {
const allTransfers: { [chain: string]: any[] } = {};
await PromisePool.withConcurrency(5)
.for(Object.keys(explorers))
.process(async (chainId) => {
const chainSlug: string = chainIdMap[chainId as any];
let offset: number = 0;
allTransfers[chainSlug] = [];

let transfers: any = { length: 1000 };
while (transfers.length == 1000) {
console.time(`transfer ${chainId}`);
transfers = (
await get(`https://peluche.llamao.fi/token-transfers`, {
query: {
chainId: `${chainId}`,
addresses,
from_address: "true",
to_address: "true",
limit: "1000",
offset: `${offset}`,
},
})
).transfers;

console.timeEnd(`transfer ${chainId}`);
allTransfers[chainSlug].push(...transfers);
offset += 1000;
}
});

return allTransfers;
}
export async function filterDeposits(
allTransfers: { [chain: string]: any[] },
addresses: string[]
): Promise<RawDeposits> {
let allRawDeposits: RawDeposits = {};
await Promise.all(
Object.keys(allTransfers).map(async (chain: string) => {
let rawDeposits: RawDeposits = {};
allTransfers[chain].map((t: any) => {
const { to_address: to, from_address: from } = t;
if (addresses.includes(from) && !(to in rawDeposits)) rawDeposits[to] = t;
else if (addresses.includes(to) && from in rawDeposits) delete rawDeposits[from];
});

let rpcIndex: number = 0;
await PromisePool.withConcurrency(5)
.for(Object.keys(rawDeposits))
.process(async (target) => {
async function rpcCall(index: number) {
const rpc = providers[chain as keyof typeof providers].rpc[index];
const res = await get(rpc, {
method: "post",
body: JSON.stringify({
method: "eth_getCode",
params: [target, "latest"],
id: 1,
jsonrpc: "2.0",
}),
});
if (res.result != "0x") {
allRawDeposits[`${chain}:${target}`] = rawDeposits[target];
}
}

try {
await rpcCall(rpcIndex);
} catch (e) {
try {
rpcIndex += 1;
await rpcCall(rpcIndex);
} catch (e) {
throw new Error(`rpcs failed with ${e}`);
}
}
});
})
);

return allRawDeposits;
}
export async function fetchPrices(deposits: RawDeposits): Promise<{ [key: string]: CoinData }> {
const burl = "https://coins.llama.fi/prices/current/";
const queries = [];
let query = burl;

const priceQueries: string[] = [
...new Set(Object.values(deposits).map((f: any) => `${chainIdMap[f.chain]}:${f.token}`)),
];

for (const key of priceQueries) {
if (query.length + key.length > 1900) {
queries.push(`${query.slice(0, -1)}?apikey=${process.env.COINS_KEY}`);
query = burl;
}
query += `${key},`;
}

const responses: { [key: string]: CoinData }[] = [];
queries.push(query.slice(0, -1));
const { errors } = await PromisePool.withConcurrency(5)
.for(queries)
.process(async (query) => {
responses.push((await get(query)).coins);
});

if (errors.length) throw new Error(errors[0].message);

let coinData: { [key: string]: CoinData } = {};
responses.map((r: { [key: string]: CoinData }) => {
coinData = { ...coinData, ...r };
});

return coinData;
}
export function parseDeposits(
rawDeposits: RawDeposits,
coinsData: { [key: string]: CoinData },
threshold: number = 1
): ReadableDeposit[] {
const readableDeposits: ReadableDeposit[] = [];
Object.keys(rawDeposits).map((d: string) => {
const { token, value, chain: chainId, timestamp, transaction_hash } = rawDeposits[d];
const chain = chainIdMap[chainId];
const key = `${chain}:${token}`;

const coinData: CoinData = coinsData[key];
if (!coinData) return;

const usdValue = (coinData.price * value) / 10 ** coinData.decimals;
if (usdValue < threshold) return;

readableDeposits.push({
usdValue: usdValue.toFixed(0),
symbol: coinData.symbol.toUpperCase(),
chain,
timestamp,
url: `${explorers[chainId]}${transaction_hash}`,
});
});

return readableDeposits;
}
21 changes: 21 additions & 0 deletions defi/src/depositedContracts/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export type RawDeposits = {
[from: string]: {
timestamp: string;
token: string;
chain: number;
value: number;
transaction_hash: string;
};
};
export type ReadableDeposit = {
timestamp: string;
symbol: string;
chain: string;
usdValue: string;
url: string;
};
export type CoinData = {
price: number;
decimals: number;
symbol: string;
};
45 changes: 45 additions & 0 deletions defi/src/getDepositedContracts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { CoinData, ReadableDeposit } from "./depositedContracts/types";
import { fetchPrices, fetchTransfers, filterDeposits, parseDeposits } from "./depositedContracts/helpers";
import { wrap, IResponse, successResponse, errorResponse } from "./utils/shared";
import setEnvSecrets from "./utils/shared/setEnvSecrets";

const handler = async (event: AWSLambda.APIGatewayEvent): Promise<IResponse> => {
if (!event.pathParameters)
return errorResponse({
message: "please supply at least one wallet address",
});

const { addresses, threshold } = event.pathParameters;
if (!addresses)
return errorResponse({
message: "please supply at least one wallet address",
});

if (threshold && isNaN(Number(threshold)))
return errorResponse({
message: "threshold must be a number or undefined",
});

try {
await setEnvSecrets();
console.time("total");
const noramlisedAddresses = addresses.toLowerCase();

console.time("transfers");
const allTransfers: { [chain: string]: any[] } = await fetchTransfers(noramlisedAddresses);
console.timeEnd("transfers");

const deposits = await filterDeposits(allTransfers, noramlisedAddresses.split(","));
const coinsData: { [key: string]: CoinData } = await fetchPrices(deposits);
const userData: ReadableDeposit[] = parseDeposits(deposits, coinsData, Number(threshold));
console.timeEnd("total");

return successResponse({ data: userData }, 3600);
} catch (e) {
return errorResponse({
message: `${e}`,
});
}
};

export default wrap(handler); // ts-node defi/src/getDepositedContracts.ts

0 comments on commit d2417ed

Please sign in to comment.