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

feat: gelato and local signers #1346

Merged
merged 19 commits into from
Dec 30, 2024
7 changes: 7 additions & 0 deletions api/_cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ export class RedisCache implements interfaces.CachingMechanismInterface {
throw error;
}
}

async del(key: string) {
if (!this.client) {
return;
}
await this.client.del(key);
}
}

export const redisCache = new RedisCache();
Expand Down
55 changes: 50 additions & 5 deletions api/_dexes/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BigNumber, constants } from "ethers";
import { BigNumber, BigNumberish, constants } from "ethers";
import { utils } from "@across-protocol/sdk";
import { SpokePool } from "@across-protocol/contracts/dist/typechain";

Expand All @@ -24,7 +24,9 @@ import {
isOutputTokenBridgeable,
getSpokePool,
} from "../_utils";

import { GAS_SPONSOR_ADDRESS } from "../relay/_utils";
import { SpokePoolV3PeripheryInterface } from "../_typechain/SpokePoolV3Periphery";
import { TransferType } from "../_spoke-pool-periphery";
export type CrossSwapType =
(typeof CROSS_SWAP_TYPE)[keyof typeof CROSS_SWAP_TYPE];

Expand Down Expand Up @@ -195,7 +197,11 @@ export function getFallbackRecipient(crossSwap: CrossSwap) {
}

export async function extractDepositDataStruct(
crossSwapQuotes: CrossSwapQuotes
crossSwapQuotes: CrossSwapQuotes,
submissionFees?: {
amount: BigNumberish;
recipient: string;
}
) {
const originChainId = crossSwapQuotes.crossSwap.inputToken.chainId;
const destinationChainId = crossSwapQuotes.crossSwap.outputToken.chainId;
Expand All @@ -204,7 +210,7 @@ export async function extractDepositDataStruct(
const refundAddress =
crossSwapQuotes.crossSwap.refundAddress ??
crossSwapQuotes.crossSwap.depositor;
const deposit = {
const baseDepositData = {
depositor: crossSwapQuotes.crossSwap.refundOnOrigin
? refundAddress
: crossSwapQuotes.crossSwap.depositor,
Expand All @@ -226,7 +232,46 @@ export async function extractDepositDataStruct(
crossSwapQuotes.bridgeQuote.suggestedFees.exclusivityDeadline,
message,
};
return deposit;
return {
inputAmount: baseDepositData.inputAmount,
baseDepositData,
submissionFees: submissionFees || {
amount: "0",
recipient: GAS_SPONSOR_ADDRESS,
},
};
}

export async function extractSwapAndDepositDataStruct(
crossSwapQuotes: CrossSwapQuotes,
submissionFees?: {
amount: BigNumberish;
recipient: string;
}
): Promise<SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct> {
const { originSwapQuote, contracts } = crossSwapQuotes;
const { originRouter } = contracts;
if (!originSwapQuote || !originRouter) {
throw new Error(
"Can not extract 'SwapAndDepositDataStruct' without originSwapQuote and originRouter"
);
}

const { baseDepositData, submissionFees: _submissionFees } =
await extractDepositDataStruct(crossSwapQuotes, submissionFees);
return {
submissionFees: submissionFees || _submissionFees,
depositData: baseDepositData,
swapToken: originSwapQuote.tokenIn.address,
swapTokenAmount: originSwapQuote.maximumAmountIn,
minExpectedInputTokenAmount: originSwapQuote.minAmountOut,
routerCalldata: originSwapQuote.swapTx.data,
exchange: originRouter.address,
transferType:
originRouter.name === "UniswapV3UniversalRouter"
? TransferType.Transfer
: TransferType.Approval,
};
}

async function getFillDeadline(spokePool: SpokePool): Promise<number> {
Expand Down
18 changes: 0 additions & 18 deletions api/_permit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,24 +87,6 @@ export async function getPermitTypedData(params: {
domainSeparator,
eip712: {
types: {
EIP712Domain: [
{
name: "name",
type: "string",
},
{
name: "version",
type: "string",
},
{
name: "chainId",
type: "uint256",
},
{
name: "verifyingContract",
type: "address",
},
],
Permit: [
{
name: "owner",
Expand Down
82 changes: 41 additions & 41 deletions api/_spoke-pool-periphery.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,9 @@
import { BigNumber } from "ethers";
import { extractDepositDataStruct } from "./_dexes/utils";
import { SpokePoolPeripheryProxy__factory } from "./_typechain/factories/SpokePoolPeripheryProxy__factory";
import { SpokePoolV3Periphery__factory } from "./_typechain/factories/SpokePoolV3Periphery__factory";
import { ENABLED_ROUTES, getProvider } from "./_utils";
import { SpokePoolV3PeripheryInterface } from "./_typechain/SpokePoolV3Periphery";

const sharedEIP712Types = {
EIP712Domain: [
{
name: "name",
type: "string",
},
{
name: "version",
type: "string",
},
{
name: "chainId",
type: "uint256",
},
{
name: "verifyingContract",
type: "address",
},
],
Fees: [
{
name: "amount",
Expand Down Expand Up @@ -133,14 +114,7 @@ export function getSpokePoolPeripheryProxy(address: string, chainId: number) {
}

export async function getDepositTypedData(params: {
depositData: {
submissionFees: {
amount: BigNumber;
recipient: string;
};
baseDepositData: Awaited<ReturnType<typeof extractDepositDataStruct>>;
inputAmount: BigNumber;
};
depositData: SpokePoolV3PeripheryInterface.DepositDataStruct;
chainId: number;
}) {
const spokePoolPeriphery = getSpokePoolPeriphery(
Expand Down Expand Up @@ -185,19 +159,7 @@ export async function getDepositTypedData(params: {
}

export async function getSwapAndDepositTypedData(params: {
swapAndDepositData: {
submissionFees: {
amount: BigNumber;
recipient: string;
};
depositData: Awaited<ReturnType<typeof extractDepositDataStruct>>;
swapToken: string;
exchange: string;
transferType: TransferType;
swapTokenAmount: BigNumber;
minExpectedInputTokenAmount: BigNumber;
routerCalldata: string;
};
swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct;
chainId: number;
}) {
const spokePoolPeriphery = getSpokePoolPeriphery(
Expand Down Expand Up @@ -260,3 +222,41 @@ export async function getSwapAndDepositTypedData(params: {
},
};
}

export function encodeDepositWithPermitCalldata(args: {
signatureOwner: string;
depositData: SpokePoolV3PeripheryInterface.DepositDataStruct;
deadline: number;
permitSignature: string;
depositDataSignature: string;
}) {
return SpokePoolV3Periphery__factory.createInterface().encodeFunctionData(
"depositWithPermit",
[
args.signatureOwner,
args.depositData,
args.deadline,
args.permitSignature,
args.depositDataSignature,
]
);
}

export function encodeSwapAndBridgeWithPermitCalldata(args: {
signatureOwner: string;
swapAndDepositData: SpokePoolV3PeripheryInterface.SwapAndDepositDataStruct;
deadline: number;
permitSignature: string;
swapAndDepositDataSignature: string;
}) {
return SpokePoolV3Periphery__factory.createInterface().encodeFunctionData(
"swapAndBridgeWithPermit",
[
args.signatureOwner,
args.swapAndDepositData,
args.deadline,
args.permitSignature,
args.swapAndDepositDataSignature,
]
);
}
12 changes: 9 additions & 3 deletions api/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ export const getLogger = (): LoggingUtility => {
* Resolves the current vercel endpoint dynamically
* @returns A valid URL of the current endpoint in vercel
*/
export const resolveVercelEndpoint = () => {
if (process.env.REACT_APP_VERCEL_API_BASE_URL_OVERRIDE) {
export const resolveVercelEndpoint = (omitOverride = false) => {
if (!omitOverride && process.env.REACT_APP_VERCEL_API_BASE_URL_OVERRIDE) {
return process.env.REACT_APP_VERCEL_API_BASE_URL_OVERRIDE;
}
const url = process.env.VERCEL_URL ?? "across.to";
Expand Down Expand Up @@ -1487,7 +1487,7 @@ export function validAddressOrENS() {

export function positiveIntStr() {
return define<string>("positiveIntStr", (value) => {
return Number.isInteger(Number(value)) && Number(value) > 0;
return Number.isInteger(Number(value)) && Number(value) >= 0;
});
}

Expand All @@ -1503,6 +1503,12 @@ export function boolStr() {
});
}

export function hexString() {
return define<string>("hexString", (value) => {
return utils.isHexString(value);
});
}

/**
* Returns the cushion for a given token symbol and route. If no route is specified, the cushion for the token symbol
* @param symbol The token symbol
Expand Down
46 changes: 46 additions & 0 deletions api/relay/_queue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Client } from "@upstash/qstash";

import { RelayRequest, RelayStrategy, RelayStrategyName } from "./_types";
import { resolveVercelEndpoint } from "../_utils";

const client = new Client({
token: process.env.QSTASH_TOKEN!,
});

export async function pushRelayRequestToQueue({
request,
strategy,
}: {
request: RelayRequest;
strategy: RelayStrategy;
}) {
const strategyName = strategy.strategyName;
const queue = getRelayRequestQueue(strategyName, request.chainId);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is each queue/strategy using different EOAs? If not, how do we avoid nonce collisions between txns submitted within the same block between these queues?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it depends on the strategy. for example, if using Gelato, we don't need to handle nonce collisions because they take care of it. if using local signers, then we need to implement a mechanism/handler that's able to prevent nonce collisions.

await queue.upsert({
parallelism: strategy.queueParallelism,
});

const baseUrl = resolveVercelEndpoint(true);
const response = await queue.enqueueJSON({
retries: 3,
contentBasedDeduplication: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

does this solve nonce collision?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yea that's the idea. by using a queue and setting parallelism to the number of signers we have, we should be able to mitigate nonce collisions on a single chain.

headers: new Headers({
"Retry-After": "1",
}),
url: `${baseUrl}/api/relay/jobs/process`,
body: {
request,
strategyName,
},
});
return response;
}

function getRelayRequestQueue(
strategyName: RelayStrategyName,
chainId: number
) {
return client.queue({
queueName: `relay-request-queue-${chainId}-${strategyName}`,
});
}
Loading
Loading