diff --git a/packages/relayer/Dockerfile b/packages/relayer/Dockerfile index 4db9eff..bb8a03a 100644 --- a/packages/relayer/Dockerfile +++ b/packages/relayer/Dockerfile @@ -1,4 +1,6 @@ FROM node:23 +ARG GIT_SHA="0000000" +ARG BUILD_DATE= WORKDIR /build RUN yarn global add typescript @@ -9,6 +11,8 @@ RUN yarn install COPY src/ ./src/ RUN yarn build +ENV GIT_SHA=$GIT_SHA +ENV BUILD_DATE=$BUILD_DATE EXPOSE 3000 -CMD ["node", "dist/src/index.js"] +CMD ["node", "dist/index.js"] diff --git a/packages/relayer/package.json b/packages/relayer/package.json index 02551b3..e3b3347 100644 --- a/packages/relayer/package.json +++ b/packages/relayer/package.json @@ -6,8 +6,8 @@ "license": "Apache-2.0", "author": "Wonderland", "type": "module", - "main": "./dist/src/index.js", - "types": "./dist/src/index.d.ts", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "directories": { "src": "src" }, @@ -18,7 +18,8 @@ ], "scripts": { "build": "tsc -p tsconfig.build.json", - "start": "node ./dist/src/index.js", + "build:clean": "rm -r ./tsconfig.build.tsbuildinfo ./dist && tsc -p tsconfig.build.json", + "start": "node ./dist/index.js", "build:start": "yarn build && yarn start", "start:ts": "node --no-warnings=ExperimentalWarning --loader ts-node/esm src/index.ts", "check-types": "tsc --noEmit -p ./tsconfig.json", @@ -34,7 +35,11 @@ }, "dependencies": { "@0xbow/privacy-pools-core-sdk": "0.1.17", - "@uniswap/v3-sdk": "^3.25.2", + "@ethersproject/bignumber": "5.8.0", + "@ethersproject/bytes": "5.8.0", + "@uniswap/sdk-core": "7.7.2", + "@uniswap/universal-router-sdk": "4.19.5", + "@uniswap/v3-sdk": "3.25.2", "ajv": "8.17.1", "body-parser": "1.20.3", "cors": "^2.8.5", @@ -45,7 +50,10 @@ "zod": "3.24.1" }, "devDependencies": { + "@types/body-parser": "^1.19.6", "@types/cors": "^2.8.17", - "@types/express": "5.0.0" + "@types/express": "5.0.0", + "@vitest/coverage-v8": "^3.2.4", + "vitest": "^3.2.4" } } diff --git a/packages/relayer/src/app.ts b/packages/relayer/src/app.ts index 4cb4af9..b24683c 100644 --- a/packages/relayer/src/app.ts +++ b/packages/relayer/src/app.ts @@ -7,7 +7,7 @@ import { notFoundMiddleware, } from "./middlewares/index.js"; import { relayerRouter } from "./routes/index.js"; -import { CONFIG } from "./config/index.js"; +import { CORS_ALLOW_ALL, ALLOWED_DOMAINS } from "./config/index.js"; // Initialize the express app const app = express(); @@ -15,16 +15,25 @@ const app = express(); // Middleware functions const parseJsonMiddleware = bodyParser.json(); -// CORS config +// CORS config - allow all origins by default for development and testnet +const isTestnetRelayer = process.env.NODE_ENV === 'production' && + (process.env.RELAYER_HOST === 'testnet-relayer.privacypools.com' || + process.env.HOST === 'testnet-relayer.privacypools.com'); + +const shouldAllowAll = CORS_ALLOW_ALL || isTestnetRelayer; + const corsOptions = { - origin: CONFIG.cors_allow_all ? '*' : function (origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) { - if (!origin || CONFIG.allowed_domains.indexOf(origin) !== -1) { + origin: shouldAllowAll ? '*' : function (origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) { + // Allow requests without origin (like mobile apps) or from allowed domains + if (!origin || ALLOWED_DOMAINS.indexOf(origin) !== -1) { callback(null, true); } else { - console.log(`Request blocked by CORS middleware: ${origin}. Allowed domains: ${CONFIG.allowed_domains}`); + console.log(`Request blocked by CORS middleware: ${origin}. Allowed domains: ${ALLOWED_DOMAINS}`); callback(new Error("Not allowed by CORS")); } }, + credentials: true, + optionsSuccessStatus: 200 }; diff --git a/packages/relayer/src/config/index.ts b/packages/relayer/src/config/index.ts index 7cabe75..4ba30e8 100644 --- a/packages/relayer/src/config/index.ts +++ b/packages/relayer/src/config/index.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import path from "node:path"; -import { ConfigError } from "../exceptions/base.exception.js"; +import { ConfigError, RelayerError } from "../exceptions/base.exception.js"; import { zConfig } from "./schemas.js"; -import { ChainConfig, AssetConfig } from "./types.js"; +import { AssetConfig, ChainConfig } from "./types.js"; /** * Reads the configuration file from the path specified in the CONFIG_PATH environment variable @@ -110,18 +110,26 @@ export function getEntrypointAddress(chainId: number): string { * * @param {number} chainId - The chain ID * @param {string} assetAddress - The asset address - * @returns {AssetConfig | undefined} The asset configuration, or undefined if not found + * @returns {AssetConfig} The asset configuration, or undefined if not found */ -export function getAssetConfig(chainId: number, assetAddress: string): AssetConfig | undefined { +export function getAssetConfig(chainId: number, assetAddress: string): AssetConfig { const chainConfig = getChainConfig(chainId); + if (!chainConfig.supported_assets) { - return undefined; + throw RelayerError.assetNotSupported(); } - return chainConfig.supported_assets.find( + const assetConfig = chainConfig.supported_assets.find( asset => asset.asset_address.toLowerCase() === assetAddress.toLowerCase() ); + + if (!assetConfig) { + throw RelayerError.assetNotSupported(); + } + + return assetConfig + } // Re-export types -export * from "./types.js"; +export * from "./types.js"; diff --git a/packages/relayer/src/config/schemas.ts b/packages/relayer/src/config/schemas.ts index 0abeebb..37b6e6e 100644 --- a/packages/relayer/src/config/schemas.ts +++ b/packages/relayer/src/config/schemas.ts @@ -64,8 +64,8 @@ export const zChainConfig = z.object({ // Common configuration schema export const zCommonConfig = z.object({ sqlite_db_path: z.string().transform((p) => path.resolve(p)), - cors_allow_all: z.boolean().default(false), - allowed_domains: z.array(z.string().url()), + cors_allow_all: z.boolean().default(true), + allowed_domains: z.array(z.string().url()).default(["https://testnet.privacypools.com, https://prod-privacy-pool-ui.vercel.app, https://staging-privacy-pool-ui.vercel.app, https://dev-privacy-pool-ui.vercel.app, http://localhost:3000"]), }); // Default configuration schema diff --git a/packages/relayer/src/exceptions/base.exception.ts b/packages/relayer/src/exceptions/base.exception.ts index 924572a..d767bfe 100644 --- a/packages/relayer/src/exceptions/base.exception.ts +++ b/packages/relayer/src/exceptions/base.exception.ts @@ -82,6 +82,12 @@ export class RelayerError extends Error { public static unknown(message?: string): RelayerError { return new RelayerError(message || "", ErrorCode.UNKNOWN); } + + public static assetNotSupported( + details?: Record | string) { + return new RelayerError("Asset is not supported", ErrorCode.ASSET_NOT_SUPPORTED, details); + } + } export class ValidationError extends RelayerError { @@ -246,7 +252,7 @@ export class WithdrawalValidationError extends RelayerError { ); } - public static assetNotSupported(details: string) { + public static override assetNotSupported(details: string) { return new WithdrawalValidationError( "Asset not supported on this chain", ErrorCode.ASSET_NOT_SUPPORTED, @@ -285,7 +291,7 @@ export class QuoterError extends RelayerError { this.name = this.constructor.name; } - public static assetNotSupported( + public static override assetNotSupported( details?: Record | string) { return new QuoterError("Asset is not supported", ErrorCode.ASSET_NOT_SUPPORTED, details); } diff --git a/packages/relayer/src/handlers/relayer/quote.ts b/packages/relayer/src/handlers/relayer/quote.ts index 2e0958d..ca9aeeb 100644 --- a/packages/relayer/src/handlers/relayer/quote.ts +++ b/packages/relayer/src/handlers/relayer/quote.ts @@ -1,13 +1,18 @@ import { NextFunction, Request, Response } from "express"; import { getAddress } from "viem"; -import { getAssetConfig, getFeeReceiverAddress } from "../../config/index.js"; +import { getAssetConfig, getFeeReceiverAddress, getSignerPrivateKey } from "../../config/index.js"; import { QuoterError } from "../../exceptions/base.exception.js"; import { web3Provider } from "../../providers/index.js"; import { quoteService } from "../../services/index.js"; import { QuoteMarshall } from "../../types.js"; -import { encodeWithdrawalData } from "../../utils.js"; +import { encodeWithdrawalData, isFeeReceiverSameAsSigner, isNative } from "../../utils.js"; +import { privateKeyToAccount } from "viem/accounts"; +import { QuoteFee } from "../../services/quote.service.js"; -const TIME_20_SECS = 20 * 1000; +// const TIME_20_SECS = 20 * 1000; +const TIME_60_SECS = 60 * 1000; + +const EXPIRATION_TIME = TIME_60_SECS; export async function relayQuoteHandler( req: Request, @@ -17,34 +22,63 @@ export async function relayQuoteHandler( const chainId = Number(req.body.chainId!); const amountIn = BigInt(req.body.amount!.toString()); - const assetAddress = getAddress(req.body.asset!.toString()) + const asset = getAddress(req.body.asset!.toString()); + let extraGas = Boolean(req.body.extraGas); - const config = getAssetConfig(chainId, assetAddress); + const config = getAssetConfig(chainId, asset); if (config === undefined) - return next(QuoterError.assetNotSupported(`Asset ${assetAddress} for chain ${chainId} is not supported`)); + return next(QuoterError.assetNotSupported(`Asset ${asset} for chain ${chainId} is not supported`)); - const feeBPS = await quoteService.quoteFeeBPSNative({ - chainId, amountIn, assetAddress, baseFeeBPS: config.fee_bps, value: 0n - }); + if (isNative(asset)) { + extraGas = false; + } - const recipient = req.body.recipient ? getAddress(req.body.recipient.toString()) : undefined + let quote: QuoteFee; + try { + quote = await quoteService.quoteFeeBPSNative({ + chainId, amountIn, assetAddress: asset, baseFeeBPS: config.fee_bps, extraGas: extraGas + }); + } catch (e) { + return next(e); + } + + const { feeBPS, gasPrice, extraGasFundAmount, relayTxCost, extraGasTxCost } = quote; + const recipient = req.body.recipient ? getAddress(req.body.recipient.toString()) : undefined; + const detail = { + relayTxCost: { gas: relayTxCost, eth: relayTxCost * gasPrice }, + extraGasFundAmount: extraGasFundAmount ? { gas: extraGasFundAmount, eth: extraGasFundAmount * gasPrice } : undefined, + extraGasTxCost: extraGasTxCost ? { gas: extraGasTxCost, eth: extraGasTxCost * gasPrice } : undefined, + }; const quoteResponse = new QuoteMarshall({ baseFeeBPS: config.fee_bps, feeBPS, + gasPrice, + detail, }); if (recipient) { - const feeReceiverAddress = getFeeReceiverAddress(chainId); + let feeReceiverAddress: `0x${string}`; + const finalFeeReceiverAddress = getAddress(getFeeReceiverAddress(chainId)); + if (extraGas) { + const signer = privateKeyToAccount(getSignerPrivateKey(chainId) as `0x${string}`); + if (isFeeReceiverSameAsSigner(chainId)) { + feeReceiverAddress = finalFeeReceiverAddress; + } else { + feeReceiverAddress = signer.address; + } + } else { + feeReceiverAddress = finalFeeReceiverAddress; + } const withdrawalData = encodeWithdrawalData({ feeRecipient: getAddress(feeReceiverAddress), recipient, relayFeeBPS: feeBPS - }) - const expiration = Number(new Date()) + TIME_20_SECS - const relayerCommitment = { withdrawalData, expiration }; + }); + const expiration = Number(new Date()) + EXPIRATION_TIME; + const relayerCommitment = { withdrawalData, expiration, asset, amount: amountIn, extraGas }; const signedRelayerCommitment = await web3Provider.signRelayerCommitment(chainId, relayerCommitment); - quoteResponse.addFeeCommitment({ expiration, withdrawalData, signedRelayerCommitment }) + quoteResponse.addFeeCommitment({ expiration, asset, withdrawalData, signedRelayerCommitment, extraGas, amount: amountIn }); } res diff --git a/packages/relayer/src/handlers/relayer/request.ts b/packages/relayer/src/handlers/relayer/request.ts index ea4569c..2095ab1 100644 --- a/packages/relayer/src/handlers/relayer/request.ts +++ b/packages/relayer/src/handlers/relayer/request.ts @@ -1,16 +1,14 @@ import { NextFunction, Request, Response } from "express"; -import { getAddress } from "viem"; +import { CONFIG, getChainConfig } from "../../config/index.js"; import { ConfigError, ValidationError } from "../../exceptions/base.exception.js"; import { RelayerResponse, - RelayRequestBody, WithdrawalPayload, } from "../../interfaces/relayer/request.js"; -import { validateRelayRequestBody } from "../../schemes/relayer/request.scheme.js"; +import { web3Provider } from "../../providers/index.js"; +import { zRelayRequest } from "../../schemes/relayer/request.scheme.js"; import { privacyPoolRelayer } from "../../services/index.js"; import { RequestMashall } from "../../types.js"; -import { CONFIG, getChainConfig } from "../../config/index.js"; -import { web3Provider } from "../../providers/index.js"; /** * Converts a RelayRequestBody into a WithdrawalPayload. @@ -19,25 +17,19 @@ import { web3Provider } from "../../providers/index.js"; * @returns {WithdrawalPayload} - The formatted withdrawal payload. */ function relayRequestBodyToWithdrawalPayload( - body: RelayRequestBody, + body: ReturnType, ): WithdrawalPayload { - const proof = { ...body.proof, protocol: "groth16", curve: "bn128" }; - const publicSignals = body.publicSignals; - const scope = BigInt(body.scope); - const withdrawal = { - processooor: getAddress(body.withdrawal.processooor), - data: body.withdrawal.data as `0x{string}`, - }; - const wp = { + return { + ...body, proof: { - proof, - publicSignals, + proof: { + ...body.proof, + protocol: "groth16", + curve: "bn128" + }, + publicSignals: body.publicSignals, }, - withdrawal, - scope, - feeCommitment: body.feeCommitment }; - return wp; } /** @@ -58,36 +50,23 @@ function isChainSupported(chainId: number): boolean { * @throws {ValidationError} - If the input data is invalid. * @throws {ConfigError} - If the chain is not supported. */ -function parseWithdrawal(body: Request["body"]): { payload: WithdrawalPayload, chainId: number } { - if (validateRelayRequestBody(body)) { - try { - const payload = relayRequestBodyToWithdrawalPayload(body); - const chainId = typeof body.chainId === 'string' ? parseInt(body.chainId, 10) : body.chainId; +function parseWithdrawal(body: Request["body"]): { payload: WithdrawalPayload, chainId: number; } { - if (isNaN(chainId)) { - throw ValidationError.invalidInput({ message: "Invalid chain ID format" }); - } + const { data, error, success } = zRelayRequest.safeParse(body); - // Check if the chain is supported early - if (!isChainSupported(chainId)) { - throw ValidationError.invalidInput({ message: `Chain with ID ${chainId} not supported.` }); - } - - return { payload, chainId }; - } catch (error) { - console.error(error); - // Re-throw ConfigError as is - if (error instanceof ConfigError) { - throw error; - } - // TODO: extend this catch to return more details about the issue (viem error, node error, etc) - throw ValidationError.invalidInput({ - message: "Can't parse payload into SDK structure", - }); - } - } else { - throw ValidationError.invalidInput({ message: "Payload format error" }); + if (!success) { + throw ValidationError.invalidInput({ error, message: "Error parsing payload" }); } + + const payload = relayRequestBodyToWithdrawalPayload(data); + + // Check if the chain is supported early + if (!isChainSupported(data.chainId)) { + throw ValidationError.invalidInput({ message: `Chain with ID ${data.chainId} not supported.` }); + } + + return { payload, chainId: data.chainId }; + } /** @@ -104,10 +83,12 @@ export async function relayRequestHandler( ) { try { const { payload: withdrawalPayload, chainId } = parseWithdrawal(req.body); + const maxGasPrice = getChainConfig(chainId)?.max_gas_price; const currentGasPrice = await web3Provider.getGasPrice(chainId); + if (maxGasPrice !== undefined && currentGasPrice > maxGasPrice) { - throw ConfigError.maxGasPrice(`Current gas price ${currentGasPrice} is higher than max price ${maxGasPrice}`) + throw ConfigError.maxGasPrice(`Current gas price ${currentGasPrice} is higher than max price ${maxGasPrice}`); } const requestResponse: RelayerResponse = diff --git a/packages/relayer/src/interfaces/relayer/common.ts b/packages/relayer/src/interfaces/relayer/common.ts index c665eda..7319b79 100644 --- a/packages/relayer/src/interfaces/relayer/common.ts +++ b/packages/relayer/src/interfaces/relayer/common.ts @@ -3,8 +3,11 @@ * Represents the relayer commitment for a pre-built withdrawal. */ export interface FeeCommitment { - expiration: number, withdrawalData: `0x${string}`, + asset: `0x${string}`, + expiration: number, + amount: bigint, + extraGas: boolean, signedRelayerCommitment: `0x${string}`, } diff --git a/packages/relayer/src/interfaces/relayer/quote.ts b/packages/relayer/src/interfaces/relayer/quote.ts index 72aa833..60e283a 100644 --- a/packages/relayer/src/interfaces/relayer/quote.ts +++ b/packages/relayer/src/interfaces/relayer/quote.ts @@ -1,5 +1,3 @@ -import { FeeCommitment } from "./common.js"; - export interface QuotetBody { /** Chain ID to process the request on */ chainId: string | number; @@ -9,10 +7,20 @@ export interface QuotetBody { asset: string; /** Asset address */ recipient?: string; + /** Extra gas flag */ + extraGas: boolean; } export interface QuoteResponse { baseFeeBPS: bigint, feeBPS: bigint, - feeCommitment?: FeeCommitment + gasPrice: bigint, + detail: { [key: string]: { gas: bigint, eth: bigint; } | undefined; }; + feeCommitment?: { + expiration: number, + withdrawalData: `0x${string}`, + amount: string, + extraGas: boolean, + signedRelayerCommitment: `0x${string}`, + }; } diff --git a/packages/relayer/src/interfaces/relayer/request.ts b/packages/relayer/src/interfaces/relayer/request.ts index c5feab4..1d9036a 100644 --- a/packages/relayer/src/interfaces/relayer/request.ts +++ b/packages/relayer/src/interfaces/relayer/request.ts @@ -75,6 +75,8 @@ export interface RelayerResponse { requestId: string; /** Optional transaction hash */ txHash?: string; + /** Optional transaction swap hash */ + txSwap?: string; /** Optional error message */ error?: string; } diff --git a/packages/relayer/src/middlewares/relayer/request.ts b/packages/relayer/src/middlewares/relayer/request.ts index 28d5d20..e4e6e88 100644 --- a/packages/relayer/src/middlewares/relayer/request.ts +++ b/packages/relayer/src/middlewares/relayer/request.ts @@ -1,7 +1,7 @@ import { NextFunction, Request, Response } from "express"; import { ValidationError } from "../../exceptions/base.exception.js"; import { validateDetailsQuerystring } from "../../schemes/relayer/details.scheme.js"; -import { validateRelayRequestBody } from "../../schemes/relayer/request.scheme.js"; +import { zRelayRequest } from "../../schemes/relayer/request.scheme.js"; import { validateQuoteBody } from "../../schemes/relayer/quote.scheme.js"; // Middleware to validate the details querying @@ -26,11 +26,9 @@ export function validateRelayRequestMiddleware( res: Response, next: NextFunction, ) { - const isValid = validateRelayRequestBody(req.body); - if (!isValid) { - const messages: string[] = []; - validateRelayRequestBody.errors?.forEach(e => e?.message ? messages.push(e.message) : undefined); - next(ValidationError.invalidInput({ message: messages.join("\n") })); + const { success, error } = zRelayRequest.safeParse(req.body); + if (!success) { + next(ValidationError.invalidInput({ message: error.errors.map(i => `${i.path.join('.')}: ${i.message}`).join("\n") })); return; } next(); diff --git a/packages/relayer/src/providers/index.ts b/packages/relayer/src/providers/index.ts index 9c580ec..7e874ea 100644 --- a/packages/relayer/src/providers/index.ts +++ b/packages/relayer/src/providers/index.ts @@ -1,11 +1,11 @@ import { Web3Provider } from "./web3.provider.js"; -import { UniswapProvider } from "./uniswap.provider.js" +import { UniswapProvider } from "./uniswap/uniswap.provider.js" import { QuoteProvider } from "./quote.provider.js"; export { db } from "./db.provider.js"; export { SdkProvider } from "./sdk.provider.js"; export { SqliteDatabase } from "./sqlite.provider.js"; -export { UniswapProvider } from "./uniswap.provider.js" +export { UniswapProvider } from "./uniswap/uniswap.provider.js" export const web3Provider = new Web3Provider(); export const uniswapProvider = new UniswapProvider(); diff --git a/packages/relayer/src/providers/quote.provider.ts b/packages/relayer/src/providers/quote.provider.ts index 1076222..e09b648 100644 --- a/packages/relayer/src/providers/quote.provider.ts +++ b/packages/relayer/src/providers/quote.provider.ts @@ -6,9 +6,9 @@ export class QuoteProvider { constructor() { } - async quoteNativeTokenInERC20(chainId: number, addressIn: Address, amountIn: bigint): Promise<{ num: bigint, den: bigint }> { - const { in: in_, out } = (await uniswapProvider.quoteNativeToken(chainId, addressIn, amountIn))!; - return { num: out.amount, den: in_.amount }; + async quoteNativeTokenInERC20(chainId: number, addressIn: Address, amountIn: bigint): Promise<{ num: bigint, den: bigint, path: (string|number)[] }> { + const { in: in_, out, path } = (await uniswapProvider.quoteNativeToken(chainId, addressIn, amountIn))!; + return { num: out.amount, den: in_.amount, path }; } } diff --git a/packages/relayer/src/providers/uniswap.provider.ts b/packages/relayer/src/providers/uniswap.provider.ts deleted file mode 100644 index eef5b6d..0000000 --- a/packages/relayer/src/providers/uniswap.provider.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Token } from '@uniswap/sdk-core' -import { FeeAmount } from '@uniswap/v3-sdk' -import { Address, getContract } from 'viem' - -import { web3Provider } from '../providers/index.js' -import { BlockchainError, RelayerError } from '../exceptions/base.exception.js' -import { isViemError } from '../utils.js' -import { QUOTER_CONTRACT_ADDRESS, WRAPPED_NATIVE_TOKEN_ADDRESS } from './uniswap/constants.js' -import { IERC20MinimalABI } from './uniswap/erc20.abi.js' -import { QuoterV2ABI } from './uniswap/quoterV2.abi.js' - -export type UniswapQuote = { - chainId: number; - addressIn: string; - addressOut: string; - amountIn: bigint; -}; - -type QuoteToken = { amount: bigint, decimals: number } -export type Quote = { - in: QuoteToken - out: QuoteToken -}; - -export class UniswapProvider { - - async getTokenInfo(chainId: number, address: Address): Promise { - const contract = getContract({ - address, - abi: IERC20MinimalABI.abi, - client: web3Provider.client(chainId) - }); - const [decimals, symbol] = await Promise.all([ - contract.read.decimals(), - contract.read.symbol(), - ]) - return new Token(chainId, address, Number(decimals), symbol); - } - - async quoteNativeToken(chainId: number, addressIn: Address, amountIn: bigint): Promise { - const addressOut = WRAPPED_NATIVE_TOKEN_ADDRESS[chainId.toString()]! - return this.quote({ - chainId, - amountIn, - addressOut, - addressIn - }); - } - - async quote({ chainId, addressIn, addressOut, amountIn }: UniswapQuote) { - const tokenIn = await this.getTokenInfo(chainId, addressIn as Address); - const tokenOut = await this.getTokenInfo(chainId, addressOut as Address); - const quoterContract = getContract({ - address: QUOTER_CONTRACT_ADDRESS[chainId.toString()]!, - abi: QuoterV2ABI.abi, - client: web3Provider.client(chainId) - }); - - try { - - const quotedAmountOut = await quoterContract.simulate.quoteExactInputSingle([{ - tokenIn: tokenIn.address as Address, - tokenOut: tokenOut.address as Address, - fee: FeeAmount.MEDIUM, - amountIn, - sqrtPriceLimitX96: 0n, - }]) - - // amount, sqrtPriceX96After, tickAfter, gasEstimate - const [amount, , , ] = quotedAmountOut.result; - return { - in: { - amount: amountIn, decimals: tokenIn.decimals - }, - out: { - amount, decimals: tokenOut.decimals - } - }; - } catch (error) { - if (error instanceof Error && isViemError(error)) { - const { metaMessages, shortMessage } = error; - throw BlockchainError.txError((metaMessages ? metaMessages[0] : undefined) || shortMessage) - } else { - throw RelayerError.unknown("Something went wrong while quoting") - } - } - - } - -} diff --git a/packages/relayer/src/providers/uniswap/abis/erc20.abi.ts b/packages/relayer/src/providers/uniswap/abis/erc20.abi.ts new file mode 100644 index 0000000..39535b5 --- /dev/null +++ b/packages/relayer/src/providers/uniswap/abis/erc20.abi.ts @@ -0,0 +1,200 @@ +export const IERC20MinimalABI = [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] as const; diff --git a/packages/relayer/src/providers/uniswap/abis/factoryV3.abi.ts b/packages/relayer/src/providers/uniswap/abis/factoryV3.abi.ts new file mode 100644 index 0000000..f2c10ec --- /dev/null +++ b/packages/relayer/src/providers/uniswap/abis/factoryV3.abi.ts @@ -0,0 +1,15 @@ +export const FactoryV3ABI = [ + { + type: 'function', + name: 'getPool', + stateMutability: 'view', + inputs: [ + { name: 'tokenA', type: 'address' }, + { name: 'tokenB', type: 'address' }, + { name: 'fee', type: 'uint24' } + ], + outputs: [ + { name: 'pool', type: 'address' } + ] + } +] as const; diff --git a/packages/relayer/src/providers/uniswap/abis/permit2.abi.ts b/packages/relayer/src/providers/uniswap/abis/permit2.abi.ts new file mode 100644 index 0000000..83bf4bc --- /dev/null +++ b/packages/relayer/src/providers/uniswap/abis/permit2.abi.ts @@ -0,0 +1,901 @@ +export const Permit2ABI = [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "AllowanceExpired", + "type": "error" + }, + { + "inputs": [], + "name": "ExcessiveInvalidation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "maxAmount", + "type": "uint256" + } + ], + "name": "InvalidAmount", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidContractSignature", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidNonce", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSignature", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSignatureLength", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSigner", + "type": "error" + }, + { + "inputs": [], + "name": "LengthMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "signatureDeadline", + "type": "uint256" + } + ], + "name": "SignatureExpired", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint160", + "name": "amount", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "uint48", + "name": "expiration", + "type": "uint48" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "Lockdown", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint48", + "name": "newNonce", + "type": "uint48" + }, + { + "indexed": false, + "internalType": "uint48", + "name": "oldNonce", + "type": "uint48" + } + ], + "name": "NonceInvalidation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint160", + "name": "amount", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "uint48", + "name": "expiration", + "type": "uint48" + }, + { + "indexed": false, + "internalType": "uint48", + "name": "nonce", + "type": "uint48" + } + ], + "name": "Permit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "word", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "mask", + "type": "uint256" + } + ], + "name": "UnorderedNonceInvalidation", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint160", + "name": "amount", + "type": "uint160" + }, + { + "internalType": "uint48", + "name": "expiration", + "type": "uint48" + }, + { + "internalType": "uint48", + "name": "nonce", + "type": "uint48" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint160", + "name": "amount", + "type": "uint160" + }, + { + "internalType": "uint48", + "name": "expiration", + "type": "uint48" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint48", + "name": "newNonce", + "type": "uint48" + } + ], + "name": "invalidateNonces", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "wordPos", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "mask", + "type": "uint256" + } + ], + "name": "invalidateUnorderedNonces", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "internalType": "struct IAllowanceTransfer.TokenSpenderPair[]", + "name": "approvals", + "type": "tuple[]" + } + ], + "name": "lockdown", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "nonceBitmap", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint160", + "name": "amount", + "type": "uint160" + }, + { + "internalType": "uint48", + "name": "expiration", + "type": "uint48" + }, + { + "internalType": "uint48", + "name": "nonce", + "type": "uint48" + } + ], + "internalType": "struct IAllowanceTransfer.PermitDetails[]", + "name": "details", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "sigDeadline", + "type": "uint256" + } + ], + "internalType": "struct IAllowanceTransfer.PermitBatch", + "name": "permitBatch", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint160", + "name": "amount", + "type": "uint160" + }, + { + "internalType": "uint48", + "name": "expiration", + "type": "uint48" + }, + { + "internalType": "uint48", + "name": "nonce", + "type": "uint48" + } + ], + "internalType": "struct IAllowanceTransfer.PermitDetails", + "name": "details", + "type": "tuple" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "sigDeadline", + "type": "uint256" + } + ], + "internalType": "struct IAllowanceTransfer.PermitSingle", + "name": "permitSingle", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct ISignatureTransfer.TokenPermissions", + "name": "permitted", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct ISignatureTransfer.PermitTransferFrom", + "name": "permit", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "requestedAmount", + "type": "uint256" + } + ], + "internalType": "struct ISignatureTransfer.SignatureTransferDetails", + "name": "transferDetails", + "type": "tuple" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "permitTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct ISignatureTransfer.TokenPermissions[]", + "name": "permitted", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct ISignatureTransfer.PermitBatchTransferFrom", + "name": "permit", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "requestedAmount", + "type": "uint256" + } + ], + "internalType": "struct ISignatureTransfer.SignatureTransferDetails[]", + "name": "transferDetails", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "permitTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct ISignatureTransfer.TokenPermissions", + "name": "permitted", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct ISignatureTransfer.PermitTransferFrom", + "name": "permit", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "requestedAmount", + "type": "uint256" + } + ], + "internalType": "struct ISignatureTransfer.SignatureTransferDetails", + "name": "transferDetails", + "type": "tuple" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "witness", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "witnessTypeString", + "type": "string" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "permitWitnessTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct ISignatureTransfer.TokenPermissions[]", + "name": "permitted", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct ISignatureTransfer.PermitBatchTransferFrom", + "name": "permit", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "requestedAmount", + "type": "uint256" + } + ], + "internalType": "struct ISignatureTransfer.SignatureTransferDetails[]", + "name": "transferDetails", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "witness", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "witnessTypeString", + "type": "string" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "permitWitnessTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint160", + "name": "amount", + "type": "uint160" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "internalType": "struct IAllowanceTransfer.AllowanceTransferDetails[]", + "name": "transferDetails", + "type": "tuple[]" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint160", + "name": "amount", + "type": "uint160" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] as const; diff --git a/packages/relayer/src/providers/uniswap/quoter.abi.ts b/packages/relayer/src/providers/uniswap/abis/quoter.abi.ts similarity index 100% rename from packages/relayer/src/providers/uniswap/quoter.abi.ts rename to packages/relayer/src/providers/uniswap/abis/quoter.abi.ts diff --git a/packages/relayer/src/providers/uniswap/abis/quoterV2.abi.ts b/packages/relayer/src/providers/uniswap/abis/quoterV2.abi.ts new file mode 100644 index 0000000..07bb42a --- /dev/null +++ b/packages/relayer/src/providers/uniswap/abis/quoterV2.abi.ts @@ -0,0 +1,267 @@ +export const QuoterV2ABI = [ + { + "inputs": [ + { + "internalType": "address", + "name": "_factory", + "type": "address" + }, + { + "internalType": "address", + "name": "_WETH9", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "WETH9", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "path", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + } + ], + "name": "quoteExactInput", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint160[]", + "name": "sqrtPriceX96AfterList", + "type": "uint160[]" + }, + { + "internalType": "uint32[]", + "name": "initializedTicksCrossedList", + "type": "uint32[]" + }, + { + "internalType": "uint256", + "name": "gasEstimate", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "internalType": "struct IQuoterV2.QuoteExactInputSingleParams", + "name": "params", + "type": "tuple" + } + ], + "name": "quoteExactInputSingle", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceX96After", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "initializedTicksCrossed", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "gasEstimate", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "path", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "name": "quoteExactOutput", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint160[]", + "name": "sqrtPriceX96AfterList", + "type": "uint160[]" + }, + { + "internalType": "uint32[]", + "name": "initializedTicksCrossedList", + "type": "uint32[]" + }, + { + "internalType": "uint256", + "name": "gasEstimate", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "internalType": "struct IQuoterV2.QuoteExactOutputSingleParams", + "name": "params", + "type": "tuple" + } + ], + "name": "quoteExactOutputSingle", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceX96After", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "initializedTicksCrossed", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "gasEstimate", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "amount0Delta", + "type": "int256" + }, + { + "internalType": "int256", + "name": "amount1Delta", + "type": "int256" + }, + { + "internalType": "bytes", + "name": "path", + "type": "bytes" + } + ], + "name": "uniswapV3SwapCallback", + "outputs": [], + "stateMutability": "view", + "type": "function" + } +] as const; diff --git a/packages/relayer/src/providers/uniswap/abis/universalRouter.abi.ts b/packages/relayer/src/providers/uniswap/abis/universalRouter.abi.ts new file mode 100644 index 0000000..63d60c0 --- /dev/null +++ b/packages/relayer/src/providers/uniswap/abis/universalRouter.abi.ts @@ -0,0 +1,443 @@ +export const UniversalRouterABI = [ + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "permit2", + "type": "address" + }, + { + "internalType": "address", + "name": "weth9", + "type": "address" + }, + { + "internalType": "address", + "name": "v2Factory", + "type": "address" + }, + { + "internalType": "address", + "name": "v3Factory", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "pairInitCodeHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "poolInitCodeHash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "v4PoolManager", + "type": "address" + }, + { + "internalType": "address", + "name": "v3NFTPositionManager", + "type": "address" + }, + { + "internalType": "address", + "name": "v4PositionManager", + "type": "address" + } + ], + "internalType": "struct RouterParameters", + "name": "params", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "BalanceTooLow", + "type": "error" + }, + { + "inputs": [], + "name": "ContractLocked", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "Currency", + "name": "currency", + "type": "address" + } + ], + "name": "DeltaNotNegative", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "Currency", + "name": "currency", + "type": "address" + } + ], + "name": "DeltaNotPositive", + "type": "error" + }, + { + "inputs": [], + "name": "ETHNotAccepted", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "commandIndex", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "message", + "type": "bytes" + } + ], + "name": "ExecutionFailed", + "type": "error" + }, + { + "inputs": [], + "name": "FromAddressIsNotOwner", + "type": "error" + }, + { + "inputs": [], + "name": "InputLengthMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientETH", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientToken", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "action", + "type": "bytes4" + } + ], + "name": "InvalidAction", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidBips", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "commandType", + "type": "uint256" + } + ], + "name": "InvalidCommandType", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidEthSender", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPath", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidReserves", + "type": "error" + }, + { + "inputs": [], + "name": "LengthMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "NotAuthorizedForToken", + "type": "error" + }, + { + "inputs": [], + "name": "NotPoolManager", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyMintAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "SliceOutOfBounds", + "type": "error" + }, + { + "inputs": [], + "name": "TransactionDeadlinePassed", + "type": "error" + }, + { + "inputs": [], + "name": "UnsafeCast", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "action", + "type": "uint256" + } + ], + "name": "UnsupportedAction", + "type": "error" + }, + { + "inputs": [], + "name": "V2InvalidPath", + "type": "error" + }, + { + "inputs": [], + "name": "V2TooLittleReceived", + "type": "error" + }, + { + "inputs": [], + "name": "V2TooMuchRequested", + "type": "error" + }, + { + "inputs": [], + "name": "V3InvalidAmountOut", + "type": "error" + }, + { + "inputs": [], + "name": "V3InvalidCaller", + "type": "error" + }, + { + "inputs": [], + "name": "V3InvalidSwap", + "type": "error" + }, + { + "inputs": [], + "name": "V3TooLittleReceived", + "type": "error" + }, + { + "inputs": [], + "name": "V3TooMuchRequested", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "minAmountOutReceived", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountReceived", + "type": "uint256" + } + ], + "name": "V4TooLittleReceived", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "maxAmountInRequested", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountRequested", + "type": "uint256" + } + ], + "name": "V4TooMuchRequested", + "type": "error" + }, + { + "inputs": [], + "name": "V3_POSITION_MANAGER", + "outputs": [ + { + "internalType": "contract INonfungiblePositionManager", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "V4_POSITION_MANAGER", + "outputs": [ + { + "internalType": "contract IPositionManager", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "commands", + "type": "bytes" + }, + { + "internalType": "bytes[]", + "name": "inputs", + "type": "bytes[]" + } + ], + "name": "execute", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "commands", + "type": "bytes" + }, + { + "internalType": "bytes[]", + "name": "inputs", + "type": "bytes[]" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "execute", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "msgSender", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "poolManager", + "outputs": [ + { + "internalType": "contract IPoolManager", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "amount0Delta", + "type": "int256" + }, + { + "internalType": "int256", + "name": "amount1Delta", + "type": "int256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "uniswapV3SwapCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "unlockCallback", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] as const; diff --git a/packages/relayer/src/providers/uniswap/abis/v3pool.abi.ts b/packages/relayer/src/providers/uniswap/abis/v3pool.abi.ts new file mode 100644 index 0000000..a165414 --- /dev/null +++ b/packages/relayer/src/providers/uniswap/abis/v3pool.abi.ts @@ -0,0 +1,983 @@ +export const v3PoolABI = [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Burn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "name": "Collect", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "name": "CollectProtocol", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paid0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paid1", + "type": "uint256" + } + ], + "name": "Flash", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint16", + "name": "observationCardinalityNextOld", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "observationCardinalityNextNew", + "type": "uint16" + } + ], + "name": "IncreaseObservationCardinalityNext", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "Initialize", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol0Old", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol1Old", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol0New", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol1New", + "type": "uint8" + } + ], + "name": "SetFeeProtocol", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount0", + "type": "int256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount1", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "Swap", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount0Requested", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Requested", + "type": "uint128" + } + ], + "name": "collect", + "outputs": [ + { + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint128", + "name": "amount0Requested", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Requested", + "type": "uint128" + } + ], + "name": "collectProtocol", + "outputs": [ + { + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "fee", + "outputs": [ + { + "internalType": "uint24", + "name": "", + "type": "uint24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeGrowthGlobal0X128", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeGrowthGlobal1X128", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "flash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "observationCardinalityNext", + "type": "uint16" + } + ], + "name": "increaseObservationCardinalityNext", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "liquidity", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxLiquidityPerTick", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "observations", + "outputs": [ + { + "internalType": "uint32", + "name": "blockTimestamp", + "type": "uint32" + }, + { + "internalType": "int56", + "name": "tickCumulative", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityCumulativeX128", + "type": "uint160" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32[]", + "name": "secondsAgos", + "type": "uint32[]" + } + ], + "name": "observe", + "outputs": [ + { + "internalType": "int56[]", + "name": "tickCumulatives", + "type": "int56[]" + }, + { + "internalType": "uint160[]", + "name": "secondsPerLiquidityCumulativeX128s", + "type": "uint160[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "key", + "type": "bytes32" + } + ], + "name": "positions", + "outputs": [ + { + "internalType": "uint128", + "name": "_liquidity", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside0LastX128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside1LastX128", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "tokensOwed0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "tokensOwed1", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "protocolFees", + "outputs": [ + { + "internalType": "uint128", + "name": "token0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "token1", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "feeProtocol0", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "feeProtocol1", + "type": "uint8" + } + ], + "name": "setFeeProtocol", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "slot0", + "outputs": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "internalType": "uint16", + "name": "observationIndex", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinality", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinalityNext", + "type": "uint16" + }, + { + "internalType": "uint8", + "name": "feeProtocol", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "unlocked", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + } + ], + "name": "snapshotCumulativesInside", + "outputs": [ + { + "internalType": "int56", + "name": "tickCumulativeInside", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityInsideX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "secondsInside", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "zeroForOne", + "type": "bool" + }, + { + "internalType": "int256", + "name": "amountSpecified", + "type": "int256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "int256", + "name": "amount0", + "type": "int256" + }, + { + "internalType": "int256", + "name": "amount1", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int16", + "name": "wordPosition", + "type": "int16" + } + ], + "name": "tickBitmap", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tickSpacing", + "outputs": [ + { + "internalType": "int24", + "name": "", + "type": "int24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "ticks", + "outputs": [ + { + "internalType": "uint128", + "name": "liquidityGross", + "type": "uint128" + }, + { + "internalType": "int128", + "name": "liquidityNet", + "type": "int128" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside0X128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside1X128", + "type": "uint256" + }, + { + "internalType": "int56", + "name": "tickCumulativeOutside", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityOutsideX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "secondsOutside", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token0", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token1", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ] as const; diff --git a/packages/relayer/src/providers/uniswap/commands.ts b/packages/relayer/src/providers/uniswap/commands.ts new file mode 100644 index 0000000..142857f --- /dev/null +++ b/packages/relayer/src/providers/uniswap/commands.ts @@ -0,0 +1,198 @@ +import { encodeAbiParameters, getAddress } from "viem"; + +const V3_SWAP_EXACT_IN = 0x00; +const UNWRAP_WETH = 0x0c; +const PERMIT2_TRANSFER_FROM = 0x02; +const SWEEP = 0x04; +const TRANSFER = 0x05; +const PERMIT2_PERMIT = 0x0a; + +export type CommandPair = [number, `0x${string}`]; + +export interface IPermitSingle { + details: { + token: `0x${string}`; + amount: bigint; + expiration: number; + nonce: number; + }; + spender: `0x${string}`; + sigDeadline: bigint; +} + +export interface Permit2Params { + permit: IPermitSingle; + signature: string; +} + +export function permit2({ permit, signature }: Permit2Params): CommandPair { + const encodedInput = encodeAbiParameters( + [ + { + name: 'PermitSingle', type: 'tuple', + components: [ + { + name: "details", type: "tuple", components: [ + { name: "token", type: "address" }, + { name: "amount", type: "uint160" }, + { name: "expiration", type: "uint48" }, + { name: "nonce", type: "uint48" }, + ] + }, + { name: "spender", type: "address" }, + { name: "sigDeadline", type: "uint256" } + ] + }, + { name: 'signature', type: 'bytes' }, + ], + [ + permit, + signature as `0x${string}` + ] + ); + return [PERMIT2_PERMIT, encodedInput]; +} + +interface TransferParams { + token: string; + recipient: `0x${string}`; + amount: bigint; +} + +export function transferWithPermit({ token, recipient, amount }: TransferParams): CommandPair { + const encodedInput = encodeAbiParameters( + [ + { name: 'token', type: 'address' }, + { name: 'recipient', type: 'address' }, + { name: 'amount', type: 'uint256' }, + ], + [ + getAddress(token), getAddress(recipient), amount + ] + ); + return [PERMIT2_TRANSFER_FROM, encodedInput]; +} + +export function transfer({ token, recipient, amount }: TransferParams): CommandPair { + const encodedInput = encodeAbiParameters( + [ + { name: 'token', type: 'address' }, + { name: 'recipient', type: 'address' }, + { name: 'amount', type: 'uint256' }, + ], + [ + getAddress(token), getAddress(recipient), amount + ] + ); + return [TRANSFER, encodedInput]; +} + +interface V3SwapExactInParams { + recipient: string; + amountIn: bigint; + minAmountOut: bigint; + path: `0x${string}`; + payerIsUser: boolean; +} + +export function swapV3ExactIn({ + recipient, + amountIn, + minAmountOut, + path, + payerIsUser +}: V3SwapExactInParams): CommandPair { + const encodedInput = encodeAbiParameters( + [ + { name: 'recipient', type: 'address' }, + { name: 'amountIn', type: 'uint256' }, + { name: 'minAmountOut', type: 'uint256' }, + { name: 'path', type: 'bytes' }, + { name: 'payerIsUser', type: 'bool' }, + ], + [ + getAddress(recipient), amountIn, minAmountOut, path, payerIsUser + ] + ); + return [V3_SWAP_EXACT_IN, encodedInput]; +} + +interface UnwrapWethParams { + recipient: string; + minAmountOut: bigint; +} + +export function unwrapWeth({ recipient, minAmountOut }: UnwrapWethParams): CommandPair { + const encodedInput = encodeAbiParameters( + [ + { name: 'recipient', type: 'address' }, + { name: 'minAmountOut', type: 'uint256' }, + ], + [ + getAddress(recipient), minAmountOut + ] + ); + return [UNWRAP_WETH, encodedInput]; +} + +interface SweepParams { + token: string; + recipient: string; + minAmountOut: bigint; +} + +export function sweep({ token, recipient, minAmountOut }: SweepParams): CommandPair { + const encodedInput = encodeAbiParameters( + [ + { name: 'token', type: 'address' }, + { name: 'recipient', type: 'address' }, + { name: 'minAmountOut', type: 'uint256' }, + ], + [ + getAddress(token), getAddress(recipient), minAmountOut + ] + ); + return [SWEEP, encodedInput]; +} + +export const enum Command { + permit2 = "permit2", + transfer = "transfer", + transferWithPermit = "transferWithPermit", + swapV3ExactIn = "swapV3ExactIn", + unrwapWeth = "unrwapWeth", + sweep = "sweep", +}; + +interface CommandParams { + [Command.permit2]: Permit2Params; + [Command.transfer]: TransferParams; + [Command.transferWithPermit]: TransferParams; + [Command.swapV3ExactIn]: V3SwapExactInParams; + [Command.unrwapWeth]: UnwrapWethParams; + [Command.sweep]: SweepParams; +}; + +type CommandFnMap = { + [K in keyof CommandParams]: (params: CommandParams[K]) => CommandPair; +}; + +export type Instruction = { + [K in keyof CommandParams]: { + command: K; + params: CommandParams[K]; + } +}[keyof CommandParams]; + +export const commandFnMap: CommandFnMap = { + [Command.permit2]: permit2, + [Command.transfer]: transfer, + [Command.transferWithPermit]: transferWithPermit, + [Command.sweep]: sweep, + [Command.swapV3ExactIn]: swapV3ExactIn, + [Command.unrwapWeth]: unwrapWeth, +}; + +export function encodeInstruction(ins: { command: K, params: CommandParams[K]; }): CommandPair { + return commandFnMap[ins.command](ins.params); +} diff --git a/packages/relayer/src/providers/uniswap/constants.ts b/packages/relayer/src/providers/uniswap/constants.ts index 5a3c764..91e3857 100644 --- a/packages/relayer/src/providers/uniswap/constants.ts +++ b/packages/relayer/src/providers/uniswap/constants.ts @@ -1,16 +1,33 @@ -import { Address } from "viem"; +import { QUOTER_ADDRESSES, V3_CORE_FACTORY_ADDRESSES } from "@uniswap/sdk-core"; +import { UNIVERSAL_ROUTER_ADDRESS, UniversalRouterVersion } from "@uniswap/universal-router-sdk"; +import { FeeAmount } from "@uniswap/v3-sdk"; +import { Address, getAddress } from "viem"; + +export { WETH9 as WRAPPED_NATIVE_TOKEN_ADDRESS } from "@uniswap/sdk-core"; + +export const PERMIT2_ADDRESS = '0x000000000022D473030F116dDEE9F6B43aC78BA3' + +export function permit2Address(chainId?: number): `0x${string}` { + switch (chainId) { + case 324: + return '0x0000000000225e31D15943971F47aD3022F714Fa' + default: + return PERMIT2_ADDRESS + } +} /** -* Mainnet, Polygon, Optimism, Arbitrum, Testnets Address +* Mainnet (1), Polygon (137), Optimism (10), Arbitrum (42161), Testnets Address (11155111) * source: https://github.com/Uniswap/v3-periphery/blob/main/deploys.md */ -export const QUOTER_CONTRACT_ADDRESS: Record = { - "1": "0x61fFE014bA17989E743c5F6cB21bF9697530B21e", // Ethereum - "137": "0x61fFE014bA17989E743c5F6cB21bF9697530B21e", // polygon - "10": "0x61fFE014bA17989E743c5F6cB21bF9697530B21e", // Optimism - "42161": "0x61fFE014bA17989E743c5F6cB21bF9697530B21e", // Arbitrum - "11155111": "0xEd1f6473345F45b75F8179591dd5bA1888cf2FB3", // Sepolia -}; + +// export const QUOTER_CONTRACT_ADDRESS: Record = { +// "1": "0x61fFE014bA17989E743c5F6cB21bF9697530B21e", // Ethereum +// "137": "0x61fFE014bA17989E743c5F6cB21bF9697530B21e", // polygon +// "10": "0x61fFE014bA17989E743c5F6cB21bF9697530B21e", // Optimism +// "42161": "0x61fFE014bA17989E743c5F6cB21bF9697530B21e", // Arbitrum +// "11155111": "0xEd1f6473345F45b75F8179591dd5bA1888cf2FB3", // Sepolia +// }; export const FACTORY_CONTRACT_ADDRESS: Record = { "1": "0x1F98431c8aD98523631AE4a59f267346ea31F984", // Ethereum @@ -18,13 +35,45 @@ export const FACTORY_CONTRACT_ADDRESS: Record = { "10": "0x1F98431c8aD98523631AE4a59f267346ea31F984", // Optimism "42161": "0x1F98431c8aD98523631AE4a59f267346ea31F984", // Arbitrum "11155111": "0x0227628f3f023bb0b980b67d528571c95c6dac1c", // Sepolia +}; + +// Common intermediate tokens for multi-hop routing +export const INTERMEDIATE_TOKENS: Record = { + '1': [ // Mainnet + '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC + '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT + '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI + '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', // WBTC + ], + '11155111': [ // Sepolia + '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', // USDC + '0x7169D38820dfd117C3FA1f22a697dBA58d90BA06', // USDT + ], +}; + +export function getRouterAddress(chainId: number) { + return getAddress(UNIVERSAL_ROUTER_ADDRESS(UniversalRouterVersion.V2_0, chainId)); } -export const WRAPPED_NATIVE_TOKEN_ADDRESS: Record = { - "1": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // mainnet (WETH) - "137": "0x0000000000000000000000000000000000001010", // polygon (POL) - // "137": "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", // (WPOL) TODO: compare which token to use - "10": "0x4200000000000000000000000000000000000006", // Optimism (WETH) - "42161": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", // Arbitrum (WETH) - "11155111": "0xfff9976782d46cc05630d1f6ebab18b2324d6b14", // sepolia (WETH) +export function getPermit2Address(chainId: number) { + return getAddress(permit2Address(chainId)); } + +export function getV3Factory(chainId: number) { + return getAddress(V3_CORE_FACTORY_ADDRESSES[chainId]!) +} + +export function getQuoterAddress(chainId: number) { + const mainnetQuoter = "0x61fFE014bA17989E743c5F6cB21bF9697530B21e"; + return getAddress(chainId !== 1 ? QUOTER_ADDRESSES[chainId]! : mainnetQuoter) +} + +export const FeeTiers: FeeAmount[] = [ + FeeAmount.LOWEST, + FeeAmount.LOW_200, + FeeAmount.LOW_300, + FeeAmount.LOW_400, + FeeAmount.LOW, + FeeAmount.MEDIUM, + FeeAmount.HIGH, +]; diff --git a/packages/relayer/src/providers/uniswap/createPermit.ts b/packages/relayer/src/providers/uniswap/createPermit.ts new file mode 100644 index 0000000..739ee99 --- /dev/null +++ b/packages/relayer/src/providers/uniswap/createPermit.ts @@ -0,0 +1,58 @@ +import { Address, CustomSource, getContract } from "viem"; + +import { web3Provider } from "../index.js"; +import { Permit2ABI } from "./abis/permit2.abi.js"; +import { createPermitSingleData, PermitSingle } from "./permit2.js"; + +export async function createPermit2({ + signer, + chainId, + permit2Address, + routerAddress, + assetAddress, + allowanceAmount, +}: { + signer: Signer, + chainId: number, + permit2Address: Address, + routerAddress: Address, + assetAddress: Address, + allowanceAmount: bigint, +}): Promise<[PermitSingle, `0x${string}`]> { + + const deadline = Math.floor(3600 + Number(new Date()) / 1000); // one hour + + const permitContract = getContract({ + abi: Permit2ABI, + address: permit2Address, + client: web3Provider.client(chainId) + }); + + const allowance = await permitContract.read.allowance([ + signer.address, + assetAddress, + routerAddress + ]); + const [, , nonce] = allowance; + + const permitSingle = { + spender: routerAddress, + details: { + token: assetAddress, + amount: allowanceAmount, + expiration: deadline, + nonce + }, + sigDeadline: BigInt(deadline) + }; + const permitData = createPermitSingleData(permitSingle, permit2Address, chainId); + + const signature = await signer.signTypedData({ + domain: permitData.domain, + types: permitData.types, + message: permitSingle, + primaryType: "PermitSingle" + }); + + return [permitSingle, signature]; +} diff --git a/packages/relayer/src/providers/uniswap/erc20.abi.ts b/packages/relayer/src/providers/uniswap/erc20.abi.ts deleted file mode 100644 index ffe5abe..0000000 --- a/packages/relayer/src/providers/uniswap/erc20.abi.ts +++ /dev/null @@ -1,202 +0,0 @@ -export const IERC20MinimalABI = { - "abi": [ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - } - ], -} as const; diff --git a/packages/relayer/src/providers/uniswap/factory.abi.ts b/packages/relayer/src/providers/uniswap/factory.abi.ts new file mode 100644 index 0000000..d6a8946 --- /dev/null +++ b/packages/relayer/src/providers/uniswap/factory.abi.ts @@ -0,0 +1,17 @@ +export const FactoryABI = { + abi: [ + { + type: 'function', + name: 'getPool', + stateMutability: 'view', + inputs: [ + { name: 'tokenA', type: 'address' }, + { name: 'tokenB', type: 'address' }, + { name: 'fee', type: 'uint24' } + ], + outputs: [ + { name: 'pool', type: 'address' } + ] + } + ] +} as const; diff --git a/packages/relayer/src/providers/uniswap/permit2.ts b/packages/relayer/src/providers/uniswap/permit2.ts new file mode 100644 index 0000000..c5b57e1 --- /dev/null +++ b/packages/relayer/src/providers/uniswap/permit2.ts @@ -0,0 +1,60 @@ +const PERMIT2_DOMAIN_NAME = 'Permit2'; + +export function permit2Domain(permit2Address: `0x${string}`, chainId: number) { + return { + name: PERMIT2_DOMAIN_NAME, + chainId, + verifyingContract: permit2Address, + }; +} + +export const PERMIT_DETAILS = [ + { name: 'token', type: 'address' }, + { name: 'amount', type: 'uint160' }, + { name: 'expiration', type: 'uint48' }, + { name: 'nonce', type: 'uint48' }, +]; + +export const PERMIT_TYPES = { + PermitSingle: [ + { name: 'details', type: 'PermitDetails' }, + { name: 'spender', type: 'address' }, + { name: 'sigDeadline', type: 'uint256' }, + ], + PermitDetails: PERMIT_DETAILS, +}; + +export interface PermitDetails { + token: `0x${string}`, + amount: bigint, + expiration: number, + nonce: number; +} + +export interface PermitSingle { + details: PermitDetails; + spender: `0x${string}`; + sigDeadline: bigint; +} + +const MaxUint48 = BigInt('0xffffffffffff'); +const MaxUint160 = BigInt('0xffffffffffffffffffffffffffffffffffffffff'); +const MaxAllowanceTransferAmount = MaxUint160; +const MaxAllowanceExpiration = MaxUint48; +const MaxOrderedNonce = MaxUint48; + +export function validatePermitDetails(details: PermitDetails) { + if (MaxOrderedNonce <= details.nonce) throw new Error('NONCE_OUT_OF_RANGE'); + if (MaxAllowanceTransferAmount <= details.amount) throw new Error('AMOUNT_OUT_OF_RANGE'); + if (MaxAllowanceExpiration <= details.expiration) throw new Error('EXPIRATION_OUT_OF_RANGE'); +} + +export function createPermitSingleData(permit: PermitSingle, permit2Address: `0x${string}`, chainId: number) { + const domain = permit2Domain(permit2Address, chainId); + validatePermitDetails(permit.details); + return { + domain, + types: PERMIT_TYPES, + values: permit, + }; +} diff --git a/packages/relayer/src/providers/uniswap/pools.ts b/packages/relayer/src/providers/uniswap/pools.ts new file mode 100644 index 0000000..ce128b7 --- /dev/null +++ b/packages/relayer/src/providers/uniswap/pools.ts @@ -0,0 +1,76 @@ +import { Token, WETH9 } from "@uniswap/sdk-core"; +import { encodeRouteToPath, FeeAmount, Pool, Route as V3Route } from '@uniswap/v3-sdk'; +import { getAddress, getContract, toHex } from "viem"; + +import { getV3Factory } from "./constants.js"; +import { web3Provider } from "../index.js"; +import { v3PoolABI } from "./abis/v3pool.abi.js"; + + +function sortTokens(tokenA: Token, tokenB: Token): [Token, Token] { + return tokenA.address.toLowerCase() < tokenB.address.toLowerCase() + ? [tokenA, tokenB] + : [tokenB, tokenA]; +} + +export async function getPool(chainId: number, tokenA: Token, tokenB: Token, fee: FeeAmount) { + + const V3_FACTORY = getV3Factory(chainId); + + const [token0, token1] = sortTokens(tokenA, tokenB); + const poolAddress = Pool.getAddress(token0, token1, fee, undefined, V3_FACTORY); + + const poolContract = getContract({ + abi: v3PoolABI, + address: getAddress(poolAddress), + client: web3Provider.client(chainId), + }); + + const [liquidity, slot0] = await Promise.all([ + poolContract.read.liquidity(), + poolContract.read.slot0(), + ]); + + const [sqrtPriceX96, tick, , , , ,] = slot0; + + const pool = new Pool(token0, token1, fee, toHex(sqrtPriceX96), toHex(liquidity), tick); + + return pool; +} + +export async function getPoolPath(tokenIn: `0x${string}`, chainId: number) { + const weth = WETH9[chainId]!; + const TokenIn = new Token(chainId, tokenIn, 1); + const pool = await getPool(chainId, TokenIn, weth, FeeAmount.LOW); // 0.05% fee tier + const route = new V3Route([pool], TokenIn, weth); + const pathParams = encodeRouteToPath(route, false) as `0x${string}`; + return pathParams; +} + +export function hopsFromAddressRoute(route: `0x${string}`[]) { + const hops: [`0x${string}`, `0x${string}`][] = []; + for (let i = 1; i <= route.length - 1; i++) { + hops.push([route[i - 1]!, route[i]!]); + } + return hops; +} + +function isAddress(p: `0x${string}` | FeeAmount): p is `0x${string}` { + return (p as `0x${string}`).length !== undefined; +} + +export function encodePath(path: (`0x${string}` | FeeAmount)[]): `0x${string}` { + // Encode the path for quoteExactInput + // Path encoding: token0 (20 bytes) + fee0 (3 bytes) + token1 (20 bytes) + fee1 (3 bytes) + token2 (20 bytes)... + let encodedPath: `0x${string}` = '0x'; + path.forEach(p => { + // is address + if (isAddress(p)) { + encodedPath += p.replace(/^0x/, ""); // Remove '0x' prefix + // is number + } else { + encodedPath += p.toString(16).padStart(6, '0'); + } + }); + return encodedPath +} diff --git a/packages/relayer/src/providers/uniswap/quoterV2.abi.ts b/packages/relayer/src/providers/uniswap/quoterV2.abi.ts deleted file mode 100644 index 5512f1f..0000000 --- a/packages/relayer/src/providers/uniswap/quoterV2.abi.ts +++ /dev/null @@ -1,269 +0,0 @@ -export const QuoterV2ABI = { - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "_factory", - "type": "address" - }, - { - "internalType": "address", - "name": "_WETH9", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "WETH9", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "factory", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "path", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "amountIn", - "type": "uint256" - } - ], - "name": "quoteExactInput", - "outputs": [ - { - "internalType": "uint256", - "name": "amountOut", - "type": "uint256" - }, - { - "internalType": "uint160[]", - "name": "sqrtPriceX96AfterList", - "type": "uint160[]" - }, - { - "internalType": "uint32[]", - "name": "initializedTicksCrossedList", - "type": "uint32[]" - }, - { - "internalType": "uint256", - "name": "gasEstimate", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "tokenIn", - "type": "address" - }, - { - "internalType": "address", - "name": "tokenOut", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amountIn", - "type": "uint256" - }, - { - "internalType": "uint24", - "name": "fee", - "type": "uint24" - }, - { - "internalType": "uint160", - "name": "sqrtPriceLimitX96", - "type": "uint160" - } - ], - "internalType": "struct IQuoterV2.QuoteExactInputSingleParams", - "name": "params", - "type": "tuple" - } - ], - "name": "quoteExactInputSingle", - "outputs": [ - { - "internalType": "uint256", - "name": "amountOut", - "type": "uint256" - }, - { - "internalType": "uint160", - "name": "sqrtPriceX96After", - "type": "uint160" - }, - { - "internalType": "uint32", - "name": "initializedTicksCrossed", - "type": "uint32" - }, - { - "internalType": "uint256", - "name": "gasEstimate", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "path", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "amountOut", - "type": "uint256" - } - ], - "name": "quoteExactOutput", - "outputs": [ - { - "internalType": "uint256", - "name": "amountIn", - "type": "uint256" - }, - { - "internalType": "uint160[]", - "name": "sqrtPriceX96AfterList", - "type": "uint160[]" - }, - { - "internalType": "uint32[]", - "name": "initializedTicksCrossedList", - "type": "uint32[]" - }, - { - "internalType": "uint256", - "name": "gasEstimate", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "tokenIn", - "type": "address" - }, - { - "internalType": "address", - "name": "tokenOut", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint24", - "name": "fee", - "type": "uint24" - }, - { - "internalType": "uint160", - "name": "sqrtPriceLimitX96", - "type": "uint160" - } - ], - "internalType": "struct IQuoterV2.QuoteExactOutputSingleParams", - "name": "params", - "type": "tuple" - } - ], - "name": "quoteExactOutputSingle", - "outputs": [ - { - "internalType": "uint256", - "name": "amountIn", - "type": "uint256" - }, - { - "internalType": "uint160", - "name": "sqrtPriceX96After", - "type": "uint160" - }, - { - "internalType": "uint32", - "name": "initializedTicksCrossed", - "type": "uint32" - }, - { - "internalType": "uint256", - "name": "gasEstimate", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "int256", - "name": "amount0Delta", - "type": "int256" - }, - { - "internalType": "int256", - "name": "amount1Delta", - "type": "int256" - }, - { - "internalType": "bytes", - "name": "path", - "type": "bytes" - } - ], - "name": "uniswapV3SwapCallback", - "outputs": [], - "stateMutability": "view", - "type": "function" - } - ] -} as const; diff --git a/packages/relayer/src/providers/uniswap/uniswap.provider.ts b/packages/relayer/src/providers/uniswap/uniswap.provider.ts new file mode 100644 index 0000000..b770cf2 --- /dev/null +++ b/packages/relayer/src/providers/uniswap/uniswap.provider.ts @@ -0,0 +1,564 @@ +import { Token } from '@uniswap/sdk-core'; +import { FeeAmount } from '@uniswap/v3-sdk'; +import { Account, Address, getAddress, getContract, GetContractReturnType, PublicClient, WriteContractParameters } from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; + +import { getSignerPrivateKey } from "../../config/index.js"; +import { BlockchainError, RelayerError } from '../../exceptions/base.exception.js'; +import { web3Provider } from '../../providers/index.js'; +import { isFeeReceiverSameAsSigner, isViemError } from '../../utils.js'; +import { IERC20MinimalABI } from './abis/erc20.abi.js'; +import { FactoryV3ABI } from './abis/factoryV3.abi.js'; +import { QuoterV2ABI } from './abis/quoterV2.abi.js'; +import { UniversalRouterABI } from './abis/universalRouter.abi.js'; +import { v3PoolABI } from './abis/v3pool.abi.js'; +import { Command, CommandPair, encodeInstruction, Instruction, Permit2Params } from './commands.js'; +import { FeeTiers, getPermit2Address, getQuoterAddress, getRouterAddress, getV3Factory, INTERMEDIATE_TOKENS, WRAPPED_NATIVE_TOKEN_ADDRESS } from './constants.js'; +import { createPermit2 } from './createPermit.js'; +import { encodePath, hopsFromAddressRoute } from './pools.js'; + +export type UniswapQuote = { + chainId: number; + addressIn: string; + addressOut: string; + amountIn: bigint; +}; + +type QuoteToken = { amount: bigint, decimals: number; }; + +export type Quote = { + path: (string | FeeAmount)[]; + in: QuoteToken; + out: QuoteToken; +}; + +interface SwapWithRefundParams { + feeReceiver: `0x${string}`; + nativeRecipient: `0x${string}`; + tokenIn: `0x${string}`; + feeGross: bigint; + refundAmount: bigint; + chainId: number; + feeBase: bigint; +} + +interface CreateInstructionsFeeReceiveerIsRelayer { + router: { address: Address; }; + relayer: Account; + nativeRecipient: Address; + amountToSwap: bigint; + minAmountOut: bigint; + permitParmas: Permit2Params; + pathParams: `0x${string}`; + refundAmount: bigint; +} + +interface CreateInstructionsFeeReceiveerIsNotRelayer extends CreateInstructionsFeeReceiveerIsRelayer { + tokenIn: Address; + feeReceiver: Address; + feeBase: bigint; +} + +const ZERO_ADDRESS = getAddress("0x0000000000000000000000000000000000000000"); +function isNullAddress(a: string) { + return getAddress(a) === ZERO_ADDRESS; +} + +export class UniswapProvider { + + static readonly ZERO_ADDRESS = ZERO_ADDRESS; + + async getTokenInfo(chainId: number, address: Address): Promise { + const contract = getContract({ + address, + abi: IERC20MinimalABI, + client: web3Provider.client(chainId) + }); + const [decimals, symbol] = await Promise.all([ + contract.read.decimals(), + contract.read.symbol(), + ]); + return new Token(chainId, address, Number(decimals), symbol); + } + + getFactory(chainId: number) { + const factoryAddress = getV3Factory(chainId); + if (!factoryAddress) { + throw RelayerError.unknown(`No Uniswap V3 factory address configured for chain ${chainId}`); + } + return getContract({ + address: factoryAddress, + abi: FactoryV3ABI, + client: web3Provider.client(chainId) + }); + } + + getQuoter(chainId: number) { + return getContract({ + address: getQuoterAddress(chainId), + abi: QuoterV2ABI, + client: web3Provider.client(chainId) + }); + } + + getPool(chainId: number, poolAddress: `0x${string}`): GetContractReturnType { + return getContract({ + abi: v3PoolABI, + address: getAddress(poolAddress), + client: web3Provider.client(chainId), + }); + } + + async quoteNativeToken(chainId: number, addressIn: Address, amountIn: bigint): Promise { + const weth = WRAPPED_NATIVE_TOKEN_ADDRESS[chainId]!; + // First try direct quote + try { + return await this.quote({ + chainId, + amountIn, + addressOut: weth.address, + addressIn + }); + } catch (directError) { + + // If direct quote fails, try multi-hop routing + const intermediateTokens = INTERMEDIATE_TOKENS[chainId.toString()] || []; + for (const intermediateToken of intermediateTokens) { + + // Skip if intermediate token is same as input or output + if (intermediateToken.toLowerCase() === addressIn.toLowerCase() || + intermediateToken.toLowerCase() === weth.address.toLowerCase()) { + continue; + } + + try { + return await this.quoteMultiHop({ + chainId, + amountIn, + path: [addressIn as Address, intermediateToken, weth.address as Address] + }); + } catch { + continue; + } + + } + + throw directError; + } + } + + private async poolHasLiquidity(chainId: number, poolAddress: `0x${string}`) { + const pool = this.getPool(chainId, poolAddress); + const [liq, slot0] = await Promise.all([ + pool.read.liquidity(), + pool.read.slot0() + ]); + if (liq === 0n) + return false; + // sqrtPriceX96, tick, observationIndex, observationCardinality, observationCardinalityNext, feeProtocol, unlocked + const tick = slot0[1]; + const unlocked = slot0[6]; + if (!unlocked || tick === 0) + return false; + + return true; + } + + async findLowestFeePoolForPair(chainId: number, addressIn: string, addressOut: string): Promise { + const factory = this.getFactory(chainId); + let fee: FeeAmount | undefined; + for (const candidateFee of FeeTiers) { + const pool = await factory.read.getPool([ + addressIn as Address, + addressOut as Address, + candidateFee, + ]); + + // we found one! + if (!isNullAddress(pool) && await this.poolHasLiquidity(chainId, pool)) { + fee = candidateFee; + break; + } + } + if (fee === undefined) { + throw RelayerError.unknown( + `No usable Uniswap V3 pool found for pair ${addressIn}/${addressOut} on any known fee tier` + ); + } + return fee; + } + + async quote({ chainId, addressIn, addressOut, amountIn }: UniswapQuote) { + const tokenIn = await this.getTokenInfo(chainId, addressIn as Address); + const tokenOut = await this.getTokenInfo(chainId, addressOut as Address); + const quoterContract = this.getQuoter(chainId); + + const fee = await this.findLowestFeePoolForPair(chainId, addressIn, addressOut); + try { + + const quotedAmountOut = await quoterContract.simulate.quoteExactInputSingle([{ + tokenIn: tokenIn.address as Address, + tokenOut: tokenOut.address as Address, + fee, + amountIn, + sqrtPriceLimitX96: 0n, + }]); + + // amount, sqrtPriceX96After, tickAfter, gasEstimate + const [amount, , ,] = quotedAmountOut.result; + return { + path: [tokenIn.address, fee, tokenOut.address], + in: { + amount: amountIn, decimals: tokenIn.decimals + }, + out: { + amount, decimals: tokenOut.decimals + } + }; + } catch (error) { + if (error instanceof Error && isViemError(error)) { + const { metaMessages, shortMessage } = error; + throw BlockchainError.txError((metaMessages ? metaMessages[0] : undefined) || shortMessage); + } else { + throw RelayerError.unknown("Something went wrong while quoting"); + } + } + } + + async quoteMultiHop({ chainId, amountIn, path }: { chainId: number, amountIn: bigint, path: Address[]; }): Promise { + if (path.length < 2) { + throw RelayerError.unknown('Path must contain at least 2 addresses'); + } + + const quoterContract = this.getQuoter(chainId); + + // Get token info for input and output + const [tokenIn, tokenOut] = await Promise.all([ + this.getTokenInfo(chainId, path[0]!), + this.getTokenInfo(chainId, path[path.length - 1]!) + ]); + + // For each hop, we need to find a valid pool and fee tier + const pathWithFees: { token: Address, fee: FeeAmount; }[] = []; + const hops: [`0x${string}`, `0x${string}`][] = []; + for (let i = 1; i <= path.length - 1; i++) { + hops.push([path[i - 1]!, path[i]!]); + } + + for (const hop of hops) { + const [tokenA, tokenB] = hop; + try { + const fee = await this.findLowestFeePoolForPair(chainId, tokenA, tokenB); + const feePath = { token: tokenA, fee }; + pathWithFees.push(feePath); + } catch { + throw RelayerError.unknown( + `No pool found for hop: ${tokenA} -> ${tokenB}` + ); + } + } + pathWithFees.push({ token: path[path.length - 1] as Address, fee: FeeAmount.MEDIUM }); // fee doesn't matter for last token + + // Encode the path for quoteExactInput + // Path encoding: token0 (20 bytes) + fee0 (3 bytes) + token1 (20 bytes) + fee1 (3 bytes) + token2 (20 bytes)... + let encodedPath = '0x'; + const plainPath: (string | FeeAmount)[] = []; + pathWithFees.forEach((p, i) => { + const { token, fee } = p; + encodedPath += token.replace(/^0x/, ""); // Remove '0x' prefix + plainPath.push(token); + if (i < pathWithFees.length - 1) { + // Add fee as 3 bytes (24 bits) + encodedPath += fee.toString(16).padStart(6, '0'); + plainPath.push(fee); + } + }); + + try { + const quotedAmount = await quoterContract.simulate.quoteExactInput([ + encodedPath as `0x${string}`, + amountIn + ]); + + const [amountOut] = quotedAmount.result; + + return { + path: plainPath, + in: { + amount: amountIn, + decimals: tokenIn.decimals + }, + out: { + amount: amountOut, + decimals: tokenOut.decimals + } + }; + } catch { + throw RelayerError.unknown( + `Failed to get multi-hop quote for path: ${path.join(' -> ')}` + ); + } + } + + async approvePermit2forERC20(tokenIn: `0x${string}`, chainId: number) { + // 0) - (this is done only once) - Approve Permit2 to move Relayer's ERC20 + const relayer = privateKeyToAccount(getSignerPrivateKey(chainId) as `0x${string}`); + const PERMIT2_ADDRESS = getPermit2Address(chainId); + const client = web3Provider.client(chainId); + const erc20 = getContract({ + abi: IERC20MinimalABI, + address: tokenIn, + client, + }); + const allowance = await erc20.read.allowance([relayer.address, PERMIT2_ADDRESS]); + if (allowance < 2n ** 128n) { + const hash = await erc20.write.approve( + [PERMIT2_ADDRESS, 2n ** 256n - 1n], + { chain: client.chain, account: relayer } + ); + await client.waitForTransactionReceipt({ hash }); + } + } + + static createInstructionsIfFeeReceiverIsNotRelayer({ + permitParmas, router, pathParams, relayer, + tokenIn, feeReceiver, feeBase, + refundAmount, amountToSwap, minAmountOut, nativeRecipient + }: CreateInstructionsFeeReceiveerIsNotRelayer): Instruction[] { + // OPERATIONS: + // 1) Send permit for Router to move Gross Fees in Token from Relayer + // 2) AllowanceTransfer from Relayer to feeReceiver for Base Fees + // 3) Swap ERC20 for WETH consuming (Gross-Base) Fees, destination Router, setting the payerIsUser=true flag, meaning to use permit2 (Relayer has the tokens) + // 4) Unwrap WETH to Router + // 5) Transfer native Refund value to Relayer + // 6) Sweep whatever is left to Recipient + return [ + // This is used to authorize the router to move our tokens + { command: Command.permit2, params: permitParmas }, + // We send relaying fees to feeReceiver + { command: Command.transferWithPermit, params: { token: tokenIn, recipient: feeReceiver, amount: feeBase } }, + + // Swap consuming all + { + command: Command.swapV3ExactIn, params: { + // we're going to unwrap weth from here + recipient: router.address, + amountIn: amountToSwap, + minAmountOut, + // USDC-WETH + path: pathParams, + // The relayer is the tx initiator + payerIsUser: true, + } + }, + // the router will hold the value for further splitting + { command: Command.unrwapWeth, params: { recipient: router.address, minAmountOut } }, + // gas refund to relayer + // 0 address means moving native + { command: Command.transfer, params: { token: this.ZERO_ADDRESS, recipient: relayer.address, amount: refundAmount } }, + // 0 address means moving native + // sweep reminder to the withdrawal address + { command: Command.sweep, params: { token: this.ZERO_ADDRESS, recipient: nativeRecipient, minAmountOut: 0n } } + ]; + } + + static createInstructionsIfFeeReceiverIsRelayer({ + permitParmas, router, pathParams, relayer, + refundAmount, amountToSwap, minAmountOut, nativeRecipient + }: CreateInstructionsFeeReceiveerIsRelayer): Instruction[] { + // OPERATIONS: + // 1) Send permit for Router to move Gross Fees in Token from Relayer + // 2) Swap ERC20 for WETH, destination Router, setting the payerIsUser=true flag + // 3) Unwrap WETH to Router + // 4) Transfer native Refund value to Relayer + // 5) Sweep whatever is left to Recipient + return [ + // This is used to authorize the router to move our tokens + { command: Command.permit2, params: permitParmas }, + // Swap consuming all + { + command: Command.swapV3ExactIn, params: { + // we're going to unwrap weth from here + recipient: router.address, + amountIn: amountToSwap, + minAmountOut, + // USDC-WETH + path: pathParams, + // The relayer is the tx initiator + payerIsUser: true, + } + }, + // the router will hold the value for further splitting + { command: Command.unrwapWeth, params: { recipient: router.address, minAmountOut } }, + // gas refund to relayer + // 0 address means moving native + { command: Command.transfer, params: { token: this.ZERO_ADDRESS, recipient: relayer.address, amount: refundAmount } }, + // 0 address means moving native + // sweep reminder to the withdrawal address + { command: Command.sweep, params: { token: this.ZERO_ADDRESS, recipient: nativeRecipient, minAmountOut: 0n } } + ]; + } + + async simulateSwapExactInputForWeth({ + nativeRecipient, + feeReceiver, + feeBase, + feeGross, + tokenIn, + encodedPath, + refundAmount, + chainId + }: SwapWithRefundParams & { encodedPath: `0x${string}`; }): Promise { + + await this.approvePermit2forERC20(tokenIn, chainId); + + const minAmountOut = refundAmount; + const ROUTER_ADDRESS = getRouterAddress(chainId); + const PERMIT2_ADDRESS = getPermit2Address(chainId); + const relayer = privateKeyToAccount(getSignerPrivateKey(chainId) as `0x${string}`); + const client = web3Provider.client(chainId); + + const router = getContract({ + abi: UniversalRouterABI, + address: ROUTER_ADDRESS, + client + }); + + const amountToSwap = feeGross - feeBase; + + const [permit, signature] = await createPermit2({ + signer: relayer, + chainId, + allowanceAmount: feeGross, + permit2Address: PERMIT2_ADDRESS, + routerAddress: ROUTER_ADDRESS, + assetAddress: tokenIn + }); + + let instructions; + if (isFeeReceiverSameAsSigner(chainId)) { + // If feeReceiver is the same as signer, moving coins around is easier + instructions = UniswapProvider.createInstructionsIfFeeReceiverIsRelayer({ + relayer, + router, + amountToSwap, + permitParmas: { permit, signature }, + refundAmount, + minAmountOut, + pathParams: encodedPath, + nativeRecipient + }); + + } else { + instructions = UniswapProvider.createInstructionsIfFeeReceiverIsNotRelayer({ + relayer, + router, + amountToSwap, + permitParmas: { permit, signature }, + refundAmount, + minAmountOut, + pathParams: encodedPath, + nativeRecipient, + // we need to know receiver and how much to take + feeReceiver, + feeBase, + tokenIn + }); + } + + const commandPairs: CommandPair[] = []; + instructions.forEach((ins) => commandPairs.push(encodeInstruction(ins))); + + const commands = "0x" + commandPairs.map(x => x[0].toString(16).padStart(2, "0")).join("") as `0x${string}`; + const params = commandPairs.map(x => x[1]); + + try { + const { request: simulation } = await router.simulate.execute([commands, params], { account: relayer }); + const estimateGas = await client.estimateContractGas(simulation); + + const { + address, + abi, + functionName, + args, + chain, + nonce, + } = simulation; + + return { + functionName, + account: relayer, + address, + abi, + args, + chain, + nonce, + gas: estimateGas * 11n / 10n + }; + } catch (e) { + console.error(e); + throw e; + } + + } + + async findSingleOrMultiHopPath(chainId: number, tokenIn: `0x${string}`) { + const weth = WRAPPED_NATIVE_TOKEN_ADDRESS[chainId]!; + + let path: (`0x${string}` | number)[] = []; + try { + const fee = await this.findLowestFeePoolForPair(chainId, tokenIn, weth.address); + path = [tokenIn, fee, getAddress(weth.address)]; + } catch (error) { + if (!(error instanceof RelayerError)) { + throw error; + } + // we try multi-hop + const intermediateTokens = INTERMEDIATE_TOKENS[chainId.toString()] || []; + for (const auxToken of intermediateTokens) { + try { + const hops = hopsFromAddressRoute([tokenIn, auxToken, getAddress(weth.address)]); + const feeHops = await Promise.all(hops.map(async hop => { + const [tokenIn, tokenOut] = hop; + return { + token: tokenIn, + fee: await this.findLowestFeePoolForPair(chainId, tokenIn, tokenOut) + }; + })); + feeHops.push({ token: getAddress(weth.address), fee: FeeAmount.LOWEST }); // the last fee is not encoded + const _path: (`0x${string}` | number)[] = []; + feeHops.forEach((h, i) => { + const { token, fee } = h; + _path.push(token); + if (i < feeHops.length - 1) { + _path.push(fee); + } + }); + path = _path; + break; + } catch { + continue; + } + } + + if (path.length === 0) { + throw RelayerError.unknown( + `Couldn't find any Uniswap V3 route for pair ${tokenIn}/weth on any known fee tier` + ); + } + } + + return path; + } + + async swapExactInputForWeth(params: SwapWithRefundParams) { + const { chainId, tokenIn } = params; + const path = await this.findSingleOrMultiHopPath(chainId, tokenIn); + const encodedPath = encodePath(path); + const writeContractParams = await this.simulateSwapExactInputForWeth({ ...params, encodedPath }); + const relayer = web3Provider.signer(chainId); + const txHash = await relayer.writeContract(writeContractParams); + return txHash; + } + +} diff --git a/packages/relayer/src/providers/uniswap/uniswapAddresses.ts b/packages/relayer/src/providers/uniswap/uniswapAddresses.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/relayer/src/providers/web3.provider.ts b/packages/relayer/src/providers/web3.provider.ts index c98a5e2..12d1ea9 100644 --- a/packages/relayer/src/providers/web3.provider.ts +++ b/packages/relayer/src/providers/web3.provider.ts @@ -1,11 +1,11 @@ -import { Chain, createPublicClient, Hex, http, PublicClient, verifyTypedData } from "viem"; +import { Chain, createPublicClient, createWalletClient, Hex, http, PublicClient, verifyTypedData, WalletClient } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; import { CONFIG, getSignerPrivateKey } from "../config/index.js"; -import { createChainObject } from "../utils.js"; -import { privateKeyToAccount } from "viem/accounts"; import { FeeCommitment } from "../interfaces/relayer/common.js"; +import { createChainObject } from "../utils.js"; interface IWeb3Provider { client(chainId: number): PublicClient; @@ -16,12 +16,15 @@ const domain = (chainId: number) => ({ name: "Privacy Pools Relayer", version: "1", chainId, -} as const) +} as const); const RelayerCommitmentTypes = { RelayerCommitment: [ { name: "withdrawalData", type: "bytes" }, + { name: "asset", type: "address" }, { name: "expiration", type: "uint256" }, + { name: "amount", type: "uint256" }, + { name: "extraGas", type: "bool" }, ] } as const; @@ -29,8 +32,9 @@ const RelayerCommitmentTypes = { * Class representing the provider for interacting with several chains */ export class Web3Provider implements IWeb3Provider { - chains: { [key: number]: Chain }; - clients: { [key: number]: PublicClient }; + chains: { [key: number]: Chain; }; + clients: { [key: number]: PublicClient; }; + signers: { [key: number]: WalletClient; }; constructor() { this.chains = Object.fromEntries(CONFIG.chains.map(chainConfig => { @@ -43,38 +47,60 @@ export class Web3Provider implements IWeb3Provider { chain, transport: http(chain.rpcUrls.default.http[0]) })]; - })) + })); + this.signers = Object.fromEntries(Object.entries(this.chains).map(([chainId, chain]) => { + const account = privateKeyToAccount(getSignerPrivateKey(Number(chainId)) as `0x${string}`); + return [ + Number(chainId), + createWalletClient({ + account, + chain, + transport: http(chain.rpcUrls.default.http[0]) + })]; + })); + } client(chainId: number): PublicClient { const client = this.clients[chainId]; if (client === undefined) { - throw Error(`Web3ProviderError::UnsupportedChainId(${chainId})`) + throw Error(`Web3ProviderError::UnsupportedChainId(${chainId})`); } - else return client + else return client; + } + + signer(chainId: number): WalletClient { + const signer = this.signers[chainId]; + if (signer === undefined) { + throw Error(`Web3ProviderError::UnsupportedChainId(${chainId})`); + } + else return signer; } async getGasPrice(chainId: number): Promise { - return await this.client(chainId).getGasPrice() + return await this.client(chainId).getGasPrice(); } async signRelayerCommitment(chainId: number, commitment: Omit) { const signer = privateKeyToAccount(getSignerPrivateKey(chainId) as Hex); - const { withdrawalData, expiration } = commitment; + const { withdrawalData, expiration, extraGas, amount, asset } = commitment; return signer.signTypedData({ domain: domain(chainId), types: RelayerCommitmentTypes, primaryType: 'RelayerCommitment', message: { withdrawalData, + asset, + amount, + extraGas, expiration: BigInt(expiration) } - }) + }); } async verifyRelayerCommitment(chainId: number, commitment: FeeCommitment): Promise { const signer = privateKeyToAccount(getSignerPrivateKey(chainId) as Hex); - const { withdrawalData, expiration, signedRelayerCommitment } = commitment; + const { withdrawalData, asset, expiration, amount, extraGas, signedRelayerCommitment } = commitment; return verifyTypedData({ address: signer.address, domain: domain(chainId), @@ -82,10 +108,13 @@ export class Web3Provider implements IWeb3Provider { primaryType: 'RelayerCommitment', message: { withdrawalData, + asset, + amount, + extraGas, expiration: BigInt(expiration) }, signature: signedRelayerCommitment - }) + }); } } diff --git a/packages/relayer/src/schemes/relayer/quote.scheme.ts b/packages/relayer/src/schemes/relayer/quote.scheme.ts index 4b3f623..aa130d8 100644 --- a/packages/relayer/src/schemes/relayer/quote.scheme.ts +++ b/packages/relayer/src/schemes/relayer/quote.scheme.ts @@ -11,6 +11,7 @@ const quoteSchema: JSONSchemaType = { amount: { type: ["string"] }, asset: { type: ["string"] }, recipient: { type: ["string"], nullable: true }, + extraGas: { type: "boolean" } }, required: ["chainId", "amount", "asset"], } as const; diff --git a/packages/relayer/src/schemes/relayer/request.scheme.ts b/packages/relayer/src/schemes/relayer/request.scheme.ts index 4d7c0e0..e7de58e 100644 --- a/packages/relayer/src/schemes/relayer/request.scheme.ts +++ b/packages/relayer/src/schemes/relayer/request.scheme.ts @@ -1,59 +1,56 @@ -import { Ajv, JSONSchemaType } from "ajv"; -import { RelayRequestBody } from "../../interfaces/relayer/request.js"; +import { getAddress } from "viem"; +import { z } from "zod"; -// AJV schema for validation -const ajv = new Ajv(); +const zNonNegativeBigInt = z + .string() + .or(z.number()) + .pipe(z.coerce.bigint().nonnegative()); -const relayRequestSchema: JSONSchemaType = { - type: "object", - properties: { - withdrawal: { - type: "object", - properties: { - processooor: { type: "string" }, - data: { type: "string", pattern: "0x[0-9a-fA-F]+" }, - }, - required: ["processooor", "data"], - }, - publicSignals: { - type: "array", - items: { type: "string" }, - minItems: 8, - maxItems: 8, - }, - proof: { - type: "object", - properties: { - protocol: { type: "string" }, - curve: { type: "string" }, - pi_a: { type: "array", items: { type: "string" }, minItems: 1 }, - pi_b: { - type: "array", - items: { - type: "array", - items: { type: "string" }, - minItems: 1, - }, - minItems: 1, - }, - pi_c: { type: "array", items: { type: "string" }, minItems: 1 }, - }, - required: ["pi_a", "pi_b", "pi_c"], - }, - scope: { type: "string" }, - chainId: { type: ["string", "number"] }, - feeCommitment: { - type: "object", - properties: { - expiration: { type: "number" }, - withdrawalData: { type: "string", pattern: "0x[0-9a-fA-F]+" }, - signedRelayerCommitment: { type: "string", pattern: "0x[0-9a-fA-F]+" } - }, - nullable: true, - required: ["expiration", "signedRelayerCommitment"] - } - }, - required: ["withdrawal", "proof", "publicSignals", "scope", "chainId"], -} as const; +// Address validation schema +export const zAddress = z + .string() + .regex(/^0x[0-9a-fA-F]+/) + .length(42) + .transform((v) => getAddress(v)); -export const validateRelayRequestBody = ajv.compile(relayRequestSchema); +export const zHex = z + .string() + .regex(/^0x[0-9a-fA-F]+/) + .transform(x => x as `0x${string}`); + +export const zWithdrawal = z.object({ + processooor: zAddress, + data: zHex +}); + +export const zProof = z.object({ + protocol: z.string().optional(), + curve: z.string().optional(), + pi_a: z.tuple([z.string(), z.string(), z.string()]), + pi_b: z.tuple([ + z.tuple([z.string(), z.string()]), + z.tuple([z.string(), z.string()]), + z.tuple([z.string(), z.string()]), + ]), + pi_c: z.tuple([z.string(), z.string(), z.string()]), +}); + +export const zFeeCommitment = z.object({ + expiration: z.number().nonnegative().int(), + withdrawalData: zHex, + asset: zAddress, + signedRelayerCommitment: zHex, + extraGas: z.boolean(), + amount: zNonNegativeBigInt +}); + +export const zRelayRequest = z.object({ + withdrawal: zWithdrawal, + publicSignals: z.array(z.string()).length(8), + proof: zProof, + scope: zNonNegativeBigInt, + chainId: z.string().or(z.number()).pipe(z.coerce.number().positive()), + feeCommitment: zFeeCommitment.optional() +}) + .strict() + .readonly(); diff --git a/packages/relayer/src/services/privacyPoolRelayer.service.ts b/packages/relayer/src/services/privacyPoolRelayer.service.ts index 5115603..b51b65e 100644 --- a/packages/relayer/src/services/privacyPoolRelayer.service.ts +++ b/packages/relayer/src/services/privacyPoolRelayer.service.ts @@ -1,11 +1,12 @@ /** * Handles withdrawal requests within the Privacy Pool relayer. */ -import { getAddress } from "viem"; +import { Address, getAddress } from "viem"; import { getAssetConfig, getEntrypointAddress, - getFeeReceiverAddress + getFeeReceiverAddress, + getSignerPrivateKey } from "../config/index.js"; import { BlockchainError, @@ -17,13 +18,17 @@ import { RelayerResponse, WithdrawalPayload, } from "../interfaces/relayer/request.js"; -import { db, SdkProvider, web3Provider } from "../providers/index.js"; +import { db, SdkProvider, UniswapProvider, web3Provider } from "../providers/index.js"; import { RelayerDatabase } from "../types/db.types.js"; import { SdkProviderInterface } from "../types/sdk.types.js"; -import { decodeWithdrawalData, isViemError, parseSignals } from "../utils.js"; +import { decodeWithdrawalData, isFeeReceiverSameAsSigner, isNative, isViemError, parseSignals } from "../utils.js"; import { quoteService } from "./index.js"; import { Web3Provider } from "../providers/web3.provider.js"; import { FeeCommitment } from "../interfaces/relayer/common.js"; +import { uniswapProvider } from "../providers/index.js"; +import { WRAPPED_NATIVE_TOKEN_ADDRESS } from "../providers/uniswap/constants.js"; +import { Withdrawal, WithdrawalProof } from "@0xbow/privacy-pools-core-sdk"; +import { privateKeyToAccount } from "viem/accounts"; /** * Class representing the Privacy Pool Relayer, responsible for processing withdrawal requests. @@ -35,6 +40,7 @@ export class PrivacyPoolRelayer { protected sdkProvider: SdkProviderInterface; /** Web3 provider for handling blockchain interactions. */ protected web3Provider: Web3Provider; + protected uniswapProvider: UniswapProvider; /** * Initializes a new instance of the Privacy Pool Relayer. @@ -43,6 +49,7 @@ export class PrivacyPoolRelayer { this.db = db; this.sdkProvider = new SdkProvider(); this.web3Provider = web3Provider; + this.uniswapProvider = uniswapProvider; } /** @@ -60,17 +67,33 @@ export class PrivacyPoolRelayer { await this.db.createNewRequest(requestId, timestamp, req); await this.validateWithdrawal(req, chainId); + const extraGas = req.feeCommitment?.extraGas ?? false; + const isValidWithdrawalProof = await this.verifyProof(req.proof); if (!isValidWithdrawalProof) { throw ZkError.invalidProof(); } + // We do early check, before relaying + if (extraGas) { + if (!WRAPPED_NATIVE_TOKEN_ADDRESS[chainId]) + throw RelayerError.unknown(`Missing wrapped native token for chain ${chainId}`); + } + const response = await this.broadcastWithdrawal(req, chainId); + // const response = { hash: "0x" } + + let txSwap; + if (extraGas) { + txSwap = await this.swapForNativeAndFund(req.scope, req.withdrawal, req.proof, chainId, response.hash); + } + await this.db.updateBroadcastedRequest(requestId, response.hash); return { success: true, txHash: response.hash, + txSwap, timestamp, requestId, }; @@ -116,6 +139,42 @@ export class PrivacyPoolRelayer { } } + async swapForNativeAndFund(scope: bigint, withdrawal: Withdrawal, proof: WithdrawalProof, chainId: number, relayTx: string) { + + const { assetAddress } = await this.sdkProvider.scopeData(scope, chainId); + if (isNative(assetAddress)) { + // we shouldn't be here + return; + } + + const relayReceipt = await web3Provider.client(chainId).waitForTransactionReceipt({ hash: relayTx as `0x${string}` }); + const { gasUsed: relayGasUsed, effectiveGasPrice: relayGasPrice } = relayReceipt; + + const assetConfig = getAssetConfig(chainId, assetAddress); + const feeReceiver = getFeeReceiverAddress(chainId) as Address; + const { recipient, relayFeeBPS } = decodeWithdrawalData(withdrawal.data); + const withdrawnValue = parseSignals(proof.publicSignals).withdrawnValue; + const gasPrice = await web3Provider.getGasPrice(chainId); + + const feeGross = withdrawnValue * relayFeeBPS / 10_000n; + const feeBase = withdrawnValue * assetConfig.fee_bps / 10_000n; + + const relayerGasRefundValue = gasPrice * quoteService.extraGasTxCost + relayGasPrice * relayGasUsed; + + const txHash = await this.uniswapProvider.swapExactInputForWeth({ + chainId, + feeGross, + feeBase, + refundAmount: relayerGasRefundValue, + tokenIn: assetAddress, + nativeRecipient: recipient, + feeReceiver + }); + + return txHash; + + } + /** * Verifies a withdrawal proof. * @@ -138,15 +197,15 @@ export class PrivacyPoolRelayer { protected async broadcastWithdrawal( withdrawal: WithdrawalPayload, chainId: number, - ): Promise<{ hash: string }> { + ): Promise<{ hash: string; }> { try { return await this.sdkProvider.broadcastWithdrawal(withdrawal, chainId); } catch (error) { if (isViemError(error)) { const { metaMessages, shortMessage } = error; - throw BlockchainError.txError((metaMessages ? metaMessages[0] : undefined) || shortMessage) + throw BlockchainError.txError((metaMessages ? metaMessages[0] : undefined) || shortMessage); } else { - throw RelayerError.unknown("Something went wrong while broadcasting Tx") + throw RelayerError.unknown("Something went wrong while broadcasting Tx"); } } } @@ -162,26 +221,49 @@ export class PrivacyPoolRelayer { protected async validateWithdrawal(wp: WithdrawalPayload, chainId: number) { const entrypointAddress = getEntrypointAddress(chainId); const feeReceiverAddress = getFeeReceiverAddress(chainId); + const signerAddress = privateKeyToAccount(getSignerPrivateKey(chainId) as `0x${string}`).address; - const { feeRecipient, relayFeeBPS } = decodeWithdrawalData( - wp.withdrawal.data, - ); + const extraGas = wp.feeCommitment?.extraGas ?? false; + + // If there's a fee commitment, then we use it's withdrawalData as source of truth to check against the proof. + const withdrawalData = wp.feeCommitment ? wp.feeCommitment.withdrawalData : wp.withdrawal.data; + if ((wp.feeCommitment !== undefined) && (wp.feeCommitment.withdrawalData !== wp.withdrawal.data)) { + throw WithdrawalValidationError.relayerCommitmentRejected( + `Signed commitment does not match withdrawal data, exiting early: commitment data ${wp.feeCommitment.withdrawalData}, request data ${wp.withdrawal.data}`, + ); + } + + const { feeRecipient, relayFeeBPS } = decodeWithdrawalData(withdrawalData); const proofSignals = parseSignals(wp.proof.publicSignals); + if ((wp.feeCommitment !== undefined) && (wp.feeCommitment.amount > proofSignals.withdrawnValue)) { + throw WithdrawalValidationError.withdrawnValueTooSmall( + `WithdrawnValue too small: expected "${wp.feeCommitment.amount}", got "${proofSignals.withdrawnValue}".`, + ); + } + if (wp.withdrawal.processooor !== entrypointAddress) { throw WithdrawalValidationError.processooorMismatch( `Processooor mismatch: expected "${entrypointAddress}", got "${wp.withdrawal.processooor}".`, ); } - if (getAddress(feeRecipient) !== feeReceiverAddress) { - throw WithdrawalValidationError.feeReceiverMismatch( - `Fee recipient mismatch: expected "${feeReceiverAddress}", got "${feeRecipient}".`, - ); + if (extraGas && !isFeeReceiverSameAsSigner(chainId)) { + if (getAddress(feeRecipient) !== getAddress(signerAddress)) { + throw WithdrawalValidationError.feeReceiverMismatch( + `Fee recipient with extraGas mismatch: expected "${signerAddress}", got "${feeRecipient}".`, + ); + } + } else { + if (getAddress(feeRecipient) !== feeReceiverAddress) { + throw WithdrawalValidationError.feeReceiverMismatch( + `Fee recipient mismatch: expected "${feeReceiverAddress}", got "${feeRecipient}".`, + ); + } } const withdrawalContext = BigInt( - this.sdkProvider.calculateContext(wp.withdrawal, wp.scope), + this.sdkProvider.calculateContext({ processooor: wp.withdrawal.processooor, data: withdrawalData }, wp.scope), ); if (proofSignals.context !== withdrawalContext) { throw WithdrawalValidationError.contextMismatch( @@ -202,6 +284,20 @@ export class PrivacyPoolRelayer { if (wp.feeCommitment) { + if (wp.feeCommitment.asset != assetAddress) { + throw WithdrawalValidationError.relayerCommitmentRejected( + `Asset in commitment does not match withdrawal scope asset: expected ${wp.feeCommitment.asset}, received ${assetAddress}`, + ); + } + + // TODO: remove this check beacuse we should already have errored out at the begining + const { relayFeeBPS: commitmentRelayFeeBPS } = decodeWithdrawalData(wp.feeCommitment.withdrawalData); + if (relayFeeBPS !== commitmentRelayFeeBPS) { + throw WithdrawalValidationError.relayerCommitmentRejected( + `Proof relay fee does not match signed commitment: pi:=${relayFeeBPS}, commitment:=${commitmentRelayFeeBPS}`, + ); + } + if (commitmentExpired(wp.feeCommitment)) { throw WithdrawalValidationError.relayerCommitmentRejected( `Relay fee commitment expired, please quote again`, @@ -217,10 +313,14 @@ export class PrivacyPoolRelayer { } else { const currentFeeBPS = await quoteService.quoteFeeBPSNative({ - chainId, amountIn: proofSignals.withdrawnValue, assetAddress, baseFeeBPS: assetConfig.fee_bps, value: 0n + chainId, + amountIn: proofSignals.withdrawnValue, + assetAddress, + baseFeeBPS: assetConfig.fee_bps, + extraGas }); - if (relayFeeBPS < currentFeeBPS) { + if (relayFeeBPS < currentFeeBPS.feeBPS) { throw WithdrawalValidationError.feeTooLow( `Relay fee too low: expected at least "${currentFeeBPS}", got "${relayFeeBPS}".`, ); @@ -239,9 +339,9 @@ export class PrivacyPoolRelayer { } function commitmentExpired(feeCommitment: FeeCommitment): boolean { - return feeCommitment.expiration < Number(new Date()) + return feeCommitment.expiration < Number(new Date()); } async function validFeeCommitment(chainId: number, feeCommitment: FeeCommitment): Promise { - return web3Provider.verifyRelayerCommitment(chainId, feeCommitment) + return web3Provider.verifyRelayerCommitment(chainId, feeCommitment); } diff --git a/packages/relayer/src/services/quote.service.ts b/packages/relayer/src/services/quote.service.ts index b80f0f8..c767eac 100644 --- a/packages/relayer/src/services/quote.service.ts +++ b/packages/relayer/src/services/quote.service.ts @@ -5,39 +5,66 @@ interface QuoteFeeBPSParams { chainId: number, assetAddress: Address, amountIn: bigint, - value: bigint, - baseFeeBPS: bigint + baseFeeBPS: bigint, + extraGas: boolean; }; const NativeAddress = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; +export interface QuoteFee { + feeBPS: bigint; + path: (string | number)[]; + gasPrice: bigint; + relayTxCost: bigint; + extraGasTxCost?: bigint; + extraGasFundAmount?: bigint; +}; + export class QuoteService { - readonly txCost: bigint; + readonly relayTxCost: bigint; + readonly extraGasTxCost: bigint; + readonly extraGasFundAmount: bigint; constructor() { // a typical withdrawal costs between 450k-650k gas - this.txCost = 700_000n; + this.relayTxCost = 650_000n; + // approximate value of a uniswap Router call. Can vary greatly if doing multi-hop swaps. + this.extraGasTxCost = 320_000n; + // this gas will be transformed into equivalent native units at the time of the fund swap. + this.extraGasFundAmount = 600_000n; } - async netFeeBPSNative(baseFee: bigint, balance: bigint, nativeQuote: { num: bigint, den: bigint }, gasPrice: bigint, value: bigint): Promise { - const nativeCosts = (1n * gasPrice * this.txCost + value) + async netFeeBPSNative(baseFee: bigint, balance: bigint, nativeQuote: { num: bigint, den: bigint; }, gasPrice: bigint, extraGasUnits: bigint): Promise { + const totalGasUnits = this.relayTxCost + extraGasUnits; + const nativeCosts = (1n * gasPrice * totalGasUnits); return baseFee + nativeQuote.den * 10_000n * nativeCosts / balance / nativeQuote.num; } - async quoteFeeBPSNative(quoteParams: QuoteFeeBPSParams): Promise { - const { chainId, assetAddress, amountIn, baseFeeBPS, value } = quoteParams; + async quoteFeeBPSNative(quoteParams: QuoteFeeBPSParams): Promise { + const { chainId, assetAddress, amountIn, baseFeeBPS, extraGas } = quoteParams; const gasPrice = await web3Provider.getGasPrice(chainId); - let quote: { num: bigint, den: bigint }; + const EXTRA_GAS_AMOUNT = this.extraGasTxCost + this.extraGasFundAmount; + const extraGasUnits = extraGas ? EXTRA_GAS_AMOUNT : 0n; + const extraGasDetail = extraGas ? { extraGasTxCost: this.extraGasTxCost, extraGasFundAmount: this.extraGasFundAmount } : undefined; + + let quote: { num: bigint, den: bigint; path: (string | number)[]; }; if (assetAddress.toLowerCase() === NativeAddress.toLowerCase()) { - quote = { num: 1n, den: 1n }; + quote = { num: 1n, den: 1n, path: [] }; } else { quote = await quoteProvider.quoteNativeTokenInERC20(chainId, assetAddress, amountIn); } - const feeBPS = await this.netFeeBPSNative(baseFeeBPS, amountIn, quote, gasPrice, value); - return feeBPS + const feeBPS = await this.netFeeBPSNative(baseFeeBPS, amountIn, quote, gasPrice, extraGasUnits); + + return { + feeBPS, + gasPrice, + relayTxCost: this.relayTxCost, + ...extraGasDetail, + path: quote.path + }; } } diff --git a/packages/relayer/src/types.ts b/packages/relayer/src/types.ts index d442c98..58a4cf8 100644 --- a/packages/relayer/src/types.ts +++ b/packages/relayer/src/types.ts @@ -1,6 +1,7 @@ import { Address } from "viem/accounts"; import { RelayerResponse } from "./interfaces/relayer/request.js"; import { QuoteResponse } from "./interfaces/relayer/quote.js"; +import { FeeCommitment } from "./interfaces/relayer/common.js"; export abstract class RelayerMarshall { abstract toJSON(): object; @@ -52,19 +53,26 @@ export class QuoteMarshall extends RelayerMarshall { super(); } - addFeeCommitment(feeCommitment: { - expiration: number - withdrawalData: `0x${string}`, - signedRelayerCommitment: `0x${string}` - }) { - this.response.feeCommitment = feeCommitment; + addFeeCommitment(feeCommitment: FeeCommitment) { + this.response.feeCommitment = { + ...feeCommitment, + amount: feeCommitment.amount.toString() + }; } override toJSON(): object { + const detail = Object.fromEntries( + Object.entries(this.response.detail) + .map(([k, v]) => { + return [k, v ? { gas: v.gas.toString(), eth: v.eth.toString() } : undefined]; + }) + ); return { baseFeeBPS: this.response.baseFeeBPS.toString(), feeBPS: this.response.feeBPS.toString(), - feeCommitment: this.response.feeCommitment - } + gasPrice: this.response.gasPrice.toString(), + feeCommitment: this.response.feeCommitment, + detail, + }; } } diff --git a/packages/relayer/src/utils.ts b/packages/relayer/src/utils.ts index f40f336..d0e1fd0 100644 --- a/packages/relayer/src/utils.ts +++ b/packages/relayer/src/utils.ts @@ -17,11 +17,13 @@ import { WithdrawPublicSignals, } from "./interfaces/relayer/request.js"; import { FeeDataAbi } from "./types/abi.types.js"; +import { getFeeReceiverAddress, getSignerPrivateKey } from "./config/index.js"; +import { privateKeyToAccount } from "viem/accounts"; interface WithdrawalData { recipient: Address, feeRecipient: Address, - relayFeeBPS: bigint + relayFeeBPS: bigint; } export function decodeWithdrawalData(data: `0x${string}`): WithdrawalData { @@ -42,7 +44,7 @@ export function decodeWithdrawalData(data: `0x${string}`): WithdrawalData { export function encodeWithdrawalData(withdrawalData: WithdrawalData): `0x${string}` { try { - return encodeAbiParameters(FeeDataAbi, [withdrawalData]) + return encodeAbiParameters(FeeDataAbi, [withdrawalData]); } catch (e) { const error = e as EncodeAbiParametersErrorType; throw WithdrawalValidationError.invalidWithdrawalAbi({ @@ -86,7 +88,7 @@ export function createChainObject(chainConfig: { chain_id: number; chain_name: string; rpc_url: string; - native_currency?: { name: string; symbol: string; decimals: number }; + native_currency?: { name: string; symbol: string; decimals: number; }; }): Chain { return { id: chainConfig.chain_id, @@ -107,6 +109,16 @@ export function isViemError(error: unknown): error is ViemError { const viemErrorNames = [ ContractFunctionExecutionError.prototype.constructor.name, ContractFunctionRevertedError.prototype.constructor.name, - ] + ]; return viemErrorNames.includes(error?.constructor?.name || ""); } + +export function isFeeReceiverSameAsSigner(chainId: number) { + const feeReceiverAddress = getFeeReceiverAddress(chainId); + const signerAddress = privateKeyToAccount(getSignerPrivateKey(chainId) as `0x${string}`).address; + return feeReceiverAddress.toLowerCase() === signerAddress.toLowerCase(); +} + +export function isNative(asset: `0x${string}`) { + return asset.toLowerCase() === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; +} diff --git a/packages/relayer/test/integration/relay/package.json b/packages/relayer/test/integration/relay/package.json index bf39fa1..697835d 100644 --- a/packages/relayer/test/integration/relay/package.json +++ b/packages/relayer/test/integration/relay/package.json @@ -4,15 +4,18 @@ "main": "index.js", "license": "Apache-2.0", "type": "module", - "dependencies": { - "viem": "2.22.14" - }, "scripts": { "build": "tsc -p ./tsconfig.json", + "tree-cache": "node ./dist/main.js tree --fromBlock $(echo $(cast bn) - 50 | bc) --output ./tree-cache", "main": "node dist/main.js" }, + "dependencies": { + "minimist": "1.2.8", + "viem": "2.22.14" + }, "devDependencies": { - "@0xbow/privacy-pools-core-sdk": "0.1.5", + "@types/minimist": "1.2.5", + "@0xbow/privacy-pools-core-sdk": "0.1.21", "@types/node": "^22.13.0", "typescript": "5.5.4" } diff --git a/packages/relayer/test/integration/relay/src/abis/ERC20.abi.ts b/packages/relayer/test/integration/relay/src/abis/ERC20.abi.ts new file mode 100644 index 0000000..5c60b78 --- /dev/null +++ b/packages/relayer/test/integration/relay/src/abis/ERC20.abi.ts @@ -0,0 +1,185 @@ +export const abi = [ + { + "type": "function", + "name": "allowance", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + }, + { + "name": "spender", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "approve", + "inputs": [ + { + "name": "spender", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "balanceOf", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "totalSupply", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transfer", + "inputs": [ + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferFrom", + "inputs": [ + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "Approval", + "inputs": [ + { + "name": "owner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "spender", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Transfer", + "inputs": [ + { + "name": "from", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + } +] as const; diff --git a/packages/relayer/test/integration/relay/src/abis/Entrypoint.abi.ts b/packages/relayer/test/integration/relay/src/abis/Entrypoint.abi.ts new file mode 100644 index 0000000..9edc0a3 --- /dev/null +++ b/packages/relayer/test/integration/relay/src/abis/Entrypoint.abi.ts @@ -0,0 +1,681 @@ +export const abi = [ + { + "type": "function", + "name": "assetConfig", + "inputs": [ + { + "name": "_asset", + "type": "address", + "internalType": "contract IERC20" + } + ], + "outputs": [ + { + "name": "_pool", + "type": "address", + "internalType": "contract IPrivacyPool" + }, + { + "name": "_minimumDepositAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_vettingFeeBPS", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_maxRelayFeeBPS", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "associationSets", + "inputs": [ + { + "name": "_index", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "_root", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_ipfsCID", + "type": "string", + "internalType": "string" + }, + { + "name": "_timestamp", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "deposit", + "inputs": [ + { + "name": "_asset", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "_value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_precommitment", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "_commitment", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "deposit", + "inputs": [ + { + "name": "_precommitment", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "_commitment", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "_owner", + "type": "address", + "internalType": "address" + }, + { + "name": "_postman", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "latestRoot", + "inputs": [], + "outputs": [ + { + "name": "_root", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "registerPool", + "inputs": [ + { + "name": "_asset", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "_pool", + "type": "address", + "internalType": "contract IPrivacyPool" + }, + { + "name": "_minimumDepositAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_vettingFeeBPS", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_maxRelayFeeBPS", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "relay", + "inputs": [ + { + "name": "_withdrawal", + "type": "tuple", + "internalType": "struct IPrivacyPool.Withdrawal", + "components": [ + { + "name": "processooor", + "type": "address", + "internalType": "address" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "_proof", + "type": "tuple", + "internalType": "struct ProofLib.WithdrawProof", + "components": [ + { + "name": "pA", + "type": "uint256[2]", + "internalType": "uint256[2]" + }, + { + "name": "pB", + "type": "uint256[2][2]", + "internalType": "uint256[2][2]" + }, + { + "name": "pC", + "type": "uint256[2]", + "internalType": "uint256[2]" + }, + { + "name": "pubSignals", + "type": "uint256[8]", + "internalType": "uint256[8]" + } + ] + }, + { + "name": "_scope", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "removePool", + "inputs": [ + { + "name": "_asset", + "type": "address", + "internalType": "contract IERC20" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "rootByIndex", + "inputs": [ + { + "name": "_index", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "_root", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "scopeToPool", + "inputs": [ + { + "name": "_scope", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "_pool", + "type": "address", + "internalType": "contract IPrivacyPool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "updatePoolConfiguration", + "inputs": [ + { + "name": "_asset", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "_minimumDepositAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_vettingFeeBPS", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_maxRelayFeeBPS", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "updateRoot", + "inputs": [ + { + "name": "_root", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_ipfsCID", + "type": "string", + "internalType": "string" + } + ], + "outputs": [ + { + "name": "_index", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "windDownPool", + "inputs": [ + { + "name": "_pool", + "type": "address", + "internalType": "contract IPrivacyPool" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "withdrawFees", + "inputs": [ + { + "name": "_asset", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "_recipient", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "Deposited", + "inputs": [ + { + "name": "_depositor", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "_pool", + "type": "address", + "indexed": true, + "internalType": "contract IPrivacyPool" + }, + { + "name": "_commitment", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "_amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "FeesWithdrawn", + "inputs": [ + { + "name": "_asset", + "type": "address", + "indexed": false, + "internalType": "contract IERC20" + }, + { + "name": "_recipient", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "_amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PoolConfigurationUpdated", + "inputs": [ + { + "name": "_pool", + "type": "address", + "indexed": false, + "internalType": "contract IPrivacyPool" + }, + { + "name": "_asset", + "type": "address", + "indexed": false, + "internalType": "contract IERC20" + }, + { + "name": "_newMinimumDepositAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "_newVettingFeeBPS", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "_newMaxRelayFeeBPS", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PoolRegistered", + "inputs": [ + { + "name": "_pool", + "type": "address", + "indexed": false, + "internalType": "contract IPrivacyPool" + }, + { + "name": "_asset", + "type": "address", + "indexed": false, + "internalType": "contract IERC20" + }, + { + "name": "_scope", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PoolRemoved", + "inputs": [ + { + "name": "_pool", + "type": "address", + "indexed": false, + "internalType": "contract IPrivacyPool" + }, + { + "name": "_asset", + "type": "address", + "indexed": false, + "internalType": "contract IERC20" + }, + { + "name": "_scope", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PoolWindDown", + "inputs": [ + { + "name": "_pool", + "type": "address", + "indexed": false, + "internalType": "contract IPrivacyPool" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RootUpdated", + "inputs": [ + { + "name": "_root", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "_ipfsCID", + "type": "string", + "indexed": false, + "internalType": "string" + }, + { + "name": "_timestamp", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "WithdrawalRelayed", + "inputs": [ + { + "name": "_relayer", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "_recipient", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "_asset", + "type": "address", + "indexed": true, + "internalType": "contract IERC20" + }, + { + "name": "_amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "_feeAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AssetMismatch", + "inputs": [] + }, + { + "type": "error", + "name": "AssetPoolAlreadyRegistered", + "inputs": [] + }, + { + "type": "error", + "name": "EmptyRoot", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidEntrypointForPool", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidFeeBPS", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidIPFSCIDLength", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidIndex", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidPoolState", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidProcessooor", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidWithdrawalAmount", + "inputs": [] + }, + { + "type": "error", + "name": "MinimumDepositAmount", + "inputs": [] + }, + { + "type": "error", + "name": "NativeAssetNotAccepted", + "inputs": [] + }, + { + "type": "error", + "name": "NativeAssetTransferFailed", + "inputs": [] + }, + { + "type": "error", + "name": "NoRootsAvailable", + "inputs": [] + }, + { + "type": "error", + "name": "PoolIsDead", + "inputs": [] + }, + { + "type": "error", + "name": "PoolNotFound", + "inputs": [] + }, + { + "type": "error", + "name": "RelayFeeGreaterThanMax", + "inputs": [] + }, + { + "type": "error", + "name": "ScopePoolAlreadyRegistered", + "inputs": [] + }, + { + "type": "error", + "name": "ZeroAddress", + "inputs": [] + } +] as const; diff --git a/packages/relayer/test/integration/relay/src/abis/Pool.abi.ts b/packages/relayer/test/integration/relay/src/abis/Pool.abi.ts new file mode 100644 index 0000000..84ada32 --- /dev/null +++ b/packages/relayer/test/integration/relay/src/abis/Pool.abi.ts @@ -0,0 +1,561 @@ +export const abi = [ + { + "type": "function", + "name": "ASSET", + "inputs": [], + "outputs": [ + { + "name": "_asset", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "ENTRYPOINT", + "inputs": [], + "outputs": [ + { + "name": "_entrypoint", + "type": "address", + "internalType": "contract IEntrypoint" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "MAX_TREE_DEPTH", + "inputs": [], + "outputs": [ + { + "name": "_maxDepth", + "type": "uint32", + "internalType": "uint32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "RAGEQUIT_VERIFIER", + "inputs": [], + "outputs": [ + { + "name": "_verifier", + "type": "address", + "internalType": "contract IVerifier" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "ROOT_HISTORY_SIZE", + "inputs": [], + "outputs": [ + { + "name": "_size", + "type": "uint32", + "internalType": "uint32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "SCOPE", + "inputs": [], + "outputs": [ + { + "name": "_scope", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "WITHDRAWAL_VERIFIER", + "inputs": [], + "outputs": [ + { + "name": "_verifier", + "type": "address", + "internalType": "contract IVerifier" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "currentRoot", + "inputs": [], + "outputs": [ + { + "name": "_root", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "currentRootIndex", + "inputs": [], + "outputs": [ + { + "name": "_index", + "type": "uint32", + "internalType": "uint32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "currentTreeDepth", + "inputs": [], + "outputs": [ + { + "name": "_depth", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "currentTreeSize", + "inputs": [], + "outputs": [ + { + "name": "_size", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "dead", + "inputs": [], + "outputs": [ + { + "name": "_dead", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "deposit", + "inputs": [ + { + "name": "_depositor", + "type": "address", + "internalType": "address" + }, + { + "name": "_value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_precommitment", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "_commitment", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "depositors", + "inputs": [ + { + "name": "_label", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "_depositor", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nonce", + "inputs": [], + "outputs": [ + { + "name": "_nonce", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nullifierHashes", + "inputs": [ + { + "name": "_nullifierHash", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "_spent", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "ragequit", + "inputs": [ + { + "name": "_p", + "type": "tuple", + "internalType": "struct ProofLib.RagequitProof", + "components": [ + { + "name": "pA", + "type": "uint256[2]", + "internalType": "uint256[2]" + }, + { + "name": "pB", + "type": "uint256[2][2]", + "internalType": "uint256[2][2]" + }, + { + "name": "pC", + "type": "uint256[2]", + "internalType": "uint256[2]" + }, + { + "name": "pubSignals", + "type": "uint256[4]", + "internalType": "uint256[4]" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "roots", + "inputs": [ + { + "name": "_index", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "_root", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "windDown", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "withdraw", + "inputs": [ + { + "name": "_w", + "type": "tuple", + "internalType": "struct IPrivacyPool.Withdrawal", + "components": [ + { + "name": "processooor", + "type": "address", + "internalType": "address" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "_p", + "type": "tuple", + "internalType": "struct ProofLib.WithdrawProof", + "components": [ + { + "name": "pA", + "type": "uint256[2]", + "internalType": "uint256[2]" + }, + { + "name": "pB", + "type": "uint256[2][2]", + "internalType": "uint256[2][2]" + }, + { + "name": "pC", + "type": "uint256[2]", + "internalType": "uint256[2]" + }, + { + "name": "pubSignals", + "type": "uint256[8]", + "internalType": "uint256[8]" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "Deposited", + "inputs": [ + { + "name": "_depositor", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "_commitment", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "_label", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "_value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "_precommitmentHash", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "LeafInserted", + "inputs": [ + { + "name": "_index", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "_leaf", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "_root", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PoolDied", + "inputs": [], + "anonymous": false + }, + { + "type": "event", + "name": "Ragequit", + "inputs": [ + { + "name": "_ragequitter", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "_commitment", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "_label", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "_value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Withdrawn", + "inputs": [ + { + "name": "_processooor", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "_value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "_spentNullifier", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "_newCommitment", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "ContextMismatch", + "inputs": [] + }, + { + "type": "error", + "name": "IncorrectASPRoot", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidCommitment", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidDepositValue", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidProcessooor", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidProof", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidTreeDepth", + "inputs": [] + }, + { + "type": "error", + "name": "MaxTreeDepthReached", + "inputs": [] + }, + { + "type": "error", + "name": "NotYetRagequitteable", + "inputs": [] + }, + { + "type": "error", + "name": "NullifierAlreadySpent", + "inputs": [] + }, + { + "type": "error", + "name": "OnlyEntrypoint", + "inputs": [] + }, + { + "type": "error", + "name": "OnlyOriginalDepositor", + "inputs": [] + }, + { + "type": "error", + "name": "PoolIsDead", + "inputs": [] + }, + { + "type": "error", + "name": "ScopeMismatch", + "inputs": [] + }, + { + "type": "error", + "name": "UnknownStateRoot", + "inputs": [] + }, + { + "type": "error", + "name": "ZeroAddress", + "inputs": [] + } +] as const; diff --git a/packages/relayer/test/integration/relay/src/api-test.js b/packages/relayer/test/integration/relay/src/api-test.ts similarity index 54% rename from packages/relayer/test/integration/relay/src/api-test.js rename to packages/relayer/test/integration/relay/src/api-test.ts index 5a952f6..d42f50c 100644 --- a/packages/relayer/test/integration/relay/src/api-test.js +++ b/packages/relayer/test/integration/relay/src/api-test.ts @@ -1,26 +1,26 @@ export const ping = async () => { - let r = await fetch("http://localhost:3000/ping", { + const r = await fetch("http://localhost:3000/ping", { method: "get", }); console.log(JSON.stringify(await r.text(), null, 2)); }; export const details = async () => { - let r = await fetch("http://localhost:3000/relayer/details", { + const r = await fetch("http://localhost:3000/relayer/details", { method: "get", }); console.log(JSON.stringify(await r.json(), null, 2)); }; export const notFound = async () => { - let r = await fetch("http://localhost:3000/HOLA", { + const r = await fetch("http://localhost:3000/HOLA", { method: "get", }); console.log(JSON.stringify(await r.json(), null, 2)); }; -export const request = async (requestBody) => { - let r = await fetch("http://localhost:3000/relayer/request", { +export const request = async (requestBody: object) => { + const r = await fetch("http://localhost:3000/relayer/request", { method: "post", headers: { "Content-Type": "application/json", @@ -30,8 +30,18 @@ export const request = async (requestBody) => { console.log(JSON.stringify(await r.json(), null, 2)); }; -export const quote = async (quoteBody) => { - let r = await fetch("http://localhost:3000/relayer/quote", { +interface QuoteResponse { + baseFeeBPS: bigint, + feeBPS: bigint, + feeCommitment?: { + expiration: number, + withdrawalData: `0x${string}`, + signedRelayerCommitment: `0x${string}`, + }; +} + +export const quote = async (quoteBody: object) => { + const r = await fetch("http://localhost:3000/relayer/quote", { method: "post", headers: { 'Content-Type': 'application/json' @@ -40,5 +50,5 @@ export const quote = async (quoteBody) => { }) const quoteResponse = await r.json(); console.log(JSON.stringify(quoteResponse, null, 2)) - return quoteResponse; + return quoteResponse as QuoteResponse; } diff --git a/packages/relayer/test/integration/relay/src/chain.ts b/packages/relayer/test/integration/relay/src/chain.ts index a65e195..cb41ced 100644 --- a/packages/relayer/test/integration/relay/src/chain.ts +++ b/packages/relayer/test/integration/relay/src/chain.ts @@ -1,16 +1,86 @@ -import { createPublicClient, defineChain, getContract, http, parseAbi } from "viem"; +import { Account, createPublicClient, defineChain, getContract, GetContractReturnType, http, PublicClient } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; import { localhost } from "viem/chains"; -import { ETH_POOL_ADDRESS, LOCAL_ANVIL_RPC } from "./constants.js"; +import { abi as EntrypointAbi } from "./abis/Entrypoint.abi.js"; +import { abi as Erc20Abi } from "./abis/ERC20.abi.js"; +import { abi as PoolAbi } from "./abis/Pool.abi.js"; +import { ENTRYPOINT_ADDRESS, LOCAL_ANVIL_RPC } from "./constants.js"; -export const anvilChain = defineChain({ ...localhost, id: 31337 }); +type PoolContract = GetContractReturnType; -export const publicClient = createPublicClient({ - chain: anvilChain, - transport: http(LOCAL_ANVIL_RPC), -}); +export interface IChainContext { + account: Account, + chain: ReturnType; + client: PublicClient; + entrypoint: GetContractReturnType, + getPoolContract: (asset: `0x${string}`) => Promise; + getPoolContractByScope: (scope: bigint) => PoolContract; + getErc20Contract: (asset: `0x${string}`) => GetContractReturnType; +} + +export function ChainContext(chainId: number, privateKey: `0x${string}`): IChainContext { + + const _poolCacheByAsset: { [key: `0x${string}`]: PoolContract; } = {}; + const _poolCacheByScope: { [key: string]: PoolContract; } = {}; + + const anvilChain = defineChain({ ...localhost, id: chainId }); + + const publicClient = createPublicClient({ + chain: anvilChain, + transport: http(LOCAL_ANVIL_RPC), + }); + + const entrypoint = getContract({ + address: ENTRYPOINT_ADDRESS, + abi: EntrypointAbi, + client: publicClient, + }); + + async function getPoolContract(asset: `0x${string}`) { + const cachedPool = _poolCacheByAsset[asset]; + if (cachedPool !== undefined) + return cachedPool; + const [ + poolAddress, + _minimumDepositAmount, // eslint-disable-line @typescript-eslint/no-unused-vars + _vettingFeeBPS, // eslint-disable-line @typescript-eslint/no-unused-vars + _maxRelayFeeBPS // eslint-disable-line @typescript-eslint/no-unused-vars + ] = await entrypoint.read.assetConfig([asset]); + const pool = getContract({ + address: poolAddress, + abi: PoolAbi, + client: publicClient, + }); + const scope = await pool.read.SCOPE(); + _poolCacheByAsset[asset] = pool; + _poolCacheByScope[scope.toString()] = pool; + return pool; + } + + function getPoolContractByScope(scope: bigint) { + const cachedPool = _poolCacheByScope[scope.toString()]; + if (cachedPool !== undefined) + return cachedPool; + throw Error("Pool is not instantiated") + } + + function getErc20Contract(asset: `0x${string}`) { + return getContract({ + address: asset, + client: publicClient, + abi: Erc20Abi + }); + } + + return { + account: privateKeyToAccount(privateKey), + chain: anvilChain, + client: publicClient, + entrypoint, + getPoolContract, + getErc20Contract, + getPoolContractByScope + }; + +} -export const pool = getContract({ - address: ETH_POOL_ADDRESS, - abi: parseAbi(["function SCOPE() view returns (uint256)"]), - client: publicClient, -}) diff --git a/packages/relayer/test/integration/relay/src/cli.ts b/packages/relayer/test/integration/relay/src/cli.ts new file mode 100644 index 0000000..b2c20df --- /dev/null +++ b/packages/relayer/test/integration/relay/src/cli.ts @@ -0,0 +1,270 @@ +import minimist from 'minimist'; +import { getAddress } from 'viem'; +import { quote, request } from "./api-test.js"; +import { ChainContext } from "./chain.js"; +import { feeRecipient, PRIVATE_KEY, processooor, recipient } from "./constants.js"; +import { encodeFeeData, isNative } from "./util.js"; +import { SdkWrapper } from './sdk-wrapper.js'; +import * as fs from "fs"; + +interface Context { + chainId: number; + privateKey: `0x${string}`; +} + +interface DepositCli { + context: Context; + accNonce: bigint; + amount: bigint; + asset: `0x${string}`; +} + +export async function depositCli({ accNonce, amount, asset, context }: DepositCli) { + const { chainId, privateKey } = context; + const sdkWrapper = new SdkWrapper(ChainContext(chainId, privateKey)); + let r; + if (isNative(asset)) { + r = await sdkWrapper.deposit(accNonce, amount); + } else { + r = await sdkWrapper.depositAsset(accNonce, amount, asset); + } + await r.wait(); + console.log(`Successful deposit, hash := ${r.hash}`); +} + +interface QuoteCli { + context: Context; + asset: `0x${string}`; + amount: bigint; + extraGas: boolean; +} + +export async function quoteCli({ context, asset, amount, extraGas }: QuoteCli) { + return quote({ + chainId: context.chainId, + amount: amount.toString(), + asset, + recipient, + extraGas + }); +} + +interface RelayCli { + context: Context; + + asset: `0x${string}`; + withQuote: boolean; + extraGas: boolean; + amount: bigint; + + fromDeposit: boolean; + fromLabel?: bigint; + accNonce: bigint; + value: bigint; + + leaves: { + index: bigint; + leaf: bigint; + root: bigint; + block: bigint; + }[]; +} + +export async function relayCli({ asset, withQuote, amount, extraGas, fromDeposit, fromLabel, accNonce, value, context, leaves }: RelayCli) { + + const { chainId, privateKey } = context; + const sdkWrapper = new SdkWrapper(ChainContext(chainId, privateKey)); + + const pool = await sdkWrapper.chainContext.getPoolContract(asset); + const scope = await pool.read.SCOPE(); + + let note: { nullifier: bigint, secret: bigint; }; + let newNote: { nullifier: bigint, secret: bigint; } | undefined; + let label: bigint; + if (fromDeposit) { + note = sdkWrapper.depositSecret(scope, accNonce); + const noteLabel = await sdkWrapper.findLabelFromDepositNote(asset, note); + label = noteLabel; + newNote = sdkWrapper.withdrawSecret(noteLabel, 0n); + } else if (fromLabel !== undefined) { + note = sdkWrapper.withdrawSecret(fromLabel, accNonce); + newNote = sdkWrapper.withdrawSecret(fromLabel, accNonce + 1n); + label = fromLabel; + } else { + throw new Error("No deposit or label"); + } + + console.log("note", note); + console.log("newnote", newNote); + + // 0.1 ETH or 1.5 dollars + const withdrawAmount = amount; + + let data; + let feeCommitment = undefined; + if (withQuote) { + const quoteRes = await quote({ + chainId, + amount: withdrawAmount.toString(), + asset, + recipient, + extraGas + }); + data = quoteRes.feeCommitment!.withdrawalData as `0x${string}`; + feeCommitment = { + ...quoteRes.feeCommitment, + }; + } else { + data = encodeFeeData({ recipient, feeRecipient, relayFeeBPS: 100n }); + } + + const withdrawal = { processooor, data }; + + // prove + const { proof, publicSignals } = await sdkWrapper.proveWithdrawal(withdrawAmount, withdrawal, scope, label, value, note, newNote, leaves); + + const requestBody = { + scope: scope.toString(), + chainId: sdkWrapper.chainContext.chain.id, + withdrawal, + publicSignals, + proof, + feeCommitment + }; + + await request(requestBody); +} + +interface DefArgs { + _: string[], + chainId: number; + privateKey: `0x${string}`; +} + +export async function cli() { + let args = minimist(process.argv.slice(2), { + string: ["asset", "fromLabel", "accNonce", "output"], + boolean: ["quote", "extraGas", "fromDeposit"], + alias: { + "private-key": "privateKey", + "chain-id": "chainId", + "from-deposit": "fromDeposit", + "acc-nonce": "accNonce", + "from-label": "fromLabel", + "cache-file": "cacheFile" + }, + default: { + "chainId": process.env["CHAIN_ID"] || 1115511, + "privateKey": process.env["PRIVATE_KEY"] || PRIVATE_KEY, + "asset": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + "extraGas": true, + "quote": false, + "fromDeposit": false, + } + }); + const action = process.argv[2]!; + const actions = [ + "deposit", + "quote", + "relay", + "tree" + ]; + + if (!actions.includes(action)) { + console.log("No action selected"); + process.exit(0); + } + + const context = { chainId: Number.parseInt(args.chainId), privateKey: args.privateKey }; + + switch (action) { + case "deposit": { + args = args as DefArgs & { amount: string, asset?: string; note: string; }; + const r = await depositCli({ accNonce: BigInt(args.accNonce), amount: BigInt(args.amount), asset: args.asset, context }); + console.log(r); + break; + } + case "quote": { + if (args.length < 3) { + throw Error("Not enough args"); + } + args = args as DefArgs & { amount: string, asset: string; extraGas: boolean; }; + await quoteCli({ + context, + asset: getAddress(args.asset), + amount: BigInt(args.amount), + extraGas: args.extraGas + }); + break; + } + case "relay": { + args = args as DefArgs & { + amount: string; + asset: string; + quote: boolean; + extraGas: boolean; + + fromDeposit: boolean; + fromLabel: string; + accNonce: string; + value: string; + + cacheFile: string; + }; + + await relayCli({ + context, + asset: getAddress(args.asset), + amount: BigInt(args.amount), + extraGas: args.extraGas, + withQuote: args.quote, + + fromDeposit: args.fromDeposit, + fromLabel: args.fromLabel ? BigInt(args.fromLabel) : undefined, + accNonce: BigInt(args.accNonce), + value: BigInt(args.value), + + leaves: readLeavesFromFile(args.cacheFile), + }); + break; + } + case "tree": { + args = args as DefArgs & { fromBlock: string; asset: string; output?: string; }; + buildTreeCache({ context, asset: args.asset, fromBlock: args.fromBlock, output: args.output }); + break; + } + case undefined: { + console.log("No action selected"); + break; + } + } + +} + +async function buildTreeCache({ context, asset, fromBlock, output }: { fromBlock: string; asset: string; output?: string; } & { context: Context; }) { + console.log("Building tree"); + const { chainId, privateKey } = context; + const sdkWrapper = new SdkWrapper(ChainContext(chainId, privateKey)); + const pool = await sdkWrapper.chainContext.getPoolContract(asset as `0x${string}`); + const leavesRaw = await pool.getEvents.LeafInserted({ fromBlock: BigInt(fromBlock) }); + const leaves = leavesRaw.map(l => ({ + index: l.args._index!.toString(), + leaf: l.args._leaf!.toString(), + root: l.args._root!.toString(), + block: l.blockNumber.toString() + })); + const timestamp = (new Date()).toISOString().replaceAll(":", "_").replace(new RegExp(".[0-9]{3}Z"), ""); + const treeFileName = output || `./tree-cache-${timestamp}.json`; + fs.writeFileSync(treeFileName, JSON.stringify(leaves, null, 2)); + console.log(`Wrote ${leaves.length} leaves to file ${treeFileName}`); +} + +function readLeavesFromFile(filePath: string) { + const rawLeaves = JSON.parse(fs.readFileSync(filePath, { encoding: 'utf-8' })) as { index: string, leaf: string, root: string, block: string; }[]; + return rawLeaves.map(l => ({ + index: BigInt(l.index), + leaf: BigInt(l.leaf), + root: BigInt(l.root), + block: BigInt(l.block), + })); +} diff --git a/packages/relayer/test/integration/relay/src/constants.ts b/packages/relayer/test/integration/relay/src/constants.ts index 985f8ad..d57d631 100644 --- a/packages/relayer/test/integration/relay/src/constants.ts +++ b/packages/relayer/test/integration/relay/src/constants.ts @@ -1,12 +1,18 @@ -import { Address, Hex } from "viem"; +import { Address, getAddress, Hex } from "viem"; // // mainnet // export const ENTRYPOINT_ADDRESS: Address = "0x6818809EefCe719E480a7526D76bD3e561526b46"; -// export const ETH_POOL_ADDRESS: Address = "0xF241d57C6DebAe225c0F2e6eA1529373C9A9C9fB"; -// localnet -export const ENTRYPOINT_ADDRESS: Address = "0xd6DB18A83F9eE4e2d0FC8D6BEd075A2905A83FDA"; -export const ETH_POOL_ADDRESS: Address = "0x4Cb503503047b66aA5e64b9BDC8148E769ac52f6"; +// sepolia (localnet) +export const ENTRYPOINT_ADDRESS: Address = "0x1fF2EA3C98E22d5589d66829a1599cB74b566E94"; + export const LOCAL_ANVIL_RPC = "http://127.0.0.1:8545"; -export const PRIVATE_KEY: Hex = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + +// export const PRIVATE_KEY: Hex = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; +export const PRIVATE_KEY: Hex = "0xa278275fee36ebb6f4689e79bf1a8b4650c9aec0fc39e03c111461e5b08730eb"; // 0xb9edc9DD585C13891F5B2dE85f182d3Ea4AaEa09 + +export const processooor = ENTRYPOINT_ADDRESS; +export const feeRecipient = getAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); +export const recipient = getAddress("0xabA6aeB1bCFF1096f4b8148085C4231FED9FE8E4"); + diff --git a/packages/relayer/test/integration/relay/src/create-withdrawal.ts b/packages/relayer/test/integration/relay/src/create-withdrawal.ts deleted file mode 100644 index d3d0b6f..0000000 --- a/packages/relayer/test/integration/relay/src/create-withdrawal.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { - bigintToHash, - calculateContext, - Circuits, - getCommitment, - hashPrecommitment, - LeanIMTMerkleProof, - PrivacyPoolSDK, - Secret, - Withdrawal, - WithdrawalProof, - WithdrawalProofInput, - Hash, -} from "@0xbow/privacy-pools-core-sdk"; -import { - ENTRYPOINT_ADDRESS, - LOCAL_ANVIL_RPC, - PRIVATE_KEY, -} from "./constants.js"; -import { anvilChain } from "./chain.js"; - -/* - TestToken deployed at: 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 - Withdrawal Verifier deployed at: 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 - Ragequit Verifier deployed at: 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 - Entrypoint deployed at: 0x0165878A594ca255338adfa4d48449f69242Eb8F - ETH Pool deployed at: 0xa513E6E4b8f2a923D98304ec87F64353C4D5C853 - TST Pool deployed at: 0x8A791620dd6260079BF849Dc5567aDC3F2FdC318 -*/ - -const sdk = new PrivacyPoolSDK(new Circuits({ browser: false })); - -const contracts = sdk.createContractInstance( - LOCAL_ANVIL_RPC, - anvilChain, - ENTRYPOINT_ADDRESS, - PRIVATE_KEY, -); - -export async function deposit() { - const existingValue = BigInt("5000000000000000000"); // 5 eth - const existingNullifier = BigInt("2827991637673173") as Secret; - const existingSecret = BigInt("7338940278733227") as Secret; - const precommitment = { - hash: hashPrecommitment(existingNullifier, existingSecret), - nullifier: existingNullifier, - secret: existingSecret, - }; - return contracts.depositETH(existingValue, precommitment.hash); -} - -export async function proveWithdrawal( - w: Withdrawal, - scope: bigint, -): Promise { - try { - console.log("🚀 Initializing PrivacyPoolSDK..."); - - // **Retrieve On-Chain Scope** - console.log( - "🔹 Retrieved Scope from Withdrawal:", - `0x${scope.toString(16)}`, - ); - - // **Load Valid Input Values** - const withdrawnValue = BigInt("100000000000000000"); // 0.1 eth - const stateRoot = BigInt( - "11647068014638404411083963959916324311405860401109309104995569418439086324505", - ); - const stateTreeDepth = BigInt("2"); - const aspRoot = BigInt( - "17509119559942543382744731935952318540675152427220720285867932301410542597330", - ); - const aspTreeDepth = BigInt("2"); - const label = BigInt("2310129299332319"); - - // **Commitment Data** - const existingValue = BigInt("5000000000000000000"); - const existingNullifier = BigInt("2827991637673173") as Secret; - const existingSecret = BigInt("7338940278733227") as Secret; - const newNullifier = BigInt("1800210687471587") as Secret; - const newSecret = BigInt("6593588285288381") as Secret; - - console.log("🛠️ Generating commitments..."); - - const commitment = getCommitment( - existingValue, - label, - existingNullifier, - existingSecret, - ); - - // **State Merkle Proof** - const stateMerkleProof: LeanIMTMerkleProof = { - root: stateRoot, - leaf: commitment.hash, - index: 3, - siblings: [ - BigInt("6398878698952029"), - BigInt( - "13585012987205807684735841540436202984635744455909835202346884556845854938903", - ), - ...Array(30).fill(BigInt(0)), - ], - }; - - // **ASP Merkle Proof** - const aspMerkleProof: LeanIMTMerkleProof = { - root: aspRoot, - leaf: label, - index: 3, - siblings: [ - BigInt("3189334085279373"), - BigInt( - "1131383056830993841196498111009024161908281953428245130508088856824218714105", - ), - ...Array(30).fill(BigInt(0)), - ], - }; - - // console.log("✅ State Merkle Proof:", stateMerkleProof); - // console.log("✅ ASP Merkle Proof:", aspMerkleProof); - - // **Correctly Compute Context Hash** - const computedContext = calculateContext(w, scope as Hash); - console.log("🔹 Computed Context:", computedContext.toString()); - - // **Create Withdrawal Proof Input** - const proofInput: WithdrawalProofInput = { - context: BigInt(computedContext), - withdrawalAmount: withdrawnValue, - stateMerkleProof: stateMerkleProof, - aspMerkleProof: aspMerkleProof, - stateRoot: bigintToHash(stateRoot), - stateTreeDepth: stateTreeDepth, - aspRoot: bigintToHash(aspRoot), - aspTreeDepth: aspTreeDepth, - newSecret: newSecret, - newNullifier: newNullifier, - }; - - console.log("🚀 Generating withdrawal proof..."); - const proofPayload: WithdrawalProof = await sdk.proveWithdrawal( - commitment, - proofInput, - ); - return proofPayload; - - // if (!proofPayload) { - // throw new Error("❌ Withdrawal proof generation failed: proofPayload is null or undefined"); - // } - - // console.log("✅ Proof Payload:", proofPayload); - - // console.log("🚀 Sending withdrawal transaction..."); - // const withdrawalTx = await sdk.getContractInteractions().withdraw(withdrawObj, proofPayload); - - // console.log("✅ Withdrawal transaction sent:", withdrawalTx?.hash ?? "❌ No transaction hash returned"); - - // if (!withdrawalTx?.hash) { - // throw new Error("❌ Withdrawal transaction failed: No transaction hash returned."); - // } - - // await withdrawalTx.wait(); - // console.log("🎉 Withdrawal transaction confirmed!"); - } catch (error) { - console.error("❌ **Error running testWithdraw script**:", error); - process.exit(1); - } -} diff --git a/packages/relayer/test/integration/relay/src/main.ts b/packages/relayer/test/integration/relay/src/main.ts index 3947679..0d106b8 100644 --- a/packages/relayer/test/integration/relay/src/main.ts +++ b/packages/relayer/test/integration/relay/src/main.ts @@ -1,138 +1,7 @@ -import { Hash, Withdrawal } from "@0xbow/privacy-pools-core-sdk"; -import { encodeAbiParameters, getAddress, Hex } from "viem"; -import { request, quote } from "./api-test.js"; -import { anvilChain, pool } from "./chain.js"; -import { ENTRYPOINT_ADDRESS } from "./constants.js"; -import { deposit, proveWithdrawal } from "./create-withdrawal.js"; - -interface QuoteResponse { - baseFeeBPS: bigint, - feeBPS: bigint, - feeCommitment?: { - expiration: number, - withdrawalData: `0x${string}`, - signedRelayerCommitment: `0x${string}`, - } -} - -const FeeDataAbi = [ - { - name: "FeeData", - type: "tuple", - components: [ - { name: "recipient", type: "address" }, - { name: "feeRecipient", type: "address" }, - { name: "relayFeeBPS", type: "uint256" }, - ], - }, -]; - -const recipient = getAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"); -const processooor = ENTRYPOINT_ADDRESS; -const FEE_RECEIVER_ADDRESS = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; - -async function prove(w: Withdrawal, scope: bigint) { - return proveWithdrawal(w, scope); -} - -async function depositCli() { - const r = await deposit(); - await r.wait(); - console.log(`Successful deposit, hash := ${r.hash}`); -} - -async function quoteReq(chainId: number, asset: string, recipient: string, amount: string) { - return (await quote({ - chainId, - amount, - asset, - recipient - }) as QuoteResponse); -} - -async function quoteCli(chainId: string, asset: string, amount?: string) { - const _amount = amount ? Number(amount) : 100_000_000_000_000_000n - quoteReq(Number(chainId), asset, recipient, _amount.toString()) -} - -async function relayCli(chainId: string, asset: string, withQuote: boolean) { - - const scope = await pool.read.SCOPE() as Hash; - - let data; - let feeCommitment = undefined; - if (withQuote) { - const amount = "100000000000000000"; // 0.1 ETH - const quoteRes = await quoteReq(Number(chainId), asset, recipient, amount); - data = quoteRes.feeCommitment!.withdrawalData as Hex - feeCommitment = { - ...quoteRes.feeCommitment, - }; - } else { - data = encodeAbiParameters(FeeDataAbi, [ - { - recipient, - feeRecipient: FEE_RECEIVER_ADDRESS, - relayFeeBPS: 100n, - }, - ]) as Hex; - } - - const withdrawal = { processooor, data }; - - // prove - const { proof, publicSignals } = await prove(withdrawal, scope); - - const requestBody = { - scope: scope.toString(), - chainId: anvilChain.id, - withdrawal, - publicSignals, - proof, - feeCommitment - }; - - await request(requestBody); -} - -async function cli() { - const args = process.argv.slice(2) - const action = args[0]; - switch (action) { - case "deposit": { - console.log(action) - await depositCli(); - break; - } - case "quote": { - console.log(action) - if (args.length < 3) { - throw Error("Not enough args") - } - await quoteCli(args[1]!, args[2]!, args[3]) - break; - } - case "relay": { - console.log(...args) - const withQuote = args.includes("--with-quote") - const noFlags = args.slice(1).filter(a => a !== "--with-quote") - if (noFlags.length < 2) { - throw Error("Not enough args") - } - await relayCli(noFlags[0]!, noFlags[1]!, withQuote); - break; - } - case undefined: { - console.log("No action selected") - break; - } - } - -} - +import { cli } from "./cli.js"; (async () => { - cli(); + await cli(); })(); diff --git a/packages/relayer/test/integration/relay/src/sdk-wrapper.ts b/packages/relayer/test/integration/relay/src/sdk-wrapper.ts new file mode 100644 index 0000000..3ae6a6e --- /dev/null +++ b/packages/relayer/test/integration/relay/src/sdk-wrapper.ts @@ -0,0 +1,272 @@ +import { + bigintToHash, + calculateContext, + Circuits, + ContractInteractionsService, + generateDepositSecrets, + generateMasterKeys, + generateMerkleProof, + getCommitment, + Hash, + hashPrecommitment, + LeanIMTMerkleProof, + MasterKeys, + PrivacyPoolSDK, + Secret, + Withdrawal, + WithdrawalProof, + WithdrawalProofInput +} from "@0xbow/privacy-pools-core-sdk"; + +import { IChainContext } from "./chain.js"; +import { + ENTRYPOINT_ADDRESS, + PRIVATE_KEY +} from "./constants.js"; + +type Note = { nullifier: bigint, secret: bigint; }; + +/* + TestToken deployed at: 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 + Withdrawal Verifier deployed at: 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 + Ragequit Verifier deployed at: 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 + Entrypoint deployed at: 0x0165878A594ca255338adfa4d48449f69242Eb8F + ETH Pool deployed at: 0xa513E6E4b8f2a923D98304ec87F64353C4D5C853 + TST Pool deployed at: 0x8A791620dd6260079BF849Dc5567aDC3F2FdC318 +*/ + +export class SdkWrapper { + + chainContext: IChainContext; + sdk: PrivacyPoolSDK; + contracts: ContractInteractionsService; + mnemonic: string; + masterKeys: MasterKeys; + + constructor(chainContext: IChainContext) { + this.chainContext = chainContext; + this.sdk = new PrivacyPoolSDK(new Circuits({ browser: false })); + this.contracts = this.sdk.createContractInstance( + this.chainContext.client.transport.url, + this.chainContext.chain, + ENTRYPOINT_ADDRESS, + PRIVATE_KEY, + ); + this.mnemonic = "muscle horse fly praise focus mixed annual disorder false black bottom uncover"; + this.masterKeys = generateMasterKeys(this.mnemonic); + + } + + depositSecret(scope: bigint, index: bigint) { + return generateDepositSecrets(this.masterKeys, scope as Hash, index); + } + + withdrawSecret(label: bigint, index: bigint) { + return generateDepositSecrets(this.masterKeys, label as Hash, index); + } + + async findLabelFromDepositNote(asset: `0x${string}`, note: { nullifier: bigint; secret: bigint; }): Promise { + const pool = await this.chainContext.getPoolContract(asset); + const depositEvents = await pool.getEvents.Deposited(undefined, { fromBlock: (await this.chainContext.client.getBlockNumber()) - 50n }); + const preCommitment = hashPrecommitment(note.nullifier as Secret, note.secret as Secret); + const event = depositEvents.filter(de => de.args._precommitmentHash === preCommitment).pop(); + if (event && event?.args?._label !== undefined) { + return event.args._label; + } else { + throw Error("Can't find matching label"); + } + } + + async deposit(accNonce: bigint, amount: bigint) { + + const pool = await this.chainContext.getPoolContract("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"); + const scope = await pool.read.SCOPE() as Hash; + + const { secret, nullifier } = this.depositSecret(scope, accNonce); + + const precommitment = { + hash: hashPrecommitment(nullifier!, secret!), + nullifier: secret, + secret: nullifier, + }; + + const tx = await this.contracts.depositETH(amount, precommitment.hash); + await tx.wait(); + const depositEvents = await pool.getEvents.Deposited({ _depositor: this.chainContext.account.address }); + depositEvents.forEach(e => { + console.log("Deposited<", { + ...e.args, + blockNumber: e.blockNumber, + blockHash: e.blockHash + }, ">"); + }); + return tx; + } + + async depositAsset(accNonce: bigint, amount: bigint, assetAddress: `0x${string}`) { + + const pool = await this.chainContext.getPoolContract(assetAddress); + const scope = await pool.read.SCOPE() as Hash; + + const { secret, nullifier } = this.depositSecret(scope, accNonce); + + const precommitment = { + hash: hashPrecommitment(nullifier!, secret!), + nullifier: secret, + secret: nullifier, + }; + + const erc20 = this.chainContext.getErc20Contract(assetAddress); + await erc20.write.approve([ENTRYPOINT_ADDRESS, 2n ** 256n - 1n], { + account: this.chainContext.account, + chain: this.chainContext.chain + }); + const tx = await this.contracts.depositERC20(assetAddress, amount, precommitment.hash); + await tx.wait(); + const depositEvents = await pool.getEvents.Deposited({ _depositor: this.chainContext.account.address }); + depositEvents.forEach(e => { + console.log("Deposited<", { + ...e.args, + blockNumber: e.blockNumber, + blockHash: e.blockHash + }, ">"); + }); + return tx; + } + + async proveWithdrawal( + withdrawAmount: bigint, + w: Withdrawal, + scope: bigint, + label: bigint, + oldNoteValue: bigint, + oldNote: Note, + newNote: Note, + leaves: { + index: bigint; + leaf: bigint; + root: bigint; + block: bigint; + }[] + ): Promise { + + try { + console.log("🚀 Initializing PrivacyPoolSDK..."); + + // **Retrieve On-Chain Scope** + console.log( + "🔹 Retrieved Scope from Withdrawal:", + `0x${scope.toString(16)}`, + ); + + const pool = this.chainContext.getPoolContractByScope(scope); + + // **Load Valid Input Values** + const stateTreeDepth = await pool.read.currentTreeDepth(); + // pool.read.currentTreeSize(); + const stateRoot = await pool.read.currentRoot(); + + // const stateRoot = BigInt( + // "11647068014638404411083963959916324311405860401109309104995569418439086324505", + // ); + // const stateTreeDepth = BigInt("2"); + + const { secret: existingSecret, nullifier: existingNullifier } = oldNote; + const { secret: newSecret, nullifier: newNullifier } = newNote; + + console.log("🛠️ Generating commitments..."); + + const commitment = getCommitment( + oldNoteValue, + label, + existingNullifier as Secret, + existingSecret as Secret, + ); + + const sortedLeaves = leaves.sort((a, b) => Number(a.index - b.index)).map(x => x.leaf); + + // **State Merkle Proof** + const stateMerkleProof: LeanIMTMerkleProof = generateMerkleProof(sortedLeaves, commitment.hash); + stateMerkleProof.index = Number.isNaN(stateMerkleProof.index) ? 0 : stateMerkleProof.index; + if (stateMerkleProof.siblings.length < 32) { + const N = 32 - stateMerkleProof.siblings.length; + const siblings = [...stateMerkleProof.siblings, ...Array(N).fill(0n)]; + stateMerkleProof.siblings = siblings; + } + stateMerkleProof.siblings = stateMerkleProof.siblings.length === 0 ? [stateRoot, ...Array(31).fill(0n)] : stateMerkleProof.siblings; + console.log(stateMerkleProof); + + // const stateMerkleProof: LeanIMTMerkleProof = { + // root: stateRoot, + // leaf: commitment.hash, + // index: 3, + // siblings: [ + // BigInt("6398878698952029"), + // BigInt( + // "13585012987205807684735841540436202984635744455909835202346884556845854938903", + // ), + // ...Array(30).fill(BigInt(0)), + // ], + // }; + + // const aspRoot = BigInt( + // "17509119559942543382744731935952318540675152427220720285867932301410542597330", + // ); + // const aspTreeDepth = BigInt("2"); + + const firstSib = 1n; + const aspRoot = hashPrecommitment(label as Secret, firstSib as Secret); + const aspTreeDepth = 2n; + // **ASP Merkle Proof** + const aspMerkleProof: LeanIMTMerkleProof = { + root: aspRoot, + leaf: label, + index: 0, + siblings: [ + firstSib, + ...Array(31).fill(BigInt(0)), + // BigInt("3189334085279373"), + // BigInt( + // "1131383056830993841196498111009024161908281953428245130508088856824218714105", + // ), + // ...Array(30).fill(BigInt(0)), + ], + }; + + // console.log("✅ State Merkle Proof:", stateMerkleProof); + // console.log("✅ ASP Merkle Proof:", aspMerkleProof); + + // **Correctly Compute Context Hash** + const computedContext = calculateContext(w, scope as Hash); + console.log("🔹 Computed Context:", computedContext.toString()); + + // **Create Withdrawal Proof Input** + const proofInput: WithdrawalProofInput = { + context: BigInt(computedContext), + withdrawalAmount: withdrawAmount, + stateMerkleProof: stateMerkleProof, + aspMerkleProof: aspMerkleProof, + stateRoot: bigintToHash(stateRoot), + stateTreeDepth: stateTreeDepth, + aspRoot: bigintToHash(aspRoot), + aspTreeDepth: aspTreeDepth, + newSecret: newSecret as Secret, + newNullifier: newNullifier as Secret, + }; + + console.log("🚀 Generating withdrawal proof..."); + const proofPayload: WithdrawalProof = await this.sdk.proveWithdrawal( + commitment, + proofInput, + ); + + console.log(proofPayload) + return proofPayload; + + } catch (error) { + console.error("❌ **Error running testWithdraw script**:", error); + process.exit(1); + } + } + +} diff --git a/packages/relayer/test/integration/relay/src/util.ts b/packages/relayer/test/integration/relay/src/util.ts new file mode 100644 index 0000000..16534a4 --- /dev/null +++ b/packages/relayer/test/integration/relay/src/util.ts @@ -0,0 +1,31 @@ +import { encodeAbiParameters } from "viem"; + +const FeeDataAbi = [ + { + name: "FeeData", + type: "tuple", + components: [ + { name: "recipient", type: "address" }, + { name: "feeRecipient", type: "address" }, + { name: "relayFeeBPS", type: "uint256" }, + ], + }, +]; + +export function encodeFeeData({ + recipient, feeRecipient, relayFeeBPS +}: { + recipient: `0x${string}`; feeRecipient: `0x${string}`; relayFeeBPS: bigint; +}) { + return encodeAbiParameters(FeeDataAbi, [ + { + recipient, + feeRecipient, + relayFeeBPS, + }, + ]); +} + +export function isNative(asset: `0x${string}`) { + return asset.toLowerCase() === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; +} diff --git a/packages/relayer/test/integration/relay/yarn.lock b/packages/relayer/test/integration/relay/yarn.lock index 8fe46fe..199267f 100644 --- a/packages/relayer/test/integration/relay/yarn.lock +++ b/packages/relayer/test/integration/relay/yarn.lock @@ -2,11 +2,48 @@ # yarn lockfile v1 +"@0xbow/privacy-pools-core-sdk@0.1.21": + version "0.1.21" + resolved "https://registry.yarnpkg.com/@0xbow/privacy-pools-core-sdk/-/privacy-pools-core-sdk-0.1.21.tgz#8811694288525c7a12f14150fbea085c6da88b59" + integrity sha512-UTNPHSkdR1XdQoU2u7XLaGcRCh09WP9C9yIlXrUgF0SUkiaaE2ZnyUIrhtT/oW7/zreYNZArWv8v647uVSNCzw== + dependencies: + "@types/snarkjs" "0.7.9" + "@zk-kit/lean-imt" "2.2.2" + maci-crypto "2.5.0" + snarkjs "0.7.5" + typescript "^5.7.3" + viem "2.22.14" + +"@adraffy/ens-normalize@1.10.1": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz#63430d04bd8c5e74f8d7d049338f1cd9d4f02069" + integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw== + "@adraffy/ens-normalize@^1.10.1": version "1.11.0" resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33" integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg== +"@iden3/bigarray@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@iden3/bigarray/-/bigarray-0.0.2.tgz#6fc4ba5be18daf8a26ee393f2fb62b80d98c05e9" + integrity sha512-Xzdyxqm1bOFF6pdIsiHLLl3HkSLjbhqJHVyqaTxXt3RqXBEnmsUmEW47H7VOi/ak7TdkRpNkxjyK5Zbkm+y52g== + +"@iden3/binfileutils@0.0.12": + version "0.0.12" + resolved "https://registry.yarnpkg.com/@iden3/binfileutils/-/binfileutils-0.0.12.tgz#3772552f57551814ff606fa68ea1e0ef52795ce3" + integrity sha512-naAmzuDufRIcoNfQ1d99d7hGHufLA3wZSibtr4dMe6ZeiOPV1KwOZWTJ1YVz4HbaWlpDuzVU72dS4ATQS4PXBQ== + dependencies: + fastfile "0.0.20" + ffjavascript "^0.3.0" + +"@noble/curves@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + "@noble/curves@1.8.1", "@noble/curves@^1.6.0", "@noble/curves@~1.8.1": version "1.8.1" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.8.1.tgz#19bc3970e205c99e4bdb1c64a4785706bce497ff" @@ -14,6 +51,11 @@ dependencies: "@noble/hashes" "1.7.1" +"@noble/hashes@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + "@noble/hashes@1.7.1", "@noble/hashes@^1.5.0", "@noble/hashes@~1.7.1": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.7.1.tgz#5738f6d765710921e7a751e00c20ae091ed8db0f" @@ -41,6 +83,18 @@ "@noble/hashes" "~1.7.1" "@scure/base" "~1.2.4" +"@types/minimist@1.2.8": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e" + integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== + +"@types/node@22.7.5": + version "22.7.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b" + integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ== + dependencies: + undici-types "~6.19.2" + "@types/node@^22.13.0": version "22.13.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.0.tgz#d376dd9a0ee2f9382d86c2d5d7beb4d198b4ea8c" @@ -48,21 +102,377 @@ dependencies: undici-types "~6.20.0" +"@types/snarkjs@0.7.9": + version "0.7.9" + resolved "https://registry.yarnpkg.com/@types/snarkjs/-/snarkjs-0.7.9.tgz#7a3b99bd86009133a74dcb215a475382c772c37c" + integrity sha512-pb4Bq3GI2YQOQOG0dR/YuQs/mqcuL6k/vnz68LIPtpA2frrUL3twf69a3AUK9eUmNNeW0RIKkq6scDlC75Is+g== + +"@zk-kit/baby-jubjub@1.0.3", "@zk-kit/baby-jubjub@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@zk-kit/baby-jubjub/-/baby-jubjub-1.0.3.tgz#8d2eccd20d729f1dbd39203dbff9a245a61dea76" + integrity sha512-Wl+QfV6XGOMk1yU2JTqHXeKWfJVXp83is0+dtqfj9wx4wsAPpb+qzYvwAxW5PBx5/Nu71Bh7jp/5vM+6QgHSwA== + dependencies: + "@zk-kit/utils" "1.2.1" + +"@zk-kit/eddsa-poseidon@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@zk-kit/eddsa-poseidon/-/eddsa-poseidon-1.1.0.tgz#08ef95ccbb2fbb5260617b6b5120dfb25a25229b" + integrity sha512-Djc+zOZjd73FpLLf32/XeVZi8GX4ShPQJGS4Iig1QMAR/2CggEi++6Jrkr9N2FM3M4MRCH1qxz2u22DjOLtASg== + dependencies: + "@zk-kit/baby-jubjub" "1.0.3" + "@zk-kit/utils" "1.2.1" + blakejs "^1.2.1" + buffer "6.0.3" + poseidon-lite "0.3.0" + +"@zk-kit/lean-imt@2.2.2": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@zk-kit/lean-imt/-/lean-imt-2.2.2.tgz#79c8bd70fc0d444638328cb4781479b14c69a9dd" + integrity sha512-rscIPEgBBcu9vP/DJ3J+3187G/ObKETl343G5enPawNT81oeQSdHx3e2ZapTC+GfrZ/AS2AHHUOpRS1FfdSwjg== + dependencies: + "@zk-kit/utils" "1.2.1" + +"@zk-kit/poseidon-cipher@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@zk-kit/poseidon-cipher/-/poseidon-cipher-0.3.2.tgz#a2afcc1e4fcfa9db3b245d584183cd7fb7fcd4ab" + integrity sha512-Ezz1e0mj/GRDlHdU5m0uhj5iHY72zWJU0BP8DsCCvPubU7LPI2tVaPpxrAjT4JQqatbVRQHLIhixW7F7BPzaFg== + dependencies: + "@zk-kit/baby-jubjub" "1.0.3" + "@zk-kit/utils" "1.2.1" + +"@zk-kit/utils@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@zk-kit/utils/-/utils-1.2.1.tgz#6cb38120535c73ab68cd0f09684882af148f256d" + integrity sha512-H2nTsyWdicVOyvqC5AjgU7tsTgmR6PDrruFJNmlmdhKp7RxEia/E1B1swMZjaasYa2QMp4Zc6oB7cWchty7B2Q== + dependencies: + buffer "^6.0.3" + abitype@1.0.8, abitype@^1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.8.tgz#3554f28b2e9d6e9f35eb59878193eabd1b9f46ba" integrity sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg== +aes-js@4.0.0-beta.5: + version "4.0.0-beta.5" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873" + integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +async@^3.2.3: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + +b4a@^1.0.1: + version "1.6.7" + resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.7.tgz#a99587d4ebbfbd5a6e3b21bdb5d5fa385767abe4" + integrity sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bfj@^7.0.2: + version "7.1.0" + resolved "https://registry.yarnpkg.com/bfj/-/bfj-7.1.0.tgz#c5177d522103f9040e1b12980fe8c38cf41d3f8b" + integrity sha512-I6MMLkn+anzNdCUp9hMRyui1HaNEUCco50lxbvNS4+EyXg8lN3nJ48PjPWtbH8UVS9CuMoaKE9U2V3l29DaRQw== + dependencies: + bluebird "^3.7.2" + check-types "^11.2.3" + hoopy "^0.1.4" + jsonpath "^1.1.1" + tryer "^1.0.1" + +blake2b-wasm@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/blake2b-wasm/-/blake2b-wasm-2.4.0.tgz#9115649111edbbd87eb24ce7c04b427e4e2be5be" + integrity sha512-S1kwmW2ZhZFFFOghcx73+ZajEfKBqhP82JMssxtLVMxlaPea1p9uoLiUZ5WYyHn0KddwbLc+0vh4wR0KBNoT5w== + dependencies: + b4a "^1.0.1" + nanoassert "^2.0.0" + +blakejs@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" + integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ== + +bluebird@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +buffer@6.0.3, buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +chalk@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +check-types@^11.2.3: + version "11.2.3" + resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.2.3.tgz#1ffdf68faae4e941fce252840b1787b8edc93b71" + integrity sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg== + +circom_runtime@0.1.28: + version "0.1.28" + resolved "https://registry.yarnpkg.com/circom_runtime/-/circom_runtime-0.1.28.tgz#4ea4606956eeac4499f71f65354f45b54faa93fe" + integrity sha512-ACagpQ7zBRLKDl5xRZ4KpmYIcZDUjOiNRuxvXLqhnnlLSVY1Dbvh73TI853nqoR0oEbihtWmMSjgc5f+pXf/jQ== + dependencies: + ffjavascript "0.3.1" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +ejs@^3.1.6: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + +escodegen@^1.8.1: + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +esprima@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-1.2.2.tgz#76a0fd66fcfe154fd292667dc264019750b1657b" + integrity sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A== + +esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +ethers@^6.13.4: + version "6.14.3" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.14.3.tgz#7c4443c165ee59b2964e691600fd4586004b2000" + integrity sha512-qq7ft/oCJohoTcsNPFaXSQUm457MA5iWqkf1Mb11ujONdg7jBI6sAOrHaTi3j0CBqIGFSCeR/RMc+qwRRub7IA== + dependencies: + "@adraffy/ens-normalize" "1.10.1" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@types/node" "22.7.5" + aes-js "4.0.0-beta.5" + tslib "2.7.0" + ws "8.17.1" + eventemitter3@5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastfile@0.0.20: + version "0.0.20" + resolved "https://registry.yarnpkg.com/fastfile/-/fastfile-0.0.20.tgz#794a143d58cfda2e24c298e5ef619c748c8a1879" + integrity sha512-r5ZDbgImvVWCP0lA/cGNgQcZqR+aYdFx3u+CtJqUE510pBUVGMn4ulL/iRTI4tACTYsNJ736uzFxEBXesPAktA== + +ffjavascript@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/ffjavascript/-/ffjavascript-0.3.0.tgz#442cd8fbb1ee4cbb1be9d26fd7b2951a1ea45d6a" + integrity sha512-l7sR5kmU3gRwDy8g0Z2tYBXy5ttmafRPFOqY7S6af5cq51JqJWt5eQ/lSR/rs2wQNbDYaYlQr5O+OSUf/oMLoQ== + dependencies: + wasmbuilder "0.0.16" + wasmcurves "0.2.2" + web-worker "1.2.0" + +ffjavascript@0.3.1, ffjavascript@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/ffjavascript/-/ffjavascript-0.3.1.tgz#3761bbb3f4a67b58a94a463080272bf6b5877b03" + integrity sha512-4PbK1WYodQtuF47D4pRI5KUg3Q392vuP5WjE1THSnceHdXwU3ijaoS0OqxTzLknCtz4Z2TtABzkBdBdMn3B/Aw== + dependencies: + wasmbuilder "0.0.16" + wasmcurves "0.2.2" + web-worker "1.2.0" + +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hoopy@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d" + integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ== + +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + isows@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.6.tgz#0da29d706fa51551c663c627ace42769850f86e7" integrity sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw== +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + +js-sha3@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + +jsonpath@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/jsonpath/-/jsonpath-1.1.1.tgz#0ca1ed8fb65bb3309248cc9d5466d12d5b0b9901" + integrity sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w== + dependencies: + esprima "1.2.2" + static-eval "2.0.2" + underscore "1.12.1" + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +logplease@^1.2.15: + version "1.2.15" + resolved "https://registry.yarnpkg.com/logplease/-/logplease-1.2.15.tgz#3da442e93751a5992cc19010a826b08d0293c48a" + integrity sha512-jLlHnlsPSJjpwUfcNyUxXCl33AYg2cHhIf9QhGL2T4iPT0XPB+xP1LRKFPgIg1M/sg9kAJvy94w9CzBNrfnstA== + +maci-crypto@2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/maci-crypto/-/maci-crypto-2.5.0.tgz#89a99921517b79564af8a356863c446b49e4cb0f" + integrity sha512-ozrLDH6kaK62TomNr5tnVgrUs6szXHCwcRyPzsQy07Wg2ZX61nyY0EFgWKAuU8kXqvYRdTtQdgflw6qpVz/4+w== + dependencies: + "@zk-kit/baby-jubjub" "^1.0.3" + "@zk-kit/eddsa-poseidon" "^1.1.0" + "@zk-kit/poseidon-cipher" "^0.3.2" + ethers "^6.13.4" + +minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimist@1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +nanoassert@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/nanoassert/-/nanoassert-2.0.0.tgz#a05f86de6c7a51618038a620f88878ed1e490c09" + integrity sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA== + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + ox@0.6.7: version "0.6.7" resolved "https://registry.yarnpkg.com/ox/-/ox-0.6.7.tgz#afd53f2ecef68b8526660e9d29dee6e6b599a832" @@ -76,11 +486,98 @@ ox@0.6.7: abitype "^1.0.6" eventemitter3 "5.0.1" +poseidon-lite@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/poseidon-lite/-/poseidon-lite-0.3.0.tgz#93c42f6f9b870f154f2722dfd686b909c4285765" + integrity sha512-ilJj4MIve4uBEG7SrtPqUUNkvpJ/pLVbndxa0WvebcQqeIhe+h72JR4g0EvwchUzm9sOQDlOjiDNmRAgxNZl4A== + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== + +r1csfile@0.0.48: + version "0.0.48" + resolved "https://registry.yarnpkg.com/r1csfile/-/r1csfile-0.0.48.tgz#a317fc75407a9da92631666c75bdfc13f0a7835a" + integrity sha512-kHRkKUJNaor31l05f2+RFzvcH5XSa7OfEfd/l4hzjte6NL6fjRkSMfZ4BjySW9wmfdwPOtq3mXurzPvPGEf5Tw== + dependencies: + "@iden3/bigarray" "0.0.2" + "@iden3/binfileutils" "0.0.12" + fastfile "0.0.20" + ffjavascript "0.3.0" + +snarkjs@0.7.5: + version "0.7.5" + resolved "https://registry.yarnpkg.com/snarkjs/-/snarkjs-0.7.5.tgz#334d83b61468bdffbbf922b20734ca47be50b8ab" + integrity sha512-h+3c4rXZKLhLuHk4LHydZCk/h5GcNvk5GjVKRRkHmfb6Ntf8gHOA9zea3g656iclRuhqQ3iKDWFgiD9ypLrKiA== + dependencies: + "@iden3/binfileutils" "0.0.12" + bfj "^7.0.2" + blake2b-wasm "^2.4.0" + circom_runtime "0.1.28" + ejs "^3.1.6" + fastfile "0.0.20" + ffjavascript "0.3.1" + js-sha3 "^0.8.0" + logplease "^1.2.15" + r1csfile "0.0.48" + +source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +static-eval@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/static-eval/-/static-eval-2.0.2.tgz#2d1759306b1befa688938454c546b7871f806a42" + integrity sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg== + dependencies: + escodegen "^1.8.1" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +tryer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" + integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== + +tslib@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== + dependencies: + prelude-ls "~1.1.2" + typescript@5.5.4: version "5.5.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== +typescript@^5.7.3: + version "5.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" + integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== + +underscore@1.12.1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.12.1.tgz#7bb8cc9b3d397e201cf8553336d262544ead829e" + integrity sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw== + +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + undici-types@~6.20.0: version "6.20.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" @@ -100,6 +597,33 @@ viem@2.22.14: ox "0.6.7" ws "8.18.0" +wasmbuilder@0.0.16: + version "0.0.16" + resolved "https://registry.yarnpkg.com/wasmbuilder/-/wasmbuilder-0.0.16.tgz#f34c1f2c047d2f6e1065cbfec5603988f16d8549" + integrity sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA== + +wasmcurves@0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/wasmcurves/-/wasmcurves-0.2.2.tgz#ca444f6a6f6e2a5cbe6629d98ff478a62b4ccb2b" + integrity sha512-JRY908NkmKjFl4ytnTu5ED6AwPD+8VJ9oc94kdq7h5bIwbj0L4TDJ69mG+2aLs2SoCmGfqIesMWTEJjtYsoQXQ== + dependencies: + wasmbuilder "0.0.16" + +web-worker@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/web-worker/-/web-worker-1.2.0.tgz#5d85a04a7fbc1e7db58f66595d7a3ac7c9c180da" + integrity sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA== + +word-wrap@~1.2.3: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +ws@8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== + ws@8.18.0: version "8.18.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" diff --git a/packages/relayer/test/unit/handlers.relayer.request.spec.ts b/packages/relayer/test/unit/handlers.relayer.request.spec.ts index 6189e2b..e74f212 100644 --- a/packages/relayer/test/unit/handlers.relayer.request.spec.ts +++ b/packages/relayer/test/unit/handlers.relayer.request.spec.ts @@ -58,12 +58,13 @@ const withdrawalPayload = { data: "0xfeeMismatch", }, proof: { - pi_a: ["0", "0"], + pi_a: ["0", "0", "0"], pi_b: [ ["0", "0"], ["0", "0"], + ["0", "0"], ], - pi_c: ["0", "0"], + pi_c: ["0", "0", "0"], protocol: "groth16", curve: "bn128", }, @@ -119,6 +120,7 @@ describe("relayRequestHandler", () => { vi.spyOn(privacyPoolRelayer, "handleRequest").mockResolvedValue(undefined); await relayRequestHandler(req, resMock, nextMock); const error = nextMock.mock.calls[0][0] + console.log(error) expect(error).toBeInstanceOf(ConfigError) expect(error.code).toEqual(ErrorCode.MAX_GAS_PRICE); }); diff --git a/packages/relayer/tsconfig.build.json b/packages/relayer/tsconfig.build.json index 96dfb8b..90d4eb1 100644 --- a/packages/relayer/tsconfig.build.json +++ b/packages/relayer/tsconfig.build.json @@ -4,6 +4,7 @@ "declarationMap": true, "declaration": true, "outDir": "dist", + "rootDir": "src", /* Base Options: */ "esModuleInterop": true, "skipLibCheck": true, @@ -26,5 +27,5 @@ "lib": ["es2022"] }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "test"] + "exclude": ["node_modules", "dist", "test", "coverage"] } diff --git a/yarn.lock b/yarn.lock index d242218..731a6fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -576,7 +576,7 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/abi@^5.5.0": +"@ethersproject/abi@5.8.0", "@ethersproject/abi@^5.5.0", "@ethersproject/abi@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.8.0.tgz#e79bb51940ac35fe6f3262d7fe2cdb25ad5f07d9" integrity sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q== @@ -604,7 +604,7 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/web" "^5.7.0" -"@ethersproject/abstract-provider@^5.8.0": +"@ethersproject/abstract-provider@5.8.0", "@ethersproject/abstract-provider@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.8.0.tgz#7581f9be601afa1d02b95d26b9d9840926a35b0c" integrity sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg== @@ -628,7 +628,7 @@ "@ethersproject/logger" "^5.7.0" "@ethersproject/properties" "^5.7.0" -"@ethersproject/abstract-signer@^5.8.0": +"@ethersproject/abstract-signer@5.8.0", "@ethersproject/abstract-signer@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.8.0.tgz#8d7417e95e4094c1797a9762e6789c7356db0754" integrity sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA== @@ -650,7 +650,7 @@ "@ethersproject/logger" "^5.7.0" "@ethersproject/rlp" "^5.7.0" -"@ethersproject/address@^5.0.2", "@ethersproject/address@^5.8.0": +"@ethersproject/address@5.8.0", "@ethersproject/address@^5.0.2", "@ethersproject/address@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.8.0.tgz#3007a2c352eee566ad745dca1dbbebdb50a6a983" integrity sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA== @@ -668,7 +668,7 @@ dependencies: "@ethersproject/bytes" "^5.7.0" -"@ethersproject/base64@^5.8.0": +"@ethersproject/base64@5.8.0", "@ethersproject/base64@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.8.0.tgz#61c669c648f6e6aad002c228465d52ac93ee83eb" integrity sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ== @@ -683,6 +683,14 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/properties" "^5.7.0" +"@ethersproject/basex@5.8.0", "@ethersproject/basex@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.8.0.tgz#1d279a90c4be84d1c1139114a1f844869e57d03a" + integrity sha512-PIgTszMlDRmNwW9nhS6iqtVfdTAKosA7llYXNmGPw4YAI1PUyMv28988wAb41/gHF/WqGdoLv0erHaRcHRKW2Q== + dependencies: + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" @@ -692,7 +700,7 @@ "@ethersproject/logger" "^5.7.0" bn.js "^5.2.1" -"@ethersproject/bignumber@^5.8.0": +"@ethersproject/bignumber@5.8.0", "@ethersproject/bignumber@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.8.0.tgz#c381d178f9eeb370923d389284efa19f69efa5d7" integrity sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA== @@ -708,7 +716,7 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/bytes@^5.8.0": +"@ethersproject/bytes@5.8.0", "@ethersproject/bytes@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.8.0.tgz#9074820e1cac7507a34372cadeb035461463be34" integrity sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A== @@ -722,7 +730,7 @@ dependencies: "@ethersproject/bignumber" "^5.7.0" -"@ethersproject/constants@^5.8.0": +"@ethersproject/constants@5.8.0", "@ethersproject/constants@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.8.0.tgz#12f31c2f4317b113a4c19de94e50933648c90704" integrity sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg== @@ -745,6 +753,22 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/transactions" "^5.7.0" +"@ethersproject/contracts@5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.8.0.tgz#243a38a2e4aa3e757215ea64e276f8a8c9d8ed73" + integrity sha512-0eFjGz9GtuAi6MZwhb4uvUM216F38xiuR0yYCjKJpNfSEy4HUM8hvqqBj9Jmm0IUz8l0xKEhWwLIhPgxNY0yvQ== + dependencies: + "@ethersproject/abi" "^5.8.0" + "@ethersproject/abstract-provider" "^5.8.0" + "@ethersproject/abstract-signer" "^5.8.0" + "@ethersproject/address" "^5.8.0" + "@ethersproject/bignumber" "^5.8.0" + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/constants" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/transactions" "^5.8.0" + "@ethersproject/hash@5.7.0", "@ethersproject/hash@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" @@ -760,7 +784,7 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/hash@^5.8.0": +"@ethersproject/hash@5.8.0", "@ethersproject/hash@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.8.0.tgz#b8893d4629b7f8462a90102572f8cd65a0192b4c" integrity sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA== @@ -793,6 +817,24 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/wordlists" "^5.7.0" +"@ethersproject/hdnode@5.8.0", "@ethersproject/hdnode@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.8.0.tgz#a51ae2a50bcd48ef6fd108c64cbae5e6ff34a761" + integrity sha512-4bK1VF6E83/3/Im0ERnnUeWOY3P1BZml4ZD3wcH8Ys0/d1h1xaFt6Zc+Dh9zXf9TapGro0T4wvO71UTCp3/uoA== + dependencies: + "@ethersproject/abstract-signer" "^5.8.0" + "@ethersproject/basex" "^5.8.0" + "@ethersproject/bignumber" "^5.8.0" + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/pbkdf2" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/sha2" "^5.8.0" + "@ethersproject/signing-key" "^5.8.0" + "@ethersproject/strings" "^5.8.0" + "@ethersproject/transactions" "^5.8.0" + "@ethersproject/wordlists" "^5.8.0" + "@ethersproject/json-wallets@5.7.0", "@ethersproject/json-wallets@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz#5e3355287b548c32b368d91014919ebebddd5360" @@ -812,6 +854,25 @@ aes-js "3.0.0" scrypt-js "3.0.1" +"@ethersproject/json-wallets@5.8.0", "@ethersproject/json-wallets@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.8.0.tgz#d18de0a4cf0f185f232eb3c17d5e0744d97eb8c9" + integrity sha512-HxblNck8FVUtNxS3VTEYJAcwiKYsBIF77W15HufqlBF9gGfhmYOJtYZp8fSDZtn9y5EaXTE87zDwzxRoTFk11w== + dependencies: + "@ethersproject/abstract-signer" "^5.8.0" + "@ethersproject/address" "^5.8.0" + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/hdnode" "^5.8.0" + "@ethersproject/keccak256" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/pbkdf2" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/random" "^5.8.0" + "@ethersproject/strings" "^5.8.0" + "@ethersproject/transactions" "^5.8.0" + aes-js "3.0.0" + scrypt-js "3.0.1" + "@ethersproject/keccak256@5.7.0", "@ethersproject/keccak256@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" @@ -820,7 +881,7 @@ "@ethersproject/bytes" "^5.7.0" js-sha3 "0.8.0" -"@ethersproject/keccak256@^5.8.0": +"@ethersproject/keccak256@5.8.0", "@ethersproject/keccak256@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.8.0.tgz#d2123a379567faf2d75d2aaea074ffd4df349e6a" integrity sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng== @@ -833,7 +894,7 @@ resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== -"@ethersproject/logger@^5.8.0": +"@ethersproject/logger@5.8.0", "@ethersproject/logger@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.8.0.tgz#f0232968a4f87d29623a0481690a2732662713d6" integrity sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA== @@ -845,7 +906,7 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/networks@^5.8.0": +"@ethersproject/networks@5.8.0", "@ethersproject/networks@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.8.0.tgz#8b4517a3139380cba9fb00b63ffad0a979671fde" integrity sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg== @@ -860,6 +921,14 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/sha2" "^5.7.0" +"@ethersproject/pbkdf2@5.8.0", "@ethersproject/pbkdf2@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.8.0.tgz#cd2621130e5dd51f6a0172e63a6e4a0c0a0ec37e" + integrity sha512-wuHiv97BrzCmfEaPbUFpMjlVg/IDkZThp9Ri88BpjRleg4iePJaj2SW8AIyE8cXn5V1tuAaMj6lzvsGJkGWskg== + dependencies: + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/sha2" "^5.8.0" + "@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" @@ -867,7 +936,7 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/properties@^5.8.0": +"@ethersproject/properties@5.8.0", "@ethersproject/properties@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.8.0.tgz#405a8affb6311a49a91dabd96aeeae24f477020e" integrity sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw== @@ -900,6 +969,32 @@ bech32 "1.1.4" ws "7.4.6" +"@ethersproject/providers@5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.8.0.tgz#6c2ae354f7f96ee150439f7de06236928bc04cb4" + integrity sha512-3Il3oTzEx3o6kzcg9ZzbE+oCZYyY+3Zh83sKkn4s1DZfTUjIegHnN2Cm0kbn9YFy45FDVcuCLLONhU7ny0SsCw== + dependencies: + "@ethersproject/abstract-provider" "^5.8.0" + "@ethersproject/abstract-signer" "^5.8.0" + "@ethersproject/address" "^5.8.0" + "@ethersproject/base64" "^5.8.0" + "@ethersproject/basex" "^5.8.0" + "@ethersproject/bignumber" "^5.8.0" + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/constants" "^5.8.0" + "@ethersproject/hash" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/networks" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/random" "^5.8.0" + "@ethersproject/rlp" "^5.8.0" + "@ethersproject/sha2" "^5.8.0" + "@ethersproject/strings" "^5.8.0" + "@ethersproject/transactions" "^5.8.0" + "@ethersproject/web" "^5.8.0" + bech32 "1.1.4" + ws "8.18.0" + "@ethersproject/random@5.7.0", "@ethersproject/random@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.7.0.tgz#af19dcbc2484aae078bb03656ec05df66253280c" @@ -908,6 +1003,14 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/logger" "^5.7.0" +"@ethersproject/random@5.8.0", "@ethersproject/random@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.8.0.tgz#1bced04d49449f37c6437c701735a1a022f0057a" + integrity sha512-E4I5TDl7SVqyg4/kkA/qTfuLWAQGXmSOgYyO01So8hLfwgKvYK5snIlzxJMk72IFdG/7oh8yuSqY2KX7MMwg+A== + dependencies: + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/rlp@5.7.0", "@ethersproject/rlp@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" @@ -916,7 +1019,7 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/rlp@^5.8.0": +"@ethersproject/rlp@5.8.0", "@ethersproject/rlp@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.8.0.tgz#5a0d49f61bc53e051532a5179472779141451de5" integrity sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q== @@ -933,7 +1036,7 @@ "@ethersproject/logger" "^5.7.0" hash.js "1.1.7" -"@ethersproject/sha2@^5.8.0": +"@ethersproject/sha2@5.8.0", "@ethersproject/sha2@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.8.0.tgz#8954a613bb78dac9b46829c0a95de561ef74e5e1" integrity sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A== @@ -954,7 +1057,7 @@ elliptic "6.5.4" hash.js "1.1.7" -"@ethersproject/signing-key@^5.8.0": +"@ethersproject/signing-key@5.8.0", "@ethersproject/signing-key@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.8.0.tgz#9797e02c717b68239c6349394ea85febf8893119" integrity sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w== @@ -978,7 +1081,7 @@ "@ethersproject/sha2" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/solidity@^5.0.9": +"@ethersproject/solidity@5.8.0", "@ethersproject/solidity@^5.0.9": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.8.0.tgz#429bb9fcf5521307a9448d7358c26b93695379b9" integrity sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA== @@ -999,7 +1102,7 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/strings@^5.8.0": +"@ethersproject/strings@5.8.0", "@ethersproject/strings@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.8.0.tgz#ad79fafbf0bd272d9765603215ac74fd7953908f" integrity sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg== @@ -1023,7 +1126,7 @@ "@ethersproject/rlp" "^5.7.0" "@ethersproject/signing-key" "^5.7.0" -"@ethersproject/transactions@^5.8.0": +"@ethersproject/transactions@5.8.0", "@ethersproject/transactions@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.8.0.tgz#1e518822403abc99def5a043d1c6f6fe0007e46b" integrity sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg== @@ -1047,6 +1150,15 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" +"@ethersproject/units@5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.8.0.tgz#c12f34ba7c3a2de0e9fa0ed0ee32f3e46c5c2c6a" + integrity sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ== + dependencies: + "@ethersproject/bignumber" "^5.8.0" + "@ethersproject/constants" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/wallet@5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.7.0.tgz#4e5d0790d96fe21d61d38fb40324e6c7ef350b2d" @@ -1068,6 +1180,27 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/wordlists" "^5.7.0" +"@ethersproject/wallet@5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.8.0.tgz#49c300d10872e6986d953e8310dc33d440da8127" + integrity sha512-G+jnzmgg6UxurVKRKvw27h0kvG75YKXZKdlLYmAHeF32TGUzHkOFd7Zn6QHOTYRFWnfjtSSFjBowKo7vfrXzPA== + dependencies: + "@ethersproject/abstract-provider" "^5.8.0" + "@ethersproject/abstract-signer" "^5.8.0" + "@ethersproject/address" "^5.8.0" + "@ethersproject/bignumber" "^5.8.0" + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/hash" "^5.8.0" + "@ethersproject/hdnode" "^5.8.0" + "@ethersproject/json-wallets" "^5.8.0" + "@ethersproject/keccak256" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/random" "^5.8.0" + "@ethersproject/signing-key" "^5.8.0" + "@ethersproject/transactions" "^5.8.0" + "@ethersproject/wordlists" "^5.8.0" + "@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0": version "5.7.1" resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" @@ -1079,7 +1212,7 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/web@^5.8.0": +"@ethersproject/web@5.8.0", "@ethersproject/web@^5.8.0": version "5.8.0" resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.8.0.tgz#3e54badc0013b7a801463a7008a87988efce8a37" integrity sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw== @@ -1101,6 +1234,17 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" +"@ethersproject/wordlists@5.8.0", "@ethersproject/wordlists@^5.8.0": + version "5.8.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.8.0.tgz#7a5654ee8d1bb1f4dbe43f91d217356d650ad821" + integrity sha512-2df9bbXicZws2Sb5S6ET493uJ0Z84Fjr3pC4tu/qlnZERibZCeUVuqdtt+7Tv9xxhUxHoIekIA7avrKUWHrezg== + dependencies: + "@ethersproject/bytes" "^5.8.0" + "@ethersproject/hash" "^5.8.0" + "@ethersproject/logger" "^5.8.0" + "@ethersproject/properties" "^5.8.0" + "@ethersproject/strings" "^5.8.0" + "@gar/promisify@^1.0.1": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" @@ -1349,6 +1493,16 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2-solc-0.7.tgz#38f4dbab672631034076ccdf2f3201fab1726635" integrity sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA== +"@openzeppelin/contracts@4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.7.0.tgz#3092d70ea60e3d1835466266b1d68ad47035a2d5" + integrity sha512-52Qb+A1DdOss8QvJrijYYPSf32GUg2pGaG/yCxtaA3cu4jduouTdg4XZSMLW9op54m1jH7J8hoajhHKOPsoJFw== + +"@openzeppelin/contracts@5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.2.tgz#b1d03075e49290d06570b2fd42154d76c2a5d210" + integrity sha512-ytPc6eLGcHHnapAZ9S+5qsdomhjo6QBHTDRRBFfTxXIpsicMhVPouPgmUPebZZZGX7vt9USA+Z+0M0dSVtSUEA== + "@openzeppelin/contracts@5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.1.0.tgz#4e61162f2a2bf414c4e10c45eca98ce5f1aadbd4" @@ -1937,7 +2091,27 @@ resolved "https://registry.yarnpkg.com/@uniswap/lib/-/lib-4.0.1-alpha.tgz#2881008e55f075344675b3bca93f020b028fbd02" integrity sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA== -"@uniswap/sdk-core@^7.7.1": +"@uniswap/permit2-sdk@^1.3.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@uniswap/permit2-sdk/-/permit2-sdk-1.3.1.tgz#831ced0240e2f9f323c53b9a0366e92ae351b2b8" + integrity sha512-Eq2by4zVEVSZL3PJ1Yuf5+AZ/yE1GOuksWzPXPoxr5WRm3hqh34jKEqtyTImHqwuPrdILG8i02xJmgGLTH1QfA== + dependencies: + ethers "^5.7.0" + tiny-invariant "^1.1.0" + +"@uniswap/router-sdk@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@uniswap/router-sdk/-/router-sdk-2.0.3.tgz#4f7e1398bb4f9e01128810205e3b39e7f0ac091f" + integrity sha512-LWDdkpg8qS3wGtLetNojQPdGvBDb9ZSJ9XePmFQhqy2VR/YcERcAsLEjVAKhObPxkazjULIIlmf2yIsCAYzX/w== + dependencies: + "@ethersproject/abi" "^5.5.0" + "@uniswap/sdk-core" "^7.7.2" + "@uniswap/swap-router-contracts" "^1.3.0" + "@uniswap/v2-sdk" "^4.15.2" + "@uniswap/v3-sdk" "^3.25.2" + "@uniswap/v4-sdk" "^1.21.2" + +"@uniswap/sdk-core@7.7.2", "@uniswap/sdk-core@^7.7.1", "@uniswap/sdk-core@^7.7.2": version "7.7.2" resolved "https://registry.yarnpkg.com/@uniswap/sdk-core/-/sdk-core-7.7.2.tgz#dc3a9b63b343640754860dce05d06972815eaee6" integrity sha512-0KqXw+y0opBo6eoPAEoLHEkNpOu0NG9gEk7GAYIGok+SHX89WlykWsRYeJKTg9tOwhLpcG9oHg8xZgQ390iOrA== @@ -1964,11 +2138,49 @@ dotenv "^14.2.0" hardhat-watcher "^2.1.1" -"@uniswap/v2-core@^1.0.1": +"@uniswap/universal-router-sdk@4.19.5": + version "4.19.5" + resolved "https://registry.yarnpkg.com/@uniswap/universal-router-sdk/-/universal-router-sdk-4.19.5.tgz#6c11ad0168e33c652014caffe21bf8c462253b14" + integrity sha512-FpfvTrJnvxhLdfX1N+GqOfAbHEsbFHP7J3JbpSAFWl3QDW63BB1/rqgW3E17xQha+WnPdP7uMLHwe7oi6mr39Q== + dependencies: + "@openzeppelin/contracts" "4.7.0" + "@uniswap/permit2-sdk" "^1.3.0" + "@uniswap/router-sdk" "^2.0.2" + "@uniswap/sdk-core" "^7.7.1" + "@uniswap/universal-router" "2.0.0-beta.2" + "@uniswap/v2-core" "^1.0.1" + "@uniswap/v2-sdk" "^4.15.2" + "@uniswap/v3-core" "1.0.0" + "@uniswap/v3-sdk" "^3.25.2" + "@uniswap/v4-sdk" "^1.21.2" + bignumber.js "^9.0.2" + ethers "^5.7.0" + +"@uniswap/universal-router@2.0.0-beta.2": + version "2.0.0-beta.2" + resolved "https://registry.yarnpkg.com/@uniswap/universal-router/-/universal-router-2.0.0-beta.2.tgz#0f891c4772733c356465d8ed2f76a43989ec9092" + integrity sha512-/USVkWZrOCjLeZluR7Yk8SpfWDUKG/MLcOyuxuwnqM1xCJj5ekguSYhct+Yfo/3t9fsZcnL8vSYgz0MKqAomGg== + dependencies: + "@openzeppelin/contracts" "5.0.2" + "@uniswap/v2-core" "1.0.1" + "@uniswap/v3-core" "1.0.0" + +"@uniswap/v2-core@1.0.1", "@uniswap/v2-core@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@uniswap/v2-core/-/v2-core-1.0.1.tgz#af8f508bf183204779938969e2e54043e147d425" integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q== +"@uniswap/v2-sdk@^4.15.2": + version "4.15.2" + resolved "https://registry.yarnpkg.com/@uniswap/v2-sdk/-/v2-sdk-4.15.2.tgz#904c09449e9012e6888c44f4cd0cfd8e61c8f4c8" + integrity sha512-EtROgWTdhHzw4EUj7SdK9wjppOG7psJ16c656cRuv69nWbD9QyDL2shVcQccEiY7ak9WlJ+bIv/VldybXYBDuw== + dependencies: + "@ethersproject/address" "^5.0.2" + "@ethersproject/solidity" "^5.0.9" + "@uniswap/sdk-core" "^7.7.1" + tiny-invariant "^1.1.0" + tiny-warning "^1.0.3" + "@uniswap/v3-core@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@uniswap/v3-core/-/v3-core-1.0.0.tgz#6c24adacc4c25dceee0ba3ca142b35adbd7e359d" @@ -1990,7 +2202,7 @@ "@uniswap/v3-core" "^1.0.0" base64-sol "1.0.1" -"@uniswap/v3-sdk@^3.25.2": +"@uniswap/v3-sdk@3.25.2", "@uniswap/v3-sdk@^3.25.2": version "3.25.2" resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-3.25.2.tgz#cb6ee174b58d86a3b3b18b3ba72f662e58c415da" integrity sha512-0oiyJNGjUVbc958uZmAr+m4XBCjV7PfMs/OUeBv+XDl33MEYF/eH86oBhvqGDM8S/cYaK55tCXzoWkmRUByrHg== @@ -2013,6 +2225,17 @@ "@uniswap/v3-core" "1.0.0" "@uniswap/v3-periphery" "^1.0.1" +"@uniswap/v4-sdk@^1.21.2": + version "1.21.4" + resolved "https://registry.yarnpkg.com/@uniswap/v4-sdk/-/v4-sdk-1.21.4.tgz#b009be0535e757d26fb01af7375ae11e96b6728f" + integrity sha512-so3c/CmaRmRSvgKFyrUWy6DCSogyzyVaoYCec/TJ4k2hXlJ8MK4vumcuxtmRr1oMnZ5KmaCPBS12Knb4FC3nsw== + dependencies: + "@ethersproject/solidity" "^5.0.9" + "@uniswap/sdk-core" "^7.7.1" + "@uniswap/v3-sdk" "3.25.2" + tiny-invariant "^1.1.0" + tiny-warning "^1.0.3" + "@vitest/coverage-v8@3.0.5": version "3.0.5" resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-3.0.5.tgz#22a5f6730f13703ce6736f7d0032251a3619a300" @@ -2421,6 +2644,11 @@ big.js@^5.2.2: resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== +bignumber.js@^9.0.2: + version "9.3.0" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.3.0.tgz#bdba7e2a4c1a2eba08290e8dcad4f36393c92acd" + integrity sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA== + binary-extensions@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" @@ -3535,6 +3763,42 @@ ethers@^5.5.1: "@ethersproject/web" "5.7.1" "@ethersproject/wordlists" "5.7.0" +ethers@^5.7.0: + version "5.8.0" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.8.0.tgz#97858dc4d4c74afce83ea7562fe9493cedb4d377" + integrity sha512-DUq+7fHrCg1aPDFCHx6UIPb3nmt2XMpM7Y/g2gLhsl3lIBqeAfOJIl1qEvRf2uq3BiKxmh6Fh5pfp2ieyek7Kg== + dependencies: + "@ethersproject/abi" "5.8.0" + "@ethersproject/abstract-provider" "5.8.0" + "@ethersproject/abstract-signer" "5.8.0" + "@ethersproject/address" "5.8.0" + "@ethersproject/base64" "5.8.0" + "@ethersproject/basex" "5.8.0" + "@ethersproject/bignumber" "5.8.0" + "@ethersproject/bytes" "5.8.0" + "@ethersproject/constants" "5.8.0" + "@ethersproject/contracts" "5.8.0" + "@ethersproject/hash" "5.8.0" + "@ethersproject/hdnode" "5.8.0" + "@ethersproject/json-wallets" "5.8.0" + "@ethersproject/keccak256" "5.8.0" + "@ethersproject/logger" "5.8.0" + "@ethersproject/networks" "5.8.0" + "@ethersproject/pbkdf2" "5.8.0" + "@ethersproject/properties" "5.8.0" + "@ethersproject/providers" "5.8.0" + "@ethersproject/random" "5.8.0" + "@ethersproject/rlp" "5.8.0" + "@ethersproject/sha2" "5.8.0" + "@ethersproject/signing-key" "5.8.0" + "@ethersproject/solidity" "5.8.0" + "@ethersproject/strings" "5.8.0" + "@ethersproject/transactions" "5.8.0" + "@ethersproject/units" "5.8.0" + "@ethersproject/wallet" "5.8.0" + "@ethersproject/web" "5.8.0" + "@ethersproject/wordlists" "5.8.0" + ethers@^6.13.4: version "6.13.5" resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.13.5.tgz#8c1d6ac988ac08abc3c1d8fabbd4b8b602851ac4"