@provex/react 1.11.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { createContext, useMemo, useContext, useCallback, useState, useRef, useEffect } from 'react';
1
+ import { createContext, useMemo, useContext, useCallback, useState, useEffect, useRef } from 'react';
2
2
  import { QueryClient, QueryClientProvider, useQueryClient, useQuery } from '@tanstack/react-query';
3
3
  import { createPublicClient, http, encodeAbiParameters, parseAbiParameters, zeroAddress, encodeFunctionData, decodeEventLog } from 'viem';
4
4
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
@@ -13,9 +13,9 @@ import { normalizeFeePrecision } from '@provex/utils/fees';
13
13
  import { OrchestratorAbi, escrowAbiByVersion, V3EscrowAbi, PaymentVerifierRegistryAbi, UnifiedPaymentVerifierAbi, NullifierRegistryAbi, V4OrchestratorAbi } from '@provex/abis';
14
14
  import { publicClient, getDefaultToken } from '@provex/utils/tokens';
15
15
  import { chainIdToChain } from '@provex/utils/chain';
16
- import { base } from 'viem/chains';
16
+ import { pulsechain } from 'viem/chains';
17
17
  import { DEFAULT_REPUTATION, getTierFromVolume, TIER_ORDER, TAKER_TIERS, getVolumeRangeDisplay, getCooldownHours, getEffectiveCap, isOverOrderCap, UNLIMITED_CAP_BASE_UNITS, getEffectiveCapDisplayUsd } from '@provex/utils/reputation';
18
- import { buildCaptureRequest, useManualTransactionSelect, useProving } from '@provex/proving';
18
+ import { useManualTransactionSelect, useProving, buildCaptureRequest } from '@provex/proving';
19
19
  import { createExtensionEnclaveAdapter } from '@provex/extension-react';
20
20
 
21
21
  // src/lib/api.ts
@@ -43,6 +43,15 @@ function createApiClient(apiUrl) {
43
43
  })
44
44
  };
45
45
  }
46
+
47
+ // src/lib/clock.ts
48
+ var systemClock = Object.freeze({
49
+ now: () => Date.now(),
50
+ setTimeout: (callback, delayMs) => globalThis.setTimeout(callback, delayMs),
51
+ clearTimeout: (handle) => globalThis.clearTimeout(handle),
52
+ setInterval: (callback, intervalMs) => globalThis.setInterval(callback, intervalMs),
53
+ clearInterval: (handle) => globalThis.clearInterval(handle)
54
+ });
46
55
  var ProvexContext = createContext(null);
47
56
  var defaultQueryClient = new QueryClient({
48
57
  defaultOptions: {
@@ -59,22 +68,23 @@ function ProvexProvider({
59
68
  publicClient: publicClient2,
60
69
  publicClients,
61
70
  wallet,
71
+ clock = systemClock,
62
72
  children
63
73
  }) {
64
74
  const resolvedQueryClient = queryClient ?? defaultQueryClient;
65
75
  const contextValue = useMemo(() => {
66
76
  const apiClient = createApiClient(config.apiUrl);
67
77
  const chainId = config.chain.id;
68
- const base2 = publicClients ?? {};
69
- const resolved = { ...base2 };
78
+ const base = publicClients ?? {};
79
+ const resolved = { ...base };
70
80
  if (publicClient2 && !resolved[chainId]) {
71
81
  resolved[chainId] = publicClient2;
72
82
  }
73
83
  if (!resolved[chainId]) {
74
84
  resolved[chainId] = createPublicClient({ chain: config.chain, transport: http() });
75
85
  }
76
- return { config, indexer, apiClient, publicClients: resolved, wallet };
77
- }, [config, indexer, publicClient2, publicClients, wallet]);
86
+ return { config, indexer, apiClient, publicClients: resolved, wallet, clock };
87
+ }, [config, indexer, publicClient2, publicClients, wallet, clock]);
78
88
  return /* @__PURE__ */ jsx(ProvexContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(QueryClientProvider, { client: resolvedQueryClient, children }) });
79
89
  }
80
90
  function useProvex() {
@@ -104,6 +114,65 @@ function useProvexWallet() {
104
114
  function useOptionalProvexWallet() {
105
115
  return useOptionalProvex()?.wallet;
106
116
  }
117
+ function useClock() {
118
+ return useOptionalProvex()?.clock ?? systemClock;
119
+ }
120
+
121
+ // src/lib/updates.ts
122
+ var POLL_INTERVAL_MS = 1e3;
123
+ var SLOW_SYNC_THRESHOLD_MS = 15e3;
124
+ var sleep = (clock, delayMs) => new Promise((resolve) => {
125
+ clock.setTimeout(resolve, delayMs);
126
+ });
127
+ var checkMutation = async ({
128
+ queryDeposit,
129
+ depositIdHex,
130
+ status,
131
+ onSuccess,
132
+ onSlowSync,
133
+ slowSyncThresholdMs = SLOW_SYNC_THRESHOLD_MS,
134
+ clock = systemClock
135
+ }) => {
136
+ const startTime = clock.now();
137
+ let slowSyncFired = false;
138
+ while (true) {
139
+ const result = await queryDeposit(depositIdHex);
140
+ if (result?.status === status) {
141
+ break;
142
+ }
143
+ if (!slowSyncFired && onSlowSync && clock.now() - startTime > slowSyncThresholdMs) {
144
+ slowSyncFired = true;
145
+ onSlowSync();
146
+ }
147
+ await sleep(clock, POLL_INTERVAL_MS);
148
+ }
149
+ clock.setTimeout(() => {
150
+ onSuccess?.();
151
+ }, POLL_INTERVAL_MS);
152
+ };
153
+ var checkTransactionIndexed = async ({
154
+ queryTransaction,
155
+ hash,
156
+ onSuccess,
157
+ onSlowSync,
158
+ slowSyncThresholdMs = SLOW_SYNC_THRESHOLD_MS,
159
+ clock = systemClock
160
+ }) => {
161
+ const startTime = clock.now();
162
+ let slowSyncFired = false;
163
+ while (true) {
164
+ const result = await queryTransaction(hash);
165
+ if (result) {
166
+ break;
167
+ }
168
+ if (!slowSyncFired && onSlowSync && clock.now() - startTime > slowSyncThresholdMs) {
169
+ slowSyncFired = true;
170
+ onSlowSync();
171
+ }
172
+ await sleep(clock, POLL_INTERVAL_MS);
173
+ }
174
+ onSuccess?.();
175
+ };
107
176
  var DEFAULT_REFETCH_INTERVAL_MS = 5e3;
108
177
  function getRate({
109
178
  deposit,
@@ -253,57 +322,6 @@ var getTransactionGasInputs = async (chainId) => {
253
322
  };
254
323
  };
255
324
 
256
- // src/lib/updates.ts
257
- var POLL_INTERVAL_MS = 1e3;
258
- var SLOW_SYNC_THRESHOLD_MS = 15e3;
259
- var checkMutation = async ({
260
- queryDeposit,
261
- depositIdHex,
262
- status,
263
- onSuccess,
264
- onSlowSync,
265
- slowSyncThresholdMs = SLOW_SYNC_THRESHOLD_MS
266
- }) => {
267
- const startTime = Date.now();
268
- let slowSyncFired = false;
269
- while (true) {
270
- const result = await queryDeposit(depositIdHex);
271
- if (result?.status === status) {
272
- break;
273
- }
274
- if (!slowSyncFired && onSlowSync && Date.now() - startTime > slowSyncThresholdMs) {
275
- slowSyncFired = true;
276
- onSlowSync();
277
- }
278
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
279
- }
280
- setTimeout(() => {
281
- onSuccess?.();
282
- }, POLL_INTERVAL_MS);
283
- };
284
- var checkTransactionIndexed = async ({
285
- queryTransaction,
286
- hash,
287
- onSuccess,
288
- onSlowSync,
289
- slowSyncThresholdMs = SLOW_SYNC_THRESHOLD_MS
290
- }) => {
291
- const startTime = Date.now();
292
- let slowSyncFired = false;
293
- while (true) {
294
- const result = await queryTransaction(hash);
295
- if (result) {
296
- break;
297
- }
298
- if (!slowSyncFired && onSlowSync && Date.now() - startTime > slowSyncThresholdMs) {
299
- slowSyncFired = true;
300
- onSlowSync();
301
- }
302
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
303
- }
304
- onSuccess?.();
305
- };
306
-
307
325
  // src/lib/errors.ts
308
326
  var ERROR_PATTERNS = [
309
327
  // Wallet / signer rejections
@@ -658,7 +676,7 @@ var ProveXClient = class {
658
676
  * Handles all encoding internally:
659
677
  * 1. Registers payee details with the API (so buyers can discover them)
660
678
  * 2. Hashes provider keys and currency codes to bytes32
661
- * 3. Expands sub-providers (e.g., 'zelle' → zelle-chase, zelle-bofa, zelle-citi)
679
+ * 3. Collapses bank-level keys to their rail (e.g., 'zelle-chase' → 'zelle'); Zelle is one rail
662
680
  * 4. Encodes gating service witness signers
663
681
  * 5. Submits the on-chain transaction
664
682
  *
@@ -948,43 +966,6 @@ var ProveXClient = class {
948
966
  });
949
967
  return res.responseObject ?? null;
950
968
  }
951
- /**
952
- * Submit zkTLS proofs and get a signed attestation for fulfillIntent.
953
- * This is the proof verification step — after the buyer makes payment and
954
- * generates proofs via the browser extension.
955
- */
956
- async getAttestation(params) {
957
- const endpoint = `/api/v0/attestation/verify/${params.platform}/${params.actionType}`;
958
- const proof = params.proofs.length === 1 ? params.proofs[0] : params.proofs;
959
- const res = await fetch(`${this.apiUrl}${endpoint}`, {
960
- method: "POST",
961
- headers: { "Content-Type": "application/json" },
962
- body: JSON.stringify({
963
- proofType: "reclaim",
964
- proof: JSON.stringify(proof),
965
- chainId: params.chainId,
966
- verifyingContract: params.verifyingContract,
967
- intent: params.intent
968
- })
969
- });
970
- if (!res.ok) {
971
- let errorMessage = `Attestation error: ${res.status}`;
972
- try {
973
- const errorJson = await res.json();
974
- if (errorJson.details?.message) errorMessage = errorJson.details.message;
975
- else if (errorJson.message) errorMessage = errorJson.message;
976
- else if (errorJson.error) errorMessage = errorJson.error;
977
- } catch {
978
- }
979
- return {
980
- success: false,
981
- message: errorMessage,
982
- statusCode: res.status,
983
- responseObject: {}
984
- };
985
- }
986
- return await res.json();
987
- }
988
969
  /** Make a POST request to the API. */
989
970
  async apiPost(path, body) {
990
971
  const res = await fetch(`${this.apiUrl}${path}`, {
@@ -1082,6 +1063,11 @@ var ProveXClient = class {
1082
1063
  function createProveXClient(config) {
1083
1064
  return new ProveXClient(config);
1084
1065
  }
1066
+ var DEFAULT_SIGNAL_INTENT_MESSAGES = {
1067
+ clientOrDepositUnavailable: "Client or deposit not available",
1068
+ noEscrowAddress: "No escrow address found",
1069
+ success: "Intent signaled successfully"
1070
+ };
1085
1071
  var ERROR_PATTERNS2 = [
1086
1072
  { pattern: /user rejected|user denied|rejected the request|user cancelled|user canceled/i, message: "Transaction was cancelled." },
1087
1073
  { pattern: /InsufficientDepositLiquidity|0xc3df48f3/i, message: "This deposit no longer has enough available liquidity. Please try a smaller amount or a different deposit." },
@@ -1096,28 +1082,48 @@ function getUserFriendlyErrorMessage2(error) {
1096
1082
  }
1097
1083
  return "Something went wrong. Please try again.";
1098
1084
  }
1085
+ function isClientRejection(error) {
1086
+ return error instanceof ProveXError && error.isRejection;
1087
+ }
1088
+ var LOADING_STATUSES = /* @__PURE__ */ new Set([
1089
+ "loading_payee",
1090
+ "loading_intent",
1091
+ "prompt_wallet_confirm",
1092
+ "writing_intent"
1093
+ ]);
1099
1094
  function useSignalIntent({
1100
1095
  wallet,
1096
+ chainId,
1097
+ indexer,
1098
+ apiUrl,
1101
1099
  onSuccess,
1102
1100
  onFailure,
1103
1101
  deposit,
1104
- refetchDeposits
1102
+ refetchDeposits,
1103
+ beginTransaction,
1104
+ isRejection = isClientRejection,
1105
+ formatError = getUserFriendlyErrorMessage2,
1106
+ messages
1105
1107
  }) {
1106
1108
  const provex = useOptionalProvex();
1107
1109
  const [status, setStatus] = useState("input");
1108
1110
  const [message, setMessage] = useState(null);
1111
+ const copy = { ...DEFAULT_SIGNAL_INTENT_MESSAGES, ...messages };
1112
+ const chain = (chainId === void 0 ? void 0 : chainIdToChain.get(chainId)) ?? provex?.config.chain;
1113
+ const resolvedIndexer = indexer ?? provex?.indexer;
1114
+ const resolvedApiUrl = apiUrl ?? provex?.config.apiUrl;
1115
+ const escrow = deposit?.escrow ?? void 0;
1109
1116
  const client = useMemo(() => {
1110
- if (!provex) return null;
1117
+ if (!chain || !wallet || !resolvedIndexer || resolvedApiUrl === void 0) return null;
1111
1118
  return createProveXClient({
1112
- chain: provex.config.chain,
1113
- apiUrl: provex.config.apiUrl,
1119
+ chain,
1114
1120
  wallet,
1115
- indexer: provex.indexer
1121
+ indexer: resolvedIndexer,
1122
+ apiUrl: resolvedApiUrl,
1123
+ escrowAddress: escrow
1116
1124
  });
1117
- }, [provex, wallet]);
1118
- const isLoading = useMemo(() => {
1119
- return status === "loading_payee" || status === "loading_intent" || status === "prompt_wallet_confirm" || status === "writing_intent";
1120
- }, [status]);
1125
+ }, [chain, wallet, resolvedIndexer, resolvedApiUrl, escrow]);
1126
+ const isLoading = LOADING_STATUSES.has(status);
1121
1127
  const startOrder = useCallback(async ({
1122
1128
  paymentMethod,
1123
1129
  depositId,
@@ -1129,42 +1135,48 @@ function useSignalIntent({
1129
1135
  }) => {
1130
1136
  if (!client || !deposit) {
1131
1137
  setStatus("error");
1132
- setMessage("Client or deposit not available");
1138
+ setMessage(copy.clientOrDepositUnavailable);
1133
1139
  return;
1134
1140
  }
1135
- const escrow = deposit.escrow;
1136
- if (!escrow) {
1141
+ const depositEscrow = deposit.escrow;
1142
+ if (!depositEscrow) {
1137
1143
  setStatus("error");
1138
- setMessage("No escrow address found");
1144
+ setMessage(copy.noEscrowAddress);
1139
1145
  return;
1140
1146
  }
1147
+ const transaction = beginTransaction ? await beginTransaction() : void 0;
1148
+ if (transaction === null) return;
1141
1149
  setStatus("loading_intent");
1142
1150
  try {
1143
1151
  const result = await client.signalIntent({
1144
- deposit: { escrow, localId: deposit.localId ?? depositId },
1152
+ deposit: { escrow: depositEscrow, localId: deposit.localId ?? depositId },
1145
1153
  paymentMethod,
1146
1154
  tokenAmount,
1147
1155
  toAddress,
1148
1156
  fiatCurrencyCode,
1149
1157
  conversionRate,
1150
- subProvider
1158
+ subProvider,
1159
+ onAwaitingSignature: () => transaction?.awaitingSignature(),
1160
+ onTransactionHash: (hash) => transaction?.attach(hash)
1151
1161
  });
1152
1162
  setStatus("success");
1153
- setMessage("Intent signaled successfully");
1163
+ setMessage(copy.success);
1154
1164
  onSuccess(result.intentHash);
1155
1165
  } catch (error) {
1156
- if (error instanceof ProveXError && error.isRejection) {
1166
+ if (isRejection(error)) {
1167
+ transaction?.failed(error);
1157
1168
  setStatus("input");
1158
1169
  setMessage(null);
1159
1170
  return;
1160
1171
  }
1161
1172
  console.error("Failed to signal intent", error);
1173
+ transaction?.failed(error);
1162
1174
  setStatus("error");
1163
- setMessage(getUserFriendlyErrorMessage2(error));
1175
+ setMessage(formatError(error));
1164
1176
  refetchDeposits?.();
1165
1177
  onFailure?.();
1166
1178
  }
1167
- }, [client, deposit, onSuccess, onFailure, refetchDeposits]);
1179
+ }, [client, deposit, beginTransaction, isRejection, formatError, onSuccess, onFailure, refetchDeposits, copy.clientOrDepositUnavailable, copy.noEscrowAddress, copy.success]);
1168
1180
  return {
1169
1181
  startOrder,
1170
1182
  status,
@@ -1172,74 +1184,116 @@ function useSignalIntent({
1172
1184
  isLoading
1173
1185
  };
1174
1186
  }
1175
- function useReputation({
1176
- address,
1177
- chainId
1187
+ function filteringTierFor(options) {
1188
+ if (options.vipStatus === "unknown") return null;
1189
+ if (options.vipStatus === "vip") return tiers.PRESIDENT;
1190
+ return options.tier;
1191
+ }
1192
+ function computeUserReputation({
1193
+ stats,
1194
+ pulseTimestamp,
1195
+ now = () => /* @__PURE__ */ new Date()
1178
1196
  }) {
1179
- const { indexer } = useProvex();
1180
- const queryClient = useQueryClient();
1181
- const isBase = chainId === base.id;
1182
- const reputationQuery = useQuery({
1183
- queryKey: ["provex", "reputation", address, isBase ? base.id : "all"],
1197
+ if (!stats) return DEFAULT_REPUTATION;
1198
+ const makerVolume = stats.fulfilledMakerVolume ?? 0n;
1199
+ const makerCount = stats.fulfilledMakerCount ?? 0;
1200
+ const combinedVolume = stats.fulfilledVolume + makerVolume;
1201
+ const combinedFulfilledCount = stats.fulfilledCount + makerCount;
1202
+ let tier = getTierFromVolume(combinedVolume);
1203
+ const lockScore = stats.lateCancellations * 50;
1204
+ const dilutedLockScore = combinedFulfilledCount > 0 ? lockScore / (1 + combinedFulfilledCount * 0.1) : lockScore;
1205
+ let tierPenalty = 0;
1206
+ if (dilutedLockScore >= 1e3) tierPenalty = 4;
1207
+ else if (dilutedLockScore >= 500) tierPenalty = 3;
1208
+ else if (dilutedLockScore >= 200) tierPenalty = 2;
1209
+ else if (dilutedLockScore >= 50) tierPenalty = 1;
1210
+ if (tierPenalty > 0) {
1211
+ const currentTierIndex = TIER_ORDER.indexOf(tier);
1212
+ const newTierIndex = Math.max(0, currentTierIndex - tierPenalty);
1213
+ tier = TIER_ORDER[newTierIndex] ?? "peasant";
1214
+ }
1215
+ const cooldownHours = TAKER_TIERS[tier].cooldownHours;
1216
+ let cooldownEndsAt = null;
1217
+ let isOnCooldown = false;
1218
+ if (cooldownHours > 0 && pulseTimestamp !== null && pulseTimestamp !== void 0) {
1219
+ const lastSignaledTime = Number(pulseTimestamp) * 1e3;
1220
+ const cooldownMs = cooldownHours * 60 * 60 * 1e3;
1221
+ cooldownEndsAt = new Date(lastSignaledTime + cooldownMs);
1222
+ isOnCooldown = cooldownEndsAt > now();
1223
+ }
1224
+ return {
1225
+ tier,
1226
+ fulfilledVolume: combinedVolume,
1227
+ lockScore: dilutedLockScore,
1228
+ cooldownEndsAt,
1229
+ isOnCooldown
1230
+ };
1231
+ }
1232
+ var useIndexerReputationSource = ({ address }) => {
1233
+ const provex = useOptionalProvex();
1234
+ if (!provex) {
1235
+ throw new Error("useUserReputation needs a <ProvexProvider> or a `source` option");
1236
+ }
1237
+ const { indexer } = provex;
1238
+ const query = useQuery({
1239
+ queryKey: ["provex", "reputation", address, pulsechain.id],
1184
1240
  queryFn: async () => {
1185
1241
  if (!address) return null;
1186
- return indexer.getUserReputation({
1187
- address,
1188
- chainId: isBase ? base.id : void 0
1189
- });
1242
+ const data = await indexer.getUserReputation({ address, chainId: pulsechain.id });
1243
+ return {
1244
+ fulfilledVolume: data.fulfilledVolume,
1245
+ fulfilledCount: data.fulfilledCount,
1246
+ lateCancellations: data.lateCancellations,
1247
+ lastSignaledAt: data.lastSignaledAt[pulsechain.id] ?? null
1248
+ };
1190
1249
  },
1191
1250
  enabled: !!address,
1192
1251
  staleTime: 6e4,
1193
1252
  refetchInterval: 12e4
1194
1253
  });
1254
+ const refetch = useCallback(() => query.refetch(), [query]);
1255
+ const row = query.data ?? null;
1256
+ return {
1257
+ stats: row,
1258
+ lastSignaledTimestamp: row ? row.lastSignaledAt : null,
1259
+ isLoading: query.isLoading,
1260
+ error: query.error,
1261
+ refetch
1262
+ };
1263
+ };
1264
+ function useUserReputation({
1265
+ address,
1266
+ source = useIndexerReputationSource,
1267
+ vipStatus = "normal",
1268
+ disabled
1269
+ }) {
1270
+ const provex = useOptionalProvex();
1271
+ const isDisabled = disabled ?? provex?.config.reputationDisabled ?? false;
1272
+ const { stats, lastSignaledTimestamp, isLoading: sourceLoading, error, refetch: refetchSource } = source({ address });
1273
+ const canBypassCooldown = vipStatus === "vip";
1195
1274
  const reputation = useMemo(() => {
1196
- const data = reputationQuery.data;
1197
- if (!data) return DEFAULT_REPUTATION;
1198
- let tier = getTierFromVolume(data.fulfilledVolume);
1199
- const lockScore = data.lateCancellations * 50;
1200
- const dilutedLockScore = data.fulfilledCount > 0 ? lockScore / (1 + data.fulfilledCount * 0.1) : lockScore;
1201
- let tierPenalty = 0;
1202
- if (dilutedLockScore >= 1e3) tierPenalty = 4;
1203
- else if (dilutedLockScore >= 500) tierPenalty = 3;
1204
- else if (dilutedLockScore >= 200) tierPenalty = 2;
1205
- else if (dilutedLockScore >= 50) tierPenalty = 1;
1206
- if (tierPenalty > 0) {
1207
- const currentTierIndex = TIER_ORDER.indexOf(tier);
1208
- const newTierIndex = Math.max(0, currentTierIndex - tierPenalty);
1209
- tier = TIER_ORDER[newTierIndex] ?? "peasant";
1210
- }
1211
- const cooldownHours = TAKER_TIERS[tier].cooldownHours;
1212
- let cooldownEndsAt = null;
1213
- let isOnCooldown = false;
1214
- const baseTimestamp = data.lastSignaledAt[base.id] ?? null;
1215
- const pulseTimestamp = data.lastSignaledAt[369] ?? null;
1216
- const candidateTimestamps = isBase ? [baseTimestamp] : [baseTimestamp, pulseTimestamp];
1217
- const lastSignaledTimestamp = candidateTimestamps.filter((ts) => ts !== null).sort((a, b) => Number(b) - Number(a))[0] ?? null;
1218
- if (cooldownHours > 0 && lastSignaledTimestamp) {
1219
- const lastSignaledTime = Number(lastSignaledTimestamp) * 1e3;
1220
- const cooldownMs = cooldownHours * 60 * 60 * 1e3;
1221
- cooldownEndsAt = new Date(lastSignaledTime + cooldownMs);
1222
- isOnCooldown = cooldownEndsAt > /* @__PURE__ */ new Date();
1223
- }
1224
- return {
1225
- tier,
1226
- fulfilledVolume: data.fulfilledVolume,
1227
- lockScore: dilutedLockScore,
1228
- cooldownEndsAt,
1229
- isOnCooldown
1230
- };
1231
- }, [reputationQuery.data, isBase]);
1232
- const hasLoadedData = reputationQuery.data !== void 0;
1233
- const isLoading = reputationQuery.isLoading && !hasLoadedData;
1234
- const refetch = useCallback(() => {
1235
- queryClient.invalidateQueries({
1236
- queryKey: ["provex", "reputation", address]
1237
- });
1238
- }, [queryClient, address]);
1275
+ if (isDisabled) return DEFAULT_REPUTATION;
1276
+ const computed = computeUserReputation({ stats, pulseTimestamp: lastSignaledTimestamp });
1277
+ if (!canBypassCooldown) return computed;
1278
+ return { ...computed, tier: tiers.PRESIDENT, isOnCooldown: false, cooldownEndsAt: null };
1279
+ }, [stats, lastSignaledTimestamp, canBypassCooldown, isDisabled]);
1280
+ const hasLoadedData = stats !== null;
1281
+ const isLoading = sourceLoading && !hasLoadedData;
1282
+ const refetch = useCallback(async () => {
1283
+ await refetchSource();
1284
+ }, [refetchSource]);
1285
+ const lastSignaledIntentMs = useMemo(
1286
+ () => lastSignaledTimestamp === null || lastSignaledTimestamp === void 0 ? null : Number(lastSignaledTimestamp) * 1e3,
1287
+ [lastSignaledTimestamp]
1288
+ );
1289
+ const filteringTier = isDisabled ? null : filteringTierFor({ vipStatus, tier: reputation.tier });
1239
1290
  return {
1240
1291
  reputation,
1292
+ lastSignaledIntentMs,
1293
+ vipStatus,
1294
+ filteringTier,
1241
1295
  isLoading,
1242
- error: reputationQuery.error,
1296
+ error,
1243
1297
  refetch
1244
1298
  };
1245
1299
  }
@@ -1261,22 +1315,52 @@ function getTierDisplayInfo(tier) {
1261
1315
  volumeRangeDisplay: getVolumeRangeDisplay(tier)
1262
1316
  };
1263
1317
  }
1318
+ var NO_LIMITS = {
1319
+ hasLimits: false,
1320
+ noCooldownProviders: [],
1321
+ selectedProviderCooldownHours: 0,
1322
+ cooldownRemaining: null,
1323
+ isOnCooldown: false,
1324
+ effectiveCap: null,
1325
+ amountExceedsCap: false,
1326
+ capExceededMessage: null,
1327
+ tierName: null,
1328
+ tierBaseCapUsd: null,
1329
+ capMultiplier: null,
1330
+ effectiveCapUsd: null,
1331
+ providerName: null
1332
+ };
1333
+ var COOLDOWN_TICK_MS = 6e4;
1264
1334
  function useReputationLimits({
1265
1335
  chainId,
1266
1336
  selectedPaymentMethod,
1267
1337
  tier,
1268
1338
  decimals,
1339
+ orderTokenAmount,
1269
1340
  amountInInt,
1270
- cooldownEndsAt
1341
+ cooldownEndsAt,
1342
+ disabled,
1343
+ clock
1271
1344
  }) {
1272
- const hasLimits = true;
1345
+ const provex = useOptionalProvex();
1346
+ const providerClock = useClock();
1347
+ const activeClock = clock ?? providerClock;
1348
+ const isDisabled = disabled ?? provex?.config.reputationDisabled ?? false;
1349
+ const hasLimits = !isDisabled;
1350
+ const orderAmount = orderTokenAmount ?? amountInInt ?? null;
1351
+ const [tick, setTick] = useState(0);
1352
+ useEffect(() => {
1353
+ if (!hasLimits || !cooldownEndsAt) return;
1354
+ const id = activeClock.setInterval(() => setTick((count) => count + 1), COOLDOWN_TICK_MS);
1355
+ return () => activeClock.clearInterval(id);
1356
+ }, [hasLimits, cooldownEndsAt, activeClock]);
1273
1357
  const { cooldownRemaining, isOnCooldown } = useMemo(() => {
1274
- const providerCooldownHours = getCooldownHours({ tier, providerKey: selectedPaymentMethod }) ;
1358
+ const providerCooldownHours = hasLimits ? getCooldownHours({ tier, providerKey: selectedPaymentMethod }) : 0;
1275
1359
  const providerHasCooldown = providerCooldownHours > 0;
1276
1360
  if (!cooldownEndsAt || !providerHasCooldown) {
1277
1361
  return { cooldownRemaining: null, isOnCooldown: false };
1278
1362
  }
1279
- const remainingMs = cooldownEndsAt.getTime() - Date.now();
1363
+ const remainingMs = cooldownEndsAt.getTime() - activeClock.now();
1280
1364
  if (remainingMs <= 0) {
1281
1365
  return { cooldownRemaining: null, isOnCooldown: false };
1282
1366
  }
@@ -1284,8 +1368,9 @@ function useReputationLimits({
1284
1368
  const minutes = Math.floor(remainingMs % (1e3 * 60 * 60) / (1e3 * 60));
1285
1369
  const formatted = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
1286
1370
  return { cooldownRemaining: formatted, isOnCooldown: true };
1287
- }, [cooldownEndsAt, hasLimits, tier, selectedPaymentMethod]);
1371
+ }, [cooldownEndsAt, hasLimits, tier, selectedPaymentMethod, tick, activeClock]);
1288
1372
  const noCooldownProviders = useMemo(() => {
1373
+ if (!hasLimits) return [];
1289
1374
  const available = verifiablePaymentMethods(chainId);
1290
1375
  return available.filter((key) => {
1291
1376
  if (key === selectedPaymentMethod) return false;
@@ -1295,22 +1380,43 @@ function useReputationLimits({
1295
1380
  });
1296
1381
  }, [hasLimits, chainId, selectedPaymentMethod, tier]);
1297
1382
  const selectedProviderCooldownHours = useMemo(() => {
1383
+ if (!hasLimits) return 0;
1298
1384
  return getCooldownHours({ tier, providerKey: selectedPaymentMethod });
1299
1385
  }, [hasLimits, tier, selectedPaymentMethod]);
1300
1386
  const effectiveCap = useMemo(() => {
1387
+ if (!hasLimits) return null;
1301
1388
  return getEffectiveCap({ tier, providerKey: selectedPaymentMethod, decimals });
1302
1389
  }, [hasLimits, tier, selectedPaymentMethod, decimals]);
1303
1390
  const amountExceedsCap = useMemo(
1304
- () => isOverOrderCap({ orderTokenAmount: amountInInt ?? null, cap: effectiveCap }),
1305
- [effectiveCap, amountInInt]
1391
+ () => isOverOrderCap({ orderTokenAmount: orderAmount, cap: effectiveCap }),
1392
+ [effectiveCap, orderAmount]
1306
1393
  );
1307
- const capExceededMessage = useMemo(() => {
1308
- if (!amountExceedsCap || effectiveCap === null) return null;
1394
+ const tierName = useMemo(
1395
+ () => hasLimits ? TAKER_TIERS[tier].name : null,
1396
+ [hasLimits, tier]
1397
+ );
1398
+ const providerName = useMemo(
1399
+ () => hasLimits ? providerConfigs(chainId)[selectedPaymentMethod]?.name ?? selectedPaymentMethod : null,
1400
+ [hasLimits, chainId, selectedPaymentMethod]
1401
+ );
1402
+ const tierBaseCapUsd = useMemo(
1403
+ () => hasLimits && effectiveCap !== null && effectiveCap !== UNLIMITED_CAP_BASE_UNITS ? TAKER_TIERS[tier].baseCap : null,
1404
+ [hasLimits, tier, effectiveCap]
1405
+ );
1406
+ const capMultiplier = useMemo(
1407
+ () => hasLimits && effectiveCap !== null && effectiveCap !== UNLIMITED_CAP_BASE_UNITS ? getPlatformRisk(chainId, selectedPaymentMethod).capMultiplier ?? 1 : null,
1408
+ [hasLimits, chainId, selectedPaymentMethod, effectiveCap]
1409
+ );
1410
+ const effectiveCapUsd = useMemo(() => {
1411
+ if (!hasLimits || effectiveCap === null) return null;
1309
1412
  if (effectiveCap === UNLIMITED_CAP_BASE_UNITS) return null;
1310
- const capUsd = getEffectiveCapDisplayUsd(effectiveCap, decimals) ?? 0;
1311
- const providerName = providerConfigs(chainId)[selectedPaymentMethod]?.name ?? selectedPaymentMethod;
1312
- return `Amount exceeds your $${capUsd.toLocaleString()} limit for ${providerName}`;
1313
- }, [amountExceedsCap, effectiveCap, chainId, selectedPaymentMethod, decimals]);
1413
+ return getEffectiveCapDisplayUsd(effectiveCap, decimals) ?? null;
1414
+ }, [hasLimits, effectiveCap, decimals]);
1415
+ const capExceededMessage = useMemo(() => {
1416
+ if (!amountExceedsCap || effectiveCapUsd === null || providerName === null) return null;
1417
+ return `Amount exceeds your $${effectiveCapUsd.toLocaleString()} limit for ${providerName}`;
1418
+ }, [amountExceedsCap, effectiveCapUsd, providerName]);
1419
+ if (!hasLimits) return NO_LIMITS;
1314
1420
  return {
1315
1421
  hasLimits,
1316
1422
  noCooldownProviders,
@@ -1319,7 +1425,12 @@ function useReputationLimits({
1319
1425
  isOnCooldown,
1320
1426
  effectiveCap,
1321
1427
  amountExceedsCap,
1322
- capExceededMessage
1428
+ capExceededMessage,
1429
+ tierName,
1430
+ tierBaseCapUsd,
1431
+ capMultiplier,
1432
+ effectiveCapUsd,
1433
+ providerName
1323
1434
  };
1324
1435
  }
1325
1436
  var intentStatuses = {
@@ -1362,7 +1473,8 @@ function usePayeeDetails({
1362
1473
  }, [indexedIntent]);
1363
1474
  const expiryTime = indexedIntent?.expiryTime ?? null;
1364
1475
  const depositQuery = useQuery({
1365
- queryKey: ["provex", "deposit", indexedIntent?.escrow, indexedIntent?.depositLocalId, chainId],
1476
+ // Query keys are hashed with JSON.stringify, which throws on bigint.
1477
+ queryKey: ["provex", "deposit", indexedIntent?.escrow, indexedIntent?.depositLocalId.toString(), chainId],
1366
1478
  queryFn: () => indexer.getDepositWithVerifiers({
1367
1479
  escrow: indexedIntent.escrow,
1368
1480
  localId: indexedIntent.depositLocalId,
@@ -1716,36 +1828,100 @@ function useNullifierRegistry({
1716
1828
  checkNullifierUsed
1717
1829
  };
1718
1830
  }
1719
- function useProvexProving(deps) {
1831
+ function encodeOrchestratorFulfill(attestation, verificationData, intentHash) {
1832
+ const args = [
1833
+ {
1834
+ paymentProof: encodeV3PaymentProof(attestation),
1835
+ intentHash,
1836
+ verificationData,
1837
+ postIntentHookData: "0x"
1838
+ }
1839
+ ];
1840
+ return {
1841
+ args,
1842
+ calldata: encodeFunctionData({ abi: OrchestratorAbi, functionName: "fulfillIntent", args }),
1843
+ abi: OrchestratorAbi
1844
+ };
1845
+ }
1846
+ function createWalletProvingClient(options) {
1720
1847
  const {
1721
- proveXClient,
1848
+ orchestratorAddress,
1849
+ chainId,
1850
+ writeContract,
1851
+ publicClient: publicClient2,
1722
1852
  checkNullifierUsed,
1853
+ getGasInputs,
1854
+ encodeFulfill = encodeOrchestratorFulfill,
1855
+ onPrefill
1856
+ } = options;
1857
+ return {
1858
+ chainId,
1859
+ async fulfillIntent(params) {
1860
+ const context = { chainId, request: void 0 };
1861
+ const gasInputs = await getGasInputs();
1862
+ const { args, calldata, abi } = encodeFulfill(
1863
+ params.attestation,
1864
+ params.verificationData,
1865
+ params.intentHash
1866
+ );
1867
+ params.onAwaitingSignature?.(context);
1868
+ const hash = await writeContract({
1869
+ address: orchestratorAddress,
1870
+ abi,
1871
+ functionName: "fulfillIntent",
1872
+ args,
1873
+ chainId,
1874
+ ...gasInputs
1875
+ });
1876
+ onPrefill?.(
1877
+ {
1878
+ to: orchestratorAddress,
1879
+ data: calldata,
1880
+ value: 0n,
1881
+ originalTip: gasInputs.maxPriorityFeePerGas
1882
+ },
1883
+ gasInputs,
1884
+ hash
1885
+ );
1886
+ params.onTransactionHash?.({ ...context, hash });
1887
+ const receipt = publicClient2 ? await publicClient2.waitForTransactionReceipt({ hash }) : void 0;
1888
+ return { hash, receipt };
1889
+ },
1890
+ async checkNullifierUsed({ nullifier }) {
1891
+ return checkNullifierUsed(nullifier);
1892
+ }
1893
+ };
1894
+ }
1895
+
1896
+ // src/hooks/useProvingFlow.ts
1897
+ function useProvingFlow(options) {
1898
+ const {
1899
+ rail,
1723
1900
  request,
1724
1901
  paymentMethodId,
1725
1902
  captureAdapter,
1726
1903
  attestationBaseUrl,
1904
+ attestationServiceUrl,
1727
1905
  onAwaitingSignature,
1728
1906
  onTransactionHash,
1729
1907
  onComplete
1730
- } = deps;
1731
- const client = useMemo(
1732
- () => ({
1733
- // Pin the rail to the intent's chain. request.chainId is the single chain
1734
- // that drives both useNullifierRegistry (the read backing checkNullifierUsed)
1735
- // and ProveXClient.fulfillIntent here, so useProving's chain-pin assertion
1736
- // is a tautology in correct wiring and a tripwire if they ever diverge.
1737
- chainId: request.chainId,
1738
- fulfillIntent: proveXClient.fulfillIntent,
1739
- checkNullifierUsed: ({ nullifier }) => checkNullifierUsed(nullifier)
1740
- }),
1741
- [proveXClient, checkNullifierUsed, request.chainId]
1742
- );
1908
+ } = options;
1909
+ const chainId = options.chainId ?? request.chainId;
1910
+ const client = useMemo(() => {
1911
+ if (rail.kind === "wallet") return createWalletProvingClient({ ...rail, chainId });
1912
+ return {
1913
+ chainId,
1914
+ fulfillIntent: rail.fulfillIntent,
1915
+ checkNullifierUsed: ({ nullifier }) => rail.checkNullifierUsed(nullifier)
1916
+ };
1917
+ }, [rail, chainId]);
1743
1918
  const resolvedCaptureAdapter = useMemo(
1744
1919
  () => captureAdapter ?? createExtensionEnclaveAdapter({
1745
1920
  getPeer: () => window.peer,
1746
- attestationBaseUrl
1921
+ attestationBaseUrl,
1922
+ ...attestationServiceUrl ? { attestationServiceUrl } : {}
1747
1923
  }),
1748
- [captureAdapter, attestationBaseUrl]
1924
+ [captureAdapter, attestationBaseUrl, attestationServiceUrl]
1749
1925
  );
1750
1926
  const manualSelect = useManualTransactionSelect();
1751
1927
  const { state, authenticate, fulfill, retryVerification, retrySubmission } = useProving({
@@ -1760,6 +1936,15 @@ function useProvexProving(deps) {
1760
1936
  });
1761
1937
  return { state, authenticate, fulfill, retryVerification, retrySubmission, manualSelect };
1762
1938
  }
1939
+ function useProvexProving(deps) {
1940
+ const { proveXClient, checkNullifierUsed, ...flowOptions } = deps;
1941
+ const { fulfillIntent } = proveXClient;
1942
+ const rail = useMemo(
1943
+ () => ({ kind: "client", fulfillIntent, checkNullifierUsed }),
1944
+ [fulfillIntent, checkNullifierUsed]
1945
+ );
1946
+ return useProvingFlow({ rail, ...flowOptions });
1947
+ }
1763
1948
 
1764
1949
  // src/useProvexBuy.ts
1765
1950
  function parseAmountSafe(value, decimals) {
@@ -1802,10 +1987,8 @@ function useProvexBuy({
1802
1987
  paymentMethod: activePaymentMethod,
1803
1988
  currency
1804
1989
  });
1805
- const { reputation } = useReputation({
1806
- address: wallet.address,
1807
- chainId
1808
- });
1990
+ const { reputation } = useUserReputation({
1991
+ address: wallet.address});
1809
1992
  const amountBigInt = useMemo(
1810
1993
  () => parseAmountSafe(amount, currency.decimals),
1811
1994
  [amount, currency.decimals]
@@ -1841,12 +2024,11 @@ function useProvexBuy({
1841
2024
  refetchAllRef.current = payeeResult.refetchAll;
1842
2025
  useEffect(() => {
1843
2026
  if (phase !== "committed") return;
1844
- if (!intentHash) return;
1845
2027
  const interval = setInterval(() => {
1846
2028
  refetchAllRef.current();
1847
2029
  }, 5e3);
1848
2030
  return () => clearInterval(interval);
1849
- }, [phase, intentHash]);
2031
+ }, [phase]);
1850
2032
  useEffect(() => {
1851
2033
  if (!payeeResult.isPruned) return;
1852
2034
  if (phase === "browse") return;
@@ -1886,6 +2068,7 @@ function useProvexBuy({
1886
2068
  payeeDetailsHash,
1887
2069
  token,
1888
2070
  intent: {
2071
+ owner: intent.owner,
1889
2072
  amount: intent.amount,
1890
2073
  timestamp: intent.timestamp,
1891
2074
  paymentMethod: intent.paymentMethod,
@@ -1904,6 +2087,7 @@ function useProvexBuy({
1904
2087
  token: token ?? { decimals: 18 },
1905
2088
  intent: {
1906
2089
  intentHash: intentHash ?? "0x",
2090
+ owner: "0x",
1907
2091
  amount: "0",
1908
2092
  timestampMs: "0",
1909
2093
  paymentMethod: "0x",
@@ -1967,7 +2151,7 @@ function useProvexBuy({
1967
2151
  selectedPaymentMethod: activePaymentMethod,
1968
2152
  tier: reputation.tier,
1969
2153
  decimals: tokenDecimals,
1970
- amountInInt: tokenAmount,
2154
+ orderTokenAmount: tokenAmount,
1971
2155
  cooldownEndsAt: reputation.cooldownEndsAt
1972
2156
  });
1973
2157
  const rateDisplay = useMemo(() => {
@@ -2054,7 +2238,7 @@ function useProvexBuy({
2054
2238
  tokenAmountDisplay,
2055
2239
  token,
2056
2240
  currency,
2057
- reputation: reputation ?? DEFAULT_REPUTATION,
2241
+ reputation,
2058
2242
  limits,
2059
2243
  feePercentage: feeInfo?.percentage ?? null,
2060
2244
  payeeDetails: payeeResult.payeeDetails,
@@ -2537,6 +2721,6 @@ function ProvexBuyRoot({
2537
2721
  );
2538
2722
  }
2539
2723
 
2540
- export { BrowsePhase, CommittedPhase, CompletePhase, ProveXClient, ProveXError, ProvexBuy, ProvexBuyProvider, ProvexProvider, ProvingPhase, checkMutation, checkTransactionIndexed, createApiClient, createProveXClient, getRate, getTierDisplayInfo, getTransactionGasInputs, getUserFriendlyErrorMessage, intentStatuses, isUserRejectionError, useContractRead, useDeposits, useNullifierRegistry, useOptionalProvex, useOptionalProvexPublicClient, useOptionalProvexWallet, useOrchestratorAddress, usePayeeDetails, useProtocolFeePercentage, useProtocolFees, useProveXClient, useProvex, useProvexBuy, useProvexBuyContext, useProvexPublicClient, useProvexWallet, useReputation, useReputationLimits, useSignalIntent };
2541
- //# sourceMappingURL=chunk-B3N7BGJD.js.map
2542
- //# sourceMappingURL=chunk-B3N7BGJD.js.map
2724
+ export { BrowsePhase, CommittedPhase, CompletePhase, DEFAULT_SIGNAL_INTENT_MESSAGES, ProveXClient, ProveXError, ProvexBuy, ProvexBuyProvider, ProvexProvider, ProvingPhase, checkMutation, checkTransactionIndexed, computeUserReputation, createApiClient, createProveXClient, filteringTierFor, getRate, getTierDisplayInfo, getTransactionGasInputs, getUserFriendlyErrorMessage, intentStatuses, isUserRejectionError, systemClock, useClock, useContractRead, useDeposits, useIndexerReputationSource, useNullifierRegistry, useOptionalProvex, useOptionalProvexPublicClient, useOptionalProvexWallet, useOrchestratorAddress, usePayeeDetails, useProtocolFeePercentage, useProtocolFees, useProveXClient, useProvex, useProvexBuy, useProvexBuyContext, useProvexPublicClient, useProvexWallet, useProvingFlow, useReputationLimits, useSignalIntent, useUserReputation };
2725
+ //# sourceMappingURL=chunk-RVG3ZJZK.js.map
2726
+ //# sourceMappingURL=chunk-RVG3ZJZK.js.map