diff --git a/.changeset/flat-mangos-knock.md b/.changeset/flat-mangos-knock.md new file mode 100644 index 000000000..d8af5584b --- /dev/null +++ b/.changeset/flat-mangos-knock.md @@ -0,0 +1,5 @@ +--- +'@relayprotocol/relay-sdk': patch +--- + +Support API key parameter in `createClient` SDK method diff --git a/demo/pages/sdk/actions/getPrice.tsx b/demo/pages/sdk/actions/getPrice.tsx deleted file mode 100644 index 5931ef5ae..000000000 --- a/demo/pages/sdk/actions/getPrice.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { NextPage } from 'next' -import { useState } from 'react' -import { Execute } from '@relayprotocol/relay-sdk' -import { useAccount } from 'wagmi' -import { base, baseGoerli, sepolia, zora } from 'viem/chains' -import { Address, isAddress, zeroAddress } from 'viem' -import { useRelayClient } from '@relayprotocol/relay-kit-ui' -import { ConnectButton } from 'components/ConnectButton' - -const GetPricePage: NextPage = () => { - const [recipient, setRecipient] = useState() - const [amount, setAmount] = useState('') - const [originCurrency, setOriginCurrency] = useState(zeroAddress) - const [destinationCurrency, setDestinationCurrency] = - useState(zeroAddress) - const [toChainId, setToChainId] = useState(zora.id) - const [fromChainId, setFromChainId] = useState(base.id) - const [useExactInput, setUseExactInput] = useState(false) - const { address } = useAccount() - const [response, setResponse] = useState(null) - const client = useRelayClient() - - return ( -
- -
- - setToChainId(Number(e.target.value))} - /> -
-
- - setFromChainId(Number(e.target.value))} - /> -
-
- - setAmount(e.target.value)} - /> -
-
- - setDestinationCurrency(e.target.value)} - /> -
-
- - setOriginCurrency(e.target.value)} - /> -
-
- - setRecipient(e.target.value)} - /> -
- -
- - { - setUseExactInput(e.target.checked) - }} - /> -
- - - {response && ( -
-
{JSON.stringify(response, null, 2)}
-
- )} -
- ) -} - -export default GetPricePage diff --git a/demo/pages/sdk/actions/getSolverCapacity.tsx b/demo/pages/sdk/actions/getSolverCapacity.tsx deleted file mode 100644 index 3e8d64724..000000000 --- a/demo/pages/sdk/actions/getSolverCapacity.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { NextPage } from 'next' -import { GetConfigResponse } from '@relayprotocol/relay-sdk' -import { useState } from 'react' -import { base, zora } from 'viem/chains' -import { zeroAddress } from 'viem' -import { useWalletClient } from 'wagmi' -import { useRelayClient } from '@relayprotocol/relay-kit-ui' -import { ConnectButton } from 'components/ConnectButton' - -const GetSolverCapcityPage: NextPage = () => { - const [destinationChainId, setDestinationChainId] = useState( - zora.id.toString() - ) - const [originChainId, setOriginChainId] = useState(base.id.toString()) - const [currency, setCurrency] = useState('eth') - const [user, setUser] = useState(zeroAddress) - const { data: wallet } = useWalletClient() - const [response, setResponse] = useState(null) - const client = useRelayClient() - - return ( -
- -
- - setOriginChainId(e.target.value)} - /> -
-
- - setDestinationChainId(e.target.value)} - /> -
-
- - setCurrency(e.target.value)} - /> -
-
- - setUser(e.target.value)} - /> -
- - {response && ( -
-
{JSON.stringify(response, null, 2)}
-
- )} -
- ) -} - -export default GetSolverCapcityPage diff --git a/packages/sdk/src/actions/apiKey.test.ts b/packages/sdk/src/actions/apiKey.test.ts new file mode 100644 index 000000000..9ea3d1ef6 --- /dev/null +++ b/packages/sdk/src/actions/apiKey.test.ts @@ -0,0 +1,113 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { createClient } from '../client' +import { MAINNET_RELAY_API } from '../constants' +import { axios } from '../utils' +import { zeroAddress } from 'viem' + +/** + * Verifies that the x-api-key header is included in requests + * when apiKey is configured in createClient. + */ + +let axiosRequestSpy: ReturnType + +const mockAxiosRequest = () => { + return vi.spyOn(axios, 'request').mockImplementation(() => { + return Promise.resolve({ + data: { status: 'success', balances: [], steps: [] }, + status: 200 + }) + }) +} + +const mockWallet = { + vmType: 'evm' as const, + getChainId: () => Promise.resolve(1), + address: () => Promise.resolve(zeroAddress), + handleSignMessageStep: vi.fn().mockResolvedValue('0x'), + handleSendTransactionStep: vi.fn().mockResolvedValue('0x'), + handleConfirmTransactionStep: vi + .fn() + .mockResolvedValue({ status: 'success' }), + switchChain: vi.fn().mockResolvedValue(undefined) +} + +describe('API Key Header Tests', () => { + beforeEach(() => { + vi.clearAllMocks() + vi.resetAllMocks() + axiosRequestSpy = mockAxiosRequest() + }) + + it('Should include x-api-key header in getQuote when apiKey is configured', async () => { + const client = createClient({ + baseApiUrl: MAINNET_RELAY_API, + apiKey: 'test-api-key' + }) + + await client.actions.getQuote( + { + toChainId: 1, + chainId: 8453, + currency: '0x0000000000000000000000000000000000000000', + toCurrency: '0x0000000000000000000000000000000000000000', + tradeType: 'EXACT_INPUT', + amount: '1000000000000000' + }, + true + ) + + expect(axiosRequestSpy).toHaveBeenCalledWith( + expect.objectContaining({ + url: expect.stringContaining('/quote'), + headers: expect.objectContaining({ + 'x-api-key': 'test-api-key' + }) + }) + ) + }) + + it('Should include x-api-key header in getAppFees when apiKey is configured', async () => { + const client = createClient({ + baseApiUrl: MAINNET_RELAY_API, + apiKey: 'test-api-key' + }) + + await client.actions.getAppFees({ + wallet: '0x0000000000000000000000000000000000000000' + }) + + expect(axiosRequestSpy).toHaveBeenCalledWith( + expect.objectContaining({ + url: expect.stringContaining('/app-fees/'), + headers: expect.objectContaining({ + 'x-api-key': 'test-api-key' + }) + }) + ) + }) + + it('Should include x-api-key header in claimAppFees when apiKey is configured', async () => { + const client = createClient({ + baseApiUrl: MAINNET_RELAY_API, + apiKey: 'test-api-key' + }) + + try { + await client.actions.claimAppFees({ + wallet: mockWallet, + chainId: 1, + currency: '0x0000000000000000000000000000000000000000' + }) + } catch {} + + expect(axiosRequestSpy).toHaveBeenCalledWith( + expect.objectContaining({ + url: expect.stringContaining('/claim'), + headers: expect.objectContaining({ + 'x-api-key': 'test-api-key' + }) + }) + ) + }) +}) diff --git a/packages/sdk/src/actions/claimAppFees.ts b/packages/sdk/src/actions/claimAppFees.ts index 4dd549165..c1991ef0c 100644 --- a/packages/sdk/src/actions/claimAppFees.ts +++ b/packages/sdk/src/actions/claimAppFees.ts @@ -4,7 +4,8 @@ import { executeSteps, adaptViemWallet, safeStructuredClone, - APIError + APIError, + getApiKeyHeader } from '../utils/index.js' import { type WalletClient } from 'viem' import { isViemWalletClient } from '../utils/viemWallet.js' @@ -62,7 +63,8 @@ export async function claimAppFees( url: `${client.baseApiUrl}/app-fees/${address}/claim`, method: 'post', data: { chainId, currency, recipient: recipient || address }, - signal: abortController.signal + signal: abortController.signal, + headers: getApiKeyHeader(client) } try { diff --git a/packages/sdk/src/actions/getAppFees.ts b/packages/sdk/src/actions/getAppFees.ts index b71a26eb8..b925a7cd3 100644 --- a/packages/sdk/src/actions/getAppFees.ts +++ b/packages/sdk/src/actions/getAppFees.ts @@ -1,7 +1,7 @@ import { type AxiosRequestConfig } from 'axios' import { axios } from '../utils/axios.js' import { getClient } from '../client.js' -import { APIError } from '../utils/index.js' +import { APIError, getApiKeyHeader } from '../utils/index.js' import type { paths } from '../types/index.js' export type GetAppFeeBody = NonNullable< @@ -32,7 +32,8 @@ export async function getAppFees( const request: AxiosRequestConfig = { url: `${client.baseApiUrl}/app-fees/${wallet}/balances`, - method: 'get' + method: 'get', + headers: getApiKeyHeader(client) } const res = await axios.request(request) diff --git a/packages/sdk/src/actions/getPrice.ts b/packages/sdk/src/actions/getPrice.ts deleted file mode 100644 index 1fd9360c3..000000000 --- a/packages/sdk/src/actions/getPrice.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { type AxiosRequestConfig } from 'axios' -import { axios } from '../utils/axios.js' -import { type Address } from 'viem' -import prepareCallTransaction from '../utils/prepareCallTransaction.js' -import { - isSimulateContractRequest, - APIError, - type SimulateContractRequest -} from '../utils/index.js' -import { getClient } from '../client.js' -import type { Execute, paths } from '../types/index.js' -import { getDeadAddress } from '../constants/address.js' - -export type PriceBody = NonNullable< - paths['/price']['post']['requestBody']['content']['application/json'] -> -export type PriceBodyOptions = Omit< - PriceBody, - | 'destinationChainId' - | 'originChainId' - | 'originCurrency' - | 'destinationCurrency' - | 'amount' - | 'recipient' -> - -export type GetPriceParameters = { - originChainId: number - originCurrency: string - destinationChainId: number - destinationCurrency: string - tradeType: PriceBodyOptions['tradeType'] - amount?: string - user?: Address - recipient?: Address - options?: Omit - txs?: (NonNullable[0] | SimulateContractRequest)[] -} - -/** - * Method to get the price - * @param data - {@link GetPriceParameters} - */ -export async function getPrice( - parameters: GetPriceParameters -): Promise { - const { - destinationChainId, - destinationCurrency, - originChainId, - originCurrency, - tradeType, - amount = '0', - user, - recipient, - options, - txs - } = parameters - - const client = getClient() - - if (!client.baseApiUrl || !client.baseApiUrl.length) { - throw new ReferenceError('RelayClient missing api url configuration') - } - - let preparedTransactions: PriceBody['txs'] - if (txs && txs.length > 0) { - preparedTransactions = txs.map((tx) => { - if (isSimulateContractRequest(tx)) { - return prepareCallTransaction( - tx as Parameters['0'] - ) - } - return tx - }) - } - const originChain = client.chains.find((chain) => chain.id === originChainId) - const deadAddress = getDeadAddress(originChain?.vmType, originChain?.id) - - const query: PriceBody = { - user: user ?? deadAddress, - destinationCurrency, - destinationChainId, - originCurrency, - originChainId, - amount, - recipient: recipient ? (recipient as string) : user ?? deadAddress, - tradeType, - referrer: client.source || undefined, - txs: preparedTransactions, - ...options - } - - const request: AxiosRequestConfig = { - url: `${client.baseApiUrl}/price`, - method: 'post', - data: query - } - - const res = await axios.request(request) - if (res.status !== 200) { - throw new APIError(res?.data?.message, res.status, res.data) - } - return { ...res.data, request } as Execute -} diff --git a/packages/sdk/src/actions/getPrices.test.ts b/packages/sdk/src/actions/getPrices.test.ts deleted file mode 100644 index 7705cf4cd..000000000 --- a/packages/sdk/src/actions/getPrices.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { RelayClient, createClient } from '../client' -import { MAINNET_RELAY_API } from '../constants' -import { axios } from '../utils' - -let client: RelayClient | undefined - -let axiosRequestSpy: ReturnType - -const mockAxiosRequest = () => { - return vi.spyOn(axios, 'request').mockImplementation((config) => { - return Promise.resolve({ - data: { status: 'success' }, - status: 200 - }) - }) -} - -describe('Should test the getPrice action.', () => { - beforeEach(() => { - vi.clearAllMocks() - vi.resetAllMocks() - axiosRequestSpy = mockAxiosRequest() - }) - - it("Should throw 'RelayClient missing api url configuration'.", async () => { - client = createClient({ - baseApiUrl: '' - }) - - await expect( - client?.actions?.getPrice({ - destinationChainId: 1, - originChainId: 8453, - originCurrency: '0x0000000000000000000000000000000000000000', - destinationCurrency: '0x0000000000000000000000000000000000000000', - tradeType: 'EXACT_INPUT', - amount: '1000000000000000' // 0.001 ETH - }) - ).rejects.toThrow('RelayClient missing api url configuration') - }) - - //TODO add a test for simulate contract txs - - it('Should allow passing in additional txs', async () => { - client = createClient({ - baseApiUrl: MAINNET_RELAY_API - }) - - await client?.actions?.getPrice({ - destinationChainId: 1, - originChainId: 8453, - originCurrency: '0x0000000000000000000000000000000000000000', - destinationCurrency: '0x0000000000000000000000000000000000000000', - tradeType: 'EXACT_INPUT', - amount: '1000000000000000', // 0.001 ETH - txs: [ - { - data: '0x', - value: '0', - to: '0x' - } - ] - }) - - expect(axiosRequestSpy).toHaveBeenCalledWith( - expect.objectContaining({ - url: expect.stringContaining('price'), - data: expect.objectContaining({ - user: '0x000000000000000000000000000000000000dead', - destinationCurrency: '0x0000000000000000000000000000000000000000', - destinationChainId: 1, - originCurrency: '0x0000000000000000000000000000000000000000', - originChainId: 8453, - amount: '1000000000000000', - recipient: '0x000000000000000000000000000000000000dead', - tradeType: 'EXACT_INPUT', - txs: [{ data: '0x', value: '0', to: '0x' }] - }) - }) - ) - }) -}) diff --git a/packages/sdk/src/actions/getQuote.ts b/packages/sdk/src/actions/getQuote.ts index 3a28ac22d..18fbc92d6 100644 --- a/packages/sdk/src/actions/getQuote.ts +++ b/packages/sdk/src/actions/getQuote.ts @@ -6,6 +6,7 @@ import { isSimulateContractRequest, APIError, adaptViemWallet, + getApiKeyHeader, type SimulateContractRequest } from '../utils/index.js' import { isViemWalletClient } from '../utils/viemWallet.js' @@ -160,7 +161,10 @@ export async function getQuote( url: `${client.baseApiUrl}/quote`, method: 'post', data: query, - headers + headers: { + ...getApiKeyHeader(client), + ...headers + } } const res = await axios.request(request) diff --git a/packages/sdk/src/actions/getSolverCapacity.test.ts b/packages/sdk/src/actions/getSolverCapacity.test.ts deleted file mode 100644 index 67127a477..000000000 --- a/packages/sdk/src/actions/getSolverCapacity.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { RelayClient, createClient, getClient } from '../client' -import { MAINNET_RELAY_API } from '../constants/servers' -import { getSolverCapacity } from './getSolverCapacity' -import { axios } from '../utils' -import { AxiosInstance } from 'axios' -import { zeroAddress } from 'viem' - -let client: RelayClient | undefined - -describe('Should test the getSolverCapcity action.', () => { - beforeEach(() => { - vi.clearAllMocks() - vi.resetAllMocks() - }) - - it("Should throw 'Client not initialized'.", async () => { - client = undefined - - await expect( - getSolverCapacity({ - originChainId: '1', - destinationChainId: '8453' - }) - ).rejects.toThrow('Client not initialized') - }) - - it('Should throw an unsupported currency error.', async () => { - client = createClient({ - baseApiUrl: MAINNET_RELAY_API - }) - - await expect( - client?.actions?.getSolverCapacity({ - originChainId: '1', - destinationChainId: '8453', - // @ts-ignore - currency: 'test' - }) - ).rejects.toThrow('currency must be equal to one of the allowed values') - }) - - it('Should throw an unsupported chain error.', async () => { - client = createClient({ - baseApiUrl: MAINNET_RELAY_API - }) - - await expect( - client?.actions?.getSolverCapacity({ - originChainId: '1', - destinationChainId: '123456789' - }) - ).rejects.toThrow('Internal error') - }) - - it('Fallsback to zero address when no user is provided.', async () => { - client = createClient({ - baseApiUrl: MAINNET_RELAY_API - }) - - const axiosRequestSpy = vi - .spyOn(axios, 'get') - .mockImplementationOnce((config, params) => { - return Promise.resolve({ - data: {}, - status: 200 - }) - }) - - await client?.actions?.getSolverCapacity({ - originChainId: '1', - destinationChainId: '8453' - }) - - expect(axiosRequestSpy.mock.lastCall).toEqual([ - 'https://api.relay.link/config/v2', - { - params: { - originChainId: '1', - destinationChainId: '8453', - user: zeroAddress, - currency: undefined - } - } - ]) - }) - - it("Throw 'No solver capacity error' when no data is returned.", async () => { - client = createClient({ - baseApiUrl: MAINNET_RELAY_API - }) - - const axiosRequestSpy = vi - .spyOn(axios, 'get') - .mockImplementationOnce((config) => { - return Promise.resolve({ - data: undefined, - status: 200 - }) - }) - - await expect( - getClient()?.actions?.getSolverCapacity({ - originChainId: '1', - destinationChainId: '123456789' - }) - ).rejects.toThrow('No solver capacity data') - }) -}) diff --git a/packages/sdk/src/actions/getSolverCapacity.ts b/packages/sdk/src/actions/getSolverCapacity.ts deleted file mode 100644 index 175645c93..000000000 --- a/packages/sdk/src/actions/getSolverCapacity.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { paths } from '../types/index.js' -import { axios } from '../utils/axios.js' -import { getClient } from '../client.js' -import { zeroAddress } from 'viem' -import type { AxiosInstance } from 'axios' - -export type GetConfigQueryParams = Required< - Pick< - NonNullable, - 'originChainId' | 'destinationChainId' - > -> & - Omit< - NonNullable, - 'originChainId' | 'destinationChainId' - > & { referrer?: string } - -export type GetConfigResponse = - paths['/config/v2']['get']['responses']['200']['content']['application/json'] - -export async function getSolverCapacity( - data: GetConfigQueryParams, - axiosInstance: AxiosInstance = axios -): Promise { - const client = getClient() - - if (!client) { - throw new Error('Client not initialized') - } - - // Fallback to zero address if user is not provided - data.user = data.user || zeroAddress - data.currency = data.currency - - const response = await axiosInstance.get( - `${client.baseApiUrl}/config/v2`, - { params: data } - ) - - if (response.data) { - return response.data - } - throw 'No solver capacity data' -} diff --git a/packages/sdk/src/actions/index.ts b/packages/sdk/src/actions/index.ts index 550684c92..99209213f 100644 --- a/packages/sdk/src/actions/index.ts +++ b/packages/sdk/src/actions/index.ts @@ -1,6 +1,4 @@ export * from './execute.js' export * from './getQuote.js' -export * from './getPrice.js' -export * from './getSolverCapacity.js' export * from './getAppFees.js' export * from './claimAppFees.js' diff --git a/packages/sdk/src/client.test.ts b/packages/sdk/src/client.test.ts index db1c62f49..2ee2a59e7 100644 --- a/packages/sdk/src/client.test.ts +++ b/packages/sdk/src/client.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { describe, it, expect, beforeEach } from 'vitest' import { RelayClient, configureDynamicChains, @@ -86,8 +86,7 @@ describe('Should test the client.', () => { ]), actions: expect.objectContaining({ execute: expect.anything(), - getQuote: expect.anything(), - getSolverCapacity: expect.anything() + getQuote: expect.anything() }) }) ) @@ -158,8 +157,7 @@ describe('Should test the client.', () => { ]), actions: expect.objectContaining({ execute: expect.anything(), - getQuote: expect.anything(), - getSolverCapacity: expect.anything() + getQuote: expect.anything() }) }) ) diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index cae6f4a5f..cf04b10a3 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -20,6 +20,7 @@ import { SDK_VERSION } from './version.js' * * @property {string} [baseApiUrl] - The base URL for the Relay API. Defaults to the mainnet API if not provided. * @property {string} [source] - The source to associate your onchain activity with, should be a domain. + * @property {string} [apiKey] - The API key to use for Relay API requests. Should not be used in the client, we recommend using baseApiUrl with a proxy api. * @property {LogLevel} [logLevel] - Log level from 0-4, the higher the more verbose. Defaults to LogLevel.None. * @property {number} [pollingInterval] - Interval (in ms) for polling the API for status updates. * @property {number} [maxPollingAttemptsBeforeTimeout] - The maximum number of polling attempts before timing out. The API is polled every 5 seconds by default (default is 30 attempts). @@ -35,6 +36,7 @@ import { SDK_VERSION } from './version.js' export type RelayClientOptions = { baseApiUrl?: string source?: string + apiKey?: string logLevel?: LogLevel pollingInterval?: number maxPollingAttemptsBeforeTimeout?: number @@ -67,6 +69,7 @@ export class RelayClient { uiVersion?: string baseApiUrl: string source?: string + apiKey?: string logLevel: LogLevel pollingInterval?: number confirmationPollingInterval?: number @@ -89,6 +92,7 @@ export class RelayClient { this.version = SDK_VERSION this.uiVersion = options.uiVersion this.baseApiUrl = options.baseApiUrl ?? MAINNET_RELAY_API + this.apiKey = options.apiKey this.logLevel = options.logLevel !== undefined ? options.logLevel : LogLevel.None this.pollingInterval = options.pollingInterval @@ -124,6 +128,7 @@ export class RelayClient { configure(options: Partial) { this.baseApiUrl = options.baseApiUrl ? options.baseApiUrl : this.baseApiUrl this.source = options.source ? options.source : this.source + this.apiKey = options.apiKey !== undefined ? options.apiKey : this.apiKey this.logLevel = options.logLevel !== undefined ? options.logLevel : LogLevel.None this.pollingInterval = options.pollingInterval @@ -167,7 +172,8 @@ export async function configureDynamicChains() { try { const chains = await utils.fetchChainConfigs( _client.baseApiUrl, - _client.source + _client.source, + _client.apiKey ) _client.chains = chains return chains diff --git a/packages/sdk/src/utils/apiKey.ts b/packages/sdk/src/utils/apiKey.ts new file mode 100644 index 000000000..67605a60f --- /dev/null +++ b/packages/sdk/src/utils/apiKey.ts @@ -0,0 +1,35 @@ +import { MAINNET_RELAY_API, TESTNET_RELAY_API } from '../constants/servers.js' +import type { RelayClient } from '../client.js' + +const RELAY_API_URLS = [MAINNET_RELAY_API, TESTNET_RELAY_API] + +/** + * Checks if a URL is a known Relay API endpoint + * @param url - The URL to validate + * @returns true if the URL starts with a known Relay API base URL + */ +export function isRelayApiUrl(url: string): boolean { + return RELAY_API_URLS.some((apiUrl) => url.startsWith(apiUrl)) +} + +/** + * Gets API key header if client has API key and URL is a Relay API endpoint + * @param client - The RelayClient instance + * @param url - Target URL (defaults to client.baseApiUrl) + * @returns Object with x-api-key header or empty object + */ +export function getApiKeyHeader( + client: RelayClient | undefined, + url?: string +): Record { + if (!client?.apiKey) { + return {} + } + + const targetUrl = url ?? client.baseApiUrl + if (!isRelayApiUrl(targetUrl)) { + return {} + } + + return { 'x-api-key': client.apiKey } +} diff --git a/packages/sdk/src/utils/executeSteps/signatureStep.ts b/packages/sdk/src/utils/executeSteps/signatureStep.ts index 8038f287b..9e8e83de1 100644 --- a/packages/sdk/src/utils/executeSteps/signatureStep.ts +++ b/packages/sdk/src/utils/executeSteps/signatureStep.ts @@ -4,7 +4,7 @@ import type { AdaptedWallet, RelayChain } from '../../types/index.js' -import { axios } from '../index.js' +import { axios, getApiKeyHeader } from '../index.js' import type { AxiosRequestConfig } from 'axios' import { LogLevel } from '../logger.js' import type { RelayClient } from '../../client.js' @@ -82,7 +82,8 @@ export async function handleSignatureStepItem({ }) const postOrderUrl = new URL(`${request.baseURL}${postData.endpoint}`) const headers = { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + ...getApiKeyHeader(client, request.baseURL) } if (postData.body && !postData.body.referrer) { @@ -153,7 +154,8 @@ export async function handleSignatureStepItem({ }) const headers = { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + ...getApiKeyHeader(client, request.baseURL) } // If websocket is enabled, wait for it to fail before falling back to polling diff --git a/packages/sdk/src/utils/fetchChainConfigs.ts b/packages/sdk/src/utils/fetchChainConfigs.ts index a28214adb..07ec93c55 100644 --- a/packages/sdk/src/utils/fetchChainConfigs.ts +++ b/packages/sdk/src/utils/fetchChainConfigs.ts @@ -1,10 +1,12 @@ import { configureViemChain } from './chain.js' import type { RelayChain } from '../types/index.js' import { axios } from './axios.js' +import { isRelayApiUrl } from './apiKey.js' export const fetchChainConfigs = async ( baseApiUrl: string, - referrer?: string + referrer?: string, + apiKey?: string ): Promise => { let queryString = '' if (referrer) { @@ -12,7 +14,15 @@ export const fetchChainConfigs = async ( queryParams.set('referrer', referrer) queryString = `?${queryParams.toString()}` } - const response = await axios.get(`${baseApiUrl}/chains${queryString}`) + + const headers: Record = {} + if (apiKey && isRelayApiUrl(baseApiUrl)) { + headers['x-api-key'] = apiKey + } + + const response = await axios.get(`${baseApiUrl}/chains${queryString}`, { + headers + }) if (response.data && response.data.chains) { return response.data.chains.map((chain: any) => configureViemChain(chain)) } diff --git a/packages/sdk/src/utils/index.ts b/packages/sdk/src/utils/index.ts index 318b0780c..c88714136 100644 --- a/packages/sdk/src/utils/index.ts +++ b/packages/sdk/src/utils/index.ts @@ -16,3 +16,4 @@ export { export { safeStructuredClone } from './structuredClone.js' export { repeatUntilOk } from './repeatUntilOk.js' export { prepareHyperliquidSignatureStep } from './hyperliquid.js' +export { isRelayApiUrl, getApiKeyHeader } from './apiKey.js' diff --git a/packages/sdk/src/utils/transaction.ts b/packages/sdk/src/utils/transaction.ts index 2028a3243..99397edd1 100644 --- a/packages/sdk/src/utils/transaction.ts +++ b/packages/sdk/src/utils/transaction.ts @@ -21,6 +21,7 @@ import { SolverStatusTimeoutError, TransactionConfirmationError } from '../errors/index.js' +import { getApiKeyHeader } from './apiKey.js' import { repeatUntilOk } from '../utils/repeatUntilOk.js' import { getTenderlyDetails, @@ -305,7 +306,10 @@ export async function sendTransactionSafely( res = await axios.request({ url: `${request.baseURL}${endpoint}`, method: check?.method, - headers: headers + headers: { + ...getApiKeyHeader(client, request.baseURL), + ...headers + } }) } catch (e) { getClient()?.log( @@ -552,7 +556,10 @@ const postSameChainTransactionToSolver = async ({ .request({ url: `${request.baseURL}/transactions/single`, method: 'POST', - headers: headers, + headers: { + ...getApiKeyHeader(getClient(), request.baseURL), + ...headers + }, data: triggerData }) .then(() => { @@ -603,7 +610,10 @@ const postTransactionToSolver = async ({ .request({ url: `${request.baseURL}/transactions/index`, method: 'POST', - headers: headers, + headers: { + ...getApiKeyHeader(getClient(), request.baseURL), + ...headers + }, data: triggerData }) .then(() => {