diff --git a/.changeset/five-frogs-tie.md b/.changeset/five-frogs-tie.md new file mode 100644 index 000000000..09850be56 --- /dev/null +++ b/.changeset/five-frogs-tie.md @@ -0,0 +1,5 @@ +--- +'@relayprotocol/relay-sdk': patch +--- + +Fast fill sdk action diff --git a/demo/components/providers/RelayKitProviderWrapper.tsx b/demo/components/providers/RelayKitProviderWrapper.tsx index f764381f7..1347fca07 100644 --- a/demo/components/providers/RelayKitProviderWrapper.tsx +++ b/demo/components/providers/RelayKitProviderWrapper.tsx @@ -65,6 +65,7 @@ export const RelayKitProviderWrapper: FC<{ } }) ) + console.log('message', message, level) } }} theme={{ diff --git a/demo/pages/api/fast-fill.ts b/demo/pages/api/fast-fill.ts new file mode 100644 index 000000000..1d7f1ad19 --- /dev/null +++ b/demo/pages/api/fast-fill.ts @@ -0,0 +1,80 @@ +import { paths, createClient } from '@relayprotocol/relay-sdk' +import type { NextApiRequest, NextApiResponse } from 'next' + +type FastFillRequest = + paths['/fast-fill']['post']['requestBody']['content']['application/json'] & { + password?: string // Password for fast fill authentication + } +type FastFillResponse = + paths['/fast-fill']['post']['responses']['200']['content']['application/json'] +type RequestsV2Response = + paths['/requests/v2']['get']['responses']['200']['content']['application/json'] + +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, password } = + req.body as FastFillRequest + + if (!requestId) { + return res.status(400).json({ error: 'requestId is required' }) + } + + // Check password first (before any API calls) + const expectedPassword = process.env.FAST_FILL_PASSWORD + if (!expectedPassword) { + return res.status(500).json({ + error: 'Fast fill password not configured on server' + }) + } + + if (!password || password !== expectedPassword) { + return res.status(401).json({ error: 'Invalid fast fill password' }) + } + + 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 { + // Create a RelayClient instance with the API key and base URL + const relayClient = createClient({ + baseApiUrl, + apiKey + }) + + // Call the fast-fill API using the SDK action + // The fastFill action uses getClient() internally, which will return + // the client we just created via createClient() + try { + const fastFillData = await relayClient.actions.fastFill({ + requestId, + ...(solverInputCurrencyAmount && { solverInputCurrencyAmount }) + }) + + return res.status(200).json(fastFillData) + } catch (error: any) { + // Handle APIError from the SDK + if (error.statusCode) { + return res.status(error.statusCode).json({ + error: error.message || 'Fast fill failed' + }) + } + throw error + } + } 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 cbb955b2c..f6e50650e 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 000000000..ff965d97c --- /dev/null +++ b/demo/pages/sdk/actions/fastFill.tsx @@ -0,0 +1,340 @@ +import { NextPage } from 'next' +import { useMemo, useState } from 'react' +import { useWalletClient } from 'wagmi' +import { useRelayClient } from '@relayprotocol/relay-kit-ui' +import { ConnectButton } from 'components/ConnectButton' +import { + adaptViemWallet, + type QuoteBody, + type AdaptedWallet, + type Execute, + type TransactionStepItem +} 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) + const [fastFillPassword, setFastFillPassword] = useState('') + + // Create a custom wallet adapter that wraps the viemWallet adapter + // and intercepts handleSendTransactionStep to call fastFill + const createFastFillWalletAdapter = ( + originalWallet: AdaptedWallet, + password: string + ): AdaptedWallet => { + return { + ...originalWallet, + handleSendTransactionStep: async ( + chainId: number, + stepItem: TransactionStepItem, + step: Execute['steps'][0] + ) => { + const txHash = await originalWallet.handleSendTransactionStep( + chainId, + stepItem, + step + ) + // Call fastFill proxy API if requestId is available + if (step.requestId && step.id === 'deposit') { + try { + console.log('Calling fastFill proxy for requestId:', step.requestId) + const response = await fetch('/api/fast-fill', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + requestId: step.requestId, + password + }) + }) + + if (response.ok) { + const data = await response.json() + console.log('FastFill called successfully:', data) + } else { + const error = await response.json() + console.warn( + 'FastFill error (continuing with transaction):', + error.error || error.message + ) + } + } catch (e: any) { + // Log error but don't fail the transaction + console.warn( + 'FastFill error (continuing with transaction):', + e?.message || String(e) + ) + } + } + + return txHash + } + } + } + + 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) + + if (!fastFillPassword) { + setError('Fast fill password is required') + setLoading(false) + return + } + + try { + // Adapt the wallet + const adaptedWallet = adaptViemWallet(wallet) + + // Wrap it with our fastFill adapter + const fastFillWallet = createFastFillWalletAdapter( + adaptedWallet, + fastFillPassword + ) + + // 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: QuoteBody + if (quoteParams.trim()) { + params = JSON.parse(quoteParams) + } else { + setError('Please provide quote parameters as JSON') + setLoading(false) + return + } + + const userAddress = wallet.account?.address + if (!userAddress) { + setError('Could not get address from connected wallet') + setLoading(false) + return + } + const adaptedWallet = adaptViemWallet(wallet) + const quoteResult = await client.actions.getQuote( + { + chainId: params.originChainId, + toChainId: params.destinationChainId, + currency: params.originCurrency, + toCurrency: params.destinationCurrency, + amount: params.amount, + tradeType: params.tradeType, + wallet: adaptedWallet, + user: userAddress, + recipient: params.recipient ?? userAddress + }, + true + ) + + setQuote(quoteResult) + } catch (e: any) { + setError(e?.message || String(e)) + } finally { + setLoading(false) + } + } + + const progressString = useMemo(() => { + try { + return JSON.stringify(progress, null, 2) + } catch (e) { + return '' + } + }, [result]) + + return ( +
+ + +
+ +
+ Note that user/recipient are not required — they are taken from the + connected wallet. +
+