@provex/proving 1.8.0 → 1.9.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.
@@ -2,6 +2,26 @@ import { useState, useRef, useCallback } from 'react';
2
2
  import { extractNullifier } from '@provex/utils';
3
3
  import { sleep } from '@provex/utils/time';
4
4
 
5
+ // src/useProving.ts
6
+
7
+ // src/types.ts
8
+ var EnclaveCaptureUnavailable = class extends Error {
9
+ platform;
10
+ constructor(platform) {
11
+ super(`No enclave (buyerTee) capture available for platform "${platform}".`);
12
+ this.name = "EnclaveCaptureUnavailable";
13
+ this.platform = platform;
14
+ }
15
+ };
16
+ var ManualSelectionRequired = class extends Error {
17
+ candidates;
18
+ constructor(candidates) {
19
+ super("Manual transaction selection is required but no resolver was provided.");
20
+ this.name = "ManualSelectionRequired";
21
+ this.candidates = candidates;
22
+ }
23
+ };
24
+
5
25
  // src/useProving.ts
6
26
  async function withMinimumVisible(floorMs, started) {
7
27
  const elapsed = Date.now() - started;
@@ -28,6 +48,11 @@ function useProving(options) {
28
48
  if (inFlightRef.current) return;
29
49
  inFlightRef.current = true;
30
50
  try {
51
+ if (client.chainId !== request.chainId) {
52
+ throw new Error(
53
+ `Refusing to proceed: proving client is on chain ${client.chainId} but the intent targets chain ${request.chainId}. A nullifier must only be verified and fulfilled on its intended chain.`
54
+ );
55
+ }
31
56
  setState({ status: "capture" });
32
57
  const handle = await captureAdapter.authenticateAndCapture(request, { selectTransaction });
33
58
  const attestStarted = Date.now();
@@ -35,20 +60,25 @@ function useProving(options) {
35
60
  const verified = await captureAdapter.attest(handle, request);
36
61
  await withMinimumVisible(1e3, attestStarted);
37
62
  const nullifier = extractNullifier(verified.attestation);
38
- if (nullifier) {
39
- const isUsed = await client.checkNullifierUsed({ paymentMethodId, nullifier });
40
- if (isUsed === true) {
41
- throw new Error(
42
- "This payment has already been verified. The nullifier has already been used onchain."
43
- );
44
- }
63
+ if (!nullifier) {
64
+ throw new Error(
65
+ "Could not extract a nullifier from the attestation; cannot confirm it is unused. Refusing to proceed."
66
+ );
67
+ }
68
+ const isUsed = await client.checkNullifierUsed({ paymentMethodId, nullifier });
69
+ if (isUsed !== false) {
70
+ throw new Error(
71
+ isUsed === true ? "This payment has already been verified. The nullifier has already been used onchain." : "Could not verify the nullifier is unused (registry unreachable). Refusing to proceed \u2014 please retry."
72
+ );
45
73
  }
46
74
  setState({ status: "readyToFulfill", attestation: verified });
47
75
  } catch (error) {
76
+ const code = error instanceof EnclaveCaptureUnavailable ? "enclave-unavailable" : error instanceof ManualSelectionRequired ? "manual-selection-required" : void 0;
48
77
  setState({
49
78
  status: "error",
50
79
  message: error instanceof Error ? error.message : "Verification failed",
51
- isSubmissionError: false
80
+ isSubmissionError: false,
81
+ code
52
82
  });
53
83
  } finally {
54
84
  inFlightRef.current = false;
@@ -61,6 +91,11 @@ function useProving(options) {
61
91
  const submitStarted = Date.now();
62
92
  setState({ status: "fulfillIntent", attestation: verified });
63
93
  try {
94
+ if (client.chainId !== request.chainId) {
95
+ throw new Error(
96
+ `Refusing to proceed: proving client is on chain ${client.chainId} but the intent targets chain ${request.chainId}. A nullifier must only be verified and fulfilled on its intended chain.`
97
+ );
98
+ }
64
99
  await client.fulfillIntent({
65
100
  intentHash: request.intent.intentHash,
66
101
  attestation: verified.attestation,
@@ -118,6 +153,6 @@ function useProving(options) {
118
153
  return { state, authenticate, fulfill, retryVerification, retrySubmission };
119
154
  }
120
155
 
121
- export { useProving };
122
- //# sourceMappingURL=chunk-LFDS3XGK.js.map
123
- //# sourceMappingURL=chunk-LFDS3XGK.js.map
156
+ export { EnclaveCaptureUnavailable, ManualSelectionRequired, useProving };
157
+ //# sourceMappingURL=chunk-OSBIXUFA.js.map
158
+ //# sourceMappingURL=chunk-OSBIXUFA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/useProving.ts"],"names":[],"mappings":"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","file":"chunk-OSBIXUFA.js","sourcesContent":["/**\n * @fileoverview Seam contract + state types for the proving orchestrator.\n *\n * `CaptureAdapter` is the swappable capture backend (extension+enclave today,\n * TLSNotary tomorrow). `ProvingState` is the single discriminated-union\n * machine that subsumes the live proof-substep machine (ProofPanel) and the\n * attestation lifecycle (submitStepDerivations). Monetary values stay bigint\n * in base units; the only stringified amount is `ProvingIntent.amount`, which\n * mirrors the existing on-the-wire BuyerTeeIntent shape (buyerTeeAttestation).\n */\n\nimport type { Hex } from 'viem'\nimport type { V3AttestationResponseObject } from '@provex/utils'\nimport type { TokenInfo } from '@provex/utils/tokens'\n\n/** Transaction candidate the adapter surfaces for manual selection. Mirrors\n * the `MetadataMessage` fields the live TransactionSelector consumes; kept\n * structural so the package needs no @provex/extension-react import. */\nexport interface CaptureCandidate {\n amount: number | string\n currency?: string\n date?: string | number | Date\n originalIndex: number\n}\n\n/** Identity the attestor binds the attestation to. Mirrors the live\n * attestationIntent built in SubmitStep.tsx:333-342 / BuyerTeeIntent. */\nexport interface ProvingIntent {\n intentHash: Hex\n /** bigint base units, stringified (matches BuyerTeeIntent.amount). */\n amount: string\n timestampMs: string\n paymentMethod: Hex\n fiatCurrency: Hex\n conversionRate: string\n /** payeeDetailsHash */\n payeeDetails: Hex\n timestampBufferMs?: string\n}\n\n/** What the orchestrator hands a capture adapter. */\nexport interface CaptureRequest {\n /** Parent provider key, e.g. 'zelle', 'revolut'. */\n provider: string\n /** Extension platform slug, e.g. 'chase'. */\n platform: string\n /** Action type, e.g. 'transfer_zelle'. */\n actionType: string\n intent: ProvingIntent\n /** UnifiedPaymentVerifier the attestation is re-signed for. */\n verifyingContract: Hex\n chainId: number\n /**\n * Settlement token for this intent (e.g. USDC on the target chain).\n * Used by the adapter to compute the fiat currency amount via\n * convertTokenInputToCurrency so auto-match works correctly for non-1:1 rates.\n */\n token: TokenInfo\n}\n\n/** Opaque per-backend capture state; the orchestrator passes it back unmodified. */\nexport interface CaptureHandle {\n readonly kind: string\n readonly material: unknown\n}\n\n/** Uniform capture output. `verificationData` names the signing witness to\n * the on-chain verifier (enclave signer for buyerTee — SubmitStep.tsx:473-475). */\nexport interface VerifiedAttestation {\n attestation: V3AttestationResponseObject\n verificationData: Hex\n}\n\n/** Resolver the UI provides so the adapter can collect a manual transaction\n * pick when auto-match fails (GenerateProofStep.tsx:391-404). */\nexport type SelectTransaction = (candidates: CaptureCandidate[]) => Promise<number>\n\n/** A pluggable capture backend. One per capture mechanism. */\nexport interface CaptureAdapter {\n /** Stable id for telemetry, e.g. 'extension-enclave'. */\n readonly kind: string\n /** Phase 1: authenticate + capture, returning opaque material to attest. */\n authenticateAndCapture(\n request: CaptureRequest,\n options?: { selectTransaction?: SelectTransaction },\n ): Promise<CaptureHandle>\n /** Phase 2: produce a verified attestation from the captured material. */\n attest(handle: CaptureHandle, request: CaptureRequest): Promise<VerifiedAttestation>\n}\n\n/** Inputs the orchestrator hook needs to drive the machine. */\nexport interface ProvingRequest {\n request: CaptureRequest\n /** Optional manual-select resolver forwarded to the adapter. */\n selectTransaction?: SelectTransaction\n}\n\n/**\n * The proving state machine (spec lines 141-151). `attest` auto-runs on\n * entry; `readyToFulfill` waits for the user's explicit Complete Order\n * action; `error.isSubmissionError` routes retry to Retry Submission\n * (post-fulfilment) vs Retry Verification (pre-fulfilment) — exactly the\n * live SubmitStep.tsx:931-955 routing.\n */\nexport type ProvingState =\n | { status: 'authenticate' }\n | { status: 'capture' }\n | { status: 'attest' }\n | { status: 'readyToFulfill'; attestation: VerifiedAttestation }\n | { status: 'fulfillIntent'; attestation: VerifiedAttestation }\n | { status: 'confirm'; attestation: VerifiedAttestation }\n | { status: 'sync'; attestation: VerifiedAttestation }\n | { status: 'complete'; attestation: VerifiedAttestation }\n | {\n status: 'error'\n message: string\n /** true ⇒ failure occurred during/after on-chain submission. */\n isSubmissionError: boolean\n attestation?: VerifiedAttestation\n /** Typed reason, set for cases the consumer must branch on. Currently\n * 'enclave-unavailable' (non-migrated provider → caller falls back to\n * Reclaim) and 'manual-selection-required' (auto-match failed, no\n * resolver supplied). Absent for ordinary verification/submission errors. */\n code?: 'enclave-unavailable' | 'manual-selection-required'\n }\n\n/**\n * Thrown by a capture adapter when the provider returned no buyerTee capture\n * (a non-migrated / Reclaim-only provider). The caller (UnifiedBuy) catches\n * this to fall back to its retained Reclaim branch — the package itself does\n * not implement Reclaim (decision (a), spec line 256).\n */\nexport class EnclaveCaptureUnavailable extends Error {\n readonly platform: string\n constructor(platform: string) {\n super(`No enclave (buyerTee) capture available for platform \"${platform}\".`)\n this.name = 'EnclaveCaptureUnavailable'\n this.platform = platform\n }\n}\n\n/**\n * Thrown by a capture adapter when auto-match failed and no `selectTransaction`\n * resolver was provided (e.g. a headless wallet). Carries the candidates so a\n * UI caller can render the manual selector and retry (spec lines 263-267).\n */\nexport class ManualSelectionRequired extends Error {\n readonly candidates: CaptureCandidate[]\n constructor(candidates: CaptureCandidate[]) {\n super('Manual transaction selection is required but no resolver was provided.')\n this.name = 'ManualSelectionRequired'\n this.candidates = candidates\n }\n}\n","/**\n * @fileoverview useProving — headless orchestrator for the enclave proving\n * pipeline. Replicates the live AuthenticateStep/GenerateProofStep/SubmitStep\n * state machine (ProofPanel) with no presentation. Renders nothing; returns\n * state + actions for a UI (UnifiedBuy ProofPanel or a wallet) to bind to.\n *\n * Owns: the ProvingState machine, the nullifier double-spend check\n * (SubmitStep.tsx:419-426), the attest→fulfil sequencing, retry routing\n * (SubmitStep.tsx:931-955), and the minimum-visible-state timings\n * (SubmitStep.tsx:404-417). Delegates capture to the injected CaptureAdapter,\n * the on-chain write (+ receipt + indexer sync) to ProveXClient.fulfillIntent.\n */\n\nimport { useCallback, useRef, useState } from 'react'\nimport type { Hex } from 'viem'\nimport { extractNullifier, type V3AttestationResponseObject } from '@provex/utils'\nimport { sleep } from '@provex/utils/time'\nimport { EnclaveCaptureUnavailable, ManualSelectionRequired } from './types'\nimport type {\n CaptureAdapter,\n CaptureRequest,\n SelectTransaction,\n ProvingState,\n VerifiedAttestation,\n} from './types'\n\n/**\n * Minimal context object that buildWritableMethod passes to the per-call hooks.\n * Kept structural so @provex/proving does not depend on @valve-tech/wallet-adapter.\n * Only `hash` is consumed by this hook — the rest is opaque context.\n */\ninterface TxCallbackContext {\n chainId: number\n request: unknown\n}\n\n/** Structural params for the fulfillIntent call. Mirrors FulfillIntentParams +\n * WriteHookParams from @provex/react but declared here to keep @provex/proving\n * from depending on the React package's built dist (which the test environment\n * doesn't require). The real ProveXClient.fulfillIntent satisfies this shape.\n *\n * IMPORTANT: onAwaitingSignature and onTransactionHash match the exact call\n * shapes in buildWritableMethod (ProveXClient.ts ~890, ~903):\n * params.onAwaitingSignature?.(ctx) → (info: TxCallbackContext) => void\n * params.onTransactionHash?.({ ...ctx, hash }) → (info: TxCallbackContext & { hash: Hex }) => void\n */\ninterface FulfillParams {\n intentHash: Hex\n attestation: V3AttestationResponseObject\n verificationData: Hex\n postIntentHookData?: Hex\n onAwaitingSignature?: (info: TxCallbackContext) => void\n onTransactionHash?: (info: TxCallbackContext & { hash: Hex }) => void\n}\n\n/** The structural slice of ProveXClient the orchestrator depends on. Keeping\n * it structural lets tests pass a light fake and avoids importing the class. */\nexport interface ProvingClient {\n /**\n * The chain this rail operates on. Must equal request.chainId — useProving\n * refuses to check the nullifier or submit on any other chain. A nullifier is\n * only meaningful on its intended chain; checking it on (or submitting it to)\n * a different chain is a cross-chain double-spend / replay vector.\n */\n readonly chainId: number\n fulfillIntent: (params: FulfillParams) => Promise<{ hash: Hex; receipt: unknown }>\n checkNullifierUsed: (params: { paymentMethodId: Hex; nullifier: Hex }) => Promise<boolean | null>\n}\n\nexport interface UseProvingOptions {\n client: ProvingClient\n captureAdapter: CaptureAdapter\n request: CaptureRequest\n /** Payment method id for the nullifier registry lookup. */\n paymentMethodId: Hex\n /** Manual-select resolver forwarded to the adapter. */\n selectTransaction?: SelectTransaction\n /**\n * Forwarded to ProveXClient.fulfillIntent for the consumer's tx-flight strip.\n * Receives the raw TxContext — the hook does not inspect the argument.\n */\n onAwaitingSignature?: () => void\n /** Receives the raw tx hash extracted from the TxContext+hash payload. */\n onTransactionHash?: (hash: Hex) => void\n /** Called once when the flow reaches `complete`. */\n onComplete?: (attestation: V3AttestationResponseObject) => void\n}\n\nexport interface UseProvingReturn {\n state: ProvingState\n /** Phase 1+2: authenticate, capture, attest, nullifier-check → readyToFulfill. */\n authenticate: () => Promise<void>\n /** Phase 3: submit fulfillIntent → confirm → sync → complete. */\n fulfill: () => Promise<void>\n /** Re-run attestation after a pre-fulfilment error. */\n retryVerification: () => Promise<void>\n /** Re-run submission after a post-fulfilment error. */\n retrySubmission: () => Promise<void>\n}\n\n/** Minimum visible-state pacing from SubmitStep.tsx:404-417 — keep a state\n * on screen at least `floorMs` so the user perceives the step. */\nasync function withMinimumVisible(floorMs: number, started: number): Promise<void> {\n const elapsed = Date.now() - started\n if (elapsed < floorMs) await sleep(floorMs - elapsed)\n}\n\n/**\n * Detect a wallet-rejection error structurally (without importing ProveXError).\n * ProveXClient always re-wraps user rejections as ProveXError with\n * code === 'WALLET_REJECTED' before the error escapes fulfillIntent.\n */\nfunction isWalletRejection(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n (error as { code: unknown }).code === 'WALLET_REJECTED'\n )\n}\n\nexport function useProving(options: UseProvingOptions): UseProvingReturn {\n const {\n client,\n captureAdapter,\n request,\n paymentMethodId,\n selectTransaction,\n onAwaitingSignature,\n onTransactionHash,\n onComplete,\n } = options\n\n const [state, setState] = useState<ProvingState>({ status: 'authenticate' })\n // Re-entry guard mirroring isRequestingAttestationRef (SubmitStep.tsx:199).\n const inFlightRef = useRef(false)\n // Separate in-flight guard for submit — prevents double on-chain spend from\n // two rapid fulfill() calls before a re-render.\n const submitInFlightRef = useRef(false)\n\n const runCaptureAndAttest = useCallback(async (): Promise<void> => {\n if (inFlightRef.current) return\n inFlightRef.current = true\n try {\n // Chain pinning: the nullifier must only ever be checked on the chain the\n // intent targets. A client wired to any other chain is refused before we\n // touch capture/attest/registry — never verify on the wrong chain.\n if (client.chainId !== request.chainId) {\n throw new Error(\n `Refusing to proceed: proving client is on chain ${client.chainId} but the intent targets chain ${request.chainId}. A nullifier must only be verified and fulfilled on its intended chain.`,\n )\n }\n\n setState({ status: 'capture' })\n const handle = await captureAdapter.authenticateAndCapture(request, { selectTransaction })\n\n const attestStarted = Date.now()\n setState({ status: 'attest' })\n const verified = await captureAdapter.attest(handle, request)\n // SubmitStep.tsx:414-417: verifying state must be visible for ≥1000ms.\n await withMinimumVisible(1000, attestStarted)\n\n // Nullifier double-spend pre-check — FAIL CLOSED. We must positively\n // confirm the nullifier is unused on the target chain before allowing\n // fulfillment. Any inability to verify (cannot extract, registry\n // unreachable/errored) blocks. \"If we can't verify the nullifier has not\n // been used, we cannot use it.\"\n const nullifier = extractNullifier(verified.attestation)\n if (!nullifier) {\n throw new Error(\n 'Could not extract a nullifier from the attestation; cannot confirm it is unused. Refusing to proceed.',\n )\n }\n const isUsed = await client.checkNullifierUsed({ paymentMethodId, nullifier })\n if (isUsed !== false) {\n throw new Error(\n isUsed === true\n ? 'This payment has already been verified. The nullifier has already been used onchain.'\n : 'Could not verify the nullifier is unused (registry unreachable). Refusing to proceed — please retry.',\n )\n }\n\n // only an explicit `false` reaches here\n setState({ status: 'readyToFulfill', attestation: verified })\n } catch (error) {\n const code =\n error instanceof EnclaveCaptureUnavailable\n ? ('enclave-unavailable' as const)\n : error instanceof ManualSelectionRequired\n ? ('manual-selection-required' as const)\n : undefined\n setState({\n status: 'error',\n message: error instanceof Error ? error.message : 'Verification failed',\n isSubmissionError: false,\n code,\n })\n } finally {\n inFlightRef.current = false\n }\n }, [captureAdapter, client, request, paymentMethodId, selectTransaction])\n\n const submit = useCallback(\n async (verified: VerifiedAttestation): Promise<void> => {\n // Re-entry guard: prevent two rapid submit calls from both calling\n // fulfillIntent (double on-chain spend). Mirrors inFlightRef pattern.\n if (submitInFlightRef.current) return\n submitInFlightRef.current = true\n\n const submitStarted = Date.now()\n setState({ status: 'fulfillIntent', attestation: verified })\n try {\n // Chain pinning (re-asserted at submit): the nullifier was verified on\n // request.chainId; it must only ever be submitted to that same chain.\n if (client.chainId !== request.chainId) {\n throw new Error(\n `Refusing to proceed: proving client is on chain ${client.chainId} but the intent targets chain ${request.chainId}. A nullifier must only be verified and fulfilled on its intended chain.`,\n )\n }\n await client.fulfillIntent({\n intentHash: request.intent.intentHash,\n attestation: verified.attestation,\n verificationData: verified.verificationData,\n // Wrap consumer callbacks to match the real buildWritableMethod\n // call shapes (ProveXClient.ts ~890, ~903).\n onAwaitingSignature: onAwaitingSignature\n ? (_info: TxCallbackContext) => { onAwaitingSignature() }\n : undefined,\n // buildWritableMethod fires onTransactionHash with { ...ctx, hash }\n // (ProveXClient.ts ~903). Extract hash and forward to the consumer's\n // simpler (hash: Hex) => void API.\n onTransactionHash: onTransactionHash\n ? (info: TxCallbackContext & { hash: Hex }) => {\n // After the tx is broadcast (a separate async tick before the\n // receipt arrives), surface the confirm state so it is\n // observable by consumers.\n setState({ status: 'confirm', attestation: verified })\n onTransactionHash(info.hash)\n }\n : ((_info: TxCallbackContext & { hash: Hex }) => {\n setState({ status: 'confirm', attestation: verified })\n }),\n })\n await withMinimumVisible(500, submitStarted)\n\n // fulfillIntent already awaited receipt + indexer sync internally\n // (ProveXClient.ts:906-915). Surface sync as a real visible state\n // (confirm was set in onTransactionHash above); then await a\n // minimum-visible window before complete so sync is not batched away.\n setState({ status: 'sync', attestation: verified })\n await sleep(600)\n setState({ status: 'complete', attestation: verified })\n onComplete?.(verified.attestation)\n } catch (error) {\n // Wallet rejection: return to readyToFulfill so the user can retry\n // without losing the attestation (mirrors SubmitStep.tsx:571-573).\n if (isWalletRejection(error)) {\n setState({ status: 'readyToFulfill', attestation: verified })\n return\n }\n setState({\n status: 'error',\n message: error instanceof Error ? error.message : 'Submission failed',\n isSubmissionError: true,\n attestation: verified,\n })\n } finally {\n submitInFlightRef.current = false\n }\n },\n [client, request.intent.intentHash, onAwaitingSignature, onTransactionHash, onComplete],\n )\n\n const authenticate = useCallback(() => runCaptureAndAttest(), [runCaptureAndAttest])\n\n const fulfill = useCallback(async () => {\n if (state.status !== 'readyToFulfill') return\n await submit(state.attestation)\n }, [state, submit])\n\n const retryVerification = useCallback(async () => {\n setState({ status: 'authenticate' })\n await runCaptureAndAttest()\n }, [runCaptureAndAttest])\n\n const retrySubmission = useCallback(async () => {\n if (state.status !== 'error' || !state.attestation) return\n await submit(state.attestation)\n }, [state, submit])\n\n return { state, authenticate, fulfill, retryVerification, retrySubmission }\n}\n"]}
package/dist/index.cjs CHANGED
@@ -3,11 +3,10 @@
3
3
  var units = require('@provex/utils/units');
4
4
  var conversionRates = require('@provex/utils/conversionRates');
5
5
  var fees = require('@provex/utils/fees');
6
- var viem = require('viem');
7
- var payment = require('@provex/utils/payment');
8
6
  var react = require('react');
9
7
  var utils = require('@provex/utils');
10
8
  var time = require('@provex/utils/time');
9
+ var payment = require('@provex/utils/payment');
11
10
 
12
11
  // src/buyerTeeAttestation.ts
13
12
  function asBuyerTeeProof(proof) {
@@ -101,100 +100,6 @@ function computePartialFulfillment({
101
100
  protocolFeeDisplay: protocolFee > 0n ? formatTokenDisplay(protocolFee) : null
102
101
  };
103
102
  }
104
- function normalizeAmount(amount, decimals, centIsInt) {
105
- const raw = String(amount);
106
- const isFormattedDollars = /[^0-9\-]/.test(raw) && raw.includes(".");
107
- const cleaned = raw.replace(/[^0-9.]/g, "");
108
- let value = viem.parseUnits(cleaned, decimals);
109
- if (centIsInt && !isFormattedDollars) {
110
- value = value / 100n;
111
- }
112
- return value;
113
- }
114
- function autoMatch(candidates, request) {
115
- const currencyAmount = conversionRates.convertTokenInputToCurrency({
116
- token: request.token,
117
- amountOutInt: BigInt(request.intent.amount),
118
- rate: BigInt(request.intent.conversionRate)
119
- });
120
- const configs = payment.providerConfigs(request.chainId);
121
- const providerConfig = configs[request.provider];
122
- const centIsInt = providerConfig?.amountInCents ?? false;
123
- return candidates.find((candidate) => {
124
- const converted = normalizeAmount(candidate.amount, request.token.decimals, centIsInt);
125
- return converted === currencyAmount || converted === currencyAmount * -1n;
126
- }) ?? null;
127
- }
128
- function awaitMetadata(peer, actionType, platform, timeoutMs) {
129
- return new Promise((resolve, reject) => {
130
- let timeoutId;
131
- const unsub = peer.onMetadataMessage((data) => {
132
- if (`transfer_${data.platform}` === actionType) {
133
- clearTimeout(timeoutId);
134
- unsub();
135
- resolve(data);
136
- }
137
- });
138
- timeoutId = setTimeout(() => {
139
- unsub();
140
- reject(new Error(`Enclave capture timed out for platform "${platform}"`));
141
- }, timeoutMs);
142
- });
143
- }
144
- function createExtensionEnclaveAdapter(config) {
145
- const timeoutMs = config.timeoutMs ?? 6e4;
146
- return {
147
- kind: "extension-enclave",
148
- async authenticateAndCapture(request, options) {
149
- const peer = config.getPeer();
150
- if (!peer) {
151
- throw new EnclaveCaptureUnavailable(request.platform);
152
- }
153
- const metadataPromise = awaitMetadata(peer, request.actionType, request.platform, timeoutMs);
154
- await peer.authenticate({
155
- actionType: request.actionType,
156
- platform: request.platform,
157
- captureMode: "buyerTee"
158
- });
159
- const response = await metadataPromise;
160
- if (!response.buyerTeeCapture) {
161
- throw new EnclaveCaptureUnavailable(request.platform);
162
- }
163
- const matched = autoMatch(response.metadata, request);
164
- let matchedIndex;
165
- if (matched) {
166
- matchedIndex = matched.originalIndex;
167
- } else if (options?.selectTransaction) {
168
- matchedIndex = await options.selectTransaction(response.metadata);
169
- } else {
170
- throw new ManualSelectionRequired(response.metadata);
171
- }
172
- const material = {
173
- encryptedSessionMaterial: response.buyerTeeCapture.encryptedSessionMaterial,
174
- matchedIndex
175
- };
176
- return { kind: "extension-enclave", material };
177
- },
178
- async attest(handle, request) {
179
- if (handle.kind !== "extension-enclave") {
180
- throw new Error(
181
- `ExtensionEnclaveAdapter.attest received a handle of kind "${handle.kind}"; expected "extension-enclave".`
182
- );
183
- }
184
- const material = handle.material;
185
- const attestation = await requestBuyerTeeAttestation({
186
- attestationBaseUrl: config.attestationBaseUrl,
187
- platform: request.platform,
188
- actionType: request.actionType,
189
- encryptedSessionMaterial: material.encryptedSessionMaterial,
190
- params: { index: material.matchedIndex },
191
- verifyingContract: request.verifyingContract,
192
- intent: request.intent
193
- });
194
- return { attestation, verificationData: attestation.signer };
195
- }
196
- };
197
- }
198
103
  async function withMinimumVisible(floorMs, started) {
199
104
  const elapsed = Date.now() - started;
200
105
  if (elapsed < floorMs) await time.sleep(floorMs - elapsed);
@@ -220,6 +125,11 @@ function useProving(options) {
220
125
  if (inFlightRef.current) return;
221
126
  inFlightRef.current = true;
222
127
  try {
128
+ if (client.chainId !== request.chainId) {
129
+ throw new Error(
130
+ `Refusing to proceed: proving client is on chain ${client.chainId} but the intent targets chain ${request.chainId}. A nullifier must only be verified and fulfilled on its intended chain.`
131
+ );
132
+ }
223
133
  setState({ status: "capture" });
224
134
  const handle = await captureAdapter.authenticateAndCapture(request, { selectTransaction });
225
135
  const attestStarted = Date.now();
@@ -227,20 +137,25 @@ function useProving(options) {
227
137
  const verified = await captureAdapter.attest(handle, request);
228
138
  await withMinimumVisible(1e3, attestStarted);
229
139
  const nullifier = utils.extractNullifier(verified.attestation);
230
- if (nullifier) {
231
- const isUsed = await client.checkNullifierUsed({ paymentMethodId, nullifier });
232
- if (isUsed === true) {
233
- throw new Error(
234
- "This payment has already been verified. The nullifier has already been used onchain."
235
- );
236
- }
140
+ if (!nullifier) {
141
+ throw new Error(
142
+ "Could not extract a nullifier from the attestation; cannot confirm it is unused. Refusing to proceed."
143
+ );
144
+ }
145
+ const isUsed = await client.checkNullifierUsed({ paymentMethodId, nullifier });
146
+ if (isUsed !== false) {
147
+ throw new Error(
148
+ isUsed === true ? "This payment has already been verified. The nullifier has already been used onchain." : "Could not verify the nullifier is unused (registry unreachable). Refusing to proceed \u2014 please retry."
149
+ );
237
150
  }
238
151
  setState({ status: "readyToFulfill", attestation: verified });
239
152
  } catch (error) {
153
+ const code = error instanceof EnclaveCaptureUnavailable ? "enclave-unavailable" : error instanceof ManualSelectionRequired ? "manual-selection-required" : void 0;
240
154
  setState({
241
155
  status: "error",
242
156
  message: error instanceof Error ? error.message : "Verification failed",
243
- isSubmissionError: false
157
+ isSubmissionError: false,
158
+ code
244
159
  });
245
160
  } finally {
246
161
  inFlightRef.current = false;
@@ -253,6 +168,11 @@ function useProving(options) {
253
168
  const submitStarted = Date.now();
254
169
  setState({ status: "fulfillIntent", attestation: verified });
255
170
  try {
171
+ if (client.chainId !== request.chainId) {
172
+ throw new Error(
173
+ `Refusing to proceed: proving client is on chain ${client.chainId} but the intent targets chain ${request.chainId}. A nullifier must only be verified and fulfilled on its intended chain.`
174
+ );
175
+ }
256
176
  await client.fulfillIntent({
257
177
  intentHash: request.intent.intentHash,
258
178
  attestation: verified.attestation,
@@ -309,13 +229,52 @@ function useProving(options) {
309
229
  }, [state, submit]);
310
230
  return { state, authenticate, fulfill, retryVerification, retrySubmission };
311
231
  }
232
+ function useManualTransactionSelect() {
233
+ const [candidates, setCandidates] = react.useState(null);
234
+ const resolveRef = react.useRef(null);
235
+ const selectTransaction = react.useCallback((next) => {
236
+ return new Promise((resolve) => {
237
+ resolveRef.current = resolve;
238
+ setCandidates(next);
239
+ });
240
+ }, []);
241
+ const pick = react.useCallback((originalIndex) => {
242
+ resolveRef.current?.(originalIndex);
243
+ resolveRef.current = null;
244
+ setCandidates(null);
245
+ }, []);
246
+ return { selectTransaction, candidates, pick };
247
+ }
248
+ function buildCaptureRequest(input) {
249
+ const parentProvider = input.providerKey.includes("-") ? input.providerKey.split("-")[0] : input.providerKey;
250
+ const platform = payment.getExtensionPlatform(input.subProvider ?? input.providerKey);
251
+ return {
252
+ provider: input.providerKey,
253
+ platform,
254
+ actionType: `transfer_${parentProvider}`,
255
+ verifyingContract: input.verifyingContract,
256
+ chainId: input.chainId,
257
+ token: input.token,
258
+ intent: {
259
+ intentHash: input.intentHash,
260
+ amount: input.intent.amount.toString(),
261
+ timestampMs: (input.intent.timestamp * 1000n).toString(),
262
+ paymentMethod: input.intent.paymentMethod ?? "0x",
263
+ fiatCurrency: input.intent.fiatCurrency,
264
+ conversionRate: input.intent.conversionRate.toString(),
265
+ payeeDetails: input.payeeDetailsHash,
266
+ timestampBufferMs: "300000"
267
+ }
268
+ };
269
+ }
312
270
 
313
271
  exports.EnclaveCaptureUnavailable = EnclaveCaptureUnavailable;
314
272
  exports.ManualSelectionRequired = ManualSelectionRequired;
315
273
  exports.asBuyerTeeProof = asBuyerTeeProof;
274
+ exports.buildCaptureRequest = buildCaptureRequest;
316
275
  exports.computePartialFulfillment = computePartialFulfillment;
317
- exports.createExtensionEnclaveAdapter = createExtensionEnclaveAdapter;
318
276
  exports.requestBuyerTeeAttestation = requestBuyerTeeAttestation;
277
+ exports.useManualTransactionSelect = useManualTransactionSelect;
319
278
  exports.useProving = useProving;
320
279
  //# sourceMappingURL=index.cjs.map
321
280
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/buyerTeeAttestation.ts","../src/types.ts","../src/derivations.ts","../src/adapters/extensionEnclave.ts","../src/useProving.ts"],"names":["calculateNetAmount","calculateFeeAmount","convertTokenInputToCurrency","formatUnits","parseUnits","providerConfigs","sleep","useState","useRef","useCallback","extractNullifier"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @fileoverview Buyer trusted-execution attestation client.\n *\n * In PeerAuth extension 0.5.2+, capture produces a buyerTee proof —\n * `{ proofType: 'buyerTee', encryptedSessionMaterial, params }` — where the\n * bank session is already encrypted toward the enclave (cookies never reach\n * our page in plaintext). Instead of re-signing a Reclaim proof through our\n * gate, we hand that material to zkp2p's trusted-execution attestation\n * service, which verifies the payment inside an Amazon Web Services Nitro\n * enclave and signs the attestation directly.\n *\n * The response `responseObject` is the same `V3AttestationResponseObject`\n * shape our gate returns, so it flows through the existing\n * `encodeV3PaymentProof` path unchanged.\n *\n * Moved verbatim from `@provex/ui` (packages/ui/src/lib/buyerTeeAttestation.ts)\n * with the base URL changed from a `@provex/ui` config import to an injected\n * `attestationBaseUrl` argument so this package carries no `@provex/ui` dep.\n */\n\nimport type { Hex } from 'viem'\nimport type { V3AttestationResponseObject } from '@provex/utils'\n\n/**\n * Runtime shape of a buyerTee proof emitted by the PeerAuth extension.\n * Narrow with {@link asBuyerTeeProof} before use.\n */\nexport interface BuyerTeeProof {\n proofType: 'buyerTee'\n /** Session material already encrypted toward the enclave (opaque to us). */\n encryptedSessionMaterial: string\n /** Platform-specific params, e.g. Revolut `{ index }`, Chase `{ index }`. */\n params: unknown\n actionType?: string\n actionPlatform?: string\n}\n\n/**\n * Narrow an extension proof to a buyerTee proof, or null when it is the\n * legacy Reclaim shape (or malformed).\n */\nexport function asBuyerTeeProof(proof: unknown): BuyerTeeProof | null {\n if (!proof || typeof proof !== 'object') return null\n const candidate = proof as Record<string, unknown>\n if (candidate.proofType !== 'buyerTee') return null\n if (typeof candidate.encryptedSessionMaterial !== 'string') return null\n return candidate as unknown as BuyerTeeProof\n}\n\n/** Intent details the enclave binds the attestation to. */\nexport interface BuyerTeeIntent {\n intentHash: Hex\n amount: string\n timestampMs: string\n paymentMethod: Hex\n fiatCurrency: Hex\n conversionRate: string\n payeeDetails: Hex\n timestampBufferMs?: string\n}\n\nexport interface RequestBuyerTeeAttestationArgs {\n /** Gate/attestation origin (was `gateForwarderBaseUrl` in @provex/ui). */\n attestationBaseUrl: string\n /** Extension platform / bank key, e.g. `revolut`, `chase`. */\n platform: string\n /** Action type, e.g. `transfer_revolut`, `transfer_zelle`. */\n actionType: string\n /** Encrypted session material from the buyerTee capture. */\n encryptedSessionMaterial: string\n /** Platform params from the capture (e.g. `{ index }`). */\n params: unknown\n /** PulseChain UnifiedPaymentVerifier the gate re-signs the attestation for. */\n verifyingContract: string\n intent: BuyerTeeIntent\n}\n\n/**\n * Verify a buyer payment via the gate. The gate proxies the encrypted capture\n * to zkp2p's enclave, then re-signs the resulting attestation for PulseChain\n * with our witness key — the enclave itself refuses to sign for 369.\n *\n * Throws with the gate's error text on a non-2xx response.\n */\nexport async function requestBuyerTeeAttestation(\n args: RequestBuyerTeeAttestationArgs,\n): Promise<V3AttestationResponseObject> {\n const url = `${args.attestationBaseUrl}/api/v0/attestation/buyer-verify/${encodeURIComponent(args.platform)}/${encodeURIComponent(args.actionType)}`\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n encryptedSessionMaterial: args.encryptedSessionMaterial,\n params: args.params,\n verifyingContract: args.verifyingContract,\n intent: args.intent,\n }),\n })\n\n if (!response.ok) {\n const detail = await response.text().catch(() => '')\n throw new Error(\n `Trusted-execution attestation failed (${response.status}): ${detail || response.statusText}`,\n )\n }\n\n const json = (await response.json()) as\n | { responseObject?: V3AttestationResponseObject }\n | V3AttestationResponseObject\n const responseObject =\n (json as { responseObject?: V3AttestationResponseObject }).responseObject ??\n (json as V3AttestationResponseObject)\n\n if (!responseObject?.typedDataValue || !responseObject?.signature) {\n throw new Error('Trusted-execution attestation response missing typedDataValue/signature')\n }\n return responseObject\n}\n","/**\n * @fileoverview Seam contract + state types for the proving orchestrator.\n *\n * `CaptureAdapter` is the swappable capture backend (extension+enclave today,\n * TLSNotary tomorrow). `ProvingState` is the single discriminated-union\n * machine that subsumes the live proof-substep machine (ProofPanel) and the\n * attestation lifecycle (submitStepDerivations). Monetary values stay bigint\n * in base units; the only stringified amount is `ProvingIntent.amount`, which\n * mirrors the existing on-the-wire BuyerTeeIntent shape (buyerTeeAttestation).\n */\n\nimport type { Hex } from 'viem'\nimport type { V3AttestationResponseObject } from '@provex/utils'\nimport type { TokenInfo } from '@provex/utils/tokens'\n\n/** Transaction candidate the adapter surfaces for manual selection. Mirrors\n * the `MetadataMessage` fields the live TransactionSelector consumes; kept\n * structural so the package needs no @provex/extension-react import. */\nexport interface CaptureCandidate {\n amount: number | string\n currency?: string\n date?: string | number | Date\n originalIndex: number\n}\n\n/** Identity the attestor binds the attestation to. Mirrors the live\n * attestationIntent built in SubmitStep.tsx:333-342 / BuyerTeeIntent. */\nexport interface ProvingIntent {\n intentHash: Hex\n /** bigint base units, stringified (matches BuyerTeeIntent.amount). */\n amount: string\n timestampMs: string\n paymentMethod: Hex\n fiatCurrency: Hex\n conversionRate: string\n /** payeeDetailsHash */\n payeeDetails: Hex\n timestampBufferMs?: string\n}\n\n/** What the orchestrator hands a capture adapter. */\nexport interface CaptureRequest {\n /** Parent provider key, e.g. 'zelle', 'revolut'. */\n provider: string\n /** Extension platform slug, e.g. 'chase'. */\n platform: string\n /** Action type, e.g. 'transfer_zelle'. */\n actionType: string\n intent: ProvingIntent\n /** UnifiedPaymentVerifier the attestation is re-signed for. */\n verifyingContract: Hex\n chainId: number\n /**\n * Settlement token for this intent (e.g. USDC on the target chain).\n * Used by the adapter to compute the fiat currency amount via\n * convertTokenInputToCurrency so auto-match works correctly for non-1:1 rates.\n */\n token: TokenInfo\n}\n\n/** Opaque per-backend capture state; the orchestrator passes it back unmodified. */\nexport interface CaptureHandle {\n readonly kind: string\n readonly material: unknown\n}\n\n/** Uniform capture output. `verificationData` names the signing witness to\n * the on-chain verifier (enclave signer for buyerTee — SubmitStep.tsx:473-475). */\nexport interface VerifiedAttestation {\n attestation: V3AttestationResponseObject\n verificationData: Hex\n}\n\n/** Resolver the UI provides so the adapter can collect a manual transaction\n * pick when auto-match fails (GenerateProofStep.tsx:391-404). */\nexport type SelectTransaction = (candidates: CaptureCandidate[]) => Promise<number>\n\n/** A pluggable capture backend. One per capture mechanism. */\nexport interface CaptureAdapter {\n /** Stable id for telemetry, e.g. 'extension-enclave'. */\n readonly kind: string\n /** Phase 1: authenticate + capture, returning opaque material to attest. */\n authenticateAndCapture(\n request: CaptureRequest,\n options?: { selectTransaction?: SelectTransaction },\n ): Promise<CaptureHandle>\n /** Phase 2: produce a verified attestation from the captured material. */\n attest(handle: CaptureHandle, request: CaptureRequest): Promise<VerifiedAttestation>\n}\n\n/** Inputs the orchestrator hook needs to drive the machine. */\nexport interface ProvingRequest {\n request: CaptureRequest\n /** Optional manual-select resolver forwarded to the adapter. */\n selectTransaction?: SelectTransaction\n}\n\n/**\n * The proving state machine (spec lines 141-151). `attest` auto-runs on\n * entry; `readyToFulfill` waits for the user's explicit Complete Order\n * action; `error.isSubmissionError` routes retry to Retry Submission\n * (post-fulfilment) vs Retry Verification (pre-fulfilment) — exactly the\n * live SubmitStep.tsx:931-955 routing.\n */\nexport type ProvingState =\n | { status: 'authenticate' }\n | { status: 'capture' }\n | { status: 'attest' }\n | { status: 'readyToFulfill'; attestation: VerifiedAttestation }\n | { status: 'fulfillIntent'; attestation: VerifiedAttestation }\n | { status: 'confirm'; attestation: VerifiedAttestation }\n | { status: 'sync'; attestation: VerifiedAttestation }\n | { status: 'complete'; attestation: VerifiedAttestation }\n | {\n status: 'error'\n message: string\n /** true ⇒ failure occurred during/after on-chain submission. */\n isSubmissionError: boolean\n attestation?: VerifiedAttestation\n }\n\n/**\n * Thrown by a capture adapter when the provider returned no buyerTee capture\n * (a non-migrated / Reclaim-only provider). The caller (UnifiedBuy) catches\n * this to fall back to its retained Reclaim branch — the package itself does\n * not implement Reclaim (decision (a), spec line 256).\n */\nexport class EnclaveCaptureUnavailable extends Error {\n readonly platform: string\n constructor(platform: string) {\n super(`No enclave (buyerTee) capture available for platform \"${platform}\".`)\n this.name = 'EnclaveCaptureUnavailable'\n this.platform = platform\n }\n}\n\n/**\n * Thrown by a capture adapter when auto-match failed and no `selectTransaction`\n * resolver was provided (e.g. a headless wallet). Carries the candidates so a\n * UI caller can render the manual selector and retry (spec lines 263-267).\n */\nexport class ManualSelectionRequired extends Error {\n readonly candidates: CaptureCandidate[]\n constructor(candidates: CaptureCandidate[]) {\n super('Manual transaction selection is required but no resolver was provided.')\n this.name = 'ManualSelectionRequired'\n this.candidates = candidates\n }\n}\n","/**\n * @fileoverview Pure proving derivations moved from @provex/ui\n * (packages/ui/src/components/UnifiedBuy/ProofPanel/submitStepDerivations.ts).\n *\n * Bigint throughout — see the `money-values-stay-bigint-until-display` rule.\n * The only string conversions happen at the formatter boundary (formatUnits),\n * which is the display layer.\n */\n\nimport type { V3AttestationResponseObject } from '@provex/utils'\nimport type { TokenInfo } from '@provex/utils/tokens'\nimport { formatUnits } from '@provex/utils/units'\nimport { convertTokenInputToCurrency } from '@provex/utils/conversionRates'\nimport { calculateFeeAmount, calculateNetAmount } from '@provex/utils/fees'\n\nexport interface PartialFulfillment {\n /** Gross USDC the gate authorized for release (typedData.releaseAmount). */\n grossReleased: bigint\n /** Net USDC the buyer actually receives (grossReleased − fee). */\n netToBuyer: bigint\n /** Protocol fee in USDC base units. 0n when no fee applies. */\n protocolFee: bigint\n /** Locked USDC returned to the maker (intent.amount − grossReleased). */\n returnedToMaker: bigint\n contractedFiatDisplay: string\n attestedFiatDisplay: string\n netToBuyerDisplay: string\n returnedToMakerDisplay: string\n /** null when protocolFee === 0n so the caller can skip rendering the row. */\n protocolFeeDisplay: string | null\n}\n\n/**\n * Partial-fulfilment breakdown derived from the verified attestation.\n *\n * Returns `null` when:\n * - attestation isn't present (nothing to compare)\n * - releaseAmount >= intent.amount (full settlement, no partial story)\n * - intent/tokenInfo missing (defensive)\n */\nexport function computePartialFulfillment({\n attestation,\n intentAmount,\n conversionRate,\n tokenInfo,\n feeBps,\n}: {\n attestation: V3AttestationResponseObject | null\n intentAmount: bigint | null | undefined\n conversionRate: bigint | null | undefined\n tokenInfo: TokenInfo | null\n feeBps: bigint | null | undefined\n}): PartialFulfillment | null {\n if (!attestation || !intentAmount || !conversionRate || !tokenInfo) return null\n const grossReleased = BigInt(attestation.typedDataValue.releaseAmount)\n if (grossReleased >= intentAmount) return null\n\n const returnedToMaker = intentAmount - grossReleased\n const effectiveFeeBps = feeBps ?? 0n\n const netToBuyer =\n effectiveFeeBps > 0n\n ? calculateNetAmount({ grossAmount: grossReleased, feeBps: effectiveFeeBps })\n : grossReleased\n const protocolFee =\n effectiveFeeBps > 0n\n ? calculateFeeAmount({ amount: grossReleased, feeBps: effectiveFeeBps })\n : 0n\n\n const contractedFiatRaw = convertTokenInputToCurrency({\n token: tokenInfo,\n amountOutInt: intentAmount,\n rate: conversionRate,\n })\n const attestedFiatRaw = convertTokenInputToCurrency({\n token: tokenInfo,\n amountOutInt: grossReleased,\n rate: conversionRate,\n })\n\n const formatTokenDisplay = (value: bigint): string =>\n formatUnits(value, {\n decimals: tokenInfo.decimals,\n truncateDecimals: 2,\n padDecimals: 2,\n })\n\n return {\n grossReleased,\n netToBuyer,\n protocolFee,\n returnedToMaker,\n contractedFiatDisplay: formatTokenDisplay(contractedFiatRaw),\n attestedFiatDisplay: formatTokenDisplay(attestedFiatRaw),\n netToBuyerDisplay: formatTokenDisplay(netToBuyer),\n returnedToMakerDisplay: formatTokenDisplay(returnedToMaker),\n protocolFeeDisplay: protocolFee > 0n ? formatTokenDisplay(protocolFee) : null,\n }\n}\n","/**\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 * @provex/proving stays framework-agnostic and needs no @provex/extension-react\n * import (spec dependency-direction, lines 103-110).\n */\n\nimport { parseUnits } from 'viem'\nimport type { Hex } from 'viem'\nimport { providerConfigs } from '@provex/utils/payment'\nimport type { ChainId } from '@provex/utils/chain'\nimport { convertTokenInputToCurrency } from '@provex/utils/conversionRates'\nimport {\n EnclaveCaptureUnavailable,\n ManualSelectionRequired,\n type CaptureAdapter,\n type CaptureCandidate,\n type CaptureHandle,\n type CaptureRequest,\n type SelectTransaction,\n type VerifiedAttestation,\n} from '../types'\nimport { asBuyerTeeProof, requestBuyerTeeAttestation } from '../buyerTeeAttestation'\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/** Minimal slice of `window.peer` this adapter depends on. */\nexport interface PeerClient {\n authenticate(inputs: { actionType: string; platform: string; captureMode: 'buyerTee' }): Promise<void>\n onMetadataMessage(fn: (data: MetadataResponse) => void): () => void\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. */\n attestationBaseUrl: 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\ninterface EnclaveMaterial {\n encryptedSessionMaterial: string\n matchedIndex: number\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 * Find the candidate whose amount equals the intent's FIAT currency amount,\n * matching 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 */\nfunction autoMatch(\n candidates: CaptureCandidate[],\n request: CaptureRequest,\n): CaptureCandidate | null {\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 return (\n candidates.find((candidate) => {\n const converted = normalizeAmount(candidate.amount, request.token.decimals, centIsInt)\n return converted === currencyAmount || converted === currencyAmount * -1n\n }) ?? null\n )\n}\n\n/**\n * Wait for the metadata message whose platform matches the action type,\n * mirroring the live listener resolve guard (AuthenticateStep.tsx:238-244).\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 */\nfunction awaitMetadata(\n peer: PeerClient,\n actionType: string,\n platform: 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 if (`transfer_${data.platform}` === actionType) {\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 // 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.actionType, request.platform, timeoutMs)\n await peer.authenticate({\n actionType: request.actionType,\n platform: request.platform,\n captureMode: 'buyerTee',\n })\n const response = await metadataPromise\n\n // No buyerTee capture ⇒ a non-migrated / Reclaim-only provider. The\n // caller falls back to UnifiedBuy's retained Reclaim branch.\n if (!response.buyerTeeCapture) {\n throw new EnclaveCaptureUnavailable(request.platform)\n }\n\n const matched = autoMatch(response.metadata, request)\n let matchedIndex: number\n if (matched) {\n matchedIndex = matched.originalIndex\n } else if (options?.selectTransaction) {\n matchedIndex = await options.selectTransaction(response.metadata)\n } else {\n throw new ManualSelectionRequired(response.metadata)\n }\n\n const material: EnclaveMaterial = {\n encryptedSessionMaterial: response.buyerTeeCapture.encryptedSessionMaterial,\n matchedIndex,\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 const attestation = await requestBuyerTeeAttestation({\n attestationBaseUrl: config.attestationBaseUrl,\n platform: request.platform,\n actionType: request.actionType,\n encryptedSessionMaterial: material.encryptedSessionMaterial,\n params: { index: material.matchedIndex },\n verifyingContract: request.verifyingContract,\n intent: request.intent,\n })\n // verificationData = enclave signer (SubmitStep.tsx:473-474).\n return { attestation, verificationData: attestation.signer as Hex }\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","/**\n * @fileoverview useProving — headless orchestrator for the enclave proving\n * pipeline. Replicates the live AuthenticateStep/GenerateProofStep/SubmitStep\n * state machine (ProofPanel) with no presentation. Renders nothing; returns\n * state + actions for a UI (UnifiedBuy ProofPanel or a wallet) to bind to.\n *\n * Owns: the ProvingState machine, the nullifier double-spend check\n * (SubmitStep.tsx:419-426), the attest→fulfil sequencing, retry routing\n * (SubmitStep.tsx:931-955), and the minimum-visible-state timings\n * (SubmitStep.tsx:404-417). Delegates capture to the injected CaptureAdapter,\n * the on-chain write (+ receipt + indexer sync) to ProveXClient.fulfillIntent.\n */\n\nimport { useCallback, useRef, useState } from 'react'\nimport type { Hex } from 'viem'\nimport { extractNullifier, type V3AttestationResponseObject } from '@provex/utils'\nimport { sleep } from '@provex/utils/time'\nimport type {\n CaptureAdapter,\n CaptureRequest,\n SelectTransaction,\n ProvingState,\n VerifiedAttestation,\n} from './types'\n\n/**\n * Minimal context object that buildWritableMethod passes to the per-call hooks.\n * Kept structural so @provex/proving does not depend on @valve-tech/wallet-adapter.\n * Only `hash` is consumed by this hook — the rest is opaque context.\n */\ninterface TxCallbackContext {\n chainId: number\n request: unknown\n}\n\n/** Structural params for the fulfillIntent call. Mirrors FulfillIntentParams +\n * WriteHookParams from @provex/react but declared here to keep @provex/proving\n * from depending on the React package's built dist (which the test environment\n * doesn't require). The real ProveXClient.fulfillIntent satisfies this shape.\n *\n * IMPORTANT: onAwaitingSignature and onTransactionHash match the exact call\n * shapes in buildWritableMethod (ProveXClient.ts ~890, ~903):\n * params.onAwaitingSignature?.(ctx) → (info: TxCallbackContext) => void\n * params.onTransactionHash?.({ ...ctx, hash }) → (info: TxCallbackContext & { hash: Hex }) => void\n */\ninterface FulfillParams {\n intentHash: Hex\n attestation: V3AttestationResponseObject\n verificationData: Hex\n postIntentHookData?: Hex\n onAwaitingSignature?: (info: TxCallbackContext) => void\n onTransactionHash?: (info: TxCallbackContext & { hash: Hex }) => void\n}\n\n/** The structural slice of ProveXClient the orchestrator depends on. Keeping\n * it structural lets tests pass a light fake and avoids importing the class. */\nexport interface ProvingClient {\n fulfillIntent: (params: FulfillParams) => Promise<{ hash: Hex; receipt: unknown }>\n checkNullifierUsed: (params: { paymentMethodId: Hex; nullifier: Hex }) => Promise<boolean | null>\n}\n\nexport interface UseProvingOptions {\n client: ProvingClient\n captureAdapter: CaptureAdapter\n request: CaptureRequest\n /** Payment method id for the nullifier registry lookup. */\n paymentMethodId: Hex\n /** Manual-select resolver forwarded to the adapter. */\n selectTransaction?: SelectTransaction\n /**\n * Forwarded to ProveXClient.fulfillIntent for the consumer's tx-flight strip.\n * Receives the raw TxContext — the hook does not inspect the argument.\n */\n onAwaitingSignature?: () => void\n /** Receives the raw tx hash extracted from the TxContext+hash payload. */\n onTransactionHash?: (hash: Hex) => void\n /** Called once when the flow reaches `complete`. */\n onComplete?: (attestation: V3AttestationResponseObject) => void\n}\n\nexport interface UseProvingReturn {\n state: ProvingState\n /** Phase 1+2: authenticate, capture, attest, nullifier-check → readyToFulfill. */\n authenticate: () => Promise<void>\n /** Phase 3: submit fulfillIntent → confirm → sync → complete. */\n fulfill: () => Promise<void>\n /** Re-run attestation after a pre-fulfilment error. */\n retryVerification: () => Promise<void>\n /** Re-run submission after a post-fulfilment error. */\n retrySubmission: () => Promise<void>\n}\n\n/** Minimum visible-state pacing from SubmitStep.tsx:404-417 — keep a state\n * on screen at least `floorMs` so the user perceives the step. */\nasync function withMinimumVisible(floorMs: number, started: number): Promise<void> {\n const elapsed = Date.now() - started\n if (elapsed < floorMs) await sleep(floorMs - elapsed)\n}\n\n/**\n * Detect a wallet-rejection error structurally (without importing ProveXError).\n * ProveXClient always re-wraps user rejections as ProveXError with\n * code === 'WALLET_REJECTED' before the error escapes fulfillIntent.\n */\nfunction isWalletRejection(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n (error as { code: unknown }).code === 'WALLET_REJECTED'\n )\n}\n\nexport function useProving(options: UseProvingOptions): UseProvingReturn {\n const {\n client,\n captureAdapter,\n request,\n paymentMethodId,\n selectTransaction,\n onAwaitingSignature,\n onTransactionHash,\n onComplete,\n } = options\n\n const [state, setState] = useState<ProvingState>({ status: 'authenticate' })\n // Re-entry guard mirroring isRequestingAttestationRef (SubmitStep.tsx:199).\n const inFlightRef = useRef(false)\n // Separate in-flight guard for submit — prevents double on-chain spend from\n // two rapid fulfill() calls before a re-render.\n const submitInFlightRef = useRef(false)\n\n const runCaptureAndAttest = useCallback(async (): Promise<void> => {\n if (inFlightRef.current) return\n inFlightRef.current = true\n try {\n setState({ status: 'capture' })\n const handle = await captureAdapter.authenticateAndCapture(request, { selectTransaction })\n\n const attestStarted = Date.now()\n setState({ status: 'attest' })\n const verified = await captureAdapter.attest(handle, request)\n // SubmitStep.tsx:414-417: verifying state must be visible for ≥1000ms.\n await withMinimumVisible(1000, attestStarted)\n\n // Nullifier double-spend check (SubmitStep.tsx:419-426).\n const nullifier = extractNullifier(verified.attestation)\n if (nullifier) {\n const isUsed = await client.checkNullifierUsed({ paymentMethodId, nullifier })\n if (isUsed === true) {\n throw new Error(\n 'This payment has already been verified. The nullifier has already been used onchain.',\n )\n }\n }\n\n setState({ status: 'readyToFulfill', attestation: verified })\n } catch (error) {\n setState({\n status: 'error',\n message: error instanceof Error ? error.message : 'Verification failed',\n isSubmissionError: false,\n })\n } finally {\n inFlightRef.current = false\n }\n }, [captureAdapter, client, request, paymentMethodId, selectTransaction])\n\n const submit = useCallback(\n async (verified: VerifiedAttestation): Promise<void> => {\n // Re-entry guard: prevent two rapid submit calls from both calling\n // fulfillIntent (double on-chain spend). Mirrors inFlightRef pattern.\n if (submitInFlightRef.current) return\n submitInFlightRef.current = true\n\n const submitStarted = Date.now()\n setState({ status: 'fulfillIntent', attestation: verified })\n try {\n await client.fulfillIntent({\n intentHash: request.intent.intentHash,\n attestation: verified.attestation,\n verificationData: verified.verificationData,\n // Wrap consumer callbacks to match the real buildWritableMethod\n // call shapes (ProveXClient.ts ~890, ~903).\n onAwaitingSignature: onAwaitingSignature\n ? (_info: TxCallbackContext) => { onAwaitingSignature() }\n : undefined,\n // buildWritableMethod fires onTransactionHash with { ...ctx, hash }\n // (ProveXClient.ts ~903). Extract hash and forward to the consumer's\n // simpler (hash: Hex) => void API.\n onTransactionHash: onTransactionHash\n ? (info: TxCallbackContext & { hash: Hex }) => {\n // After the tx is broadcast (a separate async tick before the\n // receipt arrives), surface the confirm state so it is\n // observable by consumers.\n setState({ status: 'confirm', attestation: verified })\n onTransactionHash(info.hash)\n }\n : ((_info: TxCallbackContext & { hash: Hex }) => {\n setState({ status: 'confirm', attestation: verified })\n }),\n })\n await withMinimumVisible(500, submitStarted)\n\n // fulfillIntent already awaited receipt + indexer sync internally\n // (ProveXClient.ts:906-915). Surface sync as a real visible state\n // (confirm was set in onTransactionHash above); then await a\n // minimum-visible window before complete so sync is not batched away.\n setState({ status: 'sync', attestation: verified })\n await sleep(600)\n setState({ status: 'complete', attestation: verified })\n onComplete?.(verified.attestation)\n } catch (error) {\n // Wallet rejection: return to readyToFulfill so the user can retry\n // without losing the attestation (mirrors SubmitStep.tsx:571-573).\n if (isWalletRejection(error)) {\n setState({ status: 'readyToFulfill', attestation: verified })\n return\n }\n setState({\n status: 'error',\n message: error instanceof Error ? error.message : 'Submission failed',\n isSubmissionError: true,\n attestation: verified,\n })\n } finally {\n submitInFlightRef.current = false\n }\n },\n [client, request.intent.intentHash, onAwaitingSignature, onTransactionHash, onComplete],\n )\n\n const authenticate = useCallback(() => runCaptureAndAttest(), [runCaptureAndAttest])\n\n const fulfill = useCallback(async () => {\n if (state.status !== 'readyToFulfill') return\n await submit(state.attestation)\n }, [state, submit])\n\n const retryVerification = useCallback(async () => {\n setState({ status: 'authenticate' })\n await runCaptureAndAttest()\n }, [runCaptureAndAttest])\n\n const retrySubmission = useCallback(async () => {\n if (state.status !== 'error' || !state.attestation) return\n await submit(state.attestation)\n }, [state, submit])\n\n return { state, authenticate, fulfill, retryVerification, retrySubmission }\n}\n"]}
1
+ {"version":3,"sources":["../src/buyerTeeAttestation.ts","../src/types.ts","../src/derivations.ts","../src/useProving.ts","../src/useManualTransactionSelect.ts","../src/buildCaptureRequest.ts"],"names":["calculateNetAmount","calculateFeeAmount","convertTokenInputToCurrency","formatUnits","sleep","useState","useRef","useCallback","extractNullifier","getExtensionPlatform"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @fileoverview Buyer trusted-execution attestation client.\n *\n * In PeerAuth extension 0.5.2+, capture produces a buyerTee proof —\n * `{ proofType: 'buyerTee', encryptedSessionMaterial, params }` — where the\n * bank session is already encrypted toward the enclave (cookies never reach\n * our page in plaintext). Instead of re-signing a Reclaim proof through our\n * gate, we hand that material to zkp2p's trusted-execution attestation\n * service, which verifies the payment inside an Amazon Web Services Nitro\n * enclave and signs the attestation directly.\n *\n * The response `responseObject` is the same `V3AttestationResponseObject`\n * shape our gate returns, so it flows through the existing\n * `encodeV3PaymentProof` path unchanged.\n *\n * Moved verbatim from `@provex/ui` (packages/ui/src/lib/buyerTeeAttestation.ts)\n * with the base URL changed from a `@provex/ui` config import to an injected\n * `attestationBaseUrl` argument so this package carries no `@provex/ui` dep.\n */\n\nimport type { Hex } from 'viem'\nimport type { V3AttestationResponseObject } from '@provex/utils'\n\n/**\n * Runtime shape of a buyerTee proof emitted by the PeerAuth extension.\n * Narrow with {@link asBuyerTeeProof} before use.\n */\nexport interface BuyerTeeProof {\n proofType: 'buyerTee'\n /** Session material already encrypted toward the enclave (opaque to us). */\n encryptedSessionMaterial: string\n /** Platform-specific params, e.g. Revolut `{ index }`, Chase `{ index }`. */\n params: unknown\n actionType?: string\n actionPlatform?: string\n}\n\n/**\n * Narrow an extension proof to a buyerTee proof, or null when it is the\n * legacy Reclaim shape (or malformed).\n */\nexport function asBuyerTeeProof(proof: unknown): BuyerTeeProof | null {\n if (!proof || typeof proof !== 'object') return null\n const candidate = proof as Record<string, unknown>\n if (candidate.proofType !== 'buyerTee') return null\n if (typeof candidate.encryptedSessionMaterial !== 'string') return null\n return candidate as unknown as BuyerTeeProof\n}\n\n/** Intent details the enclave binds the attestation to. */\nexport interface BuyerTeeIntent {\n intentHash: Hex\n amount: string\n timestampMs: string\n paymentMethod: Hex\n fiatCurrency: Hex\n conversionRate: string\n payeeDetails: Hex\n timestampBufferMs?: string\n}\n\nexport interface RequestBuyerTeeAttestationArgs {\n /** Gate/attestation origin (was `gateForwarderBaseUrl` in @provex/ui). */\n attestationBaseUrl: string\n /** Extension platform / bank key, e.g. `revolut`, `chase`. */\n platform: string\n /** Action type, e.g. `transfer_revolut`, `transfer_zelle`. */\n actionType: string\n /** Encrypted session material from the buyerTee capture. */\n encryptedSessionMaterial: string\n /** Platform params from the capture (e.g. `{ index }`). */\n params: unknown\n /** PulseChain UnifiedPaymentVerifier the gate re-signs the attestation for. */\n verifyingContract: string\n intent: BuyerTeeIntent\n}\n\n/**\n * Verify a buyer payment via the gate. The gate proxies the encrypted capture\n * to zkp2p's enclave, then re-signs the resulting attestation for PulseChain\n * with our witness key — the enclave itself refuses to sign for 369.\n *\n * Throws with the gate's error text on a non-2xx response.\n */\nexport async function requestBuyerTeeAttestation(\n args: RequestBuyerTeeAttestationArgs,\n): Promise<V3AttestationResponseObject> {\n const url = `${args.attestationBaseUrl}/api/v0/attestation/buyer-verify/${encodeURIComponent(args.platform)}/${encodeURIComponent(args.actionType)}`\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n encryptedSessionMaterial: args.encryptedSessionMaterial,\n params: args.params,\n verifyingContract: args.verifyingContract,\n intent: args.intent,\n }),\n })\n\n if (!response.ok) {\n const detail = await response.text().catch(() => '')\n throw new Error(\n `Trusted-execution attestation failed (${response.status}): ${detail || response.statusText}`,\n )\n }\n\n const json = (await response.json()) as\n | { responseObject?: V3AttestationResponseObject }\n | V3AttestationResponseObject\n const responseObject =\n (json as { responseObject?: V3AttestationResponseObject }).responseObject ??\n (json as V3AttestationResponseObject)\n\n if (!responseObject?.typedDataValue || !responseObject?.signature) {\n throw new Error('Trusted-execution attestation response missing typedDataValue/signature')\n }\n return responseObject\n}\n","/**\n * @fileoverview Seam contract + state types for the proving orchestrator.\n *\n * `CaptureAdapter` is the swappable capture backend (extension+enclave today,\n * TLSNotary tomorrow). `ProvingState` is the single discriminated-union\n * machine that subsumes the live proof-substep machine (ProofPanel) and the\n * attestation lifecycle (submitStepDerivations). Monetary values stay bigint\n * in base units; the only stringified amount is `ProvingIntent.amount`, which\n * mirrors the existing on-the-wire BuyerTeeIntent shape (buyerTeeAttestation).\n */\n\nimport type { Hex } from 'viem'\nimport type { V3AttestationResponseObject } from '@provex/utils'\nimport type { TokenInfo } from '@provex/utils/tokens'\n\n/** Transaction candidate the adapter surfaces for manual selection. Mirrors\n * the `MetadataMessage` fields the live TransactionSelector consumes; kept\n * structural so the package needs no @provex/extension-react import. */\nexport interface CaptureCandidate {\n amount: number | string\n currency?: string\n date?: string | number | Date\n originalIndex: number\n}\n\n/** Identity the attestor binds the attestation to. Mirrors the live\n * attestationIntent built in SubmitStep.tsx:333-342 / BuyerTeeIntent. */\nexport interface ProvingIntent {\n intentHash: Hex\n /** bigint base units, stringified (matches BuyerTeeIntent.amount). */\n amount: string\n timestampMs: string\n paymentMethod: Hex\n fiatCurrency: Hex\n conversionRate: string\n /** payeeDetailsHash */\n payeeDetails: Hex\n timestampBufferMs?: string\n}\n\n/** What the orchestrator hands a capture adapter. */\nexport interface CaptureRequest {\n /** Parent provider key, e.g. 'zelle', 'revolut'. */\n provider: string\n /** Extension platform slug, e.g. 'chase'. */\n platform: string\n /** Action type, e.g. 'transfer_zelle'. */\n actionType: string\n intent: ProvingIntent\n /** UnifiedPaymentVerifier the attestation is re-signed for. */\n verifyingContract: Hex\n chainId: number\n /**\n * Settlement token for this intent (e.g. USDC on the target chain).\n * Used by the adapter to compute the fiat currency amount via\n * convertTokenInputToCurrency so auto-match works correctly for non-1:1 rates.\n */\n token: TokenInfo\n}\n\n/** Opaque per-backend capture state; the orchestrator passes it back unmodified. */\nexport interface CaptureHandle {\n readonly kind: string\n readonly material: unknown\n}\n\n/** Uniform capture output. `verificationData` names the signing witness to\n * the on-chain verifier (enclave signer for buyerTee — SubmitStep.tsx:473-475). */\nexport interface VerifiedAttestation {\n attestation: V3AttestationResponseObject\n verificationData: Hex\n}\n\n/** Resolver the UI provides so the adapter can collect a manual transaction\n * pick when auto-match fails (GenerateProofStep.tsx:391-404). */\nexport type SelectTransaction = (candidates: CaptureCandidate[]) => Promise<number>\n\n/** A pluggable capture backend. One per capture mechanism. */\nexport interface CaptureAdapter {\n /** Stable id for telemetry, e.g. 'extension-enclave'. */\n readonly kind: string\n /** Phase 1: authenticate + capture, returning opaque material to attest. */\n authenticateAndCapture(\n request: CaptureRequest,\n options?: { selectTransaction?: SelectTransaction },\n ): Promise<CaptureHandle>\n /** Phase 2: produce a verified attestation from the captured material. */\n attest(handle: CaptureHandle, request: CaptureRequest): Promise<VerifiedAttestation>\n}\n\n/** Inputs the orchestrator hook needs to drive the machine. */\nexport interface ProvingRequest {\n request: CaptureRequest\n /** Optional manual-select resolver forwarded to the adapter. */\n selectTransaction?: SelectTransaction\n}\n\n/**\n * The proving state machine (spec lines 141-151). `attest` auto-runs on\n * entry; `readyToFulfill` waits for the user's explicit Complete Order\n * action; `error.isSubmissionError` routes retry to Retry Submission\n * (post-fulfilment) vs Retry Verification (pre-fulfilment) — exactly the\n * live SubmitStep.tsx:931-955 routing.\n */\nexport type ProvingState =\n | { status: 'authenticate' }\n | { status: 'capture' }\n | { status: 'attest' }\n | { status: 'readyToFulfill'; attestation: VerifiedAttestation }\n | { status: 'fulfillIntent'; attestation: VerifiedAttestation }\n | { status: 'confirm'; attestation: VerifiedAttestation }\n | { status: 'sync'; attestation: VerifiedAttestation }\n | { status: 'complete'; attestation: VerifiedAttestation }\n | {\n status: 'error'\n message: string\n /** true ⇒ failure occurred during/after on-chain submission. */\n isSubmissionError: boolean\n attestation?: VerifiedAttestation\n /** Typed reason, set for cases the consumer must branch on. Currently\n * 'enclave-unavailable' (non-migrated provider → caller falls back to\n * Reclaim) and 'manual-selection-required' (auto-match failed, no\n * resolver supplied). Absent for ordinary verification/submission errors. */\n code?: 'enclave-unavailable' | 'manual-selection-required'\n }\n\n/**\n * Thrown by a capture adapter when the provider returned no buyerTee capture\n * (a non-migrated / Reclaim-only provider). The caller (UnifiedBuy) catches\n * this to fall back to its retained Reclaim branch — the package itself does\n * not implement Reclaim (decision (a), spec line 256).\n */\nexport class EnclaveCaptureUnavailable extends Error {\n readonly platform: string\n constructor(platform: string) {\n super(`No enclave (buyerTee) capture available for platform \"${platform}\".`)\n this.name = 'EnclaveCaptureUnavailable'\n this.platform = platform\n }\n}\n\n/**\n * Thrown by a capture adapter when auto-match failed and no `selectTransaction`\n * resolver was provided (e.g. a headless wallet). Carries the candidates so a\n * UI caller can render the manual selector and retry (spec lines 263-267).\n */\nexport class ManualSelectionRequired extends Error {\n readonly candidates: CaptureCandidate[]\n constructor(candidates: CaptureCandidate[]) {\n super('Manual transaction selection is required but no resolver was provided.')\n this.name = 'ManualSelectionRequired'\n this.candidates = candidates\n }\n}\n","/**\n * @fileoverview Pure proving derivations moved from @provex/ui\n * (packages/ui/src/components/UnifiedBuy/ProofPanel/submitStepDerivations.ts).\n *\n * Bigint throughout — see the `money-values-stay-bigint-until-display` rule.\n * The only string conversions happen at the formatter boundary (formatUnits),\n * which is the display layer.\n */\n\nimport type { V3AttestationResponseObject } from '@provex/utils'\nimport type { TokenInfo } from '@provex/utils/tokens'\nimport { formatUnits } from '@provex/utils/units'\nimport { convertTokenInputToCurrency } from '@provex/utils/conversionRates'\nimport { calculateFeeAmount, calculateNetAmount } from '@provex/utils/fees'\n\nexport interface PartialFulfillment {\n /** Gross USDC the gate authorized for release (typedData.releaseAmount). */\n grossReleased: bigint\n /** Net USDC the buyer actually receives (grossReleased − fee). */\n netToBuyer: bigint\n /** Protocol fee in USDC base units. 0n when no fee applies. */\n protocolFee: bigint\n /** Locked USDC returned to the maker (intent.amount − grossReleased). */\n returnedToMaker: bigint\n contractedFiatDisplay: string\n attestedFiatDisplay: string\n netToBuyerDisplay: string\n returnedToMakerDisplay: string\n /** null when protocolFee === 0n so the caller can skip rendering the row. */\n protocolFeeDisplay: string | null\n}\n\n/**\n * Partial-fulfilment breakdown derived from the verified attestation.\n *\n * Returns `null` when:\n * - attestation isn't present (nothing to compare)\n * - releaseAmount >= intent.amount (full settlement, no partial story)\n * - intent/tokenInfo missing (defensive)\n */\nexport function computePartialFulfillment({\n attestation,\n intentAmount,\n conversionRate,\n tokenInfo,\n feeBps,\n}: {\n attestation: V3AttestationResponseObject | null\n intentAmount: bigint | null | undefined\n conversionRate: bigint | null | undefined\n tokenInfo: TokenInfo | null\n feeBps: bigint | null | undefined\n}): PartialFulfillment | null {\n if (!attestation || !intentAmount || !conversionRate || !tokenInfo) return null\n const grossReleased = BigInt(attestation.typedDataValue.releaseAmount)\n if (grossReleased >= intentAmount) return null\n\n const returnedToMaker = intentAmount - grossReleased\n const effectiveFeeBps = feeBps ?? 0n\n const netToBuyer =\n effectiveFeeBps > 0n\n ? calculateNetAmount({ grossAmount: grossReleased, feeBps: effectiveFeeBps })\n : grossReleased\n const protocolFee =\n effectiveFeeBps > 0n\n ? calculateFeeAmount({ amount: grossReleased, feeBps: effectiveFeeBps })\n : 0n\n\n const contractedFiatRaw = convertTokenInputToCurrency({\n token: tokenInfo,\n amountOutInt: intentAmount,\n rate: conversionRate,\n })\n const attestedFiatRaw = convertTokenInputToCurrency({\n token: tokenInfo,\n amountOutInt: grossReleased,\n rate: conversionRate,\n })\n\n const formatTokenDisplay = (value: bigint): string =>\n formatUnits(value, {\n decimals: tokenInfo.decimals,\n truncateDecimals: 2,\n padDecimals: 2,\n })\n\n return {\n grossReleased,\n netToBuyer,\n protocolFee,\n returnedToMaker,\n contractedFiatDisplay: formatTokenDisplay(contractedFiatRaw),\n attestedFiatDisplay: formatTokenDisplay(attestedFiatRaw),\n netToBuyerDisplay: formatTokenDisplay(netToBuyer),\n returnedToMakerDisplay: formatTokenDisplay(returnedToMaker),\n protocolFeeDisplay: protocolFee > 0n ? formatTokenDisplay(protocolFee) : null,\n }\n}\n","/**\n * @fileoverview useProving — headless orchestrator for the enclave proving\n * pipeline. Replicates the live AuthenticateStep/GenerateProofStep/SubmitStep\n * state machine (ProofPanel) with no presentation. Renders nothing; returns\n * state + actions for a UI (UnifiedBuy ProofPanel or a wallet) to bind to.\n *\n * Owns: the ProvingState machine, the nullifier double-spend check\n * (SubmitStep.tsx:419-426), the attest→fulfil sequencing, retry routing\n * (SubmitStep.tsx:931-955), and the minimum-visible-state timings\n * (SubmitStep.tsx:404-417). Delegates capture to the injected CaptureAdapter,\n * the on-chain write (+ receipt + indexer sync) to ProveXClient.fulfillIntent.\n */\n\nimport { useCallback, useRef, useState } from 'react'\nimport type { Hex } from 'viem'\nimport { extractNullifier, type V3AttestationResponseObject } from '@provex/utils'\nimport { sleep } from '@provex/utils/time'\nimport { EnclaveCaptureUnavailable, ManualSelectionRequired } from './types'\nimport type {\n CaptureAdapter,\n CaptureRequest,\n SelectTransaction,\n ProvingState,\n VerifiedAttestation,\n} from './types'\n\n/**\n * Minimal context object that buildWritableMethod passes to the per-call hooks.\n * Kept structural so @provex/proving does not depend on @valve-tech/wallet-adapter.\n * Only `hash` is consumed by this hook — the rest is opaque context.\n */\ninterface TxCallbackContext {\n chainId: number\n request: unknown\n}\n\n/** Structural params for the fulfillIntent call. Mirrors FulfillIntentParams +\n * WriteHookParams from @provex/react but declared here to keep @provex/proving\n * from depending on the React package's built dist (which the test environment\n * doesn't require). The real ProveXClient.fulfillIntent satisfies this shape.\n *\n * IMPORTANT: onAwaitingSignature and onTransactionHash match the exact call\n * shapes in buildWritableMethod (ProveXClient.ts ~890, ~903):\n * params.onAwaitingSignature?.(ctx) → (info: TxCallbackContext) => void\n * params.onTransactionHash?.({ ...ctx, hash }) → (info: TxCallbackContext & { hash: Hex }) => void\n */\ninterface FulfillParams {\n intentHash: Hex\n attestation: V3AttestationResponseObject\n verificationData: Hex\n postIntentHookData?: Hex\n onAwaitingSignature?: (info: TxCallbackContext) => void\n onTransactionHash?: (info: TxCallbackContext & { hash: Hex }) => void\n}\n\n/** The structural slice of ProveXClient the orchestrator depends on. Keeping\n * it structural lets tests pass a light fake and avoids importing the class. */\nexport interface ProvingClient {\n /**\n * The chain this rail operates on. Must equal request.chainId — useProving\n * refuses to check the nullifier or submit on any other chain. A nullifier is\n * only meaningful on its intended chain; checking it on (or submitting it to)\n * a different chain is a cross-chain double-spend / replay vector.\n */\n readonly chainId: number\n fulfillIntent: (params: FulfillParams) => Promise<{ hash: Hex; receipt: unknown }>\n checkNullifierUsed: (params: { paymentMethodId: Hex; nullifier: Hex }) => Promise<boolean | null>\n}\n\nexport interface UseProvingOptions {\n client: ProvingClient\n captureAdapter: CaptureAdapter\n request: CaptureRequest\n /** Payment method id for the nullifier registry lookup. */\n paymentMethodId: Hex\n /** Manual-select resolver forwarded to the adapter. */\n selectTransaction?: SelectTransaction\n /**\n * Forwarded to ProveXClient.fulfillIntent for the consumer's tx-flight strip.\n * Receives the raw TxContext — the hook does not inspect the argument.\n */\n onAwaitingSignature?: () => void\n /** Receives the raw tx hash extracted from the TxContext+hash payload. */\n onTransactionHash?: (hash: Hex) => void\n /** Called once when the flow reaches `complete`. */\n onComplete?: (attestation: V3AttestationResponseObject) => void\n}\n\nexport interface UseProvingReturn {\n state: ProvingState\n /** Phase 1+2: authenticate, capture, attest, nullifier-check → readyToFulfill. */\n authenticate: () => Promise<void>\n /** Phase 3: submit fulfillIntent → confirm → sync → complete. */\n fulfill: () => Promise<void>\n /** Re-run attestation after a pre-fulfilment error. */\n retryVerification: () => Promise<void>\n /** Re-run submission after a post-fulfilment error. */\n retrySubmission: () => Promise<void>\n}\n\n/** Minimum visible-state pacing from SubmitStep.tsx:404-417 — keep a state\n * on screen at least `floorMs` so the user perceives the step. */\nasync function withMinimumVisible(floorMs: number, started: number): Promise<void> {\n const elapsed = Date.now() - started\n if (elapsed < floorMs) await sleep(floorMs - elapsed)\n}\n\n/**\n * Detect a wallet-rejection error structurally (without importing ProveXError).\n * ProveXClient always re-wraps user rejections as ProveXError with\n * code === 'WALLET_REJECTED' before the error escapes fulfillIntent.\n */\nfunction isWalletRejection(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n (error as { code: unknown }).code === 'WALLET_REJECTED'\n )\n}\n\nexport function useProving(options: UseProvingOptions): UseProvingReturn {\n const {\n client,\n captureAdapter,\n request,\n paymentMethodId,\n selectTransaction,\n onAwaitingSignature,\n onTransactionHash,\n onComplete,\n } = options\n\n const [state, setState] = useState<ProvingState>({ status: 'authenticate' })\n // Re-entry guard mirroring isRequestingAttestationRef (SubmitStep.tsx:199).\n const inFlightRef = useRef(false)\n // Separate in-flight guard for submit — prevents double on-chain spend from\n // two rapid fulfill() calls before a re-render.\n const submitInFlightRef = useRef(false)\n\n const runCaptureAndAttest = useCallback(async (): Promise<void> => {\n if (inFlightRef.current) return\n inFlightRef.current = true\n try {\n // Chain pinning: the nullifier must only ever be checked on the chain the\n // intent targets. A client wired to any other chain is refused before we\n // touch capture/attest/registry — never verify on the wrong chain.\n if (client.chainId !== request.chainId) {\n throw new Error(\n `Refusing to proceed: proving client is on chain ${client.chainId} but the intent targets chain ${request.chainId}. A nullifier must only be verified and fulfilled on its intended chain.`,\n )\n }\n\n setState({ status: 'capture' })\n const handle = await captureAdapter.authenticateAndCapture(request, { selectTransaction })\n\n const attestStarted = Date.now()\n setState({ status: 'attest' })\n const verified = await captureAdapter.attest(handle, request)\n // SubmitStep.tsx:414-417: verifying state must be visible for ≥1000ms.\n await withMinimumVisible(1000, attestStarted)\n\n // Nullifier double-spend pre-check — FAIL CLOSED. We must positively\n // confirm the nullifier is unused on the target chain before allowing\n // fulfillment. Any inability to verify (cannot extract, registry\n // unreachable/errored) blocks. \"If we can't verify the nullifier has not\n // been used, we cannot use it.\"\n const nullifier = extractNullifier(verified.attestation)\n if (!nullifier) {\n throw new Error(\n 'Could not extract a nullifier from the attestation; cannot confirm it is unused. Refusing to proceed.',\n )\n }\n const isUsed = await client.checkNullifierUsed({ paymentMethodId, nullifier })\n if (isUsed !== false) {\n throw new Error(\n isUsed === true\n ? 'This payment has already been verified. The nullifier has already been used onchain.'\n : 'Could not verify the nullifier is unused (registry unreachable). Refusing to proceed — please retry.',\n )\n }\n\n // only an explicit `false` reaches here\n setState({ status: 'readyToFulfill', attestation: verified })\n } catch (error) {\n const code =\n error instanceof EnclaveCaptureUnavailable\n ? ('enclave-unavailable' as const)\n : error instanceof ManualSelectionRequired\n ? ('manual-selection-required' as const)\n : undefined\n setState({\n status: 'error',\n message: error instanceof Error ? error.message : 'Verification failed',\n isSubmissionError: false,\n code,\n })\n } finally {\n inFlightRef.current = false\n }\n }, [captureAdapter, client, request, paymentMethodId, selectTransaction])\n\n const submit = useCallback(\n async (verified: VerifiedAttestation): Promise<void> => {\n // Re-entry guard: prevent two rapid submit calls from both calling\n // fulfillIntent (double on-chain spend). Mirrors inFlightRef pattern.\n if (submitInFlightRef.current) return\n submitInFlightRef.current = true\n\n const submitStarted = Date.now()\n setState({ status: 'fulfillIntent', attestation: verified })\n try {\n // Chain pinning (re-asserted at submit): the nullifier was verified on\n // request.chainId; it must only ever be submitted to that same chain.\n if (client.chainId !== request.chainId) {\n throw new Error(\n `Refusing to proceed: proving client is on chain ${client.chainId} but the intent targets chain ${request.chainId}. A nullifier must only be verified and fulfilled on its intended chain.`,\n )\n }\n await client.fulfillIntent({\n intentHash: request.intent.intentHash,\n attestation: verified.attestation,\n verificationData: verified.verificationData,\n // Wrap consumer callbacks to match the real buildWritableMethod\n // call shapes (ProveXClient.ts ~890, ~903).\n onAwaitingSignature: onAwaitingSignature\n ? (_info: TxCallbackContext) => { onAwaitingSignature() }\n : undefined,\n // buildWritableMethod fires onTransactionHash with { ...ctx, hash }\n // (ProveXClient.ts ~903). Extract hash and forward to the consumer's\n // simpler (hash: Hex) => void API.\n onTransactionHash: onTransactionHash\n ? (info: TxCallbackContext & { hash: Hex }) => {\n // After the tx is broadcast (a separate async tick before the\n // receipt arrives), surface the confirm state so it is\n // observable by consumers.\n setState({ status: 'confirm', attestation: verified })\n onTransactionHash(info.hash)\n }\n : ((_info: TxCallbackContext & { hash: Hex }) => {\n setState({ status: 'confirm', attestation: verified })\n }),\n })\n await withMinimumVisible(500, submitStarted)\n\n // fulfillIntent already awaited receipt + indexer sync internally\n // (ProveXClient.ts:906-915). Surface sync as a real visible state\n // (confirm was set in onTransactionHash above); then await a\n // minimum-visible window before complete so sync is not batched away.\n setState({ status: 'sync', attestation: verified })\n await sleep(600)\n setState({ status: 'complete', attestation: verified })\n onComplete?.(verified.attestation)\n } catch (error) {\n // Wallet rejection: return to readyToFulfill so the user can retry\n // without losing the attestation (mirrors SubmitStep.tsx:571-573).\n if (isWalletRejection(error)) {\n setState({ status: 'readyToFulfill', attestation: verified })\n return\n }\n setState({\n status: 'error',\n message: error instanceof Error ? error.message : 'Submission failed',\n isSubmissionError: true,\n attestation: verified,\n })\n } finally {\n submitInFlightRef.current = false\n }\n },\n [client, request.intent.intentHash, onAwaitingSignature, onTransactionHash, onComplete],\n )\n\n const authenticate = useCallback(() => runCaptureAndAttest(), [runCaptureAndAttest])\n\n const fulfill = useCallback(async () => {\n if (state.status !== 'readyToFulfill') return\n await submit(state.attestation)\n }, [state, submit])\n\n const retryVerification = useCallback(async () => {\n setState({ status: 'authenticate' })\n await runCaptureAndAttest()\n }, [runCaptureAndAttest])\n\n const retrySubmission = useCallback(async () => {\n if (state.status !== 'error' || !state.attestation) return\n await submit(state.attestation)\n }, [state, submit])\n\n return { state, authenticate, fulfill, retryVerification, retrySubmission }\n}\n","import { useCallback, useRef, useState } from 'react'\nimport type { CaptureCandidate, SelectTransaction } from './types'\n\nexport interface ManualTransactionSelect {\n /**\n * Resolver injected into useProving — the enclave adapter calls this when\n * auto-match of the bank transaction fails (extensionEnclave.ts:189-190).\n */\n selectTransaction: SelectTransaction\n /**\n * Non-null while the adapter is awaiting a pick; drives the\n * TransactionSelector rendered inside GenerateProofStep.\n */\n candidates: CaptureCandidate[] | null\n /**\n * Called by TransactionSelector with the chosen candidate's originalIndex.\n * Resolves the pending promise and resets candidates to null.\n */\n pick: (originalIndex: number) => void\n}\n\n/**\n * Bridge the framework-agnostic {@link SelectTransaction} resolver the enclave\n * adapter expects (extensionEnclave.ts:189-190) to React state the\n * GenerateProofStep TransactionSelector can render.\n *\n * The adapter awaits the promise returned by `selectTransaction`; the user's\n * call to `pick` resolves it with the chosen `originalIndex`.\n *\n * `useRef` is justified here exclusively for holding the pending promise\n * resolver across renders without triggering a re-render cycle — the single\n * sanctioned use per the react-state-preferences memory.\n */\nexport function useManualTransactionSelect(): ManualTransactionSelect {\n const [candidates, setCandidates] = useState<CaptureCandidate[] | null>(null)\n const resolveRef = useRef<((originalIndex: number) => void) | null>(null)\n\n const selectTransaction = useCallback<SelectTransaction>((next) => {\n return new Promise<number>((resolve) => {\n resolveRef.current = resolve\n setCandidates(next)\n })\n }, [])\n\n const pick = useCallback((originalIndex: number) => {\n resolveRef.current?.(originalIndex)\n resolveRef.current = null\n setCandidates(null)\n }, [])\n\n return { selectTransaction, candidates, pick }\n}\n","/**\n * @fileoverview Pure builder that assembles a {@link CaptureRequest} from\n * resolved order data.\n *\n * Hoisted verbatim from the intent-assembly block in SubmitStep.tsx:312-342\n * so the mapping is unit-tested in isolation and reusable by the proving shell.\n *\n * Monetary values stay bigint in base units through the entire call stack;\n * this is the single boundary where they are stringified for the attestor\n * wire format.\n */\n\nimport type { Hex } from 'viem'\nimport type { CaptureRequest } from './types'\nimport { getExtensionPlatform } from '@provex/utils/payment'\nimport type { ProviderKey, SubProviderKey } from '@provex/utils/payment'\nimport type { TokenInfo } from '@provex/utils/tokens'\n\n/** All resolved order fields required to build a {@link CaptureRequest}. */\nexport interface BuildCaptureRequestInput {\n /** On-chain intent hash. */\n intentHash: Hex\n /** Chain the intent lives on. */\n chainId: number\n /** Bank-level sub-provider, e.g. `'zelle-chase'`, or `null` for top-level providers. */\n subProvider: SubProviderKey | null\n /** Provider key from the deposit. May itself be a sub-provider key (contains a dash). */\n providerKey: ProviderKey | SubProviderKey\n /** Address of the UnifiedPaymentVerifier contract for this chain. */\n verifyingContract: Hex\n /** payeeDetailsHash from the matched verifier record. */\n payeeDetailsHash: Hex\n /** Settlement token for this intent (used by the adapter for amount conversion). */\n token: TokenInfo\n /** Resolved intent fields, monetary values in bigint base units. */\n intent: {\n amount: bigint\n timestamp: bigint\n paymentMethod: Hex | undefined\n fiatCurrency: Hex\n conversionRate: bigint\n }\n}\n\n/**\n * Build the {@link CaptureRequest} from resolved order data.\n *\n * Pure hoist of the intent-assembly block in SubmitStep.tsx:312-342.\n * No side effects, no I/O, no React. Safe to call in any environment.\n *\n * @param input - All resolved order fields needed to build the request.\n * @returns A fully-formed {@link CaptureRequest} ready for a {@link CaptureAdapter}.\n */\nexport function buildCaptureRequest(input: BuildCaptureRequestInput): CaptureRequest {\n const parentProvider = input.providerKey.includes('-')\n ? input.providerKey.split('-')[0]\n : input.providerKey\n\n const platform = getExtensionPlatform(input.subProvider ?? input.providerKey)\n\n return {\n provider: input.providerKey,\n platform,\n actionType: `transfer_${parentProvider}`,\n verifyingContract: input.verifyingContract,\n chainId: input.chainId,\n token: input.token,\n intent: {\n intentHash: input.intentHash,\n amount: input.intent.amount.toString(),\n timestampMs: (input.intent.timestamp * 1000n).toString(),\n paymentMethod: input.intent.paymentMethod ?? ('0x' as Hex),\n fiatCurrency: input.intent.fiatCurrency,\n conversionRate: input.intent.conversionRate.toString(),\n payeeDetails: input.payeeDetailsHash,\n timestampBufferMs: '300000',\n },\n }\n}\n"]}