diff --git a/packages/sdk/README.md b/packages/sdk/README.md index 09b4064..e25dee5 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -182,6 +182,37 @@ await client.executeSwapQuote({ }); ``` +## Authentication + +Some Across API features require authentication via an API key. You can provide the +key at the client level or per-request. + +### Client-level API key + +```ts +const client = createAcrossClient({ + integratorId: "0xdead", + chains: [mainnet, optimism, arbitrum], + apiKey: "your-api-key", +}); + +// All authenticated requests will use this key automatically +const quote = await client.getSwapQuote({ route, amount }); +``` + +### Per-request API key + +```ts +// Override or provide the key for a single request +const quote = await client.getSwapQuote({ + route, + amount, + apiKey: "per-request-key", +}); +``` + +The key is sent as a `Bearer` token in the `Authorization` header. + ## Deposit details TODO diff --git a/packages/sdk/src/actions/getSwapQuote.ts b/packages/sdk/src/actions/getSwapQuote.ts index c794fdd..1701f52 100644 --- a/packages/sdk/src/actions/getSwapQuote.ts +++ b/packages/sdk/src/actions/getSwapQuote.ts @@ -46,6 +46,11 @@ export type GetSwapQuoteParams = Omit< * [Optional] The Across API URL to use. Defaults to the mainnet API URL. */ apiUrl?: string; + /** + * [Optional] An API key for accessing authenticated features. Will be sent + * as a Bearer token in the Authorization header. + */ + apiKey?: string; }; /** @@ -57,7 +62,7 @@ export type GetSwapQuoteParams = Omit< export async function getSwapQuote( params: GetSwapQuoteParams, ): Promise { - const { logger, apiUrl = MAINNET_API_URL, ...otherParams } = params; + const { logger, apiUrl = MAINNET_API_URL, apiKey, ...otherParams } = params; logger?.debug("Getting swap quote with params:", otherParams); @@ -83,12 +88,15 @@ export async function getSwapQuote( value: action.value?.toString() ?? "0", })), }, + apiKey, ); } else { data = await fetchAcrossApi( url, queryParams, logger, + undefined, + apiKey, ); } diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index 12ce434..1257799 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -123,6 +123,11 @@ export type AcrossClientOptions = { * Defaults to `3_000` milliseconds. */ pollingInterval?: number; + /** + * An API key for accessing authenticated Across API features. Will be sent as + * a Bearer token in the Authorization header. + */ + apiKey?: string; /** * Tenderly related options. Can be used for additional debugging support on Tenderly. * @see https://tenderly.co/transaction-simulator @@ -164,6 +169,7 @@ export class AcrossClient { private walletClient?: ConfiguredWalletClient; private apiUrl: string; private indexerUrl: string; + private apiKey?: string; logger: LoggerT; @@ -197,6 +203,7 @@ export class AcrossClient { this.indexerUrl = args?.useTestnet === true ? TESTNET_INDEXER_API : MAINNET_INDEXER_API; this.apiUrl = args?.useTestnet === true ? TESTNET_API_URL : MAINNET_API_URL; + this.apiKey = args.apiKey; this.logger = args?.logger ?? new DefaultLogger(args?.logLevel ?? CLIENT_DEFAULTS.logLevel); @@ -655,7 +662,7 @@ export class AcrossClient { * @returns See {@link SwapApprovalApiResponse}. */ async getSwapQuote( - params: MakeOptional, + params: MakeOptional, ): Promise { try { const quote = await getSwapQuote({ @@ -664,6 +671,7 @@ export class AcrossClient { integratorId: params?.integratorId ?? this.integratorId, logger: params?.logger ?? this.logger, apiUrl: params?.apiUrl ?? this.apiUrl, + apiKey: params?.apiKey ?? this.apiKey, }); return quote; } catch (e) { diff --git a/packages/sdk/src/utils/fetch.ts b/packages/sdk/src/utils/fetch.ts index 125cad0..93acc27 100644 --- a/packages/sdk/src/utils/fetch.ts +++ b/packages/sdk/src/utils/fetch.ts @@ -64,12 +64,21 @@ function makeFetcher( params: Record>, logger?: LoggerT, body?: Record, + apiKey?: string, ): Promise => { const searchParams = buildSearchParams(params); const url = `${apiUrl}?${searchParams}`; logger?.debug(`Fetching ${name}...`, url); + const headers: Record = {}; + if (method === "POST") { + headers["Content-Type"] = "application/json"; + } + if (apiKey) { + headers["Authorization"] = `Bearer ${apiKey}`; + } + const res = await fetch(url, { method, body: @@ -78,12 +87,7 @@ function makeFetcher( typeof value === "bigint" ? value.toString() : value, ) : undefined, - headers: - method === "POST" - ? { - "Content-Type": "application/json", - } - : undefined, + headers: Object.keys(headers).length > 0 ? headers : undefined, }); // Try to parse the response as JSON. If it fails, parse it as text. diff --git a/packages/sdk/test/common/sdk.ts b/packages/sdk/test/common/sdk.ts index d66ee74..21abac6 100644 --- a/packages/sdk/test/common/sdk.ts +++ b/packages/sdk/test/common/sdk.ts @@ -8,7 +8,6 @@ import { linea, lisk, scroll, - redstone, zora, sepolia, } from "viem/chains"; @@ -24,7 +23,6 @@ export const MAINNET_SUPPORTED_CHAINS = [ linea, lisk, scroll, - redstone, zora, ] as const;