Skip to content

Commit

Permalink
Merge pull request #2004 from kadirchan/main
Browse files Browse the repository at this point in the history
feat: Allow fetch functions to pass headers
  • Loading branch information
45930 authored Feb 5, 2025
2 parents 1dc90e4 + dcd73ce commit c824001
Show file tree
Hide file tree
Showing 6 changed files with 536 additions and 110 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- `setFee` and `setFeePerSnarkCost` for `Transaction` and `PendingTransaction` https://github.com/o1-labs/o1js/pull/1968
- Doc comments for various ZkProgram methods https://github.com/o1-labs/o1js/pull/1974
- `MerkleList.popOption()` for popping the last element and also learning if there was one https://github.com/o1-labs/o1js/pull/1997
- Added custom header support for `Fetch` methods such as `fetchEvents`, `fetchActions` etc. and to `Mina` instance. Also added two new methods `setMinaDefaultHeaders` and `setArchiveDefaultHeaders` https://github.com/o1-labs/o1js/pull/2004

### Changed

Expand Down
123 changes: 101 additions & 22 deletions src/lib/mina/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export {
getCachedGenesisConstants,
addCachedAccount,
networkConfig,
setMinaDefaultHeaders,
setArchiveDefaultHeaders,
setGraphqlEndpoint,
setGraphqlEndpoints,
setMinaGraphqlFallbackEndpoints,
Expand All @@ -77,6 +79,8 @@ type NetworkConfig = {
archiveEndpoint: string;
archiveFallbackEndpoints: string[];
lightnetAccountManagerEndpoint: string;
minaDefaultHeaders: HeadersInit;
archiveDefaultHeaders: HeadersInit;
};

type ActionsQueryInputs = {
Expand All @@ -91,6 +95,8 @@ let networkConfig = {
archiveEndpoint: '',
archiveFallbackEndpoints: [] as string[],
lightnetAccountManagerEndpoint: '',
minaDefaultHeaders: {},
archiveDefaultHeaders: {},
} satisfies NetworkConfig;

function checkForValidUrl(url: string) {
Expand All @@ -102,20 +108,58 @@ function checkForValidUrl(url: string) {
}
}

/**
* Sets up the default headers to be used for all Mina node GraphQL requests, example usage:
* ```typescript
* setMinaDefaultHeaders({ Authorization: 'Bearer example-token' });
* ```
*
* It can be overridden by passing headers to the individual fetch functions, example usage:
* ```typescript
* setMinaDefaultHeaders({ Authorization: 'Bearer default-token' });
* await fetchAccount({publicKey}, minaEndpoint, { headers: { Authorization: 'Bearer override-token' } });
* ```
* @param headers Arbitrary sized headers to be used for all Mina node GraphQL requests.
*/
function setMinaDefaultHeaders(headers: HeadersInit) {
networkConfig.minaDefaultHeaders = headers;
}

/**
* Sets up the default headers to be used for all Archive node GraphQL requests, example usage:
* ```typescript
* setArchiveDefaultHeaders({ Authorization: 'Bearer example-token' });
* ```
*
* It can be overridden by passing headers to the individual fetch functions, example usage:
* ```typescript
* setArchiveDefaultHeaders({ Authorization: 'Bearer default-token' });
* await fetchEvents({publicKey}, archiveEndpoint, { headers: { Authorization: 'Bearer override-token' } });
* ```
* @param headers Arbitrary sized headers to be used for all Mina Archive node GraphQL requests.
*/
function setArchiveDefaultHeaders(headers: HeadersInit) {
networkConfig.archiveDefaultHeaders = headers;
}

function setGraphqlEndpoints([
graphqlEndpoint,
...fallbackEndpoints
]: string[]) {
setGraphqlEndpoint(graphqlEndpoint);
setMinaGraphqlFallbackEndpoints(fallbackEndpoints);
}
function setGraphqlEndpoint(graphqlEndpoint: string) {
function setGraphqlEndpoint(
graphqlEndpoint: string,
minaDefaultHeaders?: HeadersInit
) {
if (!checkForValidUrl(graphqlEndpoint)) {
throw new Error(
`Invalid GraphQL endpoint: ${graphqlEndpoint}. Please specify a valid URL.`
);
}
networkConfig.minaEndpoint = graphqlEndpoint;
if (minaDefaultHeaders) setMinaDefaultHeaders(minaDefaultHeaders);
}
function setMinaGraphqlFallbackEndpoints(graphqlEndpoints: string[]) {
if (graphqlEndpoints.some((endpoint) => !checkForValidUrl(endpoint))) {
Expand All @@ -130,13 +174,17 @@ function setMinaGraphqlFallbackEndpoints(graphqlEndpoints: string[]) {
* Sets up a GraphQL endpoint to be used for fetching information from an Archive Node.
*
*/
function setArchiveGraphqlEndpoint(graphqlEndpoint: string) {
function setArchiveGraphqlEndpoint(
graphqlEndpoint: string,
archiveDefaultHeaders?: HeadersInit
) {
if (!checkForValidUrl(graphqlEndpoint)) {
throw new Error(
`Invalid GraphQL endpoint: ${graphqlEndpoint}. Please specify a valid URL.`
);
}
networkConfig.archiveEndpoint = graphqlEndpoint;
if (archiveDefaultHeaders) setArchiveDefaultHeaders(archiveDefaultHeaders);
}
function setArchiveGraphqlFallbackEndpoints(graphqlEndpoints: string[]) {
if (graphqlEndpoints.some((endpoint) => !checkForValidUrl(endpoint))) {
Expand Down Expand Up @@ -173,13 +221,13 @@ function setLightnetAccountManagerEndpoint(endpoint: string) {
* @param accountInfo.publicKey The specified publicKey to get account information on
* @param accountInfo.tokenId The specified tokenId to get account information on
* @param graphqlEndpoint The graphql endpoint to fetch from
* @param config An object that exposes an additional timeout option
* @param config An object that exposes an additional timeout and header options
* @returns zkapp information on the specified account or an error is thrown
*/
async function fetchAccount(
accountInfo: { publicKey: string | PublicKey; tokenId?: string | Field },
graphqlEndpoint = networkConfig.minaEndpoint,
{ timeout = defaultTimeout } = {}
{ timeout = defaultTimeout, headers }: FetchConfig = {}
): Promise<
| { account: Types.Account; error: undefined }
| { account: undefined; error: FetchError }
Expand All @@ -198,6 +246,7 @@ async function fetchAccount(
graphqlEndpoint,
{
timeout,
headers: { ...networkConfig.minaDefaultHeaders, ...headers },
}
);
}
Expand Down Expand Up @@ -236,7 +285,7 @@ async function fetchAccountInternal(
};
}

type FetchConfig = { timeout?: number };
type FetchConfig = { timeout?: number; headers?: HeadersInit };
type FetchResponse<TDataResponse = any> = { data: TDataResponse; errors?: any };
type FetchError = {
statusCode: number;
Expand Down Expand Up @@ -458,11 +507,15 @@ function accountCacheKey(
/**
* Fetches the last block on the Mina network.
*/
async function fetchLastBlock(graphqlEndpoint = networkConfig.minaEndpoint) {
async function fetchLastBlock(
graphqlEndpoint = networkConfig.minaEndpoint,
headers?: HeadersInit
) {
let [resp, error] = await makeGraphqlRequest<LastBlockQueryResponse>(
lastBlockQuery,
graphqlEndpoint,
networkConfig.minaFallbackEndpoints
networkConfig.minaFallbackEndpoints,
{ headers: { ...networkConfig.minaDefaultHeaders, ...headers } }
);
if (error) throw Error(error.statusText);
let lastBlock = resp?.data?.bestChain?.[0];
Expand All @@ -478,11 +531,21 @@ async function fetchLastBlock(graphqlEndpoint = networkConfig.minaEndpoint) {
return network;
}

async function fetchCurrentSlot(graphqlEndpoint = networkConfig.minaEndpoint) {
/**
* Fetches the current slot number of the Mina network.
* @param graphqlEndpoint GraphQL endpoint to fetch from
* @param headers optional headers to pass to the fetch request
* @returns The current slot number
*/
async function fetchCurrentSlot(
graphqlEndpoint = networkConfig.minaEndpoint,
headers?: HeadersInit
) {
let [resp, error] = await makeGraphqlRequest<CurrentSlotResponse>(
currentSlotQuery,
graphqlEndpoint,
networkConfig.minaFallbackEndpoints
networkConfig.minaFallbackEndpoints,
{ headers: { ...networkConfig.minaDefaultHeaders, ...headers } }
);
if (error) throw Error(`Error making GraphQL request: ${error.statusText}`);
let bestChain = resp?.data?.bestChain;
Expand All @@ -502,7 +565,8 @@ async function fetchLatestBlockZkappStatus(
await makeGraphqlRequest<LastBlockQueryFailureCheckResponse>(
lastBlockQueryFailureCheck(blockLength),
graphqlEndpoint,
networkConfig.minaFallbackEndpoints
networkConfig.minaFallbackEndpoints,
{ headers: networkConfig.minaDefaultHeaders }
);
if (error) throw Error(`Error making GraphQL request: ${error.statusText}`);
let bestChain = resp?.data;
Expand Down Expand Up @@ -597,12 +661,14 @@ function parseEpochData({
*/
async function fetchTransactionStatus(
txId: string,
graphqlEndpoint = networkConfig.minaEndpoint
graphqlEndpoint = networkConfig.minaEndpoint,
headers?: HeadersInit
): Promise<TransactionStatus> {
let [resp, error] = await makeGraphqlRequest<TransactionStatusQueryResponse>(
transactionStatusQuery(txId),
graphqlEndpoint,
networkConfig.minaFallbackEndpoints
networkConfig.minaFallbackEndpoints,
{ headers: { ...networkConfig.minaDefaultHeaders, ...headers } }
);
if (error) throw Error(error.statusText);
let txStatus = resp?.data?.transactionStatus;
Expand All @@ -618,14 +684,15 @@ async function fetchTransactionStatus(
function sendZkapp(
json: string,
graphqlEndpoint = networkConfig.minaEndpoint,
{ timeout = defaultTimeout } = {}
{ timeout = defaultTimeout, headers }: FetchConfig = {}
) {
return makeGraphqlRequest<SendZkAppResponse>(
sendZkappQuery(json),
graphqlEndpoint,
networkConfig.minaFallbackEndpoints,
{
timeout,
headers: { ...networkConfig.minaDefaultHeaders, ...headers },
}
);
}
Expand All @@ -637,6 +704,7 @@ function sendZkapp(
* @param [accountInfo.tokenId] - The optional token ID for the account.
* @param [graphqlEndpoint=networkConfig.archiveEndpoint] - The GraphQL endpoint to query. Defaults to the Archive Node GraphQL API.
* @param [filterOptions={}] - The optional filter options object.
* @param headers - Optional headers to pass to the fetch request
* @returns A promise that resolves to an array of objects containing event data, block information and transaction information for the account.
* @throws If the GraphQL request fails or the response is invalid.
* @example
Expand All @@ -649,7 +717,8 @@ function sendZkapp(
async function fetchEvents(
accountInfo: { publicKey: string; tokenId?: string },
graphqlEndpoint = networkConfig.archiveEndpoint,
filterOptions: EventActionFilterOptions = {}
filterOptions: EventActionFilterOptions = {},
headers?: HeadersInit
) {
if (!graphqlEndpoint)
throw Error(
Expand All @@ -663,7 +732,8 @@ async function fetchEvents(
filterOptions
),
graphqlEndpoint,
networkConfig.archiveFallbackEndpoints
networkConfig.archiveFallbackEndpoints,
{ headers: { ...networkConfig.archiveDefaultHeaders, ...headers } }
);
if (error) throw Error(error.statusText);
let fetchedEvents = response?.data.events;
Expand Down Expand Up @@ -697,6 +767,7 @@ async function fetchEvents(
*
* @param accountInfo - An {@link ActionsQueryInputs} containing the public key, and optional query parameters for the actions query
* @param graphqlEndpoint - The GraphQL endpoint to fetch from. Defaults to the configured Mina endpoint.
* @param headers - Optional headers to pass to the fetch request
*
* @returns A promise that resolves to an object containing the final actions hash for the account, and a list of actions
* @throws Will throw an error if the GraphQL endpoint is invalid or if the fetch request fails.
Expand All @@ -710,7 +781,8 @@ async function fetchEvents(
*/
async function fetchActions(
accountInfo: ActionsQueryInputs,
graphqlEndpoint = networkConfig.archiveEndpoint
graphqlEndpoint = networkConfig.archiveEndpoint,
headers?: HeadersInit
): Promise<
| {
actions: string[][];
Expand All @@ -731,7 +803,8 @@ async function fetchActions(
let [response, error] = await makeGraphqlRequest<ActionQueryResponse>(
getActionsQuery(publicKey, actionStates, tokenId),
graphqlEndpoint,
networkConfig.archiveFallbackEndpoints
networkConfig.archiveFallbackEndpoints,
{ headers: { ...networkConfig.archiveDefaultHeaders, ...headers } }
);
// As of 2025-01-07, minascan is running a version of the node which supports `sequenceNumber` and `zkappAccountUpdateIds` fields
// We could consider removing this fallback since no other nodes are widely used
Expand All @@ -746,7 +819,8 @@ async function fetchActions(
/* _excludeTransactionInfo= */ true
),
graphqlEndpoint,
networkConfig.archiveFallbackEndpoints
networkConfig.archiveFallbackEndpoints,
{ headers: { ...networkConfig.archiveDefaultHeaders, ...headers } }
);
if (error)
throw Error(
Expand Down Expand Up @@ -867,12 +941,14 @@ export function createActionsList(
* Fetches genesis constants.
*/
async function fetchGenesisConstants(
graphqlEndpoint = networkConfig.minaEndpoint
graphqlEndpoint = networkConfig.minaEndpoint,
headers?: HeadersInit
): Promise<GenesisConstants> {
let [resp, error] = await makeGraphqlRequest<GenesisConstantsResponse>(
genesisConstantsQuery,
graphqlEndpoint,
networkConfig.minaFallbackEndpoints
networkConfig.minaFallbackEndpoints,
{ headers: { ...networkConfig.minaDefaultHeaders, ...headers } }
);
if (error) throw Error(error.statusText);
const genesisConstants = resp?.data?.genesisConstants;
Expand Down Expand Up @@ -1029,7 +1105,7 @@ async function makeGraphqlRequest<TDataResponse = any>(
query: string,
graphqlEndpoint = networkConfig.minaEndpoint,
fallbackEndpoints: string[],
{ timeout = defaultTimeout } = {} as FetchConfig
{ timeout = defaultTimeout, headers } = {} as FetchConfig
) {
if (graphqlEndpoint === 'none')
throw Error(
Expand All @@ -1049,7 +1125,10 @@ async function makeGraphqlRequest<TDataResponse = any>(
try {
let response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers: {
'Content-Type': 'application/json',
...headers,
},
body,
signal: controller.signal,
});
Expand Down
Loading

0 comments on commit c824001

Please sign in to comment.