From 9465751fa9761c8bc9ba68ac2d351ef9ec3e67d7 Mon Sep 17 00:00:00 2001 From: pedromcunha Date: Fri, 23 Jan 2026 14:21:36 -0500 Subject: [PATCH 1/5] Add fastFill action and demo page --- demo/pages/api/fast-fill.ts | 108 +++++++++++ demo/pages/index.tsx | 1 + demo/pages/sdk/actions/fastFill.tsx | 278 +++++++++++++++++++++++++++ packages/sdk/src/actions/fastFill.ts | 49 +++++ packages/sdk/src/actions/index.ts | 1 + 5 files changed, 437 insertions(+) create mode 100644 demo/pages/api/fast-fill.ts create mode 100644 demo/pages/sdk/actions/fastFill.tsx create mode 100644 packages/sdk/src/actions/fastFill.ts diff --git a/demo/pages/api/fast-fill.ts b/demo/pages/api/fast-fill.ts new file mode 100644 index 00000000..e0980baa --- /dev/null +++ b/demo/pages/api/fast-fill.ts @@ -0,0 +1,108 @@ +import { paths } from '@relayprotocol/relay-sdk' +import type { NextApiRequest, NextApiResponse } from 'next' + +type FastFillRequest = paths['/fast-fill']['post']['requestBody']['content']['application/json'] +type FastFillResponse = paths['/fast-fill']['post']['responses']['200']['content']['application/json'] +type RequestsV2Response = paths['/requests/v2']['get']['responses']['200']['content']['application/json'] + +// Whitelist of allowed user addresses +const WHITELISTED_USERS = ['0x03508bB71268BBA25ECaCC8F620e01866650532c'] + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed' }) + } + + const { requestId, solverInputCurrencyAmount } = req.body as FastFillRequest + + if (!requestId) { + return res.status(400).json({ error: 'requestId is required' }) + } + + const apiKey = process.env.NEXT_RELAY_API_KEY + if (!apiKey) { + return res.status(500).json({ error: 'API key not configured' }) + } + + const baseApiUrl = process.env.NEXT_PUBLIC_RELAY_API_URL || 'https://api.relay.link' + + try { + // Fetch the request to check user and status + const requestsUrl = new URL(`${baseApiUrl}/requests/v2`) + requestsUrl.searchParams.set('id', requestId) + + const requestsResponse = await fetch(requestsUrl.href, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': apiKey + } + }) + + if (!requestsResponse.ok) { + return res.status(requestsResponse.status).json({ + error: `Failed to fetch request: ${requestsResponse.statusText}` + }) + } + + const requestsData = (await requestsResponse.json()) as RequestsV2Response + const request = requestsData.requests?.[0] + + if (!request) { + return res.status(404).json({ error: 'Request not found' }) + } + + // Check if user is whitelisted + const user = request.user?.toLowerCase() + const isWhitelisted = WHITELISTED_USERS.some( + (addr) => addr.toLowerCase() === user + ) + + if (!isWhitelisted) { + return res.status(403).json({ + error: `User ${request.user} is not whitelisted for fast fill` + }) + } + + // Check if request is already in success status + if (request.status === 'success') { + return res.status(400).json({ + error: 'Request is already in success status' + }) + } + + // Call the fast-fill API + const fastFillUrl = `${baseApiUrl}/fast-fill` + const fastFillBody: FastFillRequest = { + requestId, + ...(solverInputCurrencyAmount && { solverInputCurrencyAmount }) + } + + const fastFillResponse = await fetch(fastFillUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': apiKey + }, + body: JSON.stringify(fastFillBody) + }) + + const fastFillData = await fastFillResponse.json() + + if (!fastFillResponse.ok) { + return res.status(fastFillResponse.status).json({ + error: fastFillData.error || fastFillData.message || 'Fast fill failed' + }) + } + + return res.status(200).json(fastFillData as FastFillResponse) + } catch (error: any) { + console.error('Fast fill proxy error:', error) + return res.status(500).json({ + error: error?.message || 'Internal server error' + }) + } +} diff --git a/demo/pages/index.tsx b/demo/pages/index.tsx index cbb955b2..f6e50650 100644 --- a/demo/pages/index.tsx +++ b/demo/pages/index.tsx @@ -34,6 +34,7 @@ const Index: NextPage = () => { Bridge Swap Claim App Fees + Fast Fill

SDK Read Actions diff --git a/demo/pages/sdk/actions/fastFill.tsx b/demo/pages/sdk/actions/fastFill.tsx new file mode 100644 index 00000000..1511f1b8 --- /dev/null +++ b/demo/pages/sdk/actions/fastFill.tsx @@ -0,0 +1,278 @@ +import { NextPage } from 'next' +import { useState } from 'react' +import { useWalletClient } from 'wagmi' +import { useRelayClient } from '@relayprotocol/relay-kit-ui' +import { ConnectButton } from 'components/ConnectButton' +import { + adaptViemWallet, + type AdaptedWallet, + type Execute, + type GetQuoteParameters, + type TransactionStepItem, + getClient +} from '@relayprotocol/relay-sdk' + +const FastFillPage: NextPage = () => { + const { data: wallet } = useWalletClient() + const client = useRelayClient() + const [quoteParams, setQuoteParams] = useState('') + const [quote, setQuote] = useState(null) + const [result, setResult] = useState(null) + const [progress, setProgress] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + // Create a custom wallet adapter that wraps the viemWallet adapter + // and intercepts handleSendTransactionStep to call fastFill + const createFastFillWalletAdapter = ( + originalWallet: AdaptedWallet + ): AdaptedWallet => { + return { + ...originalWallet, + handleSendTransactionStep: async ( + chainId: number, + stepItem: TransactionStepItem, + step: Execute['steps'][0] + ) => { + // Call fastFill SDK action if requestId is available + if (step.requestId) { + try { + console.log('Calling fastFill for requestId:', step.requestId) + const relayClient = getClient() + await relayClient.actions.fastFill({ + requestId: step.requestId + }) + console.log('FastFill called successfully') + } catch (e: any) { + // Log error but don't fail the transaction + console.warn( + 'FastFill error (continuing with transaction):', + e?.message || String(e) + ) + } + } + + // Call the original handleSendTransactionStep method + return originalWallet.handleSendTransactionStep(chainId, stepItem, step) + } + } + } + + const handleExecute = async () => { + if (!client) { + setError('Missing Client!') + return + } + if (!wallet) { + setError('Please connect your wallet!') + return + } + if (!quote) { + setError('Please get a quote first!') + return + } + + setError(null) + setResult(null) + setProgress(null) + setLoading(true) + + try { + // Adapt the wallet + const adaptedWallet = adaptViemWallet(wallet) + + // Wrap it with our fastFill adapter + const fastFillWallet = createFastFillWalletAdapter(adaptedWallet) + + // Execute the quote with the fastFill wallet adapter + const executeResult = await client.actions.execute({ + wallet: fastFillWallet, + quote, + onProgress: setProgress + }) + + setResult(executeResult.data) + } catch (e: any) { + setError(e?.message || String(e)) + } finally { + setLoading(false) + } + } + + const handleGetQuote = async () => { + if (!client) { + setError('Missing Client!') + return + } + if (!wallet) { + setError('Please connect your wallet!') + return + } + + setError(null) + setQuote(null) + setLoading(true) + + try { + let params: GetQuoteParameters + if (quoteParams.trim()) { + params = JSON.parse(quoteParams) + } else { + setError('Please provide quote parameters as JSON') + setLoading(false) + return + } + + const adaptedWallet = adaptViemWallet(wallet) + const quoteResult = await client.actions.getQuote({ + ...params, + wallet: adaptedWallet + }) + + setQuote(quoteResult) + } catch (e: any) { + setError(e?.message || String(e)) + } finally { + setLoading(false) + } + } + + return ( +
+ + +
+ +