@opendatalabs/vana-sdk 3.10.0 → 3.11.0

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.
@@ -130,7 +130,8 @@ function createDirectDataController(config) {
130
130
  payerAddress: account.address,
131
131
  signMessage,
132
132
  escrow,
133
- fetchFn: config.personalServerFetch
133
+ fetchFn: config.personalServerFetch,
134
+ transportRetry: config.personalServerTransportRetry
134
135
  });
135
136
  try {
136
137
  await accessRequestClient.acknowledgeRead?.(input.requestId);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/direct/controller.ts"],"sourcesContent":["/**\n * Direct Data Controller — the server-side facade for the two-tab Data\n * Portability flow.\n *\n * @remarks\n * One controller owns an app's private key, source, scopes, app identity, and\n * payment flow. It exposes the three methods the builder guide documents:\n *\n * - {@link DirectDataController.createAccessRequest} — start an approval request.\n * - {@link DirectDataController.getAccessRequestStatus} — poll while the Vana tab is open.\n * - {@link DirectDataController.readApprovedData} — read from the Personal Server,\n * handling 402 Payment Required.\n *\n * Access requests are created through the Vana Account access-request API; the\n * Personal Server read uses Web3Signed auth; and payment uses the DPv2 escrow\n * surface (`protocol/escrow`) — when a read returns `402`, the controller signs\n * a `GenericPayment` with the app key, settles it through the escrow gateway,\n * and retries.\n *\n * @category Direct\n * @module direct/controller\n */\n\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport type { Hex } from \"viem\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport { parseScope } from \"../protocol/scopes\";\nimport { createEscrowGatewayClient } from \"../protocol/escrow\";\nimport { CONTRACTS } from \"../generated/addresses\";\nimport {\n createDefaultAccessRequestClient,\n type FetchLike,\n} from \"./access-request-client\";\nimport {\n getDirectDefaultNetwork,\n getDirectEndpoints,\n getDirectNetworkChainId,\n} from \"./endpoints\";\nimport { AccessNotApprovedError, DirectConfigError } from \"./errors\";\nimport {\n type EscrowPaymentConfig,\n type SignTypedDataFn,\n} from \"./escrow-payment\";\nimport {\n readPersonalServerData,\n type PersonalServerFetch,\n} from \"./personal-server-read\";\nimport type {\n AccessRequest,\n AccessRequestClient,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n AppIdentity,\n DirectAppConfig,\n DirectEnv,\n DirectNetwork,\n DirectServiceEndpoints,\n} from \"./types\";\n\n/** Configuration for {@link createDirectDataController}. */\nexport interface DirectDataControllerConfig {\n /** Target environment. Defaults to `\"production\"`. */\n env?: DirectEnv;\n /**\n * Target Vana network for chain-aware defaults. Defaults to the selected\n * environment's historical network (`mainnet` for production, `moksha` for\n * dev). Use `network: \"moksha\"` with the default production env for\n * production app/API URLs on testnet.\n */\n network?: DirectNetwork;\n /**\n * The app private key (`0x`-prefixed, 32 bytes). Server-side only — this key\n * is the app's on-chain identity and is never exposed to the browser.\n */\n appPrivateKey?: string;\n /**\n * @deprecated Use {@link DirectDataControllerConfig.appPrivateKey}. Accepted as\n * a backwards-compatible alias; if both are set, `appPrivateKey` wins.\n */\n builderPrivateKey?: string;\n /** App identity advertised during approval. */\n app: DirectAppConfig;\n /** Data source key (e.g. `\"icloud_notes\"`). */\n source: string;\n /** Scopes to request (e.g. `[\"icloud_notes.notes\"]`). At least one required. */\n scopes: string[];\n /**\n * Override the resolved service endpoints (partial). Useful for pointing at a\n * non-standard deployment.\n */\n endpoints?: Partial<DirectServiceEndpoints>;\n /**\n * Client for the Vana Account access-request API. Defaults to a client against\n * the resolved Vana Account endpoints; inject your own to point at a custom\n * deployment or to supply a test double.\n */\n accessRequestClient?: AccessRequestClient;\n /**\n * Escrow settlement config used when a Personal Server read returns `402`.\n *\n * @remarks\n * Wires the DPv2 escrow gateway (`protocol/escrow`). The controller supplies\n * the EIP-712 `signTypedData` from the app key automatically.\n *\n * When omitted (or partially omitted), the SDK derives defaults from the\n * per-network endpoints table and the contract registry:\n * - `client` defaults to a gateway client at `endpoints.escrowGatewayUrl`\n * - `escrowContract` defaults to `CONTRACTS.DataPortabilityEscrow.addresses[chainId]`\n * - `chainId` defaults to the controller's resolved chain id\n *\n * Provide this field only to override a specific default.\n */\n escrow?: Partial<DirectEscrowConfig>;\n /** `fetch` used by the default access-request client. Defaults to `globalThis.fetch`. */\n fetchFn?: FetchLike;\n /** `fetch` used for the Personal Server read. Defaults to `globalThis.fetch`. */\n personalServerFetch?: PersonalServerFetch;\n}\n\n/**\n * Controller-level escrow config — the {@link EscrowPaymentConfig} minus the\n * `signTypedData` and `chainId` the controller injects itself.\n */\nexport interface DirectEscrowConfig extends Omit<\n EscrowPaymentConfig,\n \"signTypedData\" | \"chainId\"\n> {\n /**\n * Chain id for the EIP-712 domain. Defaults to the controller's environment\n * (1480 for mainnet, 14800 for moksha).\n */\n chainId?: number;\n}\n\n/**\n * Server-side controller for the direct Data Portability flow.\n *\n * @typeParam T - Shape of the data returned by {@link DirectDataController.readApprovedData}.\n */\nexport interface DirectDataController {\n /** The on-chain address of the app, derived from `appPrivateKey`. */\n readonly appAddress: string;\n\n /**\n * The app's on-chain address — the address to fund and inspect in the Builder\n * activity report. Equivalent to {@link DirectDataController.appAddress}.\n *\n * @returns The app's `0x`-prefixed address.\n */\n getAppAddress(): string;\n\n /**\n * The app's full identity: its configured id/name/homepage plus the derived\n * on-chain address. Useful for telling builders which app address to fund or\n * look up.\n *\n * @returns `{ id, name, homepageUrl, address }`.\n */\n getAppIdentity(): AppIdentity;\n\n /**\n * Create an access request the user can approve.\n *\n * @param input - The post-approval return URL.\n * @returns `{ requestId, approvalUrl, appAddress }`.\n */\n createAccessRequest(input: { returnUrl: string }): Promise<AccessRequest>;\n\n /**\n * Fetch the current status of an access request.\n *\n * @param requestId - The `dcr_*` id from {@link DirectDataController.createAccessRequest}.\n * @returns `{ status, personalServerUrl?, grantId?, scope? }`.\n */\n getAccessRequestStatus(requestId: string): Promise<AccessRequestStatus>;\n\n /**\n * Read the approved data from the user's Personal Server.\n *\n * @remarks\n * Resolves the request to its grant + Personal Server and performs a Web3Signed\n * read. Hides the `402 Payment Required` flow by default: if a read needs\n * payment, it signs the Personal Server's payment challenge, retries with\n * `X-PAYMENT`, and attaches a {@link DirectPaymentReceipt} under `payment`\n * when the Personal Server returns one. After a successful read, the\n * controller acknowledges the DCR so Vana Web can close/redirect the approval\n * tab.\n *\n * @param input - The `dcr_*` request id to read.\n * @returns `{ scope, data, payment? }`.\n * @throws {@link AccessNotApprovedError} if the request is not approved.\n * @throws {@link PaymentRequiredError} if payment is required but unsettled.\n */\n readApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<ApprovedDataResult<T>>;\n}\n\nfunction isHexPrivateKey(value: string): value is Hex {\n return /^0x[0-9a-fA-F]{64}$/.test(value);\n}\n\nfunction isReadReadyStatus(status: AccessRequestStatusValue): boolean {\n return status === \"approved\" || status === \"ready_for_read\";\n}\n\n/**\n * Create a {@link DirectDataController}.\n *\n * @param config - Controller configuration (env, key, app identity, source, scopes).\n * @returns A ready-to-use controller.\n * @throws {@link DirectConfigError} when the key or scopes are invalid.\n */\nexport function createDirectDataController(\n config: DirectDataControllerConfig,\n): DirectDataController {\n // `appPrivateKey` is the documented field; `builderPrivateKey` is a\n // deprecated alias kept for backwards compatibility.\n const privateKey = config.appPrivateKey ?? config.builderPrivateKey;\n if (!privateKey || !isHexPrivateKey(privateKey)) {\n throw new DirectConfigError(\n \"appPrivateKey must be a 0x-prefixed 32-byte hex string\",\n );\n }\n if (!config.scopes || config.scopes.length === 0) {\n throw new DirectConfigError(\"At least one scope is required\");\n }\n // Validate scopes eagerly so misconfiguration fails at construction.\n for (const scope of config.scopes) {\n parseScope(scope);\n }\n\n const env: DirectEnv = config.env ?? \"production\";\n const network: DirectNetwork = config.network ?? getDirectDefaultNetwork(env);\n const defaultEndpoints = getDirectEndpoints(env);\n const chainId = config.endpoints?.chainId ?? getDirectNetworkChainId(network);\n const endpoints: DirectServiceEndpoints = {\n ...defaultEndpoints,\n ...config.endpoints,\n chainId,\n };\n\n const account = privateKeyToAccount(privateKey as Hex);\n const signMessage: Web3SignedSignFn = (message: string) =>\n account.signMessage({ message });\n // viem's account.signTypedData satisfies the structural SignTypedDataFn used\n // by the escrow GenericPayment signer.\n const signTypedData = account.signTypedData as unknown as SignTypedDataFn;\n const accessRequestClient: AccessRequestClient =\n config.accessRequestClient ??\n createDefaultAccessRequestClient({\n baseUrl: endpoints.accessRequestBaseUrl,\n approvalBaseUrl: endpoints.approvalAppBaseUrl,\n fetchFn: config.fetchFn,\n appAddress: account.address,\n signMessage,\n });\n\n // Build the escrow payment config, defaulting from the per-network endpoints\n // table and the contract registry when `config.escrow` is omitted or partial.\n const escrowChainId = config.escrow?.chainId ?? chainId;\n const defaultEscrowContract =\n CONTRACTS.DataPortabilityEscrow.addresses[\n escrowChainId as keyof typeof CONTRACTS.DataPortabilityEscrow.addresses\n ] ?? undefined;\n if (!config.escrow?.escrowContract && !defaultEscrowContract) {\n throw new DirectConfigError(\n `No DataPortabilityEscrow address found in the registry for chainId ${escrowChainId}. ` +\n `Provide an explicit escrow.escrowContract in the controller config.`,\n );\n }\n const escrow: EscrowPaymentConfig = {\n client:\n config.escrow?.client ??\n createEscrowGatewayClient(endpoints.escrowGatewayUrl),\n escrowContract:\n config.escrow?.escrowContract ?? (defaultEscrowContract as `0x${string}`),\n chainId: escrowChainId,\n nonceSource: config.escrow?.nonceSource,\n signTypedData,\n };\n\n return {\n appAddress: account.address,\n\n getAppAddress(): string {\n return account.address;\n },\n\n getAppIdentity(): AppIdentity {\n return {\n id: config.app.id,\n name: config.app.name,\n homepageUrl: config.app.homepageUrl,\n address: account.address,\n };\n },\n\n async createAccessRequest(input): Promise<AccessRequest> {\n return accessRequestClient.createAccessRequest({\n appAddress: account.address,\n app: config.app,\n source: config.source,\n scopes: config.scopes,\n returnUrl: input.returnUrl,\n network,\n });\n },\n\n async getAccessRequestStatus(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n return accessRequestClient.getAccessRequestStatus(requestId);\n },\n\n async readApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<ApprovedDataResult<T>> {\n const status = await accessRequestClient.getAccessRequestStatus(\n input.requestId,\n );\n if (\n !isReadReadyStatus(status.status) ||\n !status.personalServerUrl ||\n !status.grantId ||\n !status.scope\n ) {\n throw new AccessNotApprovedError(\n \"Request is not approved or is missing grantId/scope/personalServerUrl\",\n {\n requestId: input.requestId,\n status: status.status,\n hasPersonalServerUrl: Boolean(status.personalServerUrl),\n hasGrantId: Boolean(status.grantId),\n hasScope: Boolean(status.scope),\n },\n );\n }\n\n const result = await readPersonalServerData({\n personalServerUrl: status.personalServerUrl,\n scope: status.scope,\n grantId: status.grantId,\n payerAddress: account.address,\n signMessage,\n escrow,\n fetchFn: config.personalServerFetch,\n });\n try {\n await accessRequestClient.acknowledgeRead?.(input.requestId);\n } catch {\n // The read already succeeded; ack only drives Vana Web completion UX.\n }\n\n return {\n scope: status.scope,\n data: result.data as T,\n payment: result.payment,\n };\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/direct/controller.ts"],"sourcesContent":["/**\n * Direct Data Controller — the server-side facade for the two-tab Data\n * Portability flow.\n *\n * @remarks\n * One controller owns an app's private key, source, scopes, app identity, and\n * payment flow. It exposes the three methods the builder guide documents:\n *\n * - {@link DirectDataController.createAccessRequest} — start an approval request.\n * - {@link DirectDataController.getAccessRequestStatus} — poll while the Vana tab is open.\n * - {@link DirectDataController.readApprovedData} — read from the Personal Server,\n * handling 402 Payment Required.\n *\n * Access requests are created through the Vana Account access-request API; the\n * Personal Server read uses Web3Signed auth; and payment uses the DPv2 escrow\n * surface (`protocol/escrow`) — when a read returns `402`, the controller signs\n * a `GenericPayment` with the app key, settles it through the escrow gateway,\n * and retries.\n *\n * @category Direct\n * @module direct/controller\n */\n\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport type { Hex } from \"viem\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport { parseScope } from \"../protocol/scopes\";\nimport { createEscrowGatewayClient } from \"../protocol/escrow\";\nimport { CONTRACTS } from \"../generated/addresses\";\nimport {\n createDefaultAccessRequestClient,\n type FetchLike,\n} from \"./access-request-client\";\nimport {\n getDirectDefaultNetwork,\n getDirectEndpoints,\n getDirectNetworkChainId,\n} from \"./endpoints\";\nimport { AccessNotApprovedError, DirectConfigError } from \"./errors\";\nimport {\n type EscrowPaymentConfig,\n type SignTypedDataFn,\n} from \"./escrow-payment\";\nimport {\n readPersonalServerData,\n type PersonalServerFetch,\n type PersonalServerTransportRetryOptions,\n} from \"./personal-server-read\";\nimport type {\n AccessRequest,\n AccessRequestClient,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n AppIdentity,\n DirectAppConfig,\n DirectEnv,\n DirectNetwork,\n DirectServiceEndpoints,\n} from \"./types\";\n\n/** Configuration for {@link createDirectDataController}. */\nexport interface DirectDataControllerConfig {\n /** Target environment. Defaults to `\"production\"`. */\n env?: DirectEnv;\n /**\n * Target Vana network for chain-aware defaults. Defaults to the selected\n * environment's historical network (`mainnet` for production, `moksha` for\n * dev). Use `network: \"moksha\"` with the default production env for\n * production app/API URLs on testnet.\n */\n network?: DirectNetwork;\n /**\n * The app private key (`0x`-prefixed, 32 bytes). Server-side only — this key\n * is the app's on-chain identity and is never exposed to the browser.\n */\n appPrivateKey?: string;\n /**\n * @deprecated Use {@link DirectDataControllerConfig.appPrivateKey}. Accepted as\n * a backwards-compatible alias; if both are set, `appPrivateKey` wins.\n */\n builderPrivateKey?: string;\n /** App identity advertised during approval. */\n app: DirectAppConfig;\n /** Data source key (e.g. `\"icloud_notes\"`). */\n source: string;\n /** Scopes to request (e.g. `[\"icloud_notes.notes\"]`). At least one required. */\n scopes: string[];\n /**\n * Override the resolved service endpoints (partial). Useful for pointing at a\n * non-standard deployment.\n */\n endpoints?: Partial<DirectServiceEndpoints>;\n /**\n * Client for the Vana Account access-request API. Defaults to a client against\n * the resolved Vana Account endpoints; inject your own to point at a custom\n * deployment or to supply a test double.\n */\n accessRequestClient?: AccessRequestClient;\n /**\n * Escrow settlement config used when a Personal Server read returns `402`.\n *\n * @remarks\n * Wires the DPv2 escrow gateway (`protocol/escrow`). The controller supplies\n * the EIP-712 `signTypedData` from the app key automatically.\n *\n * When omitted (or partially omitted), the SDK derives defaults from the\n * per-network endpoints table and the contract registry:\n * - `client` defaults to a gateway client at `endpoints.escrowGatewayUrl`\n * - `escrowContract` defaults to `CONTRACTS.DataPortabilityEscrow.addresses[chainId]`\n * - `chainId` defaults to the controller's resolved chain id\n *\n * Provide this field only to override a specific default.\n */\n escrow?: Partial<DirectEscrowConfig>;\n /** `fetch` used by the default access-request client. Defaults to `globalThis.fetch`. */\n fetchFn?: FetchLike;\n /** `fetch` used for the Personal Server read. Defaults to `globalThis.fetch`. */\n personalServerFetch?: PersonalServerFetch;\n /**\n * Transport-retry knobs for the Personal Server read\n * ({@link PersonalServerTransportRetryOptions}). Defaults to 3 attempts with\n * exponential backoff. Retries fire only when fetch throws (the browser-PS\n * relay reconnect window), never on a received HTTP status, and never\n * re-sign a payment.\n */\n personalServerTransportRetry?: PersonalServerTransportRetryOptions;\n}\n\n/**\n * Controller-level escrow config — the {@link EscrowPaymentConfig} minus the\n * `signTypedData` and `chainId` the controller injects itself.\n */\nexport interface DirectEscrowConfig extends Omit<\n EscrowPaymentConfig,\n \"signTypedData\" | \"chainId\"\n> {\n /**\n * Chain id for the EIP-712 domain. Defaults to the controller's environment\n * (1480 for mainnet, 14800 for moksha).\n */\n chainId?: number;\n}\n\n/**\n * Server-side controller for the direct Data Portability flow.\n *\n * @typeParam T - Shape of the data returned by {@link DirectDataController.readApprovedData}.\n */\nexport interface DirectDataController {\n /** The on-chain address of the app, derived from `appPrivateKey`. */\n readonly appAddress: string;\n\n /**\n * The app's on-chain address — the address to fund and inspect in the Builder\n * activity report. Equivalent to {@link DirectDataController.appAddress}.\n *\n * @returns The app's `0x`-prefixed address.\n */\n getAppAddress(): string;\n\n /**\n * The app's full identity: its configured id/name/homepage plus the derived\n * on-chain address. Useful for telling builders which app address to fund or\n * look up.\n *\n * @returns `{ id, name, homepageUrl, address }`.\n */\n getAppIdentity(): AppIdentity;\n\n /**\n * Create an access request the user can approve.\n *\n * @param input - The post-approval return URL.\n * @returns `{ requestId, approvalUrl, appAddress }`.\n */\n createAccessRequest(input: { returnUrl: string }): Promise<AccessRequest>;\n\n /**\n * Fetch the current status of an access request.\n *\n * @param requestId - The `dcr_*` id from {@link DirectDataController.createAccessRequest}.\n * @returns `{ status, personalServerUrl?, grantId?, scope? }`.\n */\n getAccessRequestStatus(requestId: string): Promise<AccessRequestStatus>;\n\n /**\n * Read the approved data from the user's Personal Server.\n *\n * @remarks\n * Resolves the request to its grant + Personal Server and performs a Web3Signed\n * read. Hides the `402 Payment Required` flow by default: if a read needs\n * payment, it signs the Personal Server's payment challenge, retries with\n * `X-PAYMENT`, and attaches a {@link DirectPaymentReceipt} under `payment`\n * when the Personal Server returns one. After a successful read, the\n * controller acknowledges the DCR so Vana Web can close/redirect the approval\n * tab.\n *\n * @param input - The `dcr_*` request id to read.\n * @returns `{ scope, data, payment? }`.\n * @throws {@link AccessNotApprovedError} if the request is not approved.\n * @throws {@link PaymentRequiredError} if payment is required but unsettled.\n */\n readApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<ApprovedDataResult<T>>;\n}\n\nfunction isHexPrivateKey(value: string): value is Hex {\n return /^0x[0-9a-fA-F]{64}$/.test(value);\n}\n\nfunction isReadReadyStatus(status: AccessRequestStatusValue): boolean {\n return status === \"approved\" || status === \"ready_for_read\";\n}\n\n/**\n * Create a {@link DirectDataController}.\n *\n * @param config - Controller configuration (env, key, app identity, source, scopes).\n * @returns A ready-to-use controller.\n * @throws {@link DirectConfigError} when the key or scopes are invalid.\n */\nexport function createDirectDataController(\n config: DirectDataControllerConfig,\n): DirectDataController {\n // `appPrivateKey` is the documented field; `builderPrivateKey` is a\n // deprecated alias kept for backwards compatibility.\n const privateKey = config.appPrivateKey ?? config.builderPrivateKey;\n if (!privateKey || !isHexPrivateKey(privateKey)) {\n throw new DirectConfigError(\n \"appPrivateKey must be a 0x-prefixed 32-byte hex string\",\n );\n }\n if (!config.scopes || config.scopes.length === 0) {\n throw new DirectConfigError(\"At least one scope is required\");\n }\n // Validate scopes eagerly so misconfiguration fails at construction.\n for (const scope of config.scopes) {\n parseScope(scope);\n }\n\n const env: DirectEnv = config.env ?? \"production\";\n const network: DirectNetwork = config.network ?? getDirectDefaultNetwork(env);\n const defaultEndpoints = getDirectEndpoints(env);\n const chainId = config.endpoints?.chainId ?? getDirectNetworkChainId(network);\n const endpoints: DirectServiceEndpoints = {\n ...defaultEndpoints,\n ...config.endpoints,\n chainId,\n };\n\n const account = privateKeyToAccount(privateKey as Hex);\n const signMessage: Web3SignedSignFn = (message: string) =>\n account.signMessage({ message });\n // viem's account.signTypedData satisfies the structural SignTypedDataFn used\n // by the escrow GenericPayment signer.\n const signTypedData = account.signTypedData as unknown as SignTypedDataFn;\n const accessRequestClient: AccessRequestClient =\n config.accessRequestClient ??\n createDefaultAccessRequestClient({\n baseUrl: endpoints.accessRequestBaseUrl,\n approvalBaseUrl: endpoints.approvalAppBaseUrl,\n fetchFn: config.fetchFn,\n appAddress: account.address,\n signMessage,\n });\n\n // Build the escrow payment config, defaulting from the per-network endpoints\n // table and the contract registry when `config.escrow` is omitted or partial.\n const escrowChainId = config.escrow?.chainId ?? chainId;\n const defaultEscrowContract =\n CONTRACTS.DataPortabilityEscrow.addresses[\n escrowChainId as keyof typeof CONTRACTS.DataPortabilityEscrow.addresses\n ] ?? undefined;\n if (!config.escrow?.escrowContract && !defaultEscrowContract) {\n throw new DirectConfigError(\n `No DataPortabilityEscrow address found in the registry for chainId ${escrowChainId}. ` +\n `Provide an explicit escrow.escrowContract in the controller config.`,\n );\n }\n const escrow: EscrowPaymentConfig = {\n client:\n config.escrow?.client ??\n createEscrowGatewayClient(endpoints.escrowGatewayUrl),\n escrowContract:\n config.escrow?.escrowContract ?? (defaultEscrowContract as `0x${string}`),\n chainId: escrowChainId,\n nonceSource: config.escrow?.nonceSource,\n signTypedData,\n };\n\n return {\n appAddress: account.address,\n\n getAppAddress(): string {\n return account.address;\n },\n\n getAppIdentity(): AppIdentity {\n return {\n id: config.app.id,\n name: config.app.name,\n homepageUrl: config.app.homepageUrl,\n address: account.address,\n };\n },\n\n async createAccessRequest(input): Promise<AccessRequest> {\n return accessRequestClient.createAccessRequest({\n appAddress: account.address,\n app: config.app,\n source: config.source,\n scopes: config.scopes,\n returnUrl: input.returnUrl,\n network,\n });\n },\n\n async getAccessRequestStatus(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n return accessRequestClient.getAccessRequestStatus(requestId);\n },\n\n async readApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<ApprovedDataResult<T>> {\n const status = await accessRequestClient.getAccessRequestStatus(\n input.requestId,\n );\n if (\n !isReadReadyStatus(status.status) ||\n !status.personalServerUrl ||\n !status.grantId ||\n !status.scope\n ) {\n throw new AccessNotApprovedError(\n \"Request is not approved or is missing grantId/scope/personalServerUrl\",\n {\n requestId: input.requestId,\n status: status.status,\n hasPersonalServerUrl: Boolean(status.personalServerUrl),\n hasGrantId: Boolean(status.grantId),\n hasScope: Boolean(status.scope),\n },\n );\n }\n\n const result = await readPersonalServerData({\n personalServerUrl: status.personalServerUrl,\n scope: status.scope,\n grantId: status.grantId,\n payerAddress: account.address,\n signMessage,\n escrow,\n fetchFn: config.personalServerFetch,\n transportRetry: config.personalServerTransportRetry,\n });\n try {\n await accessRequestClient.acknowledgeRead?.(input.requestId);\n } catch {\n // The read already succeeded; ack only drives Vana Web completion UX.\n }\n\n return {\n scope: status.scope,\n data: result.data as T,\n payment: result.payment,\n };\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -22,7 +22,7 @@
22
22
  */
23
23
  import { type FetchLike } from "./access-request-client";
24
24
  import { type EscrowPaymentConfig } from "./escrow-payment";
25
- import { type PersonalServerFetch } from "./personal-server-read";
25
+ import { type PersonalServerFetch, type PersonalServerTransportRetryOptions } from "./personal-server-read";
26
26
  import type { AccessRequest, AccessRequestClient, AccessRequestStatus, ApprovedDataResult, AppIdentity, DirectAppConfig, DirectEnv, DirectNetwork, DirectServiceEndpoints } from "./types";
27
27
  /** Configuration for {@link createDirectDataController}. */
28
28
  export interface DirectDataControllerConfig {
@@ -82,6 +82,14 @@ export interface DirectDataControllerConfig {
82
82
  fetchFn?: FetchLike;
83
83
  /** `fetch` used for the Personal Server read. Defaults to `globalThis.fetch`. */
84
84
  personalServerFetch?: PersonalServerFetch;
85
+ /**
86
+ * Transport-retry knobs for the Personal Server read
87
+ * ({@link PersonalServerTransportRetryOptions}). Defaults to 3 attempts with
88
+ * exponential backoff. Retries fire only when fetch throws (the browser-PS
89
+ * relay reconnect window), never on a received HTTP status, and never
90
+ * re-sign a payment.
91
+ */
92
+ personalServerTransportRetry?: PersonalServerTransportRetryOptions;
85
93
  }
86
94
  /**
87
95
  * Controller-level escrow config — the {@link EscrowPaymentConfig} minus the
@@ -115,7 +115,8 @@ function createDirectDataController(config) {
115
115
  payerAddress: account.address,
116
116
  signMessage,
117
117
  escrow,
118
- fetchFn: config.personalServerFetch
118
+ fetchFn: config.personalServerFetch,
119
+ transportRetry: config.personalServerTransportRetry
119
120
  });
120
121
  try {
121
122
  await accessRequestClient.acknowledgeRead?.(input.requestId);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/direct/controller.ts"],"sourcesContent":["/**\n * Direct Data Controller — the server-side facade for the two-tab Data\n * Portability flow.\n *\n * @remarks\n * One controller owns an app's private key, source, scopes, app identity, and\n * payment flow. It exposes the three methods the builder guide documents:\n *\n * - {@link DirectDataController.createAccessRequest} — start an approval request.\n * - {@link DirectDataController.getAccessRequestStatus} — poll while the Vana tab is open.\n * - {@link DirectDataController.readApprovedData} — read from the Personal Server,\n * handling 402 Payment Required.\n *\n * Access requests are created through the Vana Account access-request API; the\n * Personal Server read uses Web3Signed auth; and payment uses the DPv2 escrow\n * surface (`protocol/escrow`) — when a read returns `402`, the controller signs\n * a `GenericPayment` with the app key, settles it through the escrow gateway,\n * and retries.\n *\n * @category Direct\n * @module direct/controller\n */\n\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport type { Hex } from \"viem\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport { parseScope } from \"../protocol/scopes\";\nimport { createEscrowGatewayClient } from \"../protocol/escrow\";\nimport { CONTRACTS } from \"../generated/addresses\";\nimport {\n createDefaultAccessRequestClient,\n type FetchLike,\n} from \"./access-request-client\";\nimport {\n getDirectDefaultNetwork,\n getDirectEndpoints,\n getDirectNetworkChainId,\n} from \"./endpoints\";\nimport { AccessNotApprovedError, DirectConfigError } from \"./errors\";\nimport {\n type EscrowPaymentConfig,\n type SignTypedDataFn,\n} from \"./escrow-payment\";\nimport {\n readPersonalServerData,\n type PersonalServerFetch,\n} from \"./personal-server-read\";\nimport type {\n AccessRequest,\n AccessRequestClient,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n AppIdentity,\n DirectAppConfig,\n DirectEnv,\n DirectNetwork,\n DirectServiceEndpoints,\n} from \"./types\";\n\n/** Configuration for {@link createDirectDataController}. */\nexport interface DirectDataControllerConfig {\n /** Target environment. Defaults to `\"production\"`. */\n env?: DirectEnv;\n /**\n * Target Vana network for chain-aware defaults. Defaults to the selected\n * environment's historical network (`mainnet` for production, `moksha` for\n * dev). Use `network: \"moksha\"` with the default production env for\n * production app/API URLs on testnet.\n */\n network?: DirectNetwork;\n /**\n * The app private key (`0x`-prefixed, 32 bytes). Server-side only — this key\n * is the app's on-chain identity and is never exposed to the browser.\n */\n appPrivateKey?: string;\n /**\n * @deprecated Use {@link DirectDataControllerConfig.appPrivateKey}. Accepted as\n * a backwards-compatible alias; if both are set, `appPrivateKey` wins.\n */\n builderPrivateKey?: string;\n /** App identity advertised during approval. */\n app: DirectAppConfig;\n /** Data source key (e.g. `\"icloud_notes\"`). */\n source: string;\n /** Scopes to request (e.g. `[\"icloud_notes.notes\"]`). At least one required. */\n scopes: string[];\n /**\n * Override the resolved service endpoints (partial). Useful for pointing at a\n * non-standard deployment.\n */\n endpoints?: Partial<DirectServiceEndpoints>;\n /**\n * Client for the Vana Account access-request API. Defaults to a client against\n * the resolved Vana Account endpoints; inject your own to point at a custom\n * deployment or to supply a test double.\n */\n accessRequestClient?: AccessRequestClient;\n /**\n * Escrow settlement config used when a Personal Server read returns `402`.\n *\n * @remarks\n * Wires the DPv2 escrow gateway (`protocol/escrow`). The controller supplies\n * the EIP-712 `signTypedData` from the app key automatically.\n *\n * When omitted (or partially omitted), the SDK derives defaults from the\n * per-network endpoints table and the contract registry:\n * - `client` defaults to a gateway client at `endpoints.escrowGatewayUrl`\n * - `escrowContract` defaults to `CONTRACTS.DataPortabilityEscrow.addresses[chainId]`\n * - `chainId` defaults to the controller's resolved chain id\n *\n * Provide this field only to override a specific default.\n */\n escrow?: Partial<DirectEscrowConfig>;\n /** `fetch` used by the default access-request client. Defaults to `globalThis.fetch`. */\n fetchFn?: FetchLike;\n /** `fetch` used for the Personal Server read. Defaults to `globalThis.fetch`. */\n personalServerFetch?: PersonalServerFetch;\n}\n\n/**\n * Controller-level escrow config — the {@link EscrowPaymentConfig} minus the\n * `signTypedData` and `chainId` the controller injects itself.\n */\nexport interface DirectEscrowConfig extends Omit<\n EscrowPaymentConfig,\n \"signTypedData\" | \"chainId\"\n> {\n /**\n * Chain id for the EIP-712 domain. Defaults to the controller's environment\n * (1480 for mainnet, 14800 for moksha).\n */\n chainId?: number;\n}\n\n/**\n * Server-side controller for the direct Data Portability flow.\n *\n * @typeParam T - Shape of the data returned by {@link DirectDataController.readApprovedData}.\n */\nexport interface DirectDataController {\n /** The on-chain address of the app, derived from `appPrivateKey`. */\n readonly appAddress: string;\n\n /**\n * The app's on-chain address — the address to fund and inspect in the Builder\n * activity report. Equivalent to {@link DirectDataController.appAddress}.\n *\n * @returns The app's `0x`-prefixed address.\n */\n getAppAddress(): string;\n\n /**\n * The app's full identity: its configured id/name/homepage plus the derived\n * on-chain address. Useful for telling builders which app address to fund or\n * look up.\n *\n * @returns `{ id, name, homepageUrl, address }`.\n */\n getAppIdentity(): AppIdentity;\n\n /**\n * Create an access request the user can approve.\n *\n * @param input - The post-approval return URL.\n * @returns `{ requestId, approvalUrl, appAddress }`.\n */\n createAccessRequest(input: { returnUrl: string }): Promise<AccessRequest>;\n\n /**\n * Fetch the current status of an access request.\n *\n * @param requestId - The `dcr_*` id from {@link DirectDataController.createAccessRequest}.\n * @returns `{ status, personalServerUrl?, grantId?, scope? }`.\n */\n getAccessRequestStatus(requestId: string): Promise<AccessRequestStatus>;\n\n /**\n * Read the approved data from the user's Personal Server.\n *\n * @remarks\n * Resolves the request to its grant + Personal Server and performs a Web3Signed\n * read. Hides the `402 Payment Required` flow by default: if a read needs\n * payment, it signs the Personal Server's payment challenge, retries with\n * `X-PAYMENT`, and attaches a {@link DirectPaymentReceipt} under `payment`\n * when the Personal Server returns one. After a successful read, the\n * controller acknowledges the DCR so Vana Web can close/redirect the approval\n * tab.\n *\n * @param input - The `dcr_*` request id to read.\n * @returns `{ scope, data, payment? }`.\n * @throws {@link AccessNotApprovedError} if the request is not approved.\n * @throws {@link PaymentRequiredError} if payment is required but unsettled.\n */\n readApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<ApprovedDataResult<T>>;\n}\n\nfunction isHexPrivateKey(value: string): value is Hex {\n return /^0x[0-9a-fA-F]{64}$/.test(value);\n}\n\nfunction isReadReadyStatus(status: AccessRequestStatusValue): boolean {\n return status === \"approved\" || status === \"ready_for_read\";\n}\n\n/**\n * Create a {@link DirectDataController}.\n *\n * @param config - Controller configuration (env, key, app identity, source, scopes).\n * @returns A ready-to-use controller.\n * @throws {@link DirectConfigError} when the key or scopes are invalid.\n */\nexport function createDirectDataController(\n config: DirectDataControllerConfig,\n): DirectDataController {\n // `appPrivateKey` is the documented field; `builderPrivateKey` is a\n // deprecated alias kept for backwards compatibility.\n const privateKey = config.appPrivateKey ?? config.builderPrivateKey;\n if (!privateKey || !isHexPrivateKey(privateKey)) {\n throw new DirectConfigError(\n \"appPrivateKey must be a 0x-prefixed 32-byte hex string\",\n );\n }\n if (!config.scopes || config.scopes.length === 0) {\n throw new DirectConfigError(\"At least one scope is required\");\n }\n // Validate scopes eagerly so misconfiguration fails at construction.\n for (const scope of config.scopes) {\n parseScope(scope);\n }\n\n const env: DirectEnv = config.env ?? \"production\";\n const network: DirectNetwork = config.network ?? getDirectDefaultNetwork(env);\n const defaultEndpoints = getDirectEndpoints(env);\n const chainId = config.endpoints?.chainId ?? getDirectNetworkChainId(network);\n const endpoints: DirectServiceEndpoints = {\n ...defaultEndpoints,\n ...config.endpoints,\n chainId,\n };\n\n const account = privateKeyToAccount(privateKey as Hex);\n const signMessage: Web3SignedSignFn = (message: string) =>\n account.signMessage({ message });\n // viem's account.signTypedData satisfies the structural SignTypedDataFn used\n // by the escrow GenericPayment signer.\n const signTypedData = account.signTypedData as unknown as SignTypedDataFn;\n const accessRequestClient: AccessRequestClient =\n config.accessRequestClient ??\n createDefaultAccessRequestClient({\n baseUrl: endpoints.accessRequestBaseUrl,\n approvalBaseUrl: endpoints.approvalAppBaseUrl,\n fetchFn: config.fetchFn,\n appAddress: account.address,\n signMessage,\n });\n\n // Build the escrow payment config, defaulting from the per-network endpoints\n // table and the contract registry when `config.escrow` is omitted or partial.\n const escrowChainId = config.escrow?.chainId ?? chainId;\n const defaultEscrowContract =\n CONTRACTS.DataPortabilityEscrow.addresses[\n escrowChainId as keyof typeof CONTRACTS.DataPortabilityEscrow.addresses\n ] ?? undefined;\n if (!config.escrow?.escrowContract && !defaultEscrowContract) {\n throw new DirectConfigError(\n `No DataPortabilityEscrow address found in the registry for chainId ${escrowChainId}. ` +\n `Provide an explicit escrow.escrowContract in the controller config.`,\n );\n }\n const escrow: EscrowPaymentConfig = {\n client:\n config.escrow?.client ??\n createEscrowGatewayClient(endpoints.escrowGatewayUrl),\n escrowContract:\n config.escrow?.escrowContract ?? (defaultEscrowContract as `0x${string}`),\n chainId: escrowChainId,\n nonceSource: config.escrow?.nonceSource,\n signTypedData,\n };\n\n return {\n appAddress: account.address,\n\n getAppAddress(): string {\n return account.address;\n },\n\n getAppIdentity(): AppIdentity {\n return {\n id: config.app.id,\n name: config.app.name,\n homepageUrl: config.app.homepageUrl,\n address: account.address,\n };\n },\n\n async createAccessRequest(input): Promise<AccessRequest> {\n return accessRequestClient.createAccessRequest({\n appAddress: account.address,\n app: config.app,\n source: config.source,\n scopes: config.scopes,\n returnUrl: input.returnUrl,\n network,\n });\n },\n\n async getAccessRequestStatus(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n return accessRequestClient.getAccessRequestStatus(requestId);\n },\n\n async readApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<ApprovedDataResult<T>> {\n const status = await accessRequestClient.getAccessRequestStatus(\n input.requestId,\n );\n if (\n !isReadReadyStatus(status.status) ||\n !status.personalServerUrl ||\n !status.grantId ||\n !status.scope\n ) {\n throw new AccessNotApprovedError(\n \"Request is not approved or is missing grantId/scope/personalServerUrl\",\n {\n requestId: input.requestId,\n status: status.status,\n hasPersonalServerUrl: Boolean(status.personalServerUrl),\n hasGrantId: Boolean(status.grantId),\n hasScope: Boolean(status.scope),\n },\n );\n }\n\n const result = await readPersonalServerData({\n personalServerUrl: status.personalServerUrl,\n scope: status.scope,\n grantId: status.grantId,\n payerAddress: account.address,\n signMessage,\n escrow,\n fetchFn: config.personalServerFetch,\n });\n try {\n await accessRequestClient.acknowledgeRead?.(input.requestId);\n } catch {\n // The read already succeeded; ack only drives Vana Web completion UX.\n }\n\n return {\n scope: status.scope,\n data: result.data as T,\n payment: result.payment,\n };\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/direct/controller.ts"],"sourcesContent":["/**\n * Direct Data Controller — the server-side facade for the two-tab Data\n * Portability flow.\n *\n * @remarks\n * One controller owns an app's private key, source, scopes, app identity, and\n * payment flow. It exposes the three methods the builder guide documents:\n *\n * - {@link DirectDataController.createAccessRequest} — start an approval request.\n * - {@link DirectDataController.getAccessRequestStatus} — poll while the Vana tab is open.\n * - {@link DirectDataController.readApprovedData} — read from the Personal Server,\n * handling 402 Payment Required.\n *\n * Access requests are created through the Vana Account access-request API; the\n * Personal Server read uses Web3Signed auth; and payment uses the DPv2 escrow\n * surface (`protocol/escrow`) — when a read returns `402`, the controller signs\n * a `GenericPayment` with the app key, settles it through the escrow gateway,\n * and retries.\n *\n * @category Direct\n * @module direct/controller\n */\n\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport type { Hex } from \"viem\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport { parseScope } from \"../protocol/scopes\";\nimport { createEscrowGatewayClient } from \"../protocol/escrow\";\nimport { CONTRACTS } from \"../generated/addresses\";\nimport {\n createDefaultAccessRequestClient,\n type FetchLike,\n} from \"./access-request-client\";\nimport {\n getDirectDefaultNetwork,\n getDirectEndpoints,\n getDirectNetworkChainId,\n} from \"./endpoints\";\nimport { AccessNotApprovedError, DirectConfigError } from \"./errors\";\nimport {\n type EscrowPaymentConfig,\n type SignTypedDataFn,\n} from \"./escrow-payment\";\nimport {\n readPersonalServerData,\n type PersonalServerFetch,\n type PersonalServerTransportRetryOptions,\n} from \"./personal-server-read\";\nimport type {\n AccessRequest,\n AccessRequestClient,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n AppIdentity,\n DirectAppConfig,\n DirectEnv,\n DirectNetwork,\n DirectServiceEndpoints,\n} from \"./types\";\n\n/** Configuration for {@link createDirectDataController}. */\nexport interface DirectDataControllerConfig {\n /** Target environment. Defaults to `\"production\"`. */\n env?: DirectEnv;\n /**\n * Target Vana network for chain-aware defaults. Defaults to the selected\n * environment's historical network (`mainnet` for production, `moksha` for\n * dev). Use `network: \"moksha\"` with the default production env for\n * production app/API URLs on testnet.\n */\n network?: DirectNetwork;\n /**\n * The app private key (`0x`-prefixed, 32 bytes). Server-side only — this key\n * is the app's on-chain identity and is never exposed to the browser.\n */\n appPrivateKey?: string;\n /**\n * @deprecated Use {@link DirectDataControllerConfig.appPrivateKey}. Accepted as\n * a backwards-compatible alias; if both are set, `appPrivateKey` wins.\n */\n builderPrivateKey?: string;\n /** App identity advertised during approval. */\n app: DirectAppConfig;\n /** Data source key (e.g. `\"icloud_notes\"`). */\n source: string;\n /** Scopes to request (e.g. `[\"icloud_notes.notes\"]`). At least one required. */\n scopes: string[];\n /**\n * Override the resolved service endpoints (partial). Useful for pointing at a\n * non-standard deployment.\n */\n endpoints?: Partial<DirectServiceEndpoints>;\n /**\n * Client for the Vana Account access-request API. Defaults to a client against\n * the resolved Vana Account endpoints; inject your own to point at a custom\n * deployment or to supply a test double.\n */\n accessRequestClient?: AccessRequestClient;\n /**\n * Escrow settlement config used when a Personal Server read returns `402`.\n *\n * @remarks\n * Wires the DPv2 escrow gateway (`protocol/escrow`). The controller supplies\n * the EIP-712 `signTypedData` from the app key automatically.\n *\n * When omitted (or partially omitted), the SDK derives defaults from the\n * per-network endpoints table and the contract registry:\n * - `client` defaults to a gateway client at `endpoints.escrowGatewayUrl`\n * - `escrowContract` defaults to `CONTRACTS.DataPortabilityEscrow.addresses[chainId]`\n * - `chainId` defaults to the controller's resolved chain id\n *\n * Provide this field only to override a specific default.\n */\n escrow?: Partial<DirectEscrowConfig>;\n /** `fetch` used by the default access-request client. Defaults to `globalThis.fetch`. */\n fetchFn?: FetchLike;\n /** `fetch` used for the Personal Server read. Defaults to `globalThis.fetch`. */\n personalServerFetch?: PersonalServerFetch;\n /**\n * Transport-retry knobs for the Personal Server read\n * ({@link PersonalServerTransportRetryOptions}). Defaults to 3 attempts with\n * exponential backoff. Retries fire only when fetch throws (the browser-PS\n * relay reconnect window), never on a received HTTP status, and never\n * re-sign a payment.\n */\n personalServerTransportRetry?: PersonalServerTransportRetryOptions;\n}\n\n/**\n * Controller-level escrow config — the {@link EscrowPaymentConfig} minus the\n * `signTypedData` and `chainId` the controller injects itself.\n */\nexport interface DirectEscrowConfig extends Omit<\n EscrowPaymentConfig,\n \"signTypedData\" | \"chainId\"\n> {\n /**\n * Chain id for the EIP-712 domain. Defaults to the controller's environment\n * (1480 for mainnet, 14800 for moksha).\n */\n chainId?: number;\n}\n\n/**\n * Server-side controller for the direct Data Portability flow.\n *\n * @typeParam T - Shape of the data returned by {@link DirectDataController.readApprovedData}.\n */\nexport interface DirectDataController {\n /** The on-chain address of the app, derived from `appPrivateKey`. */\n readonly appAddress: string;\n\n /**\n * The app's on-chain address — the address to fund and inspect in the Builder\n * activity report. Equivalent to {@link DirectDataController.appAddress}.\n *\n * @returns The app's `0x`-prefixed address.\n */\n getAppAddress(): string;\n\n /**\n * The app's full identity: its configured id/name/homepage plus the derived\n * on-chain address. Useful for telling builders which app address to fund or\n * look up.\n *\n * @returns `{ id, name, homepageUrl, address }`.\n */\n getAppIdentity(): AppIdentity;\n\n /**\n * Create an access request the user can approve.\n *\n * @param input - The post-approval return URL.\n * @returns `{ requestId, approvalUrl, appAddress }`.\n */\n createAccessRequest(input: { returnUrl: string }): Promise<AccessRequest>;\n\n /**\n * Fetch the current status of an access request.\n *\n * @param requestId - The `dcr_*` id from {@link DirectDataController.createAccessRequest}.\n * @returns `{ status, personalServerUrl?, grantId?, scope? }`.\n */\n getAccessRequestStatus(requestId: string): Promise<AccessRequestStatus>;\n\n /**\n * Read the approved data from the user's Personal Server.\n *\n * @remarks\n * Resolves the request to its grant + Personal Server and performs a Web3Signed\n * read. Hides the `402 Payment Required` flow by default: if a read needs\n * payment, it signs the Personal Server's payment challenge, retries with\n * `X-PAYMENT`, and attaches a {@link DirectPaymentReceipt} under `payment`\n * when the Personal Server returns one. After a successful read, the\n * controller acknowledges the DCR so Vana Web can close/redirect the approval\n * tab.\n *\n * @param input - The `dcr_*` request id to read.\n * @returns `{ scope, data, payment? }`.\n * @throws {@link AccessNotApprovedError} if the request is not approved.\n * @throws {@link PaymentRequiredError} if payment is required but unsettled.\n */\n readApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<ApprovedDataResult<T>>;\n}\n\nfunction isHexPrivateKey(value: string): value is Hex {\n return /^0x[0-9a-fA-F]{64}$/.test(value);\n}\n\nfunction isReadReadyStatus(status: AccessRequestStatusValue): boolean {\n return status === \"approved\" || status === \"ready_for_read\";\n}\n\n/**\n * Create a {@link DirectDataController}.\n *\n * @param config - Controller configuration (env, key, app identity, source, scopes).\n * @returns A ready-to-use controller.\n * @throws {@link DirectConfigError} when the key or scopes are invalid.\n */\nexport function createDirectDataController(\n config: DirectDataControllerConfig,\n): DirectDataController {\n // `appPrivateKey` is the documented field; `builderPrivateKey` is a\n // deprecated alias kept for backwards compatibility.\n const privateKey = config.appPrivateKey ?? config.builderPrivateKey;\n if (!privateKey || !isHexPrivateKey(privateKey)) {\n throw new DirectConfigError(\n \"appPrivateKey must be a 0x-prefixed 32-byte hex string\",\n );\n }\n if (!config.scopes || config.scopes.length === 0) {\n throw new DirectConfigError(\"At least one scope is required\");\n }\n // Validate scopes eagerly so misconfiguration fails at construction.\n for (const scope of config.scopes) {\n parseScope(scope);\n }\n\n const env: DirectEnv = config.env ?? \"production\";\n const network: DirectNetwork = config.network ?? getDirectDefaultNetwork(env);\n const defaultEndpoints = getDirectEndpoints(env);\n const chainId = config.endpoints?.chainId ?? getDirectNetworkChainId(network);\n const endpoints: DirectServiceEndpoints = {\n ...defaultEndpoints,\n ...config.endpoints,\n chainId,\n };\n\n const account = privateKeyToAccount(privateKey as Hex);\n const signMessage: Web3SignedSignFn = (message: string) =>\n account.signMessage({ message });\n // viem's account.signTypedData satisfies the structural SignTypedDataFn used\n // by the escrow GenericPayment signer.\n const signTypedData = account.signTypedData as unknown as SignTypedDataFn;\n const accessRequestClient: AccessRequestClient =\n config.accessRequestClient ??\n createDefaultAccessRequestClient({\n baseUrl: endpoints.accessRequestBaseUrl,\n approvalBaseUrl: endpoints.approvalAppBaseUrl,\n fetchFn: config.fetchFn,\n appAddress: account.address,\n signMessage,\n });\n\n // Build the escrow payment config, defaulting from the per-network endpoints\n // table and the contract registry when `config.escrow` is omitted or partial.\n const escrowChainId = config.escrow?.chainId ?? chainId;\n const defaultEscrowContract =\n CONTRACTS.DataPortabilityEscrow.addresses[\n escrowChainId as keyof typeof CONTRACTS.DataPortabilityEscrow.addresses\n ] ?? undefined;\n if (!config.escrow?.escrowContract && !defaultEscrowContract) {\n throw new DirectConfigError(\n `No DataPortabilityEscrow address found in the registry for chainId ${escrowChainId}. ` +\n `Provide an explicit escrow.escrowContract in the controller config.`,\n );\n }\n const escrow: EscrowPaymentConfig = {\n client:\n config.escrow?.client ??\n createEscrowGatewayClient(endpoints.escrowGatewayUrl),\n escrowContract:\n config.escrow?.escrowContract ?? (defaultEscrowContract as `0x${string}`),\n chainId: escrowChainId,\n nonceSource: config.escrow?.nonceSource,\n signTypedData,\n };\n\n return {\n appAddress: account.address,\n\n getAppAddress(): string {\n return account.address;\n },\n\n getAppIdentity(): AppIdentity {\n return {\n id: config.app.id,\n name: config.app.name,\n homepageUrl: config.app.homepageUrl,\n address: account.address,\n };\n },\n\n async createAccessRequest(input): Promise<AccessRequest> {\n return accessRequestClient.createAccessRequest({\n appAddress: account.address,\n app: config.app,\n source: config.source,\n scopes: config.scopes,\n returnUrl: input.returnUrl,\n network,\n });\n },\n\n async getAccessRequestStatus(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n return accessRequestClient.getAccessRequestStatus(requestId);\n },\n\n async readApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<ApprovedDataResult<T>> {\n const status = await accessRequestClient.getAccessRequestStatus(\n input.requestId,\n );\n if (\n !isReadReadyStatus(status.status) ||\n !status.personalServerUrl ||\n !status.grantId ||\n !status.scope\n ) {\n throw new AccessNotApprovedError(\n \"Request is not approved or is missing grantId/scope/personalServerUrl\",\n {\n requestId: input.requestId,\n status: status.status,\n hasPersonalServerUrl: Boolean(status.personalServerUrl),\n hasGrantId: Boolean(status.grantId),\n hasScope: Boolean(status.scope),\n },\n );\n }\n\n const result = await readPersonalServerData({\n personalServerUrl: status.personalServerUrl,\n scope: status.scope,\n grantId: status.grantId,\n payerAddress: account.address,\n signMessage,\n escrow,\n fetchFn: config.personalServerFetch,\n transportRetry: config.personalServerTransportRetry,\n });\n try {\n await accessRequestClient.acknowledgeRead?.(input.requestId);\n } catch {\n // The read already succeeded; ack only drives Vana Web completion UX.\n }\n\n return {\n scope: status.scope,\n data: result.data as T,\n payment: result.payment,\n };\n },\n };\n}\n"],"mappings":"AAuBA,SAAS,2BAA2B;AAGpC,SAAS,kBAAkB;AAC3B,SAAS,iCAAiC;AAC1C,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,wBAAwB,yBAAyB;AAK1D;AAAA,EACE;AAAA,OAGK;AAiKP,SAAS,gBAAgB,OAA6B;AACpD,SAAO,sBAAsB,KAAK,KAAK;AACzC;AAEA,SAAS,kBAAkB,QAA2C;AACpE,SAAO,WAAW,cAAc,WAAW;AAC7C;AASO,SAAS,2BACd,QACsB;AAGtB,QAAM,aAAa,OAAO,iBAAiB,OAAO;AAClD,MAAI,CAAC,cAAc,CAAC,gBAAgB,UAAU,GAAG;AAC/C,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,OAAO,UAAU,OAAO,OAAO,WAAW,GAAG;AAChD,UAAM,IAAI,kBAAkB,gCAAgC;AAAA,EAC9D;AAEA,aAAW,SAAS,OAAO,QAAQ;AACjC,eAAW,KAAK;AAAA,EAClB;AAEA,QAAM,MAAiB,OAAO,OAAO;AACrC,QAAM,UAAyB,OAAO,WAAW,wBAAwB,GAAG;AAC5E,QAAM,mBAAmB,mBAAmB,GAAG;AAC/C,QAAM,UAAU,OAAO,WAAW,WAAW,wBAAwB,OAAO;AAC5E,QAAM,YAAoC;AAAA,IACxC,GAAG;AAAA,IACH,GAAG,OAAO;AAAA,IACV;AAAA,EACF;AAEA,QAAM,UAAU,oBAAoB,UAAiB;AACrD,QAAM,cAAgC,CAAC,YACrC,QAAQ,YAAY,EAAE,QAAQ,CAAC;AAGjC,QAAM,gBAAgB,QAAQ;AAC9B,QAAM,sBACJ,OAAO,uBACP,iCAAiC;AAAA,IAC/B,SAAS,UAAU;AAAA,IACnB,iBAAiB,UAAU;AAAA,IAC3B,SAAS,OAAO;AAAA,IAChB,YAAY,QAAQ;AAAA,IACpB;AAAA,EACF,CAAC;AAIH,QAAM,gBAAgB,OAAO,QAAQ,WAAW;AAChD,QAAM,wBACJ,UAAU,sBAAsB,UAC9B,aACF,KAAK;AACP,MAAI,CAAC,OAAO,QAAQ,kBAAkB,CAAC,uBAAuB;AAC5D,UAAM,IAAI;AAAA,MACR,sEAAsE,aAAa;AAAA,IAErF;AAAA,EACF;AACA,QAAM,SAA8B;AAAA,IAClC,QACE,OAAO,QAAQ,UACf,0BAA0B,UAAU,gBAAgB;AAAA,IACtD,gBACE,OAAO,QAAQ,kBAAmB;AAAA,IACpC,SAAS;AAAA,IACT,aAAa,OAAO,QAAQ;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO;AAAA,IACL,YAAY,QAAQ;AAAA,IAEpB,gBAAwB;AACtB,aAAO,QAAQ;AAAA,IACjB;AAAA,IAEA,iBAA8B;AAC5B,aAAO;AAAA,QACL,IAAI,OAAO,IAAI;AAAA,QACf,MAAM,OAAO,IAAI;AAAA,QACjB,aAAa,OAAO,IAAI;AAAA,QACxB,SAAS,QAAQ;AAAA,MACnB;AAAA,IACF;AAAA,IAEA,MAAM,oBAAoB,OAA+B;AACvD,aAAO,oBAAoB,oBAAoB;AAAA,QAC7C,YAAY,QAAQ;AAAA,QACpB,KAAK,OAAO;AAAA,QACZ,QAAQ,OAAO;AAAA,QACf,QAAQ,OAAO;AAAA,QACf,WAAW,MAAM;AAAA,QACjB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,uBACJ,WAC8B;AAC9B,aAAO,oBAAoB,uBAAuB,SAAS;AAAA,IAC7D;AAAA,IAEA,MAAM,iBAA8B,OAED;AACjC,YAAM,SAAS,MAAM,oBAAoB;AAAA,QACvC,MAAM;AAAA,MACR;AACA,UACE,CAAC,kBAAkB,OAAO,MAAM,KAChC,CAAC,OAAO,qBACR,CAAC,OAAO,WACR,CAAC,OAAO,OACR;AACA,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,YACE,WAAW,MAAM;AAAA,YACjB,QAAQ,OAAO;AAAA,YACf,sBAAsB,QAAQ,OAAO,iBAAiB;AAAA,YACtD,YAAY,QAAQ,OAAO,OAAO;AAAA,YAClC,UAAU,QAAQ,OAAO,KAAK;AAAA,UAChC;AAAA,QACF;AAAA,MACF;AAEA,YAAM,SAAS,MAAM,uBAAuB;AAAA,QAC1C,mBAAmB,OAAO;AAAA,QAC1B,OAAO,OAAO;AAAA,QACd,SAAS,OAAO;AAAA,QAChB,cAAc,QAAQ;AAAA,QACtB;AAAA,QACA;AAAA,QACA,SAAS,OAAO;AAAA,QAChB,gBAAgB,OAAO;AAAA,MACzB,CAAC;AACD,UAAI;AACF,cAAM,oBAAoB,kBAAkB,MAAM,SAAS;AAAA,MAC7D,QAAQ;AAAA,MAER;AAEA,aAAO;AAAA,QACL,OAAO,OAAO;AAAA,QACd,MAAM,OAAO;AAAA,QACb,SAAS,OAAO;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -28,6 +28,42 @@ var import_web3_signed_builder = require("../auth/web3-signed-builder");
28
28
  var import_escrow = require("../protocol/escrow");
29
29
  var import_escrow_payment = require("./escrow-payment");
30
30
  var import_errors = require("./errors");
31
+ const TRANSPORT_RETRY_DEFAULTS = {
32
+ attempts: 3,
33
+ initialDelayMs: 1e3,
34
+ maxDelayMs: 5e3
35
+ };
36
+ function resolveAttempts(attempts) {
37
+ return Number.isFinite(attempts) ? Math.max(1, Math.floor(attempts)) : 1;
38
+ }
39
+ function isAbortError(error) {
40
+ return error instanceof Error && error.name === "AbortError";
41
+ }
42
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
43
+ async function fetchWithTransportRetry(fetchFn, buildRequest, retry) {
44
+ const attempts = resolveAttempts(retry.attempts);
45
+ let lastError;
46
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
47
+ if (attempt > 0) {
48
+ await sleep(
49
+ Math.min(retry.maxDelayMs, retry.initialDelayMs * 2 ** (attempt - 1))
50
+ );
51
+ }
52
+ const request = await buildRequest();
53
+ try {
54
+ return await fetchFn(request.url, {
55
+ method: request.method,
56
+ headers: request.headers
57
+ });
58
+ } catch (error) {
59
+ if (isAbortError(error)) {
60
+ throw error;
61
+ }
62
+ lastError = error;
63
+ }
64
+ }
65
+ throw lastError;
66
+ }
31
67
  function stripTrailingSlash(url) {
32
68
  return url.replace(/\/+$/, "");
33
69
  }
@@ -145,16 +181,21 @@ async function readPersonalServerData(params) {
145
181
  "No fetch implementation available for Personal Server read"
146
182
  );
147
183
  }
148
- const initial = await buildPersonalServerDataReadRequest({
184
+ const transportRetry = {
185
+ ...TRANSPORT_RETRY_DEFAULTS,
186
+ ...params.transportRetry
187
+ };
188
+ const buildRequest = () => buildPersonalServerDataReadRequest({
149
189
  personalServerUrl: params.personalServerUrl,
150
190
  scope: params.scope,
151
191
  grantId: params.grantId,
152
192
  signMessage: params.signMessage
153
193
  });
154
- let res = await fetchFn(initial.url, {
155
- method: initial.method,
156
- headers: initial.headers
157
- });
194
+ let res = await fetchWithTransportRetry(
195
+ fetchFn,
196
+ buildRequest,
197
+ transportRetry
198
+ );
158
199
  let payment;
159
200
  if (res.status === 402) {
160
201
  const required = await parsePersonalServerPaymentRequired(
@@ -188,16 +229,17 @@ async function readPersonalServerData(params) {
188
229
  required,
189
230
  config: params.escrow
190
231
  });
191
- const retry = await buildPersonalServerDataReadRequest({
192
- personalServerUrl: params.personalServerUrl,
193
- scope: params.scope,
194
- grantId: params.grantId,
195
- signMessage: params.signMessage
196
- });
197
- res = await fetchFn(retry.url, {
198
- method: retry.method,
199
- headers: { ...retry.headers, "X-PAYMENT": paymentHeader }
200
- });
232
+ res = await fetchWithTransportRetry(
233
+ fetchFn,
234
+ async () => {
235
+ const retry = await buildRequest();
236
+ return {
237
+ ...retry,
238
+ headers: { ...retry.headers, "X-PAYMENT": paymentHeader }
239
+ };
240
+ },
241
+ transportRetry
242
+ );
201
243
  if (res.status === 402) {
202
244
  throw new import_errors.PaymentRequiredError(
203
245
  "Personal Server still requires payment after escrow settlement",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/direct/personal-server-read.ts"],"sourcesContent":["/**\n * Personal Server data-read request builder and the 402 -> escrow-pay -> retry loop.\n *\n * @remarks\n * The read targets the Personal Server data path (`/v1/data/{scope}`),\n * authenticates with a Web3Signed header (built on {@link buildWeb3SignedHeader}),\n * and — on `402 Payment Required` — settles the grant's data-access fee through\n * the DPv2 escrow gateway and retries once.\n *\n * The 402 body is parsed into a {@link PersonalServerPaymentRequired} (grant id,\n * asset, and amount owed), which drives the escrow settlement.\n *\n * @category Direct\n * @module direct/personal-server-read\n */\n\nimport { buildWeb3SignedHeader } from \"../auth/web3-signed-builder\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport {\n NATIVE_ASSET_ADDRESS,\n type EscrowAccessRecord,\n} from \"../protocol/escrow\";\nimport {\n buildGrantPaymentHeader,\n GRANT_OP_TYPE,\n paymentReceiptFromHeader,\n type EscrowPaymentConfig,\n} from \"./escrow-payment\";\nimport { PaymentRequiredError, PersonalServerReadError } from \"./errors\";\nimport type {\n DirectPaymentReceipt,\n PersonalServerPaymentRequired,\n} from \"./types\";\n\n/** Minimal `Response`-like shape so the read loop is testable without a DOM. */\nexport interface FetchResponseLike {\n ok: boolean;\n status: number;\n statusText: string;\n headers: { get(name: string): string | null };\n json(): Promise<unknown>;\n text(): Promise<string>;\n}\n\n/** Minimal `fetch` signature accepted by {@link readPersonalServerData}. */\nexport type PersonalServerFetch = (\n input: string,\n init: {\n method: string;\n headers: Record<string, string>;\n },\n) => Promise<FetchResponseLike>;\n\n/** A built, ready-to-send Personal Server data read request. */\nexport interface PersonalServerDataReadRequest {\n /** Absolute URL of the read endpoint. */\n url: string;\n /** HTTP method (always `\"GET\"`). */\n method: \"GET\";\n /** Request path used in the Web3Signed `uri` claim (e.g. `/v1/data/{scope}`). */\n path: string;\n /** Headers including the Web3Signed `Authorization` value. */\n headers: Record<string, string>;\n}\n\n/** Outcome of {@link readPersonalServerData}: the payload plus optional receipt. */\nexport interface PersonalServerReadResult {\n /** The decoded JSON payload returned by the Personal Server. */\n data: unknown;\n /** Present only when this read required (and settled) a payment. */\n payment?: DirectPaymentReceipt;\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\n/** Compute the data path for a scope (`/v1/data/{scope}`). */\nexport function dataPathForScope(scope: string): string {\n return `/v1/data/${encodeURIComponent(scope)}`;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction stringField(\n record: Record<string, unknown> | undefined,\n field: string,\n): string | undefined {\n const value = record?.[field];\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction parseAccessRecord(value: unknown): EscrowAccessRecord | undefined {\n const record = asRecord(value);\n const dataPointId = stringField(record, \"dataPointId\");\n const version = stringField(record, \"version\");\n const accessor = stringField(record, \"accessor\");\n const recordId = stringField(record, \"recordId\");\n const signature = stringField(record, \"signature\");\n\n if (!dataPointId || !version || !accessor || !recordId || !signature) {\n return undefined;\n }\n\n return {\n dataPointId: dataPointId as `0x${string}`,\n version,\n accessor: accessor as `0x${string}`,\n recordId: recordId as `0x${string}`,\n signature: signature as `0x${string}`,\n };\n}\n\nfunction isBytes32Hex(value: string): boolean {\n return /^0x[0-9a-fA-F]{64}$/.test(value);\n}\n\nfunction assertChallengeMatchesGrant(params: {\n challengeGrantId?: string;\n challengeOpId?: string;\n challengeOpType?: string;\n grantId: string;\n}): void {\n const { challengeGrantId, challengeOpId, challengeOpType, grantId } = params;\n\n if (challengeOpType && challengeOpType !== GRANT_OP_TYPE) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge used an unsupported escrow op type\",\n 402,\n { opType: challengeOpType },\n );\n }\n\n const challengedGrantId = challengeOpId ?? challengeGrantId;\n if (!challengedGrantId) return;\n\n if (!isBytes32Hex(challengedGrantId)) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge used an invalid escrow op id\",\n 402,\n { opId: challengedGrantId },\n );\n }\n\n if (challengedGrantId.toLowerCase() !== grantId.toLowerCase()) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge did not match the requested grant\",\n 402,\n { opId: challengedGrantId, grantId },\n );\n }\n}\n\n/**\n * Build a Web3Signed-authenticated Personal Server data read request.\n *\n * @param params - Personal Server URL, scope, grant id, and an EIP-191 signer.\n * @returns The request URL, method, path, and headers (including `Authorization`).\n */\nexport async function buildPersonalServerDataReadRequest(params: {\n /** Base URL of the user's Personal Server. */\n personalServerUrl: string;\n /** Scope to read (e.g. `\"icloud_notes.notes\"`). */\n scope: string;\n /** Grant id authorizing the read. */\n grantId: string;\n /** EIP-191 signer for the Web3Signed header (the app key). */\n signMessage: Web3SignedSignFn;\n}): Promise<PersonalServerDataReadRequest> {\n const base = stripTrailingSlash(params.personalServerUrl);\n const path = dataPathForScope(params.scope);\n const authorization = await buildWeb3SignedHeader({\n signMessage: params.signMessage,\n aud: base,\n method: \"GET\",\n uri: path,\n grantId: params.grantId,\n });\n const headers: Record<string, string> = {\n Authorization: authorization,\n Accept: \"application/json\",\n };\n return { url: `${base}${path}`, method: \"GET\", path, headers };\n}\n\n/**\n * Parse a `402 Payment Required` body into a {@link PersonalServerPaymentRequired}.\n *\n * @remarks\n * Accepts a few field spellings and falls back to the read's own grantId and the\n * native asset when a field is absent.\n *\n * @param res - The 402 response.\n * @param grantId - The grant id of the read (default `opId`).\n * @returns The parsed payment requirement.\n */\nexport async function parsePersonalServerPaymentRequired(\n res: FetchResponseLike,\n grantId: string,\n): Promise<PersonalServerPaymentRequired> {\n let raw: unknown = undefined;\n try {\n raw = await res.json();\n } catch {\n raw = undefined;\n }\n const body = asRecord(raw) ?? {};\n const accept =\n Array.isArray(body.accepts) && body.accepts.length > 0\n ? asRecord(body.accepts[0])\n : undefined;\n const message = asRecord(accept?.message);\n const challengeGrantId = stringField(body, \"grantId\");\n const challengeOpId =\n stringField(message, \"opId\") ?? stringField(body, \"opId\");\n const challengeOpType =\n stringField(message, \"opType\") ?? stringField(body, \"opType\");\n\n assertChallengeMatchesGrant({\n challengeGrantId,\n challengeOpId,\n challengeOpType,\n grantId,\n });\n\n const amountValue =\n stringField(message, \"amount\") ??\n stringField(body, \"amount\") ??\n stringField(body, \"maxAmountRequired\") ??\n \"0\";\n return {\n grantId,\n network: stringField(accept, \"network\") ?? stringField(body, \"network\"),\n paymentNonce:\n stringField(message, \"paymentNonce\") ?? stringField(body, \"paymentNonce\"),\n accessRecord: parseAccessRecord(accept?.accessRecord ?? body.accessRecord),\n asset:\n stringField(message, \"asset\") ??\n stringField(body, \"asset\") ??\n NATIVE_ASSET_ADDRESS,\n amount: amountValue,\n raw,\n };\n}\n\nfunction hasPositiveAmount(amount: string): boolean {\n if (!/^\\d+$/.test(amount)) return false;\n return BigInt(amount) > 0n;\n}\n\n/**\n * Read approved data from a Personal Server, settling a 402 via escrow.\n *\n * @remarks\n * Sends a Web3Signed-authenticated `GET /v1/data/{scope}`. On `402`, parses what\n * is owed, authorizes an escrow payment for the grant via `escrow`, and retries\n * once. If escrow is not configured, throws {@link PaymentRequiredError} carrying\n * the parsed requirement so callers can debug amount/asset.\n *\n * @param params - Connection details, app signer, optional escrow config and fetch.\n * @returns `{ data, payment? }`.\n */\nexport async function readPersonalServerData(params: {\n personalServerUrl: string;\n scope: string;\n grantId: string;\n payerAddress: `0x${string}`;\n signMessage: Web3SignedSignFn;\n escrow?: EscrowPaymentConfig;\n fetchFn?: PersonalServerFetch;\n}): Promise<PersonalServerReadResult> {\n const fetchFn =\n params.fetchFn ?? (globalThis.fetch as unknown as PersonalServerFetch);\n if (!fetchFn) {\n throw new PersonalServerReadError(\n \"No fetch implementation available for Personal Server read\",\n );\n }\n\n const initial = await buildPersonalServerDataReadRequest({\n personalServerUrl: params.personalServerUrl,\n scope: params.scope,\n grantId: params.grantId,\n signMessage: params.signMessage,\n });\n\n let res = await fetchFn(initial.url, {\n method: initial.method,\n headers: initial.headers,\n });\n\n let payment: DirectPaymentReceipt | undefined;\n\n if (res.status === 402) {\n const required = await parsePersonalServerPaymentRequired(\n res,\n params.grantId,\n );\n if (!params.escrow) {\n throw new PaymentRequiredError(\n \"Personal Server requires payment but no escrow config is set\",\n {\n scope: params.scope,\n grantId: required.grantId,\n asset: required.asset,\n amount: required.amount,\n },\n );\n }\n\n if (!hasPositiveAmount(required.amount)) {\n throw new PaymentRequiredError(\n \"Personal Server payment challenge did not include a positive amount\",\n {\n scope: params.scope,\n grantId: required.grantId,\n asset: required.asset,\n amount: required.amount,\n },\n );\n }\n\n const paymentHeader = await buildGrantPaymentHeader({\n payerAddress: params.payerAddress,\n required,\n config: params.escrow,\n });\n\n // Re-sign and retry with x402 payment proof for the Personal Server to validate.\n const retry = await buildPersonalServerDataReadRequest({\n personalServerUrl: params.personalServerUrl,\n scope: params.scope,\n grantId: params.grantId,\n signMessage: params.signMessage,\n });\n res = await fetchFn(retry.url, {\n method: retry.method,\n headers: { ...retry.headers, \"X-PAYMENT\": paymentHeader },\n });\n\n if (res.status === 402) {\n throw new PaymentRequiredError(\n \"Personal Server still requires payment after escrow settlement\",\n {\n scope: params.scope,\n grantId: required.grantId,\n asset: required.asset,\n amount: required.amount,\n payment,\n },\n );\n }\n }\n\n if (!res.ok) {\n const detail = await res.text().catch(() => \"\");\n throw new PersonalServerReadError(\n `Personal Server read failed: ${res.status} ${res.statusText}`,\n res.status,\n { scope: params.scope, body: detail.slice(0, 500) },\n );\n }\n\n payment = paymentReceiptFromHeader(res.headers.get(\"X-PAYMENT-RESPONSE\"));\n return { data: await res.json(), payment };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/direct/personal-server-read.ts"],"sourcesContent":["/**\n * Personal Server data-read request builder and the 402 -> escrow-pay -> retry loop.\n *\n * @remarks\n * The read targets the Personal Server data path (`/v1/data/{scope}`),\n * authenticates with a Web3Signed header (built on {@link buildWeb3SignedHeader}),\n * and — on `402 Payment Required` — settles the grant's data-access fee through\n * the DPv2 escrow gateway and retries once.\n *\n * The 402 body is parsed into a {@link PersonalServerPaymentRequired} (grant id,\n * asset, and amount owed), which drives the escrow settlement.\n *\n * @category Direct\n * @module direct/personal-server-read\n */\n\nimport { buildWeb3SignedHeader } from \"../auth/web3-signed-builder\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport {\n NATIVE_ASSET_ADDRESS,\n type EscrowAccessRecord,\n} from \"../protocol/escrow\";\nimport {\n buildGrantPaymentHeader,\n GRANT_OP_TYPE,\n paymentReceiptFromHeader,\n type EscrowPaymentConfig,\n} from \"./escrow-payment\";\nimport { PaymentRequiredError, PersonalServerReadError } from \"./errors\";\nimport type {\n DirectPaymentReceipt,\n PersonalServerPaymentRequired,\n} from \"./types\";\n\n/** Minimal `Response`-like shape so the read loop is testable without a DOM. */\nexport interface FetchResponseLike {\n ok: boolean;\n status: number;\n statusText: string;\n headers: { get(name: string): string | null };\n json(): Promise<unknown>;\n text(): Promise<string>;\n}\n\n/** Minimal `fetch` signature accepted by {@link readPersonalServerData}. */\nexport type PersonalServerFetch = (\n input: string,\n init: {\n method: string;\n headers: Record<string, string>;\n },\n) => Promise<FetchResponseLike>;\n\n/** A built, ready-to-send Personal Server data read request. */\nexport interface PersonalServerDataReadRequest {\n /** Absolute URL of the read endpoint. */\n url: string;\n /** HTTP method (always `\"GET\"`). */\n method: \"GET\";\n /** Request path used in the Web3Signed `uri` claim (e.g. `/v1/data/{scope}`). */\n path: string;\n /** Headers including the Web3Signed `Authorization` value. */\n headers: Record<string, string>;\n}\n\n/**\n * Transport-level retry knobs for {@link readPersonalServerData}.\n *\n * @remarks\n * Applies only when the underlying `fetch` **throws** (connection reset, DNS,\n * socket died mid-handshake — the browser-PS relay drop window). A received\n * HTTP response is never retried here: 402 has its own payment loop and other\n * statuses are surfaced to the caller unchanged.\n */\nexport interface PersonalServerTransportRetryOptions {\n /** Total attempts including the first (default 3). `1` disables retries. */\n attempts?: number;\n /** Delay before the first retry (ms); doubles per retry (default 1_000). */\n initialDelayMs?: number;\n /** Cap on the between-retry delay (ms, default 5_000). */\n maxDelayMs?: number;\n}\n\nconst TRANSPORT_RETRY_DEFAULTS: Required<PersonalServerTransportRetryOptions> =\n {\n attempts: 3,\n initialDelayMs: 1_000,\n maxDelayMs: 5_000,\n };\n\n/** Clamp a caller-supplied attempt count to a finite integer >= 1. */\nfunction resolveAttempts(attempts: number): number {\n return Number.isFinite(attempts) ? Math.max(1, Math.floor(attempts)) : 1;\n}\n\n/** An aborted request is the caller's intent to stop — never retry it. */\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === \"AbortError\";\n}\n\nconst sleep = (ms: number) =>\n new Promise<void>((resolve) => setTimeout(resolve, ms));\n\n/**\n * Run a fetch with bounded transport retries.\n *\n * Each attempt rebuilds the request via `buildRequest` so the Web3Signed\n * header is freshly signed (local app-key signature — cheap), while letting\n * the caller pin anything that must NOT be regenerated across attempts\n * (an already-signed `X-PAYMENT` header keeps its paymentNonce, so a retry\n * can never mint a second escrow payment for the same read).\n */\nasync function fetchWithTransportRetry(\n fetchFn: PersonalServerFetch,\n buildRequest: () => Promise<{\n url: string;\n method: string;\n headers: Record<string, string>;\n }>,\n retry: Required<PersonalServerTransportRetryOptions>,\n): Promise<FetchResponseLike> {\n const attempts = resolveAttempts(retry.attempts);\n let lastError: unknown;\n for (let attempt = 0; attempt < attempts; attempt += 1) {\n if (attempt > 0) {\n await sleep(\n Math.min(retry.maxDelayMs, retry.initialDelayMs * 2 ** (attempt - 1)),\n );\n }\n const request = await buildRequest();\n try {\n return await fetchFn(request.url, {\n method: request.method,\n headers: request.headers,\n });\n } catch (error) {\n // An abort is a deliberate cancellation, not a flaky tunnel — surface it\n // immediately instead of burning attempts (and backoff) on it.\n if (isAbortError(error)) {\n throw error;\n }\n lastError = error;\n }\n }\n throw lastError;\n}\n\n/** Outcome of {@link readPersonalServerData}: the payload plus optional receipt. */\nexport interface PersonalServerReadResult {\n /** The decoded JSON payload returned by the Personal Server. */\n data: unknown;\n /** Present only when this read required (and settled) a payment. */\n payment?: DirectPaymentReceipt;\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\n/** Compute the data path for a scope (`/v1/data/{scope}`). */\nexport function dataPathForScope(scope: string): string {\n return `/v1/data/${encodeURIComponent(scope)}`;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction stringField(\n record: Record<string, unknown> | undefined,\n field: string,\n): string | undefined {\n const value = record?.[field];\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction parseAccessRecord(value: unknown): EscrowAccessRecord | undefined {\n const record = asRecord(value);\n const dataPointId = stringField(record, \"dataPointId\");\n const version = stringField(record, \"version\");\n const accessor = stringField(record, \"accessor\");\n const recordId = stringField(record, \"recordId\");\n const signature = stringField(record, \"signature\");\n\n if (!dataPointId || !version || !accessor || !recordId || !signature) {\n return undefined;\n }\n\n return {\n dataPointId: dataPointId as `0x${string}`,\n version,\n accessor: accessor as `0x${string}`,\n recordId: recordId as `0x${string}`,\n signature: signature as `0x${string}`,\n };\n}\n\nfunction isBytes32Hex(value: string): boolean {\n return /^0x[0-9a-fA-F]{64}$/.test(value);\n}\n\nfunction assertChallengeMatchesGrant(params: {\n challengeGrantId?: string;\n challengeOpId?: string;\n challengeOpType?: string;\n grantId: string;\n}): void {\n const { challengeGrantId, challengeOpId, challengeOpType, grantId } = params;\n\n if (challengeOpType && challengeOpType !== GRANT_OP_TYPE) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge used an unsupported escrow op type\",\n 402,\n { opType: challengeOpType },\n );\n }\n\n const challengedGrantId = challengeOpId ?? challengeGrantId;\n if (!challengedGrantId) return;\n\n if (!isBytes32Hex(challengedGrantId)) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge used an invalid escrow op id\",\n 402,\n { opId: challengedGrantId },\n );\n }\n\n if (challengedGrantId.toLowerCase() !== grantId.toLowerCase()) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge did not match the requested grant\",\n 402,\n { opId: challengedGrantId, grantId },\n );\n }\n}\n\n/**\n * Build a Web3Signed-authenticated Personal Server data read request.\n *\n * @param params - Personal Server URL, scope, grant id, and an EIP-191 signer.\n * @returns The request URL, method, path, and headers (including `Authorization`).\n */\nexport async function buildPersonalServerDataReadRequest(params: {\n /** Base URL of the user's Personal Server. */\n personalServerUrl: string;\n /** Scope to read (e.g. `\"icloud_notes.notes\"`). */\n scope: string;\n /** Grant id authorizing the read. */\n grantId: string;\n /** EIP-191 signer for the Web3Signed header (the app key). */\n signMessage: Web3SignedSignFn;\n}): Promise<PersonalServerDataReadRequest> {\n const base = stripTrailingSlash(params.personalServerUrl);\n const path = dataPathForScope(params.scope);\n const authorization = await buildWeb3SignedHeader({\n signMessage: params.signMessage,\n aud: base,\n method: \"GET\",\n uri: path,\n grantId: params.grantId,\n });\n const headers: Record<string, string> = {\n Authorization: authorization,\n Accept: \"application/json\",\n };\n return { url: `${base}${path}`, method: \"GET\", path, headers };\n}\n\n/**\n * Parse a `402 Payment Required` body into a {@link PersonalServerPaymentRequired}.\n *\n * @remarks\n * Accepts a few field spellings and falls back to the read's own grantId and the\n * native asset when a field is absent.\n *\n * @param res - The 402 response.\n * @param grantId - The grant id of the read (default `opId`).\n * @returns The parsed payment requirement.\n */\nexport async function parsePersonalServerPaymentRequired(\n res: FetchResponseLike,\n grantId: string,\n): Promise<PersonalServerPaymentRequired> {\n let raw: unknown = undefined;\n try {\n raw = await res.json();\n } catch {\n raw = undefined;\n }\n const body = asRecord(raw) ?? {};\n const accept =\n Array.isArray(body.accepts) && body.accepts.length > 0\n ? asRecord(body.accepts[0])\n : undefined;\n const message = asRecord(accept?.message);\n const challengeGrantId = stringField(body, \"grantId\");\n const challengeOpId =\n stringField(message, \"opId\") ?? stringField(body, \"opId\");\n const challengeOpType =\n stringField(message, \"opType\") ?? stringField(body, \"opType\");\n\n assertChallengeMatchesGrant({\n challengeGrantId,\n challengeOpId,\n challengeOpType,\n grantId,\n });\n\n const amountValue =\n stringField(message, \"amount\") ??\n stringField(body, \"amount\") ??\n stringField(body, \"maxAmountRequired\") ??\n \"0\";\n return {\n grantId,\n network: stringField(accept, \"network\") ?? stringField(body, \"network\"),\n paymentNonce:\n stringField(message, \"paymentNonce\") ?? stringField(body, \"paymentNonce\"),\n accessRecord: parseAccessRecord(accept?.accessRecord ?? body.accessRecord),\n asset:\n stringField(message, \"asset\") ??\n stringField(body, \"asset\") ??\n NATIVE_ASSET_ADDRESS,\n amount: amountValue,\n raw,\n };\n}\n\nfunction hasPositiveAmount(amount: string): boolean {\n if (!/^\\d+$/.test(amount)) return false;\n return BigInt(amount) > 0n;\n}\n\n/**\n * Read approved data from a Personal Server, settling a 402 via escrow.\n *\n * @remarks\n * Sends a Web3Signed-authenticated `GET /v1/data/{scope}`. On `402`, parses what\n * is owed, authorizes an escrow payment for the grant via `escrow`, and retries\n * once. If escrow is not configured, throws {@link PaymentRequiredError} carrying\n * the parsed requirement so callers can debug amount/asset.\n *\n * Transport failures (fetch throwing — the browser-PS relay reconnect window)\n * are retried with backoff per `transportRetry` (default 3 attempts). The paid\n * retry reuses the already-signed `X-PAYMENT` header, so transport retries can\n * never double-pay.\n *\n * @param params - Connection details, app signer, optional escrow config and fetch.\n * @returns `{ data, payment? }`.\n */\nexport async function readPersonalServerData(params: {\n personalServerUrl: string;\n scope: string;\n grantId: string;\n payerAddress: `0x${string}`;\n signMessage: Web3SignedSignFn;\n escrow?: EscrowPaymentConfig;\n fetchFn?: PersonalServerFetch;\n transportRetry?: PersonalServerTransportRetryOptions;\n}): Promise<PersonalServerReadResult> {\n const fetchFn =\n params.fetchFn ?? (globalThis.fetch as unknown as PersonalServerFetch);\n if (!fetchFn) {\n throw new PersonalServerReadError(\n \"No fetch implementation available for Personal Server read\",\n );\n }\n const transportRetry = {\n ...TRANSPORT_RETRY_DEFAULTS,\n ...params.transportRetry,\n };\n\n const buildRequest = () =>\n buildPersonalServerDataReadRequest({\n personalServerUrl: params.personalServerUrl,\n scope: params.scope,\n grantId: params.grantId,\n signMessage: params.signMessage,\n });\n\n let res = await fetchWithTransportRetry(\n fetchFn,\n buildRequest,\n transportRetry,\n );\n\n let payment: DirectPaymentReceipt | undefined;\n\n if (res.status === 402) {\n const required = await parsePersonalServerPaymentRequired(\n res,\n params.grantId,\n );\n if (!params.escrow) {\n throw new PaymentRequiredError(\n \"Personal Server requires payment but no escrow config is set\",\n {\n scope: params.scope,\n grantId: required.grantId,\n asset: required.asset,\n amount: required.amount,\n },\n );\n }\n\n if (!hasPositiveAmount(required.amount)) {\n throw new PaymentRequiredError(\n \"Personal Server payment challenge did not include a positive amount\",\n {\n scope: params.scope,\n grantId: required.grantId,\n asset: required.asset,\n amount: required.amount,\n },\n );\n }\n\n const paymentHeader = await buildGrantPaymentHeader({\n payerAddress: params.payerAddress,\n required,\n config: params.escrow,\n });\n\n // Re-sign and retry with x402 payment proof for the Personal Server to\n // validate. The payment header is built exactly once: transport retries\n // below re-sign only the Web3Signed auth and resend the SAME X-PAYMENT\n // (same paymentNonce), so a dropped tunnel cannot mint a second payment.\n res = await fetchWithTransportRetry(\n fetchFn,\n async () => {\n const retry = await buildRequest();\n return {\n ...retry,\n headers: { ...retry.headers, \"X-PAYMENT\": paymentHeader },\n };\n },\n transportRetry,\n );\n\n if (res.status === 402) {\n throw new PaymentRequiredError(\n \"Personal Server still requires payment after escrow settlement\",\n {\n scope: params.scope,\n grantId: required.grantId,\n asset: required.asset,\n amount: required.amount,\n payment,\n },\n );\n }\n }\n\n if (!res.ok) {\n const detail = await res.text().catch(() => \"\");\n throw new PersonalServerReadError(\n `Personal Server read failed: ${res.status} ${res.statusText}`,\n res.status,\n { scope: params.scope, body: detail.slice(0, 500) },\n );\n }\n\n payment = paymentReceiptFromHeader(res.headers.get(\"X-PAYMENT-RESPONSE\"));\n return { data: await res.json(), payment };\n}\n"],"mappings":"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","names":[]}
@@ -43,6 +43,23 @@ export interface PersonalServerDataReadRequest {
43
43
  /** Headers including the Web3Signed `Authorization` value. */
44
44
  headers: Record<string, string>;
45
45
  }
46
+ /**
47
+ * Transport-level retry knobs for {@link readPersonalServerData}.
48
+ *
49
+ * @remarks
50
+ * Applies only when the underlying `fetch` **throws** (connection reset, DNS,
51
+ * socket died mid-handshake — the browser-PS relay drop window). A received
52
+ * HTTP response is never retried here: 402 has its own payment loop and other
53
+ * statuses are surfaced to the caller unchanged.
54
+ */
55
+ export interface PersonalServerTransportRetryOptions {
56
+ /** Total attempts including the first (default 3). `1` disables retries. */
57
+ attempts?: number;
58
+ /** Delay before the first retry (ms); doubles per retry (default 1_000). */
59
+ initialDelayMs?: number;
60
+ /** Cap on the between-retry delay (ms, default 5_000). */
61
+ maxDelayMs?: number;
62
+ }
46
63
  /** Outcome of {@link readPersonalServerData}: the payload plus optional receipt. */
47
64
  export interface PersonalServerReadResult {
48
65
  /** The decoded JSON payload returned by the Personal Server. */
@@ -89,6 +106,11 @@ export declare function parsePersonalServerPaymentRequired(res: FetchResponseLik
89
106
  * once. If escrow is not configured, throws {@link PaymentRequiredError} carrying
90
107
  * the parsed requirement so callers can debug amount/asset.
91
108
  *
109
+ * Transport failures (fetch throwing — the browser-PS relay reconnect window)
110
+ * are retried with backoff per `transportRetry` (default 3 attempts). The paid
111
+ * retry reuses the already-signed `X-PAYMENT` header, so transport retries can
112
+ * never double-pay.
113
+ *
92
114
  * @param params - Connection details, app signer, optional escrow config and fetch.
93
115
  * @returns `{ data, payment? }`.
94
116
  */
@@ -100,4 +122,5 @@ export declare function readPersonalServerData(params: {
100
122
  signMessage: Web3SignedSignFn;
101
123
  escrow?: EscrowPaymentConfig;
102
124
  fetchFn?: PersonalServerFetch;
125
+ transportRetry?: PersonalServerTransportRetryOptions;
103
126
  }): Promise<PersonalServerReadResult>;
@@ -8,6 +8,42 @@ import {
8
8
  paymentReceiptFromHeader
9
9
  } from "./escrow-payment.js";
10
10
  import { PaymentRequiredError, PersonalServerReadError } from "./errors.js";
11
+ const TRANSPORT_RETRY_DEFAULTS = {
12
+ attempts: 3,
13
+ initialDelayMs: 1e3,
14
+ maxDelayMs: 5e3
15
+ };
16
+ function resolveAttempts(attempts) {
17
+ return Number.isFinite(attempts) ? Math.max(1, Math.floor(attempts)) : 1;
18
+ }
19
+ function isAbortError(error) {
20
+ return error instanceof Error && error.name === "AbortError";
21
+ }
22
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
23
+ async function fetchWithTransportRetry(fetchFn, buildRequest, retry) {
24
+ const attempts = resolveAttempts(retry.attempts);
25
+ let lastError;
26
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
27
+ if (attempt > 0) {
28
+ await sleep(
29
+ Math.min(retry.maxDelayMs, retry.initialDelayMs * 2 ** (attempt - 1))
30
+ );
31
+ }
32
+ const request = await buildRequest();
33
+ try {
34
+ return await fetchFn(request.url, {
35
+ method: request.method,
36
+ headers: request.headers
37
+ });
38
+ } catch (error) {
39
+ if (isAbortError(error)) {
40
+ throw error;
41
+ }
42
+ lastError = error;
43
+ }
44
+ }
45
+ throw lastError;
46
+ }
11
47
  function stripTrailingSlash(url) {
12
48
  return url.replace(/\/+$/, "");
13
49
  }
@@ -125,16 +161,21 @@ async function readPersonalServerData(params) {
125
161
  "No fetch implementation available for Personal Server read"
126
162
  );
127
163
  }
128
- const initial = await buildPersonalServerDataReadRequest({
164
+ const transportRetry = {
165
+ ...TRANSPORT_RETRY_DEFAULTS,
166
+ ...params.transportRetry
167
+ };
168
+ const buildRequest = () => buildPersonalServerDataReadRequest({
129
169
  personalServerUrl: params.personalServerUrl,
130
170
  scope: params.scope,
131
171
  grantId: params.grantId,
132
172
  signMessage: params.signMessage
133
173
  });
134
- let res = await fetchFn(initial.url, {
135
- method: initial.method,
136
- headers: initial.headers
137
- });
174
+ let res = await fetchWithTransportRetry(
175
+ fetchFn,
176
+ buildRequest,
177
+ transportRetry
178
+ );
138
179
  let payment;
139
180
  if (res.status === 402) {
140
181
  const required = await parsePersonalServerPaymentRequired(
@@ -168,16 +209,17 @@ async function readPersonalServerData(params) {
168
209
  required,
169
210
  config: params.escrow
170
211
  });
171
- const retry = await buildPersonalServerDataReadRequest({
172
- personalServerUrl: params.personalServerUrl,
173
- scope: params.scope,
174
- grantId: params.grantId,
175
- signMessage: params.signMessage
176
- });
177
- res = await fetchFn(retry.url, {
178
- method: retry.method,
179
- headers: { ...retry.headers, "X-PAYMENT": paymentHeader }
180
- });
212
+ res = await fetchWithTransportRetry(
213
+ fetchFn,
214
+ async () => {
215
+ const retry = await buildRequest();
216
+ return {
217
+ ...retry,
218
+ headers: { ...retry.headers, "X-PAYMENT": paymentHeader }
219
+ };
220
+ },
221
+ transportRetry
222
+ );
181
223
  if (res.status === 402) {
182
224
  throw new PaymentRequiredError(
183
225
  "Personal Server still requires payment after escrow settlement",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/direct/personal-server-read.ts"],"sourcesContent":["/**\n * Personal Server data-read request builder and the 402 -> escrow-pay -> retry loop.\n *\n * @remarks\n * The read targets the Personal Server data path (`/v1/data/{scope}`),\n * authenticates with a Web3Signed header (built on {@link buildWeb3SignedHeader}),\n * and — on `402 Payment Required` — settles the grant's data-access fee through\n * the DPv2 escrow gateway and retries once.\n *\n * The 402 body is parsed into a {@link PersonalServerPaymentRequired} (grant id,\n * asset, and amount owed), which drives the escrow settlement.\n *\n * @category Direct\n * @module direct/personal-server-read\n */\n\nimport { buildWeb3SignedHeader } from \"../auth/web3-signed-builder\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport {\n NATIVE_ASSET_ADDRESS,\n type EscrowAccessRecord,\n} from \"../protocol/escrow\";\nimport {\n buildGrantPaymentHeader,\n GRANT_OP_TYPE,\n paymentReceiptFromHeader,\n type EscrowPaymentConfig,\n} from \"./escrow-payment\";\nimport { PaymentRequiredError, PersonalServerReadError } from \"./errors\";\nimport type {\n DirectPaymentReceipt,\n PersonalServerPaymentRequired,\n} from \"./types\";\n\n/** Minimal `Response`-like shape so the read loop is testable without a DOM. */\nexport interface FetchResponseLike {\n ok: boolean;\n status: number;\n statusText: string;\n headers: { get(name: string): string | null };\n json(): Promise<unknown>;\n text(): Promise<string>;\n}\n\n/** Minimal `fetch` signature accepted by {@link readPersonalServerData}. */\nexport type PersonalServerFetch = (\n input: string,\n init: {\n method: string;\n headers: Record<string, string>;\n },\n) => Promise<FetchResponseLike>;\n\n/** A built, ready-to-send Personal Server data read request. */\nexport interface PersonalServerDataReadRequest {\n /** Absolute URL of the read endpoint. */\n url: string;\n /** HTTP method (always `\"GET\"`). */\n method: \"GET\";\n /** Request path used in the Web3Signed `uri` claim (e.g. `/v1/data/{scope}`). */\n path: string;\n /** Headers including the Web3Signed `Authorization` value. */\n headers: Record<string, string>;\n}\n\n/** Outcome of {@link readPersonalServerData}: the payload plus optional receipt. */\nexport interface PersonalServerReadResult {\n /** The decoded JSON payload returned by the Personal Server. */\n data: unknown;\n /** Present only when this read required (and settled) a payment. */\n payment?: DirectPaymentReceipt;\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\n/** Compute the data path for a scope (`/v1/data/{scope}`). */\nexport function dataPathForScope(scope: string): string {\n return `/v1/data/${encodeURIComponent(scope)}`;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction stringField(\n record: Record<string, unknown> | undefined,\n field: string,\n): string | undefined {\n const value = record?.[field];\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction parseAccessRecord(value: unknown): EscrowAccessRecord | undefined {\n const record = asRecord(value);\n const dataPointId = stringField(record, \"dataPointId\");\n const version = stringField(record, \"version\");\n const accessor = stringField(record, \"accessor\");\n const recordId = stringField(record, \"recordId\");\n const signature = stringField(record, \"signature\");\n\n if (!dataPointId || !version || !accessor || !recordId || !signature) {\n return undefined;\n }\n\n return {\n dataPointId: dataPointId as `0x${string}`,\n version,\n accessor: accessor as `0x${string}`,\n recordId: recordId as `0x${string}`,\n signature: signature as `0x${string}`,\n };\n}\n\nfunction isBytes32Hex(value: string): boolean {\n return /^0x[0-9a-fA-F]{64}$/.test(value);\n}\n\nfunction assertChallengeMatchesGrant(params: {\n challengeGrantId?: string;\n challengeOpId?: string;\n challengeOpType?: string;\n grantId: string;\n}): void {\n const { challengeGrantId, challengeOpId, challengeOpType, grantId } = params;\n\n if (challengeOpType && challengeOpType !== GRANT_OP_TYPE) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge used an unsupported escrow op type\",\n 402,\n { opType: challengeOpType },\n );\n }\n\n const challengedGrantId = challengeOpId ?? challengeGrantId;\n if (!challengedGrantId) return;\n\n if (!isBytes32Hex(challengedGrantId)) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge used an invalid escrow op id\",\n 402,\n { opId: challengedGrantId },\n );\n }\n\n if (challengedGrantId.toLowerCase() !== grantId.toLowerCase()) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge did not match the requested grant\",\n 402,\n { opId: challengedGrantId, grantId },\n );\n }\n}\n\n/**\n * Build a Web3Signed-authenticated Personal Server data read request.\n *\n * @param params - Personal Server URL, scope, grant id, and an EIP-191 signer.\n * @returns The request URL, method, path, and headers (including `Authorization`).\n */\nexport async function buildPersonalServerDataReadRequest(params: {\n /** Base URL of the user's Personal Server. */\n personalServerUrl: string;\n /** Scope to read (e.g. `\"icloud_notes.notes\"`). */\n scope: string;\n /** Grant id authorizing the read. */\n grantId: string;\n /** EIP-191 signer for the Web3Signed header (the app key). */\n signMessage: Web3SignedSignFn;\n}): Promise<PersonalServerDataReadRequest> {\n const base = stripTrailingSlash(params.personalServerUrl);\n const path = dataPathForScope(params.scope);\n const authorization = await buildWeb3SignedHeader({\n signMessage: params.signMessage,\n aud: base,\n method: \"GET\",\n uri: path,\n grantId: params.grantId,\n });\n const headers: Record<string, string> = {\n Authorization: authorization,\n Accept: \"application/json\",\n };\n return { url: `${base}${path}`, method: \"GET\", path, headers };\n}\n\n/**\n * Parse a `402 Payment Required` body into a {@link PersonalServerPaymentRequired}.\n *\n * @remarks\n * Accepts a few field spellings and falls back to the read's own grantId and the\n * native asset when a field is absent.\n *\n * @param res - The 402 response.\n * @param grantId - The grant id of the read (default `opId`).\n * @returns The parsed payment requirement.\n */\nexport async function parsePersonalServerPaymentRequired(\n res: FetchResponseLike,\n grantId: string,\n): Promise<PersonalServerPaymentRequired> {\n let raw: unknown = undefined;\n try {\n raw = await res.json();\n } catch {\n raw = undefined;\n }\n const body = asRecord(raw) ?? {};\n const accept =\n Array.isArray(body.accepts) && body.accepts.length > 0\n ? asRecord(body.accepts[0])\n : undefined;\n const message = asRecord(accept?.message);\n const challengeGrantId = stringField(body, \"grantId\");\n const challengeOpId =\n stringField(message, \"opId\") ?? stringField(body, \"opId\");\n const challengeOpType =\n stringField(message, \"opType\") ?? stringField(body, \"opType\");\n\n assertChallengeMatchesGrant({\n challengeGrantId,\n challengeOpId,\n challengeOpType,\n grantId,\n });\n\n const amountValue =\n stringField(message, \"amount\") ??\n stringField(body, \"amount\") ??\n stringField(body, \"maxAmountRequired\") ??\n \"0\";\n return {\n grantId,\n network: stringField(accept, \"network\") ?? stringField(body, \"network\"),\n paymentNonce:\n stringField(message, \"paymentNonce\") ?? stringField(body, \"paymentNonce\"),\n accessRecord: parseAccessRecord(accept?.accessRecord ?? body.accessRecord),\n asset:\n stringField(message, \"asset\") ??\n stringField(body, \"asset\") ??\n NATIVE_ASSET_ADDRESS,\n amount: amountValue,\n raw,\n };\n}\n\nfunction hasPositiveAmount(amount: string): boolean {\n if (!/^\\d+$/.test(amount)) return false;\n return BigInt(amount) > 0n;\n}\n\n/**\n * Read approved data from a Personal Server, settling a 402 via escrow.\n *\n * @remarks\n * Sends a Web3Signed-authenticated `GET /v1/data/{scope}`. On `402`, parses what\n * is owed, authorizes an escrow payment for the grant via `escrow`, and retries\n * once. If escrow is not configured, throws {@link PaymentRequiredError} carrying\n * the parsed requirement so callers can debug amount/asset.\n *\n * @param params - Connection details, app signer, optional escrow config and fetch.\n * @returns `{ data, payment? }`.\n */\nexport async function readPersonalServerData(params: {\n personalServerUrl: string;\n scope: string;\n grantId: string;\n payerAddress: `0x${string}`;\n signMessage: Web3SignedSignFn;\n escrow?: EscrowPaymentConfig;\n fetchFn?: PersonalServerFetch;\n}): Promise<PersonalServerReadResult> {\n const fetchFn =\n params.fetchFn ?? (globalThis.fetch as unknown as PersonalServerFetch);\n if (!fetchFn) {\n throw new PersonalServerReadError(\n \"No fetch implementation available for Personal Server read\",\n );\n }\n\n const initial = await buildPersonalServerDataReadRequest({\n personalServerUrl: params.personalServerUrl,\n scope: params.scope,\n grantId: params.grantId,\n signMessage: params.signMessage,\n });\n\n let res = await fetchFn(initial.url, {\n method: initial.method,\n headers: initial.headers,\n });\n\n let payment: DirectPaymentReceipt | undefined;\n\n if (res.status === 402) {\n const required = await parsePersonalServerPaymentRequired(\n res,\n params.grantId,\n );\n if (!params.escrow) {\n throw new PaymentRequiredError(\n \"Personal Server requires payment but no escrow config is set\",\n {\n scope: params.scope,\n grantId: required.grantId,\n asset: required.asset,\n amount: required.amount,\n },\n );\n }\n\n if (!hasPositiveAmount(required.amount)) {\n throw new PaymentRequiredError(\n \"Personal Server payment challenge did not include a positive amount\",\n {\n scope: params.scope,\n grantId: required.grantId,\n asset: required.asset,\n amount: required.amount,\n },\n );\n }\n\n const paymentHeader = await buildGrantPaymentHeader({\n payerAddress: params.payerAddress,\n required,\n config: params.escrow,\n });\n\n // Re-sign and retry with x402 payment proof for the Personal Server to validate.\n const retry = await buildPersonalServerDataReadRequest({\n personalServerUrl: params.personalServerUrl,\n scope: params.scope,\n grantId: params.grantId,\n signMessage: params.signMessage,\n });\n res = await fetchFn(retry.url, {\n method: retry.method,\n headers: { ...retry.headers, \"X-PAYMENT\": paymentHeader },\n });\n\n if (res.status === 402) {\n throw new PaymentRequiredError(\n \"Personal Server still requires payment after escrow settlement\",\n {\n scope: params.scope,\n grantId: required.grantId,\n asset: required.asset,\n amount: required.amount,\n payment,\n },\n );\n }\n }\n\n if (!res.ok) {\n const detail = await res.text().catch(() => \"\");\n throw new PersonalServerReadError(\n `Personal Server read failed: ${res.status} ${res.statusText}`,\n res.status,\n { scope: params.scope, body: detail.slice(0, 500) },\n );\n }\n\n payment = paymentReceiptFromHeader(res.headers.get(\"X-PAYMENT-RESPONSE\"));\n return { data: await res.json(), payment };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/direct/personal-server-read.ts"],"sourcesContent":["/**\n * Personal Server data-read request builder and the 402 -> escrow-pay -> retry loop.\n *\n * @remarks\n * The read targets the Personal Server data path (`/v1/data/{scope}`),\n * authenticates with a Web3Signed header (built on {@link buildWeb3SignedHeader}),\n * and — on `402 Payment Required` — settles the grant's data-access fee through\n * the DPv2 escrow gateway and retries once.\n *\n * The 402 body is parsed into a {@link PersonalServerPaymentRequired} (grant id,\n * asset, and amount owed), which drives the escrow settlement.\n *\n * @category Direct\n * @module direct/personal-server-read\n */\n\nimport { buildWeb3SignedHeader } from \"../auth/web3-signed-builder\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport {\n NATIVE_ASSET_ADDRESS,\n type EscrowAccessRecord,\n} from \"../protocol/escrow\";\nimport {\n buildGrantPaymentHeader,\n GRANT_OP_TYPE,\n paymentReceiptFromHeader,\n type EscrowPaymentConfig,\n} from \"./escrow-payment\";\nimport { PaymentRequiredError, PersonalServerReadError } from \"./errors\";\nimport type {\n DirectPaymentReceipt,\n PersonalServerPaymentRequired,\n} from \"./types\";\n\n/** Minimal `Response`-like shape so the read loop is testable without a DOM. */\nexport interface FetchResponseLike {\n ok: boolean;\n status: number;\n statusText: string;\n headers: { get(name: string): string | null };\n json(): Promise<unknown>;\n text(): Promise<string>;\n}\n\n/** Minimal `fetch` signature accepted by {@link readPersonalServerData}. */\nexport type PersonalServerFetch = (\n input: string,\n init: {\n method: string;\n headers: Record<string, string>;\n },\n) => Promise<FetchResponseLike>;\n\n/** A built, ready-to-send Personal Server data read request. */\nexport interface PersonalServerDataReadRequest {\n /** Absolute URL of the read endpoint. */\n url: string;\n /** HTTP method (always `\"GET\"`). */\n method: \"GET\";\n /** Request path used in the Web3Signed `uri` claim (e.g. `/v1/data/{scope}`). */\n path: string;\n /** Headers including the Web3Signed `Authorization` value. */\n headers: Record<string, string>;\n}\n\n/**\n * Transport-level retry knobs for {@link readPersonalServerData}.\n *\n * @remarks\n * Applies only when the underlying `fetch` **throws** (connection reset, DNS,\n * socket died mid-handshake — the browser-PS relay drop window). A received\n * HTTP response is never retried here: 402 has its own payment loop and other\n * statuses are surfaced to the caller unchanged.\n */\nexport interface PersonalServerTransportRetryOptions {\n /** Total attempts including the first (default 3). `1` disables retries. */\n attempts?: number;\n /** Delay before the first retry (ms); doubles per retry (default 1_000). */\n initialDelayMs?: number;\n /** Cap on the between-retry delay (ms, default 5_000). */\n maxDelayMs?: number;\n}\n\nconst TRANSPORT_RETRY_DEFAULTS: Required<PersonalServerTransportRetryOptions> =\n {\n attempts: 3,\n initialDelayMs: 1_000,\n maxDelayMs: 5_000,\n };\n\n/** Clamp a caller-supplied attempt count to a finite integer >= 1. */\nfunction resolveAttempts(attempts: number): number {\n return Number.isFinite(attempts) ? Math.max(1, Math.floor(attempts)) : 1;\n}\n\n/** An aborted request is the caller's intent to stop — never retry it. */\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === \"AbortError\";\n}\n\nconst sleep = (ms: number) =>\n new Promise<void>((resolve) => setTimeout(resolve, ms));\n\n/**\n * Run a fetch with bounded transport retries.\n *\n * Each attempt rebuilds the request via `buildRequest` so the Web3Signed\n * header is freshly signed (local app-key signature — cheap), while letting\n * the caller pin anything that must NOT be regenerated across attempts\n * (an already-signed `X-PAYMENT` header keeps its paymentNonce, so a retry\n * can never mint a second escrow payment for the same read).\n */\nasync function fetchWithTransportRetry(\n fetchFn: PersonalServerFetch,\n buildRequest: () => Promise<{\n url: string;\n method: string;\n headers: Record<string, string>;\n }>,\n retry: Required<PersonalServerTransportRetryOptions>,\n): Promise<FetchResponseLike> {\n const attempts = resolveAttempts(retry.attempts);\n let lastError: unknown;\n for (let attempt = 0; attempt < attempts; attempt += 1) {\n if (attempt > 0) {\n await sleep(\n Math.min(retry.maxDelayMs, retry.initialDelayMs * 2 ** (attempt - 1)),\n );\n }\n const request = await buildRequest();\n try {\n return await fetchFn(request.url, {\n method: request.method,\n headers: request.headers,\n });\n } catch (error) {\n // An abort is a deliberate cancellation, not a flaky tunnel — surface it\n // immediately instead of burning attempts (and backoff) on it.\n if (isAbortError(error)) {\n throw error;\n }\n lastError = error;\n }\n }\n throw lastError;\n}\n\n/** Outcome of {@link readPersonalServerData}: the payload plus optional receipt. */\nexport interface PersonalServerReadResult {\n /** The decoded JSON payload returned by the Personal Server. */\n data: unknown;\n /** Present only when this read required (and settled) a payment. */\n payment?: DirectPaymentReceipt;\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\n/** Compute the data path for a scope (`/v1/data/{scope}`). */\nexport function dataPathForScope(scope: string): string {\n return `/v1/data/${encodeURIComponent(scope)}`;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction stringField(\n record: Record<string, unknown> | undefined,\n field: string,\n): string | undefined {\n const value = record?.[field];\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction parseAccessRecord(value: unknown): EscrowAccessRecord | undefined {\n const record = asRecord(value);\n const dataPointId = stringField(record, \"dataPointId\");\n const version = stringField(record, \"version\");\n const accessor = stringField(record, \"accessor\");\n const recordId = stringField(record, \"recordId\");\n const signature = stringField(record, \"signature\");\n\n if (!dataPointId || !version || !accessor || !recordId || !signature) {\n return undefined;\n }\n\n return {\n dataPointId: dataPointId as `0x${string}`,\n version,\n accessor: accessor as `0x${string}`,\n recordId: recordId as `0x${string}`,\n signature: signature as `0x${string}`,\n };\n}\n\nfunction isBytes32Hex(value: string): boolean {\n return /^0x[0-9a-fA-F]{64}$/.test(value);\n}\n\nfunction assertChallengeMatchesGrant(params: {\n challengeGrantId?: string;\n challengeOpId?: string;\n challengeOpType?: string;\n grantId: string;\n}): void {\n const { challengeGrantId, challengeOpId, challengeOpType, grantId } = params;\n\n if (challengeOpType && challengeOpType !== GRANT_OP_TYPE) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge used an unsupported escrow op type\",\n 402,\n { opType: challengeOpType },\n );\n }\n\n const challengedGrantId = challengeOpId ?? challengeGrantId;\n if (!challengedGrantId) return;\n\n if (!isBytes32Hex(challengedGrantId)) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge used an invalid escrow op id\",\n 402,\n { opId: challengedGrantId },\n );\n }\n\n if (challengedGrantId.toLowerCase() !== grantId.toLowerCase()) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge did not match the requested grant\",\n 402,\n { opId: challengedGrantId, grantId },\n );\n }\n}\n\n/**\n * Build a Web3Signed-authenticated Personal Server data read request.\n *\n * @param params - Personal Server URL, scope, grant id, and an EIP-191 signer.\n * @returns The request URL, method, path, and headers (including `Authorization`).\n */\nexport async function buildPersonalServerDataReadRequest(params: {\n /** Base URL of the user's Personal Server. */\n personalServerUrl: string;\n /** Scope to read (e.g. `\"icloud_notes.notes\"`). */\n scope: string;\n /** Grant id authorizing the read. */\n grantId: string;\n /** EIP-191 signer for the Web3Signed header (the app key). */\n signMessage: Web3SignedSignFn;\n}): Promise<PersonalServerDataReadRequest> {\n const base = stripTrailingSlash(params.personalServerUrl);\n const path = dataPathForScope(params.scope);\n const authorization = await buildWeb3SignedHeader({\n signMessage: params.signMessage,\n aud: base,\n method: \"GET\",\n uri: path,\n grantId: params.grantId,\n });\n const headers: Record<string, string> = {\n Authorization: authorization,\n Accept: \"application/json\",\n };\n return { url: `${base}${path}`, method: \"GET\", path, headers };\n}\n\n/**\n * Parse a `402 Payment Required` body into a {@link PersonalServerPaymentRequired}.\n *\n * @remarks\n * Accepts a few field spellings and falls back to the read's own grantId and the\n * native asset when a field is absent.\n *\n * @param res - The 402 response.\n * @param grantId - The grant id of the read (default `opId`).\n * @returns The parsed payment requirement.\n */\nexport async function parsePersonalServerPaymentRequired(\n res: FetchResponseLike,\n grantId: string,\n): Promise<PersonalServerPaymentRequired> {\n let raw: unknown = undefined;\n try {\n raw = await res.json();\n } catch {\n raw = undefined;\n }\n const body = asRecord(raw) ?? {};\n const accept =\n Array.isArray(body.accepts) && body.accepts.length > 0\n ? asRecord(body.accepts[0])\n : undefined;\n const message = asRecord(accept?.message);\n const challengeGrantId = stringField(body, \"grantId\");\n const challengeOpId =\n stringField(message, \"opId\") ?? stringField(body, \"opId\");\n const challengeOpType =\n stringField(message, \"opType\") ?? stringField(body, \"opType\");\n\n assertChallengeMatchesGrant({\n challengeGrantId,\n challengeOpId,\n challengeOpType,\n grantId,\n });\n\n const amountValue =\n stringField(message, \"amount\") ??\n stringField(body, \"amount\") ??\n stringField(body, \"maxAmountRequired\") ??\n \"0\";\n return {\n grantId,\n network: stringField(accept, \"network\") ?? stringField(body, \"network\"),\n paymentNonce:\n stringField(message, \"paymentNonce\") ?? stringField(body, \"paymentNonce\"),\n accessRecord: parseAccessRecord(accept?.accessRecord ?? body.accessRecord),\n asset:\n stringField(message, \"asset\") ??\n stringField(body, \"asset\") ??\n NATIVE_ASSET_ADDRESS,\n amount: amountValue,\n raw,\n };\n}\n\nfunction hasPositiveAmount(amount: string): boolean {\n if (!/^\\d+$/.test(amount)) return false;\n return BigInt(amount) > 0n;\n}\n\n/**\n * Read approved data from a Personal Server, settling a 402 via escrow.\n *\n * @remarks\n * Sends a Web3Signed-authenticated `GET /v1/data/{scope}`. On `402`, parses what\n * is owed, authorizes an escrow payment for the grant via `escrow`, and retries\n * once. If escrow is not configured, throws {@link PaymentRequiredError} carrying\n * the parsed requirement so callers can debug amount/asset.\n *\n * Transport failures (fetch throwing — the browser-PS relay reconnect window)\n * are retried with backoff per `transportRetry` (default 3 attempts). The paid\n * retry reuses the already-signed `X-PAYMENT` header, so transport retries can\n * never double-pay.\n *\n * @param params - Connection details, app signer, optional escrow config and fetch.\n * @returns `{ data, payment? }`.\n */\nexport async function readPersonalServerData(params: {\n personalServerUrl: string;\n scope: string;\n grantId: string;\n payerAddress: `0x${string}`;\n signMessage: Web3SignedSignFn;\n escrow?: EscrowPaymentConfig;\n fetchFn?: PersonalServerFetch;\n transportRetry?: PersonalServerTransportRetryOptions;\n}): Promise<PersonalServerReadResult> {\n const fetchFn =\n params.fetchFn ?? (globalThis.fetch as unknown as PersonalServerFetch);\n if (!fetchFn) {\n throw new PersonalServerReadError(\n \"No fetch implementation available for Personal Server read\",\n );\n }\n const transportRetry = {\n ...TRANSPORT_RETRY_DEFAULTS,\n ...params.transportRetry,\n };\n\n const buildRequest = () =>\n buildPersonalServerDataReadRequest({\n personalServerUrl: params.personalServerUrl,\n scope: params.scope,\n grantId: params.grantId,\n signMessage: params.signMessage,\n });\n\n let res = await fetchWithTransportRetry(\n fetchFn,\n buildRequest,\n transportRetry,\n );\n\n let payment: DirectPaymentReceipt | undefined;\n\n if (res.status === 402) {\n const required = await parsePersonalServerPaymentRequired(\n res,\n params.grantId,\n );\n if (!params.escrow) {\n throw new PaymentRequiredError(\n \"Personal Server requires payment but no escrow config is set\",\n {\n scope: params.scope,\n grantId: required.grantId,\n asset: required.asset,\n amount: required.amount,\n },\n );\n }\n\n if (!hasPositiveAmount(required.amount)) {\n throw new PaymentRequiredError(\n \"Personal Server payment challenge did not include a positive amount\",\n {\n scope: params.scope,\n grantId: required.grantId,\n asset: required.asset,\n amount: required.amount,\n },\n );\n }\n\n const paymentHeader = await buildGrantPaymentHeader({\n payerAddress: params.payerAddress,\n required,\n config: params.escrow,\n });\n\n // Re-sign and retry with x402 payment proof for the Personal Server to\n // validate. The payment header is built exactly once: transport retries\n // below re-sign only the Web3Signed auth and resend the SAME X-PAYMENT\n // (same paymentNonce), so a dropped tunnel cannot mint a second payment.\n res = await fetchWithTransportRetry(\n fetchFn,\n async () => {\n const retry = await buildRequest();\n return {\n ...retry,\n headers: { ...retry.headers, \"X-PAYMENT\": paymentHeader },\n };\n },\n transportRetry,\n );\n\n if (res.status === 402) {\n throw new PaymentRequiredError(\n \"Personal Server still requires payment after escrow settlement\",\n {\n scope: params.scope,\n grantId: required.grantId,\n asset: required.asset,\n amount: required.amount,\n payment,\n },\n );\n }\n }\n\n if (!res.ok) {\n const detail = await res.text().catch(() => \"\");\n throw new PersonalServerReadError(\n `Personal Server read failed: ${res.status} ${res.statusText}`,\n res.status,\n { scope: params.scope, body: detail.slice(0, 500) },\n );\n }\n\n payment = paymentReceiptFromHeader(res.headers.get(\"X-PAYMENT-RESPONSE\"));\n return { data: await res.json(), payment };\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Server entry point for the Vana SDK direct Data Portability flow.\n *\n * @remarks\n * Exposes {@link createDirectDataController} and its supporting types/errors.\n * This is a Node/server entry point — it owns the app private key and must never\n * be imported into browser code.\n *\n * @example\n * ```typescript\n * import { createDirectDataController } from \"@opendatalabs/vana-sdk/server\";\n *\n * export const vana = createDirectDataController({\n * env: process.env.VANA_ENV === \"dev\" ? \"dev\" : \"production\",\n * appPrivateKey: process.env.VANA_APP_PRIVATE_KEY!,\n * app: { id: \"notes-lens\", name: \"Notes Lens\", homepageUrl: process.env.VANA_APP_URL! },\n * source: \"icloud_notes\",\n * scopes: [\"icloud_notes.notes\"],\n * });\n * ```\n *\n * @category Direct\n * @module server\n */\n\nexport {\n createDirectDataController,\n type DirectDataController,\n type DirectDataControllerConfig,\n type DirectEscrowConfig,\n} from \"./direct/controller\";\n\n// Lower-level building blocks (advanced use / custom transports).\nexport {\n createDefaultAccessRequestClient,\n buildApprovalUrl,\n type DefaultAccessRequestClientOptions,\n type FetchLike,\n} from \"./direct/access-request-client\";\nexport {\n buildPersonalServerDataReadRequest,\n readPersonalServerData,\n parsePersonalServerPaymentRequired,\n dataPathForScope,\n type PersonalServerDataReadRequest,\n type PersonalServerReadResult,\n type PersonalServerFetch,\n type FetchResponseLike,\n} from \"./direct/personal-server-read\";\n// Escrow-backed payment (built on protocol/escrow).\nexport {\n authorizeGrantPayment,\n toDirectPaymentReceipt,\n toDirectFeeBreakdown,\n createDefaultNonceSource,\n GRANT_OP_TYPE,\n type EscrowPaymentConfig,\n type SignTypedDataFn,\n type PaymentNonceSource,\n} from \"./direct/escrow-payment\";\nexport {\n getDirectEndpoints,\n PRODUCTION_ENDPOINTS,\n DEV_ENDPOINTS,\n} from \"./direct/endpoints\";\n\n// Errors\nexport {\n DirectConfigError,\n AccessNotApprovedError,\n PersonalServerReadError,\n PaymentRequiredError,\n} from \"./direct/errors\";\n\n// Shared types\nexport type {\n DirectEnv,\n DirectNetwork,\n DirectAppConfig,\n AppIdentity,\n DirectServiceEndpoints,\n AccessRequest,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n AccessRequestClient,\n DirectOpTypeValue,\n PersonalServerPaymentRequired,\n DirectPaymentReceipt,\n DirectFeeBreakdown,\n} from \"./direct/types\";\n\n// Op-type vocabulary constant.\nexport { DirectOpType } from \"./direct/types\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyBA,wBAKO;AAGP,mCAKO;AACP,kCASO;AAEP,4BASO;AACP,uBAIO;AAGP,oBAKO;AAqBP,mBAA6B;","names":[]}
1
+ {"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Server entry point for the Vana SDK direct Data Portability flow.\n *\n * @remarks\n * Exposes {@link createDirectDataController} and its supporting types/errors.\n * This is a Node/server entry point — it owns the app private key and must never\n * be imported into browser code.\n *\n * @example\n * ```typescript\n * import { createDirectDataController } from \"@opendatalabs/vana-sdk/server\";\n *\n * export const vana = createDirectDataController({\n * env: process.env.VANA_ENV === \"dev\" ? \"dev\" : \"production\",\n * appPrivateKey: process.env.VANA_APP_PRIVATE_KEY!,\n * app: { id: \"notes-lens\", name: \"Notes Lens\", homepageUrl: process.env.VANA_APP_URL! },\n * source: \"icloud_notes\",\n * scopes: [\"icloud_notes.notes\"],\n * });\n * ```\n *\n * @category Direct\n * @module server\n */\n\nexport {\n createDirectDataController,\n type DirectDataController,\n type DirectDataControllerConfig,\n type DirectEscrowConfig,\n} from \"./direct/controller\";\n\n// Lower-level building blocks (advanced use / custom transports).\nexport {\n createDefaultAccessRequestClient,\n buildApprovalUrl,\n type DefaultAccessRequestClientOptions,\n type FetchLike,\n} from \"./direct/access-request-client\";\nexport {\n buildPersonalServerDataReadRequest,\n readPersonalServerData,\n parsePersonalServerPaymentRequired,\n dataPathForScope,\n type PersonalServerDataReadRequest,\n type PersonalServerReadResult,\n type PersonalServerFetch,\n type PersonalServerTransportRetryOptions,\n type FetchResponseLike,\n} from \"./direct/personal-server-read\";\n// Escrow-backed payment (built on protocol/escrow).\nexport {\n authorizeGrantPayment,\n toDirectPaymentReceipt,\n toDirectFeeBreakdown,\n createDefaultNonceSource,\n GRANT_OP_TYPE,\n type EscrowPaymentConfig,\n type SignTypedDataFn,\n type PaymentNonceSource,\n} from \"./direct/escrow-payment\";\nexport {\n getDirectEndpoints,\n PRODUCTION_ENDPOINTS,\n DEV_ENDPOINTS,\n} from \"./direct/endpoints\";\n\n// Errors\nexport {\n DirectConfigError,\n AccessNotApprovedError,\n PersonalServerReadError,\n PaymentRequiredError,\n} from \"./direct/errors\";\n\n// Shared types\nexport type {\n DirectEnv,\n DirectNetwork,\n DirectAppConfig,\n AppIdentity,\n DirectServiceEndpoints,\n AccessRequest,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n AccessRequestClient,\n DirectOpTypeValue,\n PersonalServerPaymentRequired,\n DirectPaymentReceipt,\n DirectFeeBreakdown,\n} from \"./direct/types\";\n\n// Op-type vocabulary constant.\nexport { DirectOpType } from \"./direct/types\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyBA,wBAKO;AAGP,mCAKO;AACP,kCAUO;AAEP,4BASO;AACP,uBAIO;AAGP,oBAKO;AAqBP,mBAA6B;","names":[]}
package/dist/server.d.ts CHANGED
@@ -24,7 +24,7 @@
24
24
  */
25
25
  export { createDirectDataController, type DirectDataController, type DirectDataControllerConfig, type DirectEscrowConfig, } from "./direct/controller";
26
26
  export { createDefaultAccessRequestClient, buildApprovalUrl, type DefaultAccessRequestClientOptions, type FetchLike, } from "./direct/access-request-client";
27
- export { buildPersonalServerDataReadRequest, readPersonalServerData, parsePersonalServerPaymentRequired, dataPathForScope, type PersonalServerDataReadRequest, type PersonalServerReadResult, type PersonalServerFetch, type FetchResponseLike, } from "./direct/personal-server-read";
27
+ export { buildPersonalServerDataReadRequest, readPersonalServerData, parsePersonalServerPaymentRequired, dataPathForScope, type PersonalServerDataReadRequest, type PersonalServerReadResult, type PersonalServerFetch, type PersonalServerTransportRetryOptions, type FetchResponseLike, } from "./direct/personal-server-read";
28
28
  export { authorizeGrantPayment, toDirectPaymentReceipt, toDirectFeeBreakdown, createDefaultNonceSource, GRANT_OP_TYPE, type EscrowPaymentConfig, type SignTypedDataFn, type PaymentNonceSource, } from "./direct/escrow-payment";
29
29
  export { getDirectEndpoints, PRODUCTION_ENDPOINTS, DEV_ENDPOINTS, } from "./direct/endpoints";
30
30
  export { DirectConfigError, AccessNotApprovedError, PersonalServerReadError, PaymentRequiredError, } from "./direct/errors";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Server entry point for the Vana SDK direct Data Portability flow.\n *\n * @remarks\n * Exposes {@link createDirectDataController} and its supporting types/errors.\n * This is a Node/server entry point — it owns the app private key and must never\n * be imported into browser code.\n *\n * @example\n * ```typescript\n * import { createDirectDataController } from \"@opendatalabs/vana-sdk/server\";\n *\n * export const vana = createDirectDataController({\n * env: process.env.VANA_ENV === \"dev\" ? \"dev\" : \"production\",\n * appPrivateKey: process.env.VANA_APP_PRIVATE_KEY!,\n * app: { id: \"notes-lens\", name: \"Notes Lens\", homepageUrl: process.env.VANA_APP_URL! },\n * source: \"icloud_notes\",\n * scopes: [\"icloud_notes.notes\"],\n * });\n * ```\n *\n * @category Direct\n * @module server\n */\n\nexport {\n createDirectDataController,\n type DirectDataController,\n type DirectDataControllerConfig,\n type DirectEscrowConfig,\n} from \"./direct/controller\";\n\n// Lower-level building blocks (advanced use / custom transports).\nexport {\n createDefaultAccessRequestClient,\n buildApprovalUrl,\n type DefaultAccessRequestClientOptions,\n type FetchLike,\n} from \"./direct/access-request-client\";\nexport {\n buildPersonalServerDataReadRequest,\n readPersonalServerData,\n parsePersonalServerPaymentRequired,\n dataPathForScope,\n type PersonalServerDataReadRequest,\n type PersonalServerReadResult,\n type PersonalServerFetch,\n type FetchResponseLike,\n} from \"./direct/personal-server-read\";\n// Escrow-backed payment (built on protocol/escrow).\nexport {\n authorizeGrantPayment,\n toDirectPaymentReceipt,\n toDirectFeeBreakdown,\n createDefaultNonceSource,\n GRANT_OP_TYPE,\n type EscrowPaymentConfig,\n type SignTypedDataFn,\n type PaymentNonceSource,\n} from \"./direct/escrow-payment\";\nexport {\n getDirectEndpoints,\n PRODUCTION_ENDPOINTS,\n DEV_ENDPOINTS,\n} from \"./direct/endpoints\";\n\n// Errors\nexport {\n DirectConfigError,\n AccessNotApprovedError,\n PersonalServerReadError,\n PaymentRequiredError,\n} from \"./direct/errors\";\n\n// Shared types\nexport type {\n DirectEnv,\n DirectNetwork,\n DirectAppConfig,\n AppIdentity,\n DirectServiceEndpoints,\n AccessRequest,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n AccessRequestClient,\n DirectOpTypeValue,\n PersonalServerPaymentRequired,\n DirectPaymentReceipt,\n DirectFeeBreakdown,\n} from \"./direct/types\";\n\n// Op-type vocabulary constant.\nexport { DirectOpType } from \"./direct/types\";\n"],"mappings":"AAyBA;AAAA,EACE;AAAA,OAIK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAqBP,SAAS,oBAAoB;","names":[]}
1
+ {"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Server entry point for the Vana SDK direct Data Portability flow.\n *\n * @remarks\n * Exposes {@link createDirectDataController} and its supporting types/errors.\n * This is a Node/server entry point — it owns the app private key and must never\n * be imported into browser code.\n *\n * @example\n * ```typescript\n * import { createDirectDataController } from \"@opendatalabs/vana-sdk/server\";\n *\n * export const vana = createDirectDataController({\n * env: process.env.VANA_ENV === \"dev\" ? \"dev\" : \"production\",\n * appPrivateKey: process.env.VANA_APP_PRIVATE_KEY!,\n * app: { id: \"notes-lens\", name: \"Notes Lens\", homepageUrl: process.env.VANA_APP_URL! },\n * source: \"icloud_notes\",\n * scopes: [\"icloud_notes.notes\"],\n * });\n * ```\n *\n * @category Direct\n * @module server\n */\n\nexport {\n createDirectDataController,\n type DirectDataController,\n type DirectDataControllerConfig,\n type DirectEscrowConfig,\n} from \"./direct/controller\";\n\n// Lower-level building blocks (advanced use / custom transports).\nexport {\n createDefaultAccessRequestClient,\n buildApprovalUrl,\n type DefaultAccessRequestClientOptions,\n type FetchLike,\n} from \"./direct/access-request-client\";\nexport {\n buildPersonalServerDataReadRequest,\n readPersonalServerData,\n parsePersonalServerPaymentRequired,\n dataPathForScope,\n type PersonalServerDataReadRequest,\n type PersonalServerReadResult,\n type PersonalServerFetch,\n type PersonalServerTransportRetryOptions,\n type FetchResponseLike,\n} from \"./direct/personal-server-read\";\n// Escrow-backed payment (built on protocol/escrow).\nexport {\n authorizeGrantPayment,\n toDirectPaymentReceipt,\n toDirectFeeBreakdown,\n createDefaultNonceSource,\n GRANT_OP_TYPE,\n type EscrowPaymentConfig,\n type SignTypedDataFn,\n type PaymentNonceSource,\n} from \"./direct/escrow-payment\";\nexport {\n getDirectEndpoints,\n PRODUCTION_ENDPOINTS,\n DEV_ENDPOINTS,\n} from \"./direct/endpoints\";\n\n// Errors\nexport {\n DirectConfigError,\n AccessNotApprovedError,\n PersonalServerReadError,\n PaymentRequiredError,\n} from \"./direct/errors\";\n\n// Shared types\nexport type {\n DirectEnv,\n DirectNetwork,\n DirectAppConfig,\n AppIdentity,\n DirectServiceEndpoints,\n AccessRequest,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n AccessRequestClient,\n DirectOpTypeValue,\n PersonalServerPaymentRequired,\n DirectPaymentReceipt,\n DirectFeeBreakdown,\n} from \"./direct/types\";\n\n// Op-type vocabulary constant.\nexport { DirectOpType } from \"./direct/types\";\n"],"mappings":"AAyBA;AAAA,EACE;AAAA,OAIK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAMK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAqBP,SAAS,oBAAoB;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendatalabs/vana-sdk",
3
- "version": "3.10.0",
3
+ "version": "3.11.0",
4
4
  "description": "A TypeScript library for interacting with Vana Network smart contracts.",
5
5
  "publishConfig": {
6
6
  "access": "public"