-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: required api endpoints for dodo (#740)
* feat(vercel): helper endpoint for building deposit tx * feat(vercel): return spokePoolAddress in suggested-fees endpoint * feat(vercel): helper endpoint that returns enabled tokens * fix: use computedOriginChainId * perf: increase token list cache age * perf: cache build tx response * docs: add comment on not flooring quoteTimestamp * adjust caching * feat: allow ens name as referrer * fix: native build tx endpoint
- Loading branch information
Showing
4 changed files
with
260 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { VercelResponse } from "@vercel/node"; | ||
import { ethers } from "ethers"; | ||
import { type, assert, Infer, optional, string } from "superstruct"; | ||
import { TypedVercelRequest } from "./_types"; | ||
import { | ||
getLogger, | ||
InputError, | ||
handleErrorCondition, | ||
parsableBigNumberString, | ||
validAddress, | ||
positiveIntStr, | ||
boolStr, | ||
getSpokePool, | ||
tagReferrer, | ||
validAddressOrENS, | ||
} from "./_utils"; | ||
|
||
const BuildDepositTxQueryParamsSchema = type({ | ||
amount: parsableBigNumberString(), | ||
token: validAddress(), | ||
destinationChainId: positiveIntStr(), | ||
originChainId: positiveIntStr(), | ||
recipient: validAddress(), | ||
relayerFeePct: parsableBigNumberString(), | ||
quoteTimestamp: positiveIntStr(), | ||
message: optional(string()), | ||
maxCount: optional(boolStr()), | ||
referrer: optional(validAddressOrENS()), | ||
isNative: optional(boolStr()), | ||
}); | ||
|
||
type BuildDepositTxQueryParams = Infer<typeof BuildDepositTxQueryParamsSchema>; | ||
|
||
const handler = async ( | ||
{ query }: TypedVercelRequest<BuildDepositTxQueryParams>, | ||
response: VercelResponse | ||
) => { | ||
const logger = getLogger(); | ||
logger.debug({ | ||
at: "BuildDepositTx", | ||
message: "Query data", | ||
query, | ||
}); | ||
try { | ||
assert(query, BuildDepositTxQueryParamsSchema); | ||
|
||
let { | ||
amount: amountInput, | ||
token, | ||
destinationChainId: destinationChainIdInput, | ||
originChainId: originChainIdInput, | ||
recipient, | ||
relayerFeePct: relayerFeePctInput, | ||
// Note, that the value of `quoteTimestamp` query param needs to be taken directly as returned by the | ||
// `GET /api/suggested-fees` endpoint. This is why we don't floor the timestamp value here. | ||
quoteTimestamp, | ||
message = "0x", | ||
maxCount = ethers.constants.MaxUint256.toString(), | ||
referrer, | ||
isNative: isNativeBoolStr, | ||
} = query; | ||
|
||
recipient = ethers.utils.getAddress(recipient); | ||
token = ethers.utils.getAddress(token); | ||
const destinationChainId = parseInt(destinationChainIdInput); | ||
const originChainId = parseInt(originChainIdInput); | ||
const amount = ethers.BigNumber.from(amountInput); | ||
const relayerFeePct = ethers.BigNumber.from(relayerFeePctInput); | ||
const isNative = isNativeBoolStr === "true"; | ||
|
||
if (originChainId === destinationChainId) { | ||
throw new InputError("Origin and destination chains cannot be the same"); | ||
} | ||
|
||
const spokePool = getSpokePool(originChainId); | ||
|
||
const value = isNative ? amount : ethers.constants.Zero; | ||
const tx = await spokePool.populateTransaction.deposit( | ||
recipient, | ||
token, | ||
amount, | ||
destinationChainId, | ||
relayerFeePct, | ||
quoteTimestamp, | ||
message, | ||
maxCount, | ||
{ value } | ||
); | ||
|
||
// do not tag a referrer if data is not provided as a hex string. | ||
tx.data = referrer ? await tagReferrer(tx.data!, referrer) : tx.data; | ||
|
||
const responseJson = { | ||
data: tx.data, | ||
value: tx.value?.toString(), | ||
}; | ||
|
||
// Two different explanations for how `stale-while-revalidate` works: | ||
|
||
// https://vercel.com/docs/concepts/edge-network/caching#stale-while-revalidate | ||
// This tells our CDN the value is fresh for 10 seconds. If a request is repeated within the next 10 seconds, | ||
// the previously cached value is still fresh. The header x-vercel-cache present in the response will show the | ||
// value HIT. If the request is repeated between 1 and 20 seconds later, the cached value will be stale but | ||
// still render. In the background, a revalidation request will be made to populate the cache with a fresh value. | ||
// x-vercel-cache will have the value STALE until the cache is refreshed. | ||
|
||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control | ||
// The response is fresh for 150s. After 150s it becomes stale, but the cache is allowed to reuse it | ||
// for any requests that are made in the following 150s, provided that they revalidate the response in the background. | ||
// Revalidation will make the cache be fresh again, so it appears to clients that it was always fresh during | ||
// that period — effectively hiding the latency penalty of revalidation from them. | ||
// If no request happened during that period, the cache became stale and the next request will revalidate normally. | ||
logger.debug({ | ||
at: "BuildDepositTx", | ||
message: "Response data", | ||
responseJson, | ||
}); | ||
response.setHeader( | ||
"Cache-Control", | ||
"s-maxage=150, stale-while-revalidate=150" | ||
); | ||
response.status(200).json(responseJson); | ||
} catch (error) { | ||
return handleErrorCondition("build-deposit-tx", response, logger, error); | ||
} | ||
}; | ||
|
||
export default handler; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { VercelResponse } from "@vercel/node"; | ||
import { constants } from "@across-protocol/sdk-v2"; | ||
import { | ||
getLogger, | ||
handleErrorCondition, | ||
getFallbackTokenLogoURI, | ||
ENABLED_ROUTES, | ||
} from "./_utils"; | ||
import { TypedVercelRequest } from "./_types"; | ||
|
||
const handler = async (_: TypedVercelRequest<{}>, response: VercelResponse) => { | ||
const logger = getLogger(); | ||
|
||
try { | ||
const tokensPerChain = ENABLED_ROUTES.routes.reduce( | ||
(acc, route) => { | ||
return { | ||
...acc, | ||
[`${route.fromTokenSymbol}-${route.fromChain}`]: { | ||
symbol: route.fromTokenSymbol, | ||
chainId: route.fromChain, | ||
address: route.fromTokenAddress, | ||
isNative: route.isNative, | ||
l1TokenAddress: route.l1TokenAddress, | ||
}, | ||
}; | ||
}, | ||
{} as Record< | ||
string, | ||
{ | ||
symbol: string; | ||
chainId: number; | ||
address: string; | ||
isNative: boolean; | ||
l1TokenAddress: string; | ||
} | ||
> | ||
); | ||
|
||
const enrichedTokensPerChain = Object.values(tokensPerChain).map( | ||
(token) => { | ||
const tokenInfo = | ||
constants.TOKEN_SYMBOLS_MAP[ | ||
token.symbol as keyof typeof constants.TOKEN_SYMBOLS_MAP | ||
]; | ||
return { | ||
...token, | ||
name: tokenInfo.name, | ||
decimals: tokenInfo.decimals, | ||
logoURI: getFallbackTokenLogoURI(token.l1TokenAddress), | ||
}; | ||
} | ||
); | ||
|
||
// Instruct Vercel to cache limit data for this token for 6 hours. Caching can be used to limit number of | ||
// Vercel invocations and run time for this serverless function and trades off potential inaccuracy in times of | ||
// high volume. "max-age=0" instructs browsers not to cache, while s-maxage instructs Vercel edge caching | ||
// to cache the responses and invalidate when deployments update. | ||
logger.debug({ | ||
at: "TokenList", | ||
message: "Response data", | ||
responseJson: enrichedTokensPerChain, | ||
}); | ||
response.setHeader("Cache-Control", "s-maxage=21600"); | ||
response.status(200).json(enrichedTokensPerChain); | ||
} catch (error: unknown) { | ||
return handleErrorCondition("token-list", response, logger, error); | ||
} | ||
}; | ||
|
||
export default handler; |
8583d3a
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
goerli-frontend-v2 – ./
goerli-frontend-v2.vercel.app
goerli-frontend-v2-uma.vercel.app
goerli-frontend-v2-git-master-uma.vercel.app
8583d3a
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
frontend-v2 – ./
frontend-v2-seven.vercel.app
frontend-v2-uma.vercel.app
v2.across.to
across.to
frontend-v2-git-master-uma.vercel.app