@opendatalabs/vana-sdk 0.1.0-alpha.2b6935d → 0.1.0-alpha.2e77fcc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +33 -1
- package/dist/browser.js.map +1 -1
- package/dist/chains/index.cjs.map +1 -1
- package/dist/chains/index.d.ts +30 -1
- package/dist/chains/index.js.map +1 -1
- package/dist/config/chains.cjs.map +1 -1
- package/dist/config/chains.d.ts +99 -0
- package/dist/config/chains.js.map +1 -1
- package/dist/contracts/contractController.cjs.map +1 -1
- package/dist/contracts/contractController.d.ts +66 -10
- package/dist/contracts/contractController.js.map +1 -1
- package/dist/controllers/base.cjs +33 -0
- package/dist/controllers/base.cjs.map +1 -1
- package/dist/controllers/base.d.ts +10 -0
- package/dist/controllers/base.js +33 -0
- package/dist/controllers/base.js.map +1 -1
- package/dist/controllers/data.cjs +162 -133
- package/dist/controllers/data.cjs.map +1 -1
- package/dist/controllers/data.d.ts +222 -184
- package/dist/controllers/data.js +162 -133
- package/dist/controllers/data.js.map +1 -1
- package/dist/controllers/permissions.cjs +208 -60
- package/dist/controllers/permissions.cjs.map +1 -1
- package/dist/controllers/permissions.d.ts +75 -45
- package/dist/controllers/permissions.js +208 -60
- package/dist/controllers/permissions.js.map +1 -1
- package/dist/controllers/protocol.cjs.map +1 -1
- package/dist/controllers/protocol.d.ts +27 -28
- package/dist/controllers/protocol.js.map +1 -1
- package/dist/controllers/schemas.cjs +23 -21
- package/dist/controllers/schemas.cjs.map +1 -1
- package/dist/controllers/schemas.d.ts +47 -40
- package/dist/controllers/schemas.js +23 -21
- package/dist/controllers/schemas.js.map +1 -1
- package/dist/controllers/server.cjs +17 -15
- package/dist/controllers/server.cjs.map +1 -1
- package/dist/controllers/server.d.ts +46 -38
- package/dist/controllers/server.js +17 -15
- package/dist/controllers/server.js.map +1 -1
- package/dist/core/apiClient.cjs +53 -3
- package/dist/core/apiClient.cjs.map +1 -1
- package/dist/core/apiClient.d.ts +132 -7
- package/dist/core/apiClient.js +53 -3
- package/dist/core/apiClient.js.map +1 -1
- package/dist/core/generics.cjs +30 -3
- package/dist/core/generics.cjs.map +1 -1
- package/dist/core/generics.d.ts +95 -6
- package/dist/core/generics.js +30 -3
- package/dist/core/generics.js.map +1 -1
- package/dist/core.cjs +4 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.ts +2 -1
- package/dist/core.js +4 -1
- package/dist/core.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.node.cjs +2 -3
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.d.ts +33 -13
- package/dist/index.node.js +2 -2
- package/dist/index.node.js.map +1 -1
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.ts +39 -1
- package/dist/node.js.map +1 -1
- package/dist/platform/browser.cjs +160 -2
- package/dist/platform/browser.cjs.map +1 -1
- package/dist/platform/browser.d.ts +232 -12
- package/dist/platform/browser.js +160 -2
- package/dist/platform/browser.js.map +1 -1
- package/dist/platform/interface.cjs.map +1 -1
- package/dist/platform/interface.d.ts +283 -90
- package/dist/platform/node.cjs +163 -2
- package/dist/platform/node.cjs.map +1 -1
- package/dist/platform/node.d.ts +69 -6
- package/dist/platform/node.js +163 -2
- package/dist/platform/node.js.map +1 -1
- package/dist/server/relayerHandler.cjs +136 -98
- package/dist/server/relayerHandler.cjs.map +1 -1
- package/dist/server/relayerHandler.d.ts +3 -2
- package/dist/server/relayerHandler.js +135 -96
- package/dist/server/relayerHandler.js.map +1 -1
- package/dist/storage/manager.cjs +108 -25
- package/dist/storage/manager.cjs.map +1 -1
- package/dist/storage/manager.d.ts +119 -25
- package/dist/storage/manager.js +108 -25
- package/dist/storage/manager.js.map +1 -1
- package/dist/storage/providers/callback-storage.cjs +86 -15
- package/dist/storage/providers/callback-storage.cjs.map +1 -1
- package/dist/storage/providers/callback-storage.d.ts +109 -20
- package/dist/storage/providers/callback-storage.js +86 -15
- package/dist/storage/providers/callback-storage.js.map +1 -1
- package/dist/storage/providers/pinata.cjs.map +1 -1
- package/dist/storage/providers/pinata.d.ts +12 -14
- package/dist/storage/providers/pinata.js.map +1 -1
- package/dist/types/blockchain.cjs.map +1 -1
- package/dist/types/blockchain.d.ts +39 -11
- package/dist/types/chains.cjs.map +1 -1
- package/dist/types/chains.d.ts +74 -7
- package/dist/types/chains.js.map +1 -1
- package/dist/types/config.cjs.map +1 -1
- package/dist/types/config.d.ts +38 -4
- package/dist/types/config.js.map +1 -1
- package/dist/types/contracts.cjs.map +1 -1
- package/dist/types/contracts.d.ts +71 -7
- package/dist/types/controller-context.cjs.map +1 -1
- package/dist/types/controller-context.d.ts +3 -1
- package/dist/types/data.cjs.map +1 -1
- package/dist/types/data.d.ts +4 -6
- package/dist/types/generics.cjs.map +1 -1
- package/dist/types/generics.d.ts +81 -10
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.ts +28 -2
- package/dist/types/index.js.map +1 -1
- package/dist/types/operations.cjs.map +1 -1
- package/dist/types/operations.d.ts +178 -15
- package/dist/types/operations.js.map +1 -1
- package/dist/types/permissions.cjs.map +1 -1
- package/dist/types/permissions.d.ts +15 -20
- package/dist/types/personal.cjs.map +1 -1
- package/dist/types/personal.d.ts +131 -14
- package/dist/types/relayer.cjs.map +1 -1
- package/dist/types/relayer.d.ts +114 -18
- package/dist/types/storage.cjs.map +1 -1
- package/dist/types/storage.d.ts +9 -21
- package/dist/types/storage.js.map +1 -1
- package/dist/types/utils.cjs.map +1 -1
- package/dist/types/utils.d.ts +5 -1
- package/dist/utils/grantFiles.cjs.map +1 -1
- package/dist/utils/grantFiles.d.ts +10 -20
- package/dist/utils/grantFiles.js.map +1 -1
- package/dist/utils/grantValidation.cjs.map +1 -1
- package/dist/utils/grantValidation.d.ts +95 -16
- package/dist/utils/grantValidation.js.map +1 -1
- package/dist/utils/grants.cjs.map +1 -1
- package/dist/utils/grants.d.ts +93 -12
- package/dist/utils/grants.js.map +1 -1
- package/dist/utils/ipfs.cjs +2 -4
- package/dist/utils/ipfs.cjs.map +1 -1
- package/dist/utils/ipfs.d.ts +1 -1
- package/dist/utils/ipfs.js +2 -4
- package/dist/utils/ipfs.js.map +1 -1
- package/dist/utils/lazy-import.cjs.map +1 -1
- package/dist/utils/lazy-import.d.ts +32 -7
- package/dist/utils/lazy-import.js.map +1 -1
- package/dist/utils/signatureCache.cjs +8 -2
- package/dist/utils/signatureCache.cjs.map +1 -1
- package/dist/utils/signatureCache.d.ts +49 -8
- package/dist/utils/signatureCache.js +8 -2
- package/dist/utils/signatureCache.js.map +1 -1
- package/dist/utils/transactionHelpers.cjs.map +1 -1
- package/dist/utils/transactionHelpers.d.ts +12 -12
- package/dist/utils/transactionHelpers.js.map +1 -1
- package/dist/utils/typedDataConverter.cjs.map +1 -1
- package/dist/utils/typedDataConverter.d.ts +39 -3
- package/dist/utils/typedDataConverter.js.map +1 -1
- package/dist/utils/urlResolver.cjs +7 -0
- package/dist/utils/urlResolver.cjs.map +1 -1
- package/dist/utils/urlResolver.d.ts +22 -4
- package/dist/utils/urlResolver.js +7 -0
- package/dist/utils/urlResolver.js.map +1 -1
- package/dist/utils/wallet.cjs.map +1 -1
- package/dist/utils/wallet.d.ts +78 -16
- package/dist/utils/wallet.js.map +1 -1
- package/package.json +3 -1
- package/dist/server/handler.cjs +0 -103
- package/dist/server/handler.cjs.map +0 -1
- package/dist/server/handler.d.ts +0 -95
- package/dist/server/handler.js +0 -79
- package/dist/server/handler.js.map +0 -1
- /package/dist/tests/{server-handler.test.d.ts → permissions-transaction-options.test.d.ts} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/grantValidation.ts"],"sourcesContent":["import type { Address } from \"viem\";\nimport { getAddress } from \"viem\";\nimport Ajv, { type ValidateFunction } from \"ajv\";\nimport addFormats from \"ajv-formats\";\nimport type { GrantFile } from \"../types/permissions\";\nimport grantFileSchema from \"../schemas/grantFile.schema.json\";\n\n/**\n * Base error class for grant validation failures\n *\n * @category Permissions\n */\nexport class GrantValidationError extends Error {\n constructor(\n message: string,\n public details?: Record<string, unknown>,\n ) {\n super(message);\n this.name = \"GrantValidationError\";\n }\n}\n\n/**\n * Error thrown when a grant has expired\n *\n * @category Permissions\n */\nexport class GrantExpiredError extends GrantValidationError {\n constructor(\n message: string,\n public expires: number,\n public currentTime: number,\n ) {\n super(message, { expires, currentTime });\n this.name = \"GrantExpiredError\";\n }\n}\n\n/**\n * Error thrown when grantee doesn't match requesting address\n *\n * @category Permissions\n */\nexport class GranteeMismatchError extends GrantValidationError {\n constructor(\n message: string,\n public grantee: Address,\n public requestingAddress: Address,\n ) {\n super(message, { grantee, requestingAddress });\n this.name = \"GranteeMismatchError\";\n }\n}\n\n/**\n * Error thrown when operation is not allowed by grant\n *\n * @category Permissions\n */\nexport class OperationNotAllowedError extends GrantValidationError {\n constructor(\n message: string,\n public grantedOperation: string,\n public requestedOperation: string,\n ) {\n super(message, { grantedOperation, requestedOperation });\n this.name = \"OperationNotAllowedError\";\n }\n}\n\n/**\n * Error thrown when grant file structure is invalid\n *\n * @category Permissions\n */\nexport class GrantSchemaError extends GrantValidationError {\n constructor(\n message: string,\n public schemaErrors: unknown[],\n public invalidData: unknown,\n ) {\n super(message, { errors: schemaErrors, data: invalidData });\n this.name = \"GrantSchemaError\";\n }\n}\n\n/**\n * Ajv instance for grant file validation with draft 2020-12 support\n */\nconst ajv = new Ajv({\n strict: true,\n removeAdditional: false,\n useDefaults: false,\n coerceTypes: false,\n});\n\n// Add format validators (email, date, etc.)\naddFormats(ajv);\n\n/**\n * Compiled grant file schema validator\n */\nconst validateGrantFileSchema: ValidateFunction = ajv.compile(grantFileSchema);\n\n/**\n * Options for grant validation\n *\n * @category Permissions\n */\nexport interface GrantValidationOptions {\n /** Enable JSON schema validation (default: true) */\n schema?: boolean;\n /** Grantee address to validate access for */\n grantee?: Address;\n /** Operation to validate permission for */\n operation?: string;\n /** Override current time for expiry checking (Unix timestamp) */\n currentTime?: number;\n /** Return detailed results instead of throwing (default: false) */\n throwOnError?: boolean;\n}\n\n/**\n * Detailed validation result\n *\n * @category Permissions\n */\nexport interface GrantValidationResult {\n /** Whether validation passed */\n valid: boolean;\n /** Validation errors encountered */\n errors: Array<{\n type: \"schema\" | \"business\";\n field?: string;\n message: string;\n error?: Error;\n }>;\n /** The validated grant file (if validation passed) */\n grant?: GrantFile;\n}\n\n/**\n * Validates a grant file with comprehensive schema and business rule checking.\n *\n * This function provides flexible validation with TypeScript overloads:\n * - When `throwOnError` is false (or `{ throwOnError: false }`), returns a detailed validation result\n * - When `throwOnError` is true (default), throws specific errors or returns the validated grant\n *\n * @param data - The grant file data to validate (unknown type for safety)\n * @param options - Validation options including grantee, operation, files, etc.\n * @returns Either a GrantFile (when throwing) or GrantValidationResult (when not throwing)\n * @throws {GrantSchemaError} When the grant file structure is invalid\n * @throws {GrantExpiredError} When the grant has expired\n * @throws {GranteeMismatchError} When the grantee doesn't match the requesting address\n * @throws {OperationNotAllowedError} When the requested operation is not allowed\n * @example\n * ```typescript\n * // Throwing mode (default) - returns GrantFile or throws\n * const grant = validateGrant(data, {\n * grantee: '0x123...',\n * operation: 'llm_inference',\n * });\n *\n * // Non-throwing mode - returns validation result\n * const result = validateGrant(data, {\n * grantee: '0x123...',\n * throwOnError: false\n * });\n * if (result.valid) {\n * console.log('Grant is valid:', result.grant);\n * } else {\n * console.log('Validation errors:', result.errors);\n * }\n * ```\n */\n\nexport function validateGrant(\n data: unknown,\n options: GrantValidationOptions & { throwOnError: false },\n): GrantValidationResult;\n\nexport function validateGrant(\n data: unknown,\n options?:\n | Omit<GrantValidationOptions, \"throwOnError\">\n | (GrantValidationOptions & { throwOnError?: true }),\n): GrantFile;\n\n/**\n * Implementation function for grant validation with flexible return types\n *\n * @param data - The grant file data to validate\n * @param options - Validation configuration options\n * @returns Either a GrantFile or GrantValidationResult depending on throwOnError setting\n */\nexport function validateGrant(\n data: unknown,\n options: GrantValidationOptions = {},\n): GrantFile | GrantValidationResult {\n const {\n schema = true,\n grantee,\n operation,\n currentTime,\n throwOnError = true,\n } = options;\n\n const errors: GrantValidationResult[\"errors\"] = [];\n let grant: GrantFile | undefined;\n\n // 1. Schema Validation\n if (schema) {\n try {\n if (validateGrantFileSchema(data)) {\n grant = data as GrantFile;\n } else {\n throw new GrantValidationError(\"Invalid grant file schema\");\n }\n } catch (error) {\n if (error instanceof GrantValidationError) {\n const schemaError = new GrantSchemaError(\n error.message,\n Array.isArray(error.details?.errors) ? error.details.errors : [],\n data,\n );\n errors.push({\n type: \"schema\",\n message: error.message,\n error: schemaError,\n });\n } else {\n errors.push({\n type: \"schema\",\n message: `Schema validation failed: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\n });\n }\n }\n } else {\n // Minimal type assertion if schema validation is skipped\n grant = data as GrantFile;\n }\n\n // 2. Business Logic Validation (only if we have a valid grant)\n if (grant) {\n // Check grantee access\n if (grantee) {\n try {\n validateGranteeAccess(grant, grantee);\n } catch (error) {\n const field = extractFieldFromBusinessError(error);\n errors.push({\n type: \"business\",\n field,\n message:\n error instanceof Error\n ? error.message\n : \"Unknown business rule error\",\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\n });\n }\n }\n\n // Check expiration\n try {\n validateGrantExpiry(grant, currentTime);\n } catch (error) {\n const field = extractFieldFromBusinessError(error);\n errors.push({\n type: \"business\",\n field,\n message:\n error instanceof Error\n ? error.message\n : \"Unknown business rule error\",\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\n });\n }\n\n // Check operation access\n if (operation) {\n try {\n validateOperationAccess(grant, operation);\n } catch (error) {\n const field = extractFieldFromBusinessError(error);\n errors.push({\n type: \"business\",\n field,\n message:\n error instanceof Error\n ? error.message\n : \"Unknown business rule error\",\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\n });\n }\n }\n }\n\n // 3. Return Results\n if (errors.length > 0) {\n if (throwOnError) {\n // Throw the most specific error we have\n const firstError = errors[0];\n if (firstError.error) {\n throw firstError.error;\n } else {\n const combinedMessage = errors.map((e) => e.message).join(\"; \");\n throw new GrantValidationError(\n `Grant validation failed: ${combinedMessage}`,\n { errors, data },\n );\n }\n }\n\n return { valid: false, errors, grant };\n }\n\n if (throwOnError) {\n return grant as GrantFile;\n } else {\n return { valid: true, errors: [], grant: grant as GrantFile };\n }\n}\n\n/**\n * Helper function to extract field name from business validation errors\n *\n * @param error - The validation error to extract field information from\n * @returns The field name associated with the error, or undefined if not applicable\n */\nfunction extractFieldFromBusinessError(error: unknown): string | undefined {\n if (error instanceof GrantExpiredError) return \"expires\";\n if (error instanceof GranteeMismatchError) return \"grantee\";\n if (error instanceof OperationNotAllowedError) return \"operation\";\n return undefined;\n}\n\n/**\n * Validates that a grant file allows access for a specific grantee\n *\n * @param grantFile - The grant file to validate access for\n * @param requestingAddress - The address requesting access to check against the grantee\n */\nexport function validateGranteeAccess(\n grantFile: GrantFile,\n requestingAddress: Address,\n): void {\n const normalizedGrantee = getAddress(grantFile.grantee);\n const normalizedRequesting = getAddress(requestingAddress);\n\n if (normalizedGrantee !== normalizedRequesting) {\n throw new GranteeMismatchError(\n \"Permission denied: requesting address does not match grantee\",\n grantFile.grantee,\n requestingAddress,\n );\n }\n}\n\n/**\n * Validates that a grant has not expired (if expiry is set)\n *\n * @param grantFile - The grant file to check expiration for\n * @param currentTime - Optional override for current time (Unix timestamp)\n */\nexport function validateGrantExpiry(\n grantFile: GrantFile,\n currentTime?: number,\n): void {\n if (grantFile.expires) {\n const now =\n currentTime !== undefined ? currentTime : Math.floor(Date.now() / 1000); // Current Unix timestamp\n\n if (now > grantFile.expires) {\n throw new GrantExpiredError(\n \"Permission denied: grant has expired\",\n grantFile.expires,\n now,\n );\n }\n }\n}\n\n/**\n * Validates that a grant allows a specific operation\n *\n * @param grantFile - The grant file to validate operation access for\n * @param requestedOperation - The operation being requested to validate against the grant\n */\nexport function validateOperationAccess(\n grantFile: GrantFile,\n requestedOperation: string,\n): void {\n if (grantFile.operation !== requestedOperation) {\n throw new OperationNotAllowedError(\n \"Permission denied: operation not allowed by grant\",\n grantFile.operation,\n requestedOperation,\n );\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/grantValidation.ts"],"sourcesContent":["/**\n * Provides comprehensive validation for permission grant files.\n *\n * @remarks\n * This module implements multi-layer validation for grant files including\n * JSON schema validation, business rule checking, expiration verification,\n * and access control validation. It provides both throwing and non-throwing\n * modes for flexible error handling.\n *\n * @category Permissions\n * @module utils/grantValidation\n */\n\nimport type { Address } from \"viem\";\nimport { getAddress } from \"viem\";\nimport Ajv, { type ValidateFunction } from \"ajv\";\nimport addFormats from \"ajv-formats\";\nimport type { GrantFile } from \"../types/permissions\";\nimport grantFileSchema from \"../schemas/grantFile.schema.json\";\n\n/**\n * Indicates a general grant validation failure.\n *\n * @remarks\n * Base class for all grant validation errors. Provides structured\n * error details for debugging and recovery.\n *\n * @category Permissions\n */\nexport class GrantValidationError extends Error {\n constructor(\n message: string,\n public details?: Record<string, unknown>,\n ) {\n super(message);\n this.name = \"GrantValidationError\";\n }\n}\n\n/**\n * Indicates that a grant has expired and is no longer valid.\n *\n * @remarks\n * Thrown when attempting to use a grant past its expiration timestamp.\n * Includes both the expiration time and current time for debugging.\n *\n * @category Permissions\n */\nexport class GrantExpiredError extends GrantValidationError {\n constructor(\n message: string,\n public expires: number,\n public currentTime: number,\n ) {\n super(message, { expires, currentTime });\n this.name = \"GrantExpiredError\";\n }\n}\n\n/**\n * Indicates that the requesting address doesn't match the grant's grantee.\n *\n * @remarks\n * Thrown when a user attempts to use a grant that was issued to a\n * different address. This prevents unauthorized use of permissions.\n *\n * @category Permissions\n */\nexport class GranteeMismatchError extends GrantValidationError {\n constructor(\n message: string,\n public grantee: Address,\n public requestingAddress: Address,\n ) {\n super(message, { grantee, requestingAddress });\n this.name = \"GranteeMismatchError\";\n }\n}\n\n/**\n * Indicates that the requested operation is not allowed by the grant.\n *\n * @remarks\n * Thrown when attempting an operation that differs from what the\n * grant authorizes. Includes both granted and requested operations.\n *\n * @category Permissions\n */\nexport class OperationNotAllowedError extends GrantValidationError {\n constructor(\n message: string,\n public grantedOperation: string,\n public requestedOperation: string,\n ) {\n super(message, { grantedOperation, requestedOperation });\n this.name = \"OperationNotAllowedError\";\n }\n}\n\n/**\n * Indicates that the grant file structure violates the JSON schema.\n *\n * @remarks\n * Thrown when a grant file doesn't conform to the expected schema.\n * Includes detailed schema validation errors and the invalid data.\n *\n * @category Permissions\n */\nexport class GrantSchemaError extends GrantValidationError {\n constructor(\n message: string,\n public schemaErrors: unknown[],\n public invalidData: unknown,\n ) {\n super(message, { errors: schemaErrors, data: invalidData });\n this.name = \"GrantSchemaError\";\n }\n}\n\n/**\n * Ajv instance configured for strict grant file validation.\n *\n * @internal\n */\nconst ajv = new Ajv({\n strict: true,\n removeAdditional: false,\n useDefaults: false,\n coerceTypes: false,\n});\n\n// Add format validators (email, date, etc.)\naddFormats(ajv);\n\n/**\n * Pre-compiled grant file schema validator for performance.\n *\n * @internal\n */\nconst validateGrantFileSchema: ValidateFunction = ajv.compile(grantFileSchema);\n\n/**\n * Configures grant validation behavior and scope.\n *\n * @remarks\n * Controls which validations to perform and how to handle errors.\n * Allows selective validation of specific aspects of a grant.\n *\n * @category Permissions\n */\nexport interface GrantValidationOptions {\n /** Enable JSON schema validation (default: true) */\n schema?: boolean;\n /** Grantee address to validate access for */\n grantee?: Address;\n /** Operation to validate permission for */\n operation?: string;\n /** Override current time for expiry checking (Unix timestamp) */\n currentTime?: number;\n /** Return detailed results instead of throwing (default: false) */\n throwOnError?: boolean;\n}\n\n/**\n * Represents the detailed result of grant validation.\n *\n * @remarks\n * Provides comprehensive validation results including all errors\n * encountered during validation. Used in non-throwing mode.\n *\n * @category Permissions\n */\nexport interface GrantValidationResult {\n /** Whether validation passed */\n valid: boolean;\n /** Validation errors encountered */\n errors: Array<{\n type: \"schema\" | \"business\";\n field?: string;\n message: string;\n error?: Error;\n }>;\n /** The validated grant file (if validation passed) */\n grant?: GrantFile;\n}\n\n/**\n * Validates a grant file with comprehensive schema and business rule checking.\n *\n * This function provides flexible validation with TypeScript overloads:\n * - When `throwOnError` is false (or `{ throwOnError: false }`), returns a detailed validation result\n * - When `throwOnError` is true (default), throws specific errors or returns the validated grant\n *\n * @param data - The grant file data to validate (unknown type for safety)\n * @param options - Validation options including grantee, operation, files, etc.\n * @returns Either a GrantFile (when throwing) or GrantValidationResult (when not throwing)\n * @throws {GrantSchemaError} When the grant file structure is invalid\n * @throws {GrantExpiredError} When the grant has expired\n * @throws {GranteeMismatchError} When the grantee doesn't match the requesting address\n * @throws {OperationNotAllowedError} When the requested operation is not allowed\n * @example\n * ```typescript\n * // Throwing mode (default) - returns GrantFile or throws\n * const grant = validateGrant(data, {\n * grantee: '0x123...',\n * operation: 'llm_inference',\n * });\n *\n * // Non-throwing mode - returns validation result\n * const result = validateGrant(data, {\n * grantee: '0x123...',\n * throwOnError: false\n * });\n * if (result.valid) {\n * console.log('Grant is valid:', result.grant);\n * } else {\n * console.log('Validation errors:', result.errors);\n * }\n * ```\n */\n\nexport function validateGrant(\n data: unknown,\n options: GrantValidationOptions & { throwOnError: false },\n): GrantValidationResult;\n\nexport function validateGrant(\n data: unknown,\n options?:\n | Omit<GrantValidationOptions, \"throwOnError\">\n | (GrantValidationOptions & { throwOnError?: true }),\n): GrantFile;\n\n/** @internal */\nexport function validateGrant(\n data: unknown,\n options: GrantValidationOptions = {},\n): GrantFile | GrantValidationResult {\n const {\n schema = true,\n grantee,\n operation,\n currentTime,\n throwOnError = true,\n } = options;\n\n const errors: GrantValidationResult[\"errors\"] = [];\n let grant: GrantFile | undefined;\n\n // 1. Schema Validation\n if (schema) {\n try {\n if (validateGrantFileSchema(data)) {\n grant = data as GrantFile;\n } else {\n throw new GrantValidationError(\"Invalid grant file schema\");\n }\n } catch (error) {\n if (error instanceof GrantValidationError) {\n const schemaError = new GrantSchemaError(\n error.message,\n Array.isArray(error.details?.errors) ? error.details.errors : [],\n data,\n );\n errors.push({\n type: \"schema\",\n message: error.message,\n error: schemaError,\n });\n } else {\n errors.push({\n type: \"schema\",\n message: `Schema validation failed: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\n });\n }\n }\n } else {\n // Minimal type assertion if schema validation is skipped\n grant = data as GrantFile;\n }\n\n // 2. Business Logic Validation (only if we have a valid grant)\n if (grant) {\n // Check grantee access\n if (grantee) {\n try {\n validateGranteeAccess(grant, grantee);\n } catch (error) {\n const field = extractFieldFromBusinessError(error);\n errors.push({\n type: \"business\",\n field,\n message:\n error instanceof Error\n ? error.message\n : \"Unknown business rule error\",\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\n });\n }\n }\n\n // Check expiration\n try {\n validateGrantExpiry(grant, currentTime);\n } catch (error) {\n const field = extractFieldFromBusinessError(error);\n errors.push({\n type: \"business\",\n field,\n message:\n error instanceof Error\n ? error.message\n : \"Unknown business rule error\",\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\n });\n }\n\n // Check operation access\n if (operation) {\n try {\n validateOperationAccess(grant, operation);\n } catch (error) {\n const field = extractFieldFromBusinessError(error);\n errors.push({\n type: \"business\",\n field,\n message:\n error instanceof Error\n ? error.message\n : \"Unknown business rule error\",\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\n });\n }\n }\n }\n\n // 3. Return Results\n if (errors.length > 0) {\n if (throwOnError) {\n // Throw the most specific error we have\n const firstError = errors[0];\n if (firstError.error) {\n throw firstError.error;\n } else {\n const combinedMessage = errors.map((e) => e.message).join(\"; \");\n throw new GrantValidationError(\n `Grant validation failed: ${combinedMessage}`,\n { errors, data },\n );\n }\n }\n\n return { valid: false, errors, grant };\n }\n\n if (throwOnError) {\n return grant as GrantFile;\n } else {\n return { valid: true, errors: [], grant: grant as GrantFile };\n }\n}\n\n/**\n * Extracts the field name from business validation errors for reporting.\n *\n * @param error - The validation error to analyze\n * @returns The field name associated with the error, or undefined\n *\n * @internal\n */\nfunction extractFieldFromBusinessError(error: unknown): string | undefined {\n if (error instanceof GrantExpiredError) return \"expires\";\n if (error instanceof GranteeMismatchError) return \"grantee\";\n if (error instanceof OperationNotAllowedError) return \"operation\";\n return undefined;\n}\n\n/**\n * Validates that a grant allows access for the requesting address.\n *\n * @param grantFile - The grant file to validate.\n * Obtain from permission operations or server responses.\n * @param requestingAddress - The address requesting access.\n * Typically the current wallet address.\n *\n * @throws {GranteeMismatchError} If addresses don't match.\n * Ensure using the correct wallet that received the grant.\n *\n * @example\n * ```typescript\n * validateGranteeAccess(grantFile, walletAddress);\n * // Throws if walletAddress doesn't match grantFile.grantee\n * ```\n *\n * @category Permissions\n */\nexport function validateGranteeAccess(\n grantFile: GrantFile,\n requestingAddress: Address,\n): void {\n const normalizedGrantee = getAddress(grantFile.grantee);\n const normalizedRequesting = getAddress(requestingAddress);\n\n if (normalizedGrantee !== normalizedRequesting) {\n throw new GranteeMismatchError(\n \"Permission denied: requesting address does not match grantee\",\n grantFile.grantee,\n requestingAddress,\n );\n }\n}\n\n/**\n * Validates that a grant has not expired.\n *\n * @param grantFile - The grant file to check.\n * Must include expiry timestamp if time-limited.\n * @param currentTime - Optional time override for testing.\n * Unix timestamp in seconds. Defaults to current time.\n *\n * @throws {GrantExpiredError} If grant has expired.\n * Request a new grant from the data owner.\n *\n * @example\n * ```typescript\n * validateGrantExpiry(grantFile);\n * // Throws if current time > grantFile.expires\n * ```\n *\n * @category Permissions\n */\nexport function validateGrantExpiry(\n grantFile: GrantFile,\n currentTime?: number,\n): void {\n if (grantFile.expires) {\n const now =\n currentTime !== undefined ? currentTime : Math.floor(Date.now() / 1000); // Current Unix timestamp\n\n if (now > grantFile.expires) {\n throw new GrantExpiredError(\n \"Permission denied: grant has expired\",\n grantFile.expires,\n now,\n );\n }\n }\n}\n\n/**\n * Validates that a grant authorizes the requested operation.\n *\n * @param grantFile - The grant file to check.\n * Contains the authorized operation.\n * @param requestedOperation - The operation to validate.\n * Must match the grant's operation exactly.\n *\n * @throws {OperationNotAllowedError} If operations don't match.\n * Request a grant for the specific operation needed.\n *\n * @example\n * ```typescript\n * validateOperationAccess(grantFile, 'llm_inference');\n * // Throws if grantFile.operation !== 'llm_inference'\n * ```\n *\n * @category Permissions\n */\nexport function validateOperationAccess(\n grantFile: GrantFile,\n requestedOperation: string,\n): void {\n if (grantFile.operation !== requestedOperation) {\n throw new OperationNotAllowedError(\n \"Permission denied: operation not allowed by grant\",\n grantFile.operation,\n requestedOperation,\n );\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/grants.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"sources":["../../src/utils/grants.ts"],"sourcesContent":["/**\n * Provides high-level grant management utilities for the Vana permission system.\n *\n * @remarks\n * This module simplifies grant file creation, validation, storage, and retrieval.\n * Grants are the core mechanism for permission management in Vana, allowing users\n * to delegate specific operations to applications and services.\n *\n * @category Permissions\n * @module grants\n */\n\nimport type { Address } from \"viem\";\nimport type { GrantFile, GrantPermissionParams } from \"../types/permissions\";\nimport {\n createGrantFile,\n storeGrantFile,\n retrieveGrantFile,\n} from \"./grantFiles\";\nimport { validateGrant, GrantValidationError } from \"./grantValidation\";\n\n/**\n * Creates and validates a grant file from permission parameters.\n *\n * @remarks\n * Combines grant creation with immediate validation to ensure only valid\n * grants are created. Validates schema compliance, grantee address, and\n * operation parameters before returning the grant file.\n *\n * @param params - The permission parameters to create and validate the grant from.\n * Obtain from user input or application configuration.\n * @returns The validated grant file object ready for storage\n *\n * @throws {GrantValidationError} When grant parameters are invalid.\n * Check error message for specific validation failures.\n *\n * @example\n * ```typescript\n * const grant = createValidatedGrant({\n * grantee: '0x742d35Cc6634C0532925a3b844Bc9e7595f0b0Bb',\n * operation: 'llm_inference',\n * parameters: { model: 'gpt-4', maxTokens: 1000 },\n * expiresAt: Date.now() + 86400000 // 24 hours\n * });\n * ```\n *\n * @category Permissions\n */\nexport function createValidatedGrant(params: GrantPermissionParams): GrantFile {\n const grantFile = createGrantFile(params);\n\n // Validate the created grant file\n try {\n validateGrant(grantFile, {\n schema: true,\n grantee: params.grantee,\n operation: params.operation,\n });\n } catch (error) {\n throw new GrantValidationError(\n `Created grant file failed validation: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n { grantFile, params },\n );\n }\n\n return grantFile;\n}\n\n/**\n * Creates a grant file and stores it in IPFS.\n *\n * @remarks\n * Combines grant creation, validation, and IPFS storage in a single operation.\n * The grant is stored immutably on IPFS and can be referenced by its URL in\n * on-chain permission records.\n *\n * @param params - The permission parameters to create the grant from.\n * Obtain from user input or application configuration.\n * @param relayerUrl - The URL of the relayer service for IPFS storage.\n * Obtain from SDK configuration or environment.\n * @returns Promise resolving to an object containing the grant file and its IPFS URL\n *\n * @throws {GrantValidationError} When grant parameters are invalid.\n * Check error message for specific validation failures.\n * @throws {Error} When IPFS storage fails.\n * Retry with exponential backoff or check relayer status.\n *\n * @example\n * ```typescript\n * const { grantFile, grantUrl } = await createAndStoreGrant(\n * {\n * grantee: applicationAddress,\n * operation: 'data_processing',\n * parameters: { dataTypes: ['medical', 'financial'] }\n * },\n * 'https://relayer.vana.org'\n * );\n *\n * console.log('Grant stored at:', grantUrl);\n * ```\n *\n * @category Permissions\n */\nexport async function createAndStoreGrant(\n params: GrantPermissionParams,\n relayerUrl: string,\n): Promise<{ grantFile: GrantFile; grantUrl: string }> {\n const grantFile = createValidatedGrant(params);\n const grantUrl = await storeGrantFile(grantFile, relayerUrl);\n\n return { grantFile, grantUrl };\n}\n\n/**\n * Retrieves and validates a grant file from IPFS.\n *\n * @remarks\n * Fetches a grant file from IPFS and performs basic validation to ensure\n * the retrieved data is a valid grant structure. Use this when you need to\n * verify or process existing grants.\n *\n * @param grantUrl - The IPFS URL of the grant file to retrieve.\n * Obtain from on-chain permission records or grant events.\n * @param relayerUrl - Optional URL of the relayer service.\n * If not provided, uses default IPFS gateways.\n * @returns Promise resolving to the validated grant file\n *\n * @throws {Error} When grant retrieval fails.\n * Check network connectivity or IPFS gateway availability.\n * @throws {Error} When grant file is malformed.\n * Verify the grant URL points to a valid grant file.\n *\n * @example\n * ```typescript\n * const grant = await retrieveAndValidateGrant(\n * 'ipfs://QmXxx...'\n * );\n *\n * console.log('Grant for:', grant.grantee);\n * console.log('Operation:', grant.operation);\n * ```\n *\n * @category Permissions\n */\nexport async function retrieveAndValidateGrant(\n grantUrl: string,\n relayerUrl?: string,\n): Promise<GrantFile> {\n const grantFile = await retrieveGrantFile(grantUrl, relayerUrl);\n\n // Additional validation can be added here if needed\n return grantFile;\n}\n\n/**\n * Checks if a grant allows access for a specific request\n *\n * @param grantUrl - The IPFS URL of the grant file to check\n * @param requestingAddress - The address making the access request\n * @param operation - The operation being requested\n * @param _fileIds - Array of file IDs being accessed (currently unused but part of interface)\n * @param relayerUrl - Optional URL of the relayer service\n * @returns Promise resolving to access result with allowed status, reason, and grant file\n */\nexport async function checkGrantAccess(\n grantUrl: string,\n requestingAddress: Address,\n operation: string,\n _fileIds: number[],\n relayerUrl?: string,\n): Promise<{ allowed: boolean; reason?: string; grantFile?: GrantFile }> {\n try {\n const grantFile = await retrieveAndValidateGrant(grantUrl, relayerUrl);\n\n // Validate the grant for the request\n validateGrant(grantFile, {\n schema: true,\n grantee: requestingAddress,\n operation,\n });\n\n return { allowed: true, grantFile };\n } catch (error) {\n if (error instanceof GrantValidationError) {\n return {\n allowed: false,\n reason: error.message,\n };\n }\n\n return {\n allowed: false,\n reason: `Grant access check failed: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n };\n }\n}\n\n/**\n * Utility to check if a grant has expired\n *\n * @param grantFile - The grant file to check for expiration\n * @returns True if the grant has expired, false otherwise\n */\nexport function isGrantExpired(grantFile: GrantFile): boolean {\n if (!grantFile.expires) {\n return false; // No expiration set\n }\n\n const now = Math.floor(Date.now() / 1000);\n return now > grantFile.expires;\n}\n\n/**\n * Utility to get the time remaining before grant expires (in seconds)\n *\n * @param grantFile - The grant file to check time remaining for\n * @returns Number of seconds remaining, or null if no expiration is set\n */\nexport function getGrantTimeRemaining(grantFile: GrantFile): number | null {\n if (!grantFile.expires) {\n return null; // No expiration set\n }\n\n const now = Math.floor(Date.now() / 1000);\n const remaining = grantFile.expires - now;\n return Math.max(0, remaining);\n}\n\n/**\n * Creates a human-readable summary of a grant\n *\n * @param grantFile - The grant file to create a summary for\n * @returns A human-readable string describing the grant\n */\nexport function summarizeGrant(grantFile: GrantFile): string {\n const expiration = grantFile.expires\n ? new Date(grantFile.expires * 1000).toISOString()\n : \"No expiration\";\n\n return `Grant for ${grantFile.grantee} to perform \"${grantFile.operation}\" (expires: ${expiration})`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,wBAIO;AACP,6BAAoD;AA6B7C,SAAS,qBAAqB,QAA0C;AAC7E,QAAM,gBAAY,mCAAgB,MAAM;AAGxC,MAAI;AACF,8CAAc,WAAW;AAAA,MACvB,QAAQ;AAAA,MACR,SAAS,OAAO;AAAA,MAChB,WAAW,OAAO;AAAA,IACpB,CAAC;AAAA,EACH,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,yCAAyC,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,MACjG,EAAE,WAAW,OAAO;AAAA,IACtB;AAAA,EACF;AAEA,SAAO;AACT;AAqCA,eAAsB,oBACpB,QACA,YACqD;AACrD,QAAM,YAAY,qBAAqB,MAAM;AAC7C,QAAM,WAAW,UAAM,kCAAe,WAAW,UAAU;AAE3D,SAAO,EAAE,WAAW,SAAS;AAC/B;AAiCA,eAAsB,yBACpB,UACA,YACoB;AACpB,QAAM,YAAY,UAAM,qCAAkB,UAAU,UAAU;AAG9D,SAAO;AACT;AAYA,eAAsB,iBACpB,UACA,mBACA,WACA,UACA,YACuE;AACvE,MAAI;AACF,UAAM,YAAY,MAAM,yBAAyB,UAAU,UAAU;AAGrE,8CAAc,WAAW;AAAA,MACvB,QAAQ;AAAA,MACR,SAAS;AAAA,MACT;AAAA,IACF,CAAC;AAED,WAAO,EAAE,SAAS,MAAM,UAAU;AAAA,EACpC,SAAS,OAAO;AACd,QAAI,iBAAiB,6CAAsB;AACzC,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,MAAM;AAAA,MAChB;AAAA,IACF;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ,8BAA8B,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,IAChG;AAAA,EACF;AACF;AAQO,SAAS,eAAe,WAA+B;AAC5D,MAAI,CAAC,UAAU,SAAS;AACtB,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,SAAO,MAAM,UAAU;AACzB;AAQO,SAAS,sBAAsB,WAAqC;AACzE,MAAI,CAAC,UAAU,SAAS;AACtB,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,QAAM,YAAY,UAAU,UAAU;AACtC,SAAO,KAAK,IAAI,GAAG,SAAS;AAC9B;AAQO,SAAS,eAAe,WAA8B;AAC3D,QAAM,aAAa,UAAU,UACzB,IAAI,KAAK,UAAU,UAAU,GAAI,EAAE,YAAY,IAC/C;AAEJ,SAAO,aAAa,UAAU,OAAO,gBAAgB,UAAU,SAAS,eAAe,UAAU;AACnG;","names":[]}
|
package/dist/utils/grants.d.ts
CHANGED
|
@@ -1,32 +1,113 @@
|
|
|
1
|
-
import type { Address } from "viem";
|
|
2
|
-
import type { GrantFile, GrantPermissionParams } from "../types/permissions";
|
|
3
1
|
/**
|
|
4
|
-
*
|
|
2
|
+
* Provides high-level grant management utilities for the Vana permission system.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* This module simplifies grant file creation, validation, storage, and retrieval.
|
|
6
|
+
* Grants are the core mechanism for permission management in Vana, allowing users
|
|
7
|
+
* to delegate specific operations to applications and services.
|
|
8
|
+
*
|
|
9
|
+
* @category Permissions
|
|
10
|
+
* @module grants
|
|
5
11
|
*/
|
|
12
|
+
import type { Address } from "viem";
|
|
13
|
+
import type { GrantFile, GrantPermissionParams } from "../types/permissions";
|
|
6
14
|
/**
|
|
7
|
-
* Creates and validates a grant file from permission parameters
|
|
15
|
+
* Creates and validates a grant file from permission parameters.
|
|
8
16
|
*
|
|
9
|
-
* @
|
|
10
|
-
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* Combines grant creation with immediate validation to ensure only valid
|
|
19
|
+
* grants are created. Validates schema compliance, grantee address, and
|
|
20
|
+
* operation parameters before returning the grant file.
|
|
21
|
+
*
|
|
22
|
+
* @param params - The permission parameters to create and validate the grant from.
|
|
23
|
+
* Obtain from user input or application configuration.
|
|
24
|
+
* @returns The validated grant file object ready for storage
|
|
25
|
+
*
|
|
26
|
+
* @throws {GrantValidationError} When grant parameters are invalid.
|
|
27
|
+
* Check error message for specific validation failures.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const grant = createValidatedGrant({
|
|
32
|
+
* grantee: '0x742d35Cc6634C0532925a3b844Bc9e7595f0b0Bb',
|
|
33
|
+
* operation: 'llm_inference',
|
|
34
|
+
* parameters: { model: 'gpt-4', maxTokens: 1000 },
|
|
35
|
+
* expiresAt: Date.now() + 86400000 // 24 hours
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @category Permissions
|
|
11
40
|
*/
|
|
12
41
|
export declare function createValidatedGrant(params: GrantPermissionParams): GrantFile;
|
|
13
42
|
/**
|
|
14
|
-
* Creates a grant file and stores it in IPFS
|
|
43
|
+
* Creates a grant file and stores it in IPFS.
|
|
44
|
+
*
|
|
45
|
+
* @remarks
|
|
46
|
+
* Combines grant creation, validation, and IPFS storage in a single operation.
|
|
47
|
+
* The grant is stored immutably on IPFS and can be referenced by its URL in
|
|
48
|
+
* on-chain permission records.
|
|
15
49
|
*
|
|
16
|
-
* @param params - The permission parameters to create the grant from
|
|
17
|
-
*
|
|
50
|
+
* @param params - The permission parameters to create the grant from.
|
|
51
|
+
* Obtain from user input or application configuration.
|
|
52
|
+
* @param relayerUrl - The URL of the relayer service for IPFS storage.
|
|
53
|
+
* Obtain from SDK configuration or environment.
|
|
18
54
|
* @returns Promise resolving to an object containing the grant file and its IPFS URL
|
|
55
|
+
*
|
|
56
|
+
* @throws {GrantValidationError} When grant parameters are invalid.
|
|
57
|
+
* Check error message for specific validation failures.
|
|
58
|
+
* @throws {Error} When IPFS storage fails.
|
|
59
|
+
* Retry with exponential backoff or check relayer status.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* const { grantFile, grantUrl } = await createAndStoreGrant(
|
|
64
|
+
* {
|
|
65
|
+
* grantee: applicationAddress,
|
|
66
|
+
* operation: 'data_processing',
|
|
67
|
+
* parameters: { dataTypes: ['medical', 'financial'] }
|
|
68
|
+
* },
|
|
69
|
+
* 'https://relayer.vana.org'
|
|
70
|
+
* );
|
|
71
|
+
*
|
|
72
|
+
* console.log('Grant stored at:', grantUrl);
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* @category Permissions
|
|
19
76
|
*/
|
|
20
77
|
export declare function createAndStoreGrant(params: GrantPermissionParams, relayerUrl: string): Promise<{
|
|
21
78
|
grantFile: GrantFile;
|
|
22
79
|
grantUrl: string;
|
|
23
80
|
}>;
|
|
24
81
|
/**
|
|
25
|
-
* Retrieves and validates a grant file from IPFS
|
|
82
|
+
* Retrieves and validates a grant file from IPFS.
|
|
26
83
|
*
|
|
27
|
-
* @
|
|
28
|
-
*
|
|
84
|
+
* @remarks
|
|
85
|
+
* Fetches a grant file from IPFS and performs basic validation to ensure
|
|
86
|
+
* the retrieved data is a valid grant structure. Use this when you need to
|
|
87
|
+
* verify or process existing grants.
|
|
88
|
+
*
|
|
89
|
+
* @param grantUrl - The IPFS URL of the grant file to retrieve.
|
|
90
|
+
* Obtain from on-chain permission records or grant events.
|
|
91
|
+
* @param relayerUrl - Optional URL of the relayer service.
|
|
92
|
+
* If not provided, uses default IPFS gateways.
|
|
29
93
|
* @returns Promise resolving to the validated grant file
|
|
94
|
+
*
|
|
95
|
+
* @throws {Error} When grant retrieval fails.
|
|
96
|
+
* Check network connectivity or IPFS gateway availability.
|
|
97
|
+
* @throws {Error} When grant file is malformed.
|
|
98
|
+
* Verify the grant URL points to a valid grant file.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* const grant = await retrieveAndValidateGrant(
|
|
103
|
+
* 'ipfs://QmXxx...'
|
|
104
|
+
* );
|
|
105
|
+
*
|
|
106
|
+
* console.log('Grant for:', grant.grantee);
|
|
107
|
+
* console.log('Operation:', grant.operation);
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* @category Permissions
|
|
30
111
|
*/
|
|
31
112
|
export declare function retrieveAndValidateGrant(grantUrl: string, relayerUrl?: string): Promise<GrantFile>;
|
|
32
113
|
/**
|
package/dist/utils/grants.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/grants.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"sources":["../../src/utils/grants.ts"],"sourcesContent":["/**\n * Provides high-level grant management utilities for the Vana permission system.\n *\n * @remarks\n * This module simplifies grant file creation, validation, storage, and retrieval.\n * Grants are the core mechanism for permission management in Vana, allowing users\n * to delegate specific operations to applications and services.\n *\n * @category Permissions\n * @module grants\n */\n\nimport type { Address } from \"viem\";\nimport type { GrantFile, GrantPermissionParams } from \"../types/permissions\";\nimport {\n createGrantFile,\n storeGrantFile,\n retrieveGrantFile,\n} from \"./grantFiles\";\nimport { validateGrant, GrantValidationError } from \"./grantValidation\";\n\n/**\n * Creates and validates a grant file from permission parameters.\n *\n * @remarks\n * Combines grant creation with immediate validation to ensure only valid\n * grants are created. Validates schema compliance, grantee address, and\n * operation parameters before returning the grant file.\n *\n * @param params - The permission parameters to create and validate the grant from.\n * Obtain from user input or application configuration.\n * @returns The validated grant file object ready for storage\n *\n * @throws {GrantValidationError} When grant parameters are invalid.\n * Check error message for specific validation failures.\n *\n * @example\n * ```typescript\n * const grant = createValidatedGrant({\n * grantee: '0x742d35Cc6634C0532925a3b844Bc9e7595f0b0Bb',\n * operation: 'llm_inference',\n * parameters: { model: 'gpt-4', maxTokens: 1000 },\n * expiresAt: Date.now() + 86400000 // 24 hours\n * });\n * ```\n *\n * @category Permissions\n */\nexport function createValidatedGrant(params: GrantPermissionParams): GrantFile {\n const grantFile = createGrantFile(params);\n\n // Validate the created grant file\n try {\n validateGrant(grantFile, {\n schema: true,\n grantee: params.grantee,\n operation: params.operation,\n });\n } catch (error) {\n throw new GrantValidationError(\n `Created grant file failed validation: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n { grantFile, params },\n );\n }\n\n return grantFile;\n}\n\n/**\n * Creates a grant file and stores it in IPFS.\n *\n * @remarks\n * Combines grant creation, validation, and IPFS storage in a single operation.\n * The grant is stored immutably on IPFS and can be referenced by its URL in\n * on-chain permission records.\n *\n * @param params - The permission parameters to create the grant from.\n * Obtain from user input or application configuration.\n * @param relayerUrl - The URL of the relayer service for IPFS storage.\n * Obtain from SDK configuration or environment.\n * @returns Promise resolving to an object containing the grant file and its IPFS URL\n *\n * @throws {GrantValidationError} When grant parameters are invalid.\n * Check error message for specific validation failures.\n * @throws {Error} When IPFS storage fails.\n * Retry with exponential backoff or check relayer status.\n *\n * @example\n * ```typescript\n * const { grantFile, grantUrl } = await createAndStoreGrant(\n * {\n * grantee: applicationAddress,\n * operation: 'data_processing',\n * parameters: { dataTypes: ['medical', 'financial'] }\n * },\n * 'https://relayer.vana.org'\n * );\n *\n * console.log('Grant stored at:', grantUrl);\n * ```\n *\n * @category Permissions\n */\nexport async function createAndStoreGrant(\n params: GrantPermissionParams,\n relayerUrl: string,\n): Promise<{ grantFile: GrantFile; grantUrl: string }> {\n const grantFile = createValidatedGrant(params);\n const grantUrl = await storeGrantFile(grantFile, relayerUrl);\n\n return { grantFile, grantUrl };\n}\n\n/**\n * Retrieves and validates a grant file from IPFS.\n *\n * @remarks\n * Fetches a grant file from IPFS and performs basic validation to ensure\n * the retrieved data is a valid grant structure. Use this when you need to\n * verify or process existing grants.\n *\n * @param grantUrl - The IPFS URL of the grant file to retrieve.\n * Obtain from on-chain permission records or grant events.\n * @param relayerUrl - Optional URL of the relayer service.\n * If not provided, uses default IPFS gateways.\n * @returns Promise resolving to the validated grant file\n *\n * @throws {Error} When grant retrieval fails.\n * Check network connectivity or IPFS gateway availability.\n * @throws {Error} When grant file is malformed.\n * Verify the grant URL points to a valid grant file.\n *\n * @example\n * ```typescript\n * const grant = await retrieveAndValidateGrant(\n * 'ipfs://QmXxx...'\n * );\n *\n * console.log('Grant for:', grant.grantee);\n * console.log('Operation:', grant.operation);\n * ```\n *\n * @category Permissions\n */\nexport async function retrieveAndValidateGrant(\n grantUrl: string,\n relayerUrl?: string,\n): Promise<GrantFile> {\n const grantFile = await retrieveGrantFile(grantUrl, relayerUrl);\n\n // Additional validation can be added here if needed\n return grantFile;\n}\n\n/**\n * Checks if a grant allows access for a specific request\n *\n * @param grantUrl - The IPFS URL of the grant file to check\n * @param requestingAddress - The address making the access request\n * @param operation - The operation being requested\n * @param _fileIds - Array of file IDs being accessed (currently unused but part of interface)\n * @param relayerUrl - Optional URL of the relayer service\n * @returns Promise resolving to access result with allowed status, reason, and grant file\n */\nexport async function checkGrantAccess(\n grantUrl: string,\n requestingAddress: Address,\n operation: string,\n _fileIds: number[],\n relayerUrl?: string,\n): Promise<{ allowed: boolean; reason?: string; grantFile?: GrantFile }> {\n try {\n const grantFile = await retrieveAndValidateGrant(grantUrl, relayerUrl);\n\n // Validate the grant for the request\n validateGrant(grantFile, {\n schema: true,\n grantee: requestingAddress,\n operation,\n });\n\n return { allowed: true, grantFile };\n } catch (error) {\n if (error instanceof GrantValidationError) {\n return {\n allowed: false,\n reason: error.message,\n };\n }\n\n return {\n allowed: false,\n reason: `Grant access check failed: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n };\n }\n}\n\n/**\n * Utility to check if a grant has expired\n *\n * @param grantFile - The grant file to check for expiration\n * @returns True if the grant has expired, false otherwise\n */\nexport function isGrantExpired(grantFile: GrantFile): boolean {\n if (!grantFile.expires) {\n return false; // No expiration set\n }\n\n const now = Math.floor(Date.now() / 1000);\n return now > grantFile.expires;\n}\n\n/**\n * Utility to get the time remaining before grant expires (in seconds)\n *\n * @param grantFile - The grant file to check time remaining for\n * @returns Number of seconds remaining, or null if no expiration is set\n */\nexport function getGrantTimeRemaining(grantFile: GrantFile): number | null {\n if (!grantFile.expires) {\n return null; // No expiration set\n }\n\n const now = Math.floor(Date.now() / 1000);\n const remaining = grantFile.expires - now;\n return Math.max(0, remaining);\n}\n\n/**\n * Creates a human-readable summary of a grant\n *\n * @param grantFile - The grant file to create a summary for\n * @returns A human-readable string describing the grant\n */\nexport function summarizeGrant(grantFile: GrantFile): string {\n const expiration = grantFile.expires\n ? new Date(grantFile.expires * 1000).toISOString()\n : \"No expiration\";\n\n return `Grant for ${grantFile.grantee} to perform \"${grantFile.operation}\" (expires: ${expiration})`;\n}\n"],"mappings":"AAcA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,eAAe,4BAA4B;AA6B7C,SAAS,qBAAqB,QAA0C;AAC7E,QAAM,YAAY,gBAAgB,MAAM;AAGxC,MAAI;AACF,kBAAc,WAAW;AAAA,MACvB,QAAQ;AAAA,MACR,SAAS,OAAO;AAAA,MAChB,WAAW,OAAO;AAAA,IACpB,CAAC;AAAA,EACH,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,yCAAyC,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,MACjG,EAAE,WAAW,OAAO;AAAA,IACtB;AAAA,EACF;AAEA,SAAO;AACT;AAqCA,eAAsB,oBACpB,QACA,YACqD;AACrD,QAAM,YAAY,qBAAqB,MAAM;AAC7C,QAAM,WAAW,MAAM,eAAe,WAAW,UAAU;AAE3D,SAAO,EAAE,WAAW,SAAS;AAC/B;AAiCA,eAAsB,yBACpB,UACA,YACoB;AACpB,QAAM,YAAY,MAAM,kBAAkB,UAAU,UAAU;AAG9D,SAAO;AACT;AAYA,eAAsB,iBACpB,UACA,mBACA,WACA,UACA,YACuE;AACvE,MAAI;AACF,UAAM,YAAY,MAAM,yBAAyB,UAAU,UAAU;AAGrE,kBAAc,WAAW;AAAA,MACvB,QAAQ;AAAA,MACR,SAAS;AAAA,MACT;AAAA,IACF,CAAC;AAED,WAAO,EAAE,SAAS,MAAM,UAAU;AAAA,EACpC,SAAS,OAAO;AACd,QAAI,iBAAiB,sBAAsB;AACzC,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,MAAM;AAAA,MAChB;AAAA,IACF;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ,8BAA8B,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,IAChG;AAAA,EACF;AACF;AAQO,SAAS,eAAe,WAA+B;AAC5D,MAAI,CAAC,UAAU,SAAS;AACtB,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,SAAO,MAAM,UAAU;AACzB;AAQO,SAAS,sBAAsB,WAAqC;AACzE,MAAI,CAAC,UAAU,SAAS;AACtB,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,QAAM,YAAY,UAAU,UAAU;AACtC,SAAO,KAAK,IAAI,GAAG,SAAS;AAC9B;AAQO,SAAS,eAAe,WAA8B;AAC3D,QAAM,aAAa,UAAU,UACzB,IAAI,KAAK,UAAU,UAAU,GAAI,EAAE,YAAY,IAC/C;AAEJ,SAAO,aAAa,UAAU,OAAO,gBAAgB,UAAU,SAAS,eAAe,UAAU;AACnG;","names":[]}
|
package/dist/utils/ipfs.cjs
CHANGED
|
@@ -30,12 +30,10 @@ __export(ipfs_exports, {
|
|
|
30
30
|
module.exports = __toCommonJS(ipfs_exports);
|
|
31
31
|
const DEFAULT_IPFS_GATEWAY = "https://dweb.link/ipfs/";
|
|
32
32
|
const IPFS_GATEWAYS = [
|
|
33
|
-
"https://dweb.link/ipfs/",
|
|
34
|
-
// Interplanetary Shipyard - highly reliable
|
|
35
33
|
"https://ipfs.io/ipfs/",
|
|
36
34
|
// IPFS Foundation - reliable
|
|
37
|
-
"https://
|
|
38
|
-
//
|
|
35
|
+
"https://dweb.link/ipfs/",
|
|
36
|
+
// Interplanetary Shipyard - observed to be having issues
|
|
39
37
|
"https://gateway.pinata.cloud/ipfs/",
|
|
40
38
|
// Pinata - backup option (has rate limits)
|
|
41
39
|
"https://ipfs.filebase.io/ipfs/"
|
package/dist/utils/ipfs.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/ipfs.ts"],"sourcesContent":["/**\n * IPFS URL utilities for the Vana SDK\n *\n * Centralized functions for handling IPFS URLs, converting them to gateway URLs,\n * and extracting IPFS hashes from various URL formats.\n */\n\n/**\n * Default IPFS gateway URL\n */\nexport const DEFAULT_IPFS_GATEWAY = \"https://dweb.link/ipfs/\";\n\n/**\n * Alternative IPFS gateways for fallback - ordered by reliability and rate limits\n */\nexport const IPFS_GATEWAYS = [\n \"https://
|
|
1
|
+
{"version":3,"sources":["../../src/utils/ipfs.ts"],"sourcesContent":["/**\n * IPFS URL utilities for the Vana SDK\n *\n * Centralized functions for handling IPFS URLs, converting them to gateway URLs,\n * and extracting IPFS hashes from various URL formats.\n */\n\n/**\n * Default IPFS gateway URL\n */\nexport const DEFAULT_IPFS_GATEWAY = \"https://dweb.link/ipfs/\";\n\n/**\n * Alternative IPFS gateways for fallback - ordered by reliability and rate limits\n */\nexport const IPFS_GATEWAYS = [\n \"https://ipfs.io/ipfs/\", // IPFS Foundation - reliable\n \"https://dweb.link/ipfs/\", // Interplanetary Shipyard - observed to be having issues\n \"https://gateway.pinata.cloud/ipfs/\", // Pinata - backup option (has rate limits)\n \"https://ipfs.filebase.io/ipfs/\", // Filebase - emerging reliable option\n] as const;\n\n/**\n * Check if a URL is an IPFS URL (starts with ipfs://)\n *\n * @param url - The URL to check\n * @returns True if the URL is an IPFS URL\n */\nexport function isIpfsUrl(url: string): boolean {\n return url.startsWith(\"ipfs://\");\n}\n\n/**\n * Convert an IPFS URL to an HTTP gateway URL\n *\n * @param url - The IPFS URL to convert (e.g., \"ipfs://QmHash...\")\n * @param gateway - Optional gateway URL (defaults to DEFAULT_IPFS_GATEWAY)\n * @returns The HTTP gateway URL or original URL if not an IPFS URL\n * @example\n * ```ts\n * convertIpfsUrl(\"ipfs://QmHash123\")\n * // Returns: \"https://ipfs.io/ipfs/QmHash123\"\n *\n * convertIpfsUrl(\"ipfs://QmHash123\", \"https://gateway.pinata.cloud/ipfs/\")\n * // Returns: \"https://gateway.pinata.cloud/ipfs/QmHash123\"\n * ```\n */\nexport function convertIpfsUrl(\n url: string,\n gateway: string = DEFAULT_IPFS_GATEWAY,\n): string {\n if (isIpfsUrl(url)) {\n const hash = url.replace(\"ipfs://\", \"\");\n return `${gateway}${hash}`;\n }\n return url;\n}\n\n/**\n * Extract IPFS hash from various URL formats\n *\n * **Edge Cases:**\n * - Returns null for non-IPFS URLs or malformed hashes\n * - Handles both CIDv0 (starts with Qm) and CIDv1 formats\n * - Minimum 46 characters required for standalone hash detection\n * - Gateway paths with subdirectories are not supported\n *\n * @param url - The URL to extract hash from\n * @returns The IPFS hash or null if not found\n * @example\n * ```ts\n * extractIpfsHash(\"ipfs://QmHash123\") // Returns: \"QmHash123\"\n * extractIpfsHash(\"https://gateway.pinata.cloud/ipfs/QmHash123\") // Returns: \"QmHash123\"\n * extractIpfsHash(\"QmHash123456789012345678901234567890123456\") // Returns: \"QmHash123456789012345678901234567890123456\"\n * extractIpfsHash(\"https://example.com/file.json\") // Returns: null (not IPFS)\n * extractIpfsHash(\"ipfs://QmHash/subdirectory\") // Returns: null (subdirectories not supported)\n * ```\n */\nexport function extractIpfsHash(url: string): string | null {\n // Handle various IPFS URL formats\n const patterns = [\n /ipfs\\/([a-zA-Z0-9]+)/, // https://gateway.pinata.cloud/ipfs/HASH\n /^ipfs:\\/\\/([a-zA-Z0-9]+)$/, // ipfs://HASH\n /^([a-zA-Z0-9]{46,})$/, // Just the hash (46+ chars for IPFS hashes)\n ];\n\n for (const pattern of patterns) {\n const match = url.match(pattern);\n if (match) {\n return match[1];\n }\n }\n\n return null;\n}\n\n/**\n * Get multiple gateway URLs for an IPFS hash (useful for fallback)\n *\n * @param hash - The IPFS hash\n * @returns Array of gateway URLs\n */\nexport function getGatewayUrls(hash: string): string[] {\n return IPFS_GATEWAYS.map((gateway) => `${gateway}${hash}`);\n}\n\n/**\n * Convert an IPFS URL to multiple gateway URLs for fallback\n *\n * @param url - The IPFS URL\n * @returns Array of gateway URLs or original URL if not IPFS\n */\nexport function convertIpfsUrlWithFallbacks(url: string): string[] {\n const hash = extractIpfsHash(url);\n if (hash) {\n return getGatewayUrls(hash);\n }\n return [url];\n}\n\n/**\n * Fetch content from IPFS with automatic gateway fallbacks\n *\n * **Edge Cases:**\n * - Non-IPFS URLs are fetched directly without fallback\n * - 10-second timeout per gateway attempt to prevent hanging\n * - Rate-limited gateways (429) are skipped immediately\n * - Exponential backoff between retries (1s, 2s, 3s, etc.)\n * - AbortSignal in options is merged with timeout signal\n *\n * @param url - The IPFS URL to fetch\n * @param options - Optional fetch options\n * @returns Promise resolving to Response object\n * @throws Error if all gateways fail\n */\nexport async function fetchWithFallbacks(\n url: string,\n options?: RequestInit,\n): Promise<Response> {\n const hash = extractIpfsHash(url);\n if (!hash) {\n // Not an IPFS URL, fetch directly\n return fetch(url, options);\n }\n\n const gatewayUrls = getGatewayUrls(hash);\n let lastError: Error | null = null;\n\n for (let i = 0; i < gatewayUrls.length; i++) {\n const gatewayUrl = gatewayUrls[i];\n try {\n const response = await fetch(gatewayUrl, {\n ...options,\n // Add timeout to avoid hanging on slow gateways\n signal: AbortSignal.timeout(10000), // 10 second timeout\n });\n\n // If response is ok, return it\n if (response.ok) {\n return response;\n }\n\n // If rate limited (429), try next gateway immediately\n if (response.status === 429) {\n lastError = new Error(`Gateway rate limited: ${gatewayUrl}`);\n continue;\n }\n\n // For other HTTP errors, still try next gateway\n lastError = new Error(`Gateway error ${response.status}: ${gatewayUrl}`);\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // For rate limiting or timeout errors, continue to next gateway\n if (\n lastError.message.includes(\"429\") ||\n lastError.name === \"TimeoutError\"\n ) {\n continue;\n }\n }\n\n // Add delay between retries (except for last attempt)\n if (i < gatewayUrls.length - 1) {\n await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1))); // Exponential backoff\n }\n }\n\n throw new Error(\n `All IPFS gateways failed for hash ${hash}. Last error: ${lastError?.message}`,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUO,MAAM,uBAAuB;AAK7B,MAAM,gBAAgB;AAAA,EAC3B;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;AAQO,SAAS,UAAU,KAAsB;AAC9C,SAAO,IAAI,WAAW,SAAS;AACjC;AAiBO,SAAS,eACd,KACA,UAAkB,sBACV;AACR,MAAI,UAAU,GAAG,GAAG;AAClB,UAAM,OAAO,IAAI,QAAQ,WAAW,EAAE;AACtC,WAAO,GAAG,OAAO,GAAG,IAAI;AAAA,EAC1B;AACA,SAAO;AACT;AAsBO,SAAS,gBAAgB,KAA4B;AAE1D,QAAM,WAAW;AAAA,IACf;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA;AAAA,EACF;AAEA,aAAW,WAAW,UAAU;AAC9B,UAAM,QAAQ,IAAI,MAAM,OAAO;AAC/B,QAAI,OAAO;AACT,aAAO,MAAM,CAAC;AAAA,IAChB;AAAA,EACF;AAEA,SAAO;AACT;AAQO,SAAS,eAAe,MAAwB;AACrD,SAAO,cAAc,IAAI,CAAC,YAAY,GAAG,OAAO,GAAG,IAAI,EAAE;AAC3D;AAQO,SAAS,4BAA4B,KAAuB;AACjE,QAAM,OAAO,gBAAgB,GAAG;AAChC,MAAI,MAAM;AACR,WAAO,eAAe,IAAI;AAAA,EAC5B;AACA,SAAO,CAAC,GAAG;AACb;AAiBA,eAAsB,mBACpB,KACA,SACmB;AACnB,QAAM,OAAO,gBAAgB,GAAG;AAChC,MAAI,CAAC,MAAM;AAET,WAAO,MAAM,KAAK,OAAO;AAAA,EAC3B;AAEA,QAAM,cAAc,eAAe,IAAI;AACvC,MAAI,YAA0B;AAE9B,WAAS,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;AAC3C,UAAM,aAAa,YAAY,CAAC;AAChC,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,YAAY;AAAA,QACvC,GAAG;AAAA;AAAA,QAEH,QAAQ,YAAY,QAAQ,GAAK;AAAA;AAAA,MACnC,CAAC;AAGD,UAAI,SAAS,IAAI;AACf,eAAO;AAAA,MACT;AAGA,UAAI,SAAS,WAAW,KAAK;AAC3B,oBAAY,IAAI,MAAM,yBAAyB,UAAU,EAAE;AAC3D;AAAA,MACF;AAGA,kBAAY,IAAI,MAAM,iBAAiB,SAAS,MAAM,KAAK,UAAU,EAAE;AAAA,IACzE,SAAS,OAAO;AACd,kBAAY,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAGpE,UACE,UAAU,QAAQ,SAAS,KAAK,KAChC,UAAU,SAAS,gBACnB;AACA;AAAA,MACF;AAAA,IACF;AAGA,QAAI,IAAI,YAAY,SAAS,GAAG;AAC9B,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,OAAQ,IAAI,EAAE,CAAC;AAAA,IACpE;AAAA,EACF;AAEA,QAAM,IAAI;AAAA,IACR,qCAAqC,IAAI,iBAAiB,WAAW,OAAO;AAAA,EAC9E;AACF;","names":[]}
|
package/dist/utils/ipfs.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare const DEFAULT_IPFS_GATEWAY = "https://dweb.link/ipfs/";
|
|
|
11
11
|
/**
|
|
12
12
|
* Alternative IPFS gateways for fallback - ordered by reliability and rate limits
|
|
13
13
|
*/
|
|
14
|
-
export declare const IPFS_GATEWAYS: readonly ["https://
|
|
14
|
+
export declare const IPFS_GATEWAYS: readonly ["https://ipfs.io/ipfs/", "https://dweb.link/ipfs/", "https://gateway.pinata.cloud/ipfs/", "https://ipfs.filebase.io/ipfs/"];
|
|
15
15
|
/**
|
|
16
16
|
* Check if a URL is an IPFS URL (starts with ipfs://)
|
|
17
17
|
*
|
package/dist/utils/ipfs.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
const DEFAULT_IPFS_GATEWAY = "https://dweb.link/ipfs/";
|
|
2
2
|
const IPFS_GATEWAYS = [
|
|
3
|
-
"https://dweb.link/ipfs/",
|
|
4
|
-
// Interplanetary Shipyard - highly reliable
|
|
5
3
|
"https://ipfs.io/ipfs/",
|
|
6
4
|
// IPFS Foundation - reliable
|
|
7
|
-
"https://
|
|
8
|
-
//
|
|
5
|
+
"https://dweb.link/ipfs/",
|
|
6
|
+
// Interplanetary Shipyard - observed to be having issues
|
|
9
7
|
"https://gateway.pinata.cloud/ipfs/",
|
|
10
8
|
// Pinata - backup option (has rate limits)
|
|
11
9
|
"https://ipfs.filebase.io/ipfs/"
|
package/dist/utils/ipfs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/ipfs.ts"],"sourcesContent":["/**\n * IPFS URL utilities for the Vana SDK\n *\n * Centralized functions for handling IPFS URLs, converting them to gateway URLs,\n * and extracting IPFS hashes from various URL formats.\n */\n\n/**\n * Default IPFS gateway URL\n */\nexport const DEFAULT_IPFS_GATEWAY = \"https://dweb.link/ipfs/\";\n\n/**\n * Alternative IPFS gateways for fallback - ordered by reliability and rate limits\n */\nexport const IPFS_GATEWAYS = [\n \"https://
|
|
1
|
+
{"version":3,"sources":["../../src/utils/ipfs.ts"],"sourcesContent":["/**\n * IPFS URL utilities for the Vana SDK\n *\n * Centralized functions for handling IPFS URLs, converting them to gateway URLs,\n * and extracting IPFS hashes from various URL formats.\n */\n\n/**\n * Default IPFS gateway URL\n */\nexport const DEFAULT_IPFS_GATEWAY = \"https://dweb.link/ipfs/\";\n\n/**\n * Alternative IPFS gateways for fallback - ordered by reliability and rate limits\n */\nexport const IPFS_GATEWAYS = [\n \"https://ipfs.io/ipfs/\", // IPFS Foundation - reliable\n \"https://dweb.link/ipfs/\", // Interplanetary Shipyard - observed to be having issues\n \"https://gateway.pinata.cloud/ipfs/\", // Pinata - backup option (has rate limits)\n \"https://ipfs.filebase.io/ipfs/\", // Filebase - emerging reliable option\n] as const;\n\n/**\n * Check if a URL is an IPFS URL (starts with ipfs://)\n *\n * @param url - The URL to check\n * @returns True if the URL is an IPFS URL\n */\nexport function isIpfsUrl(url: string): boolean {\n return url.startsWith(\"ipfs://\");\n}\n\n/**\n * Convert an IPFS URL to an HTTP gateway URL\n *\n * @param url - The IPFS URL to convert (e.g., \"ipfs://QmHash...\")\n * @param gateway - Optional gateway URL (defaults to DEFAULT_IPFS_GATEWAY)\n * @returns The HTTP gateway URL or original URL if not an IPFS URL\n * @example\n * ```ts\n * convertIpfsUrl(\"ipfs://QmHash123\")\n * // Returns: \"https://ipfs.io/ipfs/QmHash123\"\n *\n * convertIpfsUrl(\"ipfs://QmHash123\", \"https://gateway.pinata.cloud/ipfs/\")\n * // Returns: \"https://gateway.pinata.cloud/ipfs/QmHash123\"\n * ```\n */\nexport function convertIpfsUrl(\n url: string,\n gateway: string = DEFAULT_IPFS_GATEWAY,\n): string {\n if (isIpfsUrl(url)) {\n const hash = url.replace(\"ipfs://\", \"\");\n return `${gateway}${hash}`;\n }\n return url;\n}\n\n/**\n * Extract IPFS hash from various URL formats\n *\n * **Edge Cases:**\n * - Returns null for non-IPFS URLs or malformed hashes\n * - Handles both CIDv0 (starts with Qm) and CIDv1 formats\n * - Minimum 46 characters required for standalone hash detection\n * - Gateway paths with subdirectories are not supported\n *\n * @param url - The URL to extract hash from\n * @returns The IPFS hash or null if not found\n * @example\n * ```ts\n * extractIpfsHash(\"ipfs://QmHash123\") // Returns: \"QmHash123\"\n * extractIpfsHash(\"https://gateway.pinata.cloud/ipfs/QmHash123\") // Returns: \"QmHash123\"\n * extractIpfsHash(\"QmHash123456789012345678901234567890123456\") // Returns: \"QmHash123456789012345678901234567890123456\"\n * extractIpfsHash(\"https://example.com/file.json\") // Returns: null (not IPFS)\n * extractIpfsHash(\"ipfs://QmHash/subdirectory\") // Returns: null (subdirectories not supported)\n * ```\n */\nexport function extractIpfsHash(url: string): string | null {\n // Handle various IPFS URL formats\n const patterns = [\n /ipfs\\/([a-zA-Z0-9]+)/, // https://gateway.pinata.cloud/ipfs/HASH\n /^ipfs:\\/\\/([a-zA-Z0-9]+)$/, // ipfs://HASH\n /^([a-zA-Z0-9]{46,})$/, // Just the hash (46+ chars for IPFS hashes)\n ];\n\n for (const pattern of patterns) {\n const match = url.match(pattern);\n if (match) {\n return match[1];\n }\n }\n\n return null;\n}\n\n/**\n * Get multiple gateway URLs for an IPFS hash (useful for fallback)\n *\n * @param hash - The IPFS hash\n * @returns Array of gateway URLs\n */\nexport function getGatewayUrls(hash: string): string[] {\n return IPFS_GATEWAYS.map((gateway) => `${gateway}${hash}`);\n}\n\n/**\n * Convert an IPFS URL to multiple gateway URLs for fallback\n *\n * @param url - The IPFS URL\n * @returns Array of gateway URLs or original URL if not IPFS\n */\nexport function convertIpfsUrlWithFallbacks(url: string): string[] {\n const hash = extractIpfsHash(url);\n if (hash) {\n return getGatewayUrls(hash);\n }\n return [url];\n}\n\n/**\n * Fetch content from IPFS with automatic gateway fallbacks\n *\n * **Edge Cases:**\n * - Non-IPFS URLs are fetched directly without fallback\n * - 10-second timeout per gateway attempt to prevent hanging\n * - Rate-limited gateways (429) are skipped immediately\n * - Exponential backoff between retries (1s, 2s, 3s, etc.)\n * - AbortSignal in options is merged with timeout signal\n *\n * @param url - The IPFS URL to fetch\n * @param options - Optional fetch options\n * @returns Promise resolving to Response object\n * @throws Error if all gateways fail\n */\nexport async function fetchWithFallbacks(\n url: string,\n options?: RequestInit,\n): Promise<Response> {\n const hash = extractIpfsHash(url);\n if (!hash) {\n // Not an IPFS URL, fetch directly\n return fetch(url, options);\n }\n\n const gatewayUrls = getGatewayUrls(hash);\n let lastError: Error | null = null;\n\n for (let i = 0; i < gatewayUrls.length; i++) {\n const gatewayUrl = gatewayUrls[i];\n try {\n const response = await fetch(gatewayUrl, {\n ...options,\n // Add timeout to avoid hanging on slow gateways\n signal: AbortSignal.timeout(10000), // 10 second timeout\n });\n\n // If response is ok, return it\n if (response.ok) {\n return response;\n }\n\n // If rate limited (429), try next gateway immediately\n if (response.status === 429) {\n lastError = new Error(`Gateway rate limited: ${gatewayUrl}`);\n continue;\n }\n\n // For other HTTP errors, still try next gateway\n lastError = new Error(`Gateway error ${response.status}: ${gatewayUrl}`);\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // For rate limiting or timeout errors, continue to next gateway\n if (\n lastError.message.includes(\"429\") ||\n lastError.name === \"TimeoutError\"\n ) {\n continue;\n }\n }\n\n // Add delay between retries (except for last attempt)\n if (i < gatewayUrls.length - 1) {\n await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1))); // Exponential backoff\n }\n }\n\n throw new Error(\n `All IPFS gateways failed for hash ${hash}. Last error: ${lastError?.message}`,\n );\n}\n"],"mappings":"AAUO,MAAM,uBAAuB;AAK7B,MAAM,gBAAgB;AAAA,EAC3B;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;AAQO,SAAS,UAAU,KAAsB;AAC9C,SAAO,IAAI,WAAW,SAAS;AACjC;AAiBO,SAAS,eACd,KACA,UAAkB,sBACV;AACR,MAAI,UAAU,GAAG,GAAG;AAClB,UAAM,OAAO,IAAI,QAAQ,WAAW,EAAE;AACtC,WAAO,GAAG,OAAO,GAAG,IAAI;AAAA,EAC1B;AACA,SAAO;AACT;AAsBO,SAAS,gBAAgB,KAA4B;AAE1D,QAAM,WAAW;AAAA,IACf;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA;AAAA,EACF;AAEA,aAAW,WAAW,UAAU;AAC9B,UAAM,QAAQ,IAAI,MAAM,OAAO;AAC/B,QAAI,OAAO;AACT,aAAO,MAAM,CAAC;AAAA,IAChB;AAAA,EACF;AAEA,SAAO;AACT;AAQO,SAAS,eAAe,MAAwB;AACrD,SAAO,cAAc,IAAI,CAAC,YAAY,GAAG,OAAO,GAAG,IAAI,EAAE;AAC3D;AAQO,SAAS,4BAA4B,KAAuB;AACjE,QAAM,OAAO,gBAAgB,GAAG;AAChC,MAAI,MAAM;AACR,WAAO,eAAe,IAAI;AAAA,EAC5B;AACA,SAAO,CAAC,GAAG;AACb;AAiBA,eAAsB,mBACpB,KACA,SACmB;AACnB,QAAM,OAAO,gBAAgB,GAAG;AAChC,MAAI,CAAC,MAAM;AAET,WAAO,MAAM,KAAK,OAAO;AAAA,EAC3B;AAEA,QAAM,cAAc,eAAe,IAAI;AACvC,MAAI,YAA0B;AAE9B,WAAS,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;AAC3C,UAAM,aAAa,YAAY,CAAC;AAChC,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,YAAY;AAAA,QACvC,GAAG;AAAA;AAAA,QAEH,QAAQ,YAAY,QAAQ,GAAK;AAAA;AAAA,MACnC,CAAC;AAGD,UAAI,SAAS,IAAI;AACf,eAAO;AAAA,MACT;AAGA,UAAI,SAAS,WAAW,KAAK;AAC3B,oBAAY,IAAI,MAAM,yBAAyB,UAAU,EAAE;AAC3D;AAAA,MACF;AAGA,kBAAY,IAAI,MAAM,iBAAiB,SAAS,MAAM,KAAK,UAAU,EAAE;AAAA,IACzE,SAAS,OAAO;AACd,kBAAY,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAGpE,UACE,UAAU,QAAQ,SAAS,KAAK,KAChC,UAAU,SAAS,gBACnB;AACA;AAAA,MACF;AAAA,IACF;AAGA,QAAI,IAAI,YAAY,SAAS,GAAG;AAC9B,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,OAAQ,IAAI,EAAE,CAAC;AAAA,IACpE;AAAA,EACF;AAEA,QAAM,IAAI;AAAA,IACR,qCAAqC,IAAI,iBAAiB,WAAW,OAAO;AAAA,EAC9E;AACF;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/lazy-import.ts"],"sourcesContent":["/**\n *
|
|
1
|
+
{"version":3,"sources":["../../src/utils/lazy-import.ts"],"sourcesContent":["/**\n * Provides lazy module loading to avoid Temporal Dead Zone issues.\n *\n * @remarks\n * This module implements a caching lazy import pattern to work around Turbopack's\n * strict module initialization. Dependencies that access globals during initialization\n * must be dynamically imported to prevent TDZ errors in Next.js environments.\n *\n * @see {@link https://github.com/vercel/next.js/issues/82632 | Turbopack TDZ Issue}\n *\n * @category Utilities\n * @module utils/lazy-import\n */\n\n/**\n * Creates a cached lazy import function for deferred module loading.\n *\n * @remarks\n * Caches the import promise (not the module) to prevent race conditions\n * during concurrent first calls. On import failure, clears the cache to\n * allow retry on next attempt.\n *\n * @typeParam T - The type of the module being imported\n *\n * @param importFn - Function that returns a dynamic import promise.\n * Should be a dynamic import expression like `() => import('module')`.\n * @returns A function that returns the cached import promise\n *\n * @example\n * ```typescript\n * // Create lazy loader for heavy crypto library\n * const getOpenPGP = lazyImport(() => import('openpgp'));\n *\n * // Use when needed (first call triggers import)\n * const openpgp = await getOpenPGP();\n * const encrypted = await openpgp.encrypt(data);\n *\n * // Subsequent calls return cached promise\n * const openpgp2 = await getOpenPGP(); // Same instance\n * ```\n *\n * @category Utilities\n */\nexport function lazyImport<T>(importFn: () => Promise<T>): () => Promise<T> {\n let cached: Promise<T> | null = null;\n\n return () => {\n cached ??= importFn().catch((err) => {\n // Clear cache on error so next attempt can retry\n cached = null;\n throw new Error(\"Failed to load module\", { cause: err });\n });\n return cached;\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA2CO,SAAS,WAAc,UAA8C;AAC1E,MAAI,SAA4B;AAEhC,SAAO,MAAM;AACX,eAAW,SAAS,EAAE,MAAM,CAAC,QAAQ;AAEnC,eAAS;AACT,YAAM,IAAI,MAAM,yBAAyB,EAAE,OAAO,IAAI,CAAC;AAAA,IACzD,CAAC;AACD,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
@@ -1,18 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Provides lazy module loading to avoid Temporal Dead Zone issues.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* This module implements a caching lazy import pattern to work around Turbopack's
|
|
6
|
+
* strict module initialization. Dependencies that access globals during initialization
|
|
7
|
+
* must be dynamically imported to prevent TDZ errors in Next.js environments.
|
|
8
|
+
*
|
|
9
|
+
* @see {@link https://github.com/vercel/next.js/issues/82632 | Turbopack TDZ Issue}
|
|
10
|
+
*
|
|
11
|
+
* @category Utilities
|
|
12
|
+
* @module utils/lazy-import
|
|
6
13
|
*/
|
|
7
14
|
/**
|
|
8
|
-
* Creates a lazy import function
|
|
9
|
-
*
|
|
15
|
+
* Creates a cached lazy import function for deferred module loading.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* Caches the import promise (not the module) to prevent race conditions
|
|
19
|
+
* during concurrent first calls. On import failure, clears the cache to
|
|
20
|
+
* allow retry on next attempt.
|
|
21
|
+
*
|
|
22
|
+
* @typeParam T - The type of the module being imported
|
|
10
23
|
*
|
|
11
|
-
* @param importFn - Function that returns a dynamic import promise
|
|
12
|
-
*
|
|
24
|
+
* @param importFn - Function that returns a dynamic import promise.
|
|
25
|
+
* Should be a dynamic import expression like `() => import('module')`.
|
|
26
|
+
* @returns A function that returns the cached import promise
|
|
13
27
|
*
|
|
14
28
|
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* // Create lazy loader for heavy crypto library
|
|
15
31
|
* const getOpenPGP = lazyImport(() => import('openpgp'));
|
|
32
|
+
*
|
|
33
|
+
* // Use when needed (first call triggers import)
|
|
16
34
|
* const openpgp = await getOpenPGP();
|
|
35
|
+
* const encrypted = await openpgp.encrypt(data);
|
|
36
|
+
*
|
|
37
|
+
* // Subsequent calls return cached promise
|
|
38
|
+
* const openpgp2 = await getOpenPGP(); // Same instance
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @category Utilities
|
|
17
42
|
*/
|
|
18
43
|
export declare function lazyImport<T>(importFn: () => Promise<T>): () => Promise<T>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/lazy-import.ts"],"sourcesContent":["/**\n *
|
|
1
|
+
{"version":3,"sources":["../../src/utils/lazy-import.ts"],"sourcesContent":["/**\n * Provides lazy module loading to avoid Temporal Dead Zone issues.\n *\n * @remarks\n * This module implements a caching lazy import pattern to work around Turbopack's\n * strict module initialization. Dependencies that access globals during initialization\n * must be dynamically imported to prevent TDZ errors in Next.js environments.\n *\n * @see {@link https://github.com/vercel/next.js/issues/82632 | Turbopack TDZ Issue}\n *\n * @category Utilities\n * @module utils/lazy-import\n */\n\n/**\n * Creates a cached lazy import function for deferred module loading.\n *\n * @remarks\n * Caches the import promise (not the module) to prevent race conditions\n * during concurrent first calls. On import failure, clears the cache to\n * allow retry on next attempt.\n *\n * @typeParam T - The type of the module being imported\n *\n * @param importFn - Function that returns a dynamic import promise.\n * Should be a dynamic import expression like `() => import('module')`.\n * @returns A function that returns the cached import promise\n *\n * @example\n * ```typescript\n * // Create lazy loader for heavy crypto library\n * const getOpenPGP = lazyImport(() => import('openpgp'));\n *\n * // Use when needed (first call triggers import)\n * const openpgp = await getOpenPGP();\n * const encrypted = await openpgp.encrypt(data);\n *\n * // Subsequent calls return cached promise\n * const openpgp2 = await getOpenPGP(); // Same instance\n * ```\n *\n * @category Utilities\n */\nexport function lazyImport<T>(importFn: () => Promise<T>): () => Promise<T> {\n let cached: Promise<T> | null = null;\n\n return () => {\n cached ??= importFn().catch((err) => {\n // Clear cache on error so next attempt can retry\n cached = null;\n throw new Error(\"Failed to load module\", { cause: err });\n });\n return cached;\n };\n}\n"],"mappings":"AA2CO,SAAS,WAAc,UAA8C;AAC1E,MAAI,SAA4B;AAEhC,SAAO,MAAM;AACX,eAAW,SAAS,EAAE,MAAM,CAAC,QAAQ;AAEnC,eAAS;AACT,YAAM,IAAI,MAAM,yBAAyB,EAAE,OAAO,IAAI,CAAC;AAAA,IACzD,CAAC;AACD,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
@@ -145,12 +145,18 @@ class SignatureCache {
|
|
|
145
145
|
return (0, import_viem.toHex)(hashBytes);
|
|
146
146
|
}
|
|
147
147
|
/**
|
|
148
|
-
* Deterministic JSON replacer
|
|
149
|
-
*
|
|
148
|
+
* Deterministic JSON replacer for consistent cache key generation.
|
|
149
|
+
*
|
|
150
|
+
* @remarks
|
|
151
|
+
* Handles BigInt serialization and sorts object keys to ensure
|
|
152
|
+
* identical objects always produce the same hash regardless of
|
|
153
|
+
* property order.
|
|
150
154
|
*
|
|
151
155
|
* @param _key - The object key being serialized (unused)
|
|
152
156
|
* @param value - The value to serialize
|
|
153
157
|
* @returns The serialized value with sorted keys for objects
|
|
158
|
+
*
|
|
159
|
+
* @internal
|
|
154
160
|
*/
|
|
155
161
|
static deterministicReplacer(_key, value) {
|
|
156
162
|
if (typeof value === "bigint") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/signatureCache.ts"],"sourcesContent":["import type { Hash } from \"viem\";\nimport { getAddress, toHex } from \"viem\";\nimport type { VanaCacheAdapter } from \"../platform/interface\";\nimport { sha256 } from \"@noble/hashes/sha256\";\n\ninterface CachedSignature {\n signature: Hash;\n expires: number;\n}\n\n/**\n * Simple signature cache using platform cache adapter to avoid repeated signing of identical messages.\n *\n * @remarks\n * This cache significantly improves UX by avoiding repeated wallet signature prompts\n * for identical operations. It's particularly useful for operations that may be\n * retried or called multiple times with the same parameters.\n *\n * Features:\n * - Platform-appropriate storage (sessionStorage in browser, memory in Node.js)\n * - Configurable TTL (default 2 hours)\n * - Automatic cleanup of expired entries\n * - Cache keys based on wallet address + message hash\n *\n * @example\n * ```typescript\n * // Check cache before requesting signature\n * const cached = SignatureCache.get(cache, walletAddress, messageHash);\n * if (cached) {\n * return cached;\n * }\n *\n * // Request signature and cache it\n * const signature = await wallet.signTypedData(typedData);\n * SignatureCache.set(cache, walletAddress, messageHash, signature);\n * ```\n * @category Utilities\n */\nexport class SignatureCache {\n private static readonly PREFIX = \"vana_sig_\";\n private static readonly DEFAULT_TTL_HOURS = 2;\n\n /**\n * Get a cached signature if it exists and hasn't expired\n *\n * @param cache - Platform cache adapter instance\n * @param walletAddress - Wallet address that created the signature\n * @param messageHash - Hash of the message that was signed\n * @returns The cached signature if valid, null if expired or not found\n * @example\n * ```typescript\n * const messageHash = SignatureCache.hashMessage(typedData);\n * const cached = SignatureCache.get(cache, '0x123...', messageHash);\n * if (cached) {\n * console.log('Using cached signature:', cached);\n * }\n * ```\n */\n static get(\n cache: VanaCacheAdapter,\n walletAddress: string,\n messageHash: string,\n ): Hash | null {\n const key = this.getCacheKey(walletAddress, messageHash);\n\n try {\n const stored = cache.get(key);\n if (!stored) return null;\n\n const cached: CachedSignature = JSON.parse(stored);\n\n // Check if expired\n if (Date.now() > cached.expires) {\n cache.delete(key);\n return null;\n }\n\n return cached.signature;\n } catch {\n // Invalid JSON or storage error, clean up\n try {\n cache.delete(key);\n } catch {\n // Ignore cache cleanup errors\n }\n return null;\n }\n }\n\n /**\n * Store a signature in the cache with configurable TTL\n *\n * @param cache - Platform cache adapter instance\n * @param walletAddress - Wallet address that created the signature\n * @param messageHash - Hash of the message that was signed\n * @param signature - The signature to cache\n * @param ttlHours - Time to live in hours (default: 2)\n * @example\n * ```typescript\n * const signature = await wallet.signTypedData(typedData);\n * const messageHash = SignatureCache.hashMessage(typedData);\n *\n * // Cache for default 2 hours\n * SignatureCache.set(cache, walletAddress, messageHash, signature);\n *\n * // Cache for 24 hours\n * SignatureCache.set(cache, walletAddress, messageHash, signature, 24);\n * ```\n */\n static set(\n cache: VanaCacheAdapter,\n walletAddress: string,\n messageHash: string,\n signature: Hash,\n ttlHours: number = this.DEFAULT_TTL_HOURS,\n ): void {\n const key = this.getCacheKey(walletAddress, messageHash);\n const cached: CachedSignature = {\n signature,\n expires: Date.now() + ttlHours * 3600000, // Convert hours to milliseconds\n };\n\n try {\n cache.set(key, JSON.stringify(cached));\n } catch {\n // Storage quota exceeded or other error, ignore silently\n // Better to continue without caching than to fail\n }\n }\n\n /**\n * Clear all cached signatures (useful for testing or explicit cleanup)\n *\n * @param cache - Platform cache adapter instance\n * @example\n * ```typescript\n * // Clear all signatures when user logs out\n * SignatureCache.clear(cache);\n *\n * // Clear before running tests\n * beforeEach(() => {\n * SignatureCache.clear(cache);\n * });\n * ```\n */\n static clear(cache: VanaCacheAdapter): void {\n try {\n cache.clear();\n } catch {\n // Ignore storage errors\n }\n }\n\n private static getCacheKey(\n walletAddress: string,\n messageHash: string,\n ): string {\n return `${this.PREFIX}${getAddress(walletAddress)}:${messageHash}`;\n }\n\n /**\n * Generate a deterministic hash of a message object for cache key generation\n *\n * @remarks\n * Creates a cryptographically secure hash from complex objects including EIP-712 typed data.\n * Uses SHA-256 for collision resistance and deterministic key generation.\n * Handles BigInt serialization and sorts object keys for consistency.\n *\n * @param message - The message object to hash (typically EIP-712 typed data)\n * @returns A hex string hash (SHA-256) suitable for cache keys\n * @example\n * ```typescript\n * const typedData = {\n * domain: { name: 'Vana', version: '1' },\n * message: { nonce: 123n, grant: '...' }\n * };\n *\n * const hash = SignatureCache.hashMessage(typedData);\n * // Returns SHA-256 hash like: \"a1b2c3d4e5f6...\"\n * ```\n */\n static hashMessage(message: object): string {\n // Deterministically stringify the object with sorted keys\n const jsonString = JSON.stringify(message, this.deterministicReplacer);\n\n // Use SHA-256 for cryptographic hashing\n const hashBytes = sha256(new TextEncoder().encode(jsonString));\n return toHex(hashBytes);\n }\n\n /**\n * Deterministic JSON replacer that handles BigInt values and sorts object keys\n * This ensures consistent cache key generation for EIP-712 typed data\n *\n * @param _key - The object key being serialized (unused)\n * @param value - The value to serialize\n * @returns The serialized value with sorted keys for objects\n */\n private static deterministicReplacer(_key: string, value: unknown): unknown {\n if (typeof value === \"bigint\") {\n return `__BIGINT__${value.toString()}`;\n }\n // Sort object keys for deterministic serialization\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n return Object.keys(value as Record<string, unknown>)\n .sort()\n .reduce(\n (sorted, key) => {\n sorted[key] = (value as Record<string, unknown>)[key];\n return sorted;\n },\n {} as Record<string, unknown>,\n );\n }\n return value;\n }\n}\n\n/**\n * Wrapper function to cache signature operations\n *\n * @param cache - The cache adapter to use for storage\n * @param walletAddress - The wallet address signing the message\n * @param typedData - The EIP-712 typed data being signed\n * @param signFn - Function that performs the actual signing\n * @param ttlHours - Cache TTL in hours (default 2)\n * @returns The signature (cached or newly generated)\n */\nexport async function withSignatureCache(\n cache: VanaCacheAdapter,\n walletAddress: string,\n typedData: Record<string, unknown>,\n signFn: () => Promise<Hash>,\n ttlHours?: number,\n): Promise<Hash> {\n // Create a hash of the typed data for the cache key\n const messageHash = SignatureCache.hashMessage(typedData);\n\n // Try to get from cache first\n const cached = SignatureCache.get(cache, walletAddress, messageHash);\n if (cached) {\n return cached;\n }\n\n // Not in cache, sign and store\n const signature = await signFn();\n SignatureCache.set(cache, walletAddress, messageHash, signature, ttlHours);\n\n return signature;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/signatureCache.ts"],"sourcesContent":["/**\n * Provides signature caching to improve UX by avoiding repeated wallet prompts.\n *\n * @remarks\n * This module implements a secure signature cache that stores signed messages\n * temporarily to avoid repeatedly prompting users for the same signature.\n * It uses platform-appropriate storage (sessionStorage in browser, memory in Node.js)\n * and includes automatic expiration and cleanup.\n *\n * @category Utilities\n * @module utils/signatureCache\n */\n\nimport type { Hash } from \"viem\";\nimport { getAddress, toHex } from \"viem\";\nimport type { VanaCacheAdapter } from \"../platform/interface\";\nimport { sha256 } from \"@noble/hashes/sha256\";\n\n/**\n * Represents a cached signature with expiration metadata.\n *\n * @internal\n */\ninterface CachedSignature {\n /** The cached signature hash */\n signature: Hash;\n /** Unix timestamp when this cache entry expires */\n expires: number;\n}\n\n/**\n * Simple signature cache using platform cache adapter to avoid repeated signing of identical messages.\n *\n * @remarks\n * This cache significantly improves UX by avoiding repeated wallet signature prompts\n * for identical operations. It's particularly useful for operations that may be\n * retried or called multiple times with the same parameters.\n *\n * Features:\n * - Platform-appropriate storage (sessionStorage in browser, memory in Node.js)\n * - Configurable TTL (default 2 hours)\n * - Automatic cleanup of expired entries\n * - Cache keys based on wallet address + message hash\n *\n * @example\n * ```typescript\n * // Check cache before requesting signature\n * const cached = SignatureCache.get(cache, walletAddress, messageHash);\n * if (cached) {\n * return cached;\n * }\n *\n * // Request signature and cache it\n * const signature = await wallet.signTypedData(typedData);\n * SignatureCache.set(cache, walletAddress, messageHash, signature);\n * ```\n * @category Utilities\n */\nexport class SignatureCache {\n private static readonly PREFIX = \"vana_sig_\";\n private static readonly DEFAULT_TTL_HOURS = 2;\n\n /**\n * Get a cached signature if it exists and hasn't expired\n *\n * @param cache - Platform cache adapter instance\n * @param walletAddress - Wallet address that created the signature\n * @param messageHash - Hash of the message that was signed\n * @returns The cached signature if valid, null if expired or not found\n * @example\n * ```typescript\n * const messageHash = SignatureCache.hashMessage(typedData);\n * const cached = SignatureCache.get(cache, '0x123...', messageHash);\n * if (cached) {\n * console.log('Using cached signature:', cached);\n * }\n * ```\n */\n static get(\n cache: VanaCacheAdapter,\n walletAddress: string,\n messageHash: string,\n ): Hash | null {\n const key = this.getCacheKey(walletAddress, messageHash);\n\n try {\n const stored = cache.get(key);\n if (!stored) return null;\n\n const cached: CachedSignature = JSON.parse(stored);\n\n // Check if expired\n if (Date.now() > cached.expires) {\n cache.delete(key);\n return null;\n }\n\n return cached.signature;\n } catch {\n // Invalid JSON or storage error, clean up\n try {\n cache.delete(key);\n } catch {\n // Ignore cache cleanup errors\n }\n return null;\n }\n }\n\n /**\n * Store a signature in the cache with configurable TTL\n *\n * @param cache - Platform cache adapter instance\n * @param walletAddress - Wallet address that created the signature\n * @param messageHash - Hash of the message that was signed\n * @param signature - The signature to cache\n * @param ttlHours - Time to live in hours (default: 2)\n * @example\n * ```typescript\n * const signature = await wallet.signTypedData(typedData);\n * const messageHash = SignatureCache.hashMessage(typedData);\n *\n * // Cache for default 2 hours\n * SignatureCache.set(cache, walletAddress, messageHash, signature);\n *\n * // Cache for 24 hours\n * SignatureCache.set(cache, walletAddress, messageHash, signature, 24);\n * ```\n */\n static set(\n cache: VanaCacheAdapter,\n walletAddress: string,\n messageHash: string,\n signature: Hash,\n ttlHours: number = this.DEFAULT_TTL_HOURS,\n ): void {\n const key = this.getCacheKey(walletAddress, messageHash);\n const cached: CachedSignature = {\n signature,\n expires: Date.now() + ttlHours * 3600000, // Convert hours to milliseconds\n };\n\n try {\n cache.set(key, JSON.stringify(cached));\n } catch {\n // Storage quota exceeded or other error, ignore silently\n // Better to continue without caching than to fail\n }\n }\n\n /**\n * Clear all cached signatures (useful for testing or explicit cleanup)\n *\n * @param cache - Platform cache adapter instance\n * @example\n * ```typescript\n * // Clear all signatures when user logs out\n * SignatureCache.clear(cache);\n *\n * // Clear before running tests\n * beforeEach(() => {\n * SignatureCache.clear(cache);\n * });\n * ```\n */\n static clear(cache: VanaCacheAdapter): void {\n try {\n cache.clear();\n } catch {\n // Ignore storage errors\n }\n }\n\n private static getCacheKey(\n walletAddress: string,\n messageHash: string,\n ): string {\n return `${this.PREFIX}${getAddress(walletAddress)}:${messageHash}`;\n }\n\n /**\n * Generate a deterministic hash of a message object for cache key generation\n *\n * @remarks\n * Creates a cryptographically secure hash from complex objects including EIP-712 typed data.\n * Uses SHA-256 for collision resistance and deterministic key generation.\n * Handles BigInt serialization and sorts object keys for consistency.\n *\n * @param message - The message object to hash (typically EIP-712 typed data)\n * @returns A hex string hash (SHA-256) suitable for cache keys\n * @example\n * ```typescript\n * const typedData = {\n * domain: { name: 'Vana', version: '1' },\n * message: { nonce: 123n, grant: '...' }\n * };\n *\n * const hash = SignatureCache.hashMessage(typedData);\n * // Returns SHA-256 hash like: \"a1b2c3d4e5f6...\"\n * ```\n */\n static hashMessage(message: object): string {\n // Deterministically stringify the object with sorted keys\n const jsonString = JSON.stringify(message, this.deterministicReplacer);\n\n // Use SHA-256 for cryptographic hashing\n const hashBytes = sha256(new TextEncoder().encode(jsonString));\n return toHex(hashBytes);\n }\n\n /**\n * Deterministic JSON replacer for consistent cache key generation.\n *\n * @remarks\n * Handles BigInt serialization and sorts object keys to ensure\n * identical objects always produce the same hash regardless of\n * property order.\n *\n * @param _key - The object key being serialized (unused)\n * @param value - The value to serialize\n * @returns The serialized value with sorted keys for objects\n *\n * @internal\n */\n private static deterministicReplacer(_key: string, value: unknown): unknown {\n if (typeof value === \"bigint\") {\n return `__BIGINT__${value.toString()}`;\n }\n // Sort object keys for deterministic serialization\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n return Object.keys(value as Record<string, unknown>)\n .sort()\n .reduce(\n (sorted, key) => {\n sorted[key] = (value as Record<string, unknown>)[key];\n return sorted;\n },\n {} as Record<string, unknown>,\n );\n }\n return value;\n }\n}\n\n/**\n * Wraps signature operations with caching to avoid repeated prompts.\n *\n * @remarks\n * This helper function checks the cache before requesting a signature\n * and stores new signatures for future use. It significantly improves\n * UX for operations that may be retried or called multiple times.\n *\n * @param cache - The cache adapter to use for storage.\n * Obtain from platform adapter.\n * @param walletAddress - The wallet address signing the message.\n * Obtain from wallet connection.\n * @param typedData - The EIP-712 typed data being signed.\n * Typically permission or grant data.\n * @param signFn - Async function that performs the actual signing.\n * Usually calls wallet.signTypedData().\n * @param ttlHours - Cache TTL in hours.\n * Defaults to 2 hours.\n * @returns The signature (cached or newly generated)\n *\n * @example\n * ```typescript\n * const signature = await withSignatureCache(\n * platformAdapter.cache,\n * walletAddress,\n * typedData,\n * async () => wallet.signTypedData(typedData),\n * 24 // Cache for 24 hours\n * );\n * ```\n *\n * @category Utilities\n */\nexport async function withSignatureCache(\n cache: VanaCacheAdapter,\n walletAddress: string,\n typedData: Record<string, unknown>,\n signFn: () => Promise<Hash>,\n ttlHours?: number,\n): Promise<Hash> {\n // Create a hash of the typed data for the cache key\n const messageHash = SignatureCache.hashMessage(typedData);\n\n // Try to get from cache first\n const cached = SignatureCache.get(cache, walletAddress, messageHash);\n if (cached) {\n return cached;\n }\n\n // Not in cache, sign and store\n const signature = await signFn();\n SignatureCache.set(cache, walletAddress, messageHash, signature, ttlHours);\n\n return signature;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,kBAAkC;AAElC,oBAAuB;AA0ChB,MAAM,eAAe;AAAA,EAC1B,OAAwB,SAAS;AAAA,EACjC,OAAwB,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkB5C,OAAO,IACL,OACA,eACA,aACa;AACb,UAAM,MAAM,KAAK,YAAY,eAAe,WAAW;AAEvD,QAAI;AACF,YAAM,SAAS,MAAM,IAAI,GAAG;AAC5B,UAAI,CAAC,OAAQ,QAAO;AAEpB,YAAM,SAA0B,KAAK,MAAM,MAAM;AAGjD,UAAI,KAAK,IAAI,IAAI,OAAO,SAAS;AAC/B,cAAM,OAAO,GAAG;AAChB,eAAO;AAAA,MACT;AAEA,aAAO,OAAO;AAAA,IAChB,QAAQ;AAEN,UAAI;AACF,cAAM,OAAO,GAAG;AAAA,MAClB,QAAQ;AAAA,MAER;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBA,OAAO,IACL,OACA,eACA,aACA,WACA,WAAmB,KAAK,mBAClB;AACN,UAAM,MAAM,KAAK,YAAY,eAAe,WAAW;AACvD,UAAM,SAA0B;AAAA,MAC9B;AAAA,MACA,SAAS,KAAK,IAAI,IAAI,WAAW;AAAA;AAAA,IACnC;AAEA,QAAI;AACF,YAAM,IAAI,KAAK,KAAK,UAAU,MAAM,CAAC;AAAA,IACvC,QAAQ;AAAA,IAGR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,OAAO,MAAM,OAA+B;AAC1C,QAAI;AACF,YAAM,MAAM;AAAA,IACd,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,OAAe,YACb,eACA,aACQ;AACR,WAAO,GAAG,KAAK,MAAM,OAAG,wBAAW,aAAa,CAAC,IAAI,WAAW;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBA,OAAO,YAAY,SAAyB;AAE1C,UAAM,aAAa,KAAK,UAAU,SAAS,KAAK,qBAAqB;AAGrE,UAAM,gBAAY,sBAAO,IAAI,YAAY,EAAE,OAAO,UAAU,CAAC;AAC7D,eAAO,mBAAM,SAAS;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,OAAe,sBAAsB,MAAc,OAAyB;AAC1E,QAAI,OAAO,UAAU,UAAU;AAC7B,aAAO,aAAa,MAAM,SAAS,CAAC;AAAA,IACtC;AAEA,QAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AACxE,aAAO,OAAO,KAAK,KAAgC,EAChD,KAAK,EACL;AAAA,QACC,CAAC,QAAQ,QAAQ;AACf,iBAAO,GAAG,IAAK,MAAkC,GAAG;AACpD,iBAAO;AAAA,QACT;AAAA,QACA,CAAC;AAAA,MACH;AAAA,IACJ;AACA,WAAO;AAAA,EACT;AACF;AAmCA,eAAsB,mBACpB,OACA,eACA,WACA,QACA,UACe;AAEf,QAAM,cAAc,eAAe,YAAY,SAAS;AAGxD,QAAM,SAAS,eAAe,IAAI,OAAO,eAAe,WAAW;AACnE,MAAI,QAAQ;AACV,WAAO;AAAA,EACT;AAGA,QAAM,YAAY,MAAM,OAAO;AAC/B,iBAAe,IAAI,OAAO,eAAe,aAAa,WAAW,QAAQ;AAEzE,SAAO;AACT;","names":[]}
|