@opendatalabs/vana-sdk 3.10.1 → 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/direct/personal-server-read.ts"],"sourcesContent":["/**\n * Personal Server data-read request builder and the 402 -> escrow-pay -> retry loop.\n *\n * @remarks\n * The read targets the Personal Server data path (`/v1/data/{scope}`),\n * authenticates with a Web3Signed header (built on {@link buildWeb3SignedHeader}),\n * and — on `402 Payment Required` — settles the grant's data-access fee through\n * the DPv2 escrow gateway and retries once.\n *\n * The 402 body is parsed into a {@link PersonalServerPaymentRequired} (grant id,\n * asset, and amount owed), which drives the escrow settlement.\n *\n * @category Direct\n * @module direct/personal-server-read\n */\n\nimport { buildWeb3SignedHeader } from \"../auth/web3-signed-builder\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport {\n NATIVE_ASSET_ADDRESS,\n type EscrowAccessRecord,\n} from \"../protocol/escrow\";\nimport {\n buildGrantPaymentHeader,\n GRANT_OP_TYPE,\n paymentReceiptFromHeader,\n type EscrowPaymentConfig,\n} from \"./escrow-payment\";\nimport { PaymentRequiredError, PersonalServerReadError } from \"./errors\";\nimport type {\n DirectPaymentReceipt,\n PersonalServerPaymentRequired,\n} from \"./types\";\n\n/** Minimal `Response`-like shape so the read loop is testable without a DOM. */\nexport interface FetchResponseLike {\n ok: boolean;\n status: number;\n statusText: string;\n headers: { get(name: string): string | null };\n json(): Promise<unknown>;\n text(): Promise<string>;\n}\n\n/** Minimal `fetch` signature accepted by {@link readPersonalServerData}. */\nexport type PersonalServerFetch = (\n input: string,\n init: {\n method: string;\n headers: Record<string, string>;\n },\n) => Promise<FetchResponseLike>;\n\n/** A built, ready-to-send Personal Server data read request. */\nexport interface PersonalServerDataReadRequest {\n /** Absolute URL of the read endpoint. */\n url: string;\n /** HTTP method (always `\"GET\"`). */\n method: \"GET\";\n /** Request path used in the Web3Signed `uri` claim (e.g. `/v1/data/{scope}`). */\n path: string;\n /** Headers including the Web3Signed `Authorization` value. */\n headers: Record<string, string>;\n}\n\n/** Outcome of {@link readPersonalServerData}: the payload plus optional receipt. */\nexport interface PersonalServerReadResult {\n /** The decoded JSON payload returned by the Personal Server. */\n data: unknown;\n /** Present only when this read required (and settled) a payment. */\n payment?: DirectPaymentReceipt;\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\n/** Compute the data path for a scope (`/v1/data/{scope}`). */\nexport function dataPathForScope(scope: string): string {\n return `/v1/data/${encodeURIComponent(scope)}`;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction stringField(\n record: Record<string, unknown> | undefined,\n field: string,\n): string | undefined {\n const value = record?.[field];\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction parseAccessRecord(value: unknown): EscrowAccessRecord | undefined {\n const record = asRecord(value);\n const dataPointId = stringField(record, \"dataPointId\");\n const version = stringField(record, \"version\");\n const accessor = stringField(record, \"accessor\");\n const recordId = stringField(record, \"recordId\");\n const signature = stringField(record, \"signature\");\n\n if (!dataPointId || !version || !accessor || !recordId || !signature) {\n return undefined;\n }\n\n return {\n dataPointId: dataPointId as `0x${string}`,\n version,\n accessor: accessor as `0x${string}`,\n recordId: recordId as `0x${string}`,\n signature: signature as `0x${string}`,\n };\n}\n\nfunction isBytes32Hex(value: string): boolean {\n return /^0x[0-9a-fA-F]{64}$/.test(value);\n}\n\nfunction assertChallengeMatchesGrant(params: {\n challengeGrantId?: string;\n challengeOpId?: string;\n challengeOpType?: string;\n grantId: string;\n}): void {\n const { challengeGrantId, challengeOpId, challengeOpType, grantId } = params;\n\n if (challengeOpType && challengeOpType !== GRANT_OP_TYPE) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge used an unsupported escrow op type\",\n 402,\n { opType: challengeOpType },\n );\n }\n\n const challengedGrantId = challengeOpId ?? challengeGrantId;\n if (!challengedGrantId) return;\n\n if (!isBytes32Hex(challengedGrantId)) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge used an invalid escrow op id\",\n 402,\n { opId: challengedGrantId },\n );\n }\n\n if (challengedGrantId.toLowerCase() !== grantId.toLowerCase()) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge did not match the requested grant\",\n 402,\n { opId: challengedGrantId, grantId },\n );\n }\n}\n\n/**\n * Build a Web3Signed-authenticated Personal Server data read request.\n *\n * @param params - Personal Server URL, scope, grant id, and an EIP-191 signer.\n * @returns The request URL, method, path, and headers (including `Authorization`).\n */\nexport async function buildPersonalServerDataReadRequest(params: {\n /** Base URL of the user's Personal Server. */\n personalServerUrl: string;\n /** Scope to read (e.g. `\"icloud_notes.notes\"`). */\n scope: string;\n /** Grant id authorizing the read. */\n grantId: string;\n /** EIP-191 signer for the Web3Signed header (the app key). */\n signMessage: Web3SignedSignFn;\n}): Promise<PersonalServerDataReadRequest> {\n const base = stripTrailingSlash(params.personalServerUrl);\n const path = dataPathForScope(params.scope);\n const authorization = await buildWeb3SignedHeader({\n signMessage: params.signMessage,\n aud: base,\n method: \"GET\",\n uri: path,\n grantId: params.grantId,\n });\n const headers: Record<string, string> = {\n Authorization: authorization,\n Accept: \"application/json\",\n };\n return { url: `${base}${path}`, method: \"GET\", path, headers };\n}\n\n/**\n * Parse a `402 Payment Required` body into a {@link PersonalServerPaymentRequired}.\n *\n * @remarks\n * Accepts a few field spellings and falls back to the read's own grantId and the\n * native asset when a field is absent.\n *\n * @param res - The 402 response.\n * @param grantId - The grant id of the read (default `opId`).\n * @returns The parsed payment requirement.\n */\nexport async function parsePersonalServerPaymentRequired(\n res: FetchResponseLike,\n grantId: string,\n): Promise<PersonalServerPaymentRequired> {\n let raw: unknown = undefined;\n try {\n raw = await res.json();\n } catch {\n raw = undefined;\n }\n const body = asRecord(raw) ?? {};\n const accept =\n Array.isArray(body.accepts) && body.accepts.length > 0\n ? asRecord(body.accepts[0])\n : undefined;\n const message = asRecord(accept?.message);\n const challengeGrantId = stringField(body, \"grantId\");\n const challengeOpId =\n stringField(message, \"opId\") ?? stringField(body, \"opId\");\n const challengeOpType =\n stringField(message, \"opType\") ?? stringField(body, \"opType\");\n\n assertChallengeMatchesGrant({\n challengeGrantId,\n challengeOpId,\n challengeOpType,\n grantId,\n });\n\n const amountValue =\n stringField(message, \"amount\") ??\n stringField(body, \"amount\") ??\n stringField(body, \"maxAmountRequired\") ??\n \"0\";\n return {\n grantId,\n network: stringField(accept, \"network\") ?? stringField(body, \"network\"),\n paymentNonce:\n stringField(message, \"paymentNonce\") ?? stringField(body, \"paymentNonce\"),\n accessRecord: parseAccessRecord(accept?.accessRecord ?? body.accessRecord),\n asset:\n stringField(message, \"asset\") ??\n stringField(body, \"asset\") ??\n NATIVE_ASSET_ADDRESS,\n amount: amountValue,\n raw,\n };\n}\n\nfunction hasPositiveAmount(amount: string): boolean {\n if (!/^\\d+$/.test(amount)) return false;\n return BigInt(amount) > 0n;\n}\n\n/**\n * Read approved data from a Personal Server, settling a 402 via escrow.\n *\n * @remarks\n * Sends a Web3Signed-authenticated `GET /v1/data/{scope}`. On `402`, parses what\n * is owed, authorizes an escrow payment for the grant via `escrow`, and retries\n * once. If escrow is not configured, throws {@link PaymentRequiredError} carrying\n * the parsed requirement so callers can debug amount/asset.\n *\n * @param params - Connection details, app signer, optional escrow config and fetch.\n * @returns `{ data, payment? }`.\n */\nexport async function readPersonalServerData(params: {\n personalServerUrl: string;\n scope: string;\n grantId: string;\n payerAddress: `0x${string}`;\n signMessage: Web3SignedSignFn;\n escrow?: EscrowPaymentConfig;\n fetchFn?: PersonalServerFetch;\n}): Promise<PersonalServerReadResult> {\n const fetchFn =\n params.fetchFn ?? (globalThis.fetch as unknown as PersonalServerFetch);\n if (!fetchFn) {\n throw new PersonalServerReadError(\n \"No fetch implementation available for Personal Server read\",\n );\n }\n\n const initial = await buildPersonalServerDataReadRequest({\n personalServerUrl: params.personalServerUrl,\n scope: params.scope,\n grantId: params.grantId,\n signMessage: params.signMessage,\n });\n\n let res = await fetchFn(initial.url, {\n method: initial.method,\n headers: initial.headers,\n });\n\n let payment: DirectPaymentReceipt | undefined;\n\n if (res.status === 402) {\n const required = await parsePersonalServerPaymentRequired(\n res,\n params.grantId,\n );\n if (!params.escrow) {\n throw new PaymentRequiredError(\n \"Personal Server requires payment but no escrow config is set\",\n {\n scope: params.scope,\n grantId: required.grantId,\n asset: required.asset,\n amount: required.amount,\n },\n );\n }\n\n if (!hasPositiveAmount(required.amount)) {\n throw new PaymentRequiredError(\n \"Personal Server payment challenge did not include a positive amount\",\n {\n scope: params.scope,\n grantId: required.grantId,\n asset: required.asset,\n amount: required.amount,\n },\n );\n }\n\n const paymentHeader = await buildGrantPaymentHeader({\n payerAddress: params.payerAddress,\n required,\n config: params.escrow,\n });\n\n // Re-sign and retry with x402 payment proof for the Personal Server to validate.\n const retry = await buildPersonalServerDataReadRequest({\n personalServerUrl: params.personalServerUrl,\n scope: params.scope,\n grantId: params.grantId,\n signMessage: params.signMessage,\n });\n res = await fetchFn(retry.url, {\n method: retry.method,\n headers: { ...retry.headers, \"X-PAYMENT\": paymentHeader },\n });\n\n if (res.status === 402) {\n throw new PaymentRequiredError(\n \"Personal Server still requires payment after escrow settlement\",\n {\n scope: params.scope,\n grantId: required.grantId,\n asset: required.asset,\n amount: required.amount,\n payment,\n },\n );\n }\n }\n\n if (!res.ok) {\n const detail = await res.text().catch(() => \"\");\n throw new PersonalServerReadError(\n `Personal Server read failed: ${res.status} ${res.statusText}`,\n res.status,\n { scope: params.scope, body: detail.slice(0, 500) },\n );\n }\n\n payment = paymentReceiptFromHeader(res.headers.get(\"X-PAYMENT-RESPONSE\"));\n return { data: await res.json(), payment };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/direct/personal-server-read.ts"],"sourcesContent":["/**\n * Personal Server data-read request builder and the 402 -> escrow-pay -> retry loop.\n *\n * @remarks\n * The read targets the Personal Server data path (`/v1/data/{scope}`),\n * authenticates with a Web3Signed header (built on {@link buildWeb3SignedHeader}),\n * and — on `402 Payment Required` — settles the grant's data-access fee through\n * the DPv2 escrow gateway and retries once.\n *\n * The 402 body is parsed into a {@link PersonalServerPaymentRequired} (grant id,\n * asset, and amount owed), which drives the escrow settlement.\n *\n * @category Direct\n * @module direct/personal-server-read\n */\n\nimport { buildWeb3SignedHeader } from \"../auth/web3-signed-builder\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport {\n NATIVE_ASSET_ADDRESS,\n type EscrowAccessRecord,\n} from \"../protocol/escrow\";\nimport {\n buildGrantPaymentHeader,\n GRANT_OP_TYPE,\n paymentReceiptFromHeader,\n type EscrowPaymentConfig,\n} from \"./escrow-payment\";\nimport { PaymentRequiredError, PersonalServerReadError } from \"./errors\";\nimport type {\n DirectPaymentReceipt,\n PersonalServerPaymentRequired,\n} from \"./types\";\n\n/** Minimal `Response`-like shape so the read loop is testable without a DOM. */\nexport interface FetchResponseLike {\n ok: boolean;\n status: number;\n statusText: string;\n headers: { get(name: string): string | null };\n json(): Promise<unknown>;\n text(): Promise<string>;\n}\n\n/** Minimal `fetch` signature accepted by {@link readPersonalServerData}. */\nexport type PersonalServerFetch = (\n input: string,\n init: {\n method: string;\n headers: Record<string, string>;\n },\n) => Promise<FetchResponseLike>;\n\n/** A built, ready-to-send Personal Server data read request. */\nexport interface PersonalServerDataReadRequest {\n /** Absolute URL of the read endpoint. */\n url: string;\n /** HTTP method (always `\"GET\"`). */\n method: \"GET\";\n /** Request path used in the Web3Signed `uri` claim (e.g. `/v1/data/{scope}`). */\n path: string;\n /** Headers including the Web3Signed `Authorization` value. */\n headers: Record<string, string>;\n}\n\n/**\n * Transport-level retry knobs for {@link readPersonalServerData}.\n *\n * @remarks\n * Applies only when the underlying `fetch` **throws** (connection reset, DNS,\n * socket died mid-handshake — the browser-PS relay drop window). A received\n * HTTP response is never retried here: 402 has its own payment loop and other\n * statuses are surfaced to the caller unchanged.\n */\nexport interface PersonalServerTransportRetryOptions {\n /** Total attempts including the first (default 3). `1` disables retries. */\n attempts?: number;\n /** Delay before the first retry (ms); doubles per retry (default 1_000). */\n initialDelayMs?: number;\n /** Cap on the between-retry delay (ms, default 5_000). */\n maxDelayMs?: number;\n}\n\nconst TRANSPORT_RETRY_DEFAULTS: Required<PersonalServerTransportRetryOptions> =\n {\n attempts: 3,\n initialDelayMs: 1_000,\n maxDelayMs: 5_000,\n };\n\n/** Clamp a caller-supplied attempt count to a finite integer >= 1. */\nfunction resolveAttempts(attempts: number): number {\n return Number.isFinite(attempts) ? Math.max(1, Math.floor(attempts)) : 1;\n}\n\n/** An aborted request is the caller's intent to stop — never retry it. */\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === \"AbortError\";\n}\n\nconst sleep = (ms: number) =>\n new Promise<void>((resolve) => setTimeout(resolve, ms));\n\n/**\n * Run a fetch with bounded transport retries.\n *\n * Each attempt rebuilds the request via `buildRequest` so the Web3Signed\n * header is freshly signed (local app-key signature — cheap), while letting\n * the caller pin anything that must NOT be regenerated across attempts\n * (an already-signed `X-PAYMENT` header keeps its paymentNonce, so a retry\n * can never mint a second escrow payment for the same read).\n */\nasync function fetchWithTransportRetry(\n fetchFn: PersonalServerFetch,\n buildRequest: () => Promise<{\n url: string;\n method: string;\n headers: Record<string, string>;\n }>,\n retry: Required<PersonalServerTransportRetryOptions>,\n): Promise<FetchResponseLike> {\n const attempts = resolveAttempts(retry.attempts);\n let lastError: unknown;\n for (let attempt = 0; attempt < attempts; attempt += 1) {\n if (attempt > 0) {\n await sleep(\n Math.min(retry.maxDelayMs, retry.initialDelayMs * 2 ** (attempt - 1)),\n );\n }\n const request = await buildRequest();\n try {\n return await fetchFn(request.url, {\n method: request.method,\n headers: request.headers,\n });\n } catch (error) {\n // An abort is a deliberate cancellation, not a flaky tunnel — surface it\n // immediately instead of burning attempts (and backoff) on it.\n if (isAbortError(error)) {\n throw error;\n }\n lastError = error;\n }\n }\n throw lastError;\n}\n\n/** Outcome of {@link readPersonalServerData}: the payload plus optional receipt. */\nexport interface PersonalServerReadResult {\n /** The decoded JSON payload returned by the Personal Server. */\n data: unknown;\n /** Present only when this read required (and settled) a payment. */\n payment?: DirectPaymentReceipt;\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\n/** Compute the data path for a scope (`/v1/data/{scope}`). */\nexport function dataPathForScope(scope: string): string {\n return `/v1/data/${encodeURIComponent(scope)}`;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction stringField(\n record: Record<string, unknown> | undefined,\n field: string,\n): string | undefined {\n const value = record?.[field];\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction parseAccessRecord(value: unknown): EscrowAccessRecord | undefined {\n const record = asRecord(value);\n const dataPointId = stringField(record, \"dataPointId\");\n const version = stringField(record, \"version\");\n const accessor = stringField(record, \"accessor\");\n const recordId = stringField(record, \"recordId\");\n const signature = stringField(record, \"signature\");\n\n if (!dataPointId || !version || !accessor || !recordId || !signature) {\n return undefined;\n }\n\n return {\n dataPointId: dataPointId as `0x${string}`,\n version,\n accessor: accessor as `0x${string}`,\n recordId: recordId as `0x${string}`,\n signature: signature as `0x${string}`,\n };\n}\n\nfunction isBytes32Hex(value: string): boolean {\n return /^0x[0-9a-fA-F]{64}$/.test(value);\n}\n\nfunction assertChallengeMatchesGrant(params: {\n challengeGrantId?: string;\n challengeOpId?: string;\n challengeOpType?: string;\n grantId: string;\n}): void {\n const { challengeGrantId, challengeOpId, challengeOpType, grantId } = params;\n\n if (challengeOpType && challengeOpType !== GRANT_OP_TYPE) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge used an unsupported escrow op type\",\n 402,\n { opType: challengeOpType },\n );\n }\n\n const challengedGrantId = challengeOpId ?? challengeGrantId;\n if (!challengedGrantId) return;\n\n if (!isBytes32Hex(challengedGrantId)) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge used an invalid escrow op id\",\n 402,\n { opId: challengedGrantId },\n );\n }\n\n if (challengedGrantId.toLowerCase() !== grantId.toLowerCase()) {\n throw new PersonalServerReadError(\n \"Personal Server payment challenge did not match the requested grant\",\n 402,\n { opId: challengedGrantId, grantId },\n );\n }\n}\n\n/**\n * Build a Web3Signed-authenticated Personal Server data read request.\n *\n * @param params - Personal Server URL, scope, grant id, and an EIP-191 signer.\n * @returns The request URL, method, path, and headers (including `Authorization`).\n */\nexport async function buildPersonalServerDataReadRequest(params: {\n /** Base URL of the user's Personal Server. */\n personalServerUrl: string;\n /** Scope to read (e.g. `\"icloud_notes.notes\"`). */\n scope: string;\n /** Grant id authorizing the read. */\n grantId: string;\n /** EIP-191 signer for the Web3Signed header (the app key). */\n signMessage: Web3SignedSignFn;\n}): Promise<PersonalServerDataReadRequest> {\n const base = stripTrailingSlash(params.personalServerUrl);\n const path = dataPathForScope(params.scope);\n const authorization = await buildWeb3SignedHeader({\n signMessage: params.signMessage,\n aud: base,\n method: \"GET\",\n uri: path,\n grantId: params.grantId,\n });\n const headers: Record<string, string> = {\n Authorization: authorization,\n Accept: \"application/json\",\n };\n return { url: `${base}${path}`, method: \"GET\", path, headers };\n}\n\n/**\n * Parse a `402 Payment Required` body into a {@link PersonalServerPaymentRequired}.\n *\n * @remarks\n * Accepts a few field spellings and falls back to the read's own grantId and the\n * native asset when a field is absent.\n *\n * @param res - The 402 response.\n * @param grantId - The grant id of the read (default `opId`).\n * @returns The parsed payment requirement.\n */\nexport async function parsePersonalServerPaymentRequired(\n res: FetchResponseLike,\n grantId: string,\n): Promise<PersonalServerPaymentRequired> {\n let raw: unknown = undefined;\n try {\n raw = await res.json();\n } catch {\n raw = undefined;\n }\n const body = asRecord(raw) ?? {};\n const accept =\n Array.isArray(body.accepts) && body.accepts.length > 0\n ? asRecord(body.accepts[0])\n : undefined;\n const message = asRecord(accept?.message);\n const challengeGrantId = stringField(body, \"grantId\");\n const challengeOpId =\n stringField(message, \"opId\") ?? stringField(body, \"opId\");\n const challengeOpType =\n stringField(message, \"opType\") ?? stringField(body, \"opType\");\n\n assertChallengeMatchesGrant({\n challengeGrantId,\n challengeOpId,\n challengeOpType,\n grantId,\n });\n\n const amountValue =\n stringField(message, \"amount\") ??\n stringField(body, \"amount\") ??\n stringField(body, \"maxAmountRequired\") ??\n \"0\";\n return {\n grantId,\n network: stringField(accept, \"network\") ?? stringField(body, \"network\"),\n paymentNonce:\n stringField(message, \"paymentNonce\") ?? stringField(body, \"paymentNonce\"),\n accessRecord: parseAccessRecord(accept?.accessRecord ?? body.accessRecord),\n asset:\n stringField(message, \"asset\") ??\n stringField(body, \"asset\") ??\n NATIVE_ASSET_ADDRESS,\n amount: amountValue,\n raw,\n };\n}\n\nfunction hasPositiveAmount(amount: string): boolean {\n if (!/^\\d+$/.test(amount)) return false;\n return BigInt(amount) > 0n;\n}\n\n/**\n * Read approved data from a Personal Server, settling a 402 via escrow.\n *\n * @remarks\n * Sends a Web3Signed-authenticated `GET /v1/data/{scope}`. On `402`, parses what\n * is owed, authorizes an escrow payment for the grant via `escrow`, and retries\n * once. If escrow is not configured, throws {@link PaymentRequiredError} carrying\n * the parsed requirement so callers can debug amount/asset.\n *\n * Transport failures (fetch throwing — the browser-PS relay reconnect window)\n * are retried with backoff per `transportRetry` (default 3 attempts). The paid\n * retry reuses the already-signed `X-PAYMENT` header, so transport retries can\n * never double-pay.\n *\n * @param params - Connection details, app signer, optional escrow config and fetch.\n * @returns `{ data, payment? }`.\n */\nexport async function readPersonalServerData(params: {\n personalServerUrl: string;\n scope: string;\n grantId: string;\n payerAddress: `0x${string}`;\n signMessage: Web3SignedSignFn;\n escrow?: EscrowPaymentConfig;\n fetchFn?: PersonalServerFetch;\n transportRetry?: PersonalServerTransportRetryOptions;\n}): Promise<PersonalServerReadResult> {\n const fetchFn =\n params.fetchFn ?? (globalThis.fetch as unknown as PersonalServerFetch);\n if (!fetchFn) {\n throw new PersonalServerReadError(\n \"No fetch implementation available for Personal Server read\",\n );\n }\n const transportRetry = {\n ...TRANSPORT_RETRY_DEFAULTS,\n ...params.transportRetry,\n };\n\n const buildRequest = () =>\n buildPersonalServerDataReadRequest({\n personalServerUrl: params.personalServerUrl,\n scope: params.scope,\n grantId: params.grantId,\n signMessage: params.signMessage,\n });\n\n let res = await fetchWithTransportRetry(\n fetchFn,\n buildRequest,\n transportRetry,\n );\n\n let payment: DirectPaymentReceipt | undefined;\n\n if (res.status === 402) {\n const required = await parsePersonalServerPaymentRequired(\n res,\n params.grantId,\n );\n if (!params.escrow) {\n throw new PaymentRequiredError(\n \"Personal Server requires payment but no escrow config is set\",\n {\n scope: params.scope,\n grantId: required.grantId,\n asset: required.asset,\n amount: required.amount,\n },\n );\n }\n\n if (!hasPositiveAmount(required.amount)) {\n throw new PaymentRequiredError(\n \"Personal Server payment challenge did not include a positive amount\",\n {\n scope: params.scope,\n grantId: required.grantId,\n asset: required.asset,\n amount: required.amount,\n },\n );\n }\n\n const paymentHeader = await buildGrantPaymentHeader({\n payerAddress: params.payerAddress,\n required,\n config: params.escrow,\n });\n\n // Re-sign and retry with x402 payment proof for the Personal Server to\n // validate. The payment header is built exactly once: transport retries\n // below re-sign only the Web3Signed auth and resend the SAME X-PAYMENT\n // (same paymentNonce), so a dropped tunnel cannot mint a second payment.\n res = await fetchWithTransportRetry(\n fetchFn,\n async () => {\n const retry = await buildRequest();\n return {\n ...retry,\n headers: { ...retry.headers, \"X-PAYMENT\": paymentHeader },\n };\n },\n transportRetry,\n );\n\n if (res.status === 402) {\n throw new PaymentRequiredError(\n \"Personal Server still requires payment after escrow settlement\",\n {\n scope: params.scope,\n grantId: required.grantId,\n asset: required.asset,\n amount: required.amount,\n payment,\n },\n );\n }\n }\n\n if (!res.ok) {\n const detail = await res.text().catch(() => \"\");\n throw new PersonalServerReadError(\n `Personal Server read failed: ${res.status} ${res.statusText}`,\n res.status,\n { scope: params.scope, body: detail.slice(0, 500) },\n );\n }\n\n payment = paymentReceiptFromHeader(res.headers.get(\"X-PAYMENT-RESPONSE\"));\n return { data: await res.json(), payment };\n}\n"],"mappings":"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","names":[]}
@@ -29028,12 +29028,14 @@ async function buildWeb3SignedHeader(params) {
29028
29028
 
29029
29029
  // src/storage/providers/vana-storage.ts
29030
29030
  var DEFAULT_ENDPOINT = "https://storage.vana.org";
29031
- var BLOB_PATH_PREFIX = "/v1/blobs";
29031
+ var LEGACY_BLOB_PATH_PREFIX = "/v1/blobs";
29032
29032
  var DEFAULT_TOKEN_TTL_SECONDS = 300;
29033
29033
  var MAX_UPLOAD_ATTEMPTS = 4;
29034
29034
  var MAX_RATE_LIMIT_DELAY_MS = 3e4;
29035
29035
  var VanaStorage = class {
29036
29036
  endpoint;
29037
+ network;
29038
+ blobPathPrefix;
29037
29039
  signer;
29038
29040
  ownerAddress;
29039
29041
  fetchImpl;
@@ -29046,6 +29048,15 @@ var VanaStorage = class {
29046
29048
  );
29047
29049
  }
29048
29050
  this.endpoint = (config.endpoint ?? DEFAULT_ENDPOINT).replace(/\/+$/, "");
29051
+ if (config.network !== void 0 && !isProtocolNetwork(config.network)) {
29052
+ throw new StorageError(
29053
+ `Unsupported vana-storage network '${String(config.network)}'`,
29054
+ "INVALID_NETWORK",
29055
+ "vana-storage"
29056
+ );
29057
+ }
29058
+ this.network = config.network;
29059
+ this.blobPathPrefix = this.network ? `/v1/networks/${this.network}/blobs` : LEGACY_BLOB_PATH_PREFIX;
29049
29060
  this.signer = config.signer;
29050
29061
  this.ownerAddress = (config.ownerAddress ?? config.signer.address).toLowerCase();
29051
29062
  this.fetchImpl = config.fetchImpl ?? globalThis.fetch.bind(globalThis);
@@ -29066,7 +29077,7 @@ var VanaStorage = class {
29066
29077
  );
29067
29078
  }
29068
29079
  const subpath = encodeRelativePath(filename);
29069
- const path = `${BLOB_PATH_PREFIX}/${this.ownerAddress}/${subpath}`;
29080
+ const path = `${this.blobPathPrefix}/${this.ownerAddress}/${subpath}`;
29070
29081
  const body = new Uint8Array(await file.arrayBuffer());
29071
29082
  const contentType = file.type !== "" ? file.type : "application/octet-stream";
29072
29083
  const header = await this.signRequest("PUT", path, body);
@@ -29234,12 +29245,18 @@ var VanaStorage = class {
29234
29245
  "vana-storage"
29235
29246
  );
29236
29247
  }
29237
- const segments = parsed.pathname.split("/").filter((s) => s.length > 0);
29238
- const isTraversal = (s) => s === "." || s === "..";
29239
- const valid = segments.length === 5 && segments[0] === "v1" && segments[1] === "blobs" && segments[2]?.toLowerCase() === this.ownerAddress && segments[3] !== void 0 && !isTraversal(segments[3]) && segments[4] !== void 0 && !isTraversal(segments[4]);
29240
- if (!valid) {
29248
+ const route = parseBlobPath(parsed.pathname);
29249
+ if (!route || route.owner.toLowerCase() !== this.ownerAddress) {
29241
29250
  throw new StorageError(
29242
- `URL path '${parsed.pathname}' must be /v1/blobs/${this.ownerAddress}/{scope}/{collectedAt}`,
29251
+ `URL path '${parsed.pathname}' must be ${this.blobPathPrefix}/${this.ownerAddress}/{scope}/{collectedAt}`,
29252
+ "INVALID_URL",
29253
+ "vana-storage"
29254
+ );
29255
+ }
29256
+ const crossNetwork = this.network ? route.network !== void 0 && route.network !== this.network : route.network !== void 0;
29257
+ if (crossNetwork) {
29258
+ throw new StorageError(
29259
+ `URL network '${route.network ?? "legacy"}' does not match provider network '${this.network ?? "legacy"}'`,
29243
29260
  "INVALID_URL",
29244
29261
  "vana-storage"
29245
29262
  );
@@ -29247,6 +29264,26 @@ var VanaStorage = class {
29247
29264
  return parsed.pathname;
29248
29265
  }
29249
29266
  };
29267
+ var PROTOCOL_NETWORKS = ["mainnet", "moksha"];
29268
+ function isProtocolNetwork(value) {
29269
+ return typeof value === "string" && PROTOCOL_NETWORKS.includes(value);
29270
+ }
29271
+ function parseBlobPath(pathname) {
29272
+ const segments = pathname.split("/").filter((s) => s.length > 0);
29273
+ const isTraversal = (s) => s === "." || s === "..";
29274
+ if (segments.length === 5 && segments[0] === "v1" && segments[1] === "blobs") {
29275
+ const [, , owner, scope, collectedAt] = segments;
29276
+ if (isTraversal(scope) || isTraversal(collectedAt)) return null;
29277
+ return { owner, scope, collectedAt };
29278
+ }
29279
+ if (segments.length === 7 && segments[0] === "v1" && segments[1] === "networks" && segments[3] === "blobs") {
29280
+ const [, , network, , owner, scope, collectedAt] = segments;
29281
+ if (!isProtocolNetwork(network)) return null;
29282
+ if (isTraversal(scope) || isTraversal(collectedAt)) return null;
29283
+ return { network, owner, scope, collectedAt };
29284
+ }
29285
+ return null;
29286
+ }
29250
29287
  function encodeRelativePath(filename) {
29251
29288
  const parts = filename.split("/");
29252
29289
  if (parts.length !== 2 || parts.some((p) => p.length === 0 || p === "." || p === "..")) {