@provex/extension-react 1.11.0 → 2.0.0-rc.20261103015923.731cc5d

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 (50) hide show
  1. package/README.md +23 -123
  2. package/dist/ZKTLSContext.cjs +7 -75
  3. package/dist/ZKTLSContext.cjs.map +1 -1
  4. package/dist/ZKTLSContext.d.cts +1 -15
  5. package/dist/ZKTLSContext.d.ts +1 -15
  6. package/dist/ZKTLSContext.js +2 -2
  7. package/dist/adapters/extensionEnclave.cjs.map +1 -1
  8. package/dist/adapters/extensionEnclave.js +1 -1
  9. package/dist/capability.cjs +50 -0
  10. package/dist/capability.cjs.map +1 -0
  11. package/dist/capability.d.cts +95 -0
  12. package/dist/capability.d.ts +95 -0
  13. package/dist/capability.js +3 -0
  14. package/dist/capability.js.map +1 -0
  15. package/dist/{chunk-T42E7EM7.js → chunk-B7RU2WKQ.js} +3 -3
  16. package/dist/{chunk-T42E7EM7.js.map → chunk-B7RU2WKQ.js.map} +1 -1
  17. package/dist/chunk-JMVW4H4H.js +44 -0
  18. package/dist/chunk-JMVW4H4H.js.map +1 -0
  19. package/dist/chunk-JU2T2TGY.js +123 -0
  20. package/dist/chunk-JU2T2TGY.js.map +1 -0
  21. package/dist/chunk-VQFWUZNJ.js +59 -0
  22. package/dist/chunk-VQFWUZNJ.js.map +1 -0
  23. package/dist/{chunk-AMJXMSDV.js → chunk-WSLUOLBW.js} +2 -2
  24. package/dist/chunk-WSLUOLBW.js.map +1 -0
  25. package/dist/index.cjs +101 -187
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +3 -3
  28. package/dist/index.d.ts +3 -3
  29. package/dist/index.js +5 -3
  30. package/dist/useProofCapability.cjs +104 -0
  31. package/dist/useProofCapability.cjs.map +1 -0
  32. package/dist/useProofCapability.d.cts +27 -0
  33. package/dist/useProofCapability.d.ts +27 -0
  34. package/dist/useProofCapability.js +4 -0
  35. package/dist/useProofCapability.js.map +1 -0
  36. package/dist/useZKTLS.cjs +7 -195
  37. package/dist/useZKTLS.cjs.map +1 -1
  38. package/dist/useZKTLS.d.cts +10 -230
  39. package/dist/useZKTLS.d.ts +10 -230
  40. package/dist/useZKTLS.js +1 -1
  41. package/dist/useZKTLSContext.cjs +0 -5
  42. package/dist/useZKTLSContext.cjs.map +1 -1
  43. package/dist/useZKTLSContext.d.cts +0 -2
  44. package/dist/useZKTLSContext.d.ts +0 -2
  45. package/dist/useZKTLSContext.js +2 -2
  46. package/package.json +15 -4
  47. package/skills/extension-react-integration/SKILL.md +16 -73
  48. package/dist/chunk-AMJXMSDV.js.map +0 -1
  49. package/dist/chunk-TAS2MB4S.js +0 -301
  50. package/dist/chunk-TAS2MB4S.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/extensionEnclave.ts"],"names":["parseUnits","convertTokenInputToCurrency","providerConfigs","ManualSelectionRequired","EnclaveCaptureUnavailable","EnclaveCaptureFailed","PaymentAlreadyUsedError","payment","providerRequiresMetadataIndex","requestBuyerTeeAttestation"],"mappings":"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","file":"extensionEnclave.cjs","sourcesContent":["/**\n * @fileoverview Extension + enclave CaptureAdapter (the v1 capture backend).\n *\n * Mirrors the live AuthenticateStep + GenerateProofStep enclave path\n * (AuthenticateStep.tsx:224-281, GenerateProofStep.tsx:237-251) reduced to\n * the buyerTee branch. Talks to an injected `window.peer`-shaped client so\n * the injected-PeerClient seam is preserved exactly.\n */\n\nimport { parseUnits } from 'viem'\nimport type { Hex } from 'viem'\nimport { providerConfigs, providerRequiresMetadataIndex } from '@provex/utils/payment'\nimport type { ProviderKey, SubProviderKey } from '@provex/utils/payment'\nimport type { ChainId } from '@provex/utils/chain'\nimport { convertTokenInputToCurrency } from '@provex/utils/conversionRates'\nimport {\n EnclaveCaptureUnavailable,\n EnclaveCaptureFailed,\n ManualSelectionRequired,\n PaymentAlreadyUsedError,\n asBuyerTeeProof,\n requestBuyerTeeAttestation,\n type CaptureAdapter,\n type CaptureCandidate,\n type CaptureHandle,\n type CaptureRequest,\n type SelectTransaction,\n type VerifiedAttestation,\n} from '@provex/proving'\n\n/** Minimal shape of the metadata response the extension posts back. */\ninterface MetadataResponse {\n platform: string\n metadata: CaptureCandidate[]\n buyerTeeCapture?: { encryptedSessionMaterial: string }\n}\n\n/**\n * zkp2p enclave used for Buyer TEE capture when the caller doesn't override\n * `ExtensionEnclaveAdapterConfig.attestationServiceUrl`. Matches the gate's\n * `TEE_ATTESTATION_URL` — the enclave the gate forwards to for the re-sign.\n */\nexport const DEFAULT_ATTESTATION_SERVICE_URL = 'https://attestation-service.zkp2p.xyz'\n\n/** Minimal slice of `window.peer` this adapter depends on. */\nexport interface PeerClient {\n authenticate(inputs: {\n actionType: string\n platform: string\n captureMode: 'buyerTee'\n /**\n * Enclave the extension establishes the Buyer TEE session with and encrypts\n * the session material toward. Required by the zkp2p 0.6.0 spec\n * (onramp-llm.md §7) — without it the extension performs no Buyer TEE\n * capture and falls back to the retired Reclaim path.\n */\n attestationServiceUrl?: string\n /** Attestation action type; defaults to `actionType` when omitted (spec §2). */\n attestationActionType?: string\n /** Attestation platform; defaults to `platform` when omitted. */\n attestationPlatform?: string\n }): Promise<void>\n onMetadataMessage(fn: (data: MetadataResponse) => void): () => void\n /** Current extension connection status; `'connected'` means ready. */\n checkConnectionStatus(): Promise<string>\n /** Prompt the user to connect the extension. Resolves true when approved. */\n requestConnection(): Promise<boolean>\n}\n\nexport interface ExtensionEnclaveAdapterConfig {\n /** Resolve the live peer client (e.g. `() => window.peer`). */\n getPeer: () => PeerClient | undefined\n /** Gate/attestation origin handed to requestBuyerTeeAttestation (the re-sign hop). */\n attestationBaseUrl: string\n /**\n * Enclave URL passed to the extension's `authenticate` so it performs the\n * Buyer TEE capture (zkp2p 0.6.0 spec §7). This is the zkp2p enclave the\n * session material is encrypted toward — the SAME enclave the gate forwards\n * to for the re-sign — NOT our gate's re-sign origin (`attestationBaseUrl`).\n * Defaults to {@link DEFAULT_ATTESTATION_SERVICE_URL} when omitted; set it to\n * override the enclave for local testing.\n */\n attestationServiceUrl?: string\n /**\n * Milliseconds before awaitMetadata rejects if the extension never posts a\n * matching metadata message. Mirrors the 60-second timeout in\n * AuthenticateStep.tsx:232-236. Defaults to 60000.\n */\n timeoutMs?: number\n}\n\n/** One bank-history row the attestation may settle this order with. */\ninterface PaymentToTry {\n /** The row's index in the captured bank history. */\n index: number\n /** The row's provider attestation params (spec §4), forwarded to the enclave. */\n params?: Record<string, string | number | boolean>\n}\n\ninterface EnclaveMaterial {\n encryptedSessionMaterial: string\n /**\n * Payments to attest, in order. One entry for a single amount match or a\n * buyer's pick; every amount match (bank order, newest first) when several\n * payments share the order's amount. Bank dates resolve only to the day,\n * so they cannot tell same-day payments apart; the gate can, by refusing a\n * payment that already settled an order.\n */\n payments: PaymentToTry[]\n}\n\n/**\n * Normalize an amount from the extension into a bigint in token base units.\n *\n * Reproduces findMetadataMessage's normalizeAmount (useZKTLS.ts:630-639).\n * The extension sends amounts in different formats per provider:\n * - Venmo: `\"- $1.00\"` (formatted dollar string — isFormattedDollars=true)\n * - Others: `100` (integer cents) or `1.00` (decimal dollars)\n *\n * When `centIsInt` is true and the raw value has no currency symbol or decimal\n * point (i.e. it arrived as integer cents), we divide by 100 to convert to\n * the same base unit scale that parseUnits applied.\n */\nfunction normalizeAmount(amount: number | string, decimals: number, centIsInt: boolean): bigint {\n const raw = String(amount)\n const isFormattedDollars = /[^0-9\\-]/.test(raw) && raw.includes('.')\n const cleaned = raw.replace(/[^0-9.]/g, '')\n let value = parseUnits(cleaned, decimals)\n if (centIsInt && !isFormattedDollars) {\n value = value / 100n\n }\n return value\n}\n\n/**\n * The outcome of matching the bank history against the intent's amount.\n *\n * - `single`: exactly one payment has the intent's amount.\n * - `several`: more than one does (e.g. two $2 Zelle payments to the same\n * seller). All are tried in bank order; the gate refuses any that already\n * settled an order. Taking only the first sent an already-used payment and\n * failed the order with \"This payment has already been verified\".\n * - `none`: no payment has that amount; the buyer picks from the full list.\n */\ntype PaymentMatch =\n | { kind: 'single'; candidate: CaptureCandidate }\n | { kind: 'several'; candidates: CaptureCandidate[] }\n | { kind: 'none' }\n\n/**\n * Match the bank history against the intent's FIAT currency amount,\n * accepting a positive or negative sign as findMetadataMessage does.\n *\n * Uses the same pipeline as the live AuthenticateStep.tsx:147-163:\n * 1. Convert token amount → fiat via convertTokenInputToCurrency.\n * 2. Normalize each candidate with the provider's centIsInt flag.\n * 3. Match equality (or negated equality for debit/refund signs).\n *\n * `centIsInt` is sourced from `providerConfigs(chainId)[provider]?.amountInCents`\n * which mirrors how the live caller derives it:\n * `targetProvider?.amountInCents ?? false`\n * where `targetProvider` is resolved via `useProvider` from the same\n * providerConfigs call. Sub-providers (e.g. zelle-chase) do NOT currently\n * set amountInCents, so the parent-provider lookup is sufficient.\n *\n * Matches keep the bank's own order (newest first for the hosted templates).\n */\nfunction matchPayment(\n candidates: CaptureCandidate[],\n request: CaptureRequest,\n): PaymentMatch {\n const currencyAmount = convertTokenInputToCurrency({\n token: request.token,\n amountOutInt: BigInt(request.intent.amount),\n rate: BigInt(request.intent.conversionRate),\n })\n\n // Resolve centIsInt from the provider config — same source as the live UI.\n // The cast is safe: CaptureRequest.chainId must be a supported chain ID.\n const configs = providerConfigs(request.chainId as ChainId)\n const providerConfig = configs[request.provider as keyof typeof configs]\n const centIsInt = (providerConfig as { amountInCents?: boolean } | undefined)?.amountInCents ?? false\n\n const matches = candidates.filter((candidate) => {\n const converted = normalizeAmount(candidate.amount, request.token.decimals, centIsInt)\n return converted === currencyAmount || converted === currencyAmount * -1n\n })\n if (matches.length === 0) return { kind: 'none' }\n if (matches.length === 1) return { kind: 'single', candidate: matches[0]! }\n return { kind: 'several', candidates: matches }\n}\n\n/** The bank-history row as a payment to try. */\nfunction toPaymentToTry(candidate: CaptureCandidate): PaymentToTry {\n return { index: candidate.originalIndex, params: candidate.params }\n}\n\n/**\n * The payments to attest, in order: the amount matches, else the buyer's pick\n * from the whole history. Throws {@link ManualSelectionRequired} when a pick is\n * needed but no resolver was supplied.\n */\nasync function resolvePaymentsToTry({\n match,\n history,\n selectTransaction,\n}: {\n match: PaymentMatch\n history: CaptureCandidate[]\n selectTransaction?: SelectTransaction\n}): Promise<PaymentToTry[]> {\n if (match.kind === 'single') return [toPaymentToTry(match.candidate)]\n if (match.kind === 'several') return match.candidates.map(toPaymentToTry)\n if (!selectTransaction) throw new ManualSelectionRequired(history)\n const pickedIndex = await selectTransaction(history)\n const picked = history.find((row) => row.originalIndex === pickedIndex)\n return [{ index: pickedIndex, params: picked?.params }]\n}\n\n/**\n * Wait for the metadata message the extension posts for `platform`.\n *\n * Races a configurable timeout (default 60s) against the metadata event,\n * mirroring AuthenticateStep.tsx:232-236. The unsubscribe function is always\n * called on resolution, timeout, or error — no listener leaks.\n *\n * @param peer - Injected extension client.\n * @param platform - The platform handed to `authenticate()` (e.g. 'zelle').\n * Echoed back when the extension could not load a template.\n * @param actionType - The capture action type (e.g. 'transfer_venmo'). Its\n * parent is echoed back when the template DID load. Both are accepted; see\n * the guard below.\n * @param timeoutMs - Reject after this long with no matching message.\n */\nfunction awaitMetadata(\n peer: PeerClient,\n platform: string,\n actionType: string,\n timeoutMs: number,\n): Promise<MetadataResponse> {\n return new Promise<MetadataResponse>((resolve, reject) => {\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n\n const unsub = peer.onMetadataMessage((data) => {\n // The extension echoes the platform from one of two places:\n //\n // - Template loaded (success): the TEMPLATE's own `metadata.platform`.\n // - Template fetch failed: there is no template, so it echoes the\n // platform we passed, alongside an errorMessage such as\n // 'Failed to fetch provider template: 404'.\n //\n // With hosted templates the two agree (generic Zelle: 'zelle' both\n // ways). Accepting either still guards against a template whose\n // metadata.platform is the parent rail rather than the platform we\n // passed, so a successful capture is never dropped into a 60s timeout.\n const echoesCapturePlatform = data.platform === platform\n const echoesActionTypeParent = `transfer_${data.platform}` === actionType\n if (echoesCapturePlatform || echoesActionTypeParent) {\n clearTimeout(timeoutId)\n unsub()\n resolve(data)\n }\n })\n\n timeoutId = setTimeout(() => {\n unsub()\n reject(new Error(`Enclave capture timed out for platform \"${platform}\"`))\n }, timeoutMs)\n })\n}\n\nexport function createExtensionEnclaveAdapter(config: ExtensionEnclaveAdapterConfig): CaptureAdapter {\n const timeoutMs = config.timeoutMs ?? 60000\n\n return {\n kind: 'extension-enclave',\n\n async authenticateAndCapture(\n request: CaptureRequest,\n options?: { selectTransaction?: SelectTransaction },\n ): Promise<CaptureHandle> {\n const peer = config.getPeer()\n if (!peer) {\n throw new EnclaveCaptureUnavailable(request.platform)\n }\n\n // Ensure the extension is connected BEFORE authenticate. A cold\n // authenticate() on a fresh page returns errorMessage \"Peer extension\n // connection required\" with no buyerTeeCapture — which trips the\n // enclave-unavailable path. The legacy AuthenticateStep connects first\n // (and onramp-llm.md §3 `ensurePeerReady` requires it); the adapter must\n // too. This is why a warm local tab worked but a fresh prod session failed.\n const status = await peer.checkConnectionStatus()\n if (status !== 'connected') {\n await peer.requestConnection()\n }\n\n // Register the listener BEFORE authenticate fires to avoid a race where\n // the extension posts metadata before the listener is attached.\n const metadataPromise = awaitMetadata(peer, request.platform, request.actionType, timeoutMs)\n await peer.authenticate({\n actionType: request.actionType,\n platform: request.platform,\n captureMode: 'buyerTee',\n // Required for the extension to actually perform the Buyer TEE capture\n // (zkp2p 0.6.0 spec §7). Omitting these is why non-Revolut rails got\n // \"no buyerTee capture\" and dead-ended on the retired Reclaim path.\n attestationServiceUrl: config.attestationServiceUrl ?? DEFAULT_ATTESTATION_SERVICE_URL,\n // The attestation route equals the capture pair for every rail today\n // (generic Zelle included: zelle/transfer_zelle_chase). No template is\n // passed: extension 0.6.7 always fetches it from api.zkp2p.xyz and\n // ignores a page-supplied one, so the capture pair must be hosted.\n attestationActionType: request.attestationActionType ?? request.actionType,\n attestationPlatform: request.attestationPlatform ?? request.platform,\n })\n const response = await metadataPromise\n\n // No buyerTee capture ⇒ the extension didn't produce an encrypted enclave\n // session. Log WHY before failing — the extension may have surfaced an\n // errorMessage, or returned metadata-only (no capture) for this platform.\n if (!response.buyerTeeCapture) {\n const errorMessage = (response as { errorMessage?: unknown }).errorMessage\n console.warn(\n '[extensionEnclave] no buyerTeeCapture for',\n request.platform,\n '— response keys:', Object.keys(response),\n '| errorMessage:', errorMessage,\n '| metadata rows:', Array.isArray(response.metadata) ? response.metadata.length : '(none)',\n '| buyerTeeCapture:', response.buyerTeeCapture,\n )\n // Two distinct failures share the \"no capture\" shape:\n // - The extension surfaced an `errorMessage` ⇒ it ATTEMPTED the capture\n // but something failed (most often: it could not reach the\n // attestation enclave — a buyer-side network / firewall / ad-blocker\n // issue). Surface that as a retryable EnclaveCaptureFailed carrying\n // the raw reason, so the user sees an actionable message instead of\n // \"provider unsupported\".\n // - No `errorMessage` ⇒ the provider produced no capture path at all\n // (non-migrated). That stays EnclaveCaptureUnavailable, the\n // Reclaim-fallback signal.\n if (typeof errorMessage === 'string' && errorMessage.trim().length > 0) {\n throw new EnclaveCaptureFailed(request.platform, errorMessage)\n }\n throw new EnclaveCaptureUnavailable(request.platform)\n }\n\n const material: EnclaveMaterial = {\n encryptedSessionMaterial: response.buyerTeeCapture.encryptedSessionMaterial,\n payments: await resolvePaymentsToTry({\n match: matchPayment(response.metadata, request),\n history: response.metadata,\n selectTransaction: options?.selectTransaction,\n }),\n }\n return { kind: 'extension-enclave', material }\n },\n\n async attest(handle: CaptureHandle, request: CaptureRequest): Promise<VerifiedAttestation> {\n if (handle.kind !== 'extension-enclave') {\n throw new Error(\n `ExtensionEnclaveAdapter.attest received a handle of kind \"${handle.kind}\"; expected \"extension-enclave\".`,\n )\n }\n const material = handle.material as EnclaveMaterial\n // Try each candidate payment in order. A payment that already settled an\n // order is refused by the gate (PaymentAlreadyUsedError) and skipped;\n // any other failure stops here and surfaces as-is.\n let alreadyUsed: PaymentAlreadyUsedError | undefined\n for (const payment of material.payments) {\n try {\n const attestation = await attestPayment({ payment, material, request })\n // verificationData = enclave signer (SubmitStep.tsx:473-474).\n return { attestation, verificationData: attestation.signer as Hex }\n } catch (error) {\n if (!(error instanceof PaymentAlreadyUsedError)) throw error\n alreadyUsed = error\n }\n }\n throw alreadyUsed ?? new Error('No bank payment was captured for this order.')\n },\n }\n\n /** Attest one candidate payment through the gate. */\n function attestPayment({\n payment,\n material,\n request,\n }: {\n payment: PaymentToTry\n material: EnclaveMaterial\n request: CaptureRequest\n }) {\n // The selected row's params ARE the attestation params (spec §5,\n // buildBuyerTeeProof) and MUST be forwarded — dropping them is why the\n // enclave 400s with \"Invalid <provider> query params\". The metadata\n // `index` is then merged in ONLY for providers that expect it (Venmo /\n // Cash App / Revolut / Zelle) and omitted for the rest (PayPal / Wise /\n // Monzo / Chime). See providerRequiresMetadataIndex.\n const params = {\n ...(payment.params ?? {}),\n ...(providerRequiresMetadataIndex(request.provider as ProviderKey | SubProviderKey)\n ? { index: payment.index }\n : {}),\n }\n return requestBuyerTeeAttestation({\n attestationBaseUrl: config.attestationBaseUrl,\n platform: request.attestationPlatform ?? request.platform,\n actionType: request.attestationActionType ?? request.actionType,\n encryptedSessionMaterial: material.encryptedSessionMaterial,\n params,\n verifyingContract: request.verifyingContract,\n intent: request.intent,\n })\n }\n}\n\n// Re-narrow export so consumers can validate proof shapes if they bypass the\n// adapter (kept for parity with the live SubmitStep check, SubmitStep.tsx:206).\nexport { asBuyerTeeProof }\n"]}
1
+ {"version":3,"sources":["../../src/adapters/extensionEnclave.ts"],"names":["parseUnits","convertTokenInputToCurrency","providerConfigs","ManualSelectionRequired","EnclaveCaptureUnavailable","EnclaveCaptureFailed","PaymentAlreadyUsedError","payment","providerRequiresMetadataIndex","requestBuyerTeeAttestation"],"mappings":"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","file":"extensionEnclave.cjs","sourcesContent":["/**\n * @fileoverview Extension + enclave CaptureAdapter (the v1 capture backend).\n *\n * Mirrors the live AuthenticateStep + GenerateProofStep enclave path\n * (AuthenticateStep.tsx:224-281, GenerateProofStep.tsx:237-251) reduced to\n * the buyerTee branch. Talks to an injected `window.peer`-shaped client so\n * the injected-PeerClient seam is preserved exactly.\n */\n\nimport { parseUnits } from 'viem'\nimport type { Hex } from 'viem'\nimport { providerConfigs, providerRequiresMetadataIndex } from '@provex/utils/payment'\nimport type { ProviderKey, SubProviderKey } from '@provex/utils/payment'\nimport type { ChainId } from '@provex/utils/chain'\nimport { convertTokenInputToCurrency } from '@provex/utils/conversionRates'\nimport {\n EnclaveCaptureUnavailable,\n EnclaveCaptureFailed,\n ManualSelectionRequired,\n PaymentAlreadyUsedError,\n asBuyerTeeProof,\n requestBuyerTeeAttestation,\n type CaptureAdapter,\n type CaptureCandidate,\n type CaptureHandle,\n type CaptureRequest,\n type SelectTransaction,\n type VerifiedAttestation,\n} from '@provex/proving'\n\n/** Minimal shape of the metadata response the extension posts back. */\ninterface MetadataResponse {\n platform: string\n metadata: CaptureCandidate[]\n buyerTeeCapture?: { encryptedSessionMaterial: string }\n}\n\n/**\n * zkp2p enclave used for Buyer TEE capture when the caller doesn't override\n * `ExtensionEnclaveAdapterConfig.attestationServiceUrl`. Matches the gate's\n * `TEE_ATTESTATION_URL` — the enclave the gate forwards to for the re-sign.\n */\nexport const DEFAULT_ATTESTATION_SERVICE_URL = 'https://attestation-service.zkp2p.xyz'\n\n/** Minimal slice of `window.peer` this adapter depends on. */\nexport interface PeerClient {\n authenticate(inputs: {\n actionType: string\n platform: string\n captureMode: 'buyerTee'\n /**\n * Enclave the extension establishes the Buyer TEE session with and encrypts\n * the session material toward. Required by the zkp2p 0.6.0 spec\n * (onramp-llm.md §7) — without it the extension performs no Buyer TEE\n * capture and falls back to the retired Reclaim path.\n */\n attestationServiceUrl?: string\n /** Attestation action type; defaults to `actionType` when omitted (spec §2). */\n attestationActionType?: string\n /** Attestation platform; defaults to `platform` when omitted. */\n attestationPlatform?: string\n }): Promise<void>\n onMetadataMessage(fn: (data: MetadataResponse) => void): () => void\n /** Current extension connection status; `'connected'` means ready. */\n checkConnectionStatus(): Promise<string>\n /** Prompt the user to connect the extension. Resolves true when approved. */\n requestConnection(): Promise<boolean>\n}\n\nexport interface ExtensionEnclaveAdapterConfig {\n /** Resolve the live peer client (e.g. `() => window.peer`). */\n getPeer: () => PeerClient | undefined\n /** Gate/attestation origin handed to requestBuyerTeeAttestation (the re-sign hop). */\n attestationBaseUrl: string\n /**\n * Enclave URL passed to the extension's `authenticate` so it performs the\n * Buyer TEE capture (zkp2p 0.6.0 spec §7). This is the zkp2p enclave the\n * session material is encrypted toward — the SAME enclave the gate forwards\n * to for the re-sign — NOT our gate's re-sign origin (`attestationBaseUrl`).\n * Defaults to {@link DEFAULT_ATTESTATION_SERVICE_URL} when omitted; set it to\n * override the enclave for local testing.\n */\n attestationServiceUrl?: string\n /**\n * Milliseconds before awaitMetadata rejects if the extension never posts a\n * matching metadata message. Mirrors the 60-second timeout in\n * AuthenticateStep.tsx:232-236. Defaults to 60000.\n */\n timeoutMs?: number\n}\n\n/** One bank-history row the attestation may settle this order with. */\ninterface PaymentToTry {\n /** The row's index in the captured bank history. */\n index: number\n /** The row's provider attestation params (spec §4), forwarded to the enclave. */\n params?: Record<string, string | number | boolean>\n}\n\ninterface EnclaveMaterial {\n encryptedSessionMaterial: string\n /**\n * Payments to attest, in order. One entry for a single amount match or a\n * buyer's pick; every amount match (bank order, newest first) when several\n * payments share the order's amount. Bank dates resolve only to the day,\n * so they cannot tell same-day payments apart; the gate can, by refusing a\n * payment that already settled an order.\n */\n payments: PaymentToTry[]\n}\n\n/**\n * Normalize an amount from the extension into a bigint in token base units.\n *\n * This is the only amount normalizer; the retired `findMetadataMessage` was removed.\n * The extension sends amounts in different formats per provider:\n * - Venmo: `\"- $1.00\"` (formatted dollar string — isFormattedDollars=true)\n * - Others: `100` (integer cents) or `1.00` (decimal dollars)\n *\n * When `centIsInt` is true and the raw value has no currency symbol or decimal\n * point (i.e. it arrived as integer cents), we divide by 100 to convert to\n * the same base unit scale that parseUnits applied.\n */\nfunction normalizeAmount(amount: number | string, decimals: number, centIsInt: boolean): bigint {\n const raw = String(amount)\n const isFormattedDollars = /[^0-9\\-]/.test(raw) && raw.includes('.')\n const cleaned = raw.replace(/[^0-9.]/g, '')\n let value = parseUnits(cleaned, decimals)\n if (centIsInt && !isFormattedDollars) {\n value = value / 100n\n }\n return value\n}\n\n/**\n * The outcome of matching the bank history against the intent's amount.\n *\n * - `single`: exactly one payment has the intent's amount.\n * - `several`: more than one does (e.g. two $2 Zelle payments to the same\n * seller). All are tried in bank order; the gate refuses any that already\n * settled an order. Taking only the first sent an already-used payment and\n * failed the order with \"This payment has already been verified\".\n * - `none`: no payment has that amount; the buyer picks from the full list.\n */\ntype PaymentMatch =\n | { kind: 'single'; candidate: CaptureCandidate }\n | { kind: 'several'; candidates: CaptureCandidate[] }\n | { kind: 'none' }\n\n/**\n * Match the bank history against the intent's FIAT currency amount,\n * accepting a positive or negative sign.\n *\n * Uses the same pipeline as the live AuthenticateStep.tsx:147-163:\n * 1. Convert token amount → fiat via convertTokenInputToCurrency.\n * 2. Normalize each candidate with the provider's centIsInt flag.\n * 3. Match equality (or negated equality for debit/refund signs).\n *\n * `centIsInt` is sourced from `providerConfigs(chainId)[provider]?.amountInCents`\n * which mirrors how the live caller derives it:\n * `targetProvider?.amountInCents ?? false`\n * where `targetProvider` is resolved via `useProvider` from the same\n * providerConfigs call. Sub-providers (e.g. zelle-chase) do NOT currently\n * set amountInCents, so the parent-provider lookup is sufficient.\n *\n * Matches keep the bank's own order (newest first for the hosted templates).\n */\nfunction matchPayment(\n candidates: CaptureCandidate[],\n request: CaptureRequest,\n): PaymentMatch {\n const currencyAmount = convertTokenInputToCurrency({\n token: request.token,\n amountOutInt: BigInt(request.intent.amount),\n rate: BigInt(request.intent.conversionRate),\n })\n\n // Resolve centIsInt from the provider config — same source as the live UI.\n // The cast is safe: CaptureRequest.chainId must be a supported chain ID.\n const configs = providerConfigs(request.chainId as ChainId)\n const providerConfig = configs[request.provider as keyof typeof configs]\n const centIsInt = (providerConfig as { amountInCents?: boolean } | undefined)?.amountInCents ?? false\n\n const matches = candidates.filter((candidate) => {\n const converted = normalizeAmount(candidate.amount, request.token.decimals, centIsInt)\n return converted === currencyAmount || converted === currencyAmount * -1n\n })\n if (matches.length === 0) return { kind: 'none' }\n if (matches.length === 1) return { kind: 'single', candidate: matches[0]! }\n return { kind: 'several', candidates: matches }\n}\n\n/** The bank-history row as a payment to try. */\nfunction toPaymentToTry(candidate: CaptureCandidate): PaymentToTry {\n return { index: candidate.originalIndex, params: candidate.params }\n}\n\n/**\n * The payments to attest, in order: the amount matches, else the buyer's pick\n * from the whole history. Throws {@link ManualSelectionRequired} when a pick is\n * needed but no resolver was supplied.\n */\nasync function resolvePaymentsToTry({\n match,\n history,\n selectTransaction,\n}: {\n match: PaymentMatch\n history: CaptureCandidate[]\n selectTransaction?: SelectTransaction\n}): Promise<PaymentToTry[]> {\n if (match.kind === 'single') return [toPaymentToTry(match.candidate)]\n if (match.kind === 'several') return match.candidates.map(toPaymentToTry)\n if (!selectTransaction) throw new ManualSelectionRequired(history)\n const pickedIndex = await selectTransaction(history)\n const picked = history.find((row) => row.originalIndex === pickedIndex)\n return [{ index: pickedIndex, params: picked?.params }]\n}\n\n/**\n * Wait for the metadata message the extension posts for `platform`.\n *\n * Races a configurable timeout (default 60s) against the metadata event,\n * mirroring AuthenticateStep.tsx:232-236. The unsubscribe function is always\n * called on resolution, timeout, or error — no listener leaks.\n *\n * @param peer - Injected extension client.\n * @param platform - The platform handed to `authenticate()` (e.g. 'zelle').\n * Echoed back when the extension could not load a template.\n * @param actionType - The capture action type (e.g. 'transfer_venmo'). Its\n * parent is echoed back when the template DID load. Both are accepted; see\n * the guard below.\n * @param timeoutMs - Reject after this long with no matching message.\n */\nfunction awaitMetadata(\n peer: PeerClient,\n platform: string,\n actionType: string,\n timeoutMs: number,\n): Promise<MetadataResponse> {\n return new Promise<MetadataResponse>((resolve, reject) => {\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n\n const unsub = peer.onMetadataMessage((data) => {\n // The extension echoes the platform from one of two places:\n //\n // - Template loaded (success): the TEMPLATE's own `metadata.platform`.\n // - Template fetch failed: there is no template, so it echoes the\n // platform we passed, alongside an errorMessage such as\n // 'Failed to fetch provider template: 404'.\n //\n // With hosted templates the two agree (generic Zelle: 'zelle' both\n // ways). Accepting either still guards against a template whose\n // metadata.platform is the parent rail rather than the platform we\n // passed, so a successful capture is never dropped into a 60s timeout.\n const echoesCapturePlatform = data.platform === platform\n const echoesActionTypeParent = `transfer_${data.platform}` === actionType\n if (echoesCapturePlatform || echoesActionTypeParent) {\n clearTimeout(timeoutId)\n unsub()\n resolve(data)\n }\n })\n\n timeoutId = setTimeout(() => {\n unsub()\n reject(new Error(`Enclave capture timed out for platform \"${platform}\"`))\n }, timeoutMs)\n })\n}\n\nexport function createExtensionEnclaveAdapter(config: ExtensionEnclaveAdapterConfig): CaptureAdapter {\n const timeoutMs = config.timeoutMs ?? 60000\n\n return {\n kind: 'extension-enclave',\n\n async authenticateAndCapture(\n request: CaptureRequest,\n options?: { selectTransaction?: SelectTransaction },\n ): Promise<CaptureHandle> {\n const peer = config.getPeer()\n if (!peer) {\n throw new EnclaveCaptureUnavailable(request.platform)\n }\n\n // Ensure the extension is connected BEFORE authenticate. A cold\n // authenticate() on a fresh page returns errorMessage \"Peer extension\n // connection required\" with no buyerTeeCapture — which trips the\n // enclave-unavailable path. The legacy AuthenticateStep connects first\n // (and onramp-llm.md §3 `ensurePeerReady` requires it); the adapter must\n // too. This is why a warm local tab worked but a fresh prod session failed.\n const status = await peer.checkConnectionStatus()\n if (status !== 'connected') {\n await peer.requestConnection()\n }\n\n // Register the listener BEFORE authenticate fires to avoid a race where\n // the extension posts metadata before the listener is attached.\n const metadataPromise = awaitMetadata(peer, request.platform, request.actionType, timeoutMs)\n await peer.authenticate({\n actionType: request.actionType,\n platform: request.platform,\n captureMode: 'buyerTee',\n // Required for the extension to actually perform the Buyer TEE capture\n // (zkp2p 0.6.0 spec §7). Omitting these is why non-Revolut rails got\n // \"no buyerTee capture\" and dead-ended on the retired Reclaim path.\n attestationServiceUrl: config.attestationServiceUrl ?? DEFAULT_ATTESTATION_SERVICE_URL,\n // The attestation route equals the capture pair for every rail today\n // (generic Zelle included: zelle/transfer_zelle_chase). No template is\n // passed: extension 0.6.7 always fetches it from api.zkp2p.xyz and\n // ignores a page-supplied one, so the capture pair must be hosted.\n attestationActionType: request.attestationActionType ?? request.actionType,\n attestationPlatform: request.attestationPlatform ?? request.platform,\n })\n const response = await metadataPromise\n\n // No buyerTee capture ⇒ the extension didn't produce an encrypted enclave\n // session. Log WHY before failing — the extension may have surfaced an\n // errorMessage, or returned metadata-only (no capture) for this platform.\n if (!response.buyerTeeCapture) {\n const errorMessage = (response as { errorMessage?: unknown }).errorMessage\n console.warn(\n '[extensionEnclave] no buyerTeeCapture for',\n request.platform,\n '— response keys:', Object.keys(response),\n '| errorMessage:', errorMessage,\n '| metadata rows:', Array.isArray(response.metadata) ? response.metadata.length : '(none)',\n '| buyerTeeCapture:', response.buyerTeeCapture,\n )\n // Two distinct failures share the \"no capture\" shape:\n // - The extension surfaced an `errorMessage` ⇒ it ATTEMPTED the capture\n // but something failed (most often: it could not reach the\n // attestation enclave — a buyer-side network / firewall / ad-blocker\n // issue). Surface that as a retryable EnclaveCaptureFailed carrying\n // the raw reason, so the user sees an actionable message instead of\n // \"provider unsupported\".\n // - No `errorMessage` ⇒ the provider produced no capture path at all\n // (non-migrated). That stays EnclaveCaptureUnavailable, the\n // Reclaim-fallback signal.\n if (typeof errorMessage === 'string' && errorMessage.trim().length > 0) {\n throw new EnclaveCaptureFailed(request.platform, errorMessage)\n }\n throw new EnclaveCaptureUnavailable(request.platform)\n }\n\n const material: EnclaveMaterial = {\n encryptedSessionMaterial: response.buyerTeeCapture.encryptedSessionMaterial,\n payments: await resolvePaymentsToTry({\n match: matchPayment(response.metadata, request),\n history: response.metadata,\n selectTransaction: options?.selectTransaction,\n }),\n }\n return { kind: 'extension-enclave', material }\n },\n\n async attest(handle: CaptureHandle, request: CaptureRequest): Promise<VerifiedAttestation> {\n if (handle.kind !== 'extension-enclave') {\n throw new Error(\n `ExtensionEnclaveAdapter.attest received a handle of kind \"${handle.kind}\"; expected \"extension-enclave\".`,\n )\n }\n const material = handle.material as EnclaveMaterial\n // Try each candidate payment in order. A payment that already settled an\n // order is refused by the gate (PaymentAlreadyUsedError) and skipped;\n // any other failure stops here and surfaces as-is.\n let alreadyUsed: PaymentAlreadyUsedError | undefined\n for (const payment of material.payments) {\n try {\n const attestation = await attestPayment({ payment, material, request })\n // verificationData = enclave signer (SubmitStep.tsx:473-474).\n return { attestation, verificationData: attestation.signer as Hex }\n } catch (error) {\n if (!(error instanceof PaymentAlreadyUsedError)) throw error\n alreadyUsed = error\n }\n }\n throw alreadyUsed ?? new Error('No bank payment was captured for this order.')\n },\n }\n\n /** Attest one candidate payment through the gate. */\n function attestPayment({\n payment,\n material,\n request,\n }: {\n payment: PaymentToTry\n material: EnclaveMaterial\n request: CaptureRequest\n }) {\n // The selected row's params ARE the attestation params (spec §5,\n // buildBuyerTeeProof) and MUST be forwarded — dropping them is why the\n // enclave 400s with \"Invalid <provider> query params\". The metadata\n // `index` is then merged in ONLY for providers that expect it (Venmo /\n // Cash App / Revolut / Zelle) and omitted for the rest (PayPal / Wise /\n // Monzo / Chime). See providerRequiresMetadataIndex.\n const params = {\n ...(payment.params ?? {}),\n ...(providerRequiresMetadataIndex(request.provider as ProviderKey | SubProviderKey)\n ? { index: payment.index }\n : {}),\n }\n return requestBuyerTeeAttestation({\n attestationBaseUrl: config.attestationBaseUrl,\n platform: request.attestationPlatform ?? request.platform,\n actionType: request.attestationActionType ?? request.actionType,\n encryptedSessionMaterial: material.encryptedSessionMaterial,\n params,\n verifyingContract: request.verifyingContract,\n intent: request.intent,\n })\n }\n}\n\n// Re-narrow export so consumers can validate proof shapes if they bypass the\n// adapter (kept for parity with the live SubmitStep check, SubmitStep.tsx:206).\nexport { asBuyerTeeProof }\n"]}
@@ -1,3 +1,3 @@
1
- export { DEFAULT_ATTESTATION_SERVICE_URL, asBuyerTeeProof, createExtensionEnclaveAdapter } from '../chunk-AMJXMSDV.js';
1
+ export { DEFAULT_ATTESTATION_SERVICE_URL, asBuyerTeeProof, createExtensionEnclaveAdapter } from '../chunk-WSLUOLBW.js';
2
2
  //# sourceMappingURL=extensionEnclave.js.map
3
3
  //# sourceMappingURL=extensionEnclave.js.map
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ // src/capability.ts
4
+ var MINIMUM_EXTENSION_VERSION = "0.6.0";
5
+ var DEFAULT_INJECTION_WAIT_MS = 1500;
6
+ var MOBILE_USER_AGENT_PATTERN = /Android|iPhone|iPad|iPod|Mobile|Windows Phone|IEMobile|BlackBerry|Opera Mini/i;
7
+ var detectMobileDevice = ({ userAgent, maxTouchPoints, platform }) => {
8
+ if (MOBILE_USER_AGENT_PATTERN.test(userAgent)) return true;
9
+ return platform === "MacIntel" && maxTouchPoints > 0;
10
+ };
11
+ var parseVersionParts = (version) => {
12
+ const parts = version.trim().replace(/^v/i, "").split(".");
13
+ return [0, 1, 2].map((index) => {
14
+ const parsed = Number.parseInt(parts[index] ?? "", 10);
15
+ return Number.isNaN(parsed) ? 0 : parsed;
16
+ });
17
+ };
18
+ var isVersionAtLeast = ({ current, minimum }) => {
19
+ const currentParts = parseVersionParts(current);
20
+ const minimumParts = parseVersionParts(minimum);
21
+ const firstDifference = currentParts.findIndex((part, index) => part !== minimumParts[index]);
22
+ if (firstDifference === -1) return true;
23
+ return currentParts[firstDifference] > minimumParts[firstDifference];
24
+ };
25
+ var resolveProofCapability = ({
26
+ isMobileDevice,
27
+ isPeerPresent,
28
+ hasInjectionWaitElapsed,
29
+ injection,
30
+ version
31
+ }) => {
32
+ if (isMobileDevice) return { status: "mobile" };
33
+ if (!isPeerPresent && injection === "not-guaranteed") return { status: "unknown" };
34
+ if (!isPeerPresent && !hasInjectionWaitElapsed) return { status: "checking" };
35
+ if (!isPeerPresent) return { status: "no-extension" };
36
+ if (version === void 0) return { status: "checking" };
37
+ if (version === null) return { status: "unknown" };
38
+ if (!isVersionAtLeast({ current: version, minimum: MINIMUM_EXTENSION_VERSION })) {
39
+ return { status: "outdated", version };
40
+ }
41
+ return { status: "ready", version };
42
+ };
43
+
44
+ exports.DEFAULT_INJECTION_WAIT_MS = DEFAULT_INJECTION_WAIT_MS;
45
+ exports.MINIMUM_EXTENSION_VERSION = MINIMUM_EXTENSION_VERSION;
46
+ exports.detectMobileDevice = detectMobileDevice;
47
+ exports.isVersionAtLeast = isVersionAtLeast;
48
+ exports.resolveProofCapability = resolveProofCapability;
49
+ //# sourceMappingURL=capability.cjs.map
50
+ //# sourceMappingURL=capability.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/capability.ts"],"names":[],"mappings":";;;AAaO,IAAM,yBAAA,GAA4B;AAGlC,IAAM,yBAAA,GAA4B;AA0CzC,IAAM,yBAAA,GAA4B,+EAAA;AAW3B,IAAM,qBAAqB,CAAC,EAAE,SAAA,EAAW,cAAA,EAAgB,UAAS,KAAoC;AAC3G,EAAA,IAAI,yBAAA,CAA0B,IAAA,CAAK,SAAS,CAAA,EAAG,OAAO,IAAA;AACtD,EAAA,OAAO,QAAA,KAAa,cAAc,cAAA,GAAiB,CAAA;AACrD;AAGA,IAAM,iBAAA,GAAoB,CAAC,OAAA,KAA8B;AACvD,EAAA,MAAM,KAAA,GAAQ,QAAQ,IAAA,EAAK,CAAE,QAAQ,KAAA,EAAO,EAAE,CAAA,CAAE,KAAA,CAAM,GAAG,CAAA;AACzD,EAAA,OAAO,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,KAAA,KAAU;AAC9B,IAAA,MAAM,SAAS,MAAA,CAAO,QAAA,CAAS,MAAM,KAAK,CAAA,IAAK,IAAI,EAAE,CAAA;AACrD,IAAA,OAAO,MAAA,CAAO,KAAA,CAAM,MAAM,CAAA,GAAI,CAAA,GAAI,MAAA;AAAA,EACpC,CAAC,CAAA;AACH,CAAA;AAUO,IAAM,gBAAA,GAAmB,CAAC,EAAE,OAAA,EAAS,SAAQ,KAAqD;AACvG,EAAA,MAAM,YAAA,GAAe,kBAAkB,OAAO,CAAA;AAC9C,EAAA,MAAM,YAAA,GAAe,kBAAkB,OAAO,CAAA;AAC9C,EAAA,MAAM,eAAA,GAAkB,aAAa,SAAA,CAAU,CAAC,MAAM,KAAA,KAAU,IAAA,KAAS,YAAA,CAAa,KAAK,CAAC,CAAA;AAC5F,EAAA,IAAI,eAAA,KAAoB,IAAI,OAAO,IAAA;AACnC,EAAA,OAAO,YAAA,CAAa,eAAe,CAAA,GAAI,YAAA,CAAa,eAAe,CAAA;AACrE;AAaO,IAAM,yBAAyB,CAAC;AAAA,EACrC,cAAA;AAAA,EACA,aAAA;AAAA,EACA,uBAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA,KAA+C;AAC7C,EAAA,IAAI,cAAA,EAAgB,OAAO,EAAE,MAAA,EAAQ,QAAA,EAAS;AAC9C,EAAA,IAAI,CAAC,aAAA,IAAiB,SAAA,KAAc,kBAAkB,OAAO,EAAE,QAAQ,SAAA,EAAU;AACjF,EAAA,IAAI,CAAC,aAAA,IAAiB,CAAC,yBAAyB,OAAO,EAAE,QAAQ,UAAA,EAAW;AAC5E,EAAA,IAAI,CAAC,aAAA,EAAe,OAAO,EAAE,QAAQ,cAAA,EAAe;AACpD,EAAA,IAAI,OAAA,KAAY,MAAA,EAAW,OAAO,EAAE,QAAQ,UAAA,EAAW;AACvD,EAAA,IAAI,OAAA,KAAY,IAAA,EAAM,OAAO,EAAE,QAAQ,SAAA,EAAU;AACjD,EAAA,IAAI,CAAC,iBAAiB,EAAE,OAAA,EAAS,SAAS,OAAA,EAAS,yBAAA,EAA2B,CAAA,EAAG;AAC/E,IAAA,OAAO,EAAE,MAAA,EAAQ,UAAA,EAAY,OAAA,EAAQ;AAAA,EACvC;AACA,EAAA,OAAO,EAAE,MAAA,EAAQ,OAAA,EAAS,OAAA,EAAQ;AACpC","file":"capability.cjs","sourcesContent":["/**\n * @fileoverview Pure functions that decide whether this browser can make a\n * Provex payment proof.\n *\n * A browser can make a proof when it is a desktop browser and the Peer\n * extension (version 0.6.0 or later) injects `window.peer`. The functions here\n * hold no state and read no globals, so every branch can be tested directly.\n */\n\n/**\n * The first extension version that works with the enclave-only proof path.\n * Version 0.6.0 removed `generateProof`.\n */\nexport const MINIMUM_EXTENSION_VERSION = '0.6.0'\n\n/** Default wait, in milliseconds, before a missing `window.peer` counts as missing. */\nexport const DEFAULT_INJECTION_WAIT_MS = 1_500\n\n/**\n * Whether the page can count on the extension to inject `window.peer`.\n * - `guaranteed`: the page runs on an origin where the extension injects.\n * - `not-guaranteed`: third-party origin or iframe. No `window.peer` proves nothing.\n */\nexport type InjectionGuarantee = 'guaranteed' | 'not-guaranteed'\n\n/** Browser facts used to detect a mobile device. */\nexport interface MobileDeviceSignals {\n userAgent: string\n maxTouchPoints: number\n platform: string\n}\n\n/** The result of a proof capability check. */\nexport type ProofCapability =\n | { status: 'mobile' }\n | { status: 'checking' }\n | { status: 'no-extension' }\n | { status: 'unknown' }\n | { status: 'outdated'; version: string }\n | { status: 'ready'; version: string }\n\n/** The facts that `resolveProofCapability` combines. */\nexport interface ProofCapabilitySignals {\n /** True when the device is a phone or tablet. */\n isMobileDevice: boolean\n /** True once `window.peer` exists. */\n isPeerPresent: boolean\n /** True once the wait for injection has passed. */\n hasInjectionWaitElapsed: boolean\n /** How the page gets the extension. */\n injection: InjectionGuarantee\n /**\n * The extension version. `undefined` while the lookup runs.\n * `null` when the lookup failed or returned nothing.\n */\n version: string | null | undefined\n}\n\nconst MOBILE_USER_AGENT_PATTERN = /Android|iPhone|iPad|iPod|Mobile|Windows Phone|IEMobile|BlackBerry|Opera Mini/i\n\n/**\n * Detect a phone or tablet from the user agent and touch support.\n * It does not look at the viewport width. A narrow desktop window is not mobile.\n * iPadOS 13 and later sends a desktop Mac user agent, so a \"MacIntel\"\n * platform with touch points counts as a tablet. Desktop Macs report zero.\n *\n * @param signals - user agent, touch point count, and platform string\n * @returns true when the device is mobile\n */\nexport const detectMobileDevice = ({ userAgent, maxTouchPoints, platform }: MobileDeviceSignals): boolean => {\n if (MOBILE_USER_AGENT_PATTERN.test(userAgent)) return true\n return platform === 'MacIntel' && maxTouchPoints > 0\n}\n\n/** Read up to three numeric parts. A missing or invalid part counts as zero. */\nconst parseVersionParts = (version: string): number[] => {\n const parts = version.trim().replace(/^v/i, '').split('.')\n return [0, 1, 2].map((index) => {\n const parsed = Number.parseInt(parts[index] ?? '', 10)\n return Number.isNaN(parsed) ? 0 : parsed\n })\n}\n\n/**\n * Compare two dotted versions numerically, so 0.10.0 is later than 0.6.0.\n * Missing parts count as zero, so \"0.6\" equals \"0.6.0\".\n *\n * @param options.current - the version to test\n * @param options.minimum - the lowest accepted version\n * @returns true when `current` is the same as or later than `minimum`\n */\nexport const isVersionAtLeast = ({ current, minimum }: { current: string; minimum: string }): boolean => {\n const currentParts = parseVersionParts(current)\n const minimumParts = parseVersionParts(minimum)\n const firstDifference = currentParts.findIndex((part, index) => part !== minimumParts[index])\n if (firstDifference === -1) return true\n return currentParts[firstDifference] > minimumParts[firstDifference]\n}\n\n/**\n * Combine device, extension, and timing facts into one capability result.\n *\n * Off-origin pages cannot prove that the extension is missing, so with\n * `not-guaranteed` injection a missing `window.peer` gives `unknown`.\n * With `guaranteed` injection it gives `checking` until the wait passes,\n * then `no-extension`.\n *\n * @param signals - the facts to combine\n * @returns the discriminated capability result\n */\nexport const resolveProofCapability = ({\n isMobileDevice,\n isPeerPresent,\n hasInjectionWaitElapsed,\n injection,\n version,\n}: ProofCapabilitySignals): ProofCapability => {\n if (isMobileDevice) return { status: 'mobile' }\n if (!isPeerPresent && injection === 'not-guaranteed') return { status: 'unknown' }\n if (!isPeerPresent && !hasInjectionWaitElapsed) return { status: 'checking' }\n if (!isPeerPresent) return { status: 'no-extension' }\n if (version === undefined) return { status: 'checking' }\n if (version === null) return { status: 'unknown' }\n if (!isVersionAtLeast({ current: version, minimum: MINIMUM_EXTENSION_VERSION })) {\n return { status: 'outdated', version }\n }\n return { status: 'ready', version }\n}\n"]}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * @fileoverview Pure functions that decide whether this browser can make a
3
+ * Provex payment proof.
4
+ *
5
+ * A browser can make a proof when it is a desktop browser and the Peer
6
+ * extension (version 0.6.0 or later) injects `window.peer`. The functions here
7
+ * hold no state and read no globals, so every branch can be tested directly.
8
+ */
9
+ /**
10
+ * The first extension version that works with the enclave-only proof path.
11
+ * Version 0.6.0 removed `generateProof`.
12
+ */
13
+ declare const MINIMUM_EXTENSION_VERSION = "0.6.0";
14
+ /** Default wait, in milliseconds, before a missing `window.peer` counts as missing. */
15
+ declare const DEFAULT_INJECTION_WAIT_MS = 1500;
16
+ /**
17
+ * Whether the page can count on the extension to inject `window.peer`.
18
+ * - `guaranteed`: the page runs on an origin where the extension injects.
19
+ * - `not-guaranteed`: third-party origin or iframe. No `window.peer` proves nothing.
20
+ */
21
+ type InjectionGuarantee = 'guaranteed' | 'not-guaranteed';
22
+ /** Browser facts used to detect a mobile device. */
23
+ interface MobileDeviceSignals {
24
+ userAgent: string;
25
+ maxTouchPoints: number;
26
+ platform: string;
27
+ }
28
+ /** The result of a proof capability check. */
29
+ type ProofCapability = {
30
+ status: 'mobile';
31
+ } | {
32
+ status: 'checking';
33
+ } | {
34
+ status: 'no-extension';
35
+ } | {
36
+ status: 'unknown';
37
+ } | {
38
+ status: 'outdated';
39
+ version: string;
40
+ } | {
41
+ status: 'ready';
42
+ version: string;
43
+ };
44
+ /** The facts that `resolveProofCapability` combines. */
45
+ interface ProofCapabilitySignals {
46
+ /** True when the device is a phone or tablet. */
47
+ isMobileDevice: boolean;
48
+ /** True once `window.peer` exists. */
49
+ isPeerPresent: boolean;
50
+ /** True once the wait for injection has passed. */
51
+ hasInjectionWaitElapsed: boolean;
52
+ /** How the page gets the extension. */
53
+ injection: InjectionGuarantee;
54
+ /**
55
+ * The extension version. `undefined` while the lookup runs.
56
+ * `null` when the lookup failed or returned nothing.
57
+ */
58
+ version: string | null | undefined;
59
+ }
60
+ /**
61
+ * Detect a phone or tablet from the user agent and touch support.
62
+ * It does not look at the viewport width. A narrow desktop window is not mobile.
63
+ * iPadOS 13 and later sends a desktop Mac user agent, so a "MacIntel"
64
+ * platform with touch points counts as a tablet. Desktop Macs report zero.
65
+ *
66
+ * @param signals - user agent, touch point count, and platform string
67
+ * @returns true when the device is mobile
68
+ */
69
+ declare const detectMobileDevice: ({ userAgent, maxTouchPoints, platform }: MobileDeviceSignals) => boolean;
70
+ /**
71
+ * Compare two dotted versions numerically, so 0.10.0 is later than 0.6.0.
72
+ * Missing parts count as zero, so "0.6" equals "0.6.0".
73
+ *
74
+ * @param options.current - the version to test
75
+ * @param options.minimum - the lowest accepted version
76
+ * @returns true when `current` is the same as or later than `minimum`
77
+ */
78
+ declare const isVersionAtLeast: ({ current, minimum }: {
79
+ current: string;
80
+ minimum: string;
81
+ }) => boolean;
82
+ /**
83
+ * Combine device, extension, and timing facts into one capability result.
84
+ *
85
+ * Off-origin pages cannot prove that the extension is missing, so with
86
+ * `not-guaranteed` injection a missing `window.peer` gives `unknown`.
87
+ * With `guaranteed` injection it gives `checking` until the wait passes,
88
+ * then `no-extension`.
89
+ *
90
+ * @param signals - the facts to combine
91
+ * @returns the discriminated capability result
92
+ */
93
+ declare const resolveProofCapability: ({ isMobileDevice, isPeerPresent, hasInjectionWaitElapsed, injection, version, }: ProofCapabilitySignals) => ProofCapability;
94
+
95
+ export { DEFAULT_INJECTION_WAIT_MS, type InjectionGuarantee, MINIMUM_EXTENSION_VERSION, type MobileDeviceSignals, type ProofCapability, type ProofCapabilitySignals, detectMobileDevice, isVersionAtLeast, resolveProofCapability };
@@ -0,0 +1,95 @@
1
+ /**
2
+ * @fileoverview Pure functions that decide whether this browser can make a
3
+ * Provex payment proof.
4
+ *
5
+ * A browser can make a proof when it is a desktop browser and the Peer
6
+ * extension (version 0.6.0 or later) injects `window.peer`. The functions here
7
+ * hold no state and read no globals, so every branch can be tested directly.
8
+ */
9
+ /**
10
+ * The first extension version that works with the enclave-only proof path.
11
+ * Version 0.6.0 removed `generateProof`.
12
+ */
13
+ declare const MINIMUM_EXTENSION_VERSION = "0.6.0";
14
+ /** Default wait, in milliseconds, before a missing `window.peer` counts as missing. */
15
+ declare const DEFAULT_INJECTION_WAIT_MS = 1500;
16
+ /**
17
+ * Whether the page can count on the extension to inject `window.peer`.
18
+ * - `guaranteed`: the page runs on an origin where the extension injects.
19
+ * - `not-guaranteed`: third-party origin or iframe. No `window.peer` proves nothing.
20
+ */
21
+ type InjectionGuarantee = 'guaranteed' | 'not-guaranteed';
22
+ /** Browser facts used to detect a mobile device. */
23
+ interface MobileDeviceSignals {
24
+ userAgent: string;
25
+ maxTouchPoints: number;
26
+ platform: string;
27
+ }
28
+ /** The result of a proof capability check. */
29
+ type ProofCapability = {
30
+ status: 'mobile';
31
+ } | {
32
+ status: 'checking';
33
+ } | {
34
+ status: 'no-extension';
35
+ } | {
36
+ status: 'unknown';
37
+ } | {
38
+ status: 'outdated';
39
+ version: string;
40
+ } | {
41
+ status: 'ready';
42
+ version: string;
43
+ };
44
+ /** The facts that `resolveProofCapability` combines. */
45
+ interface ProofCapabilitySignals {
46
+ /** True when the device is a phone or tablet. */
47
+ isMobileDevice: boolean;
48
+ /** True once `window.peer` exists. */
49
+ isPeerPresent: boolean;
50
+ /** True once the wait for injection has passed. */
51
+ hasInjectionWaitElapsed: boolean;
52
+ /** How the page gets the extension. */
53
+ injection: InjectionGuarantee;
54
+ /**
55
+ * The extension version. `undefined` while the lookup runs.
56
+ * `null` when the lookup failed or returned nothing.
57
+ */
58
+ version: string | null | undefined;
59
+ }
60
+ /**
61
+ * Detect a phone or tablet from the user agent and touch support.
62
+ * It does not look at the viewport width. A narrow desktop window is not mobile.
63
+ * iPadOS 13 and later sends a desktop Mac user agent, so a "MacIntel"
64
+ * platform with touch points counts as a tablet. Desktop Macs report zero.
65
+ *
66
+ * @param signals - user agent, touch point count, and platform string
67
+ * @returns true when the device is mobile
68
+ */
69
+ declare const detectMobileDevice: ({ userAgent, maxTouchPoints, platform }: MobileDeviceSignals) => boolean;
70
+ /**
71
+ * Compare two dotted versions numerically, so 0.10.0 is later than 0.6.0.
72
+ * Missing parts count as zero, so "0.6" equals "0.6.0".
73
+ *
74
+ * @param options.current - the version to test
75
+ * @param options.minimum - the lowest accepted version
76
+ * @returns true when `current` is the same as or later than `minimum`
77
+ */
78
+ declare const isVersionAtLeast: ({ current, minimum }: {
79
+ current: string;
80
+ minimum: string;
81
+ }) => boolean;
82
+ /**
83
+ * Combine device, extension, and timing facts into one capability result.
84
+ *
85
+ * Off-origin pages cannot prove that the extension is missing, so with
86
+ * `not-guaranteed` injection a missing `window.peer` gives `unknown`.
87
+ * With `guaranteed` injection it gives `checking` until the wait passes,
88
+ * then `no-extension`.
89
+ *
90
+ * @param signals - the facts to combine
91
+ * @returns the discriminated capability result
92
+ */
93
+ declare const resolveProofCapability: ({ isMobileDevice, isPeerPresent, hasInjectionWaitElapsed, injection, version, }: ProofCapabilitySignals) => ProofCapability;
94
+
95
+ export { DEFAULT_INJECTION_WAIT_MS, type InjectionGuarantee, MINIMUM_EXTENSION_VERSION, type MobileDeviceSignals, type ProofCapability, type ProofCapabilitySignals, detectMobileDevice, isVersionAtLeast, resolveProofCapability };
@@ -0,0 +1,3 @@
1
+ export { DEFAULT_INJECTION_WAIT_MS, MINIMUM_EXTENSION_VERSION, detectMobileDevice, isVersionAtLeast, resolveProofCapability } from './chunk-JMVW4H4H.js';
2
+ //# sourceMappingURL=capability.js.map
3
+ //# sourceMappingURL=capability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"capability.js"}
@@ -1,4 +1,4 @@
1
- import { useZKTLS } from './chunk-TAS2MB4S.js';
1
+ import { useZKTLS } from './chunk-JU2T2TGY.js';
2
2
  import { createContext, useContext } from 'react';
3
3
  import { jsx } from 'react/jsx-runtime';
4
4
 
@@ -16,5 +16,5 @@ function ZKTLSProvider({ children }) {
16
16
  }
17
17
 
18
18
  export { ZKTLSContext, ZKTLSProvider, useZKTLSContext };
19
- //# sourceMappingURL=chunk-T42E7EM7.js.map
20
- //# sourceMappingURL=chunk-T42E7EM7.js.map
19
+ //# sourceMappingURL=chunk-B7RU2WKQ.js.map
20
+ //# sourceMappingURL=chunk-B7RU2WKQ.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ZKTLSContext.tsx"],"names":[],"mappings":";;;;AAiBO,IAAM,YAAA,GAAe,cAA4C,MAAS;AAY1E,SAAS,eAAA,GAAkB;AAChC,EAAA,MAAM,OAAA,GAAU,WAAW,YAAY,CAAA;AACvC,EAAA,IAAI,YAAY,MAAA,EAAW;AACzB,IAAA,MAAM,IAAI,MAAM,qDAAqD,CAAA;AAAA,EACvE;AACA,EAAA,OAAO,OAAA;AACT;AAEO,SAAS,aAAA,CAAc,EAAE,QAAA,EAAS,EAAuB;AAC9D,EAAA,MAAM,YAAY,QAAA,EAAS;AAE3B,EAAA,2BACG,YAAA,CAAa,QAAA,EAAb,EAAsB,KAAA,EAAO,WAC3B,QAAA,EACH,CAAA;AAEJ","file":"chunk-T42E7EM7.js","sourcesContent":["/**\n * @fileoverview React context for PeerAuth extension state.\n *\n * Provides global access to the useZKTLS hook state throughout the\n * component tree, avoiding prop drilling for extension status.\n *\n * @see {@link useZKTLS} for the underlying hook\n */\n\nimport { createContext, useContext, type ReactNode } from 'react'\nimport { useZKTLS } from './useZKTLS'\n\n/**\n * Context type derived from useZKTLS hook return value.\n */\ntype ZKTLSContextType = ReturnType<typeof useZKTLS>\n\nexport const ZKTLSContext = createContext<ZKTLSContextType | undefined>(undefined)\n\ninterface ZKTLSProviderProps {\n children: ReactNode\n}\n\n/**\n * Hook to consume the ZKTLS context.\n * Must be used within a ZKTLSProvider.\n *\n * @throws Error if used outside of ZKTLSProvider\n */\nexport function useZKTLSContext() {\n const context = useContext(ZKTLSContext)\n if (context === undefined) {\n throw new Error('useZKTLSContext must be used within a ZKTLSProvider')\n }\n return context\n}\n\nexport function ZKTLSProvider({ children }: ZKTLSProviderProps) {\n const zktlsHook = useZKTLS()\n\n return (\n <ZKTLSContext.Provider value={zktlsHook}>\n {children}\n </ZKTLSContext.Provider>\n )\n}\n"]}
1
+ {"version":3,"sources":["../src/ZKTLSContext.tsx"],"names":[],"mappings":";;;;AAiBO,IAAM,YAAA,GAAe,cAA4C,MAAS;AAY1E,SAAS,eAAA,GAAkB;AAChC,EAAA,MAAM,OAAA,GAAU,WAAW,YAAY,CAAA;AACvC,EAAA,IAAI,YAAY,MAAA,EAAW;AACzB,IAAA,MAAM,IAAI,MAAM,qDAAqD,CAAA;AAAA,EACvE;AACA,EAAA,OAAO,OAAA;AACT;AAEO,SAAS,aAAA,CAAc,EAAE,QAAA,EAAS,EAAuB;AAC9D,EAAA,MAAM,YAAY,QAAA,EAAS;AAE3B,EAAA,2BACG,YAAA,CAAa,QAAA,EAAb,EAAsB,KAAA,EAAO,WAC3B,QAAA,EACH,CAAA;AAEJ","file":"chunk-B7RU2WKQ.js","sourcesContent":["/**\n * @fileoverview React context for PeerAuth extension state.\n *\n * Provides global access to the useZKTLS hook state throughout the\n * component tree, avoiding prop drilling for extension status.\n *\n * @see {@link useZKTLS} for the underlying hook\n */\n\nimport { createContext, useContext, type ReactNode } from 'react'\nimport { useZKTLS } from './useZKTLS'\n\n/**\n * Context type derived from useZKTLS hook return value.\n */\ntype ZKTLSContextType = ReturnType<typeof useZKTLS>\n\nexport const ZKTLSContext = createContext<ZKTLSContextType | undefined>(undefined)\n\ninterface ZKTLSProviderProps {\n children: ReactNode\n}\n\n/**\n * Hook to consume the ZKTLS context.\n * Must be used within a ZKTLSProvider.\n *\n * @throws Error if used outside of ZKTLSProvider\n */\nexport function useZKTLSContext() {\n const context = useContext(ZKTLSContext)\n if (context === undefined) {\n throw new Error('useZKTLSContext must be used within a ZKTLSProvider')\n }\n return context\n}\n\nexport function ZKTLSProvider({ children }: ZKTLSProviderProps) {\n const zktlsHook = useZKTLS()\n\n return (\n <ZKTLSContext.Provider value={zktlsHook}>\n {children}\n </ZKTLSContext.Provider>\n )\n}\n"]}
@@ -0,0 +1,44 @@
1
+ // src/capability.ts
2
+ var MINIMUM_EXTENSION_VERSION = "0.6.0";
3
+ var DEFAULT_INJECTION_WAIT_MS = 1500;
4
+ var MOBILE_USER_AGENT_PATTERN = /Android|iPhone|iPad|iPod|Mobile|Windows Phone|IEMobile|BlackBerry|Opera Mini/i;
5
+ var detectMobileDevice = ({ userAgent, maxTouchPoints, platform }) => {
6
+ if (MOBILE_USER_AGENT_PATTERN.test(userAgent)) return true;
7
+ return platform === "MacIntel" && maxTouchPoints > 0;
8
+ };
9
+ var parseVersionParts = (version) => {
10
+ const parts = version.trim().replace(/^v/i, "").split(".");
11
+ return [0, 1, 2].map((index) => {
12
+ const parsed = Number.parseInt(parts[index] ?? "", 10);
13
+ return Number.isNaN(parsed) ? 0 : parsed;
14
+ });
15
+ };
16
+ var isVersionAtLeast = ({ current, minimum }) => {
17
+ const currentParts = parseVersionParts(current);
18
+ const minimumParts = parseVersionParts(minimum);
19
+ const firstDifference = currentParts.findIndex((part, index) => part !== minimumParts[index]);
20
+ if (firstDifference === -1) return true;
21
+ return currentParts[firstDifference] > minimumParts[firstDifference];
22
+ };
23
+ var resolveProofCapability = ({
24
+ isMobileDevice,
25
+ isPeerPresent,
26
+ hasInjectionWaitElapsed,
27
+ injection,
28
+ version
29
+ }) => {
30
+ if (isMobileDevice) return { status: "mobile" };
31
+ if (!isPeerPresent && injection === "not-guaranteed") return { status: "unknown" };
32
+ if (!isPeerPresent && !hasInjectionWaitElapsed) return { status: "checking" };
33
+ if (!isPeerPresent) return { status: "no-extension" };
34
+ if (version === void 0) return { status: "checking" };
35
+ if (version === null) return { status: "unknown" };
36
+ if (!isVersionAtLeast({ current: version, minimum: MINIMUM_EXTENSION_VERSION })) {
37
+ return { status: "outdated", version };
38
+ }
39
+ return { status: "ready", version };
40
+ };
41
+
42
+ export { DEFAULT_INJECTION_WAIT_MS, MINIMUM_EXTENSION_VERSION, detectMobileDevice, isVersionAtLeast, resolveProofCapability };
43
+ //# sourceMappingURL=chunk-JMVW4H4H.js.map
44
+ //# sourceMappingURL=chunk-JMVW4H4H.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/capability.ts"],"names":[],"mappings":";AAaO,IAAM,yBAAA,GAA4B;AAGlC,IAAM,yBAAA,GAA4B;AA0CzC,IAAM,yBAAA,GAA4B,+EAAA;AAW3B,IAAM,qBAAqB,CAAC,EAAE,SAAA,EAAW,cAAA,EAAgB,UAAS,KAAoC;AAC3G,EAAA,IAAI,yBAAA,CAA0B,IAAA,CAAK,SAAS,CAAA,EAAG,OAAO,IAAA;AACtD,EAAA,OAAO,QAAA,KAAa,cAAc,cAAA,GAAiB,CAAA;AACrD;AAGA,IAAM,iBAAA,GAAoB,CAAC,OAAA,KAA8B;AACvD,EAAA,MAAM,KAAA,GAAQ,QAAQ,IAAA,EAAK,CAAE,QAAQ,KAAA,EAAO,EAAE,CAAA,CAAE,KAAA,CAAM,GAAG,CAAA;AACzD,EAAA,OAAO,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,KAAA,KAAU;AAC9B,IAAA,MAAM,SAAS,MAAA,CAAO,QAAA,CAAS,MAAM,KAAK,CAAA,IAAK,IAAI,EAAE,CAAA;AACrD,IAAA,OAAO,MAAA,CAAO,KAAA,CAAM,MAAM,CAAA,GAAI,CAAA,GAAI,MAAA;AAAA,EACpC,CAAC,CAAA;AACH,CAAA;AAUO,IAAM,gBAAA,GAAmB,CAAC,EAAE,OAAA,EAAS,SAAQ,KAAqD;AACvG,EAAA,MAAM,YAAA,GAAe,kBAAkB,OAAO,CAAA;AAC9C,EAAA,MAAM,YAAA,GAAe,kBAAkB,OAAO,CAAA;AAC9C,EAAA,MAAM,eAAA,GAAkB,aAAa,SAAA,CAAU,CAAC,MAAM,KAAA,KAAU,IAAA,KAAS,YAAA,CAAa,KAAK,CAAC,CAAA;AAC5F,EAAA,IAAI,eAAA,KAAoB,IAAI,OAAO,IAAA;AACnC,EAAA,OAAO,YAAA,CAAa,eAAe,CAAA,GAAI,YAAA,CAAa,eAAe,CAAA;AACrE;AAaO,IAAM,yBAAyB,CAAC;AAAA,EACrC,cAAA;AAAA,EACA,aAAA;AAAA,EACA,uBAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA,KAA+C;AAC7C,EAAA,IAAI,cAAA,EAAgB,OAAO,EAAE,MAAA,EAAQ,QAAA,EAAS;AAC9C,EAAA,IAAI,CAAC,aAAA,IAAiB,SAAA,KAAc,kBAAkB,OAAO,EAAE,QAAQ,SAAA,EAAU;AACjF,EAAA,IAAI,CAAC,aAAA,IAAiB,CAAC,yBAAyB,OAAO,EAAE,QAAQ,UAAA,EAAW;AAC5E,EAAA,IAAI,CAAC,aAAA,EAAe,OAAO,EAAE,QAAQ,cAAA,EAAe;AACpD,EAAA,IAAI,OAAA,KAAY,MAAA,EAAW,OAAO,EAAE,QAAQ,UAAA,EAAW;AACvD,EAAA,IAAI,OAAA,KAAY,IAAA,EAAM,OAAO,EAAE,QAAQ,SAAA,EAAU;AACjD,EAAA,IAAI,CAAC,iBAAiB,EAAE,OAAA,EAAS,SAAS,OAAA,EAAS,yBAAA,EAA2B,CAAA,EAAG;AAC/E,IAAA,OAAO,EAAE,MAAA,EAAQ,UAAA,EAAY,OAAA,EAAQ;AAAA,EACvC;AACA,EAAA,OAAO,EAAE,MAAA,EAAQ,OAAA,EAAS,OAAA,EAAQ;AACpC","file":"chunk-JMVW4H4H.js","sourcesContent":["/**\n * @fileoverview Pure functions that decide whether this browser can make a\n * Provex payment proof.\n *\n * A browser can make a proof when it is a desktop browser and the Peer\n * extension (version 0.6.0 or later) injects `window.peer`. The functions here\n * hold no state and read no globals, so every branch can be tested directly.\n */\n\n/**\n * The first extension version that works with the enclave-only proof path.\n * Version 0.6.0 removed `generateProof`.\n */\nexport const MINIMUM_EXTENSION_VERSION = '0.6.0'\n\n/** Default wait, in milliseconds, before a missing `window.peer` counts as missing. */\nexport const DEFAULT_INJECTION_WAIT_MS = 1_500\n\n/**\n * Whether the page can count on the extension to inject `window.peer`.\n * - `guaranteed`: the page runs on an origin where the extension injects.\n * - `not-guaranteed`: third-party origin or iframe. No `window.peer` proves nothing.\n */\nexport type InjectionGuarantee = 'guaranteed' | 'not-guaranteed'\n\n/** Browser facts used to detect a mobile device. */\nexport interface MobileDeviceSignals {\n userAgent: string\n maxTouchPoints: number\n platform: string\n}\n\n/** The result of a proof capability check. */\nexport type ProofCapability =\n | { status: 'mobile' }\n | { status: 'checking' }\n | { status: 'no-extension' }\n | { status: 'unknown' }\n | { status: 'outdated'; version: string }\n | { status: 'ready'; version: string }\n\n/** The facts that `resolveProofCapability` combines. */\nexport interface ProofCapabilitySignals {\n /** True when the device is a phone or tablet. */\n isMobileDevice: boolean\n /** True once `window.peer` exists. */\n isPeerPresent: boolean\n /** True once the wait for injection has passed. */\n hasInjectionWaitElapsed: boolean\n /** How the page gets the extension. */\n injection: InjectionGuarantee\n /**\n * The extension version. `undefined` while the lookup runs.\n * `null` when the lookup failed or returned nothing.\n */\n version: string | null | undefined\n}\n\nconst MOBILE_USER_AGENT_PATTERN = /Android|iPhone|iPad|iPod|Mobile|Windows Phone|IEMobile|BlackBerry|Opera Mini/i\n\n/**\n * Detect a phone or tablet from the user agent and touch support.\n * It does not look at the viewport width. A narrow desktop window is not mobile.\n * iPadOS 13 and later sends a desktop Mac user agent, so a \"MacIntel\"\n * platform with touch points counts as a tablet. Desktop Macs report zero.\n *\n * @param signals - user agent, touch point count, and platform string\n * @returns true when the device is mobile\n */\nexport const detectMobileDevice = ({ userAgent, maxTouchPoints, platform }: MobileDeviceSignals): boolean => {\n if (MOBILE_USER_AGENT_PATTERN.test(userAgent)) return true\n return platform === 'MacIntel' && maxTouchPoints > 0\n}\n\n/** Read up to three numeric parts. A missing or invalid part counts as zero. */\nconst parseVersionParts = (version: string): number[] => {\n const parts = version.trim().replace(/^v/i, '').split('.')\n return [0, 1, 2].map((index) => {\n const parsed = Number.parseInt(parts[index] ?? '', 10)\n return Number.isNaN(parsed) ? 0 : parsed\n })\n}\n\n/**\n * Compare two dotted versions numerically, so 0.10.0 is later than 0.6.0.\n * Missing parts count as zero, so \"0.6\" equals \"0.6.0\".\n *\n * @param options.current - the version to test\n * @param options.minimum - the lowest accepted version\n * @returns true when `current` is the same as or later than `minimum`\n */\nexport const isVersionAtLeast = ({ current, minimum }: { current: string; minimum: string }): boolean => {\n const currentParts = parseVersionParts(current)\n const minimumParts = parseVersionParts(minimum)\n const firstDifference = currentParts.findIndex((part, index) => part !== minimumParts[index])\n if (firstDifference === -1) return true\n return currentParts[firstDifference] > minimumParts[firstDifference]\n}\n\n/**\n * Combine device, extension, and timing facts into one capability result.\n *\n * Off-origin pages cannot prove that the extension is missing, so with\n * `not-guaranteed` injection a missing `window.peer` gives `unknown`.\n * With `guaranteed` injection it gives `checking` until the wait passes,\n * then `no-extension`.\n *\n * @param signals - the facts to combine\n * @returns the discriminated capability result\n */\nexport const resolveProofCapability = ({\n isMobileDevice,\n isPeerPresent,\n hasInjectionWaitElapsed,\n injection,\n version,\n}: ProofCapabilitySignals): ProofCapability => {\n if (isMobileDevice) return { status: 'mobile' }\n if (!isPeerPresent && injection === 'not-guaranteed') return { status: 'unknown' }\n if (!isPeerPresent && !hasInjectionWaitElapsed) return { status: 'checking' }\n if (!isPeerPresent) return { status: 'no-extension' }\n if (version === undefined) return { status: 'checking' }\n if (version === null) return { status: 'unknown' }\n if (!isVersionAtLeast({ current: version, minimum: MINIMUM_EXTENSION_VERSION })) {\n return { status: 'outdated', version }\n }\n return { status: 'ready', version }\n}\n"]}
@@ -0,0 +1,123 @@
1
+ import { useState, useEffect, useMemo } from 'react';
2
+
3
+ // src/useZKTLS.ts
4
+ var buildOnrampQuery = (params) => {
5
+ const search = new URLSearchParams();
6
+ Object.entries(params).forEach(([key, value]) => {
7
+ if (value === void 0 || value === null) return;
8
+ search.set(key, String(value));
9
+ });
10
+ return search.toString();
11
+ };
12
+ var extensionUrl = "https://chromewebstore.google.com/detail/ijpgccednehjpeclfcllnjjcmiohdjih";
13
+ var useZKTLS = () => {
14
+ const [isInitialized, setIsInitialized] = useState(false);
15
+ useEffect(() => {
16
+ const setup = () => {
17
+ setIsInitialized(true);
18
+ };
19
+ const eventName = "peer#initialized";
20
+ if (typeof window.peer !== "undefined") {
21
+ setup();
22
+ } else {
23
+ window.addEventListener(eventName, setup);
24
+ return () => {
25
+ window.removeEventListener(eventName, setup);
26
+ };
27
+ }
28
+ }, []);
29
+ return useMemo(() => {
30
+ return {
31
+ /** check if the PeerAuth extension is initialized */
32
+ isInitialized,
33
+ /** request a connection to the PeerAuth extension */
34
+ requestConnection: async () => {
35
+ if (!window.peer) return false;
36
+ return window.peer.requestConnection();
37
+ },
38
+ /**
39
+ * check the connection status of the PeerAuth extension
40
+ * @returns the connection status
41
+ */
42
+ checkConnectionStatus: async () => {
43
+ return await window.peer.checkConnectionStatus();
44
+ },
45
+ /** get the version of the PeerAuth extension */
46
+ getVersion: async () => {
47
+ return await window.peer?.getVersion();
48
+ },
49
+ /** authenticate with the PeerAuth extension */
50
+ authenticate: async (inputs) => {
51
+ let resolve;
52
+ const promise = new Promise((resolver) => {
53
+ resolve = resolver;
54
+ });
55
+ const unsub = window.peer.onMetadataMessage((data) => {
56
+ if (`transfer_${data.platform}` === inputs.actionType) {
57
+ resolve(data);
58
+ }
59
+ });
60
+ await window.peer.authenticate(inputs);
61
+ return await promise.then((data) => {
62
+ unsub();
63
+ return data;
64
+ });
65
+ },
66
+ /** subscribe to metadata messages */
67
+ onMetadataMessage: (fn) => {
68
+ return window.peer.onMetadataMessage(fn);
69
+ },
70
+ /** open the PeerAuth sidebar */
71
+ openSidebar: async (route) => {
72
+ return await window.peer.openSidebar(route);
73
+ },
74
+ /* v8 ignore start -- @preserve
75
+ * Excluded from coverage on purpose: the extension's `window.peer.onramp`
76
+ * API is retired (the buyer flow verifies through the zkp2p enclave
77
+ * adapter instead) and no caller in this repository invokes
78
+ * supportsOnramp, onramp or getOnrampTransaction. Testing them would
79
+ * assert behaviour production never reaches. */
80
+ /** Whether the connected extension exposes the trusted-execution on-ramp. */
81
+ supportsOnramp: () => typeof window.peer?.onramp === "function",
82
+ /**
83
+ * Begin the trusted-execution on-ramp and resolve with ready-to-send
84
+ * `fulfillIntent` calldata once the extension has intercepted the bank
85
+ * session and the zkp2p attestation service has signed the payment.
86
+ *
87
+ * The underlying extension API is callback-only with no error channel:
88
+ * the extension surfaces progress and failures in its own sidebar/tab,
89
+ * and only invokes the callback on success. The returned promise
90
+ * therefore stays pending until the prepared transaction arrives — the
91
+ * caller drives the user toward the PeerAuth window and may offer a
92
+ * cancel affordance rather than racing a timeout against a buyer who is
93
+ * still logging into their bank.
94
+ */
95
+ onramp: async (params) => {
96
+ if (typeof window.peer?.onramp !== "function") {
97
+ throw new Error(
98
+ "This PeerAuth extension version does not support the trusted-execution on-ramp. Please update the extension."
99
+ );
100
+ }
101
+ const queryString = buildOnrampQuery(params);
102
+ return new Promise((resolve) => {
103
+ window.peer.onramp(queryString, resolve);
104
+ });
105
+ },
106
+ /**
107
+ * Retrieve a previously prepared on-ramp transaction by intent hash.
108
+ * Returns null when the extension lacks the API or has nothing prepared
109
+ * yet — usable as a poll alongside {@link onramp} to recover a result
110
+ * if the one-shot callback is missed.
111
+ */
112
+ getOnrampTransaction: async (intentHash) => {
113
+ if (typeof window.peer?.getOnrampTransaction !== "function") return null;
114
+ return window.peer.getOnrampTransaction(intentHash);
115
+ }
116
+ /* v8 ignore stop -- @preserve */
117
+ };
118
+ }, [isInitialized]);
119
+ };
120
+
121
+ export { buildOnrampQuery, extensionUrl, useZKTLS };
122
+ //# sourceMappingURL=chunk-JU2T2TGY.js.map
123
+ //# sourceMappingURL=chunk-JU2T2TGY.js.map