@provex/proving 1.8.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.
@@ -0,0 +1,49 @@
1
+ export { b as BuyerTeeIntent, B as BuyerTeeProof, E as ExtensionEnclaveAdapterConfig, P as PeerClient, R as RequestBuyerTeeAttestationArgs, a as asBuyerTeeProof, c as createExtensionEnclaveAdapter, r as requestBuyerTeeAttestation } from './extensionEnclave-BjxXsuaj.cjs';
2
+ export { c as CaptureAdapter, C as CaptureCandidate, b as CaptureHandle, a as CaptureRequest, E as EnclaveCaptureUnavailable, M as ManualSelectionRequired, P as ProvingIntent, d as ProvingRequest, e as ProvingState, S as SelectTransaction, V as VerifiedAttestation } from './types-ByiBb7AN.cjs';
3
+ import { V3AttestationResponseObject } from '@provex/utils';
4
+ import { TokenInfo } from '@provex/utils/tokens';
5
+ export { ProvingClient, UseProvingOptions, UseProvingReturn, useProving } from './useProving.cjs';
6
+ import 'viem';
7
+
8
+ /**
9
+ * @fileoverview Pure proving derivations moved from @provex/ui
10
+ * (packages/ui/src/components/UnifiedBuy/ProofPanel/submitStepDerivations.ts).
11
+ *
12
+ * Bigint throughout — see the `money-values-stay-bigint-until-display` rule.
13
+ * The only string conversions happen at the formatter boundary (formatUnits),
14
+ * which is the display layer.
15
+ */
16
+
17
+ interface PartialFulfillment {
18
+ /** Gross USDC the gate authorized for release (typedData.releaseAmount). */
19
+ grossReleased: bigint;
20
+ /** Net USDC the buyer actually receives (grossReleased − fee). */
21
+ netToBuyer: bigint;
22
+ /** Protocol fee in USDC base units. 0n when no fee applies. */
23
+ protocolFee: bigint;
24
+ /** Locked USDC returned to the maker (intent.amount − grossReleased). */
25
+ returnedToMaker: bigint;
26
+ contractedFiatDisplay: string;
27
+ attestedFiatDisplay: string;
28
+ netToBuyerDisplay: string;
29
+ returnedToMakerDisplay: string;
30
+ /** null when protocolFee === 0n so the caller can skip rendering the row. */
31
+ protocolFeeDisplay: string | null;
32
+ }
33
+ /**
34
+ * Partial-fulfilment breakdown derived from the verified attestation.
35
+ *
36
+ * Returns `null` when:
37
+ * - attestation isn't present (nothing to compare)
38
+ * - releaseAmount >= intent.amount (full settlement, no partial story)
39
+ * - intent/tokenInfo missing (defensive)
40
+ */
41
+ declare function computePartialFulfillment({ attestation, intentAmount, conversionRate, tokenInfo, feeBps, }: {
42
+ attestation: V3AttestationResponseObject | null;
43
+ intentAmount: bigint | null | undefined;
44
+ conversionRate: bigint | null | undefined;
45
+ tokenInfo: TokenInfo | null;
46
+ feeBps: bigint | null | undefined;
47
+ }): PartialFulfillment | null;
48
+
49
+ export { type PartialFulfillment, computePartialFulfillment };
@@ -0,0 +1,49 @@
1
+ export { b as BuyerTeeIntent, B as BuyerTeeProof, E as ExtensionEnclaveAdapterConfig, P as PeerClient, R as RequestBuyerTeeAttestationArgs, a as asBuyerTeeProof, c as createExtensionEnclaveAdapter, r as requestBuyerTeeAttestation } from './extensionEnclave-DLCjCWxT.js';
2
+ export { c as CaptureAdapter, C as CaptureCandidate, b as CaptureHandle, a as CaptureRequest, E as EnclaveCaptureUnavailable, M as ManualSelectionRequired, P as ProvingIntent, d as ProvingRequest, e as ProvingState, S as SelectTransaction, V as VerifiedAttestation } from './types-ByiBb7AN.js';
3
+ import { V3AttestationResponseObject } from '@provex/utils';
4
+ import { TokenInfo } from '@provex/utils/tokens';
5
+ export { ProvingClient, UseProvingOptions, UseProvingReturn, useProving } from './useProving.js';
6
+ import 'viem';
7
+
8
+ /**
9
+ * @fileoverview Pure proving derivations moved from @provex/ui
10
+ * (packages/ui/src/components/UnifiedBuy/ProofPanel/submitStepDerivations.ts).
11
+ *
12
+ * Bigint throughout — see the `money-values-stay-bigint-until-display` rule.
13
+ * The only string conversions happen at the formatter boundary (formatUnits),
14
+ * which is the display layer.
15
+ */
16
+
17
+ interface PartialFulfillment {
18
+ /** Gross USDC the gate authorized for release (typedData.releaseAmount). */
19
+ grossReleased: bigint;
20
+ /** Net USDC the buyer actually receives (grossReleased − fee). */
21
+ netToBuyer: bigint;
22
+ /** Protocol fee in USDC base units. 0n when no fee applies. */
23
+ protocolFee: bigint;
24
+ /** Locked USDC returned to the maker (intent.amount − grossReleased). */
25
+ returnedToMaker: bigint;
26
+ contractedFiatDisplay: string;
27
+ attestedFiatDisplay: string;
28
+ netToBuyerDisplay: string;
29
+ returnedToMakerDisplay: string;
30
+ /** null when protocolFee === 0n so the caller can skip rendering the row. */
31
+ protocolFeeDisplay: string | null;
32
+ }
33
+ /**
34
+ * Partial-fulfilment breakdown derived from the verified attestation.
35
+ *
36
+ * Returns `null` when:
37
+ * - attestation isn't present (nothing to compare)
38
+ * - releaseAmount >= intent.amount (full settlement, no partial story)
39
+ * - intent/tokenInfo missing (defensive)
40
+ */
41
+ declare function computePartialFulfillment({ attestation, intentAmount, conversionRate, tokenInfo, feeBps, }: {
42
+ attestation: V3AttestationResponseObject | null;
43
+ intentAmount: bigint | null | undefined;
44
+ conversionRate: bigint | null | undefined;
45
+ tokenInfo: TokenInfo | null;
46
+ feeBps: bigint | null | undefined;
47
+ }): PartialFulfillment | null;
48
+
49
+ export { type PartialFulfillment, computePartialFulfillment };
package/dist/index.js ADDED
@@ -0,0 +1,51 @@
1
+ export { useProving } from './chunk-LFDS3XGK.js';
2
+ export { EnclaveCaptureUnavailable, ManualSelectionRequired, asBuyerTeeProof, createExtensionEnclaveAdapter, requestBuyerTeeAttestation } from './chunk-QLPNUQDA.js';
3
+ import { formatUnits } from '@provex/utils/units';
4
+ import { convertTokenInputToCurrency } from '@provex/utils/conversionRates';
5
+ import { calculateNetAmount, calculateFeeAmount } from '@provex/utils/fees';
6
+
7
+ function computePartialFulfillment({
8
+ attestation,
9
+ intentAmount,
10
+ conversionRate,
11
+ tokenInfo,
12
+ feeBps
13
+ }) {
14
+ if (!attestation || !intentAmount || !conversionRate || !tokenInfo) return null;
15
+ const grossReleased = BigInt(attestation.typedDataValue.releaseAmount);
16
+ if (grossReleased >= intentAmount) return null;
17
+ const returnedToMaker = intentAmount - grossReleased;
18
+ const effectiveFeeBps = feeBps ?? 0n;
19
+ const netToBuyer = effectiveFeeBps > 0n ? calculateNetAmount({ grossAmount: grossReleased, feeBps: effectiveFeeBps }) : grossReleased;
20
+ const protocolFee = effectiveFeeBps > 0n ? calculateFeeAmount({ amount: grossReleased, feeBps: effectiveFeeBps }) : 0n;
21
+ const contractedFiatRaw = convertTokenInputToCurrency({
22
+ token: tokenInfo,
23
+ amountOutInt: intentAmount,
24
+ rate: conversionRate
25
+ });
26
+ const attestedFiatRaw = convertTokenInputToCurrency({
27
+ token: tokenInfo,
28
+ amountOutInt: grossReleased,
29
+ rate: conversionRate
30
+ });
31
+ const formatTokenDisplay = (value) => formatUnits(value, {
32
+ decimals: tokenInfo.decimals,
33
+ truncateDecimals: 2,
34
+ padDecimals: 2
35
+ });
36
+ return {
37
+ grossReleased,
38
+ netToBuyer,
39
+ protocolFee,
40
+ returnedToMaker,
41
+ contractedFiatDisplay: formatTokenDisplay(contractedFiatRaw),
42
+ attestedFiatDisplay: formatTokenDisplay(attestedFiatRaw),
43
+ netToBuyerDisplay: formatTokenDisplay(netToBuyer),
44
+ returnedToMakerDisplay: formatTokenDisplay(returnedToMaker),
45
+ protocolFeeDisplay: protocolFee > 0n ? formatTokenDisplay(protocolFee) : null
46
+ };
47
+ }
48
+
49
+ export { computePartialFulfillment };
50
+ //# sourceMappingURL=index.js.map
51
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/derivations.ts"],"names":[],"mappings":";;;;;;AAwCO,SAAS,yBAAA,CAA0B;AAAA,EACxC,WAAA;AAAA,EACA,YAAA;AAAA,EACA,cAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA,EAM8B;AAC5B,EAAA,IAAI,CAAC,eAAe,CAAC,YAAA,IAAgB,CAAC,cAAA,IAAkB,CAAC,WAAW,OAAO,IAAA;AAC3E,EAAA,MAAM,aAAA,GAAgB,MAAA,CAAO,WAAA,CAAY,cAAA,CAAe,aAAa,CAAA;AACrE,EAAA,IAAI,aAAA,IAAiB,cAAc,OAAO,IAAA;AAE1C,EAAA,MAAM,kBAAkB,YAAA,GAAe,aAAA;AACvC,EAAA,MAAM,kBAAkB,MAAA,IAAU,EAAA;AAClC,EAAA,MAAM,UAAA,GACJ,eAAA,GAAkB,EAAA,GACd,kBAAA,CAAmB,EAAE,aAAa,aAAA,EAAe,MAAA,EAAQ,eAAA,EAAiB,CAAA,GAC1E,aAAA;AACN,EAAA,MAAM,WAAA,GACJ,eAAA,GAAkB,EAAA,GACd,kBAAA,CAAmB,EAAE,QAAQ,aAAA,EAAe,MAAA,EAAQ,eAAA,EAAiB,CAAA,GACrE,EAAA;AAEN,EAAA,MAAM,oBAAoB,2BAAA,CAA4B;AAAA,IACpD,KAAA,EAAO,SAAA;AAAA,IACP,YAAA,EAAc,YAAA;AAAA,IACd,IAAA,EAAM;AAAA,GACP,CAAA;AACD,EAAA,MAAM,kBAAkB,2BAAA,CAA4B;AAAA,IAClD,KAAA,EAAO,SAAA;AAAA,IACP,YAAA,EAAc,aAAA;AAAA,IACd,IAAA,EAAM;AAAA,GACP,CAAA;AAED,EAAA,MAAM,kBAAA,GAAqB,CAAC,KAAA,KAC1B,WAAA,CAAY,KAAA,EAAO;AAAA,IACjB,UAAU,SAAA,CAAU,QAAA;AAAA,IACpB,gBAAA,EAAkB,CAAA;AAAA,IAClB,WAAA,EAAa;AAAA,GACd,CAAA;AAEH,EAAA,OAAO;AAAA,IACL,aAAA;AAAA,IACA,UAAA;AAAA,IACA,WAAA;AAAA,IACA,eAAA;AAAA,IACA,qBAAA,EAAuB,mBAAmB,iBAAiB,CAAA;AAAA,IAC3D,mBAAA,EAAqB,mBAAmB,eAAe,CAAA;AAAA,IACvD,iBAAA,EAAmB,mBAAmB,UAAU,CAAA;AAAA,IAChD,sBAAA,EAAwB,mBAAmB,eAAe,CAAA;AAAA,IAC1D,kBAAA,EAAoB,WAAA,GAAc,EAAA,GAAK,kBAAA,CAAmB,WAAW,CAAA,GAAI;AAAA,GAC3E;AACF","file":"index.js","sourcesContent":["/**\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"]}
@@ -0,0 +1,144 @@
1
+ import { Hex } from 'viem';
2
+ import { V3AttestationResponseObject } from '@provex/utils';
3
+ import { TokenInfo } from '@provex/utils/tokens';
4
+
5
+ /**
6
+ * @fileoverview Seam contract + state types for the proving orchestrator.
7
+ *
8
+ * `CaptureAdapter` is the swappable capture backend (extension+enclave today,
9
+ * TLSNotary tomorrow). `ProvingState` is the single discriminated-union
10
+ * machine that subsumes the live proof-substep machine (ProofPanel) and the
11
+ * attestation lifecycle (submitStepDerivations). Monetary values stay bigint
12
+ * in base units; the only stringified amount is `ProvingIntent.amount`, which
13
+ * mirrors the existing on-the-wire BuyerTeeIntent shape (buyerTeeAttestation).
14
+ */
15
+
16
+ /** Transaction candidate the adapter surfaces for manual selection. Mirrors
17
+ * the `MetadataMessage` fields the live TransactionSelector consumes; kept
18
+ * structural so the package needs no @provex/extension-react import. */
19
+ interface CaptureCandidate {
20
+ amount: number | string;
21
+ currency?: string;
22
+ date?: string | number | Date;
23
+ originalIndex: number;
24
+ }
25
+ /** Identity the attestor binds the attestation to. Mirrors the live
26
+ * attestationIntent built in SubmitStep.tsx:333-342 / BuyerTeeIntent. */
27
+ interface ProvingIntent {
28
+ intentHash: Hex;
29
+ /** bigint base units, stringified (matches BuyerTeeIntent.amount). */
30
+ amount: string;
31
+ timestampMs: string;
32
+ paymentMethod: Hex;
33
+ fiatCurrency: Hex;
34
+ conversionRate: string;
35
+ /** payeeDetailsHash */
36
+ payeeDetails: Hex;
37
+ timestampBufferMs?: string;
38
+ }
39
+ /** What the orchestrator hands a capture adapter. */
40
+ interface CaptureRequest {
41
+ /** Parent provider key, e.g. 'zelle', 'revolut'. */
42
+ provider: string;
43
+ /** Extension platform slug, e.g. 'chase'. */
44
+ platform: string;
45
+ /** Action type, e.g. 'transfer_zelle'. */
46
+ actionType: string;
47
+ intent: ProvingIntent;
48
+ /** UnifiedPaymentVerifier the attestation is re-signed for. */
49
+ verifyingContract: Hex;
50
+ chainId: number;
51
+ /**
52
+ * Settlement token for this intent (e.g. USDC on the target chain).
53
+ * Used by the adapter to compute the fiat currency amount via
54
+ * convertTokenInputToCurrency so auto-match works correctly for non-1:1 rates.
55
+ */
56
+ token: TokenInfo;
57
+ }
58
+ /** Opaque per-backend capture state; the orchestrator passes it back unmodified. */
59
+ interface CaptureHandle {
60
+ readonly kind: string;
61
+ readonly material: unknown;
62
+ }
63
+ /** Uniform capture output. `verificationData` names the signing witness to
64
+ * the on-chain verifier (enclave signer for buyerTee — SubmitStep.tsx:473-475). */
65
+ interface VerifiedAttestation {
66
+ attestation: V3AttestationResponseObject;
67
+ verificationData: Hex;
68
+ }
69
+ /** Resolver the UI provides so the adapter can collect a manual transaction
70
+ * pick when auto-match fails (GenerateProofStep.tsx:391-404). */
71
+ type SelectTransaction = (candidates: CaptureCandidate[]) => Promise<number>;
72
+ /** A pluggable capture backend. One per capture mechanism. */
73
+ interface CaptureAdapter {
74
+ /** Stable id for telemetry, e.g. 'extension-enclave'. */
75
+ readonly kind: string;
76
+ /** Phase 1: authenticate + capture, returning opaque material to attest. */
77
+ authenticateAndCapture(request: CaptureRequest, options?: {
78
+ selectTransaction?: SelectTransaction;
79
+ }): Promise<CaptureHandle>;
80
+ /** Phase 2: produce a verified attestation from the captured material. */
81
+ attest(handle: CaptureHandle, request: CaptureRequest): Promise<VerifiedAttestation>;
82
+ }
83
+ /** Inputs the orchestrator hook needs to drive the machine. */
84
+ interface ProvingRequest {
85
+ request: CaptureRequest;
86
+ /** Optional manual-select resolver forwarded to the adapter. */
87
+ selectTransaction?: SelectTransaction;
88
+ }
89
+ /**
90
+ * The proving state machine (spec lines 141-151). `attest` auto-runs on
91
+ * entry; `readyToFulfill` waits for the user's explicit Complete Order
92
+ * action; `error.isSubmissionError` routes retry to Retry Submission
93
+ * (post-fulfilment) vs Retry Verification (pre-fulfilment) — exactly the
94
+ * live SubmitStep.tsx:931-955 routing.
95
+ */
96
+ type ProvingState = {
97
+ status: 'authenticate';
98
+ } | {
99
+ status: 'capture';
100
+ } | {
101
+ status: 'attest';
102
+ } | {
103
+ status: 'readyToFulfill';
104
+ attestation: VerifiedAttestation;
105
+ } | {
106
+ status: 'fulfillIntent';
107
+ attestation: VerifiedAttestation;
108
+ } | {
109
+ status: 'confirm';
110
+ attestation: VerifiedAttestation;
111
+ } | {
112
+ status: 'sync';
113
+ attestation: VerifiedAttestation;
114
+ } | {
115
+ status: 'complete';
116
+ attestation: VerifiedAttestation;
117
+ } | {
118
+ status: 'error';
119
+ message: string;
120
+ /** true ⇒ failure occurred during/after on-chain submission. */
121
+ isSubmissionError: boolean;
122
+ attestation?: VerifiedAttestation;
123
+ };
124
+ /**
125
+ * Thrown by a capture adapter when the provider returned no buyerTee capture
126
+ * (a non-migrated / Reclaim-only provider). The caller (UnifiedBuy) catches
127
+ * this to fall back to its retained Reclaim branch — the package itself does
128
+ * not implement Reclaim (decision (a), spec line 256).
129
+ */
130
+ declare class EnclaveCaptureUnavailable extends Error {
131
+ readonly platform: string;
132
+ constructor(platform: string);
133
+ }
134
+ /**
135
+ * Thrown by a capture adapter when auto-match failed and no `selectTransaction`
136
+ * resolver was provided (e.g. a headless wallet). Carries the candidates so a
137
+ * UI caller can render the manual selector and retry (spec lines 263-267).
138
+ */
139
+ declare class ManualSelectionRequired extends Error {
140
+ readonly candidates: CaptureCandidate[];
141
+ constructor(candidates: CaptureCandidate[]);
142
+ }
143
+
144
+ export { type CaptureCandidate as C, EnclaveCaptureUnavailable as E, ManualSelectionRequired as M, type ProvingIntent as P, type SelectTransaction as S, type VerifiedAttestation as V, type CaptureRequest as a, type CaptureHandle as b, type CaptureAdapter as c, type ProvingRequest as d, type ProvingState as e };
@@ -0,0 +1,144 @@
1
+ import { Hex } from 'viem';
2
+ import { V3AttestationResponseObject } from '@provex/utils';
3
+ import { TokenInfo } from '@provex/utils/tokens';
4
+
5
+ /**
6
+ * @fileoverview Seam contract + state types for the proving orchestrator.
7
+ *
8
+ * `CaptureAdapter` is the swappable capture backend (extension+enclave today,
9
+ * TLSNotary tomorrow). `ProvingState` is the single discriminated-union
10
+ * machine that subsumes the live proof-substep machine (ProofPanel) and the
11
+ * attestation lifecycle (submitStepDerivations). Monetary values stay bigint
12
+ * in base units; the only stringified amount is `ProvingIntent.amount`, which
13
+ * mirrors the existing on-the-wire BuyerTeeIntent shape (buyerTeeAttestation).
14
+ */
15
+
16
+ /** Transaction candidate the adapter surfaces for manual selection. Mirrors
17
+ * the `MetadataMessage` fields the live TransactionSelector consumes; kept
18
+ * structural so the package needs no @provex/extension-react import. */
19
+ interface CaptureCandidate {
20
+ amount: number | string;
21
+ currency?: string;
22
+ date?: string | number | Date;
23
+ originalIndex: number;
24
+ }
25
+ /** Identity the attestor binds the attestation to. Mirrors the live
26
+ * attestationIntent built in SubmitStep.tsx:333-342 / BuyerTeeIntent. */
27
+ interface ProvingIntent {
28
+ intentHash: Hex;
29
+ /** bigint base units, stringified (matches BuyerTeeIntent.amount). */
30
+ amount: string;
31
+ timestampMs: string;
32
+ paymentMethod: Hex;
33
+ fiatCurrency: Hex;
34
+ conversionRate: string;
35
+ /** payeeDetailsHash */
36
+ payeeDetails: Hex;
37
+ timestampBufferMs?: string;
38
+ }
39
+ /** What the orchestrator hands a capture adapter. */
40
+ interface CaptureRequest {
41
+ /** Parent provider key, e.g. 'zelle', 'revolut'. */
42
+ provider: string;
43
+ /** Extension platform slug, e.g. 'chase'. */
44
+ platform: string;
45
+ /** Action type, e.g. 'transfer_zelle'. */
46
+ actionType: string;
47
+ intent: ProvingIntent;
48
+ /** UnifiedPaymentVerifier the attestation is re-signed for. */
49
+ verifyingContract: Hex;
50
+ chainId: number;
51
+ /**
52
+ * Settlement token for this intent (e.g. USDC on the target chain).
53
+ * Used by the adapter to compute the fiat currency amount via
54
+ * convertTokenInputToCurrency so auto-match works correctly for non-1:1 rates.
55
+ */
56
+ token: TokenInfo;
57
+ }
58
+ /** Opaque per-backend capture state; the orchestrator passes it back unmodified. */
59
+ interface CaptureHandle {
60
+ readonly kind: string;
61
+ readonly material: unknown;
62
+ }
63
+ /** Uniform capture output. `verificationData` names the signing witness to
64
+ * the on-chain verifier (enclave signer for buyerTee — SubmitStep.tsx:473-475). */
65
+ interface VerifiedAttestation {
66
+ attestation: V3AttestationResponseObject;
67
+ verificationData: Hex;
68
+ }
69
+ /** Resolver the UI provides so the adapter can collect a manual transaction
70
+ * pick when auto-match fails (GenerateProofStep.tsx:391-404). */
71
+ type SelectTransaction = (candidates: CaptureCandidate[]) => Promise<number>;
72
+ /** A pluggable capture backend. One per capture mechanism. */
73
+ interface CaptureAdapter {
74
+ /** Stable id for telemetry, e.g. 'extension-enclave'. */
75
+ readonly kind: string;
76
+ /** Phase 1: authenticate + capture, returning opaque material to attest. */
77
+ authenticateAndCapture(request: CaptureRequest, options?: {
78
+ selectTransaction?: SelectTransaction;
79
+ }): Promise<CaptureHandle>;
80
+ /** Phase 2: produce a verified attestation from the captured material. */
81
+ attest(handle: CaptureHandle, request: CaptureRequest): Promise<VerifiedAttestation>;
82
+ }
83
+ /** Inputs the orchestrator hook needs to drive the machine. */
84
+ interface ProvingRequest {
85
+ request: CaptureRequest;
86
+ /** Optional manual-select resolver forwarded to the adapter. */
87
+ selectTransaction?: SelectTransaction;
88
+ }
89
+ /**
90
+ * The proving state machine (spec lines 141-151). `attest` auto-runs on
91
+ * entry; `readyToFulfill` waits for the user's explicit Complete Order
92
+ * action; `error.isSubmissionError` routes retry to Retry Submission
93
+ * (post-fulfilment) vs Retry Verification (pre-fulfilment) — exactly the
94
+ * live SubmitStep.tsx:931-955 routing.
95
+ */
96
+ type ProvingState = {
97
+ status: 'authenticate';
98
+ } | {
99
+ status: 'capture';
100
+ } | {
101
+ status: 'attest';
102
+ } | {
103
+ status: 'readyToFulfill';
104
+ attestation: VerifiedAttestation;
105
+ } | {
106
+ status: 'fulfillIntent';
107
+ attestation: VerifiedAttestation;
108
+ } | {
109
+ status: 'confirm';
110
+ attestation: VerifiedAttestation;
111
+ } | {
112
+ status: 'sync';
113
+ attestation: VerifiedAttestation;
114
+ } | {
115
+ status: 'complete';
116
+ attestation: VerifiedAttestation;
117
+ } | {
118
+ status: 'error';
119
+ message: string;
120
+ /** true ⇒ failure occurred during/after on-chain submission. */
121
+ isSubmissionError: boolean;
122
+ attestation?: VerifiedAttestation;
123
+ };
124
+ /**
125
+ * Thrown by a capture adapter when the provider returned no buyerTee capture
126
+ * (a non-migrated / Reclaim-only provider). The caller (UnifiedBuy) catches
127
+ * this to fall back to its retained Reclaim branch — the package itself does
128
+ * not implement Reclaim (decision (a), spec line 256).
129
+ */
130
+ declare class EnclaveCaptureUnavailable extends Error {
131
+ readonly platform: string;
132
+ constructor(platform: string);
133
+ }
134
+ /**
135
+ * Thrown by a capture adapter when auto-match failed and no `selectTransaction`
136
+ * resolver was provided (e.g. a headless wallet). Carries the candidates so a
137
+ * UI caller can render the manual selector and retry (spec lines 263-267).
138
+ */
139
+ declare class ManualSelectionRequired extends Error {
140
+ readonly candidates: CaptureCandidate[];
141
+ constructor(candidates: CaptureCandidate[]);
142
+ }
143
+
144
+ export { type CaptureCandidate as C, EnclaveCaptureUnavailable as E, ManualSelectionRequired as M, type ProvingIntent as P, type SelectTransaction as S, type VerifiedAttestation as V, type CaptureRequest as a, type CaptureHandle as b, type CaptureAdapter as c, type ProvingRequest as d, type ProvingState as e };
@@ -0,0 +1,125 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var utils = require('@provex/utils');
5
+ var time = require('@provex/utils/time');
6
+
7
+ // src/useProving.ts
8
+ async function withMinimumVisible(floorMs, started) {
9
+ const elapsed = Date.now() - started;
10
+ if (elapsed < floorMs) await time.sleep(floorMs - elapsed);
11
+ }
12
+ function isWalletRejection(error) {
13
+ return typeof error === "object" && error !== null && "code" in error && error.code === "WALLET_REJECTED";
14
+ }
15
+ function useProving(options) {
16
+ const {
17
+ client,
18
+ captureAdapter,
19
+ request,
20
+ paymentMethodId,
21
+ selectTransaction,
22
+ onAwaitingSignature,
23
+ onTransactionHash,
24
+ onComplete
25
+ } = options;
26
+ const [state, setState] = react.useState({ status: "authenticate" });
27
+ const inFlightRef = react.useRef(false);
28
+ const submitInFlightRef = react.useRef(false);
29
+ const runCaptureAndAttest = react.useCallback(async () => {
30
+ if (inFlightRef.current) return;
31
+ inFlightRef.current = true;
32
+ try {
33
+ setState({ status: "capture" });
34
+ const handle = await captureAdapter.authenticateAndCapture(request, { selectTransaction });
35
+ const attestStarted = Date.now();
36
+ setState({ status: "attest" });
37
+ const verified = await captureAdapter.attest(handle, request);
38
+ await withMinimumVisible(1e3, attestStarted);
39
+ const nullifier = utils.extractNullifier(verified.attestation);
40
+ if (nullifier) {
41
+ const isUsed = await client.checkNullifierUsed({ paymentMethodId, nullifier });
42
+ if (isUsed === true) {
43
+ throw new Error(
44
+ "This payment has already been verified. The nullifier has already been used onchain."
45
+ );
46
+ }
47
+ }
48
+ setState({ status: "readyToFulfill", attestation: verified });
49
+ } catch (error) {
50
+ setState({
51
+ status: "error",
52
+ message: error instanceof Error ? error.message : "Verification failed",
53
+ isSubmissionError: false
54
+ });
55
+ } finally {
56
+ inFlightRef.current = false;
57
+ }
58
+ }, [captureAdapter, client, request, paymentMethodId, selectTransaction]);
59
+ const submit = react.useCallback(
60
+ async (verified) => {
61
+ if (submitInFlightRef.current) return;
62
+ submitInFlightRef.current = true;
63
+ const submitStarted = Date.now();
64
+ setState({ status: "fulfillIntent", attestation: verified });
65
+ try {
66
+ await client.fulfillIntent({
67
+ intentHash: request.intent.intentHash,
68
+ attestation: verified.attestation,
69
+ verificationData: verified.verificationData,
70
+ // Wrap consumer callbacks to match the real buildWritableMethod
71
+ // call shapes (ProveXClient.ts ~890, ~903).
72
+ onAwaitingSignature: onAwaitingSignature ? (_info) => {
73
+ onAwaitingSignature();
74
+ } : void 0,
75
+ // buildWritableMethod fires onTransactionHash with { ...ctx, hash }
76
+ // (ProveXClient.ts ~903). Extract hash and forward to the consumer's
77
+ // simpler (hash: Hex) => void API.
78
+ onTransactionHash: onTransactionHash ? (info) => {
79
+ setState({ status: "confirm", attestation: verified });
80
+ onTransactionHash(info.hash);
81
+ } : (_info) => {
82
+ setState({ status: "confirm", attestation: verified });
83
+ }
84
+ });
85
+ await withMinimumVisible(500, submitStarted);
86
+ setState({ status: "sync", attestation: verified });
87
+ await time.sleep(600);
88
+ setState({ status: "complete", attestation: verified });
89
+ onComplete?.(verified.attestation);
90
+ } catch (error) {
91
+ if (isWalletRejection(error)) {
92
+ setState({ status: "readyToFulfill", attestation: verified });
93
+ return;
94
+ }
95
+ setState({
96
+ status: "error",
97
+ message: error instanceof Error ? error.message : "Submission failed",
98
+ isSubmissionError: true,
99
+ attestation: verified
100
+ });
101
+ } finally {
102
+ submitInFlightRef.current = false;
103
+ }
104
+ },
105
+ [client, request.intent.intentHash, onAwaitingSignature, onTransactionHash, onComplete]
106
+ );
107
+ const authenticate = react.useCallback(() => runCaptureAndAttest(), [runCaptureAndAttest]);
108
+ const fulfill = react.useCallback(async () => {
109
+ if (state.status !== "readyToFulfill") return;
110
+ await submit(state.attestation);
111
+ }, [state, submit]);
112
+ const retryVerification = react.useCallback(async () => {
113
+ setState({ status: "authenticate" });
114
+ await runCaptureAndAttest();
115
+ }, [runCaptureAndAttest]);
116
+ const retrySubmission = react.useCallback(async () => {
117
+ if (state.status !== "error" || !state.attestation) return;
118
+ await submit(state.attestation);
119
+ }, [state, submit]);
120
+ return { state, authenticate, fulfill, retryVerification, retrySubmission };
121
+ }
122
+
123
+ exports.useProving = useProving;
124
+ //# sourceMappingURL=useProving.cjs.map
125
+ //# sourceMappingURL=useProving.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useProving.ts"],"names":["sleep","useState","useRef","useCallback","extractNullifier"],"mappings":"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","file":"useProving.cjs","sourcesContent":["/**\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"]}