Skip to content
This repository was archived by the owner on Feb 5, 2025. It is now read-only.

Commit 05d30fe

Browse files
authored
Merge pull request #1274 from hypercerts-org/feat/timeouts
fix(http): unopiniated timeouts on calls. 1.4.2-alpha.1
2 parents 94f810d + f30142d commit 05d30fe

File tree

7 files changed

+88
-33
lines changed

7 files changed

+88
-33
lines changed

sdk/RELEASE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
- Improve ESM support by exporting `index.mjs` instead of `index.js`. @baumstern
66
- Added dweb IPFS gateway links and use `Promise.any` call to try and fetch data from multiple gateways.
7+
- Expose timeout on HTTP requests from storage layer up to client wrapper methods as optional config.
8+
- Default timeout on calls of 0 ms (no timeout) to avoid issues with large files or multiple IPFS calls.
79

810
## New contributors
911

sdk/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@hypercerts-org/sdk",
3-
"version": "1.4.2-alpha.0",
3+
"version": "1.4.2-alpha.1",
44
"description": "SDK for hypercerts protocol",
55
"repository": "[email protected]:hypercerts-org/hypercerts.git",
66
"author": "Hypercerts team",

sdk/src/client.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ export class HypercertClient implements HypercertClientInterface {
145145
const { account } = this.getWallet();
146146

147147
// validate and store metadata
148-
const metadataCID = await this.storage.storeMetadata(metaData);
148+
const metadataCID = await this.storage.storeMetadata(metaData, { timeout: overrides?.timeout });
149149

150150
const request = await this.simulateRequest(
151151
account,
@@ -259,11 +259,13 @@ export class HypercertClient implements HypercertClientInterface {
259259
const tree = parseAllowListEntriesToMerkleTree(allowList);
260260

261261
// store allowlist on IPFS
262-
const allowListCID = await this.storage.storeAllowList(allowList, totalUnits);
262+
const allowListCID = await this.storage.storeAllowList(allowList, totalUnits, { timeout: overrides?.timeout });
263263

264264
// store metadata on IPFS
265-
const metadataCID = await this.storage.storeMetadata({ ...metaData, allowList: allowListCID });
266-
265+
const metadataCID = await this.storage.storeMetadata(
266+
{ ...metaData, allowList: allowListCID },
267+
{ timeout: overrides?.timeout },
268+
);
267269
const request = await this.simulateRequest(
268270
account,
269271
"createAllowlist",

sdk/src/storage.ts

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
MalformedDataError,
66
StorageError,
77
AllowlistEntry,
8+
StorageConfigOverrides,
89
} from "./types";
910
import { logger, getFromIPFS, parseAllowListEntriesToMerkleTree } from "./utils";
1011
import { uploadAllowlist, uploadMetadata } from "./utils/apis";
@@ -26,11 +27,17 @@ export class HypercertsStorage implements HypercertStorageInterface {
2627
* If the metadata is valid, it creates a new Blob from the metadata and stores it using the hypercerts API. If the storage operation fails, it throws a `StorageError`.
2728
*
2829
* @param {AllowlistEntry[]} allowList - The allowList to store.
30+
* @param {bigin} totalUnits - The total number of units in the allowlist.
31+
* @param {StorageConfigOverrides} [config] - An optional configuration object.
2932
* @returns {Promise<string>} A promise that resolves to the CID of the stored metadata.
3033
* @throws {StorageError} Will throw a `StorageError` if the storage operation fails.
3134
* @throws {MalformedDataError} Will throw a `MalformedDataError` if the provided metadata is invalid.
3235
*/
33-
public async storeAllowList(allowList: AllowlistEntry[], totalUnits: bigint): Promise<string> {
36+
public async storeAllowList(
37+
allowList: AllowlistEntry[],
38+
totalUnits: bigint,
39+
config: StorageConfigOverrides = { timeout: 0 },
40+
): Promise<string> {
3441
const { valid, data, errors: allowlistErrors } = validateAllowlist(allowList, totalUnits);
3542
if (!valid) {
3643
throw new MalformedDataError(`Invalid allowList.`, { errors: allowlistErrors });
@@ -42,10 +49,13 @@ export class HypercertsStorage implements HypercertStorageInterface {
4249

4350
logger.debug("Allowlist tree: ", "storage", [tree]);
4451

45-
const { data: resData, errors: uploadAllowlistErrors } = await uploadAllowlist({
46-
allowList: JSON.stringify(tree.dump()),
47-
totalUnits: totalUnits.toString(),
48-
});
52+
const { data: resData, errors: uploadAllowlistErrors } = await uploadAllowlist(
53+
{
54+
allowList: JSON.stringify(tree.dump()),
55+
totalUnits: totalUnits.toString(),
56+
},
57+
config,
58+
);
4959

5060
const allowlistCID = resData?.cid;
5161

@@ -65,19 +75,23 @@ export class HypercertsStorage implements HypercertStorageInterface {
6575
* If the metadata is valid, it creates a new Blob from the metadata and stores it using the hypercerts API. If the storage operation fails, it throws a `StorageError`.
6676
*
6777
* @param {HypercertMetadata} data - The Hypercert metadata to store. This should be an object that conforms to the HypercertMetadata type.
78+
* @param {StorageConfigOverrides} [config] - An optional configuration object.
6879
* @returns {Promise<string>} A promise that resolves to the CID of the stored metadata.
6980
* @throws {StorageError} Will throw a `StorageError` if the storage operation fails.
7081
* @throws {MalformedDataError} Will throw a `MalformedDataError` if the provided metadata is invalid.
7182
*/
72-
public async storeMetadata(metadata: HypercertMetadata): Promise<string> {
83+
public async storeMetadata(
84+
metadata: HypercertMetadata,
85+
config: StorageConfigOverrides = { timeout: 0 },
86+
): Promise<string> {
7387
const { data, valid, errors: validationErrors } = validateMetaData(metadata);
7488
if (!valid) {
7589
throw new MalformedDataError(`Invalid metadata.`, { errors: validationErrors });
7690
}
7791

7892
logger.debug("Storing HypercertMetaData: ", "storage", [data]);
7993

80-
const { errors, data: resData } = await uploadMetadata(metadata);
94+
const { errors, data: resData } = await uploadMetadata(metadata, config);
8195

8296
const cid = resData?.cid;
8397

@@ -97,11 +111,15 @@ export class HypercertsStorage implements HypercertStorageInterface {
97111
* If the data is valid, it returns the data as a `HypercertMetadata` object.
98112
*
99113
* @param {string} cidOrIpfsUri - The CID or IPFS URI of the metadata to retrieve.
114+
* @param {StorageConfigOverrides} [config] - An optional configuration object.
100115
* @returns {Promise<HypercertMetadata>} A promise that resolves to the retrieved metadata.
101116
* @throws {MalformedDataError} Will throw a `MalformedDataError` if the retrieved data is invalid.
102117
*/
103-
public async getMetadata(cidOrIpfsUri: string): Promise<HypercertMetadata> {
104-
const res = await getFromIPFS(cidOrIpfsUri);
118+
public async getMetadata(
119+
cidOrIpfsUri: string,
120+
config: StorageConfigOverrides = { timeout: 0 },
121+
): Promise<HypercertMetadata> {
122+
const res = await getFromIPFS(cidOrIpfsUri, config);
105123

106124
const validation = validateMetaData(res);
107125
if (!validation.valid) {
@@ -117,14 +135,14 @@ export class HypercertsStorage implements HypercertStorageInterface {
117135
* This method first retrieves the data from IPFS using the `getFromIPFS` function. It then parses the retrieved data as JSON and returns it.
118136
*
119137
* @param {string} cidOrIpfsUri - The CID or IPFS URI of the data to retrieve.
138+
* @param {StorageConfigOverrides} [config] - An optional configuration object.
120139
* @returns {Promise<any>} A promise that resolves to the retrieved data.
121140
* @throws {FetchError} Will throw a `FetchError` if the retrieval operation fails.
122141
* @throws {MalformedDataError} Will throw a `MalformedDataError` if the retrieved data is not a single file.
123142
*
124143
* @remarkts Note: The original implementation using the Web3 Storage client is currently commented out due to issues with upstream repos. This will be replaced once those issues are resolved.
125144
*/
126-
public async getData(cidOrIpfsUri: string): Promise<unknown> {
127-
// TODO: replace current temporary fix or just using NFT.Storage IPFS gateway
128-
return await getFromIPFS(cidOrIpfsUri);
145+
public async getData(cidOrIpfsUri: string, config: StorageConfigOverrides = { timeout: 0 }): Promise<unknown> {
146+
return await getFromIPFS(cidOrIpfsUri, config);
129147
}
130148
}

sdk/src/types/client.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,37 @@ import { HypercertMetadata } from "./metadata";
77
import { ByteArray, Chain, Hex, PublicClient, WalletClient, GetContractReturnType } from "viem";
88
import { HypercertMinterAbi } from "@hypercerts-org/contracts";
99

10+
/**
11+
* Enum to verify the supported chainIds
12+
*
13+
* @note 10 = Optimism, 42220 = Celo, 11155111 = Sepolia
14+
*/
1015
export type SupportedChainIds = 10 | 42220 | 11155111;
11-
export type SupportedOverrides = {
16+
17+
export type SupportedOverrides = ContractOverrides & StorageConfigOverrides;
18+
19+
/**
20+
* Configuration options for the contract interactions.
21+
*
22+
* @param value The value to send with the transaction (in wei).
23+
* @param gasPrice The gas price to use for the transaction (in wei).
24+
* @param gasLimit The gas limit to use for the transaction (in wei).
25+
*/
26+
export type ContractOverrides = {
1227
value?: bigint;
1328
gasPrice?: bigint;
1429
gasLimit?: bigint;
1530
};
1631

32+
/**
33+
* Configuration options for the Hypercert storage layer.
34+
* @param timeout The timeout (im ms) for the HTTP request; for example for uploading metadata or fetching allowlists.
35+
*/
36+
export type StorageConfigOverrides = {
37+
// Axios timout in ms
38+
timeout?: number;
39+
};
40+
1741
export type Contracts =
1842
| "HypercertMinterUUPS"
1943
| "TransferManager"
@@ -84,30 +108,34 @@ export interface HypercertStorageInterface {
84108
/**
85109
* Stores the allowlost for a hypercert.
86110
* @param allowList The metadata to store.
111+
* @param {StorageConfigOverrides} [config] - An optional configuration object.
87112
* @returns A Promise that resolves to the CID of the stored metadata.
88113
*/
89-
storeAllowList: (allowList: AllowlistEntry[], totalUnits: bigint) => Promise<string>;
114+
storeAllowList: (allowList: AllowlistEntry[], totalUnits: bigint, config?: StorageConfigOverrides) => Promise<string>;
90115

91116
/**
92117
* Stores the metadata for a hypercert.
93118
* @param metadata The metadata to store.
119+
* @param {StorageConfigOverrides} [config] - An optional configuration object.
94120
* @returns A Promise that resolves to the CID of the stored metadata.
95121
*/
96-
storeMetadata: (metadata: HypercertMetadata) => Promise<string>;
122+
storeMetadata: (metadata: HypercertMetadata, config?: StorageConfigOverrides) => Promise<string>;
97123

98124
/**
99125
* Retrieves the metadata for a hypercerts.
100126
* @param cidOrIpfsUri The CID or IPFS URI of the metadata to retrieve.
127+
* @param {StorageConfigOverrides} [config] - An optional configuration object.
101128
* @returns A Promise that resolves to the retrieved metadata.
102129
*/
103-
getMetadata: (cidOrIpfsUri: string) => Promise<HypercertMetadata>;
130+
getMetadata: (cidOrIpfsUri: string, config?: StorageConfigOverrides) => Promise<HypercertMetadata>;
104131

105132
/**
106133
* Retrieves arbitrary data from IPFS.
107134
* @param cidOrIpfsUri The CID or IPFS URI of the data to retrieve.
135+
* @param {StorageConfigOverrides} [config] - An optional configuration object.
108136
* @returns A Promise that resolves to the retrieved data.
109137
*/
110-
getData: (cidOrIpfsUri: string) => Promise<unknown>;
138+
getData: (cidOrIpfsUri: string, config?: StorageConfigOverrides) => Promise<unknown>;
111139
}
112140

113141
/**

sdk/src/utils/apis.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import axios from "axios";
2-
import { HypercertMetadata } from "src/types";
2+
import { HypercertMetadata, StorageConfigOverrides } from "src/types";
33

44
/**
55
* Type for the request body when posting to the allowlist endpoint.
@@ -22,18 +22,20 @@ type ResponseData<T> = {
2222
/**
2323
* Axios instance configured with the base URL for the hypercert API.
2424
*/
25-
const api = axios.create({ timeout: 10000, headers: { "Content-Type": "application/json" } });
25+
const api = axios.create({ headers: { "Content-Type": "application/json" } });
2626

2727
/**
2828
* Uploads metadata to the API.
2929
*
30-
* @param metadata - The metadata to upload. Should be an object that conforms to the HypercertMetadata type.
30+
* @param {HypercertMetadata} metadata - The metadata to upload. Should be an object that conforms to the HypercertMetadata type.
31+
* @param {StorageConfigOverrides} [config] - An optional configuration object.
3132
* @returns The response data from the API.
3233
*/
33-
const uploadMetadata = async (metadata: HypercertMetadata) => {
34+
const uploadMetadata = async (metadata: HypercertMetadata, config: StorageConfigOverrides = { timeout: 0 }) => {
3435
const response = await api.post<ResponseData<{ cid: string }>>(
3536
"https://hypercerts-api-production.up.railway.app/api/v1/web3up/metadata",
3637
metadata,
38+
config,
3739
);
3840

3941
return response.data;
@@ -42,14 +44,16 @@ const uploadMetadata = async (metadata: HypercertMetadata) => {
4244
/**
4345
* Uploads an allowlist to the API.
4446
*
45-
* @param req - The request body containing the allowlist and total units. The allowList should be a stringified Merkle tree dump.
47+
* @param {HypercertMetadata} req - The request body containing the allowlist and total units. The allowList should be a stringified Merkle tree dump.
48+
* @param {StorageConfigOverrides} [config] - An optional configuration object.
4649
* @returns The response data from the API.
4750
*
4851
*/
49-
const uploadAllowlist = async (req: AllowListPostRequest) => {
52+
const uploadAllowlist = async (req: AllowListPostRequest, config: StorageConfigOverrides = { timeout: 0 }) => {
5053
const response = await api.post<ResponseData<{ cid: string }>>(
5154
"https://hypercerts-api-production.up.railway.app/api/v1/web3up/allowlist",
5255
req,
56+
config,
5357
);
5458

5559
return response.data;

sdk/src/utils/fetchers.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { StorageConfigOverrides } from "src/types";
12
import { StorageError } from "../types/errors";
23
import { logger } from "./logger";
34
import axios from "axios";
@@ -9,16 +10,16 @@ import axios from "axios";
910
* If the data cannot be fetched from any gateway, it throws a `StorageError`.
1011
*
1112
* @param {string} cidOrIpfsUri - The CID or IPFS URI of the data to fetch.
12-
* @param {number} [timeout=10000] - The timeout for the fetch request in milliseconds. Defaults to 10000ms.
13+
* @param {StorageConfigOverrides} [config] - An optional configuration object.
1314
* @returns {Promise<unknown>} The data fetched from IPFS.
1415
* @throws {StorageError} Will throw a `StoragjeError` if the data cannot be fetched from either gateway.
1516
* @async
1617
*/
17-
const getFromIPFS = async (cidOrIpfsUri: string, timeout: number = 10000): Promise<unknown> => {
18+
const getFromIPFS = async (cidOrIpfsUri: string, config: StorageConfigOverrides = { timeout: 0 }): Promise<unknown> => {
1819
const requests = [
19-
axios.get(getDwebLinkGatewayUri(cidOrIpfsUri), { timeout }),
20-
axios.get(getNftStorageGatewayUri(cidOrIpfsUri), { timeout }),
21-
axios.get(getWeb3UpGatewayUri(cidOrIpfsUri), { timeout }),
20+
axios.get(getDwebLinkGatewayUri(cidOrIpfsUri), { timeout: config.timeout }),
21+
axios.get(getNftStorageGatewayUri(cidOrIpfsUri), { timeout: config.timeout }),
22+
axios.get(getWeb3UpGatewayUri(cidOrIpfsUri), { timeout: config.timeout }),
2223
];
2324

2425
logger.debug(`Getting metadata for ${cidOrIpfsUri}`);

0 commit comments

Comments
 (0)