@provex/extension-react 1.10.0 → 1.11.0-rc.20261018212258.bdc024c
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.
- package/dist/adapters/extensionEnclave.cjs +61 -58
- package/dist/adapters/extensionEnclave.cjs.map +1 -1
- package/dist/adapters/extensionEnclave.d.cts +1 -11
- package/dist/adapters/extensionEnclave.d.ts +1 -11
- package/dist/adapters/extensionEnclave.js +1 -1
- package/dist/{chunk-PTUIJQ4H.js → chunk-AMJXMSDV.js} +64 -55
- package/dist/chunk-AMJXMSDV.js.map +1 -0
- package/dist/index.cjs +61 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-PTUIJQ4H.js.map +0 -1
|
@@ -3,26 +3,9 @@
|
|
|
3
3
|
var viem = require('viem');
|
|
4
4
|
var payment = require('@provex/utils/payment');
|
|
5
5
|
var conversionRates = require('@provex/utils/conversionRates');
|
|
6
|
-
var chaseTransferZelle = require('@zkp2p/providers/chase/transfer_zelle.json');
|
|
7
|
-
var bankofamericaTransferZelle = require('@zkp2p/providers/bankofamerica/transfer_zelle.json');
|
|
8
|
-
var citiTransferZelle = require('@zkp2p/providers/citi/transfer_zelle.json');
|
|
9
6
|
var proving = require('@provex/proving');
|
|
10
7
|
|
|
11
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
|
-
|
|
13
|
-
var chaseTransferZelle__default = /*#__PURE__*/_interopDefault(chaseTransferZelle);
|
|
14
|
-
var bankofamericaTransferZelle__default = /*#__PURE__*/_interopDefault(bankofamericaTransferZelle);
|
|
15
|
-
var citiTransferZelle__default = /*#__PURE__*/_interopDefault(citiTransferZelle);
|
|
16
|
-
|
|
17
8
|
// src/adapters/extensionEnclave.ts
|
|
18
|
-
var TEMPLATES_BY_CAPTURE_KEY = Object.freeze({
|
|
19
|
-
"chase/transfer_zelle": chaseTransferZelle__default.default,
|
|
20
|
-
"bankofamerica/transfer_zelle": bankofamericaTransferZelle__default.default,
|
|
21
|
-
"citi/transfer_zelle": citiTransferZelle__default.default
|
|
22
|
-
});
|
|
23
|
-
function providerTemplateFor(platform, actionType) {
|
|
24
|
-
return TEMPLATES_BY_CAPTURE_KEY[`${platform}/${actionType}`];
|
|
25
|
-
}
|
|
26
9
|
var DEFAULT_ATTESTATION_SERVICE_URL = "https://attestation-service.zkp2p.xyz";
|
|
27
10
|
function normalizeAmount(amount, decimals, centIsInt) {
|
|
28
11
|
const raw = String(amount);
|
|
@@ -34,7 +17,7 @@ function normalizeAmount(amount, decimals, centIsInt) {
|
|
|
34
17
|
}
|
|
35
18
|
return value;
|
|
36
19
|
}
|
|
37
|
-
function
|
|
20
|
+
function matchPayment(candidates, request) {
|
|
38
21
|
const currencyAmount = conversionRates.convertTokenInputToCurrency({
|
|
39
22
|
token: request.token,
|
|
40
23
|
amountOutInt: BigInt(request.intent.amount),
|
|
@@ -43,10 +26,28 @@ function autoMatch(candidates, request) {
|
|
|
43
26
|
const configs = payment.providerConfigs(request.chainId);
|
|
44
27
|
const providerConfig = configs[request.provider];
|
|
45
28
|
const centIsInt = providerConfig?.amountInCents ?? false;
|
|
46
|
-
|
|
29
|
+
const matches = candidates.filter((candidate) => {
|
|
47
30
|
const converted = normalizeAmount(candidate.amount, request.token.decimals, centIsInt);
|
|
48
31
|
return converted === currencyAmount || converted === currencyAmount * -1n;
|
|
49
|
-
})
|
|
32
|
+
});
|
|
33
|
+
if (matches.length === 0) return { kind: "none" };
|
|
34
|
+
if (matches.length === 1) return { kind: "single", candidate: matches[0] };
|
|
35
|
+
return { kind: "several", candidates: matches };
|
|
36
|
+
}
|
|
37
|
+
function toPaymentToTry(candidate) {
|
|
38
|
+
return { index: candidate.originalIndex, params: candidate.params };
|
|
39
|
+
}
|
|
40
|
+
async function resolvePaymentsToTry({
|
|
41
|
+
match,
|
|
42
|
+
history,
|
|
43
|
+
selectTransaction
|
|
44
|
+
}) {
|
|
45
|
+
if (match.kind === "single") return [toPaymentToTry(match.candidate)];
|
|
46
|
+
if (match.kind === "several") return match.candidates.map(toPaymentToTry);
|
|
47
|
+
if (!selectTransaction) throw new proving.ManualSelectionRequired(history);
|
|
48
|
+
const pickedIndex = await selectTransaction(history);
|
|
49
|
+
const picked = history.find((row) => row.originalIndex === pickedIndex);
|
|
50
|
+
return [{ index: pickedIndex, params: picked?.params }];
|
|
50
51
|
}
|
|
51
52
|
function awaitMetadata(peer, platform, actionType, timeoutMs) {
|
|
52
53
|
return new Promise((resolve, reject) => {
|
|
@@ -88,13 +89,12 @@ function createExtensionEnclaveAdapter(config) {
|
|
|
88
89
|
// (zkp2p 0.6.0 spec §7). Omitting these is why non-Revolut rails got
|
|
89
90
|
// "no buyerTee capture" and dead-ended on the retired Reclaim path.
|
|
90
91
|
attestationServiceUrl: config.attestationServiceUrl ?? DEFAULT_ATTESTATION_SERVICE_URL,
|
|
91
|
-
//
|
|
92
|
-
// Zelle
|
|
92
|
+
// The attestation route equals the capture pair for every rail today
|
|
93
|
+
// (generic Zelle included: zelle/transfer_zelle_chase). No template is
|
|
94
|
+
// passed: extension 0.6.7 always fetches it from api.zkp2p.xyz and
|
|
95
|
+
// ignores a page-supplied one, so the capture pair must be hosted.
|
|
93
96
|
attestationActionType: request.attestationActionType ?? request.actionType,
|
|
94
|
-
attestationPlatform: request.attestationPlatform ?? request.platform
|
|
95
|
-
// Bundled template for rails zkp2p does not host. Undefined for hosted
|
|
96
|
-
// rails, which keeps the extension fetching (and auto-updating) them.
|
|
97
|
-
providerConfig: providerTemplateFor(request.platform, request.actionType)
|
|
97
|
+
attestationPlatform: request.attestationPlatform ?? request.platform
|
|
98
98
|
});
|
|
99
99
|
const response = await metadataPromise;
|
|
100
100
|
if (!response.buyerTeeCapture) {
|
|
@@ -116,20 +116,13 @@ function createExtensionEnclaveAdapter(config) {
|
|
|
116
116
|
}
|
|
117
117
|
throw new proving.EnclaveCaptureUnavailable(request.platform);
|
|
118
118
|
}
|
|
119
|
-
const matched = autoMatch(response.metadata, request);
|
|
120
|
-
let matchedIndex;
|
|
121
|
-
if (matched) {
|
|
122
|
-
matchedIndex = matched.originalIndex;
|
|
123
|
-
} else if (options?.selectTransaction) {
|
|
124
|
-
matchedIndex = await options.selectTransaction(response.metadata);
|
|
125
|
-
} else {
|
|
126
|
-
throw new proving.ManualSelectionRequired(response.metadata);
|
|
127
|
-
}
|
|
128
|
-
const matchedRow = response.metadata.find((row) => row.originalIndex === matchedIndex);
|
|
129
119
|
const material = {
|
|
130
120
|
encryptedSessionMaterial: response.buyerTeeCapture.encryptedSessionMaterial,
|
|
131
|
-
|
|
132
|
-
|
|
121
|
+
payments: await resolvePaymentsToTry({
|
|
122
|
+
match: matchPayment(response.metadata, request),
|
|
123
|
+
history: response.metadata,
|
|
124
|
+
selectTransaction: options?.selectTransaction
|
|
125
|
+
})
|
|
133
126
|
};
|
|
134
127
|
return { kind: "extension-enclave", material };
|
|
135
128
|
},
|
|
@@ -140,28 +133,38 @@ function createExtensionEnclaveAdapter(config) {
|
|
|
140
133
|
);
|
|
141
134
|
}
|
|
142
135
|
const material = handle.material;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// overrides unset and fall back to the capture values.
|
|
155
|
-
platform: request.attestationPlatform ?? request.platform,
|
|
156
|
-
actionType: request.attestationActionType ?? request.actionType,
|
|
157
|
-
encryptedSessionMaterial: material.encryptedSessionMaterial,
|
|
158
|
-
params,
|
|
159
|
-
verifyingContract: request.verifyingContract,
|
|
160
|
-
intent: request.intent
|
|
161
|
-
});
|
|
162
|
-
return { attestation, verificationData: attestation.signer };
|
|
136
|
+
let alreadyUsed;
|
|
137
|
+
for (const payment of material.payments) {
|
|
138
|
+
try {
|
|
139
|
+
const attestation = await attestPayment({ payment, material, request });
|
|
140
|
+
return { attestation, verificationData: attestation.signer };
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (!(error instanceof proving.PaymentAlreadyUsedError)) throw error;
|
|
143
|
+
alreadyUsed = error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
throw alreadyUsed ?? new Error("No bank payment was captured for this order.");
|
|
163
147
|
}
|
|
164
148
|
};
|
|
149
|
+
function attestPayment({
|
|
150
|
+
payment: payment$1,
|
|
151
|
+
material,
|
|
152
|
+
request
|
|
153
|
+
}) {
|
|
154
|
+
const params = {
|
|
155
|
+
...payment$1.params ?? {},
|
|
156
|
+
...payment.providerRequiresMetadataIndex(request.provider) ? { index: payment$1.index } : {}
|
|
157
|
+
};
|
|
158
|
+
return proving.requestBuyerTeeAttestation({
|
|
159
|
+
attestationBaseUrl: config.attestationBaseUrl,
|
|
160
|
+
platform: request.attestationPlatform ?? request.platform,
|
|
161
|
+
actionType: request.attestationActionType ?? request.actionType,
|
|
162
|
+
encryptedSessionMaterial: material.encryptedSessionMaterial,
|
|
163
|
+
params,
|
|
164
|
+
verifyingContract: request.verifyingContract,
|
|
165
|
+
intent: request.intent
|
|
166
|
+
});
|
|
167
|
+
}
|
|
165
168
|
}
|
|
166
169
|
|
|
167
170
|
Object.defineProperty(exports, "asBuyerTeeProof", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/providerTemplates.ts","../../src/adapters/extensionEnclave.ts"],"names":["chaseTransferZelle","bankofamericaTransferZelle","citiTransferZelle","parseUnits","convertTokenInputToCurrency","providerConfigs","EnclaveCaptureUnavailable","EnclaveCaptureFailed","ManualSelectionRequired","providerRequiresMetadataIndex","requestBuyerTeeAttestation"],"mappings":"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","file":"extensionEnclave.cjs","sourcesContent":["/**\n * @fileoverview Provider templates supplied inline to the PeerAuth extension.\n *\n * The extension resolves a template before it can open the buyer's bank:\n *\n * function ut(e){ return `${base}${e.platform}/${e.actionType}.json` }\n * async function dt(e){\n * if (e.providerConfig) return e.providerConfig // <- inline wins\n * const r = await fetch(ut(e))\n * if (!r.ok) throw new Error(`Failed to fetch provider template: ${r.status}`)\n * }\n *\n * `base` is hardcoded to https://api.zkp2p.xyz/providers/ inside the extension,\n * and zkp2p does NOT host the Zelle bank templates there — every\n * `{bank}/transfer_zelle.json` 404s while venmo/cashapp/revolut/wise/paypal/\n * monzo return 200. The template carries `authLink`, the URL the extension opens,\n * so a 404 means no bank tab at all: the capture dies ~1ms in with\n * \"Failed to fetch provider template: 404\".\n *\n * The templates ARE published on npm (`@zkp2p/providers`), so we bundle the\n * unhosted ones and hand them over inline, which bypasses the fetch entirely.\n *\n * Scope is deliberate: ONLY the rails the API fails to serve. Hosted rails keep\n * fetching so upstream template fixes (bank markup changes) reach users without\n * a release from us. Supplying `providerConfig` also flips the extension's\n * `requiresMetadataApproval` on (`lt(e){return!!e.providerConfig}`), so\n * narrowing the blast radius keeps that UX change off rails that work today.\n */\n\nimport chaseTransferZelle from '@zkp2p/providers/chase/transfer_zelle.json'\nimport bankofamericaTransferZelle from '@zkp2p/providers/bankofamerica/transfer_zelle.json'\nimport citiTransferZelle from '@zkp2p/providers/citi/transfer_zelle.json'\n\n/**\n * A provider template, passed through to the extension verbatim. The shape is\n * zkp2p's, not ours — we never construct or mutate one, so it stays opaque\n * rather than being modelled field by field (that would rot on every upstream\n * template change).\n */\nexport type ProviderTemplate = Record<string, unknown>\n\n/**\n * Templates keyed by the capture pair the extension would otherwise fetch:\n * `${platform}/${actionType}`. Keys mirror the npm package's own directory\n * layout, so the mapping is verifiable by inspection.\n *\n * Zelle sub-provider `zelle-bofa` maps to platform `bankofamerica`, per\n * `subProviderConfigs[...].extensionPlatform` in @provex/utils.\n */\nconst TEMPLATES_BY_CAPTURE_KEY: Readonly<Record<string, ProviderTemplate>> = Object.freeze({\n 'chase/transfer_zelle': chaseTransferZelle as ProviderTemplate,\n 'bankofamerica/transfer_zelle': bankofamericaTransferZelle as ProviderTemplate,\n 'citi/transfer_zelle': citiTransferZelle as ProviderTemplate,\n})\n\n/**\n * Resolve the inline template for a capture pair, if we bundle one.\n *\n * @param platform - Extension platform slug, e.g. `chase`.\n * @param actionType - Capture action type, e.g. `transfer_zelle`.\n * @returns The template to pass as `providerConfig`, or undefined to let the\n * extension fetch it from the hosted registry (the path for every rail zkp2p\n * actually serves).\n */\nexport function providerTemplateFor(\n platform: string,\n actionType: string,\n): ProviderTemplate | undefined {\n return TEMPLATES_BY_CAPTURE_KEY[`${platform}/${actionType}`]\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 * the injected-PeerClient seam is preserved exactly.\n */\n\nimport { parseUnits } from 'viem'\nimport type { Hex } from 'viem'\nimport { providerConfigs, providerRequiresMetadataIndex } from '@provex/utils/payment'\nimport type { ProviderKey, SubProviderKey } from '@provex/utils/payment'\nimport type { ChainId } from '@provex/utils/chain'\nimport { convertTokenInputToCurrency } from '@provex/utils/conversionRates'\nimport { providerTemplateFor } from './providerTemplates'\nimport {\n EnclaveCaptureUnavailable,\n EnclaveCaptureFailed,\n ManualSelectionRequired,\n asBuyerTeeProof,\n requestBuyerTeeAttestation,\n type CaptureAdapter,\n type CaptureCandidate,\n type CaptureHandle,\n type CaptureRequest,\n type SelectTransaction,\n type VerifiedAttestation,\n} from '@provex/proving'\n\n/** Minimal shape of the metadata response the extension posts back. */\ninterface MetadataResponse {\n platform: string\n metadata: CaptureCandidate[]\n buyerTeeCapture?: { encryptedSessionMaterial: string }\n}\n\n/**\n * zkp2p enclave used for Buyer TEE capture when the caller doesn't override\n * `ExtensionEnclaveAdapterConfig.attestationServiceUrl`. Matches the gate's\n * `TEE_ATTESTATION_URL` — the enclave the gate forwards to for the re-sign.\n */\nexport const DEFAULT_ATTESTATION_SERVICE_URL = 'https://attestation-service.zkp2p.xyz'\n\n/** Minimal slice of `window.peer` this adapter depends on. */\nexport interface PeerClient {\n authenticate(inputs: {\n actionType: string\n platform: string\n captureMode: 'buyerTee'\n /**\n * Enclave the extension establishes the Buyer TEE session with and encrypts\n * the session material toward. Required by the zkp2p 0.6.0 spec\n * (onramp-llm.md §7) — without it the extension performs no Buyer TEE\n * capture and falls back to the retired Reclaim path.\n */\n attestationServiceUrl?: string\n /** Attestation action type; defaults to `actionType` when omitted (spec §2). */\n attestationActionType?: string\n /**\n * Attestation platform, when it differs from `platform`. Required for\n * generic Zelle, whose attestation route is `zelle` while capture runs as\n * the bank (`chase`).\n */\n attestationPlatform?: string\n /**\n * Provider template supplied inline. When present the extension skips its\n * own template fetch entirely — the only way to drive a rail zkp2p does not\n * host. See ./providerTemplates.\n */\n providerConfig?: Record<string, unknown>\n }): Promise<void>\n onMetadataMessage(fn: (data: MetadataResponse) => void): () => void\n /** Current extension connection status; `'connected'` means ready. */\n checkConnectionStatus(): Promise<string>\n /** Prompt the user to connect the extension. Resolves true when approved. */\n requestConnection(): Promise<boolean>\n}\n\nexport interface ExtensionEnclaveAdapterConfig {\n /** Resolve the live peer client (e.g. `() => window.peer`). */\n getPeer: () => PeerClient | undefined\n /** Gate/attestation origin handed to requestBuyerTeeAttestation (the re-sign hop). */\n attestationBaseUrl: string\n /**\n * Enclave URL passed to the extension's `authenticate` so it performs the\n * Buyer TEE capture (zkp2p 0.6.0 spec §7). This is the zkp2p enclave the\n * session material is encrypted toward — the SAME enclave the gate forwards\n * to for the re-sign — NOT our gate's re-sign origin (`attestationBaseUrl`).\n * Defaults to {@link DEFAULT_ATTESTATION_SERVICE_URL} when omitted; set it to\n * override the enclave for local testing.\n */\n attestationServiceUrl?: string\n /**\n * Milliseconds before awaitMetadata rejects if the extension never posts a\n * matching metadata message. Mirrors the 60-second timeout in\n * AuthenticateStep.tsx:232-236. Defaults to 60000.\n */\n timeoutMs?: number\n}\n\ninterface EnclaveMaterial {\n encryptedSessionMaterial: string\n matchedIndex: number\n /** Selected row's provider attestation params (spec §4) — forwarded to the enclave. */\n matchedParams?: Record<string, string | number | boolean>\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 the extension posts for `platform`.\n *\n * Races a configurable timeout (default 60s) against the metadata event,\n * mirroring AuthenticateStep.tsx:232-236. The unsubscribe function is always\n * called on resolution, timeout, or error — no listener leaks.\n *\n * @param peer - Injected extension client.\n * @param platform - The platform handed to `authenticate()` (e.g. 'chase').\n * Echoed back when the extension could not load a template.\n * @param actionType - The capture action type (e.g. 'transfer_zelle'). Its\n * parent is echoed back when the template DID load. Both are accepted; see\n * the guard below.\n * @param timeoutMs - Reject after this long with no matching message.\n */\nfunction awaitMetadata(\n peer: PeerClient,\n platform: string,\n actionType: string,\n timeoutMs: number,\n): Promise<MetadataResponse> {\n return new Promise<MetadataResponse>((resolve, reject) => {\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n\n const unsub = peer.onMetadataMessage((data) => {\n // The extension echoes DIFFERENT platform values depending on how far it\n // got, so correlating on either one alone drops real messages:\n //\n // - Template loaded (success): it echoes the TEMPLATE's own\n // `metadata.platform`. For chase/transfer_zelle.json that is 'zelle'\n // — the generic rail, not the bank. Observed with a live capture:\n // `{ platform: 'zelle', hasBuyerTeeCapture: true, rowCount: 50 }`.\n // - Template fetch failed: there is no template to read, so it echoes\n // back the platform we passed — 'chase' — alongside the errorMessage\n // explaining the failure. Observed live:\n // `{ platform: 'chase', errorMessage: 'Failed to fetch provider template: 404' }`.\n //\n // Matching only the echoed-template platform loses every failure reason\n // (60s timeout instead of the real cause). Matching only the passed\n // platform loses every SUCCESSFUL Zelle capture. Accept both.\n //\n // For providers where platform === parent (venmo, cashapp, …) the two\n // predicates coincide, so this is a strict superset of the old behavior.\n const echoesCapturePlatform = data.platform === platform\n const echoesActionTypeParent = `transfer_${data.platform}` === actionType\n if (echoesCapturePlatform || echoesActionTypeParent) {\n clearTimeout(timeoutId)\n unsub()\n resolve(data)\n }\n })\n\n timeoutId = setTimeout(() => {\n unsub()\n reject(new Error(`Enclave capture timed out for platform \"${platform}\"`))\n }, timeoutMs)\n })\n}\n\nexport function createExtensionEnclaveAdapter(config: ExtensionEnclaveAdapterConfig): CaptureAdapter {\n const timeoutMs = config.timeoutMs ?? 60000\n\n return {\n kind: 'extension-enclave',\n\n async authenticateAndCapture(\n request: CaptureRequest,\n options?: { selectTransaction?: SelectTransaction },\n ): Promise<CaptureHandle> {\n const peer = config.getPeer()\n if (!peer) {\n throw new EnclaveCaptureUnavailable(request.platform)\n }\n\n // Ensure the extension is connected BEFORE authenticate. A cold\n // authenticate() on a fresh page returns errorMessage \"Peer extension\n // connection required\" with no buyerTeeCapture — which trips the\n // enclave-unavailable path. The legacy AuthenticateStep connects first\n // (and onramp-llm.md §3 `ensurePeerReady` requires it); the adapter must\n // too. This is why a warm local tab worked but a fresh prod session failed.\n const status = await peer.checkConnectionStatus()\n if (status !== 'connected') {\n await peer.requestConnection()\n }\n\n // Register the listener BEFORE authenticate fires to avoid a race where\n // the extension posts metadata before the listener is attached.\n const metadataPromise = awaitMetadata(peer, request.platform, request.actionType, timeoutMs)\n await peer.authenticate({\n actionType: request.actionType,\n platform: request.platform,\n captureMode: 'buyerTee',\n // Required for the extension to actually perform the Buyer TEE capture\n // (zkp2p 0.6.0 spec §7). Omitting these is why non-Revolut rails got\n // \"no buyerTee capture\" and dead-ended on the retired Reclaim path.\n attestationServiceUrl: config.attestationServiceUrl ?? DEFAULT_ATTESTATION_SERVICE_URL,\n // Attestation namespace — differs from the capture pair for generic\n // Zelle (zelle/transfer_zelle_chase vs chase/transfer_zelle).\n attestationActionType: request.attestationActionType ?? request.actionType,\n attestationPlatform: request.attestationPlatform ?? request.platform,\n // Bundled template for rails zkp2p does not host. Undefined for hosted\n // rails, which keeps the extension fetching (and auto-updating) them.\n providerConfig: providerTemplateFor(request.platform, request.actionType),\n })\n const response = await metadataPromise\n\n // No buyerTee capture ⇒ the extension didn't produce an encrypted enclave\n // session. Log WHY before failing — the extension may have surfaced an\n // errorMessage, or returned metadata-only (no capture) for this platform.\n if (!response.buyerTeeCapture) {\n const errorMessage = (response as { errorMessage?: unknown }).errorMessage\n console.warn(\n '[extensionEnclave] no buyerTeeCapture for',\n request.platform,\n '— response keys:', Object.keys(response),\n '| errorMessage:', errorMessage,\n '| metadata rows:', Array.isArray(response.metadata) ? response.metadata.length : '(none)',\n '| buyerTeeCapture:', response.buyerTeeCapture,\n )\n // Two distinct failures share the \"no capture\" shape:\n // - The extension surfaced an `errorMessage` ⇒ it ATTEMPTED the capture\n // but something failed (most often: it could not reach the\n // attestation enclave — a buyer-side network / firewall / ad-blocker\n // issue). Surface that as a retryable EnclaveCaptureFailed carrying\n // the raw reason, so the user sees an actionable message instead of\n // \"provider unsupported\".\n // - No `errorMessage` ⇒ the provider produced no capture path at all\n // (non-migrated). That stays EnclaveCaptureUnavailable, the\n // Reclaim-fallback signal.\n if (typeof errorMessage === 'string' && errorMessage.trim().length > 0) {\n throw new EnclaveCaptureFailed(request.platform, errorMessage)\n }\n throw new EnclaveCaptureUnavailable(request.platform)\n }\n\n const 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 // The SELECTED row's params ARE the attestation params (spec §4) — carry\n // them through so attest() can forward them. Look up by originalIndex so\n // this works for both the auto-match and manual-selection branches.\n const matchedRow = response.metadata.find((row) => row.originalIndex === matchedIndex)\n const material: EnclaveMaterial = {\n encryptedSessionMaterial: response.buyerTeeCapture.encryptedSessionMaterial,\n matchedIndex,\n matchedParams: matchedRow?.params,\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 // The selected row's params ARE the attestation params (spec §5,\n // buildBuyerTeeProof) and MUST be forwarded — dropping them is why the\n // enclave 400s with \"Invalid <provider> query params\". The metadata\n // `index` is then merged in ONLY for providers that expect it (Venmo /\n // Cash App / Revolut / Zelle) and omitted for the rest (PayPal / Wise /\n // Monzo / Chime). See providerRequiresMetadataIndex.\n const params = {\n ...(material.matchedParams ?? {}),\n ...(providerRequiresMetadataIndex(request.provider as ProviderKey | SubProviderKey)\n ? { index: material.matchedIndex }\n : {}),\n }\n const attestation = await requestBuyerTeeAttestation({\n attestationBaseUrl: config.attestationBaseUrl,\n // The ATTESTATION namespace, which differs from the capture namespace\n // for generic Zelle: capture ran as chase/transfer_zelle, but the\n // enclave's verify route demands zelle/transfer_zelle_chase. Sending\n // the capture pair here is why a successful Zelle capture would still\n // fail at attest. Providers where the namespaces coincide leave the\n // overrides unset and fall back to the capture values.\n platform: request.attestationPlatform ?? request.platform,\n actionType: request.attestationActionType ?? request.actionType,\n encryptedSessionMaterial: material.encryptedSessionMaterial,\n params,\n verifyingContract: request.verifyingContract,\n intent: request.intent,\n })\n // 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"]}
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/extensionEnclave.ts"],"names":["parseUnits","convertTokenInputToCurrency","providerConfigs","ManualSelectionRequired","EnclaveCaptureUnavailable","EnclaveCaptureFailed","PaymentAlreadyUsedError","payment","providerRequiresMetadataIndex","requestBuyerTeeAttestation"],"mappings":"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","file":"extensionEnclave.cjs","sourcesContent":["/**\n * @fileoverview Extension + enclave CaptureAdapter (the v1 capture backend).\n *\n * Mirrors the live AuthenticateStep + GenerateProofStep enclave path\n * (AuthenticateStep.tsx:224-281, GenerateProofStep.tsx:237-251) reduced to\n * the buyerTee branch. Talks to an injected `window.peer`-shaped client so\n * the injected-PeerClient seam is preserved exactly.\n */\n\nimport { parseUnits } from 'viem'\nimport type { Hex } from 'viem'\nimport { providerConfigs, providerRequiresMetadataIndex } from '@provex/utils/payment'\nimport type { ProviderKey, SubProviderKey } from '@provex/utils/payment'\nimport type { ChainId } from '@provex/utils/chain'\nimport { convertTokenInputToCurrency } from '@provex/utils/conversionRates'\nimport {\n EnclaveCaptureUnavailable,\n EnclaveCaptureFailed,\n ManualSelectionRequired,\n PaymentAlreadyUsedError,\n asBuyerTeeProof,\n requestBuyerTeeAttestation,\n type CaptureAdapter,\n type CaptureCandidate,\n type CaptureHandle,\n type CaptureRequest,\n type SelectTransaction,\n type VerifiedAttestation,\n} from '@provex/proving'\n\n/** Minimal shape of the metadata response the extension posts back. */\ninterface MetadataResponse {\n platform: string\n metadata: CaptureCandidate[]\n buyerTeeCapture?: { encryptedSessionMaterial: string }\n}\n\n/**\n * zkp2p enclave used for Buyer TEE capture when the caller doesn't override\n * `ExtensionEnclaveAdapterConfig.attestationServiceUrl`. Matches the gate's\n * `TEE_ATTESTATION_URL` — the enclave the gate forwards to for the re-sign.\n */\nexport const DEFAULT_ATTESTATION_SERVICE_URL = 'https://attestation-service.zkp2p.xyz'\n\n/** Minimal slice of `window.peer` this adapter depends on. */\nexport interface PeerClient {\n authenticate(inputs: {\n actionType: string\n platform: string\n captureMode: 'buyerTee'\n /**\n * Enclave the extension establishes the Buyer TEE session with and encrypts\n * the session material toward. Required by the zkp2p 0.6.0 spec\n * (onramp-llm.md §7) — without it the extension performs no Buyer TEE\n * capture and falls back to the retired Reclaim path.\n */\n attestationServiceUrl?: string\n /** Attestation action type; defaults to `actionType` when omitted (spec §2). */\n attestationActionType?: string\n /** Attestation platform; defaults to `platform` when omitted. */\n attestationPlatform?: string\n }): Promise<void>\n onMetadataMessage(fn: (data: MetadataResponse) => void): () => void\n /** Current extension connection status; `'connected'` means ready. */\n checkConnectionStatus(): Promise<string>\n /** Prompt the user to connect the extension. Resolves true when approved. */\n requestConnection(): Promise<boolean>\n}\n\nexport interface ExtensionEnclaveAdapterConfig {\n /** Resolve the live peer client (e.g. `() => window.peer`). */\n getPeer: () => PeerClient | undefined\n /** Gate/attestation origin handed to requestBuyerTeeAttestation (the re-sign hop). */\n attestationBaseUrl: string\n /**\n * Enclave URL passed to the extension's `authenticate` so it performs the\n * Buyer TEE capture (zkp2p 0.6.0 spec §7). This is the zkp2p enclave the\n * session material is encrypted toward — the SAME enclave the gate forwards\n * to for the re-sign — NOT our gate's re-sign origin (`attestationBaseUrl`).\n * Defaults to {@link DEFAULT_ATTESTATION_SERVICE_URL} when omitted; set it to\n * override the enclave for local testing.\n */\n attestationServiceUrl?: string\n /**\n * Milliseconds before awaitMetadata rejects if the extension never posts a\n * matching metadata message. Mirrors the 60-second timeout in\n * AuthenticateStep.tsx:232-236. Defaults to 60000.\n */\n timeoutMs?: number\n}\n\n/** One bank-history row the attestation may settle this order with. */\ninterface PaymentToTry {\n /** The row's index in the captured bank history. */\n index: number\n /** The row's provider attestation params (spec §4), forwarded to the enclave. */\n params?: Record<string, string | number | boolean>\n}\n\ninterface EnclaveMaterial {\n encryptedSessionMaterial: string\n /**\n * Payments to attest, in order. One entry for a single amount match or a\n * buyer's pick; every amount match (bank order, newest first) when several\n * payments share the order's amount. Bank dates resolve only to the day,\n * so they cannot tell same-day payments apart; the gate can, by refusing a\n * payment that already settled an order.\n */\n payments: PaymentToTry[]\n}\n\n/**\n * Normalize an amount from the extension into a bigint in token base units.\n *\n * Reproduces findMetadataMessage's normalizeAmount (useZKTLS.ts:630-639).\n * The extension sends amounts in different formats per provider:\n * - Venmo: `\"- $1.00\"` (formatted dollar string — isFormattedDollars=true)\n * - Others: `100` (integer cents) or `1.00` (decimal dollars)\n *\n * When `centIsInt` is true and the raw value has no currency symbol or decimal\n * point (i.e. it arrived as integer cents), we divide by 100 to convert to\n * the same base unit scale that parseUnits applied.\n */\nfunction normalizeAmount(amount: number | string, decimals: number, centIsInt: boolean): bigint {\n const raw = String(amount)\n const isFormattedDollars = /[^0-9\\-]/.test(raw) && raw.includes('.')\n const cleaned = raw.replace(/[^0-9.]/g, '')\n let value = parseUnits(cleaned, decimals)\n if (centIsInt && !isFormattedDollars) {\n value = value / 100n\n }\n return value\n}\n\n/**\n * The outcome of matching the bank history against the intent's amount.\n *\n * - `single`: exactly one payment has the intent's amount.\n * - `several`: more than one does (e.g. two $2 Zelle payments to the same\n * seller). All are tried in bank order; the gate refuses any that already\n * settled an order. Taking only the first sent an already-used payment and\n * failed the order with \"This payment has already been verified\".\n * - `none`: no payment has that amount; the buyer picks from the full list.\n */\ntype PaymentMatch =\n | { kind: 'single'; candidate: CaptureCandidate }\n | { kind: 'several'; candidates: CaptureCandidate[] }\n | { kind: 'none' }\n\n/**\n * Match the bank history against the intent's FIAT currency amount,\n * accepting a positive or negative sign as findMetadataMessage does.\n *\n * Uses the same pipeline as the live AuthenticateStep.tsx:147-163:\n * 1. Convert token amount → fiat via convertTokenInputToCurrency.\n * 2. Normalize each candidate with the provider's centIsInt flag.\n * 3. Match equality (or negated equality for debit/refund signs).\n *\n * `centIsInt` is sourced from `providerConfigs(chainId)[provider]?.amountInCents`\n * which mirrors how the live caller derives it:\n * `targetProvider?.amountInCents ?? false`\n * where `targetProvider` is resolved via `useProvider` from the same\n * providerConfigs call. Sub-providers (e.g. zelle-chase) do NOT currently\n * set amountInCents, so the parent-provider lookup is sufficient.\n *\n * Matches keep the bank's own order (newest first for the hosted templates).\n */\nfunction matchPayment(\n candidates: CaptureCandidate[],\n request: CaptureRequest,\n): PaymentMatch {\n const currencyAmount = convertTokenInputToCurrency({\n token: request.token,\n amountOutInt: BigInt(request.intent.amount),\n rate: BigInt(request.intent.conversionRate),\n })\n\n // Resolve centIsInt from the provider config — same source as the live UI.\n // The cast is safe: CaptureRequest.chainId must be a supported chain ID.\n const configs = providerConfigs(request.chainId as ChainId)\n const providerConfig = configs[request.provider as keyof typeof configs]\n const centIsInt = (providerConfig as { amountInCents?: boolean } | undefined)?.amountInCents ?? false\n\n const matches = candidates.filter((candidate) => {\n const converted = normalizeAmount(candidate.amount, request.token.decimals, centIsInt)\n return converted === currencyAmount || converted === currencyAmount * -1n\n })\n if (matches.length === 0) return { kind: 'none' }\n if (matches.length === 1) return { kind: 'single', candidate: matches[0]! }\n return { kind: 'several', candidates: matches }\n}\n\n/** The bank-history row as a payment to try. */\nfunction toPaymentToTry(candidate: CaptureCandidate): PaymentToTry {\n return { index: candidate.originalIndex, params: candidate.params }\n}\n\n/**\n * The payments to attest, in order: the amount matches, else the buyer's pick\n * from the whole history. Throws {@link ManualSelectionRequired} when a pick is\n * needed but no resolver was supplied.\n */\nasync function resolvePaymentsToTry({\n match,\n history,\n selectTransaction,\n}: {\n match: PaymentMatch\n history: CaptureCandidate[]\n selectTransaction?: SelectTransaction\n}): Promise<PaymentToTry[]> {\n if (match.kind === 'single') return [toPaymentToTry(match.candidate)]\n if (match.kind === 'several') return match.candidates.map(toPaymentToTry)\n if (!selectTransaction) throw new ManualSelectionRequired(history)\n const pickedIndex = await selectTransaction(history)\n const picked = history.find((row) => row.originalIndex === pickedIndex)\n return [{ index: pickedIndex, params: picked?.params }]\n}\n\n/**\n * Wait for the metadata message the extension posts for `platform`.\n *\n * Races a configurable timeout (default 60s) against the metadata event,\n * mirroring AuthenticateStep.tsx:232-236. The unsubscribe function is always\n * called on resolution, timeout, or error — no listener leaks.\n *\n * @param peer - Injected extension client.\n * @param platform - The platform handed to `authenticate()` (e.g. 'zelle').\n * Echoed back when the extension could not load a template.\n * @param actionType - The capture action type (e.g. 'transfer_venmo'). Its\n * parent is echoed back when the template DID load. Both are accepted; see\n * the guard below.\n * @param timeoutMs - Reject after this long with no matching message.\n */\nfunction awaitMetadata(\n peer: PeerClient,\n platform: string,\n actionType: string,\n timeoutMs: number,\n): Promise<MetadataResponse> {\n return new Promise<MetadataResponse>((resolve, reject) => {\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n\n const unsub = peer.onMetadataMessage((data) => {\n // The extension echoes the platform from one of two places:\n //\n // - Template loaded (success): the TEMPLATE's own `metadata.platform`.\n // - Template fetch failed: there is no template, so it echoes the\n // platform we passed, alongside an errorMessage such as\n // 'Failed to fetch provider template: 404'.\n //\n // With hosted templates the two agree (generic Zelle: 'zelle' both\n // ways). Accepting either still guards against a template whose\n // metadata.platform is the parent rail rather than the platform we\n // passed, so a successful capture is never dropped into a 60s timeout.\n const echoesCapturePlatform = data.platform === platform\n const echoesActionTypeParent = `transfer_${data.platform}` === actionType\n if (echoesCapturePlatform || echoesActionTypeParent) {\n clearTimeout(timeoutId)\n unsub()\n resolve(data)\n }\n })\n\n timeoutId = setTimeout(() => {\n unsub()\n reject(new Error(`Enclave capture timed out for platform \"${platform}\"`))\n }, timeoutMs)\n })\n}\n\nexport function createExtensionEnclaveAdapter(config: ExtensionEnclaveAdapterConfig): CaptureAdapter {\n const timeoutMs = config.timeoutMs ?? 60000\n\n return {\n kind: 'extension-enclave',\n\n async authenticateAndCapture(\n request: CaptureRequest,\n options?: { selectTransaction?: SelectTransaction },\n ): Promise<CaptureHandle> {\n const peer = config.getPeer()\n if (!peer) {\n throw new EnclaveCaptureUnavailable(request.platform)\n }\n\n // Ensure the extension is connected BEFORE authenticate. A cold\n // authenticate() on a fresh page returns errorMessage \"Peer extension\n // connection required\" with no buyerTeeCapture — which trips the\n // enclave-unavailable path. The legacy AuthenticateStep connects first\n // (and onramp-llm.md §3 `ensurePeerReady` requires it); the adapter must\n // too. This is why a warm local tab worked but a fresh prod session failed.\n const status = await peer.checkConnectionStatus()\n if (status !== 'connected') {\n await peer.requestConnection()\n }\n\n // Register the listener BEFORE authenticate fires to avoid a race where\n // the extension posts metadata before the listener is attached.\n const metadataPromise = awaitMetadata(peer, request.platform, request.actionType, timeoutMs)\n await peer.authenticate({\n actionType: request.actionType,\n platform: request.platform,\n captureMode: 'buyerTee',\n // Required for the extension to actually perform the Buyer TEE capture\n // (zkp2p 0.6.0 spec §7). Omitting these is why non-Revolut rails got\n // \"no buyerTee capture\" and dead-ended on the retired Reclaim path.\n attestationServiceUrl: config.attestationServiceUrl ?? DEFAULT_ATTESTATION_SERVICE_URL,\n // The attestation route equals the capture pair for every rail today\n // (generic Zelle included: zelle/transfer_zelle_chase). No template is\n // passed: extension 0.6.7 always fetches it from api.zkp2p.xyz and\n // ignores a page-supplied one, so the capture pair must be hosted.\n attestationActionType: request.attestationActionType ?? request.actionType,\n attestationPlatform: request.attestationPlatform ?? request.platform,\n })\n const response = await metadataPromise\n\n // No buyerTee capture ⇒ the extension didn't produce an encrypted enclave\n // session. Log WHY before failing — the extension may have surfaced an\n // errorMessage, or returned metadata-only (no capture) for this platform.\n if (!response.buyerTeeCapture) {\n const errorMessage = (response as { errorMessage?: unknown }).errorMessage\n console.warn(\n '[extensionEnclave] no buyerTeeCapture for',\n request.platform,\n '— response keys:', Object.keys(response),\n '| errorMessage:', errorMessage,\n '| metadata rows:', Array.isArray(response.metadata) ? response.metadata.length : '(none)',\n '| buyerTeeCapture:', response.buyerTeeCapture,\n )\n // Two distinct failures share the \"no capture\" shape:\n // - The extension surfaced an `errorMessage` ⇒ it ATTEMPTED the capture\n // but something failed (most often: it could not reach the\n // attestation enclave — a buyer-side network / firewall / ad-blocker\n // issue). Surface that as a retryable EnclaveCaptureFailed carrying\n // the raw reason, so the user sees an actionable message instead of\n // \"provider unsupported\".\n // - No `errorMessage` ⇒ the provider produced no capture path at all\n // (non-migrated). That stays EnclaveCaptureUnavailable, the\n // Reclaim-fallback signal.\n if (typeof errorMessage === 'string' && errorMessage.trim().length > 0) {\n throw new EnclaveCaptureFailed(request.platform, errorMessage)\n }\n throw new EnclaveCaptureUnavailable(request.platform)\n }\n\n const material: EnclaveMaterial = {\n encryptedSessionMaterial: response.buyerTeeCapture.encryptedSessionMaterial,\n payments: await resolvePaymentsToTry({\n match: matchPayment(response.metadata, request),\n history: response.metadata,\n selectTransaction: options?.selectTransaction,\n }),\n }\n return { kind: 'extension-enclave', material }\n },\n\n async attest(handle: CaptureHandle, request: CaptureRequest): Promise<VerifiedAttestation> {\n if (handle.kind !== 'extension-enclave') {\n throw new Error(\n `ExtensionEnclaveAdapter.attest received a handle of kind \"${handle.kind}\"; expected \"extension-enclave\".`,\n )\n }\n const material = handle.material as EnclaveMaterial\n // Try each candidate payment in order. A payment that already settled an\n // order is refused by the gate (PaymentAlreadyUsedError) and skipped;\n // any other failure stops here and surfaces as-is.\n let alreadyUsed: PaymentAlreadyUsedError | undefined\n for (const payment of material.payments) {\n try {\n const attestation = await attestPayment({ payment, material, request })\n // verificationData = enclave signer (SubmitStep.tsx:473-474).\n return { attestation, verificationData: attestation.signer as Hex }\n } catch (error) {\n if (!(error instanceof PaymentAlreadyUsedError)) throw error\n alreadyUsed = error\n }\n }\n throw alreadyUsed ?? new Error('No bank payment was captured for this order.')\n },\n }\n\n /** Attest one candidate payment through the gate. */\n function attestPayment({\n payment,\n material,\n request,\n }: {\n payment: PaymentToTry\n material: EnclaveMaterial\n request: CaptureRequest\n }) {\n // The selected row's params ARE the attestation params (spec §5,\n // buildBuyerTeeProof) and MUST be forwarded — dropping them is why the\n // enclave 400s with \"Invalid <provider> query params\". The metadata\n // `index` is then merged in ONLY for providers that expect it (Venmo /\n // Cash App / Revolut / Zelle) and omitted for the rest (PayPal / Wise /\n // Monzo / Chime). See providerRequiresMetadataIndex.\n const params = {\n ...(payment.params ?? {}),\n ...(providerRequiresMetadataIndex(request.provider as ProviderKey | SubProviderKey)\n ? { index: payment.index }\n : {}),\n }\n return requestBuyerTeeAttestation({\n attestationBaseUrl: config.attestationBaseUrl,\n platform: request.attestationPlatform ?? request.platform,\n actionType: request.attestationActionType ?? request.actionType,\n encryptedSessionMaterial: material.encryptedSessionMaterial,\n params,\n verifyingContract: request.verifyingContract,\n intent: request.intent,\n })\n }\n}\n\n// Re-narrow export so consumers can validate proof shapes if they bypass the\n// adapter (kept for parity with the live SubmitStep check, SubmitStep.tsx:206).\nexport { asBuyerTeeProof }\n"]}
|
|
@@ -39,18 +39,8 @@ interface PeerClient {
|
|
|
39
39
|
attestationServiceUrl?: string;
|
|
40
40
|
/** Attestation action type; defaults to `actionType` when omitted (spec §2). */
|
|
41
41
|
attestationActionType?: string;
|
|
42
|
-
/**
|
|
43
|
-
* Attestation platform, when it differs from `platform`. Required for
|
|
44
|
-
* generic Zelle, whose attestation route is `zelle` while capture runs as
|
|
45
|
-
* the bank (`chase`).
|
|
46
|
-
*/
|
|
42
|
+
/** Attestation platform; defaults to `platform` when omitted. */
|
|
47
43
|
attestationPlatform?: string;
|
|
48
|
-
/**
|
|
49
|
-
* Provider template supplied inline. When present the extension skips its
|
|
50
|
-
* own template fetch entirely — the only way to drive a rail zkp2p does not
|
|
51
|
-
* host. See ./providerTemplates.
|
|
52
|
-
*/
|
|
53
|
-
providerConfig?: Record<string, unknown>;
|
|
54
44
|
}): Promise<void>;
|
|
55
45
|
onMetadataMessage(fn: (data: MetadataResponse) => void): () => void;
|
|
56
46
|
/** Current extension connection status; `'connected'` means ready. */
|
|
@@ -39,18 +39,8 @@ interface PeerClient {
|
|
|
39
39
|
attestationServiceUrl?: string;
|
|
40
40
|
/** Attestation action type; defaults to `actionType` when omitted (spec §2). */
|
|
41
41
|
attestationActionType?: string;
|
|
42
|
-
/**
|
|
43
|
-
* Attestation platform, when it differs from `platform`. Required for
|
|
44
|
-
* generic Zelle, whose attestation route is `zelle` while capture runs as
|
|
45
|
-
* the bank (`chase`).
|
|
46
|
-
*/
|
|
42
|
+
/** Attestation platform; defaults to `platform` when omitted. */
|
|
47
43
|
attestationPlatform?: string;
|
|
48
|
-
/**
|
|
49
|
-
* Provider template supplied inline. When present the extension skips its
|
|
50
|
-
* own template fetch entirely — the only way to drive a rail zkp2p does not
|
|
51
|
-
* host. See ./providerTemplates.
|
|
52
|
-
*/
|
|
53
|
-
providerConfig?: Record<string, unknown>;
|
|
54
44
|
}): Promise<void>;
|
|
55
45
|
onMetadataMessage(fn: (data: MetadataResponse) => void): () => void;
|
|
56
46
|
/** Current extension connection status; `'connected'` means ready. */
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { DEFAULT_ATTESTATION_SERVICE_URL, asBuyerTeeProof, createExtensionEnclaveAdapter } from '../chunk-
|
|
1
|
+
export { DEFAULT_ATTESTATION_SERVICE_URL, asBuyerTeeProof, createExtensionEnclaveAdapter } from '../chunk-AMJXMSDV.js';
|
|
2
2
|
//# sourceMappingURL=extensionEnclave.js.map
|
|
3
3
|
//# sourceMappingURL=extensionEnclave.js.map
|
|
@@ -1,21 +1,10 @@
|
|
|
1
1
|
import { parseUnits } from 'viem';
|
|
2
2
|
import { providerRequiresMetadataIndex, providerConfigs } from '@provex/utils/payment';
|
|
3
3
|
import { convertTokenInputToCurrency } from '@provex/utils/conversionRates';
|
|
4
|
-
import
|
|
5
|
-
import bankofamericaTransferZelle from '@zkp2p/providers/bankofamerica/transfer_zelle.json';
|
|
6
|
-
import citiTransferZelle from '@zkp2p/providers/citi/transfer_zelle.json';
|
|
7
|
-
import { requestBuyerTeeAttestation, EnclaveCaptureUnavailable, EnclaveCaptureFailed, ManualSelectionRequired } from '@provex/proving';
|
|
4
|
+
import { PaymentAlreadyUsedError, EnclaveCaptureUnavailable, EnclaveCaptureFailed, requestBuyerTeeAttestation, ManualSelectionRequired } from '@provex/proving';
|
|
8
5
|
export { asBuyerTeeProof } from '@provex/proving';
|
|
9
6
|
|
|
10
7
|
// src/adapters/extensionEnclave.ts
|
|
11
|
-
var TEMPLATES_BY_CAPTURE_KEY = Object.freeze({
|
|
12
|
-
"chase/transfer_zelle": chaseTransferZelle,
|
|
13
|
-
"bankofamerica/transfer_zelle": bankofamericaTransferZelle,
|
|
14
|
-
"citi/transfer_zelle": citiTransferZelle
|
|
15
|
-
});
|
|
16
|
-
function providerTemplateFor(platform, actionType) {
|
|
17
|
-
return TEMPLATES_BY_CAPTURE_KEY[`${platform}/${actionType}`];
|
|
18
|
-
}
|
|
19
8
|
var DEFAULT_ATTESTATION_SERVICE_URL = "https://attestation-service.zkp2p.xyz";
|
|
20
9
|
function normalizeAmount(amount, decimals, centIsInt) {
|
|
21
10
|
const raw = String(amount);
|
|
@@ -27,7 +16,7 @@ function normalizeAmount(amount, decimals, centIsInt) {
|
|
|
27
16
|
}
|
|
28
17
|
return value;
|
|
29
18
|
}
|
|
30
|
-
function
|
|
19
|
+
function matchPayment(candidates, request) {
|
|
31
20
|
const currencyAmount = convertTokenInputToCurrency({
|
|
32
21
|
token: request.token,
|
|
33
22
|
amountOutInt: BigInt(request.intent.amount),
|
|
@@ -36,10 +25,28 @@ function autoMatch(candidates, request) {
|
|
|
36
25
|
const configs = providerConfigs(request.chainId);
|
|
37
26
|
const providerConfig = configs[request.provider];
|
|
38
27
|
const centIsInt = providerConfig?.amountInCents ?? false;
|
|
39
|
-
|
|
28
|
+
const matches = candidates.filter((candidate) => {
|
|
40
29
|
const converted = normalizeAmount(candidate.amount, request.token.decimals, centIsInt);
|
|
41
30
|
return converted === currencyAmount || converted === currencyAmount * -1n;
|
|
42
|
-
})
|
|
31
|
+
});
|
|
32
|
+
if (matches.length === 0) return { kind: "none" };
|
|
33
|
+
if (matches.length === 1) return { kind: "single", candidate: matches[0] };
|
|
34
|
+
return { kind: "several", candidates: matches };
|
|
35
|
+
}
|
|
36
|
+
function toPaymentToTry(candidate) {
|
|
37
|
+
return { index: candidate.originalIndex, params: candidate.params };
|
|
38
|
+
}
|
|
39
|
+
async function resolvePaymentsToTry({
|
|
40
|
+
match,
|
|
41
|
+
history,
|
|
42
|
+
selectTransaction
|
|
43
|
+
}) {
|
|
44
|
+
if (match.kind === "single") return [toPaymentToTry(match.candidate)];
|
|
45
|
+
if (match.kind === "several") return match.candidates.map(toPaymentToTry);
|
|
46
|
+
if (!selectTransaction) throw new ManualSelectionRequired(history);
|
|
47
|
+
const pickedIndex = await selectTransaction(history);
|
|
48
|
+
const picked = history.find((row) => row.originalIndex === pickedIndex);
|
|
49
|
+
return [{ index: pickedIndex, params: picked?.params }];
|
|
43
50
|
}
|
|
44
51
|
function awaitMetadata(peer, platform, actionType, timeoutMs) {
|
|
45
52
|
return new Promise((resolve, reject) => {
|
|
@@ -81,13 +88,12 @@ function createExtensionEnclaveAdapter(config) {
|
|
|
81
88
|
// (zkp2p 0.6.0 spec §7). Omitting these is why non-Revolut rails got
|
|
82
89
|
// "no buyerTee capture" and dead-ended on the retired Reclaim path.
|
|
83
90
|
attestationServiceUrl: config.attestationServiceUrl ?? DEFAULT_ATTESTATION_SERVICE_URL,
|
|
84
|
-
//
|
|
85
|
-
// Zelle
|
|
91
|
+
// The attestation route equals the capture pair for every rail today
|
|
92
|
+
// (generic Zelle included: zelle/transfer_zelle_chase). No template is
|
|
93
|
+
// passed: extension 0.6.7 always fetches it from api.zkp2p.xyz and
|
|
94
|
+
// ignores a page-supplied one, so the capture pair must be hosted.
|
|
86
95
|
attestationActionType: request.attestationActionType ?? request.actionType,
|
|
87
|
-
attestationPlatform: request.attestationPlatform ?? request.platform
|
|
88
|
-
// Bundled template for rails zkp2p does not host. Undefined for hosted
|
|
89
|
-
// rails, which keeps the extension fetching (and auto-updating) them.
|
|
90
|
-
providerConfig: providerTemplateFor(request.platform, request.actionType)
|
|
96
|
+
attestationPlatform: request.attestationPlatform ?? request.platform
|
|
91
97
|
});
|
|
92
98
|
const response = await metadataPromise;
|
|
93
99
|
if (!response.buyerTeeCapture) {
|
|
@@ -109,20 +115,13 @@ function createExtensionEnclaveAdapter(config) {
|
|
|
109
115
|
}
|
|
110
116
|
throw new EnclaveCaptureUnavailable(request.platform);
|
|
111
117
|
}
|
|
112
|
-
const matched = autoMatch(response.metadata, request);
|
|
113
|
-
let matchedIndex;
|
|
114
|
-
if (matched) {
|
|
115
|
-
matchedIndex = matched.originalIndex;
|
|
116
|
-
} else if (options?.selectTransaction) {
|
|
117
|
-
matchedIndex = await options.selectTransaction(response.metadata);
|
|
118
|
-
} else {
|
|
119
|
-
throw new ManualSelectionRequired(response.metadata);
|
|
120
|
-
}
|
|
121
|
-
const matchedRow = response.metadata.find((row) => row.originalIndex === matchedIndex);
|
|
122
118
|
const material = {
|
|
123
119
|
encryptedSessionMaterial: response.buyerTeeCapture.encryptedSessionMaterial,
|
|
124
|
-
|
|
125
|
-
|
|
120
|
+
payments: await resolvePaymentsToTry({
|
|
121
|
+
match: matchPayment(response.metadata, request),
|
|
122
|
+
history: response.metadata,
|
|
123
|
+
selectTransaction: options?.selectTransaction
|
|
124
|
+
})
|
|
126
125
|
};
|
|
127
126
|
return { kind: "extension-enclave", material };
|
|
128
127
|
},
|
|
@@ -133,30 +132,40 @@ function createExtensionEnclaveAdapter(config) {
|
|
|
133
132
|
);
|
|
134
133
|
}
|
|
135
134
|
const material = handle.material;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
// overrides unset and fall back to the capture values.
|
|
148
|
-
platform: request.attestationPlatform ?? request.platform,
|
|
149
|
-
actionType: request.attestationActionType ?? request.actionType,
|
|
150
|
-
encryptedSessionMaterial: material.encryptedSessionMaterial,
|
|
151
|
-
params,
|
|
152
|
-
verifyingContract: request.verifyingContract,
|
|
153
|
-
intent: request.intent
|
|
154
|
-
});
|
|
155
|
-
return { attestation, verificationData: attestation.signer };
|
|
135
|
+
let alreadyUsed;
|
|
136
|
+
for (const payment of material.payments) {
|
|
137
|
+
try {
|
|
138
|
+
const attestation = await attestPayment({ payment, material, request });
|
|
139
|
+
return { attestation, verificationData: attestation.signer };
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (!(error instanceof PaymentAlreadyUsedError)) throw error;
|
|
142
|
+
alreadyUsed = error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
throw alreadyUsed ?? new Error("No bank payment was captured for this order.");
|
|
156
146
|
}
|
|
157
147
|
};
|
|
148
|
+
function attestPayment({
|
|
149
|
+
payment,
|
|
150
|
+
material,
|
|
151
|
+
request
|
|
152
|
+
}) {
|
|
153
|
+
const params = {
|
|
154
|
+
...payment.params ?? {},
|
|
155
|
+
...providerRequiresMetadataIndex(request.provider) ? { index: payment.index } : {}
|
|
156
|
+
};
|
|
157
|
+
return requestBuyerTeeAttestation({
|
|
158
|
+
attestationBaseUrl: config.attestationBaseUrl,
|
|
159
|
+
platform: request.attestationPlatform ?? request.platform,
|
|
160
|
+
actionType: request.attestationActionType ?? request.actionType,
|
|
161
|
+
encryptedSessionMaterial: material.encryptedSessionMaterial,
|
|
162
|
+
params,
|
|
163
|
+
verifyingContract: request.verifyingContract,
|
|
164
|
+
intent: request.intent
|
|
165
|
+
});
|
|
166
|
+
}
|
|
158
167
|
}
|
|
159
168
|
|
|
160
169
|
export { DEFAULT_ATTESTATION_SERVICE_URL, createExtensionEnclaveAdapter };
|
|
161
|
-
//# sourceMappingURL=chunk-
|
|
162
|
-
//# sourceMappingURL=chunk-
|
|
170
|
+
//# sourceMappingURL=chunk-AMJXMSDV.js.map
|
|
171
|
+
//# sourceMappingURL=chunk-AMJXMSDV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/extensionEnclave.ts"],"names":[],"mappings":"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","file":"chunk-AMJXMSDV.js","sourcesContent":["/**\n * @fileoverview Extension + enclave CaptureAdapter (the v1 capture backend).\n *\n * Mirrors the live AuthenticateStep + GenerateProofStep enclave path\n * (AuthenticateStep.tsx:224-281, GenerateProofStep.tsx:237-251) reduced to\n * the buyerTee branch. Talks to an injected `window.peer`-shaped client so\n * the injected-PeerClient seam is preserved exactly.\n */\n\nimport { parseUnits } from 'viem'\nimport type { Hex } from 'viem'\nimport { providerConfigs, providerRequiresMetadataIndex } from '@provex/utils/payment'\nimport type { ProviderKey, SubProviderKey } from '@provex/utils/payment'\nimport type { ChainId } from '@provex/utils/chain'\nimport { convertTokenInputToCurrency } from '@provex/utils/conversionRates'\nimport {\n EnclaveCaptureUnavailable,\n EnclaveCaptureFailed,\n ManualSelectionRequired,\n PaymentAlreadyUsedError,\n asBuyerTeeProof,\n requestBuyerTeeAttestation,\n type CaptureAdapter,\n type CaptureCandidate,\n type CaptureHandle,\n type CaptureRequest,\n type SelectTransaction,\n type VerifiedAttestation,\n} from '@provex/proving'\n\n/** Minimal shape of the metadata response the extension posts back. */\ninterface MetadataResponse {\n platform: string\n metadata: CaptureCandidate[]\n buyerTeeCapture?: { encryptedSessionMaterial: string }\n}\n\n/**\n * zkp2p enclave used for Buyer TEE capture when the caller doesn't override\n * `ExtensionEnclaveAdapterConfig.attestationServiceUrl`. Matches the gate's\n * `TEE_ATTESTATION_URL` — the enclave the gate forwards to for the re-sign.\n */\nexport const DEFAULT_ATTESTATION_SERVICE_URL = 'https://attestation-service.zkp2p.xyz'\n\n/** Minimal slice of `window.peer` this adapter depends on. */\nexport interface PeerClient {\n authenticate(inputs: {\n actionType: string\n platform: string\n captureMode: 'buyerTee'\n /**\n * Enclave the extension establishes the Buyer TEE session with and encrypts\n * the session material toward. Required by the zkp2p 0.6.0 spec\n * (onramp-llm.md §7) — without it the extension performs no Buyer TEE\n * capture and falls back to the retired Reclaim path.\n */\n attestationServiceUrl?: string\n /** Attestation action type; defaults to `actionType` when omitted (spec §2). */\n attestationActionType?: string\n /** Attestation platform; defaults to `platform` when omitted. */\n attestationPlatform?: string\n }): Promise<void>\n onMetadataMessage(fn: (data: MetadataResponse) => void): () => void\n /** Current extension connection status; `'connected'` means ready. */\n checkConnectionStatus(): Promise<string>\n /** Prompt the user to connect the extension. Resolves true when approved. */\n requestConnection(): Promise<boolean>\n}\n\nexport interface ExtensionEnclaveAdapterConfig {\n /** Resolve the live peer client (e.g. `() => window.peer`). */\n getPeer: () => PeerClient | undefined\n /** Gate/attestation origin handed to requestBuyerTeeAttestation (the re-sign hop). */\n attestationBaseUrl: string\n /**\n * Enclave URL passed to the extension's `authenticate` so it performs the\n * Buyer TEE capture (zkp2p 0.6.0 spec §7). This is the zkp2p enclave the\n * session material is encrypted toward — the SAME enclave the gate forwards\n * to for the re-sign — NOT our gate's re-sign origin (`attestationBaseUrl`).\n * Defaults to {@link DEFAULT_ATTESTATION_SERVICE_URL} when omitted; set it to\n * override the enclave for local testing.\n */\n attestationServiceUrl?: string\n /**\n * Milliseconds before awaitMetadata rejects if the extension never posts a\n * matching metadata message. Mirrors the 60-second timeout in\n * AuthenticateStep.tsx:232-236. Defaults to 60000.\n */\n timeoutMs?: number\n}\n\n/** One bank-history row the attestation may settle this order with. */\ninterface PaymentToTry {\n /** The row's index in the captured bank history. */\n index: number\n /** The row's provider attestation params (spec §4), forwarded to the enclave. */\n params?: Record<string, string | number | boolean>\n}\n\ninterface EnclaveMaterial {\n encryptedSessionMaterial: string\n /**\n * Payments to attest, in order. One entry for a single amount match or a\n * buyer's pick; every amount match (bank order, newest first) when several\n * payments share the order's amount. Bank dates resolve only to the day,\n * so they cannot tell same-day payments apart; the gate can, by refusing a\n * payment that already settled an order.\n */\n payments: PaymentToTry[]\n}\n\n/**\n * Normalize an amount from the extension into a bigint in token base units.\n *\n * Reproduces findMetadataMessage's normalizeAmount (useZKTLS.ts:630-639).\n * The extension sends amounts in different formats per provider:\n * - Venmo: `\"- $1.00\"` (formatted dollar string — isFormattedDollars=true)\n * - Others: `100` (integer cents) or `1.00` (decimal dollars)\n *\n * When `centIsInt` is true and the raw value has no currency symbol or decimal\n * point (i.e. it arrived as integer cents), we divide by 100 to convert to\n * the same base unit scale that parseUnits applied.\n */\nfunction normalizeAmount(amount: number | string, decimals: number, centIsInt: boolean): bigint {\n const raw = String(amount)\n const isFormattedDollars = /[^0-9\\-]/.test(raw) && raw.includes('.')\n const cleaned = raw.replace(/[^0-9.]/g, '')\n let value = parseUnits(cleaned, decimals)\n if (centIsInt && !isFormattedDollars) {\n value = value / 100n\n }\n return value\n}\n\n/**\n * The outcome of matching the bank history against the intent's amount.\n *\n * - `single`: exactly one payment has the intent's amount.\n * - `several`: more than one does (e.g. two $2 Zelle payments to the same\n * seller). All are tried in bank order; the gate refuses any that already\n * settled an order. Taking only the first sent an already-used payment and\n * failed the order with \"This payment has already been verified\".\n * - `none`: no payment has that amount; the buyer picks from the full list.\n */\ntype PaymentMatch =\n | { kind: 'single'; candidate: CaptureCandidate }\n | { kind: 'several'; candidates: CaptureCandidate[] }\n | { kind: 'none' }\n\n/**\n * Match the bank history against the intent's FIAT currency amount,\n * accepting a positive or negative sign as findMetadataMessage does.\n *\n * Uses the same pipeline as the live AuthenticateStep.tsx:147-163:\n * 1. Convert token amount → fiat via convertTokenInputToCurrency.\n * 2. Normalize each candidate with the provider's centIsInt flag.\n * 3. Match equality (or negated equality for debit/refund signs).\n *\n * `centIsInt` is sourced from `providerConfigs(chainId)[provider]?.amountInCents`\n * which mirrors how the live caller derives it:\n * `targetProvider?.amountInCents ?? false`\n * where `targetProvider` is resolved via `useProvider` from the same\n * providerConfigs call. Sub-providers (e.g. zelle-chase) do NOT currently\n * set amountInCents, so the parent-provider lookup is sufficient.\n *\n * Matches keep the bank's own order (newest first for the hosted templates).\n */\nfunction matchPayment(\n candidates: CaptureCandidate[],\n request: CaptureRequest,\n): PaymentMatch {\n const currencyAmount = convertTokenInputToCurrency({\n token: request.token,\n amountOutInt: BigInt(request.intent.amount),\n rate: BigInt(request.intent.conversionRate),\n })\n\n // Resolve centIsInt from the provider config — same source as the live UI.\n // The cast is safe: CaptureRequest.chainId must be a supported chain ID.\n const configs = providerConfigs(request.chainId as ChainId)\n const providerConfig = configs[request.provider as keyof typeof configs]\n const centIsInt = (providerConfig as { amountInCents?: boolean } | undefined)?.amountInCents ?? false\n\n const matches = candidates.filter((candidate) => {\n const converted = normalizeAmount(candidate.amount, request.token.decimals, centIsInt)\n return converted === currencyAmount || converted === currencyAmount * -1n\n })\n if (matches.length === 0) return { kind: 'none' }\n if (matches.length === 1) return { kind: 'single', candidate: matches[0]! }\n return { kind: 'several', candidates: matches }\n}\n\n/** The bank-history row as a payment to try. */\nfunction toPaymentToTry(candidate: CaptureCandidate): PaymentToTry {\n return { index: candidate.originalIndex, params: candidate.params }\n}\n\n/**\n * The payments to attest, in order: the amount matches, else the buyer's pick\n * from the whole history. Throws {@link ManualSelectionRequired} when a pick is\n * needed but no resolver was supplied.\n */\nasync function resolvePaymentsToTry({\n match,\n history,\n selectTransaction,\n}: {\n match: PaymentMatch\n history: CaptureCandidate[]\n selectTransaction?: SelectTransaction\n}): Promise<PaymentToTry[]> {\n if (match.kind === 'single') return [toPaymentToTry(match.candidate)]\n if (match.kind === 'several') return match.candidates.map(toPaymentToTry)\n if (!selectTransaction) throw new ManualSelectionRequired(history)\n const pickedIndex = await selectTransaction(history)\n const picked = history.find((row) => row.originalIndex === pickedIndex)\n return [{ index: pickedIndex, params: picked?.params }]\n}\n\n/**\n * Wait for the metadata message the extension posts for `platform`.\n *\n * Races a configurable timeout (default 60s) against the metadata event,\n * mirroring AuthenticateStep.tsx:232-236. The unsubscribe function is always\n * called on resolution, timeout, or error — no listener leaks.\n *\n * @param peer - Injected extension client.\n * @param platform - The platform handed to `authenticate()` (e.g. 'zelle').\n * Echoed back when the extension could not load a template.\n * @param actionType - The capture action type (e.g. 'transfer_venmo'). Its\n * parent is echoed back when the template DID load. Both are accepted; see\n * the guard below.\n * @param timeoutMs - Reject after this long with no matching message.\n */\nfunction awaitMetadata(\n peer: PeerClient,\n platform: string,\n actionType: string,\n timeoutMs: number,\n): Promise<MetadataResponse> {\n return new Promise<MetadataResponse>((resolve, reject) => {\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n\n const unsub = peer.onMetadataMessage((data) => {\n // The extension echoes the platform from one of two places:\n //\n // - Template loaded (success): the TEMPLATE's own `metadata.platform`.\n // - Template fetch failed: there is no template, so it echoes the\n // platform we passed, alongside an errorMessage such as\n // 'Failed to fetch provider template: 404'.\n //\n // With hosted templates the two agree (generic Zelle: 'zelle' both\n // ways). Accepting either still guards against a template whose\n // metadata.platform is the parent rail rather than the platform we\n // passed, so a successful capture is never dropped into a 60s timeout.\n const echoesCapturePlatform = data.platform === platform\n const echoesActionTypeParent = `transfer_${data.platform}` === actionType\n if (echoesCapturePlatform || echoesActionTypeParent) {\n clearTimeout(timeoutId)\n unsub()\n resolve(data)\n }\n })\n\n timeoutId = setTimeout(() => {\n unsub()\n reject(new Error(`Enclave capture timed out for platform \"${platform}\"`))\n }, timeoutMs)\n })\n}\n\nexport function createExtensionEnclaveAdapter(config: ExtensionEnclaveAdapterConfig): CaptureAdapter {\n const timeoutMs = config.timeoutMs ?? 60000\n\n return {\n kind: 'extension-enclave',\n\n async authenticateAndCapture(\n request: CaptureRequest,\n options?: { selectTransaction?: SelectTransaction },\n ): Promise<CaptureHandle> {\n const peer = config.getPeer()\n if (!peer) {\n throw new EnclaveCaptureUnavailable(request.platform)\n }\n\n // Ensure the extension is connected BEFORE authenticate. A cold\n // authenticate() on a fresh page returns errorMessage \"Peer extension\n // connection required\" with no buyerTeeCapture — which trips the\n // enclave-unavailable path. The legacy AuthenticateStep connects first\n // (and onramp-llm.md §3 `ensurePeerReady` requires it); the adapter must\n // too. This is why a warm local tab worked but a fresh prod session failed.\n const status = await peer.checkConnectionStatus()\n if (status !== 'connected') {\n await peer.requestConnection()\n }\n\n // Register the listener BEFORE authenticate fires to avoid a race where\n // the extension posts metadata before the listener is attached.\n const metadataPromise = awaitMetadata(peer, request.platform, request.actionType, timeoutMs)\n await peer.authenticate({\n actionType: request.actionType,\n platform: request.platform,\n captureMode: 'buyerTee',\n // Required for the extension to actually perform the Buyer TEE capture\n // (zkp2p 0.6.0 spec §7). Omitting these is why non-Revolut rails got\n // \"no buyerTee capture\" and dead-ended on the retired Reclaim path.\n attestationServiceUrl: config.attestationServiceUrl ?? DEFAULT_ATTESTATION_SERVICE_URL,\n // The attestation route equals the capture pair for every rail today\n // (generic Zelle included: zelle/transfer_zelle_chase). No template is\n // passed: extension 0.6.7 always fetches it from api.zkp2p.xyz and\n // ignores a page-supplied one, so the capture pair must be hosted.\n attestationActionType: request.attestationActionType ?? request.actionType,\n attestationPlatform: request.attestationPlatform ?? request.platform,\n })\n const response = await metadataPromise\n\n // No buyerTee capture ⇒ the extension didn't produce an encrypted enclave\n // session. Log WHY before failing — the extension may have surfaced an\n // errorMessage, or returned metadata-only (no capture) for this platform.\n if (!response.buyerTeeCapture) {\n const errorMessage = (response as { errorMessage?: unknown }).errorMessage\n console.warn(\n '[extensionEnclave] no buyerTeeCapture for',\n request.platform,\n '— response keys:', Object.keys(response),\n '| errorMessage:', errorMessage,\n '| metadata rows:', Array.isArray(response.metadata) ? response.metadata.length : '(none)',\n '| buyerTeeCapture:', response.buyerTeeCapture,\n )\n // Two distinct failures share the \"no capture\" shape:\n // - The extension surfaced an `errorMessage` ⇒ it ATTEMPTED the capture\n // but something failed (most often: it could not reach the\n // attestation enclave — a buyer-side network / firewall / ad-blocker\n // issue). Surface that as a retryable EnclaveCaptureFailed carrying\n // the raw reason, so the user sees an actionable message instead of\n // \"provider unsupported\".\n // - No `errorMessage` ⇒ the provider produced no capture path at all\n // (non-migrated). That stays EnclaveCaptureUnavailable, the\n // Reclaim-fallback signal.\n if (typeof errorMessage === 'string' && errorMessage.trim().length > 0) {\n throw new EnclaveCaptureFailed(request.platform, errorMessage)\n }\n throw new EnclaveCaptureUnavailable(request.platform)\n }\n\n const material: EnclaveMaterial = {\n encryptedSessionMaterial: response.buyerTeeCapture.encryptedSessionMaterial,\n payments: await resolvePaymentsToTry({\n match: matchPayment(response.metadata, request),\n history: response.metadata,\n selectTransaction: options?.selectTransaction,\n }),\n }\n return { kind: 'extension-enclave', material }\n },\n\n async attest(handle: CaptureHandle, request: CaptureRequest): Promise<VerifiedAttestation> {\n if (handle.kind !== 'extension-enclave') {\n throw new Error(\n `ExtensionEnclaveAdapter.attest received a handle of kind \"${handle.kind}\"; expected \"extension-enclave\".`,\n )\n }\n const material = handle.material as EnclaveMaterial\n // Try each candidate payment in order. A payment that already settled an\n // order is refused by the gate (PaymentAlreadyUsedError) and skipped;\n // any other failure stops here and surfaces as-is.\n let alreadyUsed: PaymentAlreadyUsedError | undefined\n for (const payment of material.payments) {\n try {\n const attestation = await attestPayment({ payment, material, request })\n // verificationData = enclave signer (SubmitStep.tsx:473-474).\n return { attestation, verificationData: attestation.signer as Hex }\n } catch (error) {\n if (!(error instanceof PaymentAlreadyUsedError)) throw error\n alreadyUsed = error\n }\n }\n throw alreadyUsed ?? new Error('No bank payment was captured for this order.')\n },\n }\n\n /** Attest one candidate payment through the gate. */\n function attestPayment({\n payment,\n material,\n request,\n }: {\n payment: PaymentToTry\n material: EnclaveMaterial\n request: CaptureRequest\n }) {\n // The selected row's params ARE the attestation params (spec §5,\n // buildBuyerTeeProof) and MUST be forwarded — dropping them is why the\n // enclave 400s with \"Invalid <provider> query params\". The metadata\n // `index` is then merged in ONLY for providers that expect it (Venmo /\n // Cash App / Revolut / Zelle) and omitted for the rest (PayPal / Wise /\n // Monzo / Chime). See providerRequiresMetadataIndex.\n const params = {\n ...(payment.params ?? {}),\n ...(providerRequiresMetadataIndex(request.provider as ProviderKey | SubProviderKey)\n ? { index: payment.index }\n : {}),\n }\n return requestBuyerTeeAttestation({\n attestationBaseUrl: config.attestationBaseUrl,\n platform: request.attestationPlatform ?? request.platform,\n actionType: request.attestationActionType ?? request.actionType,\n encryptedSessionMaterial: material.encryptedSessionMaterial,\n params,\n verifyingContract: request.verifyingContract,\n intent: request.intent,\n })\n }\n}\n\n// Re-narrow export so consumers can validate proof shapes if they bypass the\n// adapter (kept for parity with the live SubmitStep check, SubmitStep.tsx:206).\nexport { asBuyerTeeProof }\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -6,17 +6,8 @@ var time = require('@provex/utils/time');
|
|
|
6
6
|
var payment = require('@provex/utils/payment');
|
|
7
7
|
var conversionRates = require('@provex/utils/conversionRates');
|
|
8
8
|
var jsxRuntime = require('react/jsx-runtime');
|
|
9
|
-
var chaseTransferZelle = require('@zkp2p/providers/chase/transfer_zelle.json');
|
|
10
|
-
var bankofamericaTransferZelle = require('@zkp2p/providers/bankofamerica/transfer_zelle.json');
|
|
11
|
-
var citiTransferZelle = require('@zkp2p/providers/citi/transfer_zelle.json');
|
|
12
9
|
var proving = require('@provex/proving');
|
|
13
10
|
|
|
14
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
|
-
|
|
16
|
-
var chaseTransferZelle__default = /*#__PURE__*/_interopDefault(chaseTransferZelle);
|
|
17
|
-
var bankofamericaTransferZelle__default = /*#__PURE__*/_interopDefault(bankofamericaTransferZelle);
|
|
18
|
-
var citiTransferZelle__default = /*#__PURE__*/_interopDefault(citiTransferZelle);
|
|
19
|
-
|
|
20
11
|
// src/useZKTLS.ts
|
|
21
12
|
var buildOnrampQuery = (params) => {
|
|
22
13
|
const search = new URLSearchParams();
|
|
@@ -320,14 +311,6 @@ function ZKTLSProvider({ children }) {
|
|
|
320
311
|
const zktlsHook = useZKTLS();
|
|
321
312
|
return /* @__PURE__ */ jsxRuntime.jsx(ZKTLSContext.Provider, { value: zktlsHook, children });
|
|
322
313
|
}
|
|
323
|
-
var TEMPLATES_BY_CAPTURE_KEY = Object.freeze({
|
|
324
|
-
"chase/transfer_zelle": chaseTransferZelle__default.default,
|
|
325
|
-
"bankofamerica/transfer_zelle": bankofamericaTransferZelle__default.default,
|
|
326
|
-
"citi/transfer_zelle": citiTransferZelle__default.default
|
|
327
|
-
});
|
|
328
|
-
function providerTemplateFor(platform, actionType) {
|
|
329
|
-
return TEMPLATES_BY_CAPTURE_KEY[`${platform}/${actionType}`];
|
|
330
|
-
}
|
|
331
314
|
var DEFAULT_ATTESTATION_SERVICE_URL = "https://attestation-service.zkp2p.xyz";
|
|
332
315
|
function normalizeAmount(amount, decimals, centIsInt) {
|
|
333
316
|
const raw = String(amount);
|
|
@@ -339,7 +322,7 @@ function normalizeAmount(amount, decimals, centIsInt) {
|
|
|
339
322
|
}
|
|
340
323
|
return value;
|
|
341
324
|
}
|
|
342
|
-
function
|
|
325
|
+
function matchPayment(candidates, request) {
|
|
343
326
|
const currencyAmount = conversionRates.convertTokenInputToCurrency({
|
|
344
327
|
token: request.token,
|
|
345
328
|
amountOutInt: BigInt(request.intent.amount),
|
|
@@ -348,10 +331,28 @@ function autoMatch(candidates, request) {
|
|
|
348
331
|
const configs = payment.providerConfigs(request.chainId);
|
|
349
332
|
const providerConfig = configs[request.provider];
|
|
350
333
|
const centIsInt = providerConfig?.amountInCents ?? false;
|
|
351
|
-
|
|
334
|
+
const matches = candidates.filter((candidate) => {
|
|
352
335
|
const converted = normalizeAmount(candidate.amount, request.token.decimals, centIsInt);
|
|
353
336
|
return converted === currencyAmount || converted === currencyAmount * -1n;
|
|
354
|
-
})
|
|
337
|
+
});
|
|
338
|
+
if (matches.length === 0) return { kind: "none" };
|
|
339
|
+
if (matches.length === 1) return { kind: "single", candidate: matches[0] };
|
|
340
|
+
return { kind: "several", candidates: matches };
|
|
341
|
+
}
|
|
342
|
+
function toPaymentToTry(candidate) {
|
|
343
|
+
return { index: candidate.originalIndex, params: candidate.params };
|
|
344
|
+
}
|
|
345
|
+
async function resolvePaymentsToTry({
|
|
346
|
+
match,
|
|
347
|
+
history,
|
|
348
|
+
selectTransaction
|
|
349
|
+
}) {
|
|
350
|
+
if (match.kind === "single") return [toPaymentToTry(match.candidate)];
|
|
351
|
+
if (match.kind === "several") return match.candidates.map(toPaymentToTry);
|
|
352
|
+
if (!selectTransaction) throw new proving.ManualSelectionRequired(history);
|
|
353
|
+
const pickedIndex = await selectTransaction(history);
|
|
354
|
+
const picked = history.find((row) => row.originalIndex === pickedIndex);
|
|
355
|
+
return [{ index: pickedIndex, params: picked?.params }];
|
|
355
356
|
}
|
|
356
357
|
function awaitMetadata(peer, platform, actionType, timeoutMs) {
|
|
357
358
|
return new Promise((resolve, reject) => {
|
|
@@ -393,13 +394,12 @@ function createExtensionEnclaveAdapter(config) {
|
|
|
393
394
|
// (zkp2p 0.6.0 spec §7). Omitting these is why non-Revolut rails got
|
|
394
395
|
// "no buyerTee capture" and dead-ended on the retired Reclaim path.
|
|
395
396
|
attestationServiceUrl: config.attestationServiceUrl ?? DEFAULT_ATTESTATION_SERVICE_URL,
|
|
396
|
-
//
|
|
397
|
-
// Zelle
|
|
397
|
+
// The attestation route equals the capture pair for every rail today
|
|
398
|
+
// (generic Zelle included: zelle/transfer_zelle_chase). No template is
|
|
399
|
+
// passed: extension 0.6.7 always fetches it from api.zkp2p.xyz and
|
|
400
|
+
// ignores a page-supplied one, so the capture pair must be hosted.
|
|
398
401
|
attestationActionType: request.attestationActionType ?? request.actionType,
|
|
399
|
-
attestationPlatform: request.attestationPlatform ?? request.platform
|
|
400
|
-
// Bundled template for rails zkp2p does not host. Undefined for hosted
|
|
401
|
-
// rails, which keeps the extension fetching (and auto-updating) them.
|
|
402
|
-
providerConfig: providerTemplateFor(request.platform, request.actionType)
|
|
402
|
+
attestationPlatform: request.attestationPlatform ?? request.platform
|
|
403
403
|
});
|
|
404
404
|
const response = await metadataPromise;
|
|
405
405
|
if (!response.buyerTeeCapture) {
|
|
@@ -421,20 +421,13 @@ function createExtensionEnclaveAdapter(config) {
|
|
|
421
421
|
}
|
|
422
422
|
throw new proving.EnclaveCaptureUnavailable(request.platform);
|
|
423
423
|
}
|
|
424
|
-
const matched = autoMatch(response.metadata, request);
|
|
425
|
-
let matchedIndex;
|
|
426
|
-
if (matched) {
|
|
427
|
-
matchedIndex = matched.originalIndex;
|
|
428
|
-
} else if (options?.selectTransaction) {
|
|
429
|
-
matchedIndex = await options.selectTransaction(response.metadata);
|
|
430
|
-
} else {
|
|
431
|
-
throw new proving.ManualSelectionRequired(response.metadata);
|
|
432
|
-
}
|
|
433
|
-
const matchedRow = response.metadata.find((row) => row.originalIndex === matchedIndex);
|
|
434
424
|
const material = {
|
|
435
425
|
encryptedSessionMaterial: response.buyerTeeCapture.encryptedSessionMaterial,
|
|
436
|
-
|
|
437
|
-
|
|
426
|
+
payments: await resolvePaymentsToTry({
|
|
427
|
+
match: matchPayment(response.metadata, request),
|
|
428
|
+
history: response.metadata,
|
|
429
|
+
selectTransaction: options?.selectTransaction
|
|
430
|
+
})
|
|
438
431
|
};
|
|
439
432
|
return { kind: "extension-enclave", material };
|
|
440
433
|
},
|
|
@@ -445,28 +438,38 @@ function createExtensionEnclaveAdapter(config) {
|
|
|
445
438
|
);
|
|
446
439
|
}
|
|
447
440
|
const material = handle.material;
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
// overrides unset and fall back to the capture values.
|
|
460
|
-
platform: request.attestationPlatform ?? request.platform,
|
|
461
|
-
actionType: request.attestationActionType ?? request.actionType,
|
|
462
|
-
encryptedSessionMaterial: material.encryptedSessionMaterial,
|
|
463
|
-
params,
|
|
464
|
-
verifyingContract: request.verifyingContract,
|
|
465
|
-
intent: request.intent
|
|
466
|
-
});
|
|
467
|
-
return { attestation, verificationData: attestation.signer };
|
|
441
|
+
let alreadyUsed;
|
|
442
|
+
for (const payment of material.payments) {
|
|
443
|
+
try {
|
|
444
|
+
const attestation = await attestPayment({ payment, material, request });
|
|
445
|
+
return { attestation, verificationData: attestation.signer };
|
|
446
|
+
} catch (error) {
|
|
447
|
+
if (!(error instanceof proving.PaymentAlreadyUsedError)) throw error;
|
|
448
|
+
alreadyUsed = error;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
throw alreadyUsed ?? new Error("No bank payment was captured for this order.");
|
|
468
452
|
}
|
|
469
453
|
};
|
|
454
|
+
function attestPayment({
|
|
455
|
+
payment: payment$1,
|
|
456
|
+
material,
|
|
457
|
+
request
|
|
458
|
+
}) {
|
|
459
|
+
const params = {
|
|
460
|
+
...payment$1.params ?? {},
|
|
461
|
+
...payment.providerRequiresMetadataIndex(request.provider) ? { index: payment$1.index } : {}
|
|
462
|
+
};
|
|
463
|
+
return proving.requestBuyerTeeAttestation({
|
|
464
|
+
attestationBaseUrl: config.attestationBaseUrl,
|
|
465
|
+
platform: request.attestationPlatform ?? request.platform,
|
|
466
|
+
actionType: request.attestationActionType ?? request.actionType,
|
|
467
|
+
encryptedSessionMaterial: material.encryptedSessionMaterial,
|
|
468
|
+
params,
|
|
469
|
+
verifyingContract: request.verifyingContract,
|
|
470
|
+
intent: request.intent
|
|
471
|
+
});
|
|
472
|
+
}
|
|
470
473
|
}
|
|
471
474
|
|
|
472
475
|
exports.PROOF_ENCODING = PROOF_ENCODING;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/useZKTLS.ts","../src/ZKTLSContext.tsx","../src/adapters/providerTemplates.ts","../src/adapters/extensionEnclave.ts"],"names":["parseAbiParameter","encodeAbiParameters","subProviderConfigs","encodePacked","SeriesProgress","useState","useEffect","useMemo","sleep","extensionPlatforms","providers","convertTokenInputToCurrency","normalizeAmount","parseUnits","createContext","useContext","chaseTransferZelle","bankofamericaTransferZelle","citiTransferZelle","providerConfigs","EnclaveCaptureUnavailable","EnclaveCaptureFailed","ManualSelectionRequired","providerRequiresMetadataIndex","requestBuyerTeeAttestation"],"mappings":";;;;;;;;;;;;;;;;;;;;AAkRO,IAAM,gBAAA,GAAmB,CAAC,MAAA,KAA8C;AAC7E,EAAA,MAAM,MAAA,GAAS,IAAI,eAAA,EAAgB;AACnC,EAAA,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,CAAE,OAAA,CAAQ,CAAC,CAAC,GAAA,EAAK,KAAK,CAAA,KAAM;AAC/C,IAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,KAAU,IAAA,EAAM;AAC3C,IAAA,MAAA,CAAO,GAAA,CAAI,GAAA,EAAK,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,EAC/B,CAAC,CAAA;AACD,EAAA,OAAO,OAAO,QAAA,EAAS;AACzB;AAYO,IAAM,6BAA6B,CAAC;AAAA,EACzC,wBAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF,CAAA,MAIsB;AAAA,EACpB,UAAU,EAAC;AAAA,EACX,UAAA;AAAA,EACA,EAAA,EAAI,aAAa,KAAK,CAAA,CAAA;AAAA,EACtB,MAAA,EAAQ,SAAA;AAAA,EACR,SAAA,EAAW,CAAA;AAAA;AAAA;AAAA,EAGX,KAAA,EAAO;AAAA,IACL,SAAA,EAAW,UAAA;AAAA,IACX,wBAAA;AAAA,IACA,MAAA,EAAQ,EAAE,KAAA;AAAM;AAEpB,CAAA;AAEO,IAAM,YAAA,GAAe;AAGrB,IAAM,oBAAA,GAAuB,CAAC,SAAA,KAA6B;AAChE,EAAA,OAAO,CAAA,EAAA,EAAK,SAAA,CACT,GAAA,CAAI,CAAA,IAAA,KAAQ,KAAK,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAC,CAAA,CAC9C,IAAA,CAAK,EAAE,CAAC,CAAA,CAAA;AACb;AAGO,IAAM,mBAAA,GAAsB,CAAC,WAAA,KAAgC;AAClE,EAAA,OAAO;AAAA,IACL,SAAA,EAAW;AAAA,MACT,QAAA,EAAU,YAAY,KAAA,CAAM,QAAA;AAAA,MAC5B,UAAA,EAAY,YAAY,KAAA,CAAM,UAAA;AAAA,MAC9B,OAAA,EAAS,YAAY,KAAA,CAAM;AAAA,KAC7B;AAAA,IACA,WAAA,EAAa;AAAA,MACX,KAAA,EAAO;AAAA,QACL,UAAA,EAAY,YAAY,KAAA,CAAM,UAAA;AAAA,QAC9B,KAAA,EAAO,YAAY,KAAA,CAAM,KAAA;AAAA,QACzB,UAAA,EAAY,YAAY,KAAA,CAAM,UAAA;AAAA,QAC9B,KAAA,EAAO,YAAY,KAAA,CAAM;AAAA,OAC3B;AAAA,MACA,UAAA,EAAY,CAAC,oBAAA,CAAqB,MAAA,CAAO,OAAO,WAAA,CAAY,UAAA,CAAW,cAAc,CAAC,CAAC;AAAA,KACzF;AAAA,IACA,cAAA,EAAgB;AAAA,GAClB;AACF;AAGO,IAAM,cAAA,GAAiBA,uBAAkB,qMAAqM;AAG9O,SAAS,mBAAmB,KAAA,EAA0B;AAC3D,EAAA,OAAOC,yBAAoB,CAAC,cAAc,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AACtD;AAGO,IAAM,eAAA,GAAkB,CAAC,MAAA,EAAsB,MAAA,KAAyB;AAC7E,EAAA,OAAOA,wBAAA,CAAoB,CAAC,cAAA,EAAgB,cAAc,GAAG,CAAC,MAAA,EAAQ,MAAM,CAAC,CAAA;AAC/E;AAGO,IAAM,cAAc,CAAC,EAAE,eAAA,EAAiB,WAAA,EAAa,UAAS,KAI/D;AACJ,EAAA,MAAM,gBAAgB,eAAA,CAAgB,GAAA,CAAI,CAAC,CAAA,KAAM,mBAAA,CAAoB,CAAC,CAAC,CAAA;AACvE,EAAA,IAAI,aAAa,OAAA,EAAS;AAExB,IAAA,MAAM,aAAA,GAAgBC,0BAAA,CAAoB,QAAQ,CAAA,CAAG,WAA6B,CAAA,CAAG,aAAA;AACrF,IAAA,MAAM,UAAA,GAAa,aAAA,CAAc,MAAA,KAAW,CAAA,GACxC,gBAAgB,aAAA,CAAc,CAAC,CAAA,EAAI,aAAA,CAAc,CAAC,CAAE,CAAA,GACpD,kBAAA,CAAmB,aAAA,CAAc,CAAC,CAAE,CAAA;AAExC,IAAA,MAAM,oBAAoB,4BAAA,CAA6B;AAAA,MACrD,aAAA;AAAA,MACA,KAAA,EAAO;AAAA,KACR,CAAA;AACD,IAAA,OAAO,iBAAA;AAAA,EACT;AACA,EAAA,MAAM,CAAC,YAAY,CAAA,GAAI,aAAA;AACvB,EAAA,OAAO,mBAAmB,YAAa,CAAA;AACzC;AAMO,IAAM,+BAA+B,CAAC;AAAA,EAC3C,KAAA;AAAA,EACA;AACF,CAAA,KAGW;AACT,EAAA,OAAOC,iBAAA,CAAa,CAAC,OAAA,EAAS,OAAO,GAAG,CAAC,aAAA,EAAe,KAAK,CAAC,CAAA;AAChE;AAGO,IAAK,cAAA,qBAAAC,eAAAA,KAAL;AACL,EAAAA,eAAAA,CAAAA,eAAAA,CAAA,YAAA,CAAA,GAAA,CAAA,CAAA,GAAA,YAAA;AACA,EAAAA,eAAAA,CAAAA,eAAAA,CAAA,SAAA,CAAA,GAAA,CAAA,CAAA,GAAA,SAAA;AACA,EAAAA,eAAAA,CAAAA,eAAAA,CAAA,UAAA,CAAA,GAAA,CAAA,CAAA,GAAA,UAAA;AAHU,EAAA,OAAAA,eAAAA;AAAA,CAAA,EAAA,cAAA,IAAA,EAAA;AAoBL,IAAM,WAAW,MAAM;AAC5B,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAIC,eAAS,KAAK,CAAA;AACxD,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,MAAM,QAAQ,MAAM;AAClB,MAAA,gBAAA,CAAiB,IAAI,CAAA;AAAA,IACvB,CAAA;AACA,IAAA,MAAM,SAAA,GAAY,kBAAA;AAClB,IAAA,IAAI,OAAO,MAAA,CAAO,IAAA,KAAS,WAAA,EAAa;AAEtC,MAAA,KAAA,EAAM;AAAA,IACR,CAAA,MAAO;AACL,MAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,KAAK,CAAA;AACxC,MAAA,OAAO,MAAM;AACX,QAAA,MAAA,CAAO,mBAAA,CAAoB,WAAW,KAAK,CAAA;AAAA,MAC7C,CAAA;AAAA,IACF;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AACL,EAAA,OAAOC,cAAQ,MAAM;AAEnB,IAAA,MAAM,cAAA,GAAiB,OAAO,OAAA,KAAmD;AAC/E,MAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,CAAO,IAAA,CAAM,eAAe,OAAO,CAAA;AACvD,MAAA,IAAI,KAAA,IAAS,KAAA,CAAM,aAAA,EAAe,OAAO,KAAA,CAAM,aAAA;AAC/C,MAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,IAAA,CAAM,WAAA,EAAY;AAC9C,MAAA,MAAM,aAAA,GAAgB,OAAO,cAAA,CAAe,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,OAAO,CAAA;AACxE,MAAA,IAAI,eAAe,OAAO,aAAA;AAC1B,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAEA,IAAA,MAAM,YAAA,GAAe,OAAO,OAAA,EAAiB,SAAA,KAA8D;AACzG,MAAA,IAAI,aAAA,GAAsC,IAAA;AAC1C,MAAA,GAAG;AACD,QAAA,aAAA,GAAgB,MAAM,eAAe,OAAO,CAAA;AAC5C,QAAA,SAAA,GAAY,aAAa,CAAA;AACzB,QAAA,MAAMC,WAAM,GAAK,CAAA;AAAA,MACnB,SAAS,CAAC,aAAA,IAAiB,CAAC,aAAA,CAAc,MAAA,IAAU,cAAc,MAAA,KAAW,SAAA;AAC7E,MAAA,IAAI,aAAA,CAAc,MAAA,KAAW,QAAA,IAAY,aAAA,CAAc,MAAA,KAAW,SAAA,IAAa,aAAA,CAAc,MAAA,KAAW,WAAA,IAAe,aAAA,CAAc,MAAA,KAAW,OAAA,EAAS;AACvJ,QAAA,OAAA,CAAQ,MAAM,sCAAA,EAAwC,IAAA,CAAK,UAAU,aAAA,EAAe,IAAA,EAAM,CAAC,CAAC,CAAA;AAC5F,QAAA,MAAM,GAAA,GAAM,aAAA;AACZ,QAAA,MAAM,SAAS,GAAA,CAAI,YAAA,IAAgB,IAAI,KAAA,IAAS,GAAA,CAAI,WAAW,aAAA,CAAc,MAAA;AAC7E,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,yBAAA,EAA4B,MAAM,CAAA,CAAE,CAAA;AAAA,MACtD;AACA,MAAA,OAAO,aAAA;AAAA,IACT,CAAA;AAEA,IAAA,MAAM,aAAA,GAAgB,OAAO,MAAA,KAAyE;AACpG,MAAA,OAAO,MAAM,MAAA,CAAO,IAAA,CAAM,aAAA,CAAc;AAAA,QACtC,UAAA,EAAY,CAAA;AAAA,QACZ,GAAG,MAAA;AAAA,QACH,UAAA,EAAY,MAAA,CAAO,MAAA,CAAO,UAAU,EAAE,QAAA;AAAS,OAChD,CAAA;AAAA,IACH,CAAA;AACA,IAAA,OAAO;AAAA;AAAA,MAEL,aAAA;AAAA;AAAA,MAEA,mBAAmB,YAAY;AAC7B,QAAA,IAAI,CAAC,MAAA,CAAO,IAAA,EAAM,OAAO,KAAA;AACzB,QAAA,OAAO,MAAA,CAAO,KAAM,iBAAA,EAAkB;AAAA,MACxC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAKA,uBAAuB,YAAY;AACjC,QAAA,OAAO,MAAM,MAAA,CAAO,IAAA,CAAM,qBAAA,EAAsB;AAAA,MAClD,CAAA;AAAA;AAAA,MAEA,YAAY,YAAY;AACtB,QAAA,OAAO,MAAM,MAAA,CAAO,IAAA,EAAM,UAAA,EAAW;AAAA,MACvC,CAAA;AAAA;AAAA,MAEA,YAAA,EAAc,OAAO,MAAA,KAAiC;AAEpD,QAAA,IAAI,OAAA;AACJ,QAAA,MAAM,OAAA,GAAU,IAAI,OAAA,CAAiC,CAAC,QAAA,KAAa;AACjE,UAAA,OAAA,GAAU,QAAA;AAAA,QACZ,CAAC,CAAA;AAGD,QAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAM,iBAAA,CAAkB,CAAC,IAAA,KAAS;AACrD,UAAA,IAAI,CAAA,SAAA,EAAY,IAAA,CAAK,QAAQ,CAAA,CAAA,KAAO,OAAO,UAAA,EAAY;AACrD,YAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,UACd;AAAA,QACF,CAAC,CAAA;AAED,QAAA,MAAM,MAAA,CAAO,IAAA,CAAM,YAAA,CAAa,MAAM,CAAA;AAEtC,QAAA,OAAO,MAAM,OAAA,CAAQ,IAAA,CAAK,CAAC,IAAA,KAAS;AAClC,UAAA,KAAA,EAAM;AACN,UAAA,OAAO,IAAA;AAAA,QACT,CAAC,CAAA;AAAA,MACH,CAAA;AAAA;AAAA,MAEA,aAAA;AAAA;AAAA,MAEA,YAAA;AAAA;AAAA,MAEA,mBAAA,EAAqB,OACnB,MAAA,EACA,EAAE,UAAU,gBAAA,EAAiB,GAAkC,EAAC,KACnC;AAC7B,QAAA,MAAM,EAAE,QAAA,EAAU,QAAA,EAAU,UAAA,EAAY,eAAc,GAAI,MAAA;AAC1D,QAAA,MAAM,IAAA,GAAO;AAAA,UACX,QAAA,EAAU,QAAA;AAAA,UACV,aAAA;AAAA,UACA;AAAA,SACF;AAGA,QAAA,MAAM,OAAA,GAAU,aAAaC,0BAAA,CAAmB,KAAA;AAChD,QAAA,MAAM,OAAA,GAAU,aAAaC,iBAAA,CAAU,KAAA;AACvC,QAAA,MAAM,aAAA,GAAgB,OAAA,IAAW,OAAA,GAAU,CAAA,GAAI,CAAA;AAC/C,QAAA,gBAAA,GAAmB,EAAE,UAAU,CAAA,mBAA2B,IAAA,EAAM,GAAG,EAAA,EAAI,IAAA,EAAM,KAAA,EAAO,aAAA,EAAe,CAAA;AACnG,QAAA,MAAM,SAAA,GAAY,MAAM,aAAA,CAAc,EAAE,GAAG,IAAA,EAAM,UAAA,EAAY,GAAG,CAAA;AAChE,QAAA,gBAAA,GAAmB,EAAE,QAAA,EAAU,CAAA,gBAAwB,IAAA,EAAM,CAAA,EAAG,IAAI,SAAA,CAAU,OAAA,EAAS,KAAA,EAAO,aAAA,EAAe,CAAA;AAC7G,QAAA,MAAM,MAAA,GAAS,MAAM,YAAA,CAAa,SAAA,CAAU,OAAO,CAAA;AACnD,QAAA,IAAI,CAAC,OAAA,IAAW,CAAC,OAAA,EAAS;AACxB,UAAA,gBAAA,GAAmB,EAAE,UAAU,CAAA,iBAAyB,IAAA,EAAM,MAAM,EAAA,EAAI,IAAA,EAAM,KAAA,EAAO,aAAA,EAAe,CAAA;AACpG,UAAA,OAAO,CAAC,MAAM,CAAA;AAAA,QAChB;AACA,QAAA,gBAAA,GAAmB,EAAE,UAAU,CAAA,mBAA2B,IAAA,EAAM,GAAG,EAAA,EAAI,IAAA,EAAM,KAAA,EAAO,aAAA,EAAe,CAAA;AACnG,QAAA,MAAM,SAAA,GAAY,MAAM,aAAA,CAAc,EAAE,GAAG,IAAA,EAAM,UAAA,EAAY,GAAG,CAAA;AAChE,QAAA,gBAAA,GAAmB,EAAE,QAAA,EAAU,CAAA,gBAAwB,IAAA,EAAM,CAAA,EAAG,IAAI,SAAA,CAAU,OAAA,EAAS,KAAA,EAAO,aAAA,EAAe,CAAA;AAC7G,QAAA,MAAM,MAAA,GAAS,MAAM,YAAA,CAAa,SAAA,CAAU,OAAO,CAAA;AACnD,QAAA,gBAAA,GAAmB,EAAE,UAAU,CAAA,iBAAyB,IAAA,EAAM,MAAM,EAAA,EAAI,IAAA,EAAM,KAAA,EAAO,aAAA,EAAe,CAAA;AACpG,QAAA,OAAO,CAAC,QAAQ,MAAM,CAAA;AAAA,MACxB,CAAA;AAAA;AAAA,MAEA,iBAAA,EAAmB,CAAC,EAAA,KAA4B;AAC9C,QAAA,OAAO,MAAA,CAAO,IAAA,CAAM,iBAAA,CAAkB,EAAE,CAAA;AAAA,MAC1C,CAAA;AAAA;AAAA,MAEA,cAAA;AAAA;AAAA,MAEA,oBAAA,EAAsB,OAAO,iBAAA,KAA+C;AAC1E,QAAA,OAAO,MAAM,OAAA,CAAQ,GAAA,CAAI,iBAAA,CAAkB,GAAA,CAAI,CAAC,CAAA,KAAM,cAAA,CAAe,CAAA,CAAE,OAAO,CAAC,CAAC,CAAA;AAAA,MAClF,CAAA;AAAA;AAAA,MAEA,aAAa,YAAY;AACvB,QAAA,OAAO,MAAM,MAAA,CAAO,IAAA,CAAM,WAAA,EAAY;AAAA,MACxC,CAAA;AAAA;AAAA,MAEA,WAAA,EAAa,OAAO,KAAA,KAAkB;AACpC,QAAA,OAAO,MAAM,MAAA,CAAO,IAAA,CAAM,WAAA,CAAY,KAAK,CAAA;AAAA,MAC7C,CAAA;AAAA;AAAA,MAEA,cAAA,EAAgB,MAAe,OAAO,MAAA,CAAO,MAAM,MAAA,KAAW,UAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAc9D,MAAA,EAAQ,OACN,MAAA,KAC2C;AAC3C,QAAA,IAAI,OAAO,MAAA,CAAO,IAAA,EAAM,MAAA,KAAW,UAAA,EAAY;AAC7C,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA,QACF;AACA,QAAA,MAAM,WAAA,GAAc,iBAAiB,MAAM,CAAA;AAC3C,QAAA,OAAO,IAAI,OAAA,CAAuC,CAAC,OAAA,KAAY;AAC7D,UAAA,MAAA,CAAO,IAAA,CAAM,MAAA,CAAQ,WAAA,EAAa,OAAO,CAAA;AAAA,QAC3C,CAAC,CAAA;AAAA,MACH,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,oBAAA,EAAsB,OACpB,UAAA,KACkD;AAClD,QAAA,IAAI,OAAO,MAAA,CAAO,IAAA,EAAM,oBAAA,KAAyB,YAAY,OAAO,IAAA;AACpE,QAAA,OAAO,MAAA,CAAO,IAAA,CAAK,oBAAA,CAAqB,UAAU,CAAA;AAAA,MACpD;AAAA,KACF;AAAA,EACF,CAAA,EAAG,CAAC,aAAa,CAAC,CAAA;AACpB;AASO,IAAM,mBAAA,GAAsB,CAAC,EAAE,QAAA,EAAU,QAAQ,KAAA,EAAO,SAAA,GAAY,OAAM,KAOnD;AAC5B,EAAA,MAAM,iBAAiBC,2CAAA,CAA4B;AAAA,IACjD,KAAA;AAAA,IACA,cAAc,MAAA,CAAO,MAAA;AAAA,IACrB,MAAM,MAAA,CAAO;AAAA,GACd,CAAA;AAYD,EAAA,MAAMC,gBAAAA,GAAkB,CAAC,MAAA,KAAoC;AAC3D,IAAA,MAAM,GAAA,GAAM,OAAO,MAAM,CAAA;AACzB,IAAA,MAAM,qBAAqB,UAAA,CAAW,IAAA,CAAK,GAAG,CAAA,IAAK,GAAA,CAAI,SAAS,GAAG,CAAA;AACnE,IAAA,MAAM,OAAA,GAAU,GAAA,CAAI,OAAA,CAAQ,UAAA,EAAY,EAAE,CAAA;AAC1C,IAAA,IAAI,KAAA,GAAQC,eAAA,CAAW,OAAA,EAAS,KAAA,CAAM,QAAQ,CAAA;AAC9C,IAAA,IAAI,SAAA,IAAa,CAAC,kBAAA,EAAoB;AACpC,MAAA,KAAA,GAAQ,KAAA,GAAQ,IAAA;AAAA,IAClB;AACA,IAAA,OAAO,KAAA;AAAA,EACT,CAAA;AAGA,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAgB,MAAA,CAA8C,0BAAA,EAA4B;AAC9G,IAAA,OAAO,IAAA;AAAA,EACT;AAIA,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM;AACjC,IAAA,MAAM,SAAA,GAAYD,gBAAAA,CAAgB,CAAA,CAAE,MAAM,CAAA;AAC1C,IAAA,OAAO,SAAA,KAAc,cAAA,IAAkB,SAAA,KAAe,cAAA,GAAiB,CAAC,EAAA;AAAA,EAC1E,CAAC,CAAA;AAED,EAAA,IAAI,CAAC,KAAA,EAAO;AAGV,IAAA,MAAM,aAAA,GAAgB,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM;AACzC,MAAA,MAAM,SAAA,GAAYA,gBAAAA,CAAgB,CAAA,CAAE,MAAM,CAAA;AAC1C,MAAA,MAAM,GAAA,GAAM,SAAA,GAAY,EAAA,GAAK,CAAC,SAAA,GAAY,SAAA;AAC1C,MAAA,OAAO,GAAA,KAAQ,cAAA,GAAiB,IAAA,IAAQ,GAAA,GAAM,IAAA,KAAS,cAAA;AAAA,IACzD,CAAC,CAAA;AACD,IAAA,IAAI,aAAA,EAAe;AACjB,MAAA,OAAA,CAAQ,IAAA;AAAA,QACN,CAAA,mCAAA,EAAsC,MAAA,CAAO,aAAA,CAAc,MAAM,CAAC,8BAA8B,cAAA,CAAe,QAAA,EAAU,CAAA,+DAAA,EAA6D,SAAS,CAAA,CAAA;AAAA,OACjM;AAAA,IACF;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,OAAO,KAAA;AACT;ACrpBO,IAAM,YAAA,GAAeE,oBAA4C,MAAS;AAY1E,SAAS,eAAA,GAAkB;AAChC,EAAA,MAAM,OAAA,GAAUC,iBAAW,YAAY,CAAA;AACvC,EAAA,IAAI,YAAY,MAAA,EAAW;AACzB,IAAA,MAAM,IAAI,MAAM,qDAAqD,CAAA;AAAA,EACvE;AACA,EAAA,OAAO,OAAA;AACT;AAEO,SAAS,aAAA,CAAc,EAAE,QAAA,EAAS,EAAuB;AAC9D,EAAA,MAAM,YAAY,QAAA,EAAS;AAE3B,EAAA,sCACG,YAAA,CAAa,QAAA,EAAb,EAAsB,KAAA,EAAO,WAC3B,QAAA,EACH,CAAA;AAEJ;ACIA,IAAM,wBAAA,GAAuE,OAAO,MAAA,CAAO;AAAA,EACzF,sBAAA,EAAwBC,mCAAA;AAAA,EACxB,8BAAA,EAAgCC,2CAAA;AAAA,EAChC,qBAAA,EAAuBC;AACzB,CAAC,CAAA;AAWM,SAAS,mBAAA,CACd,UACA,UAAA,EAC8B;AAC9B,EAAA,OAAO,wBAAA,CAAyB,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,UAAU,CAAA,CAAE,CAAA;AAC7D;AC3BO,IAAM,+BAAA,GAAkC,uCAAA;AA8E/C,SAAS,eAAA,CAAgB,MAAA,EAAyB,QAAA,EAAkB,SAAA,EAA4B;AAC9F,EAAA,MAAM,GAAA,GAAM,OAAO,MAAM,CAAA;AACzB,EAAA,MAAM,qBAAqB,UAAA,CAAW,IAAA,CAAK,GAAG,CAAA,IAAK,GAAA,CAAI,SAAS,GAAG,CAAA;AACnE,EAAA,MAAM,OAAA,GAAU,GAAA,CAAI,OAAA,CAAQ,UAAA,EAAY,EAAE,CAAA;AAC1C,EAAA,IAAI,KAAA,GAAQL,eAAAA,CAAW,OAAA,EAAS,QAAQ,CAAA;AACxC,EAAA,IAAI,SAAA,IAAa,CAAC,kBAAA,EAAoB;AACpC,IAAA,KAAA,GAAQ,KAAA,GAAQ,IAAA;AAAA,EAClB;AACA,EAAA,OAAO,KAAA;AACT;AAkBA,SAAS,SAAA,CACP,YACA,OAAA,EACyB;AACzB,EAAA,MAAM,iBAAiBF,2CAAAA,CAA4B;AAAA,IACjD,OAAO,OAAA,CAAQ,KAAA;AAAA,IACf,YAAA,EAAc,MAAA,CAAO,OAAA,CAAQ,MAAA,CAAO,MAAM,CAAA;AAAA,IAC1C,IAAA,EAAM,MAAA,CAAO,OAAA,CAAQ,MAAA,CAAO,cAAc;AAAA,GAC3C,CAAA;AAID,EAAA,MAAM,OAAA,GAAUQ,uBAAA,CAAgB,OAAA,CAAQ,OAAkB,CAAA;AAC1D,EAAA,MAAM,cAAA,GAAiB,OAAA,CAAQ,OAAA,CAAQ,QAAgC,CAAA;AACvE,EAAA,MAAM,SAAA,GAAa,gBAA4D,aAAA,IAAiB,KAAA;AAEhG,EAAA,OACE,UAAA,CAAW,IAAA,CAAK,CAAC,SAAA,KAAc;AAC7B,IAAA,MAAM,YAAY,eAAA,CAAgB,SAAA,CAAU,QAAQ,OAAA,CAAQ,KAAA,CAAM,UAAU,SAAS,CAAA;AACrF,IAAA,OAAO,SAAA,KAAc,cAAA,IAAkB,SAAA,KAAc,cAAA,GAAiB,CAAC,EAAA;AAAA,EACzE,CAAC,CAAA,IAAK,IAAA;AAEV;AAiBA,SAAS,aAAA,CACP,IAAA,EACA,QAAA,EACA,UAAA,EACA,SAAA,EAC2B;AAC3B,EAAA,OAAO,IAAI,OAAA,CAA0B,CAAC,OAAA,EAAS,MAAA,KAAW;AACxD,IAAA,IAAI,SAAA;AAEJ,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,iBAAA,CAAkB,CAAC,IAAA,KAAS;AAmB7C,MAAA,MAAM,qBAAA,GAAwB,KAAK,QAAA,KAAa,QAAA;AAChD,MAAA,MAAM,sBAAA,GAAyB,CAAA,SAAA,EAAY,IAAA,CAAK,QAAQ,CAAA,CAAA,KAAO,UAAA;AAC/D,MAAA,IAAI,yBAAyB,sBAAA,EAAwB;AACnD,QAAA,YAAA,CAAa,SAAS,CAAA;AACtB,QAAA,KAAA,EAAM;AACN,QAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,MACd;AAAA,IACF,CAAC,CAAA;AAED,IAAA,SAAA,GAAY,WAAW,MAAM;AAC3B,MAAA,KAAA,EAAM;AACN,MAAA,MAAA,CAAO,IAAI,KAAA,CAAM,CAAA,wCAAA,EAA2C,QAAQ,GAAG,CAAC,CAAA;AAAA,IAC1E,GAAG,SAAS,CAAA;AAAA,EACd,CAAC,CAAA;AACH;AAEO,SAAS,8BAA8B,MAAA,EAAuD;AACnG,EAAA,MAAM,SAAA,GAAY,OAAO,SAAA,IAAa,GAAA;AAEtC,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,mBAAA;AAAA,IAEN,MAAM,sBAAA,CACJ,OAAA,EACA,OAAA,EACwB;AACxB,MAAA,MAAM,IAAA,GAAO,OAAO,OAAA,EAAQ;AAC5B,MAAA,IAAI,CAAC,IAAA,EAAM;AACT,QAAA,MAAM,IAAIC,iCAAA,CAA0B,OAAA,CAAQ,QAAQ,CAAA;AAAA,MACtD;AAQA,MAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,qBAAA,EAAsB;AAChD,MAAA,IAAI,WAAW,WAAA,EAAa;AAC1B,QAAA,MAAM,KAAK,iBAAA,EAAkB;AAAA,MAC/B;AAIA,MAAA,MAAM,kBAAkB,aAAA,CAAc,IAAA,EAAM,QAAQ,QAAA,EAAU,OAAA,CAAQ,YAAY,SAAS,CAAA;AAC3F,MAAA,MAAM,KAAK,YAAA,CAAa;AAAA,QACtB,YAAY,OAAA,CAAQ,UAAA;AAAA,QACpB,UAAU,OAAA,CAAQ,QAAA;AAAA,QAClB,WAAA,EAAa,UAAA;AAAA;AAAA;AAAA;AAAA,QAIb,qBAAA,EAAuB,OAAO,qBAAA,IAAyB,+BAAA;AAAA;AAAA;AAAA,QAGvD,qBAAA,EAAuB,OAAA,CAAQ,qBAAA,IAAyB,OAAA,CAAQ,UAAA;AAAA,QAChE,mBAAA,EAAqB,OAAA,CAAQ,mBAAA,IAAuB,OAAA,CAAQ,QAAA;AAAA;AAAA;AAAA,QAG5D,cAAA,EAAgB,mBAAA,CAAoB,OAAA,CAAQ,QAAA,EAAU,QAAQ,UAAU;AAAA,OACzE,CAAA;AACD,MAAA,MAAM,WAAW,MAAM,eAAA;AAKvB,MAAA,IAAI,CAAC,SAAS,eAAA,EAAiB;AAC7B,QAAA,MAAM,eAAgB,QAAA,CAAwC,YAAA;AAC9D,QAAA,OAAA,CAAQ,IAAA;AAAA,UACN,2CAAA;AAAA,UACA,OAAA,CAAQ,QAAA;AAAA,UACR,uBAAA;AAAA,UAAoB,MAAA,CAAO,KAAK,QAAQ,CAAA;AAAA,UACxC,iBAAA;AAAA,UAAmB,YAAA;AAAA,UACnB,kBAAA;AAAA,UAAoB,MAAM,OAAA,CAAQ,QAAA,CAAS,QAAQ,CAAA,GAAI,QAAA,CAAS,SAAS,MAAA,GAAS,QAAA;AAAA,UAClF,oBAAA;AAAA,UAAsB,QAAA,CAAS;AAAA,SACjC;AAWA,QAAA,IAAI,OAAO,YAAA,KAAiB,QAAA,IAAY,aAAa,IAAA,EAAK,CAAE,SAAS,CAAA,EAAG;AACtE,UAAA,MAAM,IAAIC,4BAAA,CAAqB,OAAA,CAAQ,QAAA,EAAU,YAAY,CAAA;AAAA,QAC/D;AACA,QAAA,MAAM,IAAID,iCAAA,CAA0B,OAAA,CAAQ,QAAQ,CAAA;AAAA,MACtD;AAEA,MAAA,MAAM,OAAA,GAAU,SAAA,CAAU,QAAA,CAAS,QAAA,EAAU,OAAO,CAAA;AACpD,MAAA,IAAI,YAAA;AACJ,MAAA,IAAI,OAAA,EAAS;AACX,QAAA,YAAA,GAAe,OAAA,CAAQ,aAAA;AAAA,MACzB,CAAA,MAAA,IAAW,SAAS,iBAAA,EAAmB;AACrC,QAAA,YAAA,GAAe,MAAM,OAAA,CAAQ,iBAAA,CAAkB,QAAA,CAAS,QAAQ,CAAA;AAAA,MAClE,CAAA,MAAO;AACL,QAAA,MAAM,IAAIE,+BAAA,CAAwB,QAAA,CAAS,QAAQ,CAAA;AAAA,MACrD;AAKA,MAAA,MAAM,UAAA,GAAa,SAAS,QAAA,CAAS,IAAA,CAAK,CAAC,GAAA,KAAQ,GAAA,CAAI,kBAAkB,YAAY,CAAA;AACrF,MAAA,MAAM,QAAA,GAA4B;AAAA,QAChC,wBAAA,EAA0B,SAAS,eAAA,CAAgB,wBAAA;AAAA,QACnD,YAAA;AAAA,QACA,eAAe,UAAA,EAAY;AAAA,OAC7B;AACA,MAAA,OAAO,EAAE,IAAA,EAAM,mBAAA,EAAqB,QAAA,EAAS;AAAA,IAC/C,CAAA;AAAA,IAEA,MAAM,MAAA,CAAO,MAAA,EAAuB,OAAA,EAAuD;AACzF,MAAA,IAAI,MAAA,CAAO,SAAS,mBAAA,EAAqB;AACvC,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,0DAAA,EAA6D,OAAO,IAAI,CAAA,gCAAA;AAAA,SAC1E;AAAA,MACF;AACA,MAAA,MAAM,WAAW,MAAA,CAAO,QAAA;AAOxB,MAAA,MAAM,MAAA,GAAS;AAAA,QACb,GAAI,QAAA,CAAS,aAAA,IAAiB,EAAC;AAAA,QAC/B,GAAIC,qCAAA,CAA8B,OAAA,CAAQ,QAAwC,CAAA,GAC9E,EAAE,KAAA,EAAO,QAAA,CAAS,YAAA,EAAa,GAC/B;AAAC,OACP;AACA,MAAA,MAAM,WAAA,GAAc,MAAMC,kCAAA,CAA2B;AAAA,QACnD,oBAAoB,MAAA,CAAO,kBAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAO3B,QAAA,EAAU,OAAA,CAAQ,mBAAA,IAAuB,OAAA,CAAQ,QAAA;AAAA,QACjD,UAAA,EAAY,OAAA,CAAQ,qBAAA,IAAyB,OAAA,CAAQ,UAAA;AAAA,QACrD,0BAA0B,QAAA,CAAS,wBAAA;AAAA,QACnC,MAAA;AAAA,QACA,mBAAmB,OAAA,CAAQ,iBAAA;AAAA,QAC3B,QAAQ,OAAA,CAAQ;AAAA,OACjB,CAAA;AAED,MAAA,OAAO,EAAE,WAAA,EAAa,gBAAA,EAAkB,WAAA,CAAY,MAAA,EAAc;AAAA,IACpE;AAAA,GACF;AACF","file":"index.cjs","sourcesContent":["/**\n * @fileoverview React hook and utilities for PeerAuth browser extension integration.\n *\n * The PeerAuth extension (formerly ZK-TLS) provides secure proof generation\n * for off-chain payment verification. This module provides:\n *\n * 1. Type definitions for extension API (window.peer)\n * 2. useZKTLS hook for React components\n * 3. Utility functions for transaction matching and proof encoding\n *\n * @remarks\n * The extension exposes `window.peer` (or deprecated `window.zktls`) for:\n * - Requesting connection (OAuth-like flow)\n * - Authenticating with payment providers\n * - Generating notarized proofs\n * - Fetching transaction metadata\n *\n * @see {@link ZKTLSContext} for the React context provider\n */\n\nimport { useEffect, useMemo, useState } from \"react\"\nimport { encodeAbiParameters, encodePacked, parseAbiParameter, parseUnits, type Hex } from \"viem\"\nimport { sleep } from \"@provex/utils/time\"\nimport type { IntentStruct } from \"@provex/utils/intent\"\nimport type { TokenInfo } from \"@provex/utils/tokens\"\nimport { extensionPlatforms, providers, subProviderConfigs, type ActionType, type PlatformKey, type ProviderKey, type SubProviderKey } from \"@provex/utils/payment\"\nimport { convertTokenInputToCurrency } from \"@provex/utils/conversionRates\"\n\ndeclare global {\n interface ZKTLS {\n requestConnection(): Promise<boolean>\n checkConnectionStatus(): Promise<Status>\n getVersion(): Promise<string>\n authenticate(inputs: AuthenticationInputs): Promise<void>\n generateProof(inputs: GenerateProofInputs): Promise<GenerateProofResponse>\n fetchProofById(proofId: string): Promise<ProofResponse>\n fetchProofs(): Promise<FetchProofsResponse>\n openSidebar(route: string): Promise<void>\n onMetadataMessage(fn: (data: MetadataMessageResponse) => void): () => void\n /**\n * Trusted-execution on-ramp (PeerAuth v3+). The extension opens the\n * buyer's bank in a new tab, intercepts the authenticated session, calls\n * the zkp2p trusted-execution attestation service, and returns\n * ready-to-send `fulfillIntent` calldata through the callback. Replaces\n * the retired `authenticate` + `generateProof` Reclaim path.\n *\n * Optional: older extension builds predate the on-ramp API, so guard\n * with a runtime presence check (`supportsOnramp`) before calling.\n *\n * @param queryString serialized on-ramp params (see {@link buildOnrampQuery})\n * @param callback invoked once the prepared transaction is ready\n */\n onramp?(\n queryString: string,\n callback: (result: PeerOnrampPreparedTransaction) => void,\n ): void\n /** Retrieve a previously prepared on-ramp transaction by intent hash. */\n getOnrampTransaction?(intentHash: string): Promise<PeerOnrampPreparedTransaction | null>\n }\n interface Window {\n /** @deprecated Use window.peer instead */\n zktls: ZKTLS | undefined\n /** PeerAuth extension API (replaces window.zktls) */\n peer: ZKTLS | undefined\n }\n}\n\n/** the status of the PeerAuth extension */\nexport type Status = 'connected' | 'disconnected' | 'pending'\n\n/** the individual metadata message sent by the PeerAuth extension */\nexport interface MetadataMessage {\n amount: number\n /** Currency code (e.g., 'USD', 'EUR') */\n currency?: string\n /**\n * Date of the transaction.\n * Can be a string in YYYYMMDD format, a number (YYYYMMDD or timestamp), or Date object.\n * The actual type depends on the extension implementation.\n */\n date: string | number | Date\n hidden: boolean\n originalIndex: number\n paymentId: number\n recipient: string\n}\n\n/** the response from the PeerAuth extension when a metadata message is received */\nexport interface MetadataMessageResponse {\n expiresAt: number\n platform: ProviderKey\n requestId: string\n metadata: MetadataMessage[]\n /**\n * Present only when `authenticate` was called with `captureMode: 'buyerTee'`.\n * Carries the bank session already encrypted toward the enclave — feed\n * `encryptedSessionMaterial` (plus the matched transaction's index) to the\n * trusted-execution verify endpoint. No `generateProof` call is involved.\n */\n buyerTeeCapture?: {\n encryptedSessionMaterial: string\n }\n /** Error surfaced by the extension during capture (e.g. missing platform). */\n errorMessage?: string\n}\n\n/** the response from the PeerAuth extension when fetching proofs */\nexport interface FetchProofsResponse {\n notaryRequests: NotaryRequest[]\n}\n\n/** the inputs for the generateProof function */\nexport type GenerateProofInputsAbstracted = {\n platform: PlatformKey\n provider: ProviderKey\n intentHash: Hex\n originalIndex: number\n}\n\n/** the inputs for the generateProof function */\nexport type GenerateProofInputs = {\n platform: ProviderKey\n originalIndex: number\n proofIndex?: number\n intentHash: string\n}\n\n/**\n * inputs for the generate proof function that requires the intent hash\n * as hex to ensure it can be converted to a bigint string accurately\n */\nexport type HexIntentGenerateProofInputs = Exclude<GenerateProofInputs, 'intentHash'> & { intentHash: Hex }\n\n/** the response from the PeerAuth extension when generating a proof */\nexport type GenerateProofResponse = {\n proofId: string\n platform: ProviderKey\n}\n\n/** the inputs for the authenticate function */\nexport type AuthenticationInputs = {\n actionType: ActionType\n platform: PlatformKey\n /**\n * Session capture mode. `'buyerTee'` makes the extension intercept the bank\n * session and return it encrypted toward the trusted-execution enclave in\n * `MetadataMessageResponse.buyerTeeCapture` — the replacement for the retired\n * Reclaim `generateProof` path. Omit for the legacy Reclaim flow. The\n * extension defaults the attestation service URL when this is set.\n */\n captureMode?: 'buyerTee' | 'sellerCredential'\n /**\n * Enclave the extension establishes the Buyer TEE session with and encrypts\n * the captured session material toward (zkp2p 0.6.0 spec §7). Pass explicitly\n * for newer providers — relying on the extension's built-in default is what\n * left non-Revolut rails with \"no buyerTee capture\".\n */\n attestationServiceUrl?: string\n /** Attestation action type; defaults to `actionType` when omitted (spec §2). */\n attestationActionType?: ActionType\n}\n\n/** the response from the PeerAuth extension when fetching a proof */\nexport interface Proof {\n claimInfo: ClaimInfo\n signedClaim: SignedClaim\n isAppclipProof: boolean\n status: string\n}\n\n/** the response from the PeerAuth extension when fetching a proof */\nexport interface NotarizedProof {\n claim: {\n context: string\n epoch: number\n identifier: Hex\n owner: Hex\n parameters: string\n provider: string\n timestampS: number\n },\n signatures: {\n attestorAddress: Hex\n claimSignature: Record<number, number>\n resultSignature: Record<number, number>\n },\n}\n\n/** the claim info for a proof */\nexport interface ClaimInfo {\n provider: string\n parameters: string\n context: string\n}\n\n/** the complete claim data for a proof */\nexport interface CompleteClaimData {\n identifier: Hex\n owner: Hex\n timestampS: number\n epoch: number\n}\n\n/** the signed claim for a proof */\nexport interface SignedClaim {\n claim: CompleteClaimData\n signatures: Hex[]\n}\n\n/** the reclaim proof for a proof */\nexport type ReclaimProof = {\n claimInfo: ClaimInfo\n signedClaim: SignedClaim\n isAppclipProof: boolean\n}\n\n/** the notary request for a proof */\nexport interface NotaryRequest {\n metadata: (string | number)[]\n actionType: ActionType\n id: string\n status: string\n timestamp: number\n proof: NotarizedProof\n}\n\n/** the response from the PeerAuth extension when fetching a proof */\nexport interface ProofResponse {\n notaryRequest: NotaryRequest\n}\n\n/**\n * Ready-to-send transaction the PeerAuth extension hands back once the\n * trusted-execution attestation service has signed the payment. The `data`\n * already embeds the enclave attestation, so the client signs and sends it\n * verbatim — no client-side proof encoding or gate re-signing.\n */\nexport type PeerOnrampPreparedTransaction = {\n intentHash: Hex\n status: 'calldata_ready'\n transaction: {\n to: Hex\n data: Hex\n /** Wei, as a decimal string (the extension keeps it out of JSON-number range). */\n value: string\n chainId: number\n }\n}\n\n/**\n * Structured on-ramp inputs. The extension looks the intent up from\n * `intentHash` (and the optional context fields) to resolve the chain,\n * contracts, and payment platform — we never pass contract addresses.\n */\nexport type PeerExtensionOnrampParams = {\n intentHash: string\n referrer?: string\n referrerLogo?: string\n inputCurrency?: string\n inputAmount?: string | number\n paymentPlatform?: string\n depositId?: string | number | bigint\n toToken?: string\n amountUsdc?: string | number | bigint\n recipientAddress?: string\n}\n\n/**\n * Serialize on-ramp params into the query string the extension's `onramp`\n * entry point expects. Mirrors the mapping `@zkp2p/sdk`'s\n * `peerExtensionSdk.onramp` performs: every defined field becomes one query\n * parameter, `undefined`/`null` fields are dropped, and `bigint` values are\n * stringified.\n */\nexport const buildOnrampQuery = (params: PeerExtensionOnrampParams): string => {\n const search = new URLSearchParams()\n Object.entries(params).forEach(([key, value]) => {\n if (value === undefined || value === null) return\n search.set(key, String(value))\n })\n return search.toString()\n}\n\n/**\n * Wrap a buyerTee capture (from {@link MetadataMessageResponse.buyerTeeCapture})\n * as a `NotaryRequest` so it flows through the existing submit step, which\n * narrows `proof` to a buyerTee proof and routes it to the enclave verify\n * endpoint. The `index` is the matched transaction's `originalIndex` — the\n * extension keys the captured payment on it.\n *\n * This replaces `generateProof` entirely for migrated providers: the session\n * was already captured + encrypted during `authenticate({ captureMode: 'buyerTee' })`.\n */\nexport const buildBuyerTeeNotaryRequest = ({\n encryptedSessionMaterial,\n index,\n actionType,\n}: {\n encryptedSessionMaterial: string\n index: number\n actionType: ActionType\n}): NotaryRequest => ({\n metadata: [],\n actionType,\n id: `buyer-tee-${index}`,\n status: 'success',\n timestamp: 0,\n // The runtime shape is a buyerTee proof, not a Reclaim NotarizedProof; the\n // submit step narrows it via `asBuyerTeeProof` before use.\n proof: {\n proofType: 'buyerTee',\n encryptedSessionMaterial,\n params: { index },\n } as unknown as NotarizedProof,\n})\n\nexport const extensionUrl = 'https://chromewebstore.google.com/detail/ijpgccednehjpeclfcllnjjcmiohdjih'\n\n/** convert a byte array to a hex string */\nexport const byteArrayToHexString = (byteArray: number[]): Hex => {\n return `0x${byteArray\n .map(byte => byte.toString(16).padStart(2, '0'))\n .join('')}`\n}\n\n/** parse a proof object from the PeerAuth extension */\nexport const parseExtensionProof = (proofObject: NotarizedProof) => {\n return {\n claimInfo: {\n provider: proofObject.claim.provider,\n parameters: proofObject.claim.parameters,\n context: proofObject.claim.context\n },\n signedClaim: {\n claim: {\n identifier: proofObject.claim.identifier as Hex,\n owner: proofObject.claim.owner as Hex,\n timestampS: proofObject.claim.timestampS,\n epoch: proofObject.claim.epoch\n },\n signatures: [byteArrayToHexString(Object.values(proofObject.signatures.claimSignature))]\n },\n isAppclipProof: false\n } as ReclaimProof\n}\n\n/** the abi encoding for a proof */\nexport const PROOF_ENCODING = parseAbiParameter(\"((string provider, string parameters, string context) claimInfo, ((bytes32 identifier, address owner, uint32 timestampS, uint32 epoch) claim, bytes[] signatures) signedClaim, bool isAppclipProof)\")\n\n/** encode a single proof as a bytes array */\nexport function encodeProofAsBytes(proof: ReclaimProof): Hex {\n return encodeAbiParameters([PROOF_ENCODING], [proof])\n}\n\n/** encode two proofs to be passed to the underlying sub-provider */\nexport const encodeTwoProofs = (proof1: ReclaimProof, proof2: ReclaimProof) => {\n return encodeAbiParameters([PROOF_ENCODING, PROOF_ENCODING], [proof1, proof2])\n}\n\n/** a high level function to encode a proof for a given provider and sub-provider */\nexport const encodeProof = ({ extensionProofs, subProvider, provider }: {\n extensionProofs: NotarizedProof[],\n subProvider: PlatformKey | null,\n provider: ProviderKey\n}) => {\n const reclaimProofs = extensionProofs.map((p) => parseExtensionProof(p))\n if (provider === 'zelle') {\n // move this to check the blockchain for the payment method\n const paymentMethod = subProviderConfigs![provider]![subProvider as SubProviderKey]!.paymentMethod\n const twoEncoded = reclaimProofs.length === 2\n ? encodeTwoProofs(reclaimProofs[0]!, reclaimProofs[1]!)\n : encodeProofAsBytes(reclaimProofs[0]!)\n // select between various possible sub-providers\n const withPaymentMethod = encodeProofWithPaymentMethod({\n paymentMethod,\n proof: twoEncoded,\n })\n return withPaymentMethod\n }\n const [reclaimProof] = reclaimProofs\n return encodeProofAsBytes(reclaimProof!)\n}\n\n/**\n * wrap a proof with a payment method and proof\n * this allows us to use a single verifier as an entry point for multiple underlying sub-providers\n */\nexport const encodeProofWithPaymentMethod = ({\n proof,\n paymentMethod\n}: {\n proof: Hex,\n paymentMethod: number\n}): Hex => {\n return encodePacked(['uint8', 'bytes'], [paymentMethod, proof])\n}\n\n/** the progress of a proof series */\nexport enum SeriesProgress {\n Generating,\n Polling,\n Complete,\n}\n\nexport type ProgressInfo = {\n progress: SeriesProgress\n total: number\n step: number | null\n id: string | null\n}\n\n/** the mutations for the generate proof series function */\nexport type GenerateProofSeriesMutations = {\n progress?: ({ progress, step, id }: ProgressInfo) => void\n setOwnerName?: (name: string) => void\n}\n\n/** React hook to interact with the PeerAuth extension */\nexport const useZKTLS = () => {\n const [isInitialized, setIsInitialized] = useState(false)\n useEffect(() => {\n const setup = () => {\n setIsInitialized(true)\n }\n const eventName = 'peer#initialized'\n if (typeof window.peer !== 'undefined') {\n // api is available\n setup()\n } else {\n window.addEventListener(eventName, setup)\n return () => {\n window.removeEventListener(eventName, setup)\n }\n }\n }, [])\n return useMemo(() => {\n /** fetch a proof by its id */\n const fetchProofById = async (proofId: string): Promise<NotaryRequest | null> => {\n const proof = await window.peer!.fetchProofById(proofId)\n if (proof && proof.notaryRequest) return proof.notaryRequest\n const proofs = await window.peer!.fetchProofs()\n const proofFromList = proofs.notaryRequests.find((p) => p.id === proofId)\n if (proofFromList) return proofFromList\n return null\n }\n /** wait for a proof to be generated */\n const waitForProof = async (proofId: string, onCurrent?: (notaryRequest: NotaryRequest | null) => void) => {\n let notaryRequest: NotaryRequest | null = null\n do {\n notaryRequest = await fetchProofById(proofId)\n onCurrent?.(notaryRequest)\n await sleep(1_000)\n } while (!notaryRequest || !notaryRequest.status || notaryRequest.status === 'pending')\n if (notaryRequest.status === 'failed' || notaryRequest.status === 'expired' || notaryRequest.status === 'cancelled' || notaryRequest.status === 'error') {\n console.error('[waitForProof] proof terminal state:', JSON.stringify(notaryRequest, null, 2))\n const req = notaryRequest as unknown as Record<string, unknown>\n const detail = req.errorMessage ?? req.error ?? req.message ?? notaryRequest.status\n throw new Error(`Proof generation failed: ${detail}`)\n }\n return notaryRequest\n }\n /** generate a proof */\n const generateProof = async (inputs: HexIntentGenerateProofInputs): Promise<GenerateProofResponse> => {\n return await window.peer!.generateProof({\n proofIndex: 0,\n ...inputs,\n intentHash: BigInt(inputs.intentHash).toString(),\n })\n }\n return {\n /** check if the PeerAuth extension is initialized */\n isInitialized,\n /** request a connection to the PeerAuth extension */\n requestConnection: async () => {\n if (!window.peer) return false\n return window.peer!.requestConnection()\n },\n /**\n * check the connection status of the PeerAuth extension\n * @returns the connection status\n */\n checkConnectionStatus: async () => {\n return await window.peer!.checkConnectionStatus()\n },\n /** get the version of the PeerAuth extension */\n getVersion: async () => {\n return await window.peer?.getVersion()\n },\n /** authenticate with the PeerAuth extension */\n authenticate: async (inputs: AuthenticationInputs) => {\n // Create promise first to avoid race condition where message arrives before resolver is assigned\n let resolve: (value: MetadataMessageResponse) => void\n const promise = new Promise<MetadataMessageResponse>((resolver) => {\n resolve = resolver\n })\n\n // Now set up the listener - resolve is guaranteed to be the real resolver\n const unsub = window.peer!.onMetadataMessage((data) => {\n if (`transfer_${data.platform}` === inputs.actionType) {\n resolve(data)\n }\n })\n\n await window.peer!.authenticate(inputs)\n\n return await promise.then((data) => {\n unsub()\n return data\n })\n },\n /** generate a proof */\n generateProof,\n /** wait for a proof to be generated */\n waitForProof,\n /** generate a proof series */\n generateProofSeries: async (\n inputs: GenerateProofInputsAbstracted,\n { progress: progressCallback }: GenerateProofSeriesMutations = {},\n ): Promise<NotaryRequest[]> => {\n const { platform, provider, intentHash, originalIndex } = inputs\n const args = {\n platform: provider,\n originalIndex,\n intentHash,\n } as const\n // Chase requires 2 proofs (account verification + transaction)\n // Platform is the bank name (e.g., 'chase') from extensionPlatform config\n const isChase = platform === extensionPlatforms.CHASE\n const isZelle = provider === providers.ZELLE\n const expectedTotal = isZelle && isChase ? 2 : 1\n progressCallback?.({ progress: SeriesProgress.Generating, step: 0, id: null, total: expectedTotal })\n const genProofA = await generateProof({ ...args, proofIndex: 0 })\n progressCallback?.({ progress: SeriesProgress.Polling, step: 0, id: genProofA.proofId, total: expectedTotal })\n const proofA = await waitForProof(genProofA.proofId)\n if (!isChase || !isZelle) {\n progressCallback?.({ progress: SeriesProgress.Complete, step: null, id: null, total: expectedTotal })\n return [proofA]\n }\n progressCallback?.({ progress: SeriesProgress.Generating, step: 1, id: null, total: expectedTotal })\n const genProofB = await generateProof({ ...args, proofIndex: 1 })\n progressCallback?.({ progress: SeriesProgress.Polling, step: 1, id: genProofB.proofId, total: expectedTotal })\n const proofB = await waitForProof(genProofB.proofId)\n progressCallback?.({ progress: SeriesProgress.Complete, step: null, id: null, total: expectedTotal })\n return [proofA, proofB]\n },\n /** subscribe to metadata messages */\n onMetadataMessage: (fn: (data: any) => void) => {\n return window.peer!.onMetadataMessage(fn)\n },\n /** fetch a proof by its id */\n fetchProofById,\n /** fetch generated proofs */\n fetchGeneratedProofs: async (genProofResponses: GenerateProofResponse[]) => {\n return await Promise.all(genProofResponses.map((r) => fetchProofById(r.proofId)))\n },\n /** fetch all proofs */\n fetchProofs: async () => {\n return await window.peer!.fetchProofs()\n },\n /** open the PeerAuth sidebar */\n openSidebar: async (route: string) => {\n return await window.peer!.openSidebar(route)\n },\n /** Whether the connected extension exposes the trusted-execution on-ramp. */\n supportsOnramp: (): boolean => typeof window.peer?.onramp === 'function',\n /**\n * Begin the trusted-execution on-ramp and resolve with ready-to-send\n * `fulfillIntent` calldata once the extension has intercepted the bank\n * session and the zkp2p attestation service has signed the payment.\n *\n * The underlying extension API is callback-only with no error channel:\n * the extension surfaces progress and failures in its own sidebar/tab,\n * and only invokes the callback on success. The returned promise\n * therefore stays pending until the prepared transaction arrives — the\n * caller drives the user toward the PeerAuth window and may offer a\n * cancel affordance rather than racing a timeout against a buyer who is\n * still logging into their bank.\n */\n onramp: async (\n params: PeerExtensionOnrampParams,\n ): Promise<PeerOnrampPreparedTransaction> => {\n if (typeof window.peer?.onramp !== 'function') {\n throw new Error(\n 'This PeerAuth extension version does not support the trusted-execution on-ramp. Please update the extension.',\n )\n }\n const queryString = buildOnrampQuery(params)\n return new Promise<PeerOnrampPreparedTransaction>((resolve) => {\n window.peer!.onramp!(queryString, resolve)\n })\n },\n /**\n * Retrieve a previously prepared on-ramp transaction by intent hash.\n * Returns null when the extension lacks the API or has nothing prepared\n * yet — usable as a poll alongside {@link onramp} to recover a result\n * if the one-shot callback is missed.\n */\n getOnrampTransaction: async (\n intentHash: string,\n ): Promise<PeerOnrampPreparedTransaction | null> => {\n if (typeof window.peer?.getOnrampTransaction !== 'function') return null\n return window.peer.getOnrampTransaction(intentHash)\n },\n }\n }, [isInitialized])\n}\n\n/**\n * Find the metadata message that matches the intent by amount.\n * Returns the first (most recent) transaction in the metadata array that matches the expected amount.\n *\n * Simplified matching: just find transactions with the correct amount.\n * The user can manually select if multiple matches exist.\n */\nexport const findMetadataMessage = ({ metadata, intent, token, centIsInt = false }: {\n metadata: MetadataMessage[],\n intent: IntentStruct,\n token: TokenInfo,\n /** @deprecated No longer used - kept for API compatibility */\n recipient?: string,\n centIsInt?: boolean,\n}): MetadataMessage | null => {\n const currencyAmount = convertTokenInputToCurrency({\n token,\n amountOutInt: intent.amount,\n rate: intent.conversionRate,\n })\n /**\n * Normalize an amount from the extension into a bigint in token base units.\n *\n * The extension sends amounts in different formats per provider:\n * - Venmo: `\"- $1.00\"` (formatted dollar string)\n * - Others: `100` (integer cents) or `1.00` (decimal dollars)\n *\n * We detect the format by checking for currency symbols or decimal points\n * in the raw value. If present, the value is already in dollars and we\n * skip the centIsInt division.\n */\n const normalizeAmount = (amount: number | string): 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, token.decimals)\n if (centIsInt && !isFormattedDollars) {\n value = value / 100n\n }\n return value\n }\n\n // Skip auto-match when manual selection is forced (dev/testing)\n if (typeof window !== 'undefined' && (window as unknown as Record<string, unknown>).__FORCE_MANUAL_TX_SELECT__) {\n return null\n }\n\n // Find the first transaction that matches the amount (first in array = most recent)\n // Note: We ignore the hidden flag - matching is purely by amount\n const match = metadata.find((m) => {\n const converted = normalizeAmount(m.amount)\n return converted === currencyAmount || converted === (currencyAmount * -1n)\n })\n\n if (!match) {\n // Detect 100x scale mismatch — the most common failure mode when centIsInt\n // is wrong for a provider. Fires loudly so it's obvious in dev/staging logs.\n const hundredxMatch = metadata.find((m) => {\n const converted = normalizeAmount(m.amount)\n const abs = converted < 0n ? -converted : converted\n return abs === currencyAmount * 100n || abs * 100n === currencyAmount\n })\n if (hundredxMatch) {\n console.warn(\n `[findMetadataMessage] tx[0] amount=${String(hundredxMatch.amount)} is 100x off from expected ${currencyAmount.toString()} — centIsInt is likely wrong for this provider (currently ${centIsInt})`\n )\n }\n return null\n }\n\n return match\n}\n","/**\n * @fileoverview React context for PeerAuth extension state.\n *\n * Provides global access to the useZKTLS hook state throughout the\n * component tree, avoiding prop drilling for extension status.\n *\n * @see {@link useZKTLS} for the underlying hook\n */\n\nimport { createContext, useContext, type ReactNode } from 'react'\nimport { useZKTLS } from './useZKTLS'\n\n/**\n * Context type derived from useZKTLS hook return value.\n */\ntype ZKTLSContextType = ReturnType<typeof useZKTLS>\n\nexport const ZKTLSContext = createContext<ZKTLSContextType | undefined>(undefined)\n\ninterface ZKTLSProviderProps {\n children: ReactNode\n}\n\n/**\n * Hook to consume the ZKTLS context.\n * Must be used within a ZKTLSProvider.\n *\n * @throws Error if used outside of ZKTLSProvider\n */\nexport function useZKTLSContext() {\n const context = useContext(ZKTLSContext)\n if (context === undefined) {\n throw new Error('useZKTLSContext must be used within a ZKTLSProvider')\n }\n return context\n}\n\nexport function ZKTLSProvider({ children }: ZKTLSProviderProps) {\n const zktlsHook = useZKTLS()\n\n return (\n <ZKTLSContext.Provider value={zktlsHook}>\n {children}\n </ZKTLSContext.Provider>\n )\n}\n","/**\n * @fileoverview Provider templates supplied inline to the PeerAuth extension.\n *\n * The extension resolves a template before it can open the buyer's bank:\n *\n * function ut(e){ return `${base}${e.platform}/${e.actionType}.json` }\n * async function dt(e){\n * if (e.providerConfig) return e.providerConfig // <- inline wins\n * const r = await fetch(ut(e))\n * if (!r.ok) throw new Error(`Failed to fetch provider template: ${r.status}`)\n * }\n *\n * `base` is hardcoded to https://api.zkp2p.xyz/providers/ inside the extension,\n * and zkp2p does NOT host the Zelle bank templates there — every\n * `{bank}/transfer_zelle.json` 404s while venmo/cashapp/revolut/wise/paypal/\n * monzo return 200. The template carries `authLink`, the URL the extension opens,\n * so a 404 means no bank tab at all: the capture dies ~1ms in with\n * \"Failed to fetch provider template: 404\".\n *\n * The templates ARE published on npm (`@zkp2p/providers`), so we bundle the\n * unhosted ones and hand them over inline, which bypasses the fetch entirely.\n *\n * Scope is deliberate: ONLY the rails the API fails to serve. Hosted rails keep\n * fetching so upstream template fixes (bank markup changes) reach users without\n * a release from us. Supplying `providerConfig` also flips the extension's\n * `requiresMetadataApproval` on (`lt(e){return!!e.providerConfig}`), so\n * narrowing the blast radius keeps that UX change off rails that work today.\n */\n\nimport chaseTransferZelle from '@zkp2p/providers/chase/transfer_zelle.json'\nimport bankofamericaTransferZelle from '@zkp2p/providers/bankofamerica/transfer_zelle.json'\nimport citiTransferZelle from '@zkp2p/providers/citi/transfer_zelle.json'\n\n/**\n * A provider template, passed through to the extension verbatim. The shape is\n * zkp2p's, not ours — we never construct or mutate one, so it stays opaque\n * rather than being modelled field by field (that would rot on every upstream\n * template change).\n */\nexport type ProviderTemplate = Record<string, unknown>\n\n/**\n * Templates keyed by the capture pair the extension would otherwise fetch:\n * `${platform}/${actionType}`. Keys mirror the npm package's own directory\n * layout, so the mapping is verifiable by inspection.\n *\n * Zelle sub-provider `zelle-bofa` maps to platform `bankofamerica`, per\n * `subProviderConfigs[...].extensionPlatform` in @provex/utils.\n */\nconst TEMPLATES_BY_CAPTURE_KEY: Readonly<Record<string, ProviderTemplate>> = Object.freeze({\n 'chase/transfer_zelle': chaseTransferZelle as ProviderTemplate,\n 'bankofamerica/transfer_zelle': bankofamericaTransferZelle as ProviderTemplate,\n 'citi/transfer_zelle': citiTransferZelle as ProviderTemplate,\n})\n\n/**\n * Resolve the inline template for a capture pair, if we bundle one.\n *\n * @param platform - Extension platform slug, e.g. `chase`.\n * @param actionType - Capture action type, e.g. `transfer_zelle`.\n * @returns The template to pass as `providerConfig`, or undefined to let the\n * extension fetch it from the hosted registry (the path for every rail zkp2p\n * actually serves).\n */\nexport function providerTemplateFor(\n platform: string,\n actionType: string,\n): ProviderTemplate | undefined {\n return TEMPLATES_BY_CAPTURE_KEY[`${platform}/${actionType}`]\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 * the injected-PeerClient seam is preserved exactly.\n */\n\nimport { parseUnits } from 'viem'\nimport type { Hex } from 'viem'\nimport { providerConfigs, providerRequiresMetadataIndex } from '@provex/utils/payment'\nimport type { ProviderKey, SubProviderKey } from '@provex/utils/payment'\nimport type { ChainId } from '@provex/utils/chain'\nimport { convertTokenInputToCurrency } from '@provex/utils/conversionRates'\nimport { providerTemplateFor } from './providerTemplates'\nimport {\n EnclaveCaptureUnavailable,\n EnclaveCaptureFailed,\n ManualSelectionRequired,\n asBuyerTeeProof,\n requestBuyerTeeAttestation,\n type CaptureAdapter,\n type CaptureCandidate,\n type CaptureHandle,\n type CaptureRequest,\n type SelectTransaction,\n type VerifiedAttestation,\n} from '@provex/proving'\n\n/** Minimal shape of the metadata response the extension posts back. */\ninterface MetadataResponse {\n platform: string\n metadata: CaptureCandidate[]\n buyerTeeCapture?: { encryptedSessionMaterial: string }\n}\n\n/**\n * zkp2p enclave used for Buyer TEE capture when the caller doesn't override\n * `ExtensionEnclaveAdapterConfig.attestationServiceUrl`. Matches the gate's\n * `TEE_ATTESTATION_URL` — the enclave the gate forwards to for the re-sign.\n */\nexport const DEFAULT_ATTESTATION_SERVICE_URL = 'https://attestation-service.zkp2p.xyz'\n\n/** Minimal slice of `window.peer` this adapter depends on. */\nexport interface PeerClient {\n authenticate(inputs: {\n actionType: string\n platform: string\n captureMode: 'buyerTee'\n /**\n * Enclave the extension establishes the Buyer TEE session with and encrypts\n * the session material toward. Required by the zkp2p 0.6.0 spec\n * (onramp-llm.md §7) — without it the extension performs no Buyer TEE\n * capture and falls back to the retired Reclaim path.\n */\n attestationServiceUrl?: string\n /** Attestation action type; defaults to `actionType` when omitted (spec §2). */\n attestationActionType?: string\n /**\n * Attestation platform, when it differs from `platform`. Required for\n * generic Zelle, whose attestation route is `zelle` while capture runs as\n * the bank (`chase`).\n */\n attestationPlatform?: string\n /**\n * Provider template supplied inline. When present the extension skips its\n * own template fetch entirely — the only way to drive a rail zkp2p does not\n * host. See ./providerTemplates.\n */\n providerConfig?: Record<string, unknown>\n }): Promise<void>\n onMetadataMessage(fn: (data: MetadataResponse) => void): () => void\n /** Current extension connection status; `'connected'` means ready. */\n checkConnectionStatus(): Promise<string>\n /** Prompt the user to connect the extension. Resolves true when approved. */\n requestConnection(): Promise<boolean>\n}\n\nexport interface ExtensionEnclaveAdapterConfig {\n /** Resolve the live peer client (e.g. `() => window.peer`). */\n getPeer: () => PeerClient | undefined\n /** Gate/attestation origin handed to requestBuyerTeeAttestation (the re-sign hop). */\n attestationBaseUrl: string\n /**\n * Enclave URL passed to the extension's `authenticate` so it performs the\n * Buyer TEE capture (zkp2p 0.6.0 spec §7). This is the zkp2p enclave the\n * session material is encrypted toward — the SAME enclave the gate forwards\n * to for the re-sign — NOT our gate's re-sign origin (`attestationBaseUrl`).\n * Defaults to {@link DEFAULT_ATTESTATION_SERVICE_URL} when omitted; set it to\n * override the enclave for local testing.\n */\n attestationServiceUrl?: string\n /**\n * Milliseconds before awaitMetadata rejects if the extension never posts a\n * matching metadata message. Mirrors the 60-second timeout in\n * AuthenticateStep.tsx:232-236. Defaults to 60000.\n */\n timeoutMs?: number\n}\n\ninterface EnclaveMaterial {\n encryptedSessionMaterial: string\n matchedIndex: number\n /** Selected row's provider attestation params (spec §4) — forwarded to the enclave. */\n matchedParams?: Record<string, string | number | boolean>\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 the extension posts for `platform`.\n *\n * Races a configurable timeout (default 60s) against the metadata event,\n * mirroring AuthenticateStep.tsx:232-236. The unsubscribe function is always\n * called on resolution, timeout, or error — no listener leaks.\n *\n * @param peer - Injected extension client.\n * @param platform - The platform handed to `authenticate()` (e.g. 'chase').\n * Echoed back when the extension could not load a template.\n * @param actionType - The capture action type (e.g. 'transfer_zelle'). Its\n * parent is echoed back when the template DID load. Both are accepted; see\n * the guard below.\n * @param timeoutMs - Reject after this long with no matching message.\n */\nfunction awaitMetadata(\n peer: PeerClient,\n platform: string,\n actionType: string,\n timeoutMs: number,\n): Promise<MetadataResponse> {\n return new Promise<MetadataResponse>((resolve, reject) => {\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n\n const unsub = peer.onMetadataMessage((data) => {\n // The extension echoes DIFFERENT platform values depending on how far it\n // got, so correlating on either one alone drops real messages:\n //\n // - Template loaded (success): it echoes the TEMPLATE's own\n // `metadata.platform`. For chase/transfer_zelle.json that is 'zelle'\n // — the generic rail, not the bank. Observed with a live capture:\n // `{ platform: 'zelle', hasBuyerTeeCapture: true, rowCount: 50 }`.\n // - Template fetch failed: there is no template to read, so it echoes\n // back the platform we passed — 'chase' — alongside the errorMessage\n // explaining the failure. Observed live:\n // `{ platform: 'chase', errorMessage: 'Failed to fetch provider template: 404' }`.\n //\n // Matching only the echoed-template platform loses every failure reason\n // (60s timeout instead of the real cause). Matching only the passed\n // platform loses every SUCCESSFUL Zelle capture. Accept both.\n //\n // For providers where platform === parent (venmo, cashapp, …) the two\n // predicates coincide, so this is a strict superset of the old behavior.\n const echoesCapturePlatform = data.platform === platform\n const echoesActionTypeParent = `transfer_${data.platform}` === actionType\n if (echoesCapturePlatform || echoesActionTypeParent) {\n clearTimeout(timeoutId)\n unsub()\n resolve(data)\n }\n })\n\n timeoutId = setTimeout(() => {\n unsub()\n reject(new Error(`Enclave capture timed out for platform \"${platform}\"`))\n }, timeoutMs)\n })\n}\n\nexport function createExtensionEnclaveAdapter(config: ExtensionEnclaveAdapterConfig): CaptureAdapter {\n const timeoutMs = config.timeoutMs ?? 60000\n\n return {\n kind: 'extension-enclave',\n\n async authenticateAndCapture(\n request: CaptureRequest,\n options?: { selectTransaction?: SelectTransaction },\n ): Promise<CaptureHandle> {\n const peer = config.getPeer()\n if (!peer) {\n throw new EnclaveCaptureUnavailable(request.platform)\n }\n\n // Ensure the extension is connected BEFORE authenticate. A cold\n // authenticate() on a fresh page returns errorMessage \"Peer extension\n // connection required\" with no buyerTeeCapture — which trips the\n // enclave-unavailable path. The legacy AuthenticateStep connects first\n // (and onramp-llm.md §3 `ensurePeerReady` requires it); the adapter must\n // too. This is why a warm local tab worked but a fresh prod session failed.\n const status = await peer.checkConnectionStatus()\n if (status !== 'connected') {\n await peer.requestConnection()\n }\n\n // Register the listener BEFORE authenticate fires to avoid a race where\n // the extension posts metadata before the listener is attached.\n const metadataPromise = awaitMetadata(peer, request.platform, request.actionType, timeoutMs)\n await peer.authenticate({\n actionType: request.actionType,\n platform: request.platform,\n captureMode: 'buyerTee',\n // Required for the extension to actually perform the Buyer TEE capture\n // (zkp2p 0.6.0 spec §7). Omitting these is why non-Revolut rails got\n // \"no buyerTee capture\" and dead-ended on the retired Reclaim path.\n attestationServiceUrl: config.attestationServiceUrl ?? DEFAULT_ATTESTATION_SERVICE_URL,\n // Attestation namespace — differs from the capture pair for generic\n // Zelle (zelle/transfer_zelle_chase vs chase/transfer_zelle).\n attestationActionType: request.attestationActionType ?? request.actionType,\n attestationPlatform: request.attestationPlatform ?? request.platform,\n // Bundled template for rails zkp2p does not host. Undefined for hosted\n // rails, which keeps the extension fetching (and auto-updating) them.\n providerConfig: providerTemplateFor(request.platform, request.actionType),\n })\n const response = await metadataPromise\n\n // No buyerTee capture ⇒ the extension didn't produce an encrypted enclave\n // session. Log WHY before failing — the extension may have surfaced an\n // errorMessage, or returned metadata-only (no capture) for this platform.\n if (!response.buyerTeeCapture) {\n const errorMessage = (response as { errorMessage?: unknown }).errorMessage\n console.warn(\n '[extensionEnclave] no buyerTeeCapture for',\n request.platform,\n '— response keys:', Object.keys(response),\n '| errorMessage:', errorMessage,\n '| metadata rows:', Array.isArray(response.metadata) ? response.metadata.length : '(none)',\n '| buyerTeeCapture:', response.buyerTeeCapture,\n )\n // Two distinct failures share the \"no capture\" shape:\n // - The extension surfaced an `errorMessage` ⇒ it ATTEMPTED the capture\n // but something failed (most often: it could not reach the\n // attestation enclave — a buyer-side network / firewall / ad-blocker\n // issue). Surface that as a retryable EnclaveCaptureFailed carrying\n // the raw reason, so the user sees an actionable message instead of\n // \"provider unsupported\".\n // - No `errorMessage` ⇒ the provider produced no capture path at all\n // (non-migrated). That stays EnclaveCaptureUnavailable, the\n // Reclaim-fallback signal.\n if (typeof errorMessage === 'string' && errorMessage.trim().length > 0) {\n throw new EnclaveCaptureFailed(request.platform, errorMessage)\n }\n throw new EnclaveCaptureUnavailable(request.platform)\n }\n\n const 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 // The SELECTED row's params ARE the attestation params (spec §4) — carry\n // them through so attest() can forward them. Look up by originalIndex so\n // this works for both the auto-match and manual-selection branches.\n const matchedRow = response.metadata.find((row) => row.originalIndex === matchedIndex)\n const material: EnclaveMaterial = {\n encryptedSessionMaterial: response.buyerTeeCapture.encryptedSessionMaterial,\n matchedIndex,\n matchedParams: matchedRow?.params,\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 // The selected row's params ARE the attestation params (spec §5,\n // buildBuyerTeeProof) and MUST be forwarded — dropping them is why the\n // enclave 400s with \"Invalid <provider> query params\". The metadata\n // `index` is then merged in ONLY for providers that expect it (Venmo /\n // Cash App / Revolut / Zelle) and omitted for the rest (PayPal / Wise /\n // Monzo / Chime). See providerRequiresMetadataIndex.\n const params = {\n ...(material.matchedParams ?? {}),\n ...(providerRequiresMetadataIndex(request.provider as ProviderKey | SubProviderKey)\n ? { index: material.matchedIndex }\n : {}),\n }\n const attestation = await requestBuyerTeeAttestation({\n attestationBaseUrl: config.attestationBaseUrl,\n // The ATTESTATION namespace, which differs from the capture namespace\n // for generic Zelle: capture ran as chase/transfer_zelle, but the\n // enclave's verify route demands zelle/transfer_zelle_chase. Sending\n // the capture pair here is why a successful Zelle capture would still\n // fail at attest. Providers where the namespaces coincide leave the\n // overrides unset and fall back to the capture values.\n platform: request.attestationPlatform ?? request.platform,\n actionType: request.attestationActionType ?? request.actionType,\n encryptedSessionMaterial: material.encryptedSessionMaterial,\n params,\n verifyingContract: request.verifyingContract,\n intent: request.intent,\n })\n // 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"]}
|
|
1
|
+
{"version":3,"sources":["../src/useZKTLS.ts","../src/ZKTLSContext.tsx","../src/adapters/extensionEnclave.ts"],"names":["parseAbiParameter","encodeAbiParameters","subProviderConfigs","encodePacked","SeriesProgress","useState","useEffect","useMemo","sleep","extensionPlatforms","providers","convertTokenInputToCurrency","normalizeAmount","parseUnits","createContext","useContext","providerConfigs","ManualSelectionRequired","EnclaveCaptureUnavailable","EnclaveCaptureFailed","PaymentAlreadyUsedError","payment","providerRequiresMetadataIndex","requestBuyerTeeAttestation"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @fileoverview React hook and utilities for PeerAuth browser extension integration.\n *\n * The PeerAuth extension (formerly ZK-TLS) provides secure proof generation\n * for off-chain payment verification. This module provides:\n *\n * 1. Type definitions for extension API (window.peer)\n * 2. useZKTLS hook for React components\n * 3. Utility functions for transaction matching and proof encoding\n *\n * @remarks\n * The extension exposes `window.peer` (or deprecated `window.zktls`) for:\n * - Requesting connection (OAuth-like flow)\n * - Authenticating with payment providers\n * - Generating notarized proofs\n * - Fetching transaction metadata\n *\n * @see {@link ZKTLSContext} for the React context provider\n */\n\nimport { useEffect, useMemo, useState } from \"react\"\nimport { encodeAbiParameters, encodePacked, parseAbiParameter, parseUnits, type Hex } from \"viem\"\nimport { sleep } from \"@provex/utils/time\"\nimport type { IntentStruct } from \"@provex/utils/intent\"\nimport type { TokenInfo } from \"@provex/utils/tokens\"\nimport { extensionPlatforms, providers, subProviderConfigs, type ActionType, type PlatformKey, type ProviderKey, type SubProviderKey } from \"@provex/utils/payment\"\nimport { convertTokenInputToCurrency } from \"@provex/utils/conversionRates\"\n\ndeclare global {\n interface ZKTLS {\n requestConnection(): Promise<boolean>\n checkConnectionStatus(): Promise<Status>\n getVersion(): Promise<string>\n authenticate(inputs: AuthenticationInputs): Promise<void>\n generateProof(inputs: GenerateProofInputs): Promise<GenerateProofResponse>\n fetchProofById(proofId: string): Promise<ProofResponse>\n fetchProofs(): Promise<FetchProofsResponse>\n openSidebar(route: string): Promise<void>\n onMetadataMessage(fn: (data: MetadataMessageResponse) => void): () => void\n /**\n * Trusted-execution on-ramp (PeerAuth v3+). The extension opens the\n * buyer's bank in a new tab, intercepts the authenticated session, calls\n * the zkp2p trusted-execution attestation service, and returns\n * ready-to-send `fulfillIntent` calldata through the callback. Replaces\n * the retired `authenticate` + `generateProof` Reclaim path.\n *\n * Optional: older extension builds predate the on-ramp API, so guard\n * with a runtime presence check (`supportsOnramp`) before calling.\n *\n * @param queryString serialized on-ramp params (see {@link buildOnrampQuery})\n * @param callback invoked once the prepared transaction is ready\n */\n onramp?(\n queryString: string,\n callback: (result: PeerOnrampPreparedTransaction) => void,\n ): void\n /** Retrieve a previously prepared on-ramp transaction by intent hash. */\n getOnrampTransaction?(intentHash: string): Promise<PeerOnrampPreparedTransaction | null>\n }\n interface Window {\n /** @deprecated Use window.peer instead */\n zktls: ZKTLS | undefined\n /** PeerAuth extension API (replaces window.zktls) */\n peer: ZKTLS | undefined\n }\n}\n\n/** the status of the PeerAuth extension */\nexport type Status = 'connected' | 'disconnected' | 'pending'\n\n/** the individual metadata message sent by the PeerAuth extension */\nexport interface MetadataMessage {\n amount: number\n /** Currency code (e.g., 'USD', 'EUR') */\n currency?: string\n /**\n * Date of the transaction.\n * Can be a string in YYYYMMDD format, a number (YYYYMMDD or timestamp), or Date object.\n * The actual type depends on the extension implementation.\n */\n date: string | number | Date\n hidden: boolean\n originalIndex: number\n paymentId: number\n recipient: string\n}\n\n/** the response from the PeerAuth extension when a metadata message is received */\nexport interface MetadataMessageResponse {\n expiresAt: number\n platform: ProviderKey\n requestId: string\n metadata: MetadataMessage[]\n /**\n * Present only when `authenticate` was called with `captureMode: 'buyerTee'`.\n * Carries the bank session already encrypted toward the enclave — feed\n * `encryptedSessionMaterial` (plus the matched transaction's index) to the\n * trusted-execution verify endpoint. No `generateProof` call is involved.\n */\n buyerTeeCapture?: {\n encryptedSessionMaterial: string\n }\n /** Error surfaced by the extension during capture (e.g. missing platform). */\n errorMessage?: string\n}\n\n/** the response from the PeerAuth extension when fetching proofs */\nexport interface FetchProofsResponse {\n notaryRequests: NotaryRequest[]\n}\n\n/** the inputs for the generateProof function */\nexport type GenerateProofInputsAbstracted = {\n platform: PlatformKey\n provider: ProviderKey\n intentHash: Hex\n originalIndex: number\n}\n\n/** the inputs for the generateProof function */\nexport type GenerateProofInputs = {\n platform: ProviderKey\n originalIndex: number\n proofIndex?: number\n intentHash: string\n}\n\n/**\n * inputs for the generate proof function that requires the intent hash\n * as hex to ensure it can be converted to a bigint string accurately\n */\nexport type HexIntentGenerateProofInputs = Exclude<GenerateProofInputs, 'intentHash'> & { intentHash: Hex }\n\n/** the response from the PeerAuth extension when generating a proof */\nexport type GenerateProofResponse = {\n proofId: string\n platform: ProviderKey\n}\n\n/** the inputs for the authenticate function */\nexport type AuthenticationInputs = {\n actionType: ActionType\n platform: PlatformKey\n /**\n * Session capture mode. `'buyerTee'` makes the extension intercept the bank\n * session and return it encrypted toward the trusted-execution enclave in\n * `MetadataMessageResponse.buyerTeeCapture` — the replacement for the retired\n * Reclaim `generateProof` path. Omit for the legacy Reclaim flow. The\n * extension defaults the attestation service URL when this is set.\n */\n captureMode?: 'buyerTee' | 'sellerCredential'\n /**\n * Enclave the extension establishes the Buyer TEE session with and encrypts\n * the captured session material toward (zkp2p 0.6.0 spec §7). Pass explicitly\n * for newer providers — relying on the extension's built-in default is what\n * left non-Revolut rails with \"no buyerTee capture\".\n */\n attestationServiceUrl?: string\n /** Attestation action type; defaults to `actionType` when omitted (spec §2). */\n attestationActionType?: ActionType\n}\n\n/** the response from the PeerAuth extension when fetching a proof */\nexport interface Proof {\n claimInfo: ClaimInfo\n signedClaim: SignedClaim\n isAppclipProof: boolean\n status: string\n}\n\n/** the response from the PeerAuth extension when fetching a proof */\nexport interface NotarizedProof {\n claim: {\n context: string\n epoch: number\n identifier: Hex\n owner: Hex\n parameters: string\n provider: string\n timestampS: number\n },\n signatures: {\n attestorAddress: Hex\n claimSignature: Record<number, number>\n resultSignature: Record<number, number>\n },\n}\n\n/** the claim info for a proof */\nexport interface ClaimInfo {\n provider: string\n parameters: string\n context: string\n}\n\n/** the complete claim data for a proof */\nexport interface CompleteClaimData {\n identifier: Hex\n owner: Hex\n timestampS: number\n epoch: number\n}\n\n/** the signed claim for a proof */\nexport interface SignedClaim {\n claim: CompleteClaimData\n signatures: Hex[]\n}\n\n/** the reclaim proof for a proof */\nexport type ReclaimProof = {\n claimInfo: ClaimInfo\n signedClaim: SignedClaim\n isAppclipProof: boolean\n}\n\n/** the notary request for a proof */\nexport interface NotaryRequest {\n metadata: (string | number)[]\n actionType: ActionType\n id: string\n status: string\n timestamp: number\n proof: NotarizedProof\n}\n\n/** the response from the PeerAuth extension when fetching a proof */\nexport interface ProofResponse {\n notaryRequest: NotaryRequest\n}\n\n/**\n * Ready-to-send transaction the PeerAuth extension hands back once the\n * trusted-execution attestation service has signed the payment. The `data`\n * already embeds the enclave attestation, so the client signs and sends it\n * verbatim — no client-side proof encoding or gate re-signing.\n */\nexport type PeerOnrampPreparedTransaction = {\n intentHash: Hex\n status: 'calldata_ready'\n transaction: {\n to: Hex\n data: Hex\n /** Wei, as a decimal string (the extension keeps it out of JSON-number range). */\n value: string\n chainId: number\n }\n}\n\n/**\n * Structured on-ramp inputs. The extension looks the intent up from\n * `intentHash` (and the optional context fields) to resolve the chain,\n * contracts, and payment platform — we never pass contract addresses.\n */\nexport type PeerExtensionOnrampParams = {\n intentHash: string\n referrer?: string\n referrerLogo?: string\n inputCurrency?: string\n inputAmount?: string | number\n paymentPlatform?: string\n depositId?: string | number | bigint\n toToken?: string\n amountUsdc?: string | number | bigint\n recipientAddress?: string\n}\n\n/**\n * Serialize on-ramp params into the query string the extension's `onramp`\n * entry point expects. Mirrors the mapping `@zkp2p/sdk`'s\n * `peerExtensionSdk.onramp` performs: every defined field becomes one query\n * parameter, `undefined`/`null` fields are dropped, and `bigint` values are\n * stringified.\n */\nexport const buildOnrampQuery = (params: PeerExtensionOnrampParams): string => {\n const search = new URLSearchParams()\n Object.entries(params).forEach(([key, value]) => {\n if (value === undefined || value === null) return\n search.set(key, String(value))\n })\n return search.toString()\n}\n\n/**\n * Wrap a buyerTee capture (from {@link MetadataMessageResponse.buyerTeeCapture})\n * as a `NotaryRequest` so it flows through the existing submit step, which\n * narrows `proof` to a buyerTee proof and routes it to the enclave verify\n * endpoint. The `index` is the matched transaction's `originalIndex` — the\n * extension keys the captured payment on it.\n *\n * This replaces `generateProof` entirely for migrated providers: the session\n * was already captured + encrypted during `authenticate({ captureMode: 'buyerTee' })`.\n */\nexport const buildBuyerTeeNotaryRequest = ({\n encryptedSessionMaterial,\n index,\n actionType,\n}: {\n encryptedSessionMaterial: string\n index: number\n actionType: ActionType\n}): NotaryRequest => ({\n metadata: [],\n actionType,\n id: `buyer-tee-${index}`,\n status: 'success',\n timestamp: 0,\n // The runtime shape is a buyerTee proof, not a Reclaim NotarizedProof; the\n // submit step narrows it via `asBuyerTeeProof` before use.\n proof: {\n proofType: 'buyerTee',\n encryptedSessionMaterial,\n params: { index },\n } as unknown as NotarizedProof,\n})\n\nexport const extensionUrl = 'https://chromewebstore.google.com/detail/ijpgccednehjpeclfcllnjjcmiohdjih'\n\n/** convert a byte array to a hex string */\nexport const byteArrayToHexString = (byteArray: number[]): Hex => {\n return `0x${byteArray\n .map(byte => byte.toString(16).padStart(2, '0'))\n .join('')}`\n}\n\n/** parse a proof object from the PeerAuth extension */\nexport const parseExtensionProof = (proofObject: NotarizedProof) => {\n return {\n claimInfo: {\n provider: proofObject.claim.provider,\n parameters: proofObject.claim.parameters,\n context: proofObject.claim.context\n },\n signedClaim: {\n claim: {\n identifier: proofObject.claim.identifier as Hex,\n owner: proofObject.claim.owner as Hex,\n timestampS: proofObject.claim.timestampS,\n epoch: proofObject.claim.epoch\n },\n signatures: [byteArrayToHexString(Object.values(proofObject.signatures.claimSignature))]\n },\n isAppclipProof: false\n } as ReclaimProof\n}\n\n/** the abi encoding for a proof */\nexport const PROOF_ENCODING = parseAbiParameter(\"((string provider, string parameters, string context) claimInfo, ((bytes32 identifier, address owner, uint32 timestampS, uint32 epoch) claim, bytes[] signatures) signedClaim, bool isAppclipProof)\")\n\n/** encode a single proof as a bytes array */\nexport function encodeProofAsBytes(proof: ReclaimProof): Hex {\n return encodeAbiParameters([PROOF_ENCODING], [proof])\n}\n\n/** encode two proofs to be passed to the underlying sub-provider */\nexport const encodeTwoProofs = (proof1: ReclaimProof, proof2: ReclaimProof) => {\n return encodeAbiParameters([PROOF_ENCODING, PROOF_ENCODING], [proof1, proof2])\n}\n\n/** a high level function to encode a proof for a given provider and sub-provider */\nexport const encodeProof = ({ extensionProofs, subProvider, provider }: {\n extensionProofs: NotarizedProof[],\n subProvider: PlatformKey | null,\n provider: ProviderKey\n}) => {\n const reclaimProofs = extensionProofs.map((p) => parseExtensionProof(p))\n if (provider === 'zelle') {\n // move this to check the blockchain for the payment method\n const paymentMethod = subProviderConfigs![provider]![subProvider as SubProviderKey]!.paymentMethod\n const twoEncoded = reclaimProofs.length === 2\n ? encodeTwoProofs(reclaimProofs[0]!, reclaimProofs[1]!)\n : encodeProofAsBytes(reclaimProofs[0]!)\n // select between various possible sub-providers\n const withPaymentMethod = encodeProofWithPaymentMethod({\n paymentMethod,\n proof: twoEncoded,\n })\n return withPaymentMethod\n }\n const [reclaimProof] = reclaimProofs\n return encodeProofAsBytes(reclaimProof!)\n}\n\n/**\n * wrap a proof with a payment method and proof\n * this allows us to use a single verifier as an entry point for multiple underlying sub-providers\n */\nexport const encodeProofWithPaymentMethod = ({\n proof,\n paymentMethod\n}: {\n proof: Hex,\n paymentMethod: number\n}): Hex => {\n return encodePacked(['uint8', 'bytes'], [paymentMethod, proof])\n}\n\n/** the progress of a proof series */\nexport enum SeriesProgress {\n Generating,\n Polling,\n Complete,\n}\n\nexport type ProgressInfo = {\n progress: SeriesProgress\n total: number\n step: number | null\n id: string | null\n}\n\n/** the mutations for the generate proof series function */\nexport type GenerateProofSeriesMutations = {\n progress?: ({ progress, step, id }: ProgressInfo) => void\n setOwnerName?: (name: string) => void\n}\n\n/** React hook to interact with the PeerAuth extension */\nexport const useZKTLS = () => {\n const [isInitialized, setIsInitialized] = useState(false)\n useEffect(() => {\n const setup = () => {\n setIsInitialized(true)\n }\n const eventName = 'peer#initialized'\n if (typeof window.peer !== 'undefined') {\n // api is available\n setup()\n } else {\n window.addEventListener(eventName, setup)\n return () => {\n window.removeEventListener(eventName, setup)\n }\n }\n }, [])\n return useMemo(() => {\n /** fetch a proof by its id */\n const fetchProofById = async (proofId: string): Promise<NotaryRequest | null> => {\n const proof = await window.peer!.fetchProofById(proofId)\n if (proof && proof.notaryRequest) return proof.notaryRequest\n const proofs = await window.peer!.fetchProofs()\n const proofFromList = proofs.notaryRequests.find((p) => p.id === proofId)\n if (proofFromList) return proofFromList\n return null\n }\n /** wait for a proof to be generated */\n const waitForProof = async (proofId: string, onCurrent?: (notaryRequest: NotaryRequest | null) => void) => {\n let notaryRequest: NotaryRequest | null = null\n do {\n notaryRequest = await fetchProofById(proofId)\n onCurrent?.(notaryRequest)\n await sleep(1_000)\n } while (!notaryRequest || !notaryRequest.status || notaryRequest.status === 'pending')\n if (notaryRequest.status === 'failed' || notaryRequest.status === 'expired' || notaryRequest.status === 'cancelled' || notaryRequest.status === 'error') {\n console.error('[waitForProof] proof terminal state:', JSON.stringify(notaryRequest, null, 2))\n const req = notaryRequest as unknown as Record<string, unknown>\n const detail = req.errorMessage ?? req.error ?? req.message ?? notaryRequest.status\n throw new Error(`Proof generation failed: ${detail}`)\n }\n return notaryRequest\n }\n /** generate a proof */\n const generateProof = async (inputs: HexIntentGenerateProofInputs): Promise<GenerateProofResponse> => {\n return await window.peer!.generateProof({\n proofIndex: 0,\n ...inputs,\n intentHash: BigInt(inputs.intentHash).toString(),\n })\n }\n return {\n /** check if the PeerAuth extension is initialized */\n isInitialized,\n /** request a connection to the PeerAuth extension */\n requestConnection: async () => {\n if (!window.peer) return false\n return window.peer!.requestConnection()\n },\n /**\n * check the connection status of the PeerAuth extension\n * @returns the connection status\n */\n checkConnectionStatus: async () => {\n return await window.peer!.checkConnectionStatus()\n },\n /** get the version of the PeerAuth extension */\n getVersion: async () => {\n return await window.peer?.getVersion()\n },\n /** authenticate with the PeerAuth extension */\n authenticate: async (inputs: AuthenticationInputs) => {\n // Create promise first to avoid race condition where message arrives before resolver is assigned\n let resolve: (value: MetadataMessageResponse) => void\n const promise = new Promise<MetadataMessageResponse>((resolver) => {\n resolve = resolver\n })\n\n // Now set up the listener - resolve is guaranteed to be the real resolver\n const unsub = window.peer!.onMetadataMessage((data) => {\n if (`transfer_${data.platform}` === inputs.actionType) {\n resolve(data)\n }\n })\n\n await window.peer!.authenticate(inputs)\n\n return await promise.then((data) => {\n unsub()\n return data\n })\n },\n /** generate a proof */\n generateProof,\n /** wait for a proof to be generated */\n waitForProof,\n /** generate a proof series */\n generateProofSeries: async (\n inputs: GenerateProofInputsAbstracted,\n { progress: progressCallback }: GenerateProofSeriesMutations = {},\n ): Promise<NotaryRequest[]> => {\n const { platform, provider, intentHash, originalIndex } = inputs\n const args = {\n platform: provider,\n originalIndex,\n intentHash,\n } as const\n // Chase requires 2 proofs (account verification + transaction)\n // Platform is the bank name (e.g., 'chase') from extensionPlatform config\n const isChase = platform === extensionPlatforms.CHASE\n const isZelle = provider === providers.ZELLE\n const expectedTotal = isZelle && isChase ? 2 : 1\n progressCallback?.({ progress: SeriesProgress.Generating, step: 0, id: null, total: expectedTotal })\n const genProofA = await generateProof({ ...args, proofIndex: 0 })\n progressCallback?.({ progress: SeriesProgress.Polling, step: 0, id: genProofA.proofId, total: expectedTotal })\n const proofA = await waitForProof(genProofA.proofId)\n if (!isChase || !isZelle) {\n progressCallback?.({ progress: SeriesProgress.Complete, step: null, id: null, total: expectedTotal })\n return [proofA]\n }\n progressCallback?.({ progress: SeriesProgress.Generating, step: 1, id: null, total: expectedTotal })\n const genProofB = await generateProof({ ...args, proofIndex: 1 })\n progressCallback?.({ progress: SeriesProgress.Polling, step: 1, id: genProofB.proofId, total: expectedTotal })\n const proofB = await waitForProof(genProofB.proofId)\n progressCallback?.({ progress: SeriesProgress.Complete, step: null, id: null, total: expectedTotal })\n return [proofA, proofB]\n },\n /** subscribe to metadata messages */\n onMetadataMessage: (fn: (data: any) => void) => {\n return window.peer!.onMetadataMessage(fn)\n },\n /** fetch a proof by its id */\n fetchProofById,\n /** fetch generated proofs */\n fetchGeneratedProofs: async (genProofResponses: GenerateProofResponse[]) => {\n return await Promise.all(genProofResponses.map((r) => fetchProofById(r.proofId)))\n },\n /** fetch all proofs */\n fetchProofs: async () => {\n return await window.peer!.fetchProofs()\n },\n /** open the PeerAuth sidebar */\n openSidebar: async (route: string) => {\n return await window.peer!.openSidebar(route)\n },\n /** Whether the connected extension exposes the trusted-execution on-ramp. */\n supportsOnramp: (): boolean => typeof window.peer?.onramp === 'function',\n /**\n * Begin the trusted-execution on-ramp and resolve with ready-to-send\n * `fulfillIntent` calldata once the extension has intercepted the bank\n * session and the zkp2p attestation service has signed the payment.\n *\n * The underlying extension API is callback-only with no error channel:\n * the extension surfaces progress and failures in its own sidebar/tab,\n * and only invokes the callback on success. The returned promise\n * therefore stays pending until the prepared transaction arrives — the\n * caller drives the user toward the PeerAuth window and may offer a\n * cancel affordance rather than racing a timeout against a buyer who is\n * still logging into their bank.\n */\n onramp: async (\n params: PeerExtensionOnrampParams,\n ): Promise<PeerOnrampPreparedTransaction> => {\n if (typeof window.peer?.onramp !== 'function') {\n throw new Error(\n 'This PeerAuth extension version does not support the trusted-execution on-ramp. Please update the extension.',\n )\n }\n const queryString = buildOnrampQuery(params)\n return new Promise<PeerOnrampPreparedTransaction>((resolve) => {\n window.peer!.onramp!(queryString, resolve)\n })\n },\n /**\n * Retrieve a previously prepared on-ramp transaction by intent hash.\n * Returns null when the extension lacks the API or has nothing prepared\n * yet — usable as a poll alongside {@link onramp} to recover a result\n * if the one-shot callback is missed.\n */\n getOnrampTransaction: async (\n intentHash: string,\n ): Promise<PeerOnrampPreparedTransaction | null> => {\n if (typeof window.peer?.getOnrampTransaction !== 'function') return null\n return window.peer.getOnrampTransaction(intentHash)\n },\n }\n }, [isInitialized])\n}\n\n/**\n * Find the metadata message that matches the intent by amount.\n * Returns the first (most recent) transaction in the metadata array that matches the expected amount.\n *\n * Simplified matching: just find transactions with the correct amount.\n * The user can manually select if multiple matches exist.\n */\nexport const findMetadataMessage = ({ metadata, intent, token, centIsInt = false }: {\n metadata: MetadataMessage[],\n intent: IntentStruct,\n token: TokenInfo,\n /** @deprecated No longer used - kept for API compatibility */\n recipient?: string,\n centIsInt?: boolean,\n}): MetadataMessage | null => {\n const currencyAmount = convertTokenInputToCurrency({\n token,\n amountOutInt: intent.amount,\n rate: intent.conversionRate,\n })\n /**\n * Normalize an amount from the extension into a bigint in token base units.\n *\n * The extension sends amounts in different formats per provider:\n * - Venmo: `\"- $1.00\"` (formatted dollar string)\n * - Others: `100` (integer cents) or `1.00` (decimal dollars)\n *\n * We detect the format by checking for currency symbols or decimal points\n * in the raw value. If present, the value is already in dollars and we\n * skip the centIsInt division.\n */\n const normalizeAmount = (amount: number | string): 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, token.decimals)\n if (centIsInt && !isFormattedDollars) {\n value = value / 100n\n }\n return value\n }\n\n // Skip auto-match when manual selection is forced (dev/testing)\n if (typeof window !== 'undefined' && (window as unknown as Record<string, unknown>).__FORCE_MANUAL_TX_SELECT__) {\n return null\n }\n\n // Find the first transaction that matches the amount (first in array = most recent)\n // Note: We ignore the hidden flag - matching is purely by amount\n const match = metadata.find((m) => {\n const converted = normalizeAmount(m.amount)\n return converted === currencyAmount || converted === (currencyAmount * -1n)\n })\n\n if (!match) {\n // Detect 100x scale mismatch — the most common failure mode when centIsInt\n // is wrong for a provider. Fires loudly so it's obvious in dev/staging logs.\n const hundredxMatch = metadata.find((m) => {\n const converted = normalizeAmount(m.amount)\n const abs = converted < 0n ? -converted : converted\n return abs === currencyAmount * 100n || abs * 100n === currencyAmount\n })\n if (hundredxMatch) {\n console.warn(\n `[findMetadataMessage] tx[0] amount=${String(hundredxMatch.amount)} is 100x off from expected ${currencyAmount.toString()} — centIsInt is likely wrong for this provider (currently ${centIsInt})`\n )\n }\n return null\n }\n\n return match\n}\n","/**\n * @fileoverview React context for PeerAuth extension state.\n *\n * Provides global access to the useZKTLS hook state throughout the\n * component tree, avoiding prop drilling for extension status.\n *\n * @see {@link useZKTLS} for the underlying hook\n */\n\nimport { createContext, useContext, type ReactNode } from 'react'\nimport { useZKTLS } from './useZKTLS'\n\n/**\n * Context type derived from useZKTLS hook return value.\n */\ntype ZKTLSContextType = ReturnType<typeof useZKTLS>\n\nexport const ZKTLSContext = createContext<ZKTLSContextType | undefined>(undefined)\n\ninterface ZKTLSProviderProps {\n children: ReactNode\n}\n\n/**\n * Hook to consume the ZKTLS context.\n * Must be used within a ZKTLSProvider.\n *\n * @throws Error if used outside of ZKTLSProvider\n */\nexport function useZKTLSContext() {\n const context = useContext(ZKTLSContext)\n if (context === undefined) {\n throw new Error('useZKTLSContext must be used within a ZKTLSProvider')\n }\n return context\n}\n\nexport function ZKTLSProvider({ children }: ZKTLSProviderProps) {\n const zktlsHook = useZKTLS()\n\n return (\n <ZKTLSContext.Provider value={zktlsHook}>\n {children}\n </ZKTLSContext.Provider>\n )\n}\n","/**\n * @fileoverview Extension + enclave CaptureAdapter (the v1 capture backend).\n *\n * Mirrors the live AuthenticateStep + GenerateProofStep enclave path\n * (AuthenticateStep.tsx:224-281, GenerateProofStep.tsx:237-251) reduced to\n * the buyerTee branch. Talks to an injected `window.peer`-shaped client so\n * the injected-PeerClient seam is preserved exactly.\n */\n\nimport { parseUnits } from 'viem'\nimport type { Hex } from 'viem'\nimport { providerConfigs, providerRequiresMetadataIndex } from '@provex/utils/payment'\nimport type { ProviderKey, SubProviderKey } from '@provex/utils/payment'\nimport type { ChainId } from '@provex/utils/chain'\nimport { convertTokenInputToCurrency } from '@provex/utils/conversionRates'\nimport {\n EnclaveCaptureUnavailable,\n EnclaveCaptureFailed,\n ManualSelectionRequired,\n PaymentAlreadyUsedError,\n asBuyerTeeProof,\n requestBuyerTeeAttestation,\n type CaptureAdapter,\n type CaptureCandidate,\n type CaptureHandle,\n type CaptureRequest,\n type SelectTransaction,\n type VerifiedAttestation,\n} from '@provex/proving'\n\n/** Minimal shape of the metadata response the extension posts back. */\ninterface MetadataResponse {\n platform: string\n metadata: CaptureCandidate[]\n buyerTeeCapture?: { encryptedSessionMaterial: string }\n}\n\n/**\n * zkp2p enclave used for Buyer TEE capture when the caller doesn't override\n * `ExtensionEnclaveAdapterConfig.attestationServiceUrl`. Matches the gate's\n * `TEE_ATTESTATION_URL` — the enclave the gate forwards to for the re-sign.\n */\nexport const DEFAULT_ATTESTATION_SERVICE_URL = 'https://attestation-service.zkp2p.xyz'\n\n/** Minimal slice of `window.peer` this adapter depends on. */\nexport interface PeerClient {\n authenticate(inputs: {\n actionType: string\n platform: string\n captureMode: 'buyerTee'\n /**\n * Enclave the extension establishes the Buyer TEE session with and encrypts\n * the session material toward. Required by the zkp2p 0.6.0 spec\n * (onramp-llm.md §7) — without it the extension performs no Buyer TEE\n * capture and falls back to the retired Reclaim path.\n */\n attestationServiceUrl?: string\n /** Attestation action type; defaults to `actionType` when omitted (spec §2). */\n attestationActionType?: string\n /** Attestation platform; defaults to `platform` when omitted. */\n attestationPlatform?: string\n }): Promise<void>\n onMetadataMessage(fn: (data: MetadataResponse) => void): () => void\n /** Current extension connection status; `'connected'` means ready. */\n checkConnectionStatus(): Promise<string>\n /** Prompt the user to connect the extension. Resolves true when approved. */\n requestConnection(): Promise<boolean>\n}\n\nexport interface ExtensionEnclaveAdapterConfig {\n /** Resolve the live peer client (e.g. `() => window.peer`). */\n getPeer: () => PeerClient | undefined\n /** Gate/attestation origin handed to requestBuyerTeeAttestation (the re-sign hop). */\n attestationBaseUrl: string\n /**\n * Enclave URL passed to the extension's `authenticate` so it performs the\n * Buyer TEE capture (zkp2p 0.6.0 spec §7). This is the zkp2p enclave the\n * session material is encrypted toward — the SAME enclave the gate forwards\n * to for the re-sign — NOT our gate's re-sign origin (`attestationBaseUrl`).\n * Defaults to {@link DEFAULT_ATTESTATION_SERVICE_URL} when omitted; set it to\n * override the enclave for local testing.\n */\n attestationServiceUrl?: string\n /**\n * Milliseconds before awaitMetadata rejects if the extension never posts a\n * matching metadata message. Mirrors the 60-second timeout in\n * AuthenticateStep.tsx:232-236. Defaults to 60000.\n */\n timeoutMs?: number\n}\n\n/** One bank-history row the attestation may settle this order with. */\ninterface PaymentToTry {\n /** The row's index in the captured bank history. */\n index: number\n /** The row's provider attestation params (spec §4), forwarded to the enclave. */\n params?: Record<string, string | number | boolean>\n}\n\ninterface EnclaveMaterial {\n encryptedSessionMaterial: string\n /**\n * Payments to attest, in order. One entry for a single amount match or a\n * buyer's pick; every amount match (bank order, newest first) when several\n * payments share the order's amount. Bank dates resolve only to the day,\n * so they cannot tell same-day payments apart; the gate can, by refusing a\n * payment that already settled an order.\n */\n payments: PaymentToTry[]\n}\n\n/**\n * Normalize an amount from the extension into a bigint in token base units.\n *\n * Reproduces findMetadataMessage's normalizeAmount (useZKTLS.ts:630-639).\n * The extension sends amounts in different formats per provider:\n * - Venmo: `\"- $1.00\"` (formatted dollar string — isFormattedDollars=true)\n * - Others: `100` (integer cents) or `1.00` (decimal dollars)\n *\n * When `centIsInt` is true and the raw value has no currency symbol or decimal\n * point (i.e. it arrived as integer cents), we divide by 100 to convert to\n * the same base unit scale that parseUnits applied.\n */\nfunction normalizeAmount(amount: number | string, decimals: number, centIsInt: boolean): bigint {\n const raw = String(amount)\n const isFormattedDollars = /[^0-9\\-]/.test(raw) && raw.includes('.')\n const cleaned = raw.replace(/[^0-9.]/g, '')\n let value = parseUnits(cleaned, decimals)\n if (centIsInt && !isFormattedDollars) {\n value = value / 100n\n }\n return value\n}\n\n/**\n * The outcome of matching the bank history against the intent's amount.\n *\n * - `single`: exactly one payment has the intent's amount.\n * - `several`: more than one does (e.g. two $2 Zelle payments to the same\n * seller). All are tried in bank order; the gate refuses any that already\n * settled an order. Taking only the first sent an already-used payment and\n * failed the order with \"This payment has already been verified\".\n * - `none`: no payment has that amount; the buyer picks from the full list.\n */\ntype PaymentMatch =\n | { kind: 'single'; candidate: CaptureCandidate }\n | { kind: 'several'; candidates: CaptureCandidate[] }\n | { kind: 'none' }\n\n/**\n * Match the bank history against the intent's FIAT currency amount,\n * accepting a positive or negative sign as findMetadataMessage does.\n *\n * Uses the same pipeline as the live AuthenticateStep.tsx:147-163:\n * 1. Convert token amount → fiat via convertTokenInputToCurrency.\n * 2. Normalize each candidate with the provider's centIsInt flag.\n * 3. Match equality (or negated equality for debit/refund signs).\n *\n * `centIsInt` is sourced from `providerConfigs(chainId)[provider]?.amountInCents`\n * which mirrors how the live caller derives it:\n * `targetProvider?.amountInCents ?? false`\n * where `targetProvider` is resolved via `useProvider` from the same\n * providerConfigs call. Sub-providers (e.g. zelle-chase) do NOT currently\n * set amountInCents, so the parent-provider lookup is sufficient.\n *\n * Matches keep the bank's own order (newest first for the hosted templates).\n */\nfunction matchPayment(\n candidates: CaptureCandidate[],\n request: CaptureRequest,\n): PaymentMatch {\n const currencyAmount = convertTokenInputToCurrency({\n token: request.token,\n amountOutInt: BigInt(request.intent.amount),\n rate: BigInt(request.intent.conversionRate),\n })\n\n // Resolve centIsInt from the provider config — same source as the live UI.\n // The cast is safe: CaptureRequest.chainId must be a supported chain ID.\n const configs = providerConfigs(request.chainId as ChainId)\n const providerConfig = configs[request.provider as keyof typeof configs]\n const centIsInt = (providerConfig as { amountInCents?: boolean } | undefined)?.amountInCents ?? false\n\n const matches = candidates.filter((candidate) => {\n const converted = normalizeAmount(candidate.amount, request.token.decimals, centIsInt)\n return converted === currencyAmount || converted === currencyAmount * -1n\n })\n if (matches.length === 0) return { kind: 'none' }\n if (matches.length === 1) return { kind: 'single', candidate: matches[0]! }\n return { kind: 'several', candidates: matches }\n}\n\n/** The bank-history row as a payment to try. */\nfunction toPaymentToTry(candidate: CaptureCandidate): PaymentToTry {\n return { index: candidate.originalIndex, params: candidate.params }\n}\n\n/**\n * The payments to attest, in order: the amount matches, else the buyer's pick\n * from the whole history. Throws {@link ManualSelectionRequired} when a pick is\n * needed but no resolver was supplied.\n */\nasync function resolvePaymentsToTry({\n match,\n history,\n selectTransaction,\n}: {\n match: PaymentMatch\n history: CaptureCandidate[]\n selectTransaction?: SelectTransaction\n}): Promise<PaymentToTry[]> {\n if (match.kind === 'single') return [toPaymentToTry(match.candidate)]\n if (match.kind === 'several') return match.candidates.map(toPaymentToTry)\n if (!selectTransaction) throw new ManualSelectionRequired(history)\n const pickedIndex = await selectTransaction(history)\n const picked = history.find((row) => row.originalIndex === pickedIndex)\n return [{ index: pickedIndex, params: picked?.params }]\n}\n\n/**\n * Wait for the metadata message the extension posts for `platform`.\n *\n * Races a configurable timeout (default 60s) against the metadata event,\n * mirroring AuthenticateStep.tsx:232-236. The unsubscribe function is always\n * called on resolution, timeout, or error — no listener leaks.\n *\n * @param peer - Injected extension client.\n * @param platform - The platform handed to `authenticate()` (e.g. 'zelle').\n * Echoed back when the extension could not load a template.\n * @param actionType - The capture action type (e.g. 'transfer_venmo'). Its\n * parent is echoed back when the template DID load. Both are accepted; see\n * the guard below.\n * @param timeoutMs - Reject after this long with no matching message.\n */\nfunction awaitMetadata(\n peer: PeerClient,\n platform: string,\n actionType: string,\n timeoutMs: number,\n): Promise<MetadataResponse> {\n return new Promise<MetadataResponse>((resolve, reject) => {\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n\n const unsub = peer.onMetadataMessage((data) => {\n // The extension echoes the platform from one of two places:\n //\n // - Template loaded (success): the TEMPLATE's own `metadata.platform`.\n // - Template fetch failed: there is no template, so it echoes the\n // platform we passed, alongside an errorMessage such as\n // 'Failed to fetch provider template: 404'.\n //\n // With hosted templates the two agree (generic Zelle: 'zelle' both\n // ways). Accepting either still guards against a template whose\n // metadata.platform is the parent rail rather than the platform we\n // passed, so a successful capture is never dropped into a 60s timeout.\n const echoesCapturePlatform = data.platform === platform\n const echoesActionTypeParent = `transfer_${data.platform}` === actionType\n if (echoesCapturePlatform || echoesActionTypeParent) {\n clearTimeout(timeoutId)\n unsub()\n resolve(data)\n }\n })\n\n timeoutId = setTimeout(() => {\n unsub()\n reject(new Error(`Enclave capture timed out for platform \"${platform}\"`))\n }, timeoutMs)\n })\n}\n\nexport function createExtensionEnclaveAdapter(config: ExtensionEnclaveAdapterConfig): CaptureAdapter {\n const timeoutMs = config.timeoutMs ?? 60000\n\n return {\n kind: 'extension-enclave',\n\n async authenticateAndCapture(\n request: CaptureRequest,\n options?: { selectTransaction?: SelectTransaction },\n ): Promise<CaptureHandle> {\n const peer = config.getPeer()\n if (!peer) {\n throw new EnclaveCaptureUnavailable(request.platform)\n }\n\n // Ensure the extension is connected BEFORE authenticate. A cold\n // authenticate() on a fresh page returns errorMessage \"Peer extension\n // connection required\" with no buyerTeeCapture — which trips the\n // enclave-unavailable path. The legacy AuthenticateStep connects first\n // (and onramp-llm.md §3 `ensurePeerReady` requires it); the adapter must\n // too. This is why a warm local tab worked but a fresh prod session failed.\n const status = await peer.checkConnectionStatus()\n if (status !== 'connected') {\n await peer.requestConnection()\n }\n\n // Register the listener BEFORE authenticate fires to avoid a race where\n // the extension posts metadata before the listener is attached.\n const metadataPromise = awaitMetadata(peer, request.platform, request.actionType, timeoutMs)\n await peer.authenticate({\n actionType: request.actionType,\n platform: request.platform,\n captureMode: 'buyerTee',\n // Required for the extension to actually perform the Buyer TEE capture\n // (zkp2p 0.6.0 spec §7). Omitting these is why non-Revolut rails got\n // \"no buyerTee capture\" and dead-ended on the retired Reclaim path.\n attestationServiceUrl: config.attestationServiceUrl ?? DEFAULT_ATTESTATION_SERVICE_URL,\n // The attestation route equals the capture pair for every rail today\n // (generic Zelle included: zelle/transfer_zelle_chase). No template is\n // passed: extension 0.6.7 always fetches it from api.zkp2p.xyz and\n // ignores a page-supplied one, so the capture pair must be hosted.\n attestationActionType: request.attestationActionType ?? request.actionType,\n attestationPlatform: request.attestationPlatform ?? request.platform,\n })\n const response = await metadataPromise\n\n // No buyerTee capture ⇒ the extension didn't produce an encrypted enclave\n // session. Log WHY before failing — the extension may have surfaced an\n // errorMessage, or returned metadata-only (no capture) for this platform.\n if (!response.buyerTeeCapture) {\n const errorMessage = (response as { errorMessage?: unknown }).errorMessage\n console.warn(\n '[extensionEnclave] no buyerTeeCapture for',\n request.platform,\n '— response keys:', Object.keys(response),\n '| errorMessage:', errorMessage,\n '| metadata rows:', Array.isArray(response.metadata) ? response.metadata.length : '(none)',\n '| buyerTeeCapture:', response.buyerTeeCapture,\n )\n // Two distinct failures share the \"no capture\" shape:\n // - The extension surfaced an `errorMessage` ⇒ it ATTEMPTED the capture\n // but something failed (most often: it could not reach the\n // attestation enclave — a buyer-side network / firewall / ad-blocker\n // issue). Surface that as a retryable EnclaveCaptureFailed carrying\n // the raw reason, so the user sees an actionable message instead of\n // \"provider unsupported\".\n // - No `errorMessage` ⇒ the provider produced no capture path at all\n // (non-migrated). That stays EnclaveCaptureUnavailable, the\n // Reclaim-fallback signal.\n if (typeof errorMessage === 'string' && errorMessage.trim().length > 0) {\n throw new EnclaveCaptureFailed(request.platform, errorMessage)\n }\n throw new EnclaveCaptureUnavailable(request.platform)\n }\n\n const material: EnclaveMaterial = {\n encryptedSessionMaterial: response.buyerTeeCapture.encryptedSessionMaterial,\n payments: await resolvePaymentsToTry({\n match: matchPayment(response.metadata, request),\n history: response.metadata,\n selectTransaction: options?.selectTransaction,\n }),\n }\n return { kind: 'extension-enclave', material }\n },\n\n async attest(handle: CaptureHandle, request: CaptureRequest): Promise<VerifiedAttestation> {\n if (handle.kind !== 'extension-enclave') {\n throw new Error(\n `ExtensionEnclaveAdapter.attest received a handle of kind \"${handle.kind}\"; expected \"extension-enclave\".`,\n )\n }\n const material = handle.material as EnclaveMaterial\n // Try each candidate payment in order. A payment that already settled an\n // order is refused by the gate (PaymentAlreadyUsedError) and skipped;\n // any other failure stops here and surfaces as-is.\n let alreadyUsed: PaymentAlreadyUsedError | undefined\n for (const payment of material.payments) {\n try {\n const attestation = await attestPayment({ payment, material, request })\n // verificationData = enclave signer (SubmitStep.tsx:473-474).\n return { attestation, verificationData: attestation.signer as Hex }\n } catch (error) {\n if (!(error instanceof PaymentAlreadyUsedError)) throw error\n alreadyUsed = error\n }\n }\n throw alreadyUsed ?? new Error('No bank payment was captured for this order.')\n },\n }\n\n /** Attest one candidate payment through the gate. */\n function attestPayment({\n payment,\n material,\n request,\n }: {\n payment: PaymentToTry\n material: EnclaveMaterial\n request: CaptureRequest\n }) {\n // The selected row's params ARE the attestation params (spec §5,\n // buildBuyerTeeProof) and MUST be forwarded — dropping them is why the\n // enclave 400s with \"Invalid <provider> query params\". The metadata\n // `index` is then merged in ONLY for providers that expect it (Venmo /\n // Cash App / Revolut / Zelle) and omitted for the rest (PayPal / Wise /\n // Monzo / Chime). See providerRequiresMetadataIndex.\n const params = {\n ...(payment.params ?? {}),\n ...(providerRequiresMetadataIndex(request.provider as ProviderKey | SubProviderKey)\n ? { index: payment.index }\n : {}),\n }\n return requestBuyerTeeAttestation({\n attestationBaseUrl: config.attestationBaseUrl,\n platform: request.attestationPlatform ?? request.platform,\n actionType: request.attestationActionType ?? request.actionType,\n encryptedSessionMaterial: material.encryptedSessionMaterial,\n params,\n verifyingContract: request.verifyingContract,\n intent: request.intent,\n })\n }\n}\n\n// Re-narrow export so consumers can validate proof shapes if they bypass the\n// adapter (kept for parity with the live SubmitStep check, SubmitStep.tsx:206).\nexport { asBuyerTeeProof }\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ZKTLSContext, ZKTLSProvider, useZKTLSContext } from './chunk-T42E7EM7.js';
|
|
2
2
|
export { PROOF_ENCODING, SeriesProgress, buildBuyerTeeNotaryRequest, buildOnrampQuery, byteArrayToHexString, encodeProof, encodeProofAsBytes, encodeProofWithPaymentMethod, encodeTwoProofs, extensionUrl, findMetadataMessage, parseExtensionProof, useZKTLS } from './chunk-TAS2MB4S.js';
|
|
3
|
-
export { createExtensionEnclaveAdapter } from './chunk-
|
|
3
|
+
export { createExtensionEnclaveAdapter } from './chunk-AMJXMSDV.js';
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
|
5
5
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@provex/extension-react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0-rc.20261018212258.bdc024c",
|
|
4
4
|
"description": "React hooks and context for the PeerAuth browser extension (zkTLS proof generation)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"test": "vitest run"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@provex/proving": "1.
|
|
57
|
-
"@provex/utils": "1.
|
|
56
|
+
"@provex/proving": "1.11.0-rc.20261018212258.bdc024c",
|
|
57
|
+
"@provex/utils": "1.11.0-rc.20261018212258.bdc024c",
|
|
58
58
|
"@zkp2p/providers": "^7.9.2"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters/providerTemplates.ts","../src/adapters/extensionEnclave.ts"],"names":[],"mappings":"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","file":"chunk-PTUIJQ4H.js","sourcesContent":["/**\n * @fileoverview Provider templates supplied inline to the PeerAuth extension.\n *\n * The extension resolves a template before it can open the buyer's bank:\n *\n * function ut(e){ return `${base}${e.platform}/${e.actionType}.json` }\n * async function dt(e){\n * if (e.providerConfig) return e.providerConfig // <- inline wins\n * const r = await fetch(ut(e))\n * if (!r.ok) throw new Error(`Failed to fetch provider template: ${r.status}`)\n * }\n *\n * `base` is hardcoded to https://api.zkp2p.xyz/providers/ inside the extension,\n * and zkp2p does NOT host the Zelle bank templates there — every\n * `{bank}/transfer_zelle.json` 404s while venmo/cashapp/revolut/wise/paypal/\n * monzo return 200. The template carries `authLink`, the URL the extension opens,\n * so a 404 means no bank tab at all: the capture dies ~1ms in with\n * \"Failed to fetch provider template: 404\".\n *\n * The templates ARE published on npm (`@zkp2p/providers`), so we bundle the\n * unhosted ones and hand them over inline, which bypasses the fetch entirely.\n *\n * Scope is deliberate: ONLY the rails the API fails to serve. Hosted rails keep\n * fetching so upstream template fixes (bank markup changes) reach users without\n * a release from us. Supplying `providerConfig` also flips the extension's\n * `requiresMetadataApproval` on (`lt(e){return!!e.providerConfig}`), so\n * narrowing the blast radius keeps that UX change off rails that work today.\n */\n\nimport chaseTransferZelle from '@zkp2p/providers/chase/transfer_zelle.json'\nimport bankofamericaTransferZelle from '@zkp2p/providers/bankofamerica/transfer_zelle.json'\nimport citiTransferZelle from '@zkp2p/providers/citi/transfer_zelle.json'\n\n/**\n * A provider template, passed through to the extension verbatim. The shape is\n * zkp2p's, not ours — we never construct or mutate one, so it stays opaque\n * rather than being modelled field by field (that would rot on every upstream\n * template change).\n */\nexport type ProviderTemplate = Record<string, unknown>\n\n/**\n * Templates keyed by the capture pair the extension would otherwise fetch:\n * `${platform}/${actionType}`. Keys mirror the npm package's own directory\n * layout, so the mapping is verifiable by inspection.\n *\n * Zelle sub-provider `zelle-bofa` maps to platform `bankofamerica`, per\n * `subProviderConfigs[...].extensionPlatform` in @provex/utils.\n */\nconst TEMPLATES_BY_CAPTURE_KEY: Readonly<Record<string, ProviderTemplate>> = Object.freeze({\n 'chase/transfer_zelle': chaseTransferZelle as ProviderTemplate,\n 'bankofamerica/transfer_zelle': bankofamericaTransferZelle as ProviderTemplate,\n 'citi/transfer_zelle': citiTransferZelle as ProviderTemplate,\n})\n\n/**\n * Resolve the inline template for a capture pair, if we bundle one.\n *\n * @param platform - Extension platform slug, e.g. `chase`.\n * @param actionType - Capture action type, e.g. `transfer_zelle`.\n * @returns The template to pass as `providerConfig`, or undefined to let the\n * extension fetch it from the hosted registry (the path for every rail zkp2p\n * actually serves).\n */\nexport function providerTemplateFor(\n platform: string,\n actionType: string,\n): ProviderTemplate | undefined {\n return TEMPLATES_BY_CAPTURE_KEY[`${platform}/${actionType}`]\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 * the injected-PeerClient seam is preserved exactly.\n */\n\nimport { parseUnits } from 'viem'\nimport type { Hex } from 'viem'\nimport { providerConfigs, providerRequiresMetadataIndex } from '@provex/utils/payment'\nimport type { ProviderKey, SubProviderKey } from '@provex/utils/payment'\nimport type { ChainId } from '@provex/utils/chain'\nimport { convertTokenInputToCurrency } from '@provex/utils/conversionRates'\nimport { providerTemplateFor } from './providerTemplates'\nimport {\n EnclaveCaptureUnavailable,\n EnclaveCaptureFailed,\n ManualSelectionRequired,\n asBuyerTeeProof,\n requestBuyerTeeAttestation,\n type CaptureAdapter,\n type CaptureCandidate,\n type CaptureHandle,\n type CaptureRequest,\n type SelectTransaction,\n type VerifiedAttestation,\n} from '@provex/proving'\n\n/** Minimal shape of the metadata response the extension posts back. */\ninterface MetadataResponse {\n platform: string\n metadata: CaptureCandidate[]\n buyerTeeCapture?: { encryptedSessionMaterial: string }\n}\n\n/**\n * zkp2p enclave used for Buyer TEE capture when the caller doesn't override\n * `ExtensionEnclaveAdapterConfig.attestationServiceUrl`. Matches the gate's\n * `TEE_ATTESTATION_URL` — the enclave the gate forwards to for the re-sign.\n */\nexport const DEFAULT_ATTESTATION_SERVICE_URL = 'https://attestation-service.zkp2p.xyz'\n\n/** Minimal slice of `window.peer` this adapter depends on. */\nexport interface PeerClient {\n authenticate(inputs: {\n actionType: string\n platform: string\n captureMode: 'buyerTee'\n /**\n * Enclave the extension establishes the Buyer TEE session with and encrypts\n * the session material toward. Required by the zkp2p 0.6.0 spec\n * (onramp-llm.md §7) — without it the extension performs no Buyer TEE\n * capture and falls back to the retired Reclaim path.\n */\n attestationServiceUrl?: string\n /** Attestation action type; defaults to `actionType` when omitted (spec §2). */\n attestationActionType?: string\n /**\n * Attestation platform, when it differs from `platform`. Required for\n * generic Zelle, whose attestation route is `zelle` while capture runs as\n * the bank (`chase`).\n */\n attestationPlatform?: string\n /**\n * Provider template supplied inline. When present the extension skips its\n * own template fetch entirely — the only way to drive a rail zkp2p does not\n * host. See ./providerTemplates.\n */\n providerConfig?: Record<string, unknown>\n }): Promise<void>\n onMetadataMessage(fn: (data: MetadataResponse) => void): () => void\n /** Current extension connection status; `'connected'` means ready. */\n checkConnectionStatus(): Promise<string>\n /** Prompt the user to connect the extension. Resolves true when approved. */\n requestConnection(): Promise<boolean>\n}\n\nexport interface ExtensionEnclaveAdapterConfig {\n /** Resolve the live peer client (e.g. `() => window.peer`). */\n getPeer: () => PeerClient | undefined\n /** Gate/attestation origin handed to requestBuyerTeeAttestation (the re-sign hop). */\n attestationBaseUrl: string\n /**\n * Enclave URL passed to the extension's `authenticate` so it performs the\n * Buyer TEE capture (zkp2p 0.6.0 spec §7). This is the zkp2p enclave the\n * session material is encrypted toward — the SAME enclave the gate forwards\n * to for the re-sign — NOT our gate's re-sign origin (`attestationBaseUrl`).\n * Defaults to {@link DEFAULT_ATTESTATION_SERVICE_URL} when omitted; set it to\n * override the enclave for local testing.\n */\n attestationServiceUrl?: string\n /**\n * Milliseconds before awaitMetadata rejects if the extension never posts a\n * matching metadata message. Mirrors the 60-second timeout in\n * AuthenticateStep.tsx:232-236. Defaults to 60000.\n */\n timeoutMs?: number\n}\n\ninterface EnclaveMaterial {\n encryptedSessionMaterial: string\n matchedIndex: number\n /** Selected row's provider attestation params (spec §4) — forwarded to the enclave. */\n matchedParams?: Record<string, string | number | boolean>\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 the extension posts for `platform`.\n *\n * Races a configurable timeout (default 60s) against the metadata event,\n * mirroring AuthenticateStep.tsx:232-236. The unsubscribe function is always\n * called on resolution, timeout, or error — no listener leaks.\n *\n * @param peer - Injected extension client.\n * @param platform - The platform handed to `authenticate()` (e.g. 'chase').\n * Echoed back when the extension could not load a template.\n * @param actionType - The capture action type (e.g. 'transfer_zelle'). Its\n * parent is echoed back when the template DID load. Both are accepted; see\n * the guard below.\n * @param timeoutMs - Reject after this long with no matching message.\n */\nfunction awaitMetadata(\n peer: PeerClient,\n platform: string,\n actionType: string,\n timeoutMs: number,\n): Promise<MetadataResponse> {\n return new Promise<MetadataResponse>((resolve, reject) => {\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n\n const unsub = peer.onMetadataMessage((data) => {\n // The extension echoes DIFFERENT platform values depending on how far it\n // got, so correlating on either one alone drops real messages:\n //\n // - Template loaded (success): it echoes the TEMPLATE's own\n // `metadata.platform`. For chase/transfer_zelle.json that is 'zelle'\n // — the generic rail, not the bank. Observed with a live capture:\n // `{ platform: 'zelle', hasBuyerTeeCapture: true, rowCount: 50 }`.\n // - Template fetch failed: there is no template to read, so it echoes\n // back the platform we passed — 'chase' — alongside the errorMessage\n // explaining the failure. Observed live:\n // `{ platform: 'chase', errorMessage: 'Failed to fetch provider template: 404' }`.\n //\n // Matching only the echoed-template platform loses every failure reason\n // (60s timeout instead of the real cause). Matching only the passed\n // platform loses every SUCCESSFUL Zelle capture. Accept both.\n //\n // For providers where platform === parent (venmo, cashapp, …) the two\n // predicates coincide, so this is a strict superset of the old behavior.\n const echoesCapturePlatform = data.platform === platform\n const echoesActionTypeParent = `transfer_${data.platform}` === actionType\n if (echoesCapturePlatform || echoesActionTypeParent) {\n clearTimeout(timeoutId)\n unsub()\n resolve(data)\n }\n })\n\n timeoutId = setTimeout(() => {\n unsub()\n reject(new Error(`Enclave capture timed out for platform \"${platform}\"`))\n }, timeoutMs)\n })\n}\n\nexport function createExtensionEnclaveAdapter(config: ExtensionEnclaveAdapterConfig): CaptureAdapter {\n const timeoutMs = config.timeoutMs ?? 60000\n\n return {\n kind: 'extension-enclave',\n\n async authenticateAndCapture(\n request: CaptureRequest,\n options?: { selectTransaction?: SelectTransaction },\n ): Promise<CaptureHandle> {\n const peer = config.getPeer()\n if (!peer) {\n throw new EnclaveCaptureUnavailable(request.platform)\n }\n\n // Ensure the extension is connected BEFORE authenticate. A cold\n // authenticate() on a fresh page returns errorMessage \"Peer extension\n // connection required\" with no buyerTeeCapture — which trips the\n // enclave-unavailable path. The legacy AuthenticateStep connects first\n // (and onramp-llm.md §3 `ensurePeerReady` requires it); the adapter must\n // too. This is why a warm local tab worked but a fresh prod session failed.\n const status = await peer.checkConnectionStatus()\n if (status !== 'connected') {\n await peer.requestConnection()\n }\n\n // Register the listener BEFORE authenticate fires to avoid a race where\n // the extension posts metadata before the listener is attached.\n const metadataPromise = awaitMetadata(peer, request.platform, request.actionType, timeoutMs)\n await peer.authenticate({\n actionType: request.actionType,\n platform: request.platform,\n captureMode: 'buyerTee',\n // Required for the extension to actually perform the Buyer TEE capture\n // (zkp2p 0.6.0 spec §7). Omitting these is why non-Revolut rails got\n // \"no buyerTee capture\" and dead-ended on the retired Reclaim path.\n attestationServiceUrl: config.attestationServiceUrl ?? DEFAULT_ATTESTATION_SERVICE_URL,\n // Attestation namespace — differs from the capture pair for generic\n // Zelle (zelle/transfer_zelle_chase vs chase/transfer_zelle).\n attestationActionType: request.attestationActionType ?? request.actionType,\n attestationPlatform: request.attestationPlatform ?? request.platform,\n // Bundled template for rails zkp2p does not host. Undefined for hosted\n // rails, which keeps the extension fetching (and auto-updating) them.\n providerConfig: providerTemplateFor(request.platform, request.actionType),\n })\n const response = await metadataPromise\n\n // No buyerTee capture ⇒ the extension didn't produce an encrypted enclave\n // session. Log WHY before failing — the extension may have surfaced an\n // errorMessage, or returned metadata-only (no capture) for this platform.\n if (!response.buyerTeeCapture) {\n const errorMessage = (response as { errorMessage?: unknown }).errorMessage\n console.warn(\n '[extensionEnclave] no buyerTeeCapture for',\n request.platform,\n '— response keys:', Object.keys(response),\n '| errorMessage:', errorMessage,\n '| metadata rows:', Array.isArray(response.metadata) ? response.metadata.length : '(none)',\n '| buyerTeeCapture:', response.buyerTeeCapture,\n )\n // Two distinct failures share the \"no capture\" shape:\n // - The extension surfaced an `errorMessage` ⇒ it ATTEMPTED the capture\n // but something failed (most often: it could not reach the\n // attestation enclave — a buyer-side network / firewall / ad-blocker\n // issue). Surface that as a retryable EnclaveCaptureFailed carrying\n // the raw reason, so the user sees an actionable message instead of\n // \"provider unsupported\".\n // - No `errorMessage` ⇒ the provider produced no capture path at all\n // (non-migrated). That stays EnclaveCaptureUnavailable, the\n // Reclaim-fallback signal.\n if (typeof errorMessage === 'string' && errorMessage.trim().length > 0) {\n throw new EnclaveCaptureFailed(request.platform, errorMessage)\n }\n throw new EnclaveCaptureUnavailable(request.platform)\n }\n\n const 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 // The SELECTED row's params ARE the attestation params (spec §4) — carry\n // them through so attest() can forward them. Look up by originalIndex so\n // this works for both the auto-match and manual-selection branches.\n const matchedRow = response.metadata.find((row) => row.originalIndex === matchedIndex)\n const material: EnclaveMaterial = {\n encryptedSessionMaterial: response.buyerTeeCapture.encryptedSessionMaterial,\n matchedIndex,\n matchedParams: matchedRow?.params,\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 // The selected row's params ARE the attestation params (spec §5,\n // buildBuyerTeeProof) and MUST be forwarded — dropping them is why the\n // enclave 400s with \"Invalid <provider> query params\". The metadata\n // `index` is then merged in ONLY for providers that expect it (Venmo /\n // Cash App / Revolut / Zelle) and omitted for the rest (PayPal / Wise /\n // Monzo / Chime). See providerRequiresMetadataIndex.\n const params = {\n ...(material.matchedParams ?? {}),\n ...(providerRequiresMetadataIndex(request.provider as ProviderKey | SubProviderKey)\n ? { index: material.matchedIndex }\n : {}),\n }\n const attestation = await requestBuyerTeeAttestation({\n attestationBaseUrl: config.attestationBaseUrl,\n // The ATTESTATION namespace, which differs from the capture namespace\n // for generic Zelle: capture ran as chase/transfer_zelle, but the\n // enclave's verify route demands zelle/transfer_zelle_chase. Sending\n // the capture pair here is why a successful Zelle capture would still\n // fail at attest. Providers where the namespaces coincide leave the\n // overrides unset and fall back to the capture values.\n platform: request.attestationPlatform ?? request.platform,\n actionType: request.attestationActionType ?? request.actionType,\n encryptedSessionMaterial: material.encryptedSessionMaterial,\n params,\n verifyingContract: request.verifyingContract,\n intent: request.intent,\n })\n // 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"]}
|