diff --git a/packages/relayer/src/handlers/relayer/quote.ts b/packages/relayer/src/handlers/relayer/quote.ts index c427219..09906a1 100644 --- a/packages/relayer/src/handlers/relayer/quote.ts +++ b/packages/relayer/src/handlers/relayer/quote.ts @@ -1,9 +1,12 @@ import { NextFunction, Request, Response } from "express"; import { getAddress } from "viem"; -import { getAssetConfig } from "../../config/index.js"; +import { getAssetConfig, getFeeReceiverAddress } from "../../config/index.js"; import { QuoterError } from "../../exceptions/base.exception.js"; import { quoteProvider, web3Provider } from "../../providers/index.js"; import { QuoteMarshall } from "../../types.js"; +import { encodeWithdrawalData } from "../../utils.js"; + +const TIME_20_SECS = 20 * 1000; export async function relayQuoteHandler( req: Request, @@ -25,11 +28,28 @@ export async function relayQuoteHandler( const quote = await quoteProvider.quoteNativeTokenInERC20(chainId, tokenAddress, amountIn); const feeBPS = await quoteProvider.quoteFeeBPSNative(config.fee_bps, amountIn, quote, gasPrice, value); + const recipient = req.body.recipient ? getAddress(req.body.recipient.toString()) : undefined + + const quoteResponse = new QuoteMarshall({ + baseFeeBPS: config.fee_bps, + feeBPS, + }); + + if (recipient) { + const feeReceiverAddress = getFeeReceiverAddress(chainId); + const withdrawalData = encodeWithdrawalData({ + feeRecipient: getAddress(feeReceiverAddress), + recipient, + relayFeeBPS: feeBPS + }) + const expiration = Number(new Date()) + TIME_20_SECS + const relayerCommitment = { withdrawalData, expiration }; + const signedRelayerCommitment = await web3Provider.signRelayerCommitment(chainId, relayerCommitment); + quoteResponse.addFeeCommitment({ expiration, withdrawalData, signedRelayerCommitment }) + } + res .status(200) - .json(res.locals.marshalResponse(new QuoteMarshall({ - baseFeeBPS: config.fee_bps, - feeBPS, - }))); + .json(res.locals.marshalResponse(quoteResponse)); } diff --git a/packages/relayer/src/interfaces/relayer/quote.ts b/packages/relayer/src/interfaces/relayer/quote.ts index 6165bec..322bf06 100644 --- a/packages/relayer/src/interfaces/relayer/quote.ts +++ b/packages/relayer/src/interfaces/relayer/quote.ts @@ -8,10 +8,15 @@ export interface QuotetBody { /** Asset address */ asset: string; /** Asset address */ - address?: string; + recipient?: string; } export interface QuoteResponse { baseFeeBPS: bigint, feeBPS: bigint, + feeCommitment?: { + expiration: number, + withdrawalData: string + signedRelayerCommitment: string + } } diff --git a/packages/relayer/src/providers/web3.provider.ts b/packages/relayer/src/providers/web3.provider.ts index b3471e8..c220a69 100644 --- a/packages/relayer/src/providers/web3.provider.ts +++ b/packages/relayer/src/providers/web3.provider.ts @@ -1,14 +1,29 @@ -import { Chain, createPublicClient, http, PublicClient } from "viem"; +import { Chain, createPublicClient, Hex, http, PublicClient } from "viem"; import { - CONFIG + CONFIG, + getSignerPrivateKey } from "../config/index.js"; import { createChainObject } from "../utils.js"; +import { privateKeyToAccount } from "viem/accounts"; interface IWeb3Provider { client(chainId: number): PublicClient; getGasPrice(chainId: number): Promise; } +const domain = (chainId: number) => ({ + name: "Privacy Pools Relayer", + version: "1", + chainId, +} as const) + +const RelayerCommitmentTypes = { + RelayerCommitment: [ + { name: "withdrawalData", type: "bytes" }, + { name: "expiration", type: "uint256" }, + ] +} as const; + /** * Class representing the provider for interacting with several chains */ @@ -42,4 +57,19 @@ export class Web3Provider implements IWeb3Provider { return await this.client(chainId).getGasPrice() } + async signRelayerCommitment(chainId: number, commitment: { withdrawalData: `0x${string}`, expiration: number }) { + const pk = getSignerPrivateKey(chainId) as Hex; + const signer = privateKeyToAccount(pk); + const {withdrawalData, expiration} = commitment; + return signer.signTypedData({ + domain: domain(chainId), + types: RelayerCommitmentTypes, + primaryType: 'RelayerCommitment', + message: { + withdrawalData, + expiration: BigInt(expiration) + } + }) + } + } diff --git a/packages/relayer/src/schemes/relayer/quote.scheme.ts b/packages/relayer/src/schemes/relayer/quote.scheme.ts index f888810..4b3f623 100644 --- a/packages/relayer/src/schemes/relayer/quote.scheme.ts +++ b/packages/relayer/src/schemes/relayer/quote.scheme.ts @@ -10,7 +10,7 @@ const quoteSchema: JSONSchemaType = { chainId: { type: ["string", "number"] }, amount: { type: ["string"] }, asset: { type: ["string"] }, - address: { type: ["string"], nullable: true }, + recipient: { type: ["string"], nullable: true }, }, required: ["chainId", "amount", "asset"], } as const; diff --git a/packages/relayer/src/types.ts b/packages/relayer/src/types.ts index 089e38f..6195c87 100644 --- a/packages/relayer/src/types.ts +++ b/packages/relayer/src/types.ts @@ -41,10 +41,20 @@ export class QuoteMarshall extends RelayerMarshall { constructor(readonly response: QuoteResponse) { super(); } + + addFeeCommitment(feeCommitment: { + expiration: number + withdrawalData: string, + signedRelayerCommitment: string + }) { + this.response.feeCommitment = feeCommitment; + } + override toJSON(): object { return { baseFeeBPS: this.response.baseFeeBPS.toString(), feeBPS: this.response.feeBPS.toString(), + feeCommitment: this.response.feeCommitment } } }