@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
package/dist/types/utils.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/types/utils.ts"],"sourcesContent":["import type { Address, Hash } from \"viem\";\n\n/**\n * Makes all properties in T optional except for those in K\n *\n * @remarks\n * This utility type is useful when you want to create a variant of an interface\n * where most properties are optional, but specific properties remain required.\n * Commonly used for update operations where only certain fields must be provided.\n *\n * @example\n * ```typescript\n * interface User {\n * id: string;\n * name: string;\n * email: string;\n * age: number;\n * }\n *\n * // Only 'id' is required, all other properties are optional\n * type UserUpdate = PartialExcept<User, 'id'>;\n *\n * const update: UserUpdate = {\n * id: '123', // Required\n * name: 'John' // Optional\n * // email and age are also optional\n * };\n * ```\n * @category Reference\n */\nexport type PartialExcept<T, K extends keyof T> = Partial<T> & Pick<T, K>;\n\n/**\n * Makes all properties in T required except for those in K\n *\n * @remarks\n * This utility type is useful when you want to create a variant of an interface\n * where most properties are required, but specific properties remain optional.\n * Commonly used for creation operations where most fields are mandatory.\n *\n * @example\n * ```typescript\n * interface Config {\n * apiUrl: string;\n * timeout?: number;\n * retries?: number;\n * debug?: boolean;\n * }\n *\n * // All properties required except 'debug'\n * type StrictConfig = RequiredExcept<Config, 'debug'>;\n *\n * const config: StrictConfig = {\n * apiUrl: 'https://api.vana.com', // Required\n * timeout: 5000, // Required (was optional, now required)\n * retries: 3, // Required (was optional, now required)\n * // debug remains optional\n * };\n * ```\n * @category Reference\n */\nexport type RequiredExcept<T, K extends keyof T> = Required<T> &\n Partial<Pick<T, K>>;\n\n/**\n * Extracts the return type of a promise\n *\n * @remarks\n * This utility type unwraps the type contained within a Promise.\n * If the type is not a Promise, it returns the type unchanged.\n * Note: TypeScript 4.5+ includes a built-in Awaited type with similar functionality.\n *\n * @example\n * ```typescript\n * type AsyncString = Promise<string>;\n * type SyncString = string;\n *\n * // Extracts 'string' from Promise<string>\n * type Result1 = Awaited<AsyncString>; // string\n *\n * // Returns 'string' unchanged since it's not a Promise\n * type Result2 = Awaited<SyncString>; // string\n *\n * // Practical usage with async functions\n * async function fetchUser(): Promise<{ id: string; name: string }> {\n * // ...\n * }\n *\n * type User = Awaited<ReturnType<typeof fetchUser>>; // { id: string; name: string }\n * ```\n * @category Reference\n */\nexport type Awaited<T> = T extends Promise<infer U> ? U : T;\n\n/**\n * Creates a type that accepts either T or a Promise<T>\n *\n * @remarks\n * This utility type is useful for functions that can work with both\n * synchronous and asynchronous values. It allows flexible APIs that\n * can accept immediate values or promises that resolve to those values.\n *\n * @example\n * ```typescript\n * // Function that accepts either a value or a promise of that value\n * async function processData(data: MaybePromise<string>): Promise<string> {\n * // await works on both promises and regular values\n * const resolved = await data;\n * return resolved.toUpperCase();\n * }\n *\n * // Both calls are valid:\n * processData('hello'); // Synchronous value\n * processData(Promise.resolve('world')); // Asynchronous value\n *\n * // Common use case in SDK callbacks\n * interface StorageProvider {\n * // Provider can implement sync or async file reading\n * read(path: string): MaybePromise<Buffer>;\n * }\n * ```\n * @category Reference\n */\nexport type MaybePromise<T> = T | Promise<T>;\n\n/**\n * Creates a type that accepts either T or an array of T\n *\n * @remarks\n * This utility type is useful for functions that can accept either a single\n * value or an array of values, providing a more flexible API. The implementation\n * can normalize the input to always work with arrays internally.\n *\n * @example\n * ```typescript\n * // Function that accepts either a single ID or multiple IDs\n * function deleteItems(ids: MaybeArray<string>): void {\n * // Normalize to array\n * const idArray = Array.isArray(ids) ? ids : [ids];\n * idArray.forEach(id => console.log(`Deleting ${id}`));\n * }\n *\n * // Both calls are valid:\n * deleteItems('item-1'); // Single item\n * deleteItems(['item-1', 'item-2', 'item-3']); // Multiple items\n *\n * // Common use case in permissions\n * interface GrantPermissionsParams {\n * permissions: MaybeArray<Permission>;\n * }\n * ```\n * @category Reference\n */\nexport type MaybeArray<T> = T | T[];\n\n/**\n * Pagination parameters for controlling result set size and navigation.\n *\n * Used across SDK methods that return lists of items to control how many items\n * are returned and to navigate through large result sets efficiently.\n *\n * @category Reference\n * @example\n * ```typescript\n * const pagination: PaginationParams = {\n * limit: 20, // Return 20 items\n * offset: 40, // Skip first 40 items (page 3)\n * cursor: 'eyJpZCI6MTIzfQ==' // Or use cursor-based pagination\n * };\n *\n * const files = await vana.data.getUserFiles({\n * owner: userAddress,\n * pagination\n * });\n * ```\n */\nexport interface PaginationParams {\n /** Maximum number of items to return */\n limit?: number;\n /** Number of items to skip */\n offset?: number;\n /** Cursor for cursor-based pagination */\n cursor?: string;\n}\n\n/**\n * Pagination result containing items and metadata for navigating large datasets.\n *\n * @remarks\n * This interface standardizes paginated responses across the SDK, providing\n * consistent metadata for implementing pagination UI components and handling\n * multi-page data fetching. Supports both offset-based and cursor-based pagination.\n *\n * @example\n * ```typescript\n * // Fetching paginated user files\n * async function getAllUserFiles(userAddress: string): Promise<File[]> {\n * const allFiles: File[] = [];\n * let cursor: string | undefined;\n *\n * do {\n * const result: PaginationResult<File> = await vana.data.getUserFiles({\n * owner: userAddress,\n * pagination: { limit: 50, cursor }\n * });\n *\n * allFiles.push(...result.items);\n * cursor = result.nextCursor;\n *\n * console.log(`Fetched ${result.count} of ${result.total} files`);\n * } while (result.hasMore);\n *\n * return allFiles;\n * }\n * ```\n * @category Reference\n */\nexport interface PaginationResult<T> {\n /** Array of items */\n items: T[];\n /** Total number of items available */\n total: number;\n /** Number of items returned */\n count: number;\n /** Whether there are more items available */\n hasMore: boolean;\n /** Cursor for next page */\n nextCursor?: string;\n}\n\n/**\n * Block range parameters for filtering blockchain events and transactions.\n *\n * @remarks\n * Used to specify a range of blocks when querying blockchain data.\n * Both parameters are optional - omitting fromBlock starts from genesis,\n * omitting toBlock goes to the latest block. Be cautious with large ranges\n * as they may result in heavy RPC loads or timeouts.\n *\n * @example\n * ```typescript\n * // Get events from the last 1000 blocks\n * const currentBlock = await vana.protocol.getBlockNumber();\n * const events = await vana.protocol.getEvents({\n * blockRange: {\n * fromBlock: currentBlock - 1000n,\n * toBlock: currentBlock\n * }\n * });\n *\n * // Get all historical events (use with caution)\n * const allEvents = await vana.protocol.getEvents({\n * blockRange: {\n * fromBlock: 0n\n * // toBlock omitted = up to latest\n * }\n * });\n * ```\n * @category Reference\n */\nexport interface BlockRange {\n /** Starting block number */\n fromBlock?: bigint;\n /** Ending block number */\n toBlock?: bigint;\n}\n\n/**\n * Transaction options for customizing blockchain transaction parameters.\n *\n * @remarks\n * Provides fine-grained control over transaction execution. Supports both\n * legacy (gasPrice) and EIP-1559 (maxFeePerGas) transaction types. When\n * not specified, the SDK will use appropriate defaults based on network\n * conditions. Use these options to optimize for speed or cost.\n *\n * @example\n * ```typescript\n * // High priority transaction with EIP-1559 pricing\n * await vana.permissions.grant(params, {\n * maxFeePerGas: 100n * 10n ** 9n, // 100 gwei\n * maxPriorityFeePerGas: 2n * 10n ** 9n, // 2 gwei tip\n * });\n *\n * // Legacy transaction with specific gas limit\n * await vana.data.registerFile(params, {\n * gasLimit: 500000n,\n * gasPrice: 50n * 10n ** 9n // 50 gwei\n * });\n *\n * // Send ETH with the transaction\n * await vana.protocol.execute(params, {\n * value: 10n ** 18n, // 1 ETH\n * gasLimit: 21000n\n * });\n * ```\n * @category Reference\n */\nexport interface TransactionOptions {\n /** Gas limit */\n gasLimit?: bigint;\n /** Gas price */\n gasPrice?: bigint;\n /** Max fee per gas (EIP-1559) */\n maxFeePerGas?: bigint;\n /** Max priority fee per gas (EIP-1559) */\n maxPriorityFeePerGas?: bigint;\n /** Nonce */\n nonce?: number;\n /** Value to send with transaction */\n value?: bigint;\n}\n\n/**\n * Transaction receipt with additional metadata for tracking transaction results.\n *\n * @remarks\n * Provides comprehensive information about executed transactions including\n * gas usage, success status, and emitted events. Use the logs array to\n * decode events emitted by smart contracts during transaction execution.\n * The receipt is available after a transaction is mined and included in a block.\n *\n * @example\n * ```typescript\n * // Execute transaction and wait for receipt\n * const receipt = await vana.permissions.grant({\n * grantee: '0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36',\n * dataId: 123\n * });\n *\n * // Check transaction success\n * if (receipt.status === 'success') {\n * console.log(`Gas used: ${receipt.gasUsed}`);\n * console.log(`Block: ${receipt.blockNumber}`);\n *\n * // Decode events from logs\n * receipt.logs.forEach(log => {\n * if (log.topics[0] === PERMISSION_GRANTED_TOPIC) {\n * console.log('Permission granted event detected');\n * }\n * });\n * } else {\n * console.error('Transaction reverted');\n * }\n * ```\n * @category Reference\n */\nexport interface TransactionReceipt {\n /** Transaction hash */\n transactionHash: Hash;\n /** Block number */\n blockNumber: bigint;\n /** Block hash */\n blockHash: Hash;\n /** Gas used */\n gasUsed: bigint;\n /** Transaction status */\n status: \"success\" | \"reverted\";\n /** Contract address if contract deployment */\n contractAddress?: Address;\n /** Event logs */\n logs: Array<{\n address: Address;\n topics: Hash[];\n data: string;\n }>;\n}\n\n/**\n * Response wrapper for API results providing consistent error handling.\n *\n * @remarks\n * Standardizes API responses across the SDK, making it easy to handle\n * both successful and failed requests. The generic type parameter T\n * represents the expected data type for successful responses. Check\n * the success flag before accessing data to ensure type safety.\n *\n * @example\n * ```typescript\n * // Handling API responses\n * async function fetchUserData(userId: string): Promise<User | null> {\n * const response: ApiResponse<User> = await api.getUser(userId);\n *\n * if (response.success) {\n * console.log('User fetched:', response.data.name);\n * // Access metadata if available\n * if (response.metadata?.cached) {\n * console.log('Data was cached');\n * }\n * return response.data;\n * } else {\n * console.error('Failed to fetch user:', response.error);\n * return null;\n * }\n * }\n *\n * // Type-safe error handling\n * interface UserData {\n * id: string;\n * name: string;\n * }\n *\n * const result: ApiResponse<UserData> = await api.call('/users/123');\n * if (result.success) {\n * // TypeScript knows result.data is UserData here\n * console.log(result.data.name);\n * }\n * ```\n * @category Reference\n */\nexport interface ApiResponse<T> {\n /** Response data */\n data: T;\n /** Success status */\n success: boolean;\n /** Error message if not successful */\n error?: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Retry configuration for handling transient failures with exponential backoff.\n *\n * @remarks\n * Configures automatic retry behavior for operations that may fail due to\n * temporary issues like network problems or rate limits. Supports exponential\n * backoff to avoid overwhelming services. The shouldRetry function allows\n * custom logic to determine which errors warrant a retry attempt.\n *\n * @example\n * ```typescript\n * // Simple retry configuration\n * const basicRetry: RetryConfig = {\n * attempts: 3,\n * delay: 1000 // 1 second between retries\n * };\n *\n * // Exponential backoff configuration\n * const exponentialRetry: RetryConfig = {\n * attempts: 5,\n * delay: 1000,\n * backoffMultiplier: 2, // Double delay each time\n * maxDelay: 30000, // Cap at 30 seconds\n * shouldRetry: (error) => {\n * // Only retry network and timeout errors\n * return error.name === 'NetworkError' ||\n * error.message.includes('timeout');\n * }\n * };\n *\n * // Usage with SDK\n * const vana = new Vana({\n * retryConfig: exponentialRetry\n * });\n * ```\n * @category Reference\n */\nexport interface RetryConfig {\n /** Number of retry attempts */\n attempts: number;\n /** Delay between retries in milliseconds */\n delay: number;\n /** Backoff multiplier */\n backoffMultiplier?: number;\n /** Maximum delay in milliseconds */\n maxDelay?: number;\n /** Function to determine if error should be retried */\n shouldRetry?: (error: Error) => boolean;\n}\n\n/**\n * Cache configuration for optimizing repeated data fetches.\n *\n * @remarks\n * Configures in-memory caching behavior to reduce redundant API calls and\n * improve performance. The cache automatically evicts expired entries based\n * on TTL and removes least-recently-used items when maxSize is reached.\n * Use appropriate TTL values based on your data freshness requirements.\n *\n * @example\n * ```typescript\n * // Short-lived cache for frequently changing data\n * const userCache: CacheConfig = {\n * ttl: 60000, // 1 minute\n * maxSize: 100, // Store up to 100 users\n * prefix: 'user:' // Cache keys like 'user:123'\n * };\n *\n * // Long-lived cache for stable data\n * const schemaCache: CacheConfig = {\n * ttl: 3600000, // 1 hour\n * maxSize: 1000, // Store up to 1000 schemas\n * prefix: 'schema:'\n * };\n *\n * // Disable caching by setting TTL to 0\n * const noCache: CacheConfig = {\n * ttl: 0 // No caching\n * };\n * ```\n * @category Reference\n */\nexport interface CacheConfig {\n /** Cache TTL in milliseconds */\n ttl: number;\n /** Maximum cache size */\n maxSize?: number;\n /** Cache key prefix */\n prefix?: string;\n}\n\n/**\n * Validation result for data integrity checks and schema validation.\n *\n * @remarks\n * Provides detailed feedback about validation outcomes, distinguishing between\n * errors (which prevent processing) and warnings (which indicate potential issues).\n * Used throughout the SDK for validating permissions, file formats, schemas,\n * and API parameters before operations.\n *\n * @example\n * ```typescript\n * // Validating user input\n * function validateFileUpload(file: File): ValidationResult {\n * const errors: string[] = [];\n * const warnings: string[] = [];\n *\n * // Check file size\n * if (file.size > 100 * 1024 * 1024) {\n * errors.push('File size exceeds 100MB limit');\n * } else if (file.size > 50 * 1024 * 1024) {\n * warnings.push('Large file may take time to upload');\n * }\n *\n * // Check file type\n * if (!file.type.startsWith('image/')) {\n * errors.push('Only image files are allowed');\n * }\n *\n * return {\n * valid: errors.length === 0,\n * errors,\n * warnings\n * };\n * }\n *\n * // Using validation result\n * const result = validateFileUpload(file);\n * if (!result.valid) {\n * console.error('Validation failed:', result.errors.join(', '));\n * } else {\n * if (result.warnings.length > 0) {\n * console.warn('Warnings:', result.warnings.join(', '));\n * }\n * // Proceed with upload\n * }\n * ```\n * @category Reference\n */\nexport interface ValidationResult {\n /** Whether validation passed */\n valid: boolean;\n /** Validation errors */\n errors: string[];\n /** Validation warnings */\n warnings: string[];\n}\n\n/**\n * Status information for health checks and service monitoring.\n *\n * @remarks\n * Used to report the health status of various SDK components including\n * storage providers, RPC connections, and personal servers. The details\n * field can contain provider-specific information for debugging.\n * Timestamps use Unix epoch milliseconds.\n *\n * @example\n * ```typescript\n * // Checking storage provider health\n * const status: StatusInfo = await storage.getStatus();\n *\n * if (!status.healthy) {\n * console.error(`Storage unhealthy: ${status.message}`);\n * // Check how long the issue has persisted\n * const downtime = Date.now() - status.lastCheck;\n * if (downtime > 300000) { // 5 minutes\n * // Switch to backup provider\n * }\n * }\n *\n * // Detailed status with custom fields\n * const serverStatus: StatusInfo = {\n * healthy: true,\n * message: 'All systems operational',\n * lastCheck: Date.now(),\n * details: {\n * uptime: 3600000, // 1 hour\n * requestsServed: 1234,\n * avgResponseTime: 145, // ms\n * activeConnections: 23\n * }\n * };\n * ```\n * @category Reference\n */\nexport interface StatusInfo {\n /** Whether the service is healthy */\n healthy: boolean;\n /** Status message */\n message?: string;\n /** Last check timestamp */\n lastCheck: number;\n /** Additional status details */\n details?: Record<string, unknown>;\n}\n\n/**\n * Rate limit information for API throttling and quota management.\n *\n * @remarks\n * Provides visibility into rate limiting status to help applications\n * implement appropriate backoff strategies. Rate limits protect services\n * from overload and ensure fair resource usage. Use this information\n * to pace requests and avoid hitting limits.\n *\n * @example\n * ```typescript\n * // Check rate limit before making requests\n * const rateLimit: RateLimitInfo = await api.getRateLimit();\n *\n * console.log(`Requests: ${rateLimit.requests}/${rateLimit.limit}`);\n * console.log(`Resets in: ${rateLimit.resetTime} seconds`);\n *\n * // Implement backoff if approaching limit\n * if (rateLimit.requests >= rateLimit.limit * 0.9) {\n * console.warn('Approaching rate limit, slowing down');\n * await new Promise(resolve =>\n * setTimeout(resolve, rateLimit.resetTime * 1000)\n * );\n * }\n *\n * // Calculate requests per second allowed\n * const rps = rateLimit.limit / rateLimit.window;\n * console.log(`Max ${rps} requests per second allowed`);\n * ```\n * @category Reference\n */\nexport interface RateLimitInfo {\n /** Current request count */\n requests: number;\n /** Maximum requests allowed */\n limit: number;\n /** Time window in seconds */\n window: number;\n /** Time until reset in seconds */\n resetTime: number;\n}\n\n/**\n * File upload progress tracking for user feedback and monitoring.\n *\n * @remarks\n * Provides real-time progress information during file uploads, enabling\n * applications to show progress bars and estimated completion times.\n * The speed calculation is typically based on a rolling average to\n * smooth out network fluctuations.\n *\n * @example\n * ```typescript\n * // Upload with progress tracking\n * await vana.data.upload(file, {\n * onProgress: (progress: UploadProgress) => {\n * // Update progress bar\n * progressBar.style.width = `${progress.percentage}%`;\n *\n * // Show upload speed\n * const speedMBps = (progress.speed / 1024 / 1024).toFixed(2);\n * speedDisplay.textContent = `${speedMBps} MB/s`;\n *\n * // Show time remaining\n * const minutes = Math.floor(progress.estimatedTimeRemaining / 60);\n * const seconds = progress.estimatedTimeRemaining % 60;\n * timeDisplay.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;\n *\n * // Log progress milestones\n * if (progress.percentage === 25 ||\n * progress.percentage === 50 ||\n * progress.percentage === 75) {\n * console.log(`Upload ${progress.percentage}% complete`);\n * }\n * }\n * });\n * ```\n * @category Reference\n */\nexport interface UploadProgress {\n /** Bytes uploaded */\n loaded: number;\n /** Total bytes to upload */\n total: number;\n /** Upload percentage (0-100) */\n percentage: number;\n /** Upload speed in bytes per second */\n speed: number;\n /** Estimated time remaining in seconds */\n estimatedTimeRemaining: number;\n}\n\n/**\n * Network information for blockchain connectivity and monitoring.\n *\n * @remarks\n * Provides comprehensive details about the connected blockchain network,\n * including configuration URLs and real-time status. Use this information\n * to verify network connectivity, display network details to users, and\n * construct explorer links for transactions.\n *\n * @example\n * ```typescript\n * // Get current network info\n * const network: NetworkInfo = await vana.protocol.getNetworkInfo();\n *\n * console.log(`Connected to: ${network.chainName} (ID: ${network.chainId})`);\n * console.log(`Current block: ${network.currentBlock}`);\n *\n * // Check network health\n * if (network.status !== 'healthy') {\n * console.warn(`Network ${network.status}: consider switching RPC`);\n * }\n *\n * // Generate explorer link for transaction\n * function getExplorerLink(txHash: string): string {\n * if (!network.explorerUrl) {\n * return `Transaction: ${txHash}`;\n * }\n * return `${network.explorerUrl}/tx/${txHash}`;\n * }\n *\n * // Verify expected network\n * const EXPECTED_CHAIN_ID = 1337; // Vana testnet\n * if (network.chainId !== EXPECTED_CHAIN_ID) {\n * throw new Error(`Wrong network! Expected ${EXPECTED_CHAIN_ID}, got ${network.chainId}`);\n * }\n * ```\n * @category Reference\n */\nexport interface NetworkInfo {\n /** Chain ID */\n chainId: number;\n /** Chain name */\n chainName: string;\n /** RPC URL */\n rpcUrl: string;\n /** Block explorer URL */\n explorerUrl?: string;\n /** Current block number */\n currentBlock: bigint;\n /** Network status */\n status: \"healthy\" | \"degraded\" | \"down\";\n}\n\n/**\n * Gas estimate information for transaction cost prediction.\n *\n * @remarks\n * Provides detailed gas pricing estimates to help users understand transaction\n * costs before execution. Supports both legacy (gasPrice) and EIP-1559\n * (maxFeePerGas) pricing models. The estimatedCost is calculated based on\n * current network conditions and may vary by the time of actual execution.\n *\n * @example\n * ```typescript\n * // Get gas estimate before transaction\n * const estimate: GasEstimate = await vana.permissions.estimateGas({\n * grantee: '0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36',\n * dataId: 123\n * });\n *\n * // Convert to human-readable format\n * const costInEth = Number(estimate.estimatedCost) / 1e18;\n * console.log(`Estimated cost: ${costInEth.toFixed(6)} ETH`);\n *\n * // Check if user has sufficient balance\n * const balance = await vana.protocol.getBalance(userAddress);\n * if (balance < estimate.estimatedCost) {\n * throw new Error(`Insufficient balance. Need ${costInEth} ETH`);\n * }\n *\n * // Use estimate for transaction with buffer\n * await vana.permissions.grant(params, {\n * gasLimit: estimate.gasLimit * 110n / 100n, // 10% buffer\n * maxFeePerGas: estimate.maxFeePerGas,\n * maxPriorityFeePerGas: estimate.maxPriorityFeePerGas\n * });\n * ```\n * @category Reference\n */\nexport interface GasEstimate {\n /** Gas limit */\n gasLimit: bigint;\n /** Gas price */\n gasPrice: bigint;\n /** Max fee per gas */\n maxFeePerGas?: bigint;\n /** Max priority fee per gas */\n maxPriorityFeePerGas?: bigint;\n /** Estimated cost in wei */\n estimatedCost: bigint;\n}\n\n/**\n * Time range parameters for filtering operations by time period.\n *\n * Used in various SDK methods to specify date/time ranges for queries,\n * analytics, and data filtering operations. Times are specified as Unix timestamps.\n *\n * @category Reference\n * @example\n * ```typescript\n * const lastWeek: TimeRange = {\n * from: Date.now() - (7 * 24 * 60 * 60 * 1000), // 7 days ago\n * to: Date.now()\n * };\n *\n * const permissions = await vana.permissions.getUserPermissions({\n * timeRange: lastWeek\n * });\n * ```\n */\nexport interface TimeRange {\n /** Start time (Unix timestamp) */\n from?: number;\n /** End time (Unix timestamp) */\n to?: number;\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/types/utils.ts"],"sourcesContent":["import type { Address, Hash } from \"viem\";\n\n/**\n * Makes all properties in T optional except for those in K\n *\n * @remarks\n * This utility type is useful when you want to create a variant of an interface\n * where most properties are optional, but specific properties remain required.\n * Commonly used for update operations where only certain fields must be provided.\n *\n * @example\n * ```typescript\n * interface User {\n * id: string;\n * name: string;\n * email: string;\n * age: number;\n * }\n *\n * // Only 'id' is required, all other properties are optional\n * type UserUpdate = PartialExcept<User, 'id'>;\n *\n * const update: UserUpdate = {\n * id: '123', // Required\n * name: 'John' // Optional\n * // email and age are also optional\n * };\n * ```\n * @category Reference\n */\nexport type PartialExcept<T, K extends keyof T> = Partial<T> & Pick<T, K>;\n\n/**\n * Makes all properties in T required except for those in K\n *\n * @remarks\n * This utility type is useful when you want to create a variant of an interface\n * where most properties are required, but specific properties remain optional.\n * Commonly used for creation operations where most fields are mandatory.\n *\n * @example\n * ```typescript\n * interface Config {\n * apiUrl: string;\n * timeout?: number;\n * retries?: number;\n * debug?: boolean;\n * }\n *\n * // All properties required except 'debug'\n * type StrictConfig = RequiredExcept<Config, 'debug'>;\n *\n * const config: StrictConfig = {\n * apiUrl: 'https://api.vana.com', // Required\n * timeout: 5000, // Required (was optional, now required)\n * retries: 3, // Required (was optional, now required)\n * // debug remains optional\n * };\n * ```\n * @category Reference\n */\nexport type RequiredExcept<T, K extends keyof T> = Required<T> &\n Partial<Pick<T, K>>;\n\n/**\n * Extracts the return type of a promise\n *\n * @remarks\n * This utility type unwraps the type contained within a Promise.\n * If the type is not a Promise, it returns the type unchanged.\n * Note: TypeScript 4.5+ includes a built-in Awaited type with similar functionality.\n *\n * @example\n * ```typescript\n * type AsyncString = Promise<string>;\n * type SyncString = string;\n *\n * // Extracts 'string' from Promise<string>\n * type Result1 = Awaited<AsyncString>; // string\n *\n * // Returns 'string' unchanged since it's not a Promise\n * type Result2 = Awaited<SyncString>; // string\n *\n * // Practical usage with async functions\n * async function fetchUser(): Promise<{ id: string; name: string }> {\n * // ...\n * }\n *\n * type User = Awaited<ReturnType<typeof fetchUser>>; // { id: string; name: string }\n * ```\n * @category Reference\n */\nexport type Awaited<T> = T extends Promise<infer U> ? U : T;\n\n/**\n * Creates a type that accepts either T or a Promise<T>\n *\n * @remarks\n * This utility type is useful for functions that can work with both\n * synchronous and asynchronous values. It allows flexible APIs that\n * can accept immediate values or promises that resolve to those values.\n *\n * @example\n * ```typescript\n * // Function that accepts either a value or a promise of that value\n * async function processData(data: MaybePromise<string>): Promise<string> {\n * // await works on both promises and regular values\n * const resolved = await data;\n * return resolved.toUpperCase();\n * }\n *\n * // Both calls are valid:\n * processData('hello'); // Synchronous value\n * processData(Promise.resolve('world')); // Asynchronous value\n *\n * // Common use case in SDK callbacks\n * interface StorageProvider {\n * // Provider can implement sync or async file reading\n * read(path: string): MaybePromise<Buffer>;\n * }\n * ```\n * @category Reference\n */\nexport type MaybePromise<T> = T | Promise<T>;\n\n/**\n * Creates a type that accepts either T or an array of T\n *\n * @remarks\n * This utility type is useful for functions that can accept either a single\n * value or an array of values, providing a more flexible API. The implementation\n * can normalize the input to always work with arrays internally.\n *\n * @example\n * ```typescript\n * // Function that accepts either a single ID or multiple IDs\n * function deleteItems(ids: MaybeArray<string>): void {\n * // Normalize to array\n * const idArray = Array.isArray(ids) ? ids : [ids];\n * idArray.forEach(id => console.log(`Deleting ${id}`));\n * }\n *\n * // Both calls are valid:\n * deleteItems('item-1'); // Single item\n * deleteItems(['item-1', 'item-2', 'item-3']); // Multiple items\n *\n * // Common use case in permissions\n * interface GrantPermissionsParams {\n * permissions: MaybeArray<Permission>;\n * }\n * ```\n * @category Reference\n */\nexport type MaybeArray<T> = T | T[];\n\n/**\n * Pagination parameters for controlling result set size and navigation.\n *\n * Used across SDK methods that return lists of items to control how many items\n * are returned and to navigate through large result sets efficiently.\n *\n * @category Reference\n * @example\n * ```typescript\n * const pagination: PaginationParams = {\n * limit: 20, // Return 20 items\n * offset: 40, // Skip first 40 items (page 3)\n * cursor: 'eyJpZCI6MTIzfQ==' // Or use cursor-based pagination\n * };\n *\n * const files = await vana.data.getUserFiles({\n * owner: userAddress,\n * pagination\n * });\n * ```\n */\nexport interface PaginationParams {\n /** Maximum number of items to return */\n limit?: number;\n /** Number of items to skip */\n offset?: number;\n /** Cursor for cursor-based pagination */\n cursor?: string;\n}\n\n/**\n * Pagination result containing items and metadata for navigating large datasets.\n *\n * @remarks\n * This interface standardizes paginated responses across the SDK, providing\n * consistent metadata for implementing pagination UI components and handling\n * multi-page data fetching. Supports both offset-based and cursor-based pagination.\n *\n * @example\n * ```typescript\n * // Fetching paginated user files\n * async function getAllUserFiles(userAddress: string): Promise<File[]> {\n * const allFiles: File[] = [];\n * let cursor: string | undefined;\n *\n * do {\n * const result: PaginationResult<File> = await vana.data.getUserFiles({\n * owner: userAddress,\n * pagination: { limit: 50, cursor }\n * });\n *\n * allFiles.push(...result.items);\n * cursor = result.nextCursor;\n *\n * console.log(`Fetched ${result.count} of ${result.total} files`);\n * } while (result.hasMore);\n *\n * return allFiles;\n * }\n * ```\n * @category Reference\n */\nexport interface PaginationResult<T> {\n /** Array of items */\n items: T[];\n /** Total number of items available */\n total: number;\n /** Number of items returned */\n count: number;\n /** Whether there are more items available */\n hasMore: boolean;\n /** Cursor for next page */\n nextCursor?: string;\n}\n\n/**\n * Block range parameters for filtering blockchain events and transactions.\n *\n * @remarks\n * Used to specify a range of blocks when querying blockchain data.\n * Both parameters are optional - omitting fromBlock starts from genesis,\n * omitting toBlock goes to the latest block. Be cautious with large ranges\n * as they may result in heavy RPC loads or timeouts.\n *\n * @example\n * ```typescript\n * // Get events from the last 1000 blocks\n * const currentBlock = await vana.protocol.getBlockNumber();\n * const events = await vana.protocol.getEvents({\n * blockRange: {\n * fromBlock: currentBlock - 1000n,\n * toBlock: currentBlock\n * }\n * });\n *\n * // Get all historical events (use with caution)\n * const allEvents = await vana.protocol.getEvents({\n * blockRange: {\n * fromBlock: 0n\n * // toBlock omitted = up to latest\n * }\n * });\n * ```\n * @category Reference\n */\nexport interface BlockRange {\n /** Starting block number */\n fromBlock?: bigint;\n /** Ending block number */\n toBlock?: bigint;\n}\n\n/**\n * Transaction options for customizing blockchain transaction parameters.\n *\n * @remarks\n * Provides fine-grained control over transaction execution. Supports both\n * legacy (gasPrice) and EIP-1559 (maxFeePerGas) transaction types. When\n * not specified, the SDK will use appropriate defaults based on network\n * conditions. Use these options to optimize for speed or cost.\n *\n * @example\n * ```typescript\n * // High priority transaction with EIP-1559 pricing\n * await vana.permissions.grant(params, {\n * maxFeePerGas: 100n * 10n ** 9n, // 100 gwei\n * maxPriorityFeePerGas: 2n * 10n ** 9n, // 2 gwei tip\n * timeout: 180000, // 3 minutes\n * });\n *\n * // Legacy transaction with specific gas limit\n * await vana.data.registerFile(params, {\n * gasLimit: 500000n,\n * gasPrice: 50n * 10n ** 9n, // 50 gwei\n * timeout: 600000, // 10 minutes\n * });\n *\n * // Send ETH with the transaction\n * await vana.protocol.execute(params, {\n * value: 10n ** 18n, // 1 ETH\n * gasLimit: 21000n\n * });\n * ```\n * @category Reference\n */\nexport interface TransactionOptions {\n /** Gas limit */\n gasLimit?: bigint;\n /** Gas price */\n gasPrice?: bigint;\n /** Max fee per gas (EIP-1559) */\n maxFeePerGas?: bigint;\n /** Max priority fee per gas (EIP-1559) */\n maxPriorityFeePerGas?: bigint;\n /** Nonce */\n nonce?: number;\n /** Value to send with transaction */\n value?: bigint;\n /** Transaction timeout in milliseconds for receipt waiting (default: 30000) */\n timeout?: number;\n}\n\n/**\n * Transaction receipt with additional metadata for tracking transaction results.\n *\n * @remarks\n * Provides comprehensive information about executed transactions including\n * gas usage, success status, and emitted events. Use the logs array to\n * decode events emitted by smart contracts during transaction execution.\n * The receipt is available after a transaction is mined and included in a block.\n *\n * @example\n * ```typescript\n * // Execute transaction and wait for receipt\n * const receipt = await vana.permissions.grant({\n * grantee: '0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36',\n * dataId: 123\n * });\n *\n * // Check transaction success\n * if (receipt.status === 'success') {\n * console.log(`Gas used: ${receipt.gasUsed}`);\n * console.log(`Block: ${receipt.blockNumber}`);\n *\n * // Decode events from logs\n * receipt.logs.forEach(log => {\n * if (log.topics[0] === PERMISSION_GRANTED_TOPIC) {\n * console.log('Permission granted event detected');\n * }\n * });\n * } else {\n * console.error('Transaction reverted');\n * }\n * ```\n * @category Reference\n */\nexport interface TransactionReceipt {\n /** Transaction hash */\n transactionHash: Hash;\n /** Block number */\n blockNumber: bigint;\n /** Block hash */\n blockHash: Hash;\n /** Gas used */\n gasUsed: bigint;\n /** Transaction status */\n status: \"success\" | \"reverted\";\n /** Contract address if contract deployment */\n contractAddress?: Address;\n /** Event logs */\n logs: Array<{\n address: Address;\n topics: Hash[];\n data: string;\n }>;\n}\n\n/**\n * Response wrapper for API results providing consistent error handling.\n *\n * @remarks\n * Standardizes API responses across the SDK, making it easy to handle\n * both successful and failed requests. The generic type parameter T\n * represents the expected data type for successful responses. Check\n * the success flag before accessing data to ensure type safety.\n *\n * @example\n * ```typescript\n * // Handling API responses\n * async function fetchUserData(userId: string): Promise<User | null> {\n * const response: ApiResponse<User> = await api.getUser(userId);\n *\n * if (response.success) {\n * console.log('User fetched:', response.data.name);\n * // Access metadata if available\n * if (response.metadata?.cached) {\n * console.log('Data was cached');\n * }\n * return response.data;\n * } else {\n * console.error('Failed to fetch user:', response.error);\n * return null;\n * }\n * }\n *\n * // Type-safe error handling\n * interface UserData {\n * id: string;\n * name: string;\n * }\n *\n * const result: ApiResponse<UserData> = await api.call('/users/123');\n * if (result.success) {\n * // TypeScript knows result.data is UserData here\n * console.log(result.data.name);\n * }\n * ```\n * @category Reference\n */\nexport interface ApiResponse<T> {\n /** Response data */\n data: T;\n /** Success status */\n success: boolean;\n /** Error message if not successful */\n error?: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Retry configuration for handling transient failures with exponential backoff.\n *\n * @remarks\n * Configures automatic retry behavior for operations that may fail due to\n * temporary issues like network problems or rate limits. Supports exponential\n * backoff to avoid overwhelming services. The shouldRetry function allows\n * custom logic to determine which errors warrant a retry attempt.\n *\n * @example\n * ```typescript\n * // Simple retry configuration\n * const basicRetry: RetryConfig = {\n * attempts: 3,\n * delay: 1000 // 1 second between retries\n * };\n *\n * // Exponential backoff configuration\n * const exponentialRetry: RetryConfig = {\n * attempts: 5,\n * delay: 1000,\n * backoffMultiplier: 2, // Double delay each time\n * maxDelay: 30000, // Cap at 30 seconds\n * shouldRetry: (error) => {\n * // Only retry network and timeout errors\n * return error.name === 'NetworkError' ||\n * error.message.includes('timeout');\n * }\n * };\n *\n * // Usage with SDK\n * const vana = new Vana({\n * retryConfig: exponentialRetry\n * });\n * ```\n * @category Reference\n */\nexport interface RetryConfig {\n /** Number of retry attempts */\n attempts: number;\n /** Delay between retries in milliseconds */\n delay: number;\n /** Backoff multiplier */\n backoffMultiplier?: number;\n /** Maximum delay in milliseconds */\n maxDelay?: number;\n /** Function to determine if error should be retried */\n shouldRetry?: (error: Error) => boolean;\n}\n\n/**\n * Cache configuration for optimizing repeated data fetches.\n *\n * @remarks\n * Configures in-memory caching behavior to reduce redundant API calls and\n * improve performance. The cache automatically evicts expired entries based\n * on TTL and removes least-recently-used items when maxSize is reached.\n * Use appropriate TTL values based on your data freshness requirements.\n *\n * @example\n * ```typescript\n * // Short-lived cache for frequently changing data\n * const userCache: CacheConfig = {\n * ttl: 60000, // 1 minute\n * maxSize: 100, // Store up to 100 users\n * prefix: 'user:' // Cache keys like 'user:123'\n * };\n *\n * // Long-lived cache for stable data\n * const schemaCache: CacheConfig = {\n * ttl: 3600000, // 1 hour\n * maxSize: 1000, // Store up to 1000 schemas\n * prefix: 'schema:'\n * };\n *\n * // Disable caching by setting TTL to 0\n * const noCache: CacheConfig = {\n * ttl: 0 // No caching\n * };\n * ```\n * @category Reference\n */\nexport interface CacheConfig {\n /** Cache TTL in milliseconds */\n ttl: number;\n /** Maximum cache size */\n maxSize?: number;\n /** Cache key prefix */\n prefix?: string;\n}\n\n/**\n * Validation result for data integrity checks and schema validation.\n *\n * @remarks\n * Provides detailed feedback about validation outcomes, distinguishing between\n * errors (which prevent processing) and warnings (which indicate potential issues).\n * Used throughout the SDK for validating permissions, file formats, schemas,\n * and API parameters before operations.\n *\n * @example\n * ```typescript\n * // Validating user input\n * function validateFileUpload(file: File): ValidationResult {\n * const errors: string[] = [];\n * const warnings: string[] = [];\n *\n * // Check file size\n * if (file.size > 100 * 1024 * 1024) {\n * errors.push('File size exceeds 100MB limit');\n * } else if (file.size > 50 * 1024 * 1024) {\n * warnings.push('Large file may take time to upload');\n * }\n *\n * // Check file type\n * if (!file.type.startsWith('image/')) {\n * errors.push('Only image files are allowed');\n * }\n *\n * return {\n * valid: errors.length === 0,\n * errors,\n * warnings\n * };\n * }\n *\n * // Using validation result\n * const result = validateFileUpload(file);\n * if (!result.valid) {\n * console.error('Validation failed:', result.errors.join(', '));\n * } else {\n * if (result.warnings.length > 0) {\n * console.warn('Warnings:', result.warnings.join(', '));\n * }\n * // Proceed with upload\n * }\n * ```\n * @category Reference\n */\nexport interface ValidationResult {\n /** Whether validation passed */\n valid: boolean;\n /** Validation errors */\n errors: string[];\n /** Validation warnings */\n warnings: string[];\n}\n\n/**\n * Status information for health checks and service monitoring.\n *\n * @remarks\n * Used to report the health status of various SDK components including\n * storage providers, RPC connections, and personal servers. The details\n * field can contain provider-specific information for debugging.\n * Timestamps use Unix epoch milliseconds.\n *\n * @example\n * ```typescript\n * // Checking storage provider health\n * const status: StatusInfo = await storage.getStatus();\n *\n * if (!status.healthy) {\n * console.error(`Storage unhealthy: ${status.message}`);\n * // Check how long the issue has persisted\n * const downtime = Date.now() - status.lastCheck;\n * if (downtime > 300000) { // 5 minutes\n * // Switch to backup provider\n * }\n * }\n *\n * // Detailed status with custom fields\n * const serverStatus: StatusInfo = {\n * healthy: true,\n * message: 'All systems operational',\n * lastCheck: Date.now(),\n * details: {\n * uptime: 3600000, // 1 hour\n * requestsServed: 1234,\n * avgResponseTime: 145, // ms\n * activeConnections: 23\n * }\n * };\n * ```\n * @category Reference\n */\nexport interface StatusInfo {\n /** Whether the service is healthy */\n healthy: boolean;\n /** Status message */\n message?: string;\n /** Last check timestamp */\n lastCheck: number;\n /** Additional status details */\n details?: Record<string, unknown>;\n}\n\n/**\n * Rate limit information for API throttling and quota management.\n *\n * @remarks\n * Provides visibility into rate limiting status to help applications\n * implement appropriate backoff strategies. Rate limits protect services\n * from overload and ensure fair resource usage. Use this information\n * to pace requests and avoid hitting limits.\n *\n * @example\n * ```typescript\n * // Check rate limit before making requests\n * const rateLimit: RateLimitInfo = await api.getRateLimit();\n *\n * console.log(`Requests: ${rateLimit.requests}/${rateLimit.limit}`);\n * console.log(`Resets in: ${rateLimit.resetTime} seconds`);\n *\n * // Implement backoff if approaching limit\n * if (rateLimit.requests >= rateLimit.limit * 0.9) {\n * console.warn('Approaching rate limit, slowing down');\n * await new Promise(resolve =>\n * setTimeout(resolve, rateLimit.resetTime * 1000)\n * );\n * }\n *\n * // Calculate requests per second allowed\n * const rps = rateLimit.limit / rateLimit.window;\n * console.log(`Max ${rps} requests per second allowed`);\n * ```\n * @category Reference\n */\nexport interface RateLimitInfo {\n /** Current request count */\n requests: number;\n /** Maximum requests allowed */\n limit: number;\n /** Time window in seconds */\n window: number;\n /** Time until reset in seconds */\n resetTime: number;\n}\n\n/**\n * File upload progress tracking for user feedback and monitoring.\n *\n * @remarks\n * Provides real-time progress information during file uploads, enabling\n * applications to show progress bars and estimated completion times.\n * The speed calculation is typically based on a rolling average to\n * smooth out network fluctuations.\n *\n * @example\n * ```typescript\n * // Upload with progress tracking\n * await vana.data.upload(file, {\n * onProgress: (progress: UploadProgress) => {\n * // Update progress bar\n * progressBar.style.width = `${progress.percentage}%`;\n *\n * // Show upload speed\n * const speedMBps = (progress.speed / 1024 / 1024).toFixed(2);\n * speedDisplay.textContent = `${speedMBps} MB/s`;\n *\n * // Show time remaining\n * const minutes = Math.floor(progress.estimatedTimeRemaining / 60);\n * const seconds = progress.estimatedTimeRemaining % 60;\n * timeDisplay.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;\n *\n * // Log progress milestones\n * if (progress.percentage === 25 ||\n * progress.percentage === 50 ||\n * progress.percentage === 75) {\n * console.log(`Upload ${progress.percentage}% complete`);\n * }\n * }\n * });\n * ```\n * @category Reference\n */\nexport interface UploadProgress {\n /** Bytes uploaded */\n loaded: number;\n /** Total bytes to upload */\n total: number;\n /** Upload percentage (0-100) */\n percentage: number;\n /** Upload speed in bytes per second */\n speed: number;\n /** Estimated time remaining in seconds */\n estimatedTimeRemaining: number;\n}\n\n/**\n * Network information for blockchain connectivity and monitoring.\n *\n * @remarks\n * Provides comprehensive details about the connected blockchain network,\n * including configuration URLs and real-time status. Use this information\n * to verify network connectivity, display network details to users, and\n * construct explorer links for transactions.\n *\n * @example\n * ```typescript\n * // Get current network info\n * const network: NetworkInfo = await vana.protocol.getNetworkInfo();\n *\n * console.log(`Connected to: ${network.chainName} (ID: ${network.chainId})`);\n * console.log(`Current block: ${network.currentBlock}`);\n *\n * // Check network health\n * if (network.status !== 'healthy') {\n * console.warn(`Network ${network.status}: consider switching RPC`);\n * }\n *\n * // Generate explorer link for transaction\n * function getExplorerLink(txHash: string): string {\n * if (!network.explorerUrl) {\n * return `Transaction: ${txHash}`;\n * }\n * return `${network.explorerUrl}/tx/${txHash}`;\n * }\n *\n * // Verify expected network\n * const EXPECTED_CHAIN_ID = 1337; // Vana testnet\n * if (network.chainId !== EXPECTED_CHAIN_ID) {\n * throw new Error(`Wrong network! Expected ${EXPECTED_CHAIN_ID}, got ${network.chainId}`);\n * }\n * ```\n * @category Reference\n */\nexport interface NetworkInfo {\n /** Chain ID */\n chainId: number;\n /** Chain name */\n chainName: string;\n /** RPC URL */\n rpcUrl: string;\n /** Block explorer URL */\n explorerUrl?: string;\n /** Current block number */\n currentBlock: bigint;\n /** Network status */\n status: \"healthy\" | \"degraded\" | \"down\";\n}\n\n/**\n * Gas estimate information for transaction cost prediction.\n *\n * @remarks\n * Provides detailed gas pricing estimates to help users understand transaction\n * costs before execution. Supports both legacy (gasPrice) and EIP-1559\n * (maxFeePerGas) pricing models. The estimatedCost is calculated based on\n * current network conditions and may vary by the time of actual execution.\n *\n * @example\n * ```typescript\n * // Get gas estimate before transaction\n * const estimate: GasEstimate = await vana.permissions.estimateGas({\n * grantee: '0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36',\n * dataId: 123\n * });\n *\n * // Convert to human-readable format\n * const costInEth = Number(estimate.estimatedCost) / 1e18;\n * console.log(`Estimated cost: ${costInEth.toFixed(6)} ETH`);\n *\n * // Check if user has sufficient balance\n * const balance = await vana.protocol.getBalance(userAddress);\n * if (balance < estimate.estimatedCost) {\n * throw new Error(`Insufficient balance. Need ${costInEth} ETH`);\n * }\n *\n * // Use estimate for transaction with buffer\n * await vana.permissions.grant(params, {\n * gasLimit: estimate.gasLimit * 110n / 100n, // 10% buffer\n * maxFeePerGas: estimate.maxFeePerGas,\n * maxPriorityFeePerGas: estimate.maxPriorityFeePerGas\n * });\n * ```\n * @category Reference\n */\nexport interface GasEstimate {\n /** Gas limit */\n gasLimit: bigint;\n /** Gas price */\n gasPrice: bigint;\n /** Max fee per gas */\n maxFeePerGas?: bigint;\n /** Max priority fee per gas */\n maxPriorityFeePerGas?: bigint;\n /** Estimated cost in wei */\n estimatedCost: bigint;\n}\n\n/**\n * Time range parameters for filtering operations by time period.\n *\n * Used in various SDK methods to specify date/time ranges for queries,\n * analytics, and data filtering operations. Times are specified as Unix timestamps.\n *\n * @category Reference\n * @example\n * ```typescript\n * const lastWeek: TimeRange = {\n * from: Date.now() - (7 * 24 * 60 * 60 * 1000), // 7 days ago\n * to: Date.now()\n * };\n *\n * const permissions = await vana.permissions.getUserPermissions({\n * timeRange: lastWeek\n * });\n * ```\n */\nexport interface TimeRange {\n /** Start time (Unix timestamp) */\n from?: number;\n /** End time (Unix timestamp) */\n to?: number;\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
package/dist/types/utils.d.ts
CHANGED
|
@@ -270,12 +270,14 @@ export interface BlockRange {
|
|
|
270
270
|
* await vana.permissions.grant(params, {
|
|
271
271
|
* maxFeePerGas: 100n * 10n ** 9n, // 100 gwei
|
|
272
272
|
* maxPriorityFeePerGas: 2n * 10n ** 9n, // 2 gwei tip
|
|
273
|
+
* timeout: 180000, // 3 minutes
|
|
273
274
|
* });
|
|
274
275
|
*
|
|
275
276
|
* // Legacy transaction with specific gas limit
|
|
276
277
|
* await vana.data.registerFile(params, {
|
|
277
278
|
* gasLimit: 500000n,
|
|
278
|
-
* gasPrice: 50n * 10n ** 9n // 50 gwei
|
|
279
|
+
* gasPrice: 50n * 10n ** 9n, // 50 gwei
|
|
280
|
+
* timeout: 600000, // 10 minutes
|
|
279
281
|
* });
|
|
280
282
|
*
|
|
281
283
|
* // Send ETH with the transaction
|
|
@@ -299,6 +301,8 @@ export interface TransactionOptions {
|
|
|
299
301
|
nonce?: number;
|
|
300
302
|
/** Value to send with transaction */
|
|
301
303
|
value?: bigint;
|
|
304
|
+
/** Transaction timeout in milliseconds for receipt waiting (default: 30000) */
|
|
305
|
+
timeout?: number;
|
|
302
306
|
}
|
|
303
307
|
/**
|
|
304
308
|
* Transaction receipt with additional metadata for tracking transaction results.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/grantFiles.ts"],"sourcesContent":["import { keccak256, toHex } from \"viem\";\nimport type { GrantFile, GrantPermissionParams } from \"../types/permissions\";\nimport { SerializationError, NetworkError } from \"../errors\";\n\ninterface GrantFileStorageResponse {\n success: boolean;\n error?: string;\n url?: string;\n}\n\n/**\n * Creates a grant file structure from permission parameters.\n *\n * @remarks\n * This function constructs the JSON structure that represents a permission grant\n * in the Vana protocol. The grant file contains all necessary information for\n * a grantee to perform operations on behalf of the grantor.\n *\n * @param params - The permission parameters to create the grant file from\n * @returns The constructed grant file object\n * @example\n * ```typescript\n * const grantFile = createGrantFile({\n * grantee: '0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36',\n * operation: 'llm_inference',\n * parameters: {\n * model: 'gpt-4',\n * maxTokens: 1000\n * },\n * expiresAt: Date.now() + 86400000 // 24 hours\n * });\n *\n * console.log(grantFile);\n * // {\n * // grantee: '0x742d...',\n * // operation: 'llm_inference',\n * // parameters: { model: 'gpt-4', maxTokens: 1000 },\n * // expires: 1234567890\n * // }\n * ```\n */\nexport function createGrantFile(params: GrantPermissionParams): GrantFile {\n const grantFile: GrantFile = {\n grantee: params.grantee,\n operation: params.operation,\n parameters: params.parameters,\n };\n\n // Add expiration if provided\n if (params.expiresAt) {\n grantFile.expires = params.expiresAt;\n }\n\n return grantFile;\n}\n\n/**\n * Stores a grant file in IPFS via the relayer service.\n *\n * @remarks\n * This function uploads the grant file to IPFS through the relayer's upload endpoint.\n * The returned URL can be stored on-chain as part of the permission grant, allowing\n * anyone to retrieve the detailed permission parameters later.\n *\n * @param grantFile - The grant file to store\n * @param relayerUrl - URL of the relayer service\n * @returns Promise resolving to the IPFS URL\n * @throws {NetworkError} When the upload fails or relayer is unavailable\n * @example\n * ```typescript\n * const grantFile = createGrantFile(params);\n *\n * try {\n * const ipfsUrl = await storeGrantFile(grantFile, 'https://relayer.vana.com');\n * console.log(`Grant file stored at: ${ipfsUrl}`);\n * // ipfsUrl: \"ipfs://QmHash123...\"\n * } catch (error) {\n * console.error('Failed to store grant file:', error);\n * }\n * ```\n */\nexport async function storeGrantFile(\n grantFile: GrantFile,\n relayerUrl: string,\n): Promise<string> {\n try {\n // Convert grant file to blob and use IPFS upload endpoint\n const grantFileBlob = new Blob([JSON.stringify(grantFile, null, 2)], {\n type: \"application/json\",\n });\n\n const formData = new FormData();\n formData.append(\"file\", grantFileBlob, \"grant-file.json\");\n\n const response = await fetch(`${relayerUrl}/api/ipfs/upload`, {\n method: \"POST\",\n body: formData,\n });\n\n if (!response.ok) {\n throw new NetworkError(\n `Failed to store grant file: ${response.statusText}`,\n new Error(`HTTP ${response.status}`),\n );\n }\n\n const responseData: unknown = await response.json();\n const data = responseData as GrantFileStorageResponse;\n\n if (!data.success) {\n throw new NetworkError(data.error ?? \"Failed to store grant file\");\n }\n\n if (!data.url) {\n throw new NetworkError(\"Upload succeeded but no URL was returned\");\n }\n return data.url; // The IPFS URL from the upload response\n } catch (error) {\n if (error instanceof NetworkError) {\n throw error;\n }\n throw new NetworkError(\n `Network error while storing grant file: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n error as Error,\n );\n }\n}\n\n/**\n * Retrieves detailed grant file data from IPFS or HTTP storage.\n *\n * @remarks\n * **This is Step 2 of the performant two-step permission API.**\n *\n * Use this method to resolve detailed permission data (operation, parameters, etc.)\n * for specific grants after first getting the fast on-chain data using\n * `getUserPermissionGrantsOnChain()`. This design eliminates N+1 query problems\n * by allowing selective lazy-loading of expensive off-chain data.\n *\n * **Performance**: Single network request per grant file (typically 100-500ms).\n * **Reliability**: Tries multiple IPFS gateways as fallbacks if primary URL fails.\n *\n * @param grantUrl - The grant file URL from OnChainPermissionGrant.grantUrl\n * @param _relayerUrl - URL of the relayer service (optional, unused)\n * @param downloadRelayer - Optional download relayer for proxying CORS-restricted downloads\n * @param downloadRelayer.proxyDownload - Function to proxy download requests through application server\n * @returns Promise resolving to the complete grant file with operation details\n * @throws {NetworkError} When all retrieval attempts fail\n * @throws {SerializationError} When grant file format is invalid\n * @example\n * ```typescript\n * // Step 1: Fast on-chain data (no N+1 queries)\n * const grants = await vana.permissions.getUserPermissionGrantsOnChain();\n *\n * // Step 2: Lazy-load details for specific grant when needed\n * const grantFile = await retrieveGrantFile(grants[0].grantUrl);\n *\n * console.log(`Operation: ${grantFile.operation}`);\n * console.log(`Grantee: ${grantFile.grantee}`);\n * console.log(`Parameters:`, grantFile.parameters);\n *\n * // Only fetch details for grants user actually wants to see\n * for (const grant of selectedGrants) {\n * const details = await retrieveGrantFile(grant.grantUrl);\n * displayGrantDetails(details);\n * }\n * ```\n */\nexport async function retrieveGrantFile(\n grantUrl: string,\n _relayerUrl?: string,\n downloadRelayer?: { proxyDownload: (url: string) => Promise<Blob> },\n): Promise<GrantFile> {\n try {\n // Check if the URL is a gateway URL instead of ipfs:// protocol\n if (grantUrl.startsWith(\"http\") && grantUrl.includes(\"/ipfs/\")) {\n console.warn(\n `⚠️ Grant URL uses HTTP gateway format instead of ipfs:// protocol. ` +\n `Found: ${grantUrl}. ` +\n `Consider using ipfs:// format for better protocol-agnostic storage.`,\n );\n }\n\n // Use the unified download utility\n const { universalFetch } = await import(\"./download\");\n const response = await universalFetch(grantUrl, downloadRelayer);\n\n if (!response.ok) {\n throw new NetworkError(\n `Failed to retrieve grant file: HTTP ${response.status}`,\n );\n }\n\n const text = await response.text();\n const grantFile = JSON.parse(text);\n\n if (!validateGrantFile(grantFile)) {\n throw new NetworkError(`Invalid grant file format from ${grantUrl}`);\n }\n\n return grantFile;\n } catch (error) {\n if (error instanceof NetworkError) {\n throw error;\n }\n throw new NetworkError(\n `Error retrieving grant file: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n error as Error,\n );\n }\n}\n\n/**\n * Generates a content hash for a grant file.\n * This can be used for integrity verification.\n *\n * @remarks\n * Creates a deterministic keccak256 hash of the grant file by first sorting\n * all object keys recursively to ensure consistent hashing regardless of\n * property order. This hash can be used to verify grant file integrity\n * or as a unique identifier.\n *\n * @param grantFile - The grant file to generate a hash for\n * @returns The keccak256 hash of the grant file as a hex string\n * @throws {SerializationError} When the grant file cannot be serialized\n * @example\n * ```typescript\n * const grantFile = {\n * grantee: '0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36',\n * operation: 'read',\n * parameters: { version: '1.0', mode: 'full' }\n * };\n *\n * const hash = getGrantFileHash(grantFile);\n * console.log(`Grant file hash: ${hash}`);\n * // \"0x1234567890abcdef...\"\n *\n * // Same grant file with different property order produces same hash\n * const grantFile2 = {\n * operation: 'read',\n * parameters: { mode: 'full', version: '1.0' },\n * grantee: '0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36'\n * };\n *\n * const hash2 = getGrantFileHash(grantFile2);\n * console.log(hash === hash2); // true\n * ```\n */\nexport function getGrantFileHash(grantFile: GrantFile): string {\n try {\n // Create a stable JSON representation\n const sortedFile: GrantFile = {\n grantee: grantFile.grantee,\n operation: grantFile.operation,\n parameters: sortObjectKeys(grantFile.parameters) as Record<\n string,\n unknown\n >,\n };\n\n // Add expires if present\n if (grantFile.expires !== undefined) {\n sortedFile.expires = grantFile.expires;\n }\n\n const jsonString = JSON.stringify(sortedFile);\n console.info(`Hash: ${keccak256(toHex(jsonString))}`);\n return keccak256(toHex(jsonString));\n } catch (error) {\n throw new SerializationError(\n `Failed to generate grant file hash: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n}\n\n/**\n * Recursively sorts object keys for stable serialization.\n *\n * @param obj - The object to sort keys recursively\n * @returns The object with all keys sorted recursively\n */\nfunction sortObjectKeys(obj: unknown): unknown {\n if (obj === null || typeof obj !== \"object\") {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => sortObjectKeys(item));\n }\n\n const sortedObj: Record<string, unknown> = {};\n Object.keys(obj as Record<string, unknown>)\n .sort()\n .forEach((key) => {\n sortedObj[key] = sortObjectKeys((obj as Record<string, unknown>)[key]);\n });\n\n return sortedObj;\n}\n\n/**\n * Validates that a grant file has the required structure.\n *\n * @remarks\n * Performs runtime validation to ensure data conforms to the GrantFile interface.\n * Checks for required fields (grantee, operation, parameters) and validates their\n * types and formats. This is a type guard function that enables TypeScript to\n * narrow the type when it returns true.\n *\n * @param data - The data to validate as a grant file\n * @returns True if the data is a valid grant file, false otherwise\n * @example\n * ```typescript\n * const unknownData = await fetch(url).then(r => r.json());\n *\n * if (validateGrantFile(unknownData)) {\n * // TypeScript now knows unknownData is a GrantFile\n * console.log(`Grant for operation: ${unknownData.operation}`);\n * console.log(`Grantee: ${unknownData.grantee}`);\n * } else {\n * throw new Error('Invalid grant file format');\n * }\n *\n * // Validation examples:\n * validateGrantFile({\n * grantee: '0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36',\n * operation: 'read',\n * parameters: {}\n * }); // true\n *\n * validateGrantFile({\n * grantee: 'invalid-address',\n * operation: 'read',\n * parameters: {}\n * }); // false (invalid address format)\n *\n * validateGrantFile({\n * operation: 'read',\n * parameters: {}\n * }); // false (missing grantee)\n * ```\n */\nexport function validateGrantFile(data: unknown): data is GrantFile {\n if (!data || typeof data !== \"object\") {\n return false;\n }\n\n const obj = data as Record<string, unknown>;\n\n // Validate required fields\n // Validate grantee address\n if (\n typeof obj.grantee !== \"string\" ||\n !obj.grantee.match(/^0x[a-fA-F0-9]{40}$/)\n ) {\n return false;\n }\n\n if (typeof obj.operation !== \"string\" || obj.operation.length === 0) {\n return false;\n }\n\n // Files are no longer stored in grant files - they're tracked in the contract\n\n if (!obj.parameters || typeof obj.parameters !== \"object\") {\n return false;\n }\n\n // Validate optional expires field\n if (obj.expires !== undefined) {\n if (\n typeof obj.expires !== \"number\" ||\n obj.expires < 0 ||\n !Number.isInteger(obj.expires)\n ) {\n return false;\n }\n }\n\n return true;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/grantFiles.ts"],"sourcesContent":["import { keccak256, toHex } from \"viem\";\nimport type { GrantFile, GrantPermissionParams } from \"../types/permissions\";\nimport { SerializationError, NetworkError } from \"../errors\";\n\ninterface GrantFileStorageResponse {\n success: boolean;\n error?: string;\n url?: string;\n}\n\n/**\n * Creates grant file structure for permission storage.\n *\n * @remarks\n * Constructs JSON structure that represents a permission grant\n * in the Vana protocol. The grant file contains all necessary information\n * for a grantee to perform operations on behalf of the grantor.\n *\n * @param params - Permission parameters to create the grant file from\n * @returns Grant file object for IPFS storage\n *\n * @example\n * ```typescript\n * const grant = createGrantFile({\n * grantee: '0x742d35Cc...',\n * operation: 'llm_inference',\n * parameters: { model: 'gpt-4' },\n * expiresAt: Date.now() + 86400000 // 24 hours\n * });\n * ```\n */\nexport function createGrantFile(params: GrantPermissionParams): GrantFile {\n const grantFile: GrantFile = {\n grantee: params.grantee,\n operation: params.operation,\n parameters: params.parameters,\n };\n\n // Add expiration if provided\n if (params.expiresAt) {\n grantFile.expires = params.expiresAt;\n }\n\n return grantFile;\n}\n\n/**\n * Stores a grant file in IPFS via the relayer service.\n *\n * @remarks\n * This function uploads the grant file to IPFS through the relayer's upload endpoint.\n * The returned URL can be stored on-chain as part of the permission grant, allowing\n * anyone to retrieve the detailed permission parameters later.\n *\n * @param grantFile - The grant file to store\n * @param relayerUrl - URL of the relayer service\n * @returns Promise resolving to the IPFS URL\n * @throws {NetworkError} When the upload fails or relayer is unavailable\n * @example\n * ```typescript\n * const grantFile = createGrantFile(params);\n *\n * try {\n * const ipfsUrl = await storeGrantFile(grantFile, 'https://relayer.vana.com');\n * console.log(`Grant file stored at: ${ipfsUrl}`);\n * // ipfsUrl: \"ipfs://QmHash123...\"\n * } catch (error) {\n * console.error('Failed to store grant file:', error);\n * }\n * ```\n */\nexport async function storeGrantFile(\n grantFile: GrantFile,\n relayerUrl: string,\n): Promise<string> {\n try {\n // Convert grant file to blob and use IPFS upload endpoint\n const grantFileBlob = new Blob([JSON.stringify(grantFile, null, 2)], {\n type: \"application/json\",\n });\n\n const formData = new FormData();\n formData.append(\"file\", grantFileBlob, \"grant-file.json\");\n\n const response = await fetch(`${relayerUrl}/api/ipfs/upload`, {\n method: \"POST\",\n body: formData,\n });\n\n if (!response.ok) {\n throw new NetworkError(\n `Failed to store grant file: ${response.statusText}`,\n new Error(`HTTP ${response.status}`),\n );\n }\n\n const responseData: unknown = await response.json();\n const data = responseData as GrantFileStorageResponse;\n\n if (!data.success) {\n throw new NetworkError(data.error ?? \"Failed to store grant file\");\n }\n\n if (!data.url) {\n throw new NetworkError(\"Upload succeeded but no URL was returned\");\n }\n return data.url; // The IPFS URL from the upload response\n } catch (error) {\n if (error instanceof NetworkError) {\n throw error;\n }\n throw new NetworkError(\n `Network error while storing grant file: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n error as Error,\n );\n }\n}\n\n/**\n * Retrieves detailed grant file data from IPFS or HTTP storage.\n *\n * @remarks\n * **This is Step 2 of the performant two-step permission API.**\n *\n * Use this method to resolve detailed permission data (operation, parameters, etc.)\n * for specific grants after first getting the fast on-chain data using\n * `getUserPermissionGrantsOnChain()`. This design eliminates N+1 query problems\n * by allowing selective lazy-loading of expensive off-chain data.\n *\n * **Performance**: Single network request per grant file (typically 100-500ms).\n * **Reliability**: Tries multiple IPFS gateways as fallbacks if primary URL fails.\n *\n * @param grantUrl - The grant file URL from OnChainPermissionGrant.grantUrl\n * @param _relayerUrl - URL of the relayer service (optional, unused)\n * @param downloadRelayer - Optional download relayer for proxying CORS-restricted downloads\n * @param downloadRelayer.proxyDownload - Function to proxy download requests through application server\n * @returns Promise resolving to the complete grant file with operation details\n * @throws {NetworkError} When all retrieval attempts fail\n * @throws {SerializationError} When grant file format is invalid\n * @example\n * ```typescript\n * // Step 1: Fast on-chain data (no N+1 queries)\n * const grants = await vana.permissions.getUserPermissionGrantsOnChain();\n *\n * // Step 2: Lazy-load details for specific grant when needed\n * const grantFile = await retrieveGrantFile(grants[0].grantUrl);\n *\n * console.log(`Operation: ${grantFile.operation}`);\n * console.log(`Grantee: ${grantFile.grantee}`);\n * console.log(`Parameters:`, grantFile.parameters);\n *\n * // Only fetch details for grants user actually wants to see\n * for (const grant of selectedGrants) {\n * const details = await retrieveGrantFile(grant.grantUrl);\n * displayGrantDetails(details);\n * }\n * ```\n */\nexport async function retrieveGrantFile(\n grantUrl: string,\n _relayerUrl?: string,\n downloadRelayer?: { proxyDownload: (url: string) => Promise<Blob> },\n): Promise<GrantFile> {\n try {\n // Check if the URL is a gateway URL instead of ipfs:// protocol\n if (grantUrl.startsWith(\"http\") && grantUrl.includes(\"/ipfs/\")) {\n console.warn(\n `⚠️ Grant URL uses HTTP gateway format instead of ipfs:// protocol. ` +\n `Found: ${grantUrl}. ` +\n `Consider using ipfs:// format for better protocol-agnostic storage.`,\n );\n }\n\n // Use the unified download utility\n const { universalFetch } = await import(\"./download\");\n const response = await universalFetch(grantUrl, downloadRelayer);\n\n if (!response.ok) {\n throw new NetworkError(\n `Failed to retrieve grant file: HTTP ${response.status}`,\n );\n }\n\n const text = await response.text();\n const grantFile = JSON.parse(text);\n\n if (!validateGrantFile(grantFile)) {\n throw new NetworkError(`Invalid grant file format from ${grantUrl}`);\n }\n\n return grantFile;\n } catch (error) {\n if (error instanceof NetworkError) {\n throw error;\n }\n throw new NetworkError(\n `Error retrieving grant file: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n error as Error,\n );\n }\n}\n\n/**\n * Generates a content hash for a grant file.\n * This can be used for integrity verification.\n *\n * @remarks\n * Creates a deterministic keccak256 hash of the grant file by first sorting\n * all object keys recursively to ensure consistent hashing regardless of\n * property order. This hash can be used to verify grant file integrity\n * or as a unique identifier.\n *\n * @param grantFile - The grant file to generate a hash for\n * @returns The keccak256 hash of the grant file as a hex string\n * @throws {SerializationError} When the grant file cannot be serialized\n * @example\n * ```typescript\n * const grantFile = {\n * grantee: '0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36',\n * operation: 'read',\n * parameters: { version: '1.0', mode: 'full' }\n * };\n *\n * const hash = getGrantFileHash(grantFile);\n * console.log(`Grant file hash: ${hash}`);\n * // \"0x1234567890abcdef...\"\n *\n * // Same grant file with different property order produces same hash\n * const grantFile2 = {\n * operation: 'read',\n * parameters: { mode: 'full', version: '1.0' },\n * grantee: '0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36'\n * };\n *\n * const hash2 = getGrantFileHash(grantFile2);\n * console.log(hash === hash2); // true\n * ```\n */\nexport function getGrantFileHash(grantFile: GrantFile): string {\n try {\n // Create a stable JSON representation\n const sortedFile: GrantFile = {\n grantee: grantFile.grantee,\n operation: grantFile.operation,\n parameters: sortObjectKeys(grantFile.parameters) as Record<\n string,\n unknown\n >,\n };\n\n // Add expires if present\n if (grantFile.expires !== undefined) {\n sortedFile.expires = grantFile.expires;\n }\n\n const jsonString = JSON.stringify(sortedFile);\n console.info(`Hash: ${keccak256(toHex(jsonString))}`);\n return keccak256(toHex(jsonString));\n } catch (error) {\n throw new SerializationError(\n `Failed to generate grant file hash: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n}\n\n/**\n * Recursively sorts object keys for stable serialization.\n *\n * @param obj - The object to sort keys recursively\n * @returns The object with all keys sorted recursively\n */\nfunction sortObjectKeys(obj: unknown): unknown {\n if (obj === null || typeof obj !== \"object\") {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => sortObjectKeys(item));\n }\n\n const sortedObj: Record<string, unknown> = {};\n Object.keys(obj as Record<string, unknown>)\n .sort()\n .forEach((key) => {\n sortedObj[key] = sortObjectKeys((obj as Record<string, unknown>)[key]);\n });\n\n return sortedObj;\n}\n\n/**\n * Validates that a grant file has the required structure.\n *\n * @remarks\n * Performs runtime validation to ensure data conforms to the GrantFile interface.\n * Checks for required fields (grantee, operation, parameters) and validates their\n * types and formats. This is a type guard function that enables TypeScript to\n * narrow the type when it returns true.\n *\n * @param data - The data to validate as a grant file\n * @returns True if the data is a valid grant file, false otherwise\n * @example\n * ```typescript\n * const unknownData = await fetch(url).then(r => r.json());\n *\n * if (validateGrantFile(unknownData)) {\n * // TypeScript now knows unknownData is a GrantFile\n * console.log(`Grant for operation: ${unknownData.operation}`);\n * console.log(`Grantee: ${unknownData.grantee}`);\n * } else {\n * throw new Error('Invalid grant file format');\n * }\n *\n * // Validation examples:\n * validateGrantFile({\n * grantee: '0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36',\n * operation: 'read',\n * parameters: {}\n * }); // true\n *\n * validateGrantFile({\n * grantee: 'invalid-address',\n * operation: 'read',\n * parameters: {}\n * }); // false (invalid address format)\n *\n * validateGrantFile({\n * operation: 'read',\n * parameters: {}\n * }); // false (missing grantee)\n * ```\n */\nexport function validateGrantFile(data: unknown): data is GrantFile {\n if (!data || typeof data !== \"object\") {\n return false;\n }\n\n const obj = data as Record<string, unknown>;\n\n // Validate required fields\n // Validate grantee address\n if (\n typeof obj.grantee !== \"string\" ||\n !obj.grantee.match(/^0x[a-fA-F0-9]{40}$/)\n ) {\n return false;\n }\n\n if (typeof obj.operation !== \"string\" || obj.operation.length === 0) {\n return false;\n }\n\n // Files are no longer stored in grant files - they're tracked in the contract\n\n if (!obj.parameters || typeof obj.parameters !== \"object\") {\n return false;\n }\n\n // Validate optional expires field\n if (obj.expires !== undefined) {\n if (\n typeof obj.expires !== \"number\" ||\n obj.expires < 0 ||\n !Number.isInteger(obj.expires)\n ) {\n return false;\n }\n }\n\n return true;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,33 +1,23 @@
|
|
|
1
1
|
import type { GrantFile, GrantPermissionParams } from "../types/permissions";
|
|
2
2
|
/**
|
|
3
|
-
* Creates
|
|
3
|
+
* Creates grant file structure for permission storage.
|
|
4
4
|
*
|
|
5
5
|
* @remarks
|
|
6
|
-
*
|
|
7
|
-
* in the Vana protocol. The grant file contains all necessary information
|
|
8
|
-
* a grantee to perform operations on behalf of the grantor.
|
|
6
|
+
* Constructs JSON structure that represents a permission grant
|
|
7
|
+
* in the Vana protocol. The grant file contains all necessary information
|
|
8
|
+
* for a grantee to perform operations on behalf of the grantor.
|
|
9
|
+
*
|
|
10
|
+
* @param params - Permission parameters to create the grant file from
|
|
11
|
+
* @returns Grant file object for IPFS storage
|
|
9
12
|
*
|
|
10
|
-
* @param params - The permission parameters to create the grant file from
|
|
11
|
-
* @returns The constructed grant file object
|
|
12
13
|
* @example
|
|
13
14
|
* ```typescript
|
|
14
|
-
* const
|
|
15
|
-
* grantee: '
|
|
15
|
+
* const grant = createGrantFile({
|
|
16
|
+
* grantee: '0x742d35Cc...',
|
|
16
17
|
* operation: 'llm_inference',
|
|
17
|
-
* parameters: {
|
|
18
|
-
* model: 'gpt-4',
|
|
19
|
-
* maxTokens: 1000
|
|
20
|
-
* },
|
|
18
|
+
* parameters: { model: 'gpt-4' },
|
|
21
19
|
* expiresAt: Date.now() + 86400000 // 24 hours
|
|
22
20
|
* });
|
|
23
|
-
*
|
|
24
|
-
* console.log(grantFile);
|
|
25
|
-
* // {
|
|
26
|
-
* // grantee: '0x742d...',
|
|
27
|
-
* // operation: 'llm_inference',
|
|
28
|
-
* // parameters: { model: 'gpt-4', maxTokens: 1000 },
|
|
29
|
-
* // expires: 1234567890
|
|
30
|
-
* // }
|
|
31
21
|
* ```
|
|
32
22
|
*/
|
|
33
23
|
export declare function createGrantFile(params: GrantPermissionParams): GrantFile;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/grantFiles.ts"],"sourcesContent":["import { keccak256, toHex } from \"viem\";\nimport type { GrantFile, GrantPermissionParams } from \"../types/permissions\";\nimport { SerializationError, NetworkError } from \"../errors\";\n\ninterface GrantFileStorageResponse {\n success: boolean;\n error?: string;\n url?: string;\n}\n\n/**\n * Creates a grant file structure from permission parameters.\n *\n * @remarks\n * This function constructs the JSON structure that represents a permission grant\n * in the Vana protocol. The grant file contains all necessary information for\n * a grantee to perform operations on behalf of the grantor.\n *\n * @param params - The permission parameters to create the grant file from\n * @returns The constructed grant file object\n * @example\n * ```typescript\n * const grantFile = createGrantFile({\n * grantee: '0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36',\n * operation: 'llm_inference',\n * parameters: {\n * model: 'gpt-4',\n * maxTokens: 1000\n * },\n * expiresAt: Date.now() + 86400000 // 24 hours\n * });\n *\n * console.log(grantFile);\n * // {\n * // grantee: '0x742d...',\n * // operation: 'llm_inference',\n * // parameters: { model: 'gpt-4', maxTokens: 1000 },\n * // expires: 1234567890\n * // }\n * ```\n */\nexport function createGrantFile(params: GrantPermissionParams): GrantFile {\n const grantFile: GrantFile = {\n grantee: params.grantee,\n operation: params.operation,\n parameters: params.parameters,\n };\n\n // Add expiration if provided\n if (params.expiresAt) {\n grantFile.expires = params.expiresAt;\n }\n\n return grantFile;\n}\n\n/**\n * Stores a grant file in IPFS via the relayer service.\n *\n * @remarks\n * This function uploads the grant file to IPFS through the relayer's upload endpoint.\n * The returned URL can be stored on-chain as part of the permission grant, allowing\n * anyone to retrieve the detailed permission parameters later.\n *\n * @param grantFile - The grant file to store\n * @param relayerUrl - URL of the relayer service\n * @returns Promise resolving to the IPFS URL\n * @throws {NetworkError} When the upload fails or relayer is unavailable\n * @example\n * ```typescript\n * const grantFile = createGrantFile(params);\n *\n * try {\n * const ipfsUrl = await storeGrantFile(grantFile, 'https://relayer.vana.com');\n * console.log(`Grant file stored at: ${ipfsUrl}`);\n * // ipfsUrl: \"ipfs://QmHash123...\"\n * } catch (error) {\n * console.error('Failed to store grant file:', error);\n * }\n * ```\n */\nexport async function storeGrantFile(\n grantFile: GrantFile,\n relayerUrl: string,\n): Promise<string> {\n try {\n // Convert grant file to blob and use IPFS upload endpoint\n const grantFileBlob = new Blob([JSON.stringify(grantFile, null, 2)], {\n type: \"application/json\",\n });\n\n const formData = new FormData();\n formData.append(\"file\", grantFileBlob, \"grant-file.json\");\n\n const response = await fetch(`${relayerUrl}/api/ipfs/upload`, {\n method: \"POST\",\n body: formData,\n });\n\n if (!response.ok) {\n throw new NetworkError(\n `Failed to store grant file: ${response.statusText}`,\n new Error(`HTTP ${response.status}`),\n );\n }\n\n const responseData: unknown = await response.json();\n const data = responseData as GrantFileStorageResponse;\n\n if (!data.success) {\n throw new NetworkError(data.error ?? \"Failed to store grant file\");\n }\n\n if (!data.url) {\n throw new NetworkError(\"Upload succeeded but no URL was returned\");\n }\n return data.url; // The IPFS URL from the upload response\n } catch (error) {\n if (error instanceof NetworkError) {\n throw error;\n }\n throw new NetworkError(\n `Network error while storing grant file: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n error as Error,\n );\n }\n}\n\n/**\n * Retrieves detailed grant file data from IPFS or HTTP storage.\n *\n * @remarks\n * **This is Step 2 of the performant two-step permission API.**\n *\n * Use this method to resolve detailed permission data (operation, parameters, etc.)\n * for specific grants after first getting the fast on-chain data using\n * `getUserPermissionGrantsOnChain()`. This design eliminates N+1 query problems\n * by allowing selective lazy-loading of expensive off-chain data.\n *\n * **Performance**: Single network request per grant file (typically 100-500ms).\n * **Reliability**: Tries multiple IPFS gateways as fallbacks if primary URL fails.\n *\n * @param grantUrl - The grant file URL from OnChainPermissionGrant.grantUrl\n * @param _relayerUrl - URL of the relayer service (optional, unused)\n * @param downloadRelayer - Optional download relayer for proxying CORS-restricted downloads\n * @param downloadRelayer.proxyDownload - Function to proxy download requests through application server\n * @returns Promise resolving to the complete grant file with operation details\n * @throws {NetworkError} When all retrieval attempts fail\n * @throws {SerializationError} When grant file format is invalid\n * @example\n * ```typescript\n * // Step 1: Fast on-chain data (no N+1 queries)\n * const grants = await vana.permissions.getUserPermissionGrantsOnChain();\n *\n * // Step 2: Lazy-load details for specific grant when needed\n * const grantFile = await retrieveGrantFile(grants[0].grantUrl);\n *\n * console.log(`Operation: ${grantFile.operation}`);\n * console.log(`Grantee: ${grantFile.grantee}`);\n * console.log(`Parameters:`, grantFile.parameters);\n *\n * // Only fetch details for grants user actually wants to see\n * for (const grant of selectedGrants) {\n * const details = await retrieveGrantFile(grant.grantUrl);\n * displayGrantDetails(details);\n * }\n * ```\n */\nexport async function retrieveGrantFile(\n grantUrl: string,\n _relayerUrl?: string,\n downloadRelayer?: { proxyDownload: (url: string) => Promise<Blob> },\n): Promise<GrantFile> {\n try {\n // Check if the URL is a gateway URL instead of ipfs:// protocol\n if (grantUrl.startsWith(\"http\") && grantUrl.includes(\"/ipfs/\")) {\n console.warn(\n `⚠️ Grant URL uses HTTP gateway format instead of ipfs:// protocol. ` +\n `Found: ${grantUrl}. ` +\n `Consider using ipfs:// format for better protocol-agnostic storage.`,\n );\n }\n\n // Use the unified download utility\n const { universalFetch } = await import(\"./download\");\n const response = await universalFetch(grantUrl, downloadRelayer);\n\n if (!response.ok) {\n throw new NetworkError(\n `Failed to retrieve grant file: HTTP ${response.status}`,\n );\n }\n\n const text = await response.text();\n const grantFile = JSON.parse(text);\n\n if (!validateGrantFile(grantFile)) {\n throw new NetworkError(`Invalid grant file format from ${grantUrl}`);\n }\n\n return grantFile;\n } catch (error) {\n if (error instanceof NetworkError) {\n throw error;\n }\n throw new NetworkError(\n `Error retrieving grant file: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n error as Error,\n );\n }\n}\n\n/**\n * Generates a content hash for a grant file.\n * This can be used for integrity verification.\n *\n * @remarks\n * Creates a deterministic keccak256 hash of the grant file by first sorting\n * all object keys recursively to ensure consistent hashing regardless of\n * property order. This hash can be used to verify grant file integrity\n * or as a unique identifier.\n *\n * @param grantFile - The grant file to generate a hash for\n * @returns The keccak256 hash of the grant file as a hex string\n * @throws {SerializationError} When the grant file cannot be serialized\n * @example\n * ```typescript\n * const grantFile = {\n * grantee: '0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36',\n * operation: 'read',\n * parameters: { version: '1.0', mode: 'full' }\n * };\n *\n * const hash = getGrantFileHash(grantFile);\n * console.log(`Grant file hash: ${hash}`);\n * // \"0x1234567890abcdef...\"\n *\n * // Same grant file with different property order produces same hash\n * const grantFile2 = {\n * operation: 'read',\n * parameters: { mode: 'full', version: '1.0' },\n * grantee: '0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36'\n * };\n *\n * const hash2 = getGrantFileHash(grantFile2);\n * console.log(hash === hash2); // true\n * ```\n */\nexport function getGrantFileHash(grantFile: GrantFile): string {\n try {\n // Create a stable JSON representation\n const sortedFile: GrantFile = {\n grantee: grantFile.grantee,\n operation: grantFile.operation,\n parameters: sortObjectKeys(grantFile.parameters) as Record<\n string,\n unknown\n >,\n };\n\n // Add expires if present\n if (grantFile.expires !== undefined) {\n sortedFile.expires = grantFile.expires;\n }\n\n const jsonString = JSON.stringify(sortedFile);\n console.info(`Hash: ${keccak256(toHex(jsonString))}`);\n return keccak256(toHex(jsonString));\n } catch (error) {\n throw new SerializationError(\n `Failed to generate grant file hash: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n}\n\n/**\n * Recursively sorts object keys for stable serialization.\n *\n * @param obj - The object to sort keys recursively\n * @returns The object with all keys sorted recursively\n */\nfunction sortObjectKeys(obj: unknown): unknown {\n if (obj === null || typeof obj !== \"object\") {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => sortObjectKeys(item));\n }\n\n const sortedObj: Record<string, unknown> = {};\n Object.keys(obj as Record<string, unknown>)\n .sort()\n .forEach((key) => {\n sortedObj[key] = sortObjectKeys((obj as Record<string, unknown>)[key]);\n });\n\n return sortedObj;\n}\n\n/**\n * Validates that a grant file has the required structure.\n *\n * @remarks\n * Performs runtime validation to ensure data conforms to the GrantFile interface.\n * Checks for required fields (grantee, operation, parameters) and validates their\n * types and formats. This is a type guard function that enables TypeScript to\n * narrow the type when it returns true.\n *\n * @param data - The data to validate as a grant file\n * @returns True if the data is a valid grant file, false otherwise\n * @example\n * ```typescript\n * const unknownData = await fetch(url).then(r => r.json());\n *\n * if (validateGrantFile(unknownData)) {\n * // TypeScript now knows unknownData is a GrantFile\n * console.log(`Grant for operation: ${unknownData.operation}`);\n * console.log(`Grantee: ${unknownData.grantee}`);\n * } else {\n * throw new Error('Invalid grant file format');\n * }\n *\n * // Validation examples:\n * validateGrantFile({\n * grantee: '0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36',\n * operation: 'read',\n * parameters: {}\n * }); // true\n *\n * validateGrantFile({\n * grantee: 'invalid-address',\n * operation: 'read',\n * parameters: {}\n * }); // false (invalid address format)\n *\n * validateGrantFile({\n * operation: 'read',\n * parameters: {}\n * }); // false (missing grantee)\n * ```\n */\nexport function validateGrantFile(data: unknown): data is GrantFile {\n if (!data || typeof data !== \"object\") {\n return false;\n }\n\n const obj = data as Record<string, unknown>;\n\n // Validate required fields\n // Validate grantee address\n if (\n typeof obj.grantee !== \"string\" ||\n !obj.grantee.match(/^0x[a-fA-F0-9]{40}$/)\n ) {\n return false;\n }\n\n if (typeof obj.operation !== \"string\" || obj.operation.length === 0) {\n return false;\n }\n\n // Files are no longer stored in grant files - they're tracked in the contract\n\n if (!obj.parameters || typeof obj.parameters !== \"object\") {\n return false;\n }\n\n // Validate optional expires field\n if (obj.expires !== undefined) {\n if (\n typeof obj.expires !== \"number\" ||\n obj.expires < 0 ||\n !Number.isInteger(obj.expires)\n ) {\n return false;\n }\n }\n\n return true;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/grantFiles.ts"],"sourcesContent":["import { keccak256, toHex } from \"viem\";\nimport type { GrantFile, GrantPermissionParams } from \"../types/permissions\";\nimport { SerializationError, NetworkError } from \"../errors\";\n\ninterface GrantFileStorageResponse {\n success: boolean;\n error?: string;\n url?: string;\n}\n\n/**\n * Creates grant file structure for permission storage.\n *\n * @remarks\n * Constructs JSON structure that represents a permission grant\n * in the Vana protocol. The grant file contains all necessary information\n * for a grantee to perform operations on behalf of the grantor.\n *\n * @param params - Permission parameters to create the grant file from\n * @returns Grant file object for IPFS storage\n *\n * @example\n * ```typescript\n * const grant = createGrantFile({\n * grantee: '0x742d35Cc...',\n * operation: 'llm_inference',\n * parameters: { model: 'gpt-4' },\n * expiresAt: Date.now() + 86400000 // 24 hours\n * });\n * ```\n */\nexport function createGrantFile(params: GrantPermissionParams): GrantFile {\n const grantFile: GrantFile = {\n grantee: params.grantee,\n operation: params.operation,\n parameters: params.parameters,\n };\n\n // Add expiration if provided\n if (params.expiresAt) {\n grantFile.expires = params.expiresAt;\n }\n\n return grantFile;\n}\n\n/**\n * Stores a grant file in IPFS via the relayer service.\n *\n * @remarks\n * This function uploads the grant file to IPFS through the relayer's upload endpoint.\n * The returned URL can be stored on-chain as part of the permission grant, allowing\n * anyone to retrieve the detailed permission parameters later.\n *\n * @param grantFile - The grant file to store\n * @param relayerUrl - URL of the relayer service\n * @returns Promise resolving to the IPFS URL\n * @throws {NetworkError} When the upload fails or relayer is unavailable\n * @example\n * ```typescript\n * const grantFile = createGrantFile(params);\n *\n * try {\n * const ipfsUrl = await storeGrantFile(grantFile, 'https://relayer.vana.com');\n * console.log(`Grant file stored at: ${ipfsUrl}`);\n * // ipfsUrl: \"ipfs://QmHash123...\"\n * } catch (error) {\n * console.error('Failed to store grant file:', error);\n * }\n * ```\n */\nexport async function storeGrantFile(\n grantFile: GrantFile,\n relayerUrl: string,\n): Promise<string> {\n try {\n // Convert grant file to blob and use IPFS upload endpoint\n const grantFileBlob = new Blob([JSON.stringify(grantFile, null, 2)], {\n type: \"application/json\",\n });\n\n const formData = new FormData();\n formData.append(\"file\", grantFileBlob, \"grant-file.json\");\n\n const response = await fetch(`${relayerUrl}/api/ipfs/upload`, {\n method: \"POST\",\n body: formData,\n });\n\n if (!response.ok) {\n throw new NetworkError(\n `Failed to store grant file: ${response.statusText}`,\n new Error(`HTTP ${response.status}`),\n );\n }\n\n const responseData: unknown = await response.json();\n const data = responseData as GrantFileStorageResponse;\n\n if (!data.success) {\n throw new NetworkError(data.error ?? \"Failed to store grant file\");\n }\n\n if (!data.url) {\n throw new NetworkError(\"Upload succeeded but no URL was returned\");\n }\n return data.url; // The IPFS URL from the upload response\n } catch (error) {\n if (error instanceof NetworkError) {\n throw error;\n }\n throw new NetworkError(\n `Network error while storing grant file: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n error as Error,\n );\n }\n}\n\n/**\n * Retrieves detailed grant file data from IPFS or HTTP storage.\n *\n * @remarks\n * **This is Step 2 of the performant two-step permission API.**\n *\n * Use this method to resolve detailed permission data (operation, parameters, etc.)\n * for specific grants after first getting the fast on-chain data using\n * `getUserPermissionGrantsOnChain()`. This design eliminates N+1 query problems\n * by allowing selective lazy-loading of expensive off-chain data.\n *\n * **Performance**: Single network request per grant file (typically 100-500ms).\n * **Reliability**: Tries multiple IPFS gateways as fallbacks if primary URL fails.\n *\n * @param grantUrl - The grant file URL from OnChainPermissionGrant.grantUrl\n * @param _relayerUrl - URL of the relayer service (optional, unused)\n * @param downloadRelayer - Optional download relayer for proxying CORS-restricted downloads\n * @param downloadRelayer.proxyDownload - Function to proxy download requests through application server\n * @returns Promise resolving to the complete grant file with operation details\n * @throws {NetworkError} When all retrieval attempts fail\n * @throws {SerializationError} When grant file format is invalid\n * @example\n * ```typescript\n * // Step 1: Fast on-chain data (no N+1 queries)\n * const grants = await vana.permissions.getUserPermissionGrantsOnChain();\n *\n * // Step 2: Lazy-load details for specific grant when needed\n * const grantFile = await retrieveGrantFile(grants[0].grantUrl);\n *\n * console.log(`Operation: ${grantFile.operation}`);\n * console.log(`Grantee: ${grantFile.grantee}`);\n * console.log(`Parameters:`, grantFile.parameters);\n *\n * // Only fetch details for grants user actually wants to see\n * for (const grant of selectedGrants) {\n * const details = await retrieveGrantFile(grant.grantUrl);\n * displayGrantDetails(details);\n * }\n * ```\n */\nexport async function retrieveGrantFile(\n grantUrl: string,\n _relayerUrl?: string,\n downloadRelayer?: { proxyDownload: (url: string) => Promise<Blob> },\n): Promise<GrantFile> {\n try {\n // Check if the URL is a gateway URL instead of ipfs:// protocol\n if (grantUrl.startsWith(\"http\") && grantUrl.includes(\"/ipfs/\")) {\n console.warn(\n `⚠️ Grant URL uses HTTP gateway format instead of ipfs:// protocol. ` +\n `Found: ${grantUrl}. ` +\n `Consider using ipfs:// format for better protocol-agnostic storage.`,\n );\n }\n\n // Use the unified download utility\n const { universalFetch } = await import(\"./download\");\n const response = await universalFetch(grantUrl, downloadRelayer);\n\n if (!response.ok) {\n throw new NetworkError(\n `Failed to retrieve grant file: HTTP ${response.status}`,\n );\n }\n\n const text = await response.text();\n const grantFile = JSON.parse(text);\n\n if (!validateGrantFile(grantFile)) {\n throw new NetworkError(`Invalid grant file format from ${grantUrl}`);\n }\n\n return grantFile;\n } catch (error) {\n if (error instanceof NetworkError) {\n throw error;\n }\n throw new NetworkError(\n `Error retrieving grant file: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n error as Error,\n );\n }\n}\n\n/**\n * Generates a content hash for a grant file.\n * This can be used for integrity verification.\n *\n * @remarks\n * Creates a deterministic keccak256 hash of the grant file by first sorting\n * all object keys recursively to ensure consistent hashing regardless of\n * property order. This hash can be used to verify grant file integrity\n * or as a unique identifier.\n *\n * @param grantFile - The grant file to generate a hash for\n * @returns The keccak256 hash of the grant file as a hex string\n * @throws {SerializationError} When the grant file cannot be serialized\n * @example\n * ```typescript\n * const grantFile = {\n * grantee: '0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36',\n * operation: 'read',\n * parameters: { version: '1.0', mode: 'full' }\n * };\n *\n * const hash = getGrantFileHash(grantFile);\n * console.log(`Grant file hash: ${hash}`);\n * // \"0x1234567890abcdef...\"\n *\n * // Same grant file with different property order produces same hash\n * const grantFile2 = {\n * operation: 'read',\n * parameters: { mode: 'full', version: '1.0' },\n * grantee: '0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36'\n * };\n *\n * const hash2 = getGrantFileHash(grantFile2);\n * console.log(hash === hash2); // true\n * ```\n */\nexport function getGrantFileHash(grantFile: GrantFile): string {\n try {\n // Create a stable JSON representation\n const sortedFile: GrantFile = {\n grantee: grantFile.grantee,\n operation: grantFile.operation,\n parameters: sortObjectKeys(grantFile.parameters) as Record<\n string,\n unknown\n >,\n };\n\n // Add expires if present\n if (grantFile.expires !== undefined) {\n sortedFile.expires = grantFile.expires;\n }\n\n const jsonString = JSON.stringify(sortedFile);\n console.info(`Hash: ${keccak256(toHex(jsonString))}`);\n return keccak256(toHex(jsonString));\n } catch (error) {\n throw new SerializationError(\n `Failed to generate grant file hash: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n}\n\n/**\n * Recursively sorts object keys for stable serialization.\n *\n * @param obj - The object to sort keys recursively\n * @returns The object with all keys sorted recursively\n */\nfunction sortObjectKeys(obj: unknown): unknown {\n if (obj === null || typeof obj !== \"object\") {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => sortObjectKeys(item));\n }\n\n const sortedObj: Record<string, unknown> = {};\n Object.keys(obj as Record<string, unknown>)\n .sort()\n .forEach((key) => {\n sortedObj[key] = sortObjectKeys((obj as Record<string, unknown>)[key]);\n });\n\n return sortedObj;\n}\n\n/**\n * Validates that a grant file has the required structure.\n *\n * @remarks\n * Performs runtime validation to ensure data conforms to the GrantFile interface.\n * Checks for required fields (grantee, operation, parameters) and validates their\n * types and formats. This is a type guard function that enables TypeScript to\n * narrow the type when it returns true.\n *\n * @param data - The data to validate as a grant file\n * @returns True if the data is a valid grant file, false otherwise\n * @example\n * ```typescript\n * const unknownData = await fetch(url).then(r => r.json());\n *\n * if (validateGrantFile(unknownData)) {\n * // TypeScript now knows unknownData is a GrantFile\n * console.log(`Grant for operation: ${unknownData.operation}`);\n * console.log(`Grantee: ${unknownData.grantee}`);\n * } else {\n * throw new Error('Invalid grant file format');\n * }\n *\n * // Validation examples:\n * validateGrantFile({\n * grantee: '0x742d35Cc6558Fd4D9e9E0E888F0462ef6919Bd36',\n * operation: 'read',\n * parameters: {}\n * }); // true\n *\n * validateGrantFile({\n * grantee: 'invalid-address',\n * operation: 'read',\n * parameters: {}\n * }); // false (invalid address format)\n *\n * validateGrantFile({\n * operation: 'read',\n * parameters: {}\n * }); // false (missing grantee)\n * ```\n */\nexport function validateGrantFile(data: unknown): data is GrantFile {\n if (!data || typeof data !== \"object\") {\n return false;\n }\n\n const obj = data as Record<string, unknown>;\n\n // Validate required fields\n // Validate grantee address\n if (\n typeof obj.grantee !== \"string\" ||\n !obj.grantee.match(/^0x[a-fA-F0-9]{40}$/)\n ) {\n return false;\n }\n\n if (typeof obj.operation !== \"string\" || obj.operation.length === 0) {\n return false;\n }\n\n // Files are no longer stored in grant files - they're tracked in the contract\n\n if (!obj.parameters || typeof obj.parameters !== \"object\") {\n return false;\n }\n\n // Validate optional expires field\n if (obj.expires !== undefined) {\n if (\n typeof obj.expires !== \"number\" ||\n obj.expires < 0 ||\n !Number.isInteger(obj.expires)\n ) {\n return false;\n }\n }\n\n return true;\n}\n"],"mappings":"AAAA,SAAS,WAAW,aAAa;AAEjC,SAAS,oBAAoB,oBAAoB;AA6B1C,SAAS,gBAAgB,QAA0C;AACxE,QAAM,YAAuB;AAAA,IAC3B,SAAS,OAAO;AAAA,IAChB,WAAW,OAAO;AAAA,IAClB,YAAY,OAAO;AAAA,EACrB;AAGA,MAAI,OAAO,WAAW;AACpB,cAAU,UAAU,OAAO;AAAA,EAC7B;AAEA,SAAO;AACT;AA2BA,eAAsB,eACpB,WACA,YACiB;AACjB,MAAI;AAEF,UAAM,gBAAgB,IAAI,KAAK,CAAC,KAAK,UAAU,WAAW,MAAM,CAAC,CAAC,GAAG;AAAA,MACnE,MAAM;AAAA,IACR,CAAC;AAED,UAAM,WAAW,IAAI,SAAS;AAC9B,aAAS,OAAO,QAAQ,eAAe,iBAAiB;AAExD,UAAM,WAAW,MAAM,MAAM,GAAG,UAAU,oBAAoB;AAAA,MAC5D,QAAQ;AAAA,MACR,MAAM;AAAA,IACR,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI;AAAA,QACR,+BAA+B,SAAS,UAAU;AAAA,QAClD,IAAI,MAAM,QAAQ,SAAS,MAAM,EAAE;AAAA,MACrC;AAAA,IACF;AAEA,UAAM,eAAwB,MAAM,SAAS,KAAK;AAClD,UAAM,OAAO;AAEb,QAAI,CAAC,KAAK,SAAS;AACjB,YAAM,IAAI,aAAa,KAAK,SAAS,4BAA4B;AAAA,IACnE;AAEA,QAAI,CAAC,KAAK,KAAK;AACb,YAAM,IAAI,aAAa,0CAA0C;AAAA,IACnE;AACA,WAAO,KAAK;AAAA,EACd,SAAS,OAAO;AACd,QAAI,iBAAiB,cAAc;AACjC,YAAM;AAAA,IACR;AACA,UAAM,IAAI;AAAA,MACR,2CAA2C,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,MACnG;AAAA,IACF;AAAA,EACF;AACF;AA0CA,eAAsB,kBACpB,UACA,aACA,iBACoB;AACpB,MAAI;AAEF,QAAI,SAAS,WAAW,MAAM,KAAK,SAAS,SAAS,QAAQ,GAAG;AAC9D,cAAQ;AAAA,QACN,wFACY,QAAQ;AAAA,MAEtB;AAAA,IACF;AAGA,UAAM,EAAE,eAAe,IAAI,MAAM,OAAO,YAAY;AACpD,UAAM,WAAW,MAAM,eAAe,UAAU,eAAe;AAE/D,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI;AAAA,QACR,uCAAuC,SAAS,MAAM;AAAA,MACxD;AAAA,IACF;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAM,YAAY,KAAK,MAAM,IAAI;AAEjC,QAAI,CAAC,kBAAkB,SAAS,GAAG;AACjC,YAAM,IAAI,aAAa,kCAAkC,QAAQ,EAAE;AAAA,IACrE;AAEA,WAAO;AAAA,EACT,SAAS,OAAO;AACd,QAAI,iBAAiB,cAAc;AACjC,YAAM;AAAA,IACR;AACA,UAAM,IAAI;AAAA,MACR,gCAAgC,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,MACxF;AAAA,IACF;AAAA,EACF;AACF;AAsCO,SAAS,iBAAiB,WAA8B;AAC7D,MAAI;AAEF,UAAM,aAAwB;AAAA,MAC5B,SAAS,UAAU;AAAA,MACnB,WAAW,UAAU;AAAA,MACrB,YAAY,eAAe,UAAU,UAAU;AAAA,IAIjD;AAGA,QAAI,UAAU,YAAY,QAAW;AACnC,iBAAW,UAAU,UAAU;AAAA,IACjC;AAEA,UAAM,aAAa,KAAK,UAAU,UAAU;AAC5C,YAAQ,KAAK,SAAS,UAAU,MAAM,UAAU,CAAC,CAAC,EAAE;AACpD,WAAO,UAAU,MAAM,UAAU,CAAC;AAAA,EACpC,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,uCAAuC,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,IACjG;AAAA,EACF;AACF;AAQA,SAAS,eAAe,KAAuB;AAC7C,MAAI,QAAQ,QAAQ,OAAO,QAAQ,UAAU;AAC3C,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,WAAO,IAAI,IAAI,CAAC,SAAS,eAAe,IAAI,CAAC;AAAA,EAC/C;AAEA,QAAM,YAAqC,CAAC;AAC5C,SAAO,KAAK,GAA8B,EACvC,KAAK,EACL,QAAQ,CAAC,QAAQ;AAChB,cAAU,GAAG,IAAI,eAAgB,IAAgC,GAAG,CAAC;AAAA,EACvE,CAAC;AAEH,SAAO;AACT;AA4CO,SAAS,kBAAkB,MAAkC;AAClE,MAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,WAAO;AAAA,EACT;AAEA,QAAM,MAAM;AAIZ,MACE,OAAO,IAAI,YAAY,YACvB,CAAC,IAAI,QAAQ,MAAM,qBAAqB,GACxC;AACA,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,IAAI,cAAc,YAAY,IAAI,UAAU,WAAW,GAAG;AACnE,WAAO;AAAA,EACT;AAIA,MAAI,CAAC,IAAI,cAAc,OAAO,IAAI,eAAe,UAAU;AACzD,WAAO;AAAA,EACT;AAGA,MAAI,IAAI,YAAY,QAAW;AAC7B,QACE,OAAO,IAAI,YAAY,YACvB,IAAI,UAAU,KACd,CAAC,OAAO,UAAU,IAAI,OAAO,GAC7B;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|
|
@@ -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":["Ajv","addFormats","grantFileSchema"]}
|
|
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":["Ajv","addFormats","grantFileSchema"]}
|
|
@@ -1,7 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provides comprehensive validation for permission grant files.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* This module implements multi-layer validation for grant files including
|
|
6
|
+
* JSON schema validation, business rule checking, expiration verification,
|
|
7
|
+
* and access control validation. It provides both throwing and non-throwing
|
|
8
|
+
* modes for flexible error handling.
|
|
9
|
+
*
|
|
10
|
+
* @category Permissions
|
|
11
|
+
* @module utils/grantValidation
|
|
12
|
+
*/
|
|
1
13
|
import type { Address } from "viem";
|
|
2
14
|
import type { GrantFile } from "../types/permissions";
|
|
3
15
|
/**
|
|
4
|
-
*
|
|
16
|
+
* Indicates a general grant validation failure.
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* Base class for all grant validation errors. Provides structured
|
|
20
|
+
* error details for debugging and recovery.
|
|
5
21
|
*
|
|
6
22
|
* @category Permissions
|
|
7
23
|
*/
|
|
@@ -10,7 +26,11 @@ export declare class GrantValidationError extends Error {
|
|
|
10
26
|
constructor(message: string, details?: Record<string, unknown> | undefined);
|
|
11
27
|
}
|
|
12
28
|
/**
|
|
13
|
-
*
|
|
29
|
+
* Indicates that a grant has expired and is no longer valid.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* Thrown when attempting to use a grant past its expiration timestamp.
|
|
33
|
+
* Includes both the expiration time and current time for debugging.
|
|
14
34
|
*
|
|
15
35
|
* @category Permissions
|
|
16
36
|
*/
|
|
@@ -20,7 +40,11 @@ export declare class GrantExpiredError extends GrantValidationError {
|
|
|
20
40
|
constructor(message: string, expires: number, currentTime: number);
|
|
21
41
|
}
|
|
22
42
|
/**
|
|
23
|
-
*
|
|
43
|
+
* Indicates that the requesting address doesn't match the grant's grantee.
|
|
44
|
+
*
|
|
45
|
+
* @remarks
|
|
46
|
+
* Thrown when a user attempts to use a grant that was issued to a
|
|
47
|
+
* different address. This prevents unauthorized use of permissions.
|
|
24
48
|
*
|
|
25
49
|
* @category Permissions
|
|
26
50
|
*/
|
|
@@ -30,7 +54,11 @@ export declare class GranteeMismatchError extends GrantValidationError {
|
|
|
30
54
|
constructor(message: string, grantee: Address, requestingAddress: Address);
|
|
31
55
|
}
|
|
32
56
|
/**
|
|
33
|
-
*
|
|
57
|
+
* Indicates that the requested operation is not allowed by the grant.
|
|
58
|
+
*
|
|
59
|
+
* @remarks
|
|
60
|
+
* Thrown when attempting an operation that differs from what the
|
|
61
|
+
* grant authorizes. Includes both granted and requested operations.
|
|
34
62
|
*
|
|
35
63
|
* @category Permissions
|
|
36
64
|
*/
|
|
@@ -40,7 +68,11 @@ export declare class OperationNotAllowedError extends GrantValidationError {
|
|
|
40
68
|
constructor(message: string, grantedOperation: string, requestedOperation: string);
|
|
41
69
|
}
|
|
42
70
|
/**
|
|
43
|
-
*
|
|
71
|
+
* Indicates that the grant file structure violates the JSON schema.
|
|
72
|
+
*
|
|
73
|
+
* @remarks
|
|
74
|
+
* Thrown when a grant file doesn't conform to the expected schema.
|
|
75
|
+
* Includes detailed schema validation errors and the invalid data.
|
|
44
76
|
*
|
|
45
77
|
* @category Permissions
|
|
46
78
|
*/
|
|
@@ -50,7 +82,11 @@ export declare class GrantSchemaError extends GrantValidationError {
|
|
|
50
82
|
constructor(message: string, schemaErrors: unknown[], invalidData: unknown);
|
|
51
83
|
}
|
|
52
84
|
/**
|
|
53
|
-
*
|
|
85
|
+
* Configures grant validation behavior and scope.
|
|
86
|
+
*
|
|
87
|
+
* @remarks
|
|
88
|
+
* Controls which validations to perform and how to handle errors.
|
|
89
|
+
* Allows selective validation of specific aspects of a grant.
|
|
54
90
|
*
|
|
55
91
|
* @category Permissions
|
|
56
92
|
*/
|
|
@@ -67,7 +103,11 @@ export interface GrantValidationOptions {
|
|
|
67
103
|
throwOnError?: boolean;
|
|
68
104
|
}
|
|
69
105
|
/**
|
|
70
|
-
*
|
|
106
|
+
* Represents the detailed result of grant validation.
|
|
107
|
+
*
|
|
108
|
+
* @remarks
|
|
109
|
+
* Provides comprehensive validation results including all errors
|
|
110
|
+
* encountered during validation. Used in non-throwing mode.
|
|
71
111
|
*
|
|
72
112
|
* @category Permissions
|
|
73
113
|
*/
|
|
@@ -125,23 +165,62 @@ export declare function validateGrant(data: unknown, options?: Omit<GrantValidat
|
|
|
125
165
|
throwOnError?: true;
|
|
126
166
|
})): GrantFile;
|
|
127
167
|
/**
|
|
128
|
-
* Validates that a grant
|
|
168
|
+
* Validates that a grant allows access for the requesting address.
|
|
169
|
+
*
|
|
170
|
+
* @param grantFile - The grant file to validate.
|
|
171
|
+
* Obtain from permission operations or server responses.
|
|
172
|
+
* @param requestingAddress - The address requesting access.
|
|
173
|
+
* Typically the current wallet address.
|
|
129
174
|
*
|
|
130
|
-
* @
|
|
131
|
-
*
|
|
175
|
+
* @throws {GranteeMismatchError} If addresses don't match.
|
|
176
|
+
* Ensure using the correct wallet that received the grant.
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```typescript
|
|
180
|
+
* validateGranteeAccess(grantFile, walletAddress);
|
|
181
|
+
* // Throws if walletAddress doesn't match grantFile.grantee
|
|
182
|
+
* ```
|
|
183
|
+
*
|
|
184
|
+
* @category Permissions
|
|
132
185
|
*/
|
|
133
186
|
export declare function validateGranteeAccess(grantFile: GrantFile, requestingAddress: Address): void;
|
|
134
187
|
/**
|
|
135
|
-
* Validates that a grant has not expired
|
|
188
|
+
* Validates that a grant has not expired.
|
|
189
|
+
*
|
|
190
|
+
* @param grantFile - The grant file to check.
|
|
191
|
+
* Must include expiry timestamp if time-limited.
|
|
192
|
+
* @param currentTime - Optional time override for testing.
|
|
193
|
+
* Unix timestamp in seconds. Defaults to current time.
|
|
194
|
+
*
|
|
195
|
+
* @throws {GrantExpiredError} If grant has expired.
|
|
196
|
+
* Request a new grant from the data owner.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```typescript
|
|
200
|
+
* validateGrantExpiry(grantFile);
|
|
201
|
+
* // Throws if current time > grantFile.expires
|
|
202
|
+
* ```
|
|
136
203
|
*
|
|
137
|
-
* @
|
|
138
|
-
* @param currentTime - Optional override for current time (Unix timestamp)
|
|
204
|
+
* @category Permissions
|
|
139
205
|
*/
|
|
140
206
|
export declare function validateGrantExpiry(grantFile: GrantFile, currentTime?: number): void;
|
|
141
207
|
/**
|
|
142
|
-
* Validates that a grant
|
|
208
|
+
* Validates that a grant authorizes the requested operation.
|
|
209
|
+
*
|
|
210
|
+
* @param grantFile - The grant file to check.
|
|
211
|
+
* Contains the authorized operation.
|
|
212
|
+
* @param requestedOperation - The operation to validate.
|
|
213
|
+
* Must match the grant's operation exactly.
|
|
214
|
+
*
|
|
215
|
+
* @throws {OperationNotAllowedError} If operations don't match.
|
|
216
|
+
* Request a grant for the specific operation needed.
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```typescript
|
|
220
|
+
* validateOperationAccess(grantFile, 'llm_inference');
|
|
221
|
+
* // Throws if grantFile.operation !== 'llm_inference'
|
|
222
|
+
* ```
|
|
143
223
|
*
|
|
144
|
-
* @
|
|
145
|
-
* @param requestedOperation - The operation being requested to validate against the grant
|
|
224
|
+
* @category Permissions
|
|
146
225
|
*/
|
|
147
226
|
export declare function validateOperationAccess(grantFile: GrantFile, requestedOperation: string): void;
|