Skip to content

Commit

Permalink
improve: add token caching
Browse files Browse the repository at this point in the history
  • Loading branch information
james-a-morris committed Oct 23, 2023
1 parent a8e7c84 commit 703312b
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 13 deletions.
52 changes: 46 additions & 6 deletions api/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,23 +611,63 @@ export const isRouteEnabled = (
};

/**
* Resolves the balance of a given ERC20 token at a provided address
* Resolves the balance of a given ERC20 token at a provided address. If no token is provided, the balance of the
* native currency will be returned.
* @param chainId The blockchain Id to query against
* @param token The valid ERC20 token address on the given `chainId`
* @param token The valid ERC20 token address on the given `chainId`. If undefined, the native currency will be used
* @param account A valid Web3 wallet address
* @param blockTag A blockTag to specify a historical balance date
* @param blockTag A blockTag to specify a historical balance date or the latest balance
* @returns A promise that resolves to the BigNumber of the balance
*/
export const getBalance = (
chainId: string | number,
token: string,
account: string,
token?: string,
blockTag: number | "latest" = "latest"
): Promise<BigNumber> => {
return ERC20__factory.connect(token, getProvider(Number(chainId))).balanceOf(
const provider = getProvider(Number(chainId));
if (sdk.utils.isDefined(token)) {
return sdk.utils.getTokenBalance(account, token, provider, blockTag);
} else {
return provider.getBalance(account, blockTag);
}
};

/**
* Resolves the cached balance of a given ERC20 token at a provided address. If no token is provided, the balance of the
* native currency will be returned.
* @param chainId The blockchain Id to query against
* @param token The valid ERC20 token address on the given `chainId`. If undefined, the native currency will be used
* @param account A valid Web3 wallet address
* @param blockTag A blockTag to specify a historical balance date or the latest balance
* @returns A promise that resolves to the BigNumber of the balance
*/
export const getCachedTokenBalance = async (
chainId: string | number,
account: string,
token?: string,
blockTag: number | "latest" = "latest"
): Promise<BigNumber> => {
// Define the initial params
const params: Record<string, unknown> = {
chainId,
account,
{ blockTag }
};
if (token) {
params["token"] = token;
}
if (blockTag !== "latest") {
params["blockTag"] = blockTag;
}
// Make the request
const response = await axios.get<{ balance: string }>(
`${resolveVercelEndpoint()}/api/account-balance`,
{
params,
}
);
// Return the balance
return BigNumber.from(response.data.balance);
};

/**
Expand Down
70 changes: 70 additions & 0 deletions api/account-balance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { VercelResponse } from "@vercel/node";
import { assert, Infer, optional, type, min, integer } from "superstruct";
import { TypedVercelRequest } from "./_types";
import {
getBalance,
getLogger,
handleErrorCondition,
validAddress,
} from "./_utils";

const AccountBalanceQueryParamsSchema = type({
token: optional(validAddress()),
account: validAddress(),
chainId: min(integer(), 1),
blockTag: optional(min(integer(), 0)),
});

type AccountBalanceQueryParams = Infer<typeof AccountBalanceQueryParamsSchema>;

const handler = async (
{ query }: TypedVercelRequest<AccountBalanceQueryParams>,
response: VercelResponse
) => {
const logger = getLogger();
logger.debug({
at: "AccountBalance",
message: "Query data",
query,
});
try {
// Validate the query parameters
assert(query, AccountBalanceQueryParamsSchema);
// Deconstruct the query parameters
let { token, account, chainId, blockTag } = query;
// Rely on the utils to query the balance of either the native
// token or an ERC20 token.
const balance = await getBalance(chainId, account, token, blockTag);
// Package the response
const result = {
balance: balance.toString(),
account: account,
token: token,
isNative: token === undefined,
tag: blockTag,
};
// Determine the age of the caching of the response
// 1. If the blockTag is specified, then we can cache for 5 minutes (300 seconds)
// 2. If the blockTag is not specified, the "latest" block is used, and we should
// only cache for 10 seconds.
const cachingTime = blockTag ? 300 : 10;
// Log the response
logger.debug({
at: "AccountBalance",
message: "Response data",
responseJson: result,
secondsCached: cachingTime,
});
// Set the caching headers that will be used by the CDN.
response.setHeader(
"Cache-Control",
`s-maxage=${cachingTime}, stale-while-revalidate=${cachingTime}}`
);
// Return the response
response.status(200).json(result);
} catch (error: unknown) {
return handleErrorCondition("account-balance", response, logger, error);
}
};

export default handler;
8 changes: 4 additions & 4 deletions api/limits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
getRelayerFeeDetails,
getCachedTokenPrice,
getTokenDetails,
getBalance,
getCachedTokenBalance,
maxBN,
minBN,
isRouteEnabled,
Expand Down Expand Up @@ -156,7 +156,7 @@ const handler = async (
hubPool.callStatic.multicall(multicallInput, { blockTag: BLOCK_TAG_LAG }),
Promise.all(
fullRelayers.map((relayer) =>
getBalance(
getCachedTokenBalance(
destinationChainId!,
destinationToken,
relayer,
Expand All @@ -166,7 +166,7 @@ const handler = async (
),
Promise.all(
transferRestrictedRelayers.map((relayer) =>
getBalance(
getCachedTokenBalance(
destinationChainId!,
destinationToken,
relayer,
Expand All @@ -178,7 +178,7 @@ const handler = async (
fullRelayers.map((relayer) =>
destinationChainId === "1"
? ethers.BigNumber.from("0")
: getBalance("1", l1Token, relayer, BLOCK_TAG_LAG)
: getCachedTokenBalance("1", l1Token, relayer, BLOCK_TAG_LAG)
)
),
]);
Expand Down
7 changes: 4 additions & 3 deletions api/suggested-fees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
HUB_POOL_CHAIN_ID,
ENABLED_ROUTES,
getSpokePoolAddress,
getCachedTokenBalance,
} from "./_utils";

const SuggestedFeesQueryParamsSchema = type({
Expand Down Expand Up @@ -115,10 +116,10 @@ const handler = async (
`Could not resolve token address on ${destinationChainId} for ${l1Token}`
);
}
const balanceOfToken = await sdk.utils.getTokenBalance(
relayerAddress,
const balanceOfToken = await getCachedTokenBalance(
destinationChainId,
destinationToken,
provider
relayerAddress
);
if (balanceOfToken.lt(amountInput)) {
throw new InputError(
Expand Down

0 comments on commit 703312b

Please sign in to comment.