@opendatalabs/vana-sdk 3.13.2 → 3.13.4-pr.184.80a4b1f

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.
Files changed (104) hide show
  1. package/dist/account/personal-server-lite-owner-binding.d.ts +1 -1
  2. package/dist/account/personal-server-registration.d.ts +1 -1
  3. package/dist/auth/errors.d.ts +1 -1
  4. package/dist/auth/oauth-client.d.ts +1 -1
  5. package/dist/browser.d.ts +1 -1
  6. package/dist/chains/index.d.ts +2 -2
  7. package/dist/chains.browser.d.ts +2 -2
  8. package/dist/chains.d.ts +2 -2
  9. package/dist/chains.node.d.ts +2 -2
  10. package/dist/contracts/contractController.d.ts +4 -4
  11. package/dist/core/client.d.ts +1 -1
  12. package/dist/crypto/ecies/base.d.ts +1 -1
  13. package/dist/crypto/ecies/browser.d.ts +1 -1
  14. package/dist/crypto/ecies/index.d.ts +2 -2
  15. package/dist/crypto/ecies/node.d.ts +1 -1
  16. package/dist/crypto/services/WalletKeyEncryptionService.d.ts +1 -1
  17. package/dist/direct/access-request-client.cjs +1 -0
  18. package/dist/direct/access-request-client.cjs.map +1 -1
  19. package/dist/direct/access-request-client.d.ts +2 -2
  20. package/dist/direct/access-request-client.js +1 -0
  21. package/dist/direct/access-request-client.js.map +1 -1
  22. package/dist/direct/connect-flow.cjs +1 -1
  23. package/dist/direct/connect-flow.cjs.map +1 -1
  24. package/dist/direct/connect-flow.d.ts +1 -1
  25. package/dist/direct/connect-flow.js +1 -1
  26. package/dist/direct/connect-flow.js.map +1 -1
  27. package/dist/direct/controller.cjs.map +1 -1
  28. package/dist/direct/controller.d.ts +8 -8
  29. package/dist/direct/controller.js.map +1 -1
  30. package/dist/direct/endpoints.d.ts +1 -1
  31. package/dist/direct/errors.d.ts +1 -1
  32. package/dist/direct/escrow-payment.cjs +142 -11
  33. package/dist/direct/escrow-payment.cjs.map +1 -1
  34. package/dist/direct/escrow-payment.d.ts +65 -12
  35. package/dist/direct/escrow-payment.js +138 -11
  36. package/dist/direct/escrow-payment.js.map +1 -1
  37. package/dist/direct/personal-server-read.cjs +156 -23
  38. package/dist/direct/personal-server-read.cjs.map +1 -1
  39. package/dist/direct/personal-server-read.d.ts +28 -18
  40. package/dist/direct/personal-server-read.js +159 -25
  41. package/dist/direct/personal-server-read.js.map +1 -1
  42. package/dist/direct/types.cjs.map +1 -1
  43. package/dist/direct/types.d.ts +61 -22
  44. package/dist/direct/types.js.map +1 -1
  45. package/dist/direct/use-direct-vana-connect.d.ts +1 -1
  46. package/dist/generated/abi/index.d.ts +25 -25
  47. package/dist/index.browser.d.ts +44 -44
  48. package/dist/index.browser.js.map +2 -2
  49. package/dist/index.node.cjs +480 -0
  50. package/dist/index.node.cjs.map +4 -4
  51. package/dist/index.node.d.ts +48 -45
  52. package/dist/index.node.js +468 -0
  53. package/dist/index.node.js.map +4 -4
  54. package/dist/node.d.ts +1 -1
  55. package/dist/platform/browser-only.d.ts +3 -3
  56. package/dist/platform/browser-safe.d.ts +3 -3
  57. package/dist/platform/browser.d.ts +1 -1
  58. package/dist/platform/index.d.ts +5 -5
  59. package/dist/platform/node.d.ts +1 -1
  60. package/dist/platform/ports/openpgp-port.d.ts +1 -1
  61. package/dist/platform/utils.d.ts +1 -1
  62. package/dist/platform.browser.d.ts +4 -4
  63. package/dist/platform.d.ts +5 -5
  64. package/dist/platform.node.d.ts +5 -5
  65. package/dist/protocol/data-point-status.d.ts +1 -1
  66. package/dist/protocol/eip712.cjs.map +1 -1
  67. package/dist/protocol/eip712.js.map +1 -1
  68. package/dist/protocol/escrow-deposit.d.ts +1 -1
  69. package/dist/protocol/escrow.cjs.map +1 -1
  70. package/dist/protocol/escrow.d.ts +11 -4
  71. package/dist/protocol/escrow.js.map +1 -1
  72. package/dist/protocol/fee-registry.d.ts +1 -1
  73. package/dist/protocol/gateway.cjs.map +1 -1
  74. package/dist/protocol/gateway.js.map +1 -1
  75. package/dist/protocol/grants.d.ts +1 -1
  76. package/dist/protocol/personal-server-data.d.ts +2 -2
  77. package/dist/protocol/personal-server-registration.d.ts +2 -2
  78. package/dist/react.d.ts +3 -3
  79. package/dist/server.cjs +10 -0
  80. package/dist/server.cjs.map +1 -1
  81. package/dist/server.d.ts +8 -8
  82. package/dist/server.js +10 -0
  83. package/dist/server.js.map +1 -1
  84. package/dist/session-relay/client.d.ts +1 -1
  85. package/dist/session-relay/endpoints.d.ts +1 -1
  86. package/dist/session-relay/errors.d.ts +1 -1
  87. package/dist/session-relay/index.d.ts +5 -5
  88. package/dist/session-relay/signing.d.ts +1 -1
  89. package/dist/session-relay/types.d.ts +1 -1
  90. package/dist/session-relay.d.ts +5 -5
  91. package/dist/storage/default.d.ts +2 -2
  92. package/dist/storage/index.d.ts +16 -16
  93. package/dist/storage/manager.d.ts +1 -1
  94. package/dist/storage/providers/callback-storage.d.ts +2 -2
  95. package/dist/storage/providers/dropbox.d.ts +1 -1
  96. package/dist/storage/providers/google-drive.d.ts +1 -1
  97. package/dist/storage/providers/ipfs.d.ts +1 -1
  98. package/dist/storage/providers/pinata.d.ts +1 -1
  99. package/dist/storage/providers/r2.d.ts +1 -1
  100. package/dist/storage/providers/vana-storage.d.ts +3 -3
  101. package/dist/types/config.d.ts +1 -1
  102. package/dist/types/index.d.ts +6 -6
  103. package/dist/types.d.ts +1 -1
  104. package/package.json +13 -1
@@ -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/**\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
+ {"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` — signs the challenged escrow operation and\n * retries once.\n *\n * The 402 body is parsed into a validated grant or receipt-bound data-access\n * operation, 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 buildEscrowPaymentHeader,\n DATA_ACCESS_OP_TYPE,\n GRANT_OP_TYPE,\n paymentResponseMetadataFromHeader,\n type EscrowPaymentHeaderConfig,\n} from \"./escrow-payment\";\nimport { PaymentRequiredError, PersonalServerReadError } from \"./errors\";\nimport type {\n DirectPaymentResponseMetadata,\n PersonalServerDataAccessPaymentOperation,\n PersonalServerPaymentOperation,\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}. */\nexport interface PersonalServerReadResult {\n /** The decoded JSON payload returned by the Personal Server. */\n data: unknown;\n /**\n * Shape-validated but unauthenticated payment metadata echoed by the\n * Personal Server. Never treat this field as accounting proof.\n */\n payment?: DirectPaymentResponseMetadata;\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\nconst UINT256_MAX = (1n << 256n) - 1n;\n\nfunction isAddressHex(value: string): boolean {\n return /^0x[0-9a-fA-F]{40}$/.test(value);\n}\n\nfunction isUint256Decimal(value: string, allowZero: boolean): boolean {\n const pattern = allowZero ? /^(0|[1-9]\\d*)$/ : /^[1-9]\\d*$/;\n return (\n value.length <= UINT256_MAX.toString().length &&\n pattern.test(value) &&\n BigInt(value) <= UINT256_MAX\n );\n}\n\nfunction isValidDataAccessRecord(record: EscrowAccessRecord): boolean {\n return (\n isBytes32Hex(record.dataPointId) &&\n isUint256Decimal(record.version, false) &&\n isAddressHex(record.accessor) &&\n isBytes32Hex(record.recordId) &&\n /^0x[0-9a-fA-F]{130}$/.test(record.signature)\n );\n}\n\nfunction parseLegacyGrantOperation(params: {\n challengeGrantId?: string;\n challengeOpId?: string;\n challengeOpType?: string;\n grantId: string;\n}): { opType: typeof GRANT_OP_TYPE; opId: string } {\n const { challengeGrantId, challengeOpId, challengeOpType, grantId } = params;\n\n const opType = challengeOpType ?? GRANT_OP_TYPE;\n if (opType !== 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 opId = challengeOpId ?? challengeGrantId ?? grantId;\n\n if (!opId || !isBytes32Hex(opId)) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge used an invalid escrow op id\",\n 402,\n { opId },\n );\n }\n\n if (opId.toLowerCase() !== grantId.toLowerCase()) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge did not match the requested grant\",\n 402,\n { opId, grantId },\n );\n }\n\n return { opType: GRANT_OP_TYPE, opId };\n}\n\nfunction hasDataAccessMarker(body: Record<string, unknown>): boolean {\n if (stringField(body, \"opType\") === DATA_ACCESS_OP_TYPE) return true;\n return (\n Array.isArray(body.accepts) &&\n body.accepts.some((value) => {\n const accept = asRecord(value);\n return (\n stringField(asRecord(accept?.message), \"opType\") === DATA_ACCESS_OP_TYPE\n );\n })\n );\n}\n\nfunction parseCanonicalDataAccessAccept(\n value: unknown,\n): Omit<PersonalServerDataAccessPaymentOperation, \"grantId\" | \"raw\"> | null {\n const accept = asRecord(value);\n const message = asRecord(accept?.message);\n if (\n stringField(accept, \"scheme\") !== \"vana-escrow-grant\" ||\n stringField(message, \"opType\") !== DATA_ACCESS_OP_TYPE\n ) {\n return null;\n }\n\n const network = stringField(accept, \"network\");\n const payerAddress = stringField(message, \"payerAddress\");\n const opId = stringField(message, \"opId\");\n const asset = stringField(message, \"asset\");\n const amount = stringField(message, \"amount\");\n const paymentNonce = stringField(message, \"paymentNonce\");\n const accessRecord = parseAccessRecord(accept?.accessRecord);\n const acceptAsset = stringField(accept, \"asset\");\n const acceptAmount = stringField(accept, \"amount\");\n\n if (\n !network ||\n !payerAddress ||\n !isAddressHex(payerAddress) ||\n !opId ||\n !isBytes32Hex(opId) ||\n !asset ||\n !isAddressHex(asset) ||\n !amount ||\n !isUint256Decimal(amount, true) ||\n !paymentNonce ||\n !isUint256Decimal(paymentNonce, false) ||\n !accessRecord ||\n !isValidDataAccessRecord(accessRecord) ||\n payerAddress.toLowerCase() !== accessRecord.accessor.toLowerCase() ||\n opId.toLowerCase() !== accessRecord.recordId.toLowerCase() ||\n !acceptAsset ||\n acceptAsset.toLowerCase() !== asset.toLowerCase() ||\n acceptAmount !== amount\n ) {\n return null;\n }\n\n return {\n opType: DATA_ACCESS_OP_TYPE,\n opId,\n network,\n paymentNonce,\n accessRecord,\n asset,\n amount,\n };\n}\n\nfunction parseCanonicalDataAccessPayment(params: {\n body: Record<string, unknown>;\n grantId: string;\n raw: unknown;\n}): PersonalServerDataAccessPaymentOperation {\n const { body, grantId, raw } = params;\n if (body.x402Version !== 1 || body.error !== \"PAYMENT_REQUIRED\") {\n throw new PersonalServerReadError(\n \"Personal Server data-access payment response was not a canonical x402 challenge\",\n 402,\n );\n }\n\n const operation = Array.isArray(body.accepts)\n ? body.accepts\n .map(parseCanonicalDataAccessAccept)\n .find((candidate) => candidate !== null)\n : undefined;\n if (!operation) {\n throw new PersonalServerReadError(\n \"Personal Server data-access payment challenge was untrusted or incomplete\",\n 402,\n );\n }\n\n return { grantId, raw, ...operation };\n}\n\nfunction preferredLegacyAccept(body: Record<string, unknown>) {\n if (!Array.isArray(body.accepts)) return undefined;\n return body.accepts.map(asRecord).find((accept) => {\n const message = asRecord(accept?.message);\n return (\n stringField(accept, \"scheme\") === \"vana-escrow-grant\" &&\n stringField(message, \"opType\") === GRANT_OP_TYPE\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 validated payment operation.\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 from a legacy grant challenge.\n *\n * Receipt-bound `data_access` uses a fail-closed canonical path: one compatible\n * `accepts` entry must contain the scheme, network, message, and complete\n * receipt. Its positive uint256 `paymentNonce` is mandatory because the\n * Personal Server encodes challenge freshness in that nonce and checks it on\n * retry. This parser shape-validates the receipt and binds `opId` to `recordId`;\n * it does not recover or verify the server signature.\n *\n * @param res - The 402 response.\n * @param grantId - The grant id of the read (default legacy grant `opId`).\n * @returns The parsed payment requirement.\n */\nexport async function parsePersonalServerPaymentRequired(\n res: FetchResponseLike,\n grantId: string,\n): Promise<PersonalServerPaymentOperation> {\n let raw: unknown = undefined;\n try {\n raw = await res.json();\n } catch {\n raw = undefined;\n }\n const body = asRecord(raw) ?? {};\n if (hasDataAccessMarker(body)) {\n return parseCanonicalDataAccessPayment({ body, grantId, raw });\n }\n\n // Legacy grant challenges predate the canonical x402 envelope. Retain their\n // field fallbacks only for truly flat bodies. Once an accepts envelope is\n // present, validate its canonical framing and select only a compatible offer.\n const hasAcceptsEnvelope = Object.prototype.hasOwnProperty.call(\n body,\n \"accepts\",\n );\n if (\n hasAcceptsEnvelope &&\n (body.x402Version !== 1 || body.error !== \"PAYMENT_REQUIRED\")\n ) {\n throw new PersonalServerReadError(\n \"Personal Server grant payment response was not a canonical x402 challenge\",\n 402,\n );\n }\n const accept = preferredLegacyAccept(body);\n if (hasAcceptsEnvelope && !accept) {\n throw new PersonalServerReadError(\n \"Personal Server grant payment challenge had no compatible escrow offer\",\n 402,\n );\n }\n\n if (accept) {\n const message = asRecord(accept.message);\n const network = stringField(accept, \"network\");\n const payerAddress = stringField(message, \"payerAddress\");\n const opId = stringField(message, \"opId\");\n const asset = stringField(message, \"asset\");\n const amount = stringField(message, \"amount\");\n const paymentNonce = stringField(message, \"paymentNonce\");\n const acceptAsset = stringField(accept, \"asset\");\n const acceptAmount = stringField(accept, \"amount\");\n const accessRecord = parseAccessRecord(accept.accessRecord);\n const hasAccessRecord = accept.accessRecord !== undefined;\n if (\n !network ||\n !payerAddress ||\n !isAddressHex(payerAddress) ||\n !opId ||\n !isBytes32Hex(opId) ||\n !asset ||\n !isAddressHex(asset) ||\n !amount ||\n !isUint256Decimal(amount, true) ||\n !paymentNonce ||\n !isUint256Decimal(paymentNonce, false) ||\n !acceptAsset ||\n acceptAsset.toLowerCase() !== asset.toLowerCase() ||\n acceptAmount !== amount ||\n (hasAccessRecord &&\n (!accessRecord ||\n !isValidDataAccessRecord(accessRecord) ||\n accessRecord.accessor.toLowerCase() !==\n payerAddress.toLowerCase())) ||\n (amount === \"0\" && !accessRecord)\n ) {\n throw new PersonalServerReadError(\n \"Personal Server grant payment challenge was untrusted or incomplete\",\n 402,\n );\n }\n const operation = parseLegacyGrantOperation({\n challengeOpId: opId,\n challengeOpType: stringField(message, \"opType\"),\n grantId,\n });\n return {\n grantId,\n ...operation,\n network,\n paymentNonce,\n asset,\n amount,\n raw,\n ...(accessRecord ? { accessRecord } : {}),\n };\n }\n\n const challengeGrantId = stringField(body, \"grantId\");\n const challengeOpId = stringField(body, \"opId\");\n const challengeOpType = stringField(body, \"opType\");\n\n const accessRecord = parseAccessRecord(body.accessRecord);\n const operation = parseLegacyGrantOperation({\n challengeGrantId,\n challengeOpId,\n challengeOpType,\n grantId,\n });\n\n const amountValue =\n stringField(body, \"amount\") ??\n stringField(body, \"maxAmountRequired\") ??\n \"0\";\n const payment = {\n grantId,\n network: stringField(body, \"network\"),\n paymentNonce: stringField(body, \"paymentNonce\"),\n asset: stringField(body, \"asset\") ?? NATIVE_ASSET_ADDRESS,\n amount: amountValue,\n raw,\n };\n return {\n ...payment,\n ...operation,\n ...(accessRecord ? { accessRecord } : {}),\n };\n}\n\nfunction hasPositiveAmount(amount: string): boolean {\n if (!/^\\d+$/.test(amount)) return false;\n return BigInt(amount) > 0n;\n}\n\nfunction isReceiptOnlyGrantAcknowledgment(\n required: PersonalServerPaymentOperation,\n payerAddress: string,\n): boolean {\n return (\n required.opType === GRANT_OP_TYPE &&\n required.amount === \"0\" &&\n required.accessRecord !== undefined &&\n isValidDataAccessRecord(required.accessRecord) &&\n required.accessRecord.accessor.toLowerCase() === payerAddress.toLowerCase()\n );\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 the challenged escrow operation, and retries once. If\n * escrow is not configured, throws {@link PaymentRequiredError} carrying the\n * 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?: EscrowPaymentHeaderConfig;\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: DirectPaymentResponseMetadata | 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 (\n required.opType === GRANT_OP_TYPE &&\n !hasPositiveAmount(required.amount) &&\n !isReceiptOnlyGrantAcknowledgment(required, params.payerAddress)\n ) {\n throw new PaymentRequiredError(\n \"Personal Server payment challenge included neither a positive amount nor a valid access receipt\",\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 buildEscrowPaymentHeader({\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 = paymentResponseMetadataFromHeader(\n res.headers.get(\"X-PAYMENT-RESPONSE\"),\n );\n return { data: await res.json(), payment };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBA,iCAAsC;AAEtC,oBAGO;AACP,4BAMO;AACP,oBAA8D;AAwD9D,MAAM,2BACJ;AAAA,EACE,UAAU;AAAA,EACV,gBAAgB;AAAA,EAChB,YAAY;AACd;AAGF,SAAS,gBAAgB,UAA0B;AACjD,SAAO,OAAO,SAAS,QAAQ,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,CAAC,IAAI;AACzE;AAGA,SAAS,aAAa,OAAyB;AAC7C,SAAO,iBAAiB,SAAS,MAAM,SAAS;AAClD;AAEA,MAAM,QAAQ,CAAC,OACb,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAWxD,eAAe,wBACb,SACA,cAKA,OAC4B;AAC5B,QAAM,WAAW,gBAAgB,MAAM,QAAQ;AAC/C,MAAI;AACJ,WAAS,UAAU,GAAG,UAAU,UAAU,WAAW,GAAG;AACtD,QAAI,UAAU,GAAG;AACf,YAAM;AAAA,QACJ,KAAK,IAAI,MAAM,YAAY,MAAM,iBAAiB,MAAM,UAAU,EAAE;AAAA,MACtE;AAAA,IACF;AACA,UAAM,UAAU,MAAM,aAAa;AACnC,QAAI;AACF,aAAO,MAAM,QAAQ,QAAQ,KAAK;AAAA,QAChC,QAAQ,QAAQ;AAAA,QAChB,SAAS,QAAQ;AAAA,MACnB,CAAC;AAAA,IACH,SAAS,OAAO;AAGd,UAAI,aAAa,KAAK,GAAG;AACvB,cAAM;AAAA,MACR;AACA,kBAAY;AAAA,IACd;AAAA,EACF;AACA,QAAM;AACR;AAaA,SAAS,mBAAmB,KAAqB;AAC/C,SAAO,IAAI,QAAQ,QAAQ,EAAE;AAC/B;AAGO,SAAS,iBAAiB,OAAuB;AACtD,SAAO,YAAY,mBAAmB,KAAK,CAAC;AAC9C;AAEA,SAAS,SAAS,OAAqD;AACrE,SAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,IAC5D,QACD;AACN;AAEA,SAAS,YACP,QACA,OACoB;AACpB,QAAM,QAAQ,SAAS,KAAK;AAC5B,SAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;AAEA,SAAS,kBAAkB,OAAgD;AACzE,QAAM,SAAS,SAAS,KAAK;AAC7B,QAAM,cAAc,YAAY,QAAQ,aAAa;AACrD,QAAM,UAAU,YAAY,QAAQ,SAAS;AAC7C,QAAM,WAAW,YAAY,QAAQ,UAAU;AAC/C,QAAM,WAAW,YAAY,QAAQ,UAAU;AAC/C,QAAM,YAAY,YAAY,QAAQ,WAAW;AAEjD,MAAI,CAAC,eAAe,CAAC,WAAW,CAAC,YAAY,CAAC,YAAY,CAAC,WAAW;AACpE,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,aAAa,OAAwB;AAC5C,SAAO,sBAAsB,KAAK,KAAK;AACzC;AAEA,MAAM,eAAe,MAAM,QAAQ;AAEnC,SAAS,aAAa,OAAwB;AAC5C,SAAO,sBAAsB,KAAK,KAAK;AACzC;AAEA,SAAS,iBAAiB,OAAe,WAA6B;AACpE,QAAM,UAAU,YAAY,mBAAmB;AAC/C,SACE,MAAM,UAAU,YAAY,SAAS,EAAE,UACvC,QAAQ,KAAK,KAAK,KAClB,OAAO,KAAK,KAAK;AAErB;AAEA,SAAS,wBAAwB,QAAqC;AACpE,SACE,aAAa,OAAO,WAAW,KAC/B,iBAAiB,OAAO,SAAS,KAAK,KACtC,aAAa,OAAO,QAAQ,KAC5B,aAAa,OAAO,QAAQ,KAC5B,uBAAuB,KAAK,OAAO,SAAS;AAEhD;AAEA,SAAS,0BAA0B,QAKgB;AACjD,QAAM,EAAE,kBAAkB,eAAe,iBAAiB,QAAQ,IAAI;AAEtE,QAAM,SAAS,mBAAmB;AAClC,MAAI,WAAW,qCAAe;AAC5B,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA,EAAE,QAAQ,gBAAgB;AAAA,IAC5B;AAAA,EACF;AAEA,QAAM,OAAO,iBAAiB,oBAAoB;AAElD,MAAI,CAAC,QAAQ,CAAC,aAAa,IAAI,GAAG;AAChC,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA,EAAE,KAAK;AAAA,IACT;AAAA,EACF;AAEA,MAAI,KAAK,YAAY,MAAM,QAAQ,YAAY,GAAG;AAChD,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA,EAAE,MAAM,QAAQ;AAAA,IAClB;AAAA,EACF;AAEA,SAAO,EAAE,QAAQ,qCAAe,KAAK;AACvC;AAEA,SAAS,oBAAoB,MAAwC;AACnE,MAAI,YAAY,MAAM,QAAQ,MAAM,0CAAqB,QAAO;AAChE,SACE,MAAM,QAAQ,KAAK,OAAO,KAC1B,KAAK,QAAQ,KAAK,CAAC,UAAU;AAC3B,UAAM,SAAS,SAAS,KAAK;AAC7B,WACE,YAAY,SAAS,QAAQ,OAAO,GAAG,QAAQ,MAAM;AAAA,EAEzD,CAAC;AAEL;AAEA,SAAS,+BACP,OAC0E;AAC1E,QAAM,SAAS,SAAS,KAAK;AAC7B,QAAM,UAAU,SAAS,QAAQ,OAAO;AACxC,MACE,YAAY,QAAQ,QAAQ,MAAM,uBAClC,YAAY,SAAS,QAAQ,MAAM,2CACnC;AACA,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,YAAY,QAAQ,SAAS;AAC7C,QAAM,eAAe,YAAY,SAAS,cAAc;AACxD,QAAM,OAAO,YAAY,SAAS,MAAM;AACxC,QAAM,QAAQ,YAAY,SAAS,OAAO;AAC1C,QAAM,SAAS,YAAY,SAAS,QAAQ;AAC5C,QAAM,eAAe,YAAY,SAAS,cAAc;AACxD,QAAM,eAAe,kBAAkB,QAAQ,YAAY;AAC3D,QAAM,cAAc,YAAY,QAAQ,OAAO;AAC/C,QAAM,eAAe,YAAY,QAAQ,QAAQ;AAEjD,MACE,CAAC,WACD,CAAC,gBACD,CAAC,aAAa,YAAY,KAC1B,CAAC,QACD,CAAC,aAAa,IAAI,KAClB,CAAC,SACD,CAAC,aAAa,KAAK,KACnB,CAAC,UACD,CAAC,iBAAiB,QAAQ,IAAI,KAC9B,CAAC,gBACD,CAAC,iBAAiB,cAAc,KAAK,KACrC,CAAC,gBACD,CAAC,wBAAwB,YAAY,KACrC,aAAa,YAAY,MAAM,aAAa,SAAS,YAAY,KACjE,KAAK,YAAY,MAAM,aAAa,SAAS,YAAY,KACzD,CAAC,eACD,YAAY,YAAY,MAAM,MAAM,YAAY,KAChD,iBAAiB,QACjB;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,gCAAgC,QAII;AAC3C,QAAM,EAAE,MAAM,SAAS,IAAI,IAAI;AAC/B,MAAI,KAAK,gBAAgB,KAAK,KAAK,UAAU,oBAAoB;AAC/D,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,YAAY,MAAM,QAAQ,KAAK,OAAO,IACxC,KAAK,QACF,IAAI,8BAA8B,EAClC,KAAK,CAAC,cAAc,cAAc,IAAI,IACzC;AACJ,MAAI,CAAC,WAAW;AACd,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,KAAK,GAAG,UAAU;AACtC;AAEA,SAAS,sBAAsB,MAA+B;AAC5D,MAAI,CAAC,MAAM,QAAQ,KAAK,OAAO,EAAG,QAAO;AACzC,SAAO,KAAK,QAAQ,IAAI,QAAQ,EAAE,KAAK,CAAC,WAAW;AACjD,UAAM,UAAU,SAAS,QAAQ,OAAO;AACxC,WACE,YAAY,QAAQ,QAAQ,MAAM,uBAClC,YAAY,SAAS,QAAQ,MAAM;AAAA,EAEvC,CAAC;AACH;AAQA,eAAsB,mCAAmC,QASd;AACzC,QAAM,OAAO,mBAAmB,OAAO,iBAAiB;AACxD,QAAM,OAAO,iBAAiB,OAAO,KAAK;AAC1C,QAAM,gBAAgB,UAAM,kDAAsB;AAAA,IAChD,aAAa,OAAO;AAAA,IACpB,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,KAAK;AAAA,IACL,SAAS,OAAO;AAAA,EAClB,CAAC;AACD,QAAM,UAAkC;AAAA,IACtC,eAAe;AAAA,IACf,QAAQ;AAAA,EACV;AACA,SAAO,EAAE,KAAK,GAAG,IAAI,GAAG,IAAI,IAAI,QAAQ,OAAO,MAAM,QAAQ;AAC/D;AAoBA,eAAsB,mCACpB,KACA,SACyC;AACzC,MAAI,MAAe;AACnB,MAAI;AACF,UAAM,MAAM,IAAI,KAAK;AAAA,EACvB,QAAQ;AACN,UAAM;AAAA,EACR;AACA,QAAM,OAAO,SAAS,GAAG,KAAK,CAAC;AAC/B,MAAI,oBAAoB,IAAI,GAAG;AAC7B,WAAO,gCAAgC,EAAE,MAAM,SAAS,IAAI,CAAC;AAAA,EAC/D;AAKA,QAAM,qBAAqB,OAAO,UAAU,eAAe;AAAA,IACzD;AAAA,IACA;AAAA,EACF;AACA,MACE,uBACC,KAAK,gBAAgB,KAAK,KAAK,UAAU,qBAC1C;AACA,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,QAAM,SAAS,sBAAsB,IAAI;AACzC,MAAI,sBAAsB,CAAC,QAAQ;AACjC,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,MAAI,QAAQ;AACV,UAAM,UAAU,SAAS,OAAO,OAAO;AACvC,UAAM,UAAU,YAAY,QAAQ,SAAS;AAC7C,UAAM,eAAe,YAAY,SAAS,cAAc;AACxD,UAAM,OAAO,YAAY,SAAS,MAAM;AACxC,UAAM,QAAQ,YAAY,SAAS,OAAO;AAC1C,UAAM,SAAS,YAAY,SAAS,QAAQ;AAC5C,UAAM,eAAe,YAAY,SAAS,cAAc;AACxD,UAAM,cAAc,YAAY,QAAQ,OAAO;AAC/C,UAAM,eAAe,YAAY,QAAQ,QAAQ;AACjD,UAAMA,gBAAe,kBAAkB,OAAO,YAAY;AAC1D,UAAM,kBAAkB,OAAO,iBAAiB;AAChD,QACE,CAAC,WACD,CAAC,gBACD,CAAC,aAAa,YAAY,KAC1B,CAAC,QACD,CAAC,aAAa,IAAI,KAClB,CAAC,SACD,CAAC,aAAa,KAAK,KACnB,CAAC,UACD,CAAC,iBAAiB,QAAQ,IAAI,KAC9B,CAAC,gBACD,CAAC,iBAAiB,cAAc,KAAK,KACrC,CAAC,eACD,YAAY,YAAY,MAAM,MAAM,YAAY,KAChD,iBAAiB,UAChB,oBACE,CAACA,iBACA,CAAC,wBAAwBA,aAAY,KACrCA,cAAa,SAAS,YAAY,MAChC,aAAa,YAAY,MAC9B,WAAW,OAAO,CAACA,eACpB;AACA,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,UAAMC,aAAY,0BAA0B;AAAA,MAC1C,eAAe;AAAA,MACf,iBAAiB,YAAY,SAAS,QAAQ;AAAA,MAC9C;AAAA,IACF,CAAC;AACD,WAAO;AAAA,MACL;AAAA,MACA,GAAGA;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAID,gBAAe,EAAE,cAAAA,cAAa,IAAI,CAAC;AAAA,IACzC;AAAA,EACF;AAEA,QAAM,mBAAmB,YAAY,MAAM,SAAS;AACpD,QAAM,gBAAgB,YAAY,MAAM,MAAM;AAC9C,QAAM,kBAAkB,YAAY,MAAM,QAAQ;AAElD,QAAM,eAAe,kBAAkB,KAAK,YAAY;AACxD,QAAM,YAAY,0BAA0B;AAAA,IAC1C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,cACJ,YAAY,MAAM,QAAQ,KAC1B,YAAY,MAAM,mBAAmB,KACrC;AACF,QAAM,UAAU;AAAA,IACd;AAAA,IACA,SAAS,YAAY,MAAM,SAAS;AAAA,IACpC,cAAc,YAAY,MAAM,cAAc;AAAA,IAC9C,OAAO,YAAY,MAAM,OAAO,KAAK;AAAA,IACrC,QAAQ;AAAA,IACR;AAAA,EACF;AACA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;AAAA,EACzC;AACF;AAEA,SAAS,kBAAkB,QAAyB;AAClD,MAAI,CAAC,QAAQ,KAAK,MAAM,EAAG,QAAO;AAClC,SAAO,OAAO,MAAM,IAAI;AAC1B;AAEA,SAAS,iCACP,UACA,cACS;AACT,SACE,SAAS,WAAW,uCACpB,SAAS,WAAW,OACpB,SAAS,iBAAiB,UAC1B,wBAAwB,SAAS,YAAY,KAC7C,SAAS,aAAa,SAAS,YAAY,MAAM,aAAa,YAAY;AAE9E;AAmBA,eAAsB,uBAAuB,QASP;AACpC,QAAM,UACJ,OAAO,WAAY,WAAW;AAChC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,iBAAiB;AAAA,IACrB,GAAG;AAAA,IACH,GAAG,OAAO;AAAA,EACZ;AAEA,QAAM,eAAe,MACnB,mCAAmC;AAAA,IACjC,mBAAmB,OAAO;AAAA,IAC1B,OAAO,OAAO;AAAA,IACd,SAAS,OAAO;AAAA,IAChB,aAAa,OAAO;AAAA,EACtB,CAAC;AAEH,MAAI,MAAM,MAAM;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI;AAEJ,MAAI,IAAI,WAAW,KAAK;AACtB,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA,OAAO;AAAA,IACT;AACA,QAAI,CAAC,OAAO,QAAQ;AAClB,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,UACE,OAAO,OAAO;AAAA,UACd,SAAS,SAAS;AAAA,UAClB,OAAO,SAAS;AAAA,UAChB,QAAQ,SAAS;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AAEA,QACE,SAAS,WAAW,uCACpB,CAAC,kBAAkB,SAAS,MAAM,KAClC,CAAC,iCAAiC,UAAU,OAAO,YAAY,GAC/D;AACA,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,UACE,OAAO,OAAO;AAAA,UACd,SAAS,SAAS;AAAA,UAClB,OAAO,SAAS;AAAA,UAChB,QAAQ,SAAS;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,gBAAgB,UAAM,gDAAyB;AAAA,MACnD,cAAc,OAAO;AAAA,MACrB;AAAA,MACA,QAAQ,OAAO;AAAA,IACjB,CAAC;AAMD,UAAM,MAAM;AAAA,MACV;AAAA,MACA,YAAY;AACV,cAAM,QAAQ,MAAM,aAAa;AACjC,eAAO;AAAA,UACL,GAAG;AAAA,UACH,SAAS,EAAE,GAAG,MAAM,SAAS,aAAa,cAAc;AAAA,QAC1D;AAAA,MACF;AAAA,MACA;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,KAAK;AACtB,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,UACE,OAAO,OAAO;AAAA,UACd,SAAS,SAAS;AAAA,UAClB,OAAO,SAAS;AAAA,UAChB,QAAQ,SAAS;AAAA,UACjB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,SAAS,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC9C,UAAM,IAAI;AAAA,MACR,gCAAgC,IAAI,MAAM,IAAI,IAAI,UAAU;AAAA,MAC5D,IAAI;AAAA,MACJ,EAAE,OAAO,OAAO,OAAO,MAAM,OAAO,MAAM,GAAG,GAAG,EAAE;AAAA,IACpD;AAAA,EACF;AAEA,gBAAU;AAAA,IACR,IAAI,QAAQ,IAAI,oBAAoB;AAAA,EACtC;AACA,SAAO,EAAE,MAAM,MAAM,IAAI,KAAK,GAAG,QAAQ;AAC3C;","names":["accessRecord","operation"]}
@@ -4,18 +4,18 @@
4
4
  * @remarks
5
5
  * The read targets the Personal Server data path (`/v1/data/{scope}`),
6
6
  * authenticates with a Web3Signed header (built on {@link buildWeb3SignedHeader}),
7
- * and — on `402 Payment Required` — settles the grant's data-access fee through
8
- * the DPv2 escrow gateway and retries once.
7
+ * and — on `402 Payment Required` — signs the challenged escrow operation and
8
+ * retries once.
9
9
  *
10
- * The 402 body is parsed into a {@link PersonalServerPaymentRequired} (grant id,
11
- * asset, and amount owed), which drives the escrow settlement.
10
+ * The 402 body is parsed into a validated grant or receipt-bound data-access
11
+ * operation, which drives the escrow settlement.
12
12
  *
13
13
  * @category Direct
14
14
  * @module direct/personal-server-read
15
15
  */
16
- import type { Web3SignedSignFn } from "../auth/web3-signed-builder";
17
- import { type EscrowPaymentConfig } from "./escrow-payment";
18
- import type { DirectPaymentReceipt, PersonalServerPaymentRequired } from "./types";
16
+ import type { Web3SignedSignFn } from "../auth/web3-signed-builder.js";
17
+ import { type EscrowPaymentHeaderConfig } from "./escrow-payment.js";
18
+ import type { DirectPaymentResponseMetadata, PersonalServerPaymentOperation } from "./types.js";
19
19
  /** Minimal `Response`-like shape so the read loop is testable without a DOM. */
20
20
  export interface FetchResponseLike {
21
21
  ok: boolean;
@@ -60,12 +60,15 @@ export interface PersonalServerTransportRetryOptions {
60
60
  /** Cap on the between-retry delay (ms, default 5_000). */
61
61
  maxDelayMs?: number;
62
62
  }
63
- /** Outcome of {@link readPersonalServerData}: the payload plus optional receipt. */
63
+ /** Outcome of {@link readPersonalServerData}. */
64
64
  export interface PersonalServerReadResult {
65
65
  /** The decoded JSON payload returned by the Personal Server. */
66
66
  data: unknown;
67
- /** Present only when this read required (and settled) a payment. */
68
- payment?: DirectPaymentReceipt;
67
+ /**
68
+ * Shape-validated but unauthenticated payment metadata echoed by the
69
+ * Personal Server. Never treat this field as accounting proof.
70
+ */
71
+ payment?: DirectPaymentResponseMetadata;
69
72
  }
70
73
  /** Compute the data path for a scope (`/v1/data/{scope}`). */
71
74
  export declare function dataPathForScope(scope: string): string;
@@ -86,25 +89,32 @@ export declare function buildPersonalServerDataReadRequest(params: {
86
89
  signMessage: Web3SignedSignFn;
87
90
  }): Promise<PersonalServerDataReadRequest>;
88
91
  /**
89
- * Parse a `402 Payment Required` body into a {@link PersonalServerPaymentRequired}.
92
+ * Parse a `402 Payment Required` body into a validated payment operation.
90
93
  *
91
94
  * @remarks
92
95
  * Accepts a few field spellings and falls back to the read's own grantId and the
93
- * native asset when a field is absent.
96
+ * native asset when a field is absent from a legacy grant challenge.
97
+ *
98
+ * Receipt-bound `data_access` uses a fail-closed canonical path: one compatible
99
+ * `accepts` entry must contain the scheme, network, message, and complete
100
+ * receipt. Its positive uint256 `paymentNonce` is mandatory because the
101
+ * Personal Server encodes challenge freshness in that nonce and checks it on
102
+ * retry. This parser shape-validates the receipt and binds `opId` to `recordId`;
103
+ * it does not recover or verify the server signature.
94
104
  *
95
105
  * @param res - The 402 response.
96
- * @param grantId - The grant id of the read (default `opId`).
106
+ * @param grantId - The grant id of the read (default legacy grant `opId`).
97
107
  * @returns The parsed payment requirement.
98
108
  */
99
- export declare function parsePersonalServerPaymentRequired(res: FetchResponseLike, grantId: string): Promise<PersonalServerPaymentRequired>;
109
+ export declare function parsePersonalServerPaymentRequired(res: FetchResponseLike, grantId: string): Promise<PersonalServerPaymentOperation>;
100
110
  /**
101
111
  * Read approved data from a Personal Server, settling a 402 via escrow.
102
112
  *
103
113
  * @remarks
104
114
  * Sends a Web3Signed-authenticated `GET /v1/data/{scope}`. On `402`, parses what
105
- * is owed, authorizes an escrow payment for the grant via `escrow`, and retries
106
- * once. If escrow is not configured, throws {@link PaymentRequiredError} carrying
107
- * the parsed requirement so callers can debug amount/asset.
115
+ * is owed, authorizes the challenged escrow operation, and retries once. If
116
+ * escrow is not configured, throws {@link PaymentRequiredError} carrying the
117
+ * parsed requirement so callers can debug amount/asset.
108
118
  *
109
119
  * Transport failures (fetch throwing — the browser-PS relay reconnect window)
110
120
  * are retried with backoff per `transportRetry` (default 3 attempts). The paid
@@ -120,7 +130,7 @@ export declare function readPersonalServerData(params: {
120
130
  grantId: string;
121
131
  payerAddress: `0x${string}`;
122
132
  signMessage: Web3SignedSignFn;
123
- escrow?: EscrowPaymentConfig;
133
+ escrow?: EscrowPaymentHeaderConfig;
124
134
  fetchFn?: PersonalServerFetch;
125
135
  transportRetry?: PersonalServerTransportRetryOptions;
126
136
  }): Promise<PersonalServerReadResult>;
@@ -3,9 +3,10 @@ import {
3
3
  NATIVE_ASSET_ADDRESS
4
4
  } from "../protocol/escrow.js";
5
5
  import {
6
- buildGrantPaymentHeader,
6
+ buildEscrowPaymentHeader,
7
+ DATA_ACCESS_OP_TYPE,
7
8
  GRANT_OP_TYPE,
8
- paymentReceiptFromHeader
9
+ paymentResponseMetadataFromHeader
9
10
  } from "./escrow-payment.js";
10
11
  import { PaymentRequiredError, PersonalServerReadError } from "./errors.js";
11
12
  const TRANSPORT_RETRY_DEFAULTS = {
@@ -78,31 +79,102 @@ function parseAccessRecord(value) {
78
79
  function isBytes32Hex(value) {
79
80
  return /^0x[0-9a-fA-F]{64}$/.test(value);
80
81
  }
81
- function assertChallengeMatchesGrant(params) {
82
+ const UINT256_MAX = (1n << 256n) - 1n;
83
+ function isAddressHex(value) {
84
+ return /^0x[0-9a-fA-F]{40}$/.test(value);
85
+ }
86
+ function isUint256Decimal(value, allowZero) {
87
+ const pattern = allowZero ? /^(0|[1-9]\d*)$/ : /^[1-9]\d*$/;
88
+ return value.length <= UINT256_MAX.toString().length && pattern.test(value) && BigInt(value) <= UINT256_MAX;
89
+ }
90
+ function isValidDataAccessRecord(record) {
91
+ return isBytes32Hex(record.dataPointId) && isUint256Decimal(record.version, false) && isAddressHex(record.accessor) && isBytes32Hex(record.recordId) && /^0x[0-9a-fA-F]{130}$/.test(record.signature);
92
+ }
93
+ function parseLegacyGrantOperation(params) {
82
94
  const { challengeGrantId, challengeOpId, challengeOpType, grantId } = params;
83
- if (challengeOpType && challengeOpType !== GRANT_OP_TYPE) {
95
+ const opType = challengeOpType ?? GRANT_OP_TYPE;
96
+ if (opType !== GRANT_OP_TYPE) {
84
97
  throw new PersonalServerReadError(
85
98
  "Personal Server payment challenge used an unsupported escrow op type",
86
99
  402,
87
100
  { opType: challengeOpType }
88
101
  );
89
102
  }
90
- const challengedGrantId = challengeOpId ?? challengeGrantId;
91
- if (!challengedGrantId) return;
92
- if (!isBytes32Hex(challengedGrantId)) {
103
+ const opId = challengeOpId ?? challengeGrantId ?? grantId;
104
+ if (!opId || !isBytes32Hex(opId)) {
93
105
  throw new PersonalServerReadError(
94
106
  "Personal Server payment challenge used an invalid escrow op id",
95
107
  402,
96
- { opId: challengedGrantId }
108
+ { opId }
97
109
  );
98
110
  }
99
- if (challengedGrantId.toLowerCase() !== grantId.toLowerCase()) {
111
+ if (opId.toLowerCase() !== grantId.toLowerCase()) {
100
112
  throw new PersonalServerReadError(
101
113
  "Personal Server payment challenge did not match the requested grant",
102
114
  402,
103
- { opId: challengedGrantId, grantId }
115
+ { opId, grantId }
104
116
  );
105
117
  }
118
+ return { opType: GRANT_OP_TYPE, opId };
119
+ }
120
+ function hasDataAccessMarker(body) {
121
+ if (stringField(body, "opType") === DATA_ACCESS_OP_TYPE) return true;
122
+ return Array.isArray(body.accepts) && body.accepts.some((value) => {
123
+ const accept = asRecord(value);
124
+ return stringField(asRecord(accept?.message), "opType") === DATA_ACCESS_OP_TYPE;
125
+ });
126
+ }
127
+ function parseCanonicalDataAccessAccept(value) {
128
+ const accept = asRecord(value);
129
+ const message = asRecord(accept?.message);
130
+ if (stringField(accept, "scheme") !== "vana-escrow-grant" || stringField(message, "opType") !== DATA_ACCESS_OP_TYPE) {
131
+ return null;
132
+ }
133
+ const network = stringField(accept, "network");
134
+ const payerAddress = stringField(message, "payerAddress");
135
+ const opId = stringField(message, "opId");
136
+ const asset = stringField(message, "asset");
137
+ const amount = stringField(message, "amount");
138
+ const paymentNonce = stringField(message, "paymentNonce");
139
+ const accessRecord = parseAccessRecord(accept?.accessRecord);
140
+ const acceptAsset = stringField(accept, "asset");
141
+ const acceptAmount = stringField(accept, "amount");
142
+ if (!network || !payerAddress || !isAddressHex(payerAddress) || !opId || !isBytes32Hex(opId) || !asset || !isAddressHex(asset) || !amount || !isUint256Decimal(amount, true) || !paymentNonce || !isUint256Decimal(paymentNonce, false) || !accessRecord || !isValidDataAccessRecord(accessRecord) || payerAddress.toLowerCase() !== accessRecord.accessor.toLowerCase() || opId.toLowerCase() !== accessRecord.recordId.toLowerCase() || !acceptAsset || acceptAsset.toLowerCase() !== asset.toLowerCase() || acceptAmount !== amount) {
143
+ return null;
144
+ }
145
+ return {
146
+ opType: DATA_ACCESS_OP_TYPE,
147
+ opId,
148
+ network,
149
+ paymentNonce,
150
+ accessRecord,
151
+ asset,
152
+ amount
153
+ };
154
+ }
155
+ function parseCanonicalDataAccessPayment(params) {
156
+ const { body, grantId, raw } = params;
157
+ if (body.x402Version !== 1 || body.error !== "PAYMENT_REQUIRED") {
158
+ throw new PersonalServerReadError(
159
+ "Personal Server data-access payment response was not a canonical x402 challenge",
160
+ 402
161
+ );
162
+ }
163
+ const operation = Array.isArray(body.accepts) ? body.accepts.map(parseCanonicalDataAccessAccept).find((candidate) => candidate !== null) : void 0;
164
+ if (!operation) {
165
+ throw new PersonalServerReadError(
166
+ "Personal Server data-access payment challenge was untrusted or incomplete",
167
+ 402
168
+ );
169
+ }
170
+ return { grantId, raw, ...operation };
171
+ }
172
+ function preferredLegacyAccept(body) {
173
+ if (!Array.isArray(body.accepts)) return void 0;
174
+ return body.accepts.map(asRecord).find((accept) => {
175
+ const message = asRecord(accept?.message);
176
+ return stringField(accept, "scheme") === "vana-escrow-grant" && stringField(message, "opType") === GRANT_OP_TYPE;
177
+ });
106
178
  }
107
179
  async function buildPersonalServerDataReadRequest(params) {
108
180
  const base = stripTrailingSlash(params.personalServerUrl);
@@ -128,32 +200,92 @@ async function parsePersonalServerPaymentRequired(res, grantId) {
128
200
  raw = void 0;
129
201
  }
130
202
  const body = asRecord(raw) ?? {};
131
- const accept = Array.isArray(body.accepts) && body.accepts.length > 0 ? asRecord(body.accepts[0]) : void 0;
132
- const message = asRecord(accept?.message);
203
+ if (hasDataAccessMarker(body)) {
204
+ return parseCanonicalDataAccessPayment({ body, grantId, raw });
205
+ }
206
+ const hasAcceptsEnvelope = Object.prototype.hasOwnProperty.call(
207
+ body,
208
+ "accepts"
209
+ );
210
+ if (hasAcceptsEnvelope && (body.x402Version !== 1 || body.error !== "PAYMENT_REQUIRED")) {
211
+ throw new PersonalServerReadError(
212
+ "Personal Server grant payment response was not a canonical x402 challenge",
213
+ 402
214
+ );
215
+ }
216
+ const accept = preferredLegacyAccept(body);
217
+ if (hasAcceptsEnvelope && !accept) {
218
+ throw new PersonalServerReadError(
219
+ "Personal Server grant payment challenge had no compatible escrow offer",
220
+ 402
221
+ );
222
+ }
223
+ if (accept) {
224
+ const message = asRecord(accept.message);
225
+ const network = stringField(accept, "network");
226
+ const payerAddress = stringField(message, "payerAddress");
227
+ const opId = stringField(message, "opId");
228
+ const asset = stringField(message, "asset");
229
+ const amount = stringField(message, "amount");
230
+ const paymentNonce = stringField(message, "paymentNonce");
231
+ const acceptAsset = stringField(accept, "asset");
232
+ const acceptAmount = stringField(accept, "amount");
233
+ const accessRecord2 = parseAccessRecord(accept.accessRecord);
234
+ const hasAccessRecord = accept.accessRecord !== void 0;
235
+ if (!network || !payerAddress || !isAddressHex(payerAddress) || !opId || !isBytes32Hex(opId) || !asset || !isAddressHex(asset) || !amount || !isUint256Decimal(amount, true) || !paymentNonce || !isUint256Decimal(paymentNonce, false) || !acceptAsset || acceptAsset.toLowerCase() !== asset.toLowerCase() || acceptAmount !== amount || hasAccessRecord && (!accessRecord2 || !isValidDataAccessRecord(accessRecord2) || accessRecord2.accessor.toLowerCase() !== payerAddress.toLowerCase()) || amount === "0" && !accessRecord2) {
236
+ throw new PersonalServerReadError(
237
+ "Personal Server grant payment challenge was untrusted or incomplete",
238
+ 402
239
+ );
240
+ }
241
+ const operation2 = parseLegacyGrantOperation({
242
+ challengeOpId: opId,
243
+ challengeOpType: stringField(message, "opType"),
244
+ grantId
245
+ });
246
+ return {
247
+ grantId,
248
+ ...operation2,
249
+ network,
250
+ paymentNonce,
251
+ asset,
252
+ amount,
253
+ raw,
254
+ ...accessRecord2 ? { accessRecord: accessRecord2 } : {}
255
+ };
256
+ }
133
257
  const challengeGrantId = stringField(body, "grantId");
134
- const challengeOpId = stringField(message, "opId") ?? stringField(body, "opId");
135
- const challengeOpType = stringField(message, "opType") ?? stringField(body, "opType");
136
- assertChallengeMatchesGrant({
258
+ const challengeOpId = stringField(body, "opId");
259
+ const challengeOpType = stringField(body, "opType");
260
+ const accessRecord = parseAccessRecord(body.accessRecord);
261
+ const operation = parseLegacyGrantOperation({
137
262
  challengeGrantId,
138
263
  challengeOpId,
139
264
  challengeOpType,
140
265
  grantId
141
266
  });
142
- const amountValue = stringField(message, "amount") ?? stringField(body, "amount") ?? stringField(body, "maxAmountRequired") ?? "0";
143
- return {
267
+ const amountValue = stringField(body, "amount") ?? stringField(body, "maxAmountRequired") ?? "0";
268
+ const payment = {
144
269
  grantId,
145
- network: stringField(accept, "network") ?? stringField(body, "network"),
146
- paymentNonce: stringField(message, "paymentNonce") ?? stringField(body, "paymentNonce"),
147
- accessRecord: parseAccessRecord(accept?.accessRecord ?? body.accessRecord),
148
- asset: stringField(message, "asset") ?? stringField(body, "asset") ?? NATIVE_ASSET_ADDRESS,
270
+ network: stringField(body, "network"),
271
+ paymentNonce: stringField(body, "paymentNonce"),
272
+ asset: stringField(body, "asset") ?? NATIVE_ASSET_ADDRESS,
149
273
  amount: amountValue,
150
274
  raw
151
275
  };
276
+ return {
277
+ ...payment,
278
+ ...operation,
279
+ ...accessRecord ? { accessRecord } : {}
280
+ };
152
281
  }
153
282
  function hasPositiveAmount(amount) {
154
283
  if (!/^\d+$/.test(amount)) return false;
155
284
  return BigInt(amount) > 0n;
156
285
  }
286
+ function isReceiptOnlyGrantAcknowledgment(required, payerAddress) {
287
+ return required.opType === GRANT_OP_TYPE && required.amount === "0" && required.accessRecord !== void 0 && isValidDataAccessRecord(required.accessRecord) && required.accessRecord.accessor.toLowerCase() === payerAddress.toLowerCase();
288
+ }
157
289
  async function readPersonalServerData(params) {
158
290
  const fetchFn = params.fetchFn ?? globalThis.fetch;
159
291
  if (!fetchFn) {
@@ -193,9 +325,9 @@ async function readPersonalServerData(params) {
193
325
  }
194
326
  );
195
327
  }
196
- if (!hasPositiveAmount(required.amount)) {
328
+ if (required.opType === GRANT_OP_TYPE && !hasPositiveAmount(required.amount) && !isReceiptOnlyGrantAcknowledgment(required, params.payerAddress)) {
197
329
  throw new PaymentRequiredError(
198
- "Personal Server payment challenge did not include a positive amount",
330
+ "Personal Server payment challenge included neither a positive amount nor a valid access receipt",
199
331
  {
200
332
  scope: params.scope,
201
333
  grantId: required.grantId,
@@ -204,7 +336,7 @@ async function readPersonalServerData(params) {
204
336
  }
205
337
  );
206
338
  }
207
- const paymentHeader = await buildGrantPaymentHeader({
339
+ const paymentHeader = await buildEscrowPaymentHeader({
208
340
  payerAddress: params.payerAddress,
209
341
  required,
210
342
  config: params.escrow
@@ -241,7 +373,9 @@ async function readPersonalServerData(params) {
241
373
  { scope: params.scope, body: detail.slice(0, 500) }
242
374
  );
243
375
  }
244
- payment = paymentReceiptFromHeader(res.headers.get("X-PAYMENT-RESPONSE"));
376
+ payment = paymentResponseMetadataFromHeader(
377
+ res.headers.get("X-PAYMENT-RESPONSE")
378
+ );
245
379
  return { data: await res.json(), payment };
246
380
  }
247
381
  export {
@@ -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/**\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
+ {"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` — signs the challenged escrow operation and\n * retries once.\n *\n * The 402 body is parsed into a validated grant or receipt-bound data-access\n * operation, 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 buildEscrowPaymentHeader,\n DATA_ACCESS_OP_TYPE,\n GRANT_OP_TYPE,\n paymentResponseMetadataFromHeader,\n type EscrowPaymentHeaderConfig,\n} from \"./escrow-payment\";\nimport { PaymentRequiredError, PersonalServerReadError } from \"./errors\";\nimport type {\n DirectPaymentResponseMetadata,\n PersonalServerDataAccessPaymentOperation,\n PersonalServerPaymentOperation,\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}. */\nexport interface PersonalServerReadResult {\n /** The decoded JSON payload returned by the Personal Server. */\n data: unknown;\n /**\n * Shape-validated but unauthenticated payment metadata echoed by the\n * Personal Server. Never treat this field as accounting proof.\n */\n payment?: DirectPaymentResponseMetadata;\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\nconst UINT256_MAX = (1n << 256n) - 1n;\n\nfunction isAddressHex(value: string): boolean {\n return /^0x[0-9a-fA-F]{40}$/.test(value);\n}\n\nfunction isUint256Decimal(value: string, allowZero: boolean): boolean {\n const pattern = allowZero ? /^(0|[1-9]\\d*)$/ : /^[1-9]\\d*$/;\n return (\n value.length <= UINT256_MAX.toString().length &&\n pattern.test(value) &&\n BigInt(value) <= UINT256_MAX\n );\n}\n\nfunction isValidDataAccessRecord(record: EscrowAccessRecord): boolean {\n return (\n isBytes32Hex(record.dataPointId) &&\n isUint256Decimal(record.version, false) &&\n isAddressHex(record.accessor) &&\n isBytes32Hex(record.recordId) &&\n /^0x[0-9a-fA-F]{130}$/.test(record.signature)\n );\n}\n\nfunction parseLegacyGrantOperation(params: {\n challengeGrantId?: string;\n challengeOpId?: string;\n challengeOpType?: string;\n grantId: string;\n}): { opType: typeof GRANT_OP_TYPE; opId: string } {\n const { challengeGrantId, challengeOpId, challengeOpType, grantId } = params;\n\n const opType = challengeOpType ?? GRANT_OP_TYPE;\n if (opType !== 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 opId = challengeOpId ?? challengeGrantId ?? grantId;\n\n if (!opId || !isBytes32Hex(opId)) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge used an invalid escrow op id\",\n 402,\n { opId },\n );\n }\n\n if (opId.toLowerCase() !== grantId.toLowerCase()) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge did not match the requested grant\",\n 402,\n { opId, grantId },\n );\n }\n\n return { opType: GRANT_OP_TYPE, opId };\n}\n\nfunction hasDataAccessMarker(body: Record<string, unknown>): boolean {\n if (stringField(body, \"opType\") === DATA_ACCESS_OP_TYPE) return true;\n return (\n Array.isArray(body.accepts) &&\n body.accepts.some((value) => {\n const accept = asRecord(value);\n return (\n stringField(asRecord(accept?.message), \"opType\") === DATA_ACCESS_OP_TYPE\n );\n })\n );\n}\n\nfunction parseCanonicalDataAccessAccept(\n value: unknown,\n): Omit<PersonalServerDataAccessPaymentOperation, \"grantId\" | \"raw\"> | null {\n const accept = asRecord(value);\n const message = asRecord(accept?.message);\n if (\n stringField(accept, \"scheme\") !== \"vana-escrow-grant\" ||\n stringField(message, \"opType\") !== DATA_ACCESS_OP_TYPE\n ) {\n return null;\n }\n\n const network = stringField(accept, \"network\");\n const payerAddress = stringField(message, \"payerAddress\");\n const opId = stringField(message, \"opId\");\n const asset = stringField(message, \"asset\");\n const amount = stringField(message, \"amount\");\n const paymentNonce = stringField(message, \"paymentNonce\");\n const accessRecord = parseAccessRecord(accept?.accessRecord);\n const acceptAsset = stringField(accept, \"asset\");\n const acceptAmount = stringField(accept, \"amount\");\n\n if (\n !network ||\n !payerAddress ||\n !isAddressHex(payerAddress) ||\n !opId ||\n !isBytes32Hex(opId) ||\n !asset ||\n !isAddressHex(asset) ||\n !amount ||\n !isUint256Decimal(amount, true) ||\n !paymentNonce ||\n !isUint256Decimal(paymentNonce, false) ||\n !accessRecord ||\n !isValidDataAccessRecord(accessRecord) ||\n payerAddress.toLowerCase() !== accessRecord.accessor.toLowerCase() ||\n opId.toLowerCase() !== accessRecord.recordId.toLowerCase() ||\n !acceptAsset ||\n acceptAsset.toLowerCase() !== asset.toLowerCase() ||\n acceptAmount !== amount\n ) {\n return null;\n }\n\n return {\n opType: DATA_ACCESS_OP_TYPE,\n opId,\n network,\n paymentNonce,\n accessRecord,\n asset,\n amount,\n };\n}\n\nfunction parseCanonicalDataAccessPayment(params: {\n body: Record<string, unknown>;\n grantId: string;\n raw: unknown;\n}): PersonalServerDataAccessPaymentOperation {\n const { body, grantId, raw } = params;\n if (body.x402Version !== 1 || body.error !== \"PAYMENT_REQUIRED\") {\n throw new PersonalServerReadError(\n \"Personal Server data-access payment response was not a canonical x402 challenge\",\n 402,\n );\n }\n\n const operation = Array.isArray(body.accepts)\n ? body.accepts\n .map(parseCanonicalDataAccessAccept)\n .find((candidate) => candidate !== null)\n : undefined;\n if (!operation) {\n throw new PersonalServerReadError(\n \"Personal Server data-access payment challenge was untrusted or incomplete\",\n 402,\n );\n }\n\n return { grantId, raw, ...operation };\n}\n\nfunction preferredLegacyAccept(body: Record<string, unknown>) {\n if (!Array.isArray(body.accepts)) return undefined;\n return body.accepts.map(asRecord).find((accept) => {\n const message = asRecord(accept?.message);\n return (\n stringField(accept, \"scheme\") === \"vana-escrow-grant\" &&\n stringField(message, \"opType\") === GRANT_OP_TYPE\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 validated payment operation.\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 from a legacy grant challenge.\n *\n * Receipt-bound `data_access` uses a fail-closed canonical path: one compatible\n * `accepts` entry must contain the scheme, network, message, and complete\n * receipt. Its positive uint256 `paymentNonce` is mandatory because the\n * Personal Server encodes challenge freshness in that nonce and checks it on\n * retry. This parser shape-validates the receipt and binds `opId` to `recordId`;\n * it does not recover or verify the server signature.\n *\n * @param res - The 402 response.\n * @param grantId - The grant id of the read (default legacy grant `opId`).\n * @returns The parsed payment requirement.\n */\nexport async function parsePersonalServerPaymentRequired(\n res: FetchResponseLike,\n grantId: string,\n): Promise<PersonalServerPaymentOperation> {\n let raw: unknown = undefined;\n try {\n raw = await res.json();\n } catch {\n raw = undefined;\n }\n const body = asRecord(raw) ?? {};\n if (hasDataAccessMarker(body)) {\n return parseCanonicalDataAccessPayment({ body, grantId, raw });\n }\n\n // Legacy grant challenges predate the canonical x402 envelope. Retain their\n // field fallbacks only for truly flat bodies. Once an accepts envelope is\n // present, validate its canonical framing and select only a compatible offer.\n const hasAcceptsEnvelope = Object.prototype.hasOwnProperty.call(\n body,\n \"accepts\",\n );\n if (\n hasAcceptsEnvelope &&\n (body.x402Version !== 1 || body.error !== \"PAYMENT_REQUIRED\")\n ) {\n throw new PersonalServerReadError(\n \"Personal Server grant payment response was not a canonical x402 challenge\",\n 402,\n );\n }\n const accept = preferredLegacyAccept(body);\n if (hasAcceptsEnvelope && !accept) {\n throw new PersonalServerReadError(\n \"Personal Server grant payment challenge had no compatible escrow offer\",\n 402,\n );\n }\n\n if (accept) {\n const message = asRecord(accept.message);\n const network = stringField(accept, \"network\");\n const payerAddress = stringField(message, \"payerAddress\");\n const opId = stringField(message, \"opId\");\n const asset = stringField(message, \"asset\");\n const amount = stringField(message, \"amount\");\n const paymentNonce = stringField(message, \"paymentNonce\");\n const acceptAsset = stringField(accept, \"asset\");\n const acceptAmount = stringField(accept, \"amount\");\n const accessRecord = parseAccessRecord(accept.accessRecord);\n const hasAccessRecord = accept.accessRecord !== undefined;\n if (\n !network ||\n !payerAddress ||\n !isAddressHex(payerAddress) ||\n !opId ||\n !isBytes32Hex(opId) ||\n !asset ||\n !isAddressHex(asset) ||\n !amount ||\n !isUint256Decimal(amount, true) ||\n !paymentNonce ||\n !isUint256Decimal(paymentNonce, false) ||\n !acceptAsset ||\n acceptAsset.toLowerCase() !== asset.toLowerCase() ||\n acceptAmount !== amount ||\n (hasAccessRecord &&\n (!accessRecord ||\n !isValidDataAccessRecord(accessRecord) ||\n accessRecord.accessor.toLowerCase() !==\n payerAddress.toLowerCase())) ||\n (amount === \"0\" && !accessRecord)\n ) {\n throw new PersonalServerReadError(\n \"Personal Server grant payment challenge was untrusted or incomplete\",\n 402,\n );\n }\n const operation = parseLegacyGrantOperation({\n challengeOpId: opId,\n challengeOpType: stringField(message, \"opType\"),\n grantId,\n });\n return {\n grantId,\n ...operation,\n network,\n paymentNonce,\n asset,\n amount,\n raw,\n ...(accessRecord ? { accessRecord } : {}),\n };\n }\n\n const challengeGrantId = stringField(body, \"grantId\");\n const challengeOpId = stringField(body, \"opId\");\n const challengeOpType = stringField(body, \"opType\");\n\n const accessRecord = parseAccessRecord(body.accessRecord);\n const operation = parseLegacyGrantOperation({\n challengeGrantId,\n challengeOpId,\n challengeOpType,\n grantId,\n });\n\n const amountValue =\n stringField(body, \"amount\") ??\n stringField(body, \"maxAmountRequired\") ??\n \"0\";\n const payment = {\n grantId,\n network: stringField(body, \"network\"),\n paymentNonce: stringField(body, \"paymentNonce\"),\n asset: stringField(body, \"asset\") ?? NATIVE_ASSET_ADDRESS,\n amount: amountValue,\n raw,\n };\n return {\n ...payment,\n ...operation,\n ...(accessRecord ? { accessRecord } : {}),\n };\n}\n\nfunction hasPositiveAmount(amount: string): boolean {\n if (!/^\\d+$/.test(amount)) return false;\n return BigInt(amount) > 0n;\n}\n\nfunction isReceiptOnlyGrantAcknowledgment(\n required: PersonalServerPaymentOperation,\n payerAddress: string,\n): boolean {\n return (\n required.opType === GRANT_OP_TYPE &&\n required.amount === \"0\" &&\n required.accessRecord !== undefined &&\n isValidDataAccessRecord(required.accessRecord) &&\n required.accessRecord.accessor.toLowerCase() === payerAddress.toLowerCase()\n );\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 the challenged escrow operation, and retries once. If\n * escrow is not configured, throws {@link PaymentRequiredError} carrying the\n * 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?: EscrowPaymentHeaderConfig;\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: DirectPaymentResponseMetadata | 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 (\n required.opType === GRANT_OP_TYPE &&\n !hasPositiveAmount(required.amount) &&\n !isReceiptOnlyGrantAcknowledgment(required, params.payerAddress)\n ) {\n throw new PaymentRequiredError(\n \"Personal Server payment challenge included neither a positive amount nor a valid access receipt\",\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 buildEscrowPaymentHeader({\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 = paymentResponseMetadataFromHeader(\n res.headers.get(\"X-PAYMENT-RESPONSE\"),\n );\n return { data: await res.json(), payment };\n}\n"],"mappings":"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","names":["accessRecord","operation"]}