From 8e487c47fcfce87ec2ba9c73d702ce44d7ae476b Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Tue, 10 Dec 2024 16:26:27 +0000 Subject: [PATCH 01/10] wip --- api/_types/exclusivity.types.ts | 48 +++++++++++++++++++++++++++++++++ api/_types/index.ts | 1 + 2 files changed, 49 insertions(+) create mode 100644 api/_types/exclusivity.types.ts diff --git a/api/_types/exclusivity.types.ts b/api/_types/exclusivity.types.ts new file mode 100644 index 000000000..4fd93ae24 --- /dev/null +++ b/api/_types/exclusivity.types.ts @@ -0,0 +1,48 @@ +export type RelayerFillLimit = { + maxFillSize?: number; + minProfitThreshold?: number; + balanceMultiplier?: number; +}; + + +export type RelayerFillConfig = Required & { + originChainIds: number[]; + tokens: { + [inputToken: string]: RelayerFillLimit & { + originChainIds?: number[]; + [destinationChainId: number]: { + [outputToken: string]: { [maxFillSize: number]: number }, + } + } + } +} + +export const SampleConfig: RelayerFillConfig = { + originChainIds: [1, 10, 137], + maxFillSize: 10_000, // USD + minProfitThreshold: 0.0001, // 1bps + balanceMultiplier: 0.5, + + tokens: { + WETH: { + // originChainIds: [59144], + 42161: { + WETH: { + 500: 0.0005, + 1000: 0.001, + 10_000: 0.0001, + }, + } + }, + USDC: { + 42161: { + "USDC.e" : { + 100: 0.0001, + }, + USDC: { + 100: 0.0005, + }, + } + } + } +} diff --git a/api/_types/index.ts b/api/_types/index.ts index 6723d1603..3e5e03912 100644 --- a/api/_types/index.ts +++ b/api/_types/index.ts @@ -1,2 +1,3 @@ +export * from "./exclusivity.types"; export * from "./generic.types"; export * from "./utility.types"; From 846c67e563e703bae942c76f79290e950f31903c Mon Sep 17 00:00:00 2001 From: james-a-morris Date: Tue, 10 Dec 2024 12:39:26 -0600 Subject: [PATCH 02/10] updates Signed-off-by: james-a-morris --- api/_types/exclusivity.types.ts | 75 +++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/api/_types/exclusivity.types.ts b/api/_types/exclusivity.types.ts index 4fd93ae24..a7ec2df7c 100644 --- a/api/_types/exclusivity.types.ts +++ b/api/_types/exclusivity.types.ts @@ -1,48 +1,59 @@ export type RelayerFillLimit = { - maxFillSize?: number; - minProfitThreshold?: number; - balanceMultiplier?: number; + maxFillSize?: number; + minProfitThreshold?: number; + balanceMultiplier?: number; }; - export type RelayerFillConfig = Required & { originChainIds: number[]; tokens: { [inputToken: string]: RelayerFillLimit & { - originChainIds?: number[]; - [destinationChainId: number]: { - [outputToken: string]: { [maxFillSize: number]: number }, - } - } - } -} + originChainIds: number[]; + [originChainId: number] : { + [destinationChainId: number]: RelayerFillLimit & { + [outputToken: string]: { [maxFillSize: number]: number }; + }; + }; + }; + }; +}; + +POST: api/relayer/config// + + export const SampleConfig: RelayerFillConfig = { originChainIds: [1, 10, 137], maxFillSize: 10_000, // USD minProfitThreshold: 0.0001, // 1bps - balanceMultiplier: 0.5, + balanceMultiplier: 0.5, + + + + tokens: { - WETH: { - // originChainIds: [59144], - 42161: { - WETH: { - 500: 0.0005, - 1000: 0.001, - 10_000: 0.0001, - }, - } - }, USDC: { - 42161: { - "USDC.e" : { + balanceMultiplier: 0.3, + 10: { + 42161: { + USDC: { 100: 0.0001, - }, - USDC: { - 100: 0.0005, - }, - } - } - } -} + 1000: 0.0002, + 10000: 0.0005, + }, + "USDC.e": { + 100: 0.0002, + 1000: 0.0006, + } + }, + 59144: { + "USDC": { + 100: 0.0002, + 1000: 0.0005, + } + } + }, + }, + }, +}; From 313b9c5eb86447af73ba318c63fa2821aa602dbb Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Wed, 11 Dec 2024 11:20:31 -0600 Subject: [PATCH 03/10] feat(api): add relayer config enpoint Signed-off-by: Pablo Maldonado --- api/relayer-config.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 api/relayer-config.ts diff --git a/api/relayer-config.ts b/api/relayer-config.ts new file mode 100644 index 000000000..1118c2ce3 --- /dev/null +++ b/api/relayer-config.ts @@ -0,0 +1,21 @@ +import { VercelResponse } from "@vercel/node"; +import { TypedVercelRequest } from "./_types"; + +const handler = async ( + request: TypedVercelRequest, + response: VercelResponse +) => { + if (request.method === "GET") { + // Handle GET request + response.status(200).json({ message: "GET request received" }); + } else if (request.method === "POST") { + // Handle POST request + response.status(200).json({ message: "POST request received" }); + } else { + // Method not allowed + response.setHeader("Allow", ["GET", "POST"]); + response.status(405).end(`Method ${request.method} Not Allowed`); + } +}; + +export default handler; From 38ffcae9ceff531197f993ff9f514b1c67c4e428 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Wed, 11 Dec 2024 12:13:17 -0600 Subject: [PATCH 04/10] feat(api): get relayer address and check whitelist Signed-off-by: Pablo Maldonado --- api/relayer-config.ts | 54 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/api/relayer-config.ts b/api/relayer-config.ts index 1118c2ce3..f6db1eeda 100644 --- a/api/relayer-config.ts +++ b/api/relayer-config.ts @@ -1,16 +1,62 @@ import { VercelResponse } from "@vercel/node"; import { TypedVercelRequest } from "./_types"; +import { ethers } from "ethers"; + +// TODO: prevent replay attacks by checking the timestamp of the message or using a nonce +const getRelayerFromSignature = async (signature: string, message: string) => { + return ethers.utils.verifyMessage(message, signature); +}; + +// TODO: get this from gh +const whiteListedRelayers = [ + "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D", // dev wallet +]; + +const handleGetRequest = async ( + request: TypedVercelRequest, + response: VercelResponse +) => { + const { signature, message } = request.query; + + const relayer = await getRelayerFromSignature(signature, message); + + console.log("relayer", relayer); + + if (!whiteListedRelayers.includes(relayer)) { + return response.status(401).json({ message: "Unauthorized" }); + } + + // Handle authenticated GET request + response.status(200).json({ message: "Authenticated GET request received" }); +}; + +const handlePostRequest = async ( + request: TypedVercelRequest, + response: VercelResponse +) => { + const { signature, message } = request.body; + + const relayer = await getRelayerFromSignature( + signature, + JSON.stringify(message) + ); + + if (!whiteListedRelayers.includes(relayer)) { + return response.status(401).json({ message: "Unauthorized" }); + } + + // Handle POST request + response.status(200).json({ message: "POST request received" }); +}; const handler = async ( request: TypedVercelRequest, response: VercelResponse ) => { if (request.method === "GET") { - // Handle GET request - response.status(200).json({ message: "GET request received" }); + await handleGetRequest(request, response); } else if (request.method === "POST") { - // Handle POST request - response.status(200).json({ message: "POST request received" }); + await handlePostRequest(request, response); } else { // Method not allowed response.setHeader("Allow", ["GET", "POST"]); From 7f83b6587dec4c3c2bba871345d738a20c225f13 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Wed, 11 Dec 2024 12:14:47 -0600 Subject: [PATCH 05/10] refactor: comments Signed-off-by: Pablo Maldonado --- api/relayer-config.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/relayer-config.ts b/api/relayer-config.ts index f6db1eeda..2b04c75e6 100644 --- a/api/relayer-config.ts +++ b/api/relayer-config.ts @@ -20,8 +20,6 @@ const handleGetRequest = async ( const relayer = await getRelayerFromSignature(signature, message); - console.log("relayer", relayer); - if (!whiteListedRelayers.includes(relayer)) { return response.status(401).json({ message: "Unauthorized" }); } From 2f44280d1834ab8d50dc94a7454367caaf43e9a3 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Wed, 11 Dec 2024 12:21:55 -0600 Subject: [PATCH 06/10] Update api/relayer-config.ts Co-authored-by: James Morris, MS <96435344+james-a-morris@users.noreply.github.com> --- api/relayer-config.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/api/relayer-config.ts b/api/relayer-config.ts index 2b04c75e6..44cca235e 100644 --- a/api/relayer-config.ts +++ b/api/relayer-config.ts @@ -51,15 +51,13 @@ const handler = async ( request: TypedVercelRequest, response: VercelResponse ) => { - if (request.method === "GET") { - await handleGetRequest(request, response); - } else if (request.method === "POST") { - await handlePostRequest(request, response); - } else { - // Method not allowed - response.setHeader("Allow", ["GET", "POST"]); - response.status(405).end(`Method ${request.method} Not Allowed`); + if(["GET","POST"].includes(request.method) { + return request.method === "GET" ? handleGetRequest(request, response) : handlePostRequest(request, response); + } else { + response.setHeader("Allow", ["GET", "POST"]); + response.status(405).end(`Method ${request.method} Not Allowed`); } + }; export default handler; From f88e9db45c29c1475dea643a993618fb31281462 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Wed, 11 Dec 2024 12:30:23 -0600 Subject: [PATCH 07/10] refactor: organise files Signed-off-by: Pablo Maldonado --- api/_exclusivity/utils.ts | 21 ++++++++++++++++++ api/relayer-config.ts | 46 ++++++++++++++++++++++++--------------- 2 files changed, 49 insertions(+), 18 deletions(-) create mode 100644 api/_exclusivity/utils.ts diff --git a/api/_exclusivity/utils.ts b/api/_exclusivity/utils.ts new file mode 100644 index 000000000..d47fb3cc1 --- /dev/null +++ b/api/_exclusivity/utils.ts @@ -0,0 +1,21 @@ +import { ethers } from "ethers"; + +// TODO: get this from gh +export const whiteListedRelayers = [ + "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D", // dev wallet +]; + +export const getRelayerFromSignature = async ( + signature: string, + message: string +) => { + return ethers.utils.verifyMessage(message, signature); +}; + +export const isMessageFresh = ( + timestamp: number, + maxAgeSeconds: number +): boolean => { + const currentTime = Math.floor(Date.now() / 1000); + return currentTime - timestamp <= maxAgeSeconds; +}; diff --git a/api/relayer-config.ts b/api/relayer-config.ts index 44cca235e..5f15822b3 100644 --- a/api/relayer-config.ts +++ b/api/relayer-config.ts @@ -1,16 +1,12 @@ import { VercelResponse } from "@vercel/node"; +import { + getRelayerFromSignature, + isMessageFresh, + whiteListedRelayers, +} from "./_exclusivity/utils"; import { TypedVercelRequest } from "./_types"; -import { ethers } from "ethers"; -// TODO: prevent replay attacks by checking the timestamp of the message or using a nonce -const getRelayerFromSignature = async (signature: string, message: string) => { - return ethers.utils.verifyMessage(message, signature); -}; - -// TODO: get this from gh -const whiteListedRelayers = [ - "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D", // dev wallet -]; +export const MAX_MESSAGE_AGE_SECONDS = 300; const handleGetRequest = async ( request: TypedVercelRequest, @@ -18,7 +14,15 @@ const handleGetRequest = async ( ) => { const { signature, message } = request.query; - const relayer = await getRelayerFromSignature(signature, message); + const { timestamp, ...restOfMessage } = JSON.parse(message); + if (!isMessageFresh(timestamp, MAX_MESSAGE_AGE_SECONDS)) { + return response.status(400).json({ message: "Message too old" }); + } + + const relayer = await getRelayerFromSignature( + signature, + JSON.stringify(restOfMessage) + ); if (!whiteListedRelayers.includes(relayer)) { return response.status(401).json({ message: "Unauthorized" }); @@ -34,9 +38,14 @@ const handlePostRequest = async ( ) => { const { signature, message } = request.body; + const { timestamp, ...restOfMessage } = message; + if (!isMessageFresh(timestamp, MAX_MESSAGE_AGE_SECONDS)) { + return response.status(400).json({ message: "Message too old" }); + } + const relayer = await getRelayerFromSignature( signature, - JSON.stringify(message) + JSON.stringify(restOfMessage) ); if (!whiteListedRelayers.includes(relayer)) { @@ -51,13 +60,14 @@ const handler = async ( request: TypedVercelRequest, response: VercelResponse ) => { - if(["GET","POST"].includes(request.method) { - return request.method === "GET" ? handleGetRequest(request, response) : handlePostRequest(request, response); - } else { - response.setHeader("Allow", ["GET", "POST"]); - response.status(405).end(`Method ${request.method} Not Allowed`); + if (request.method && ["GET", "POST"].includes(request.method)) { + return request.method === "GET" + ? handleGetRequest(request, response) + : handlePostRequest(request, response); + } else { + response.setHeader("Allow", ["GET", "POST"]); + response.status(405).end(`Method ${request.method} Not Allowed`); } - }; export default handler; From 5f174eca5f1c8f9a29da519e05f3ed4c5f5aff10 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Wed, 11 Dec 2024 12:39:01 -0600 Subject: [PATCH 08/10] refactor: rename func Signed-off-by: Pablo Maldonado --- api/_exclusivity/utils.ts | 2 +- api/relayer-config.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/_exclusivity/utils.ts b/api/_exclusivity/utils.ts index d47fb3cc1..8ed3d1a26 100644 --- a/api/_exclusivity/utils.ts +++ b/api/_exclusivity/utils.ts @@ -12,7 +12,7 @@ export const getRelayerFromSignature = async ( return ethers.utils.verifyMessage(message, signature); }; -export const isMessageFresh = ( +export const isTimestampValid = ( timestamp: number, maxAgeSeconds: number ): boolean => { diff --git a/api/relayer-config.ts b/api/relayer-config.ts index 5f15822b3..58a048b95 100644 --- a/api/relayer-config.ts +++ b/api/relayer-config.ts @@ -1,7 +1,7 @@ import { VercelResponse } from "@vercel/node"; import { getRelayerFromSignature, - isMessageFresh, + isTimestampValid, whiteListedRelayers, } from "./_exclusivity/utils"; import { TypedVercelRequest } from "./_types"; @@ -15,7 +15,7 @@ const handleGetRequest = async ( const { signature, message } = request.query; const { timestamp, ...restOfMessage } = JSON.parse(message); - if (!isMessageFresh(timestamp, MAX_MESSAGE_AGE_SECONDS)) { + if (!isTimestampValid(timestamp, MAX_MESSAGE_AGE_SECONDS)) { return response.status(400).json({ message: "Message too old" }); } @@ -39,7 +39,7 @@ const handlePostRequest = async ( const { signature, message } = request.body; const { timestamp, ...restOfMessage } = message; - if (!isMessageFresh(timestamp, MAX_MESSAGE_AGE_SECONDS)) { + if (!isTimestampValid(timestamp, MAX_MESSAGE_AGE_SECONDS)) { return response.status(400).json({ message: "Message too old" }); } From 410febdbd06c0514b93caf7529de19452f7f613a Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 11 Dec 2024 21:52:39 +0000 Subject: [PATCH 09/10] Add scaffolding for config unpacking --- api/_exclusivity/utils.ts | 10 ++++++++++ api/_types/exclusivity.types.ts | 4 +++- api/relayer-config.ts | 12 ++++++++++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/api/_exclusivity/utils.ts b/api/_exclusivity/utils.ts index 8ed3d1a26..519c4a12c 100644 --- a/api/_exclusivity/utils.ts +++ b/api/_exclusivity/utils.ts @@ -1,4 +1,5 @@ import { ethers } from "ethers"; +import { RelayerFillLimit } from "../_types"; // TODO: get this from gh export const whiteListedRelayers = [ @@ -19,3 +20,12 @@ export const isTimestampValid = ( const currentTime = Math.floor(Date.now() / 1000); return currentTime - timestamp <= maxAgeSeconds; }; + +export async function updateLimits( + relayer: string, + limits: RelayerFillLimit[] +): Promise { + relayer; // todo + limits; // todo + return; +} diff --git a/api/_types/exclusivity.types.ts b/api/_types/exclusivity.types.ts index d3c4858e2..bd64108cb 100644 --- a/api/_types/exclusivity.types.ts +++ b/api/_types/exclusivity.types.ts @@ -1,4 +1,4 @@ -import { boolean, Infer, object, optional } from "superstruct"; +import { array, boolean, Infer, object, optional } from "superstruct"; import { positiveFloatStr, positiveIntStr, validAddress } from "../_utils"; export const RelayerFillLimitSchema = object({ @@ -14,6 +14,8 @@ export const RelayerFillLimitSchema = object({ msgFill: optional(boolean()), }); +export const RelayerFillLimitsSchema = array(RelayerFillLimitSchema); + export type RelayerFillLimit = Infer; // // Example config. diff --git a/api/relayer-config.ts b/api/relayer-config.ts index 58a048b95..92b462517 100644 --- a/api/relayer-config.ts +++ b/api/relayer-config.ts @@ -2,9 +2,10 @@ import { VercelResponse } from "@vercel/node"; import { getRelayerFromSignature, isTimestampValid, + updateLimits, whiteListedRelayers, } from "./_exclusivity/utils"; -import { TypedVercelRequest } from "./_types"; +import { RelayerFillLimitsSchema, TypedVercelRequest } from "./_types"; export const MAX_MESSAGE_AGE_SECONDS = 300; @@ -52,7 +53,14 @@ const handlePostRequest = async ( return response.status(401).json({ message: "Unauthorized" }); } - // Handle POST request + if (!RelayerFillLimitsSchema.is(message)) { + return response + .status(400) + .json({ message: "Invalid configuration payload" }); + } + + await updateLimits(relayer, restOfMessage); + response.status(200).json({ message: "POST request received" }); }; From 84783ad3a733d6555577879447871e241a5f55f1 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 11 Dec 2024 23:40:14 +0000 Subject: [PATCH 10/10] Fixtest --- test/api/relayer-config.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/api/relayer-config.test.ts b/test/api/relayer-config.test.ts index a8d6200da..b71b8d5be 100644 --- a/test/api/relayer-config.test.ts +++ b/test/api/relayer-config.test.ts @@ -28,6 +28,19 @@ describe("Relayer Config API", () => { test("POST request with valid timestamp", async () => { const message = { timestamp: Date.now() / 1000, + relayerFillLimits: [ + { + originChainId: "1", + inputToken: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + destinationChainId: "42161", + outputToken: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + minOutputAmount: "1", + maxOutputAmount: "2", + balanceMultiplier: "1", + minProfitThreshold: "0.0001", + minExclusivityPeriod: "1", + }, + ], }; const messageString = JSON.stringify(message); const signature = await whitelistedRelayer.signMessage(messageString);