@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.
package/dist/wagmi.cjs CHANGED
@@ -11,15 +11,15 @@ var currencies = require('@provex/utils/currencies');
11
11
  var tokens = require('@provex/utils/tokens');
12
12
  var units = require('@provex/utils/units');
13
13
  var conversionRates = require('@provex/utils/conversionRates');
14
- var reputation = require('@provex/utils/reputation');
15
14
  var contracts = require('@provex/utils/contracts');
16
15
  var proving = require('@provex/proving');
16
+ var chain = require('@provex/utils/chain');
17
17
  var utils = require('@provex/utils');
18
18
  var deposit = require('@provex/utils/deposit');
19
19
  var fees = require('@provex/utils/fees');
20
20
  var abis = require('@provex/abis');
21
- var chain = require('@provex/utils/chain');
22
21
  var chains = require('viem/chains');
22
+ var reputation = require('@provex/utils/reputation');
23
23
  var extensionReact = require('@provex/extension-react');
24
24
 
25
25
  // src/wagmi/useWagmiWallet.ts
@@ -80,6 +80,15 @@ function createApiClient(apiUrl) {
80
80
  })
81
81
  };
82
82
  }
83
+
84
+ // src/lib/clock.ts
85
+ var systemClock = Object.freeze({
86
+ now: () => Date.now(),
87
+ setTimeout: (callback, delayMs) => globalThis.setTimeout(callback, delayMs),
88
+ clearTimeout: (handle) => globalThis.clearTimeout(handle),
89
+ setInterval: (callback, intervalMs) => globalThis.setInterval(callback, intervalMs),
90
+ clearInterval: (handle) => globalThis.clearInterval(handle)
91
+ });
83
92
  var ProvexContext = react.createContext(null);
84
93
  var defaultQueryClient = new reactQuery.QueryClient({
85
94
  defaultOptions: {
@@ -96,22 +105,23 @@ function ProvexProvider({
96
105
  publicClient: publicClient2,
97
106
  publicClients,
98
107
  wallet,
108
+ clock = systemClock,
99
109
  children
100
110
  }) {
101
111
  const resolvedQueryClient = queryClient ?? defaultQueryClient;
102
112
  const contextValue = react.useMemo(() => {
103
113
  const apiClient = createApiClient(config.apiUrl);
104
114
  const chainId = config.chain.id;
105
- const base2 = publicClients ?? {};
106
- const resolved = { ...base2 };
115
+ const base = publicClients ?? {};
116
+ const resolved = { ...base };
107
117
  if (publicClient2 && !resolved[chainId]) {
108
118
  resolved[chainId] = publicClient2;
109
119
  }
110
120
  if (!resolved[chainId]) {
111
121
  resolved[chainId] = viem.createPublicClient({ chain: config.chain, transport: viem.http() });
112
122
  }
113
- return { config, indexer, apiClient, publicClients: resolved, wallet };
114
- }, [config, indexer, publicClient2, publicClients, wallet]);
123
+ return { config, indexer, apiClient, publicClients: resolved, wallet, clock };
124
+ }, [config, indexer, publicClient2, publicClients, wallet, clock]);
115
125
  return /* @__PURE__ */ jsxRuntime.jsx(ProvexContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: resolvedQueryClient, children }) });
116
126
  }
117
127
  function useProvex() {
@@ -130,6 +140,9 @@ function useOptionalProvexPublicClient(chainId) {
130
140
  const target = chainId ?? ctx.config.chain.id;
131
141
  return ctx.publicClients[target];
132
142
  }
143
+ function useClock() {
144
+ return useOptionalProvex()?.clock ?? systemClock;
145
+ }
133
146
  function WagmiProvexProvider(props) {
134
147
  const publicClients = useWagmiPublicClients();
135
148
  const wallet = useWagmiWallet() ?? void 0;
@@ -267,25 +280,29 @@ var getTransactionGasInputs = async (chainId) => {
267
280
  // src/lib/updates.ts
268
281
  var POLL_INTERVAL_MS = 1e3;
269
282
  var SLOW_SYNC_THRESHOLD_MS = 15e3;
283
+ var sleep = (clock, delayMs) => new Promise((resolve) => {
284
+ clock.setTimeout(resolve, delayMs);
285
+ });
270
286
  var checkTransactionIndexed = async ({
271
287
  queryTransaction,
272
288
  hash,
273
289
  onSuccess,
274
290
  onSlowSync,
275
- slowSyncThresholdMs = SLOW_SYNC_THRESHOLD_MS
291
+ slowSyncThresholdMs = SLOW_SYNC_THRESHOLD_MS,
292
+ clock = systemClock
276
293
  }) => {
277
- const startTime = Date.now();
294
+ const startTime = clock.now();
278
295
  let slowSyncFired = false;
279
296
  while (true) {
280
297
  const result = await queryTransaction(hash);
281
298
  if (result) {
282
299
  break;
283
300
  }
284
- if (!slowSyncFired && onSlowSync && Date.now() - startTime > slowSyncThresholdMs) {
301
+ if (!slowSyncFired && onSlowSync && clock.now() - startTime > slowSyncThresholdMs) {
285
302
  slowSyncFired = true;
286
303
  onSlowSync();
287
304
  }
288
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
305
+ await sleep(clock, POLL_INTERVAL_MS);
289
306
  }
290
307
  onSuccess?.();
291
308
  };
@@ -630,7 +647,7 @@ var ProveXClient = class {
630
647
  * Handles all encoding internally:
631
648
  * 1. Registers payee details with the API (so buyers can discover them)
632
649
  * 2. Hashes provider keys and currency codes to bytes32
633
- * 3. Expands sub-providers (e.g., 'zelle' → zelle-chase, zelle-bofa, zelle-citi)
650
+ * 3. Collapses bank-level keys to their rail (e.g., 'zelle-chase' → 'zelle'); Zelle is one rail
634
651
  * 4. Encodes gating service witness signers
635
652
  * 5. Submits the on-chain transaction
636
653
  *
@@ -920,43 +937,6 @@ var ProveXClient = class {
920
937
  });
921
938
  return res.responseObject ?? null;
922
939
  }
923
- /**
924
- * Submit zkTLS proofs and get a signed attestation for fulfillIntent.
925
- * This is the proof verification step — after the buyer makes payment and
926
- * generates proofs via the browser extension.
927
- */
928
- async getAttestation(params) {
929
- const endpoint = `/api/v0/attestation/verify/${params.platform}/${params.actionType}`;
930
- const proof = params.proofs.length === 1 ? params.proofs[0] : params.proofs;
931
- const res = await fetch(`${this.apiUrl}${endpoint}`, {
932
- method: "POST",
933
- headers: { "Content-Type": "application/json" },
934
- body: JSON.stringify({
935
- proofType: "reclaim",
936
- proof: JSON.stringify(proof),
937
- chainId: params.chainId,
938
- verifyingContract: params.verifyingContract,
939
- intent: params.intent
940
- })
941
- });
942
- if (!res.ok) {
943
- let errorMessage = `Attestation error: ${res.status}`;
944
- try {
945
- const errorJson = await res.json();
946
- if (errorJson.details?.message) errorMessage = errorJson.details.message;
947
- else if (errorJson.message) errorMessage = errorJson.message;
948
- else if (errorJson.error) errorMessage = errorJson.error;
949
- } catch {
950
- }
951
- return {
952
- success: false,
953
- message: errorMessage,
954
- statusCode: res.status,
955
- responseObject: {}
956
- };
957
- }
958
- return await res.json();
959
- }
960
940
  /** Make a POST request to the API. */
961
941
  async apiPost(path, body) {
962
942
  const res = await fetch(`${this.apiUrl}${path}`, {
@@ -1056,6 +1036,11 @@ function createProveXClient(config) {
1056
1036
  }
1057
1037
 
1058
1038
  // src/hooks/useSignalIntent.ts
1039
+ var DEFAULT_SIGNAL_INTENT_MESSAGES = {
1040
+ clientOrDepositUnavailable: "Client or deposit not available",
1041
+ noEscrowAddress: "No escrow address found",
1042
+ success: "Intent signaled successfully"
1043
+ };
1059
1044
  var ERROR_PATTERNS = [
1060
1045
  { pattern: /user rejected|user denied|rejected the request|user cancelled|user canceled/i, message: "Transaction was cancelled." },
1061
1046
  { pattern: /InsufficientDepositLiquidity|0xc3df48f3/i, message: "This deposit no longer has enough available liquidity. Please try a smaller amount or a different deposit." },
@@ -1070,28 +1055,48 @@ function getUserFriendlyErrorMessage(error) {
1070
1055
  }
1071
1056
  return "Something went wrong. Please try again.";
1072
1057
  }
1058
+ function isClientRejection(error) {
1059
+ return error instanceof ProveXError && error.isRejection;
1060
+ }
1061
+ var LOADING_STATUSES = /* @__PURE__ */ new Set([
1062
+ "loading_payee",
1063
+ "loading_intent",
1064
+ "prompt_wallet_confirm",
1065
+ "writing_intent"
1066
+ ]);
1073
1067
  function useSignalIntent({
1074
1068
  wallet,
1069
+ chainId,
1070
+ indexer,
1071
+ apiUrl,
1075
1072
  onSuccess,
1076
1073
  onFailure,
1077
1074
  deposit,
1078
- refetchDeposits
1075
+ refetchDeposits,
1076
+ beginTransaction,
1077
+ isRejection = isClientRejection,
1078
+ formatError = getUserFriendlyErrorMessage,
1079
+ messages
1079
1080
  }) {
1080
1081
  const provex = useOptionalProvex();
1081
1082
  const [status, setStatus] = react.useState("input");
1082
1083
  const [message, setMessage] = react.useState(null);
1084
+ const copy = { ...DEFAULT_SIGNAL_INTENT_MESSAGES, ...messages };
1085
+ const chain$1 = (chainId === void 0 ? void 0 : chain.chainIdToChain.get(chainId)) ?? provex?.config.chain;
1086
+ const resolvedIndexer = indexer ?? provex?.indexer;
1087
+ const resolvedApiUrl = apiUrl ?? provex?.config.apiUrl;
1088
+ const escrow = deposit?.escrow ?? void 0;
1083
1089
  const client = react.useMemo(() => {
1084
- if (!provex) return null;
1090
+ if (!chain$1 || !wallet || !resolvedIndexer || resolvedApiUrl === void 0) return null;
1085
1091
  return createProveXClient({
1086
- chain: provex.config.chain,
1087
- apiUrl: provex.config.apiUrl,
1092
+ chain: chain$1,
1088
1093
  wallet,
1089
- indexer: provex.indexer
1094
+ indexer: resolvedIndexer,
1095
+ apiUrl: resolvedApiUrl,
1096
+ escrowAddress: escrow
1090
1097
  });
1091
- }, [provex, wallet]);
1092
- const isLoading = react.useMemo(() => {
1093
- return status === "loading_payee" || status === "loading_intent" || status === "prompt_wallet_confirm" || status === "writing_intent";
1094
- }, [status]);
1098
+ }, [chain$1, wallet, resolvedIndexer, resolvedApiUrl, escrow]);
1099
+ const isLoading = LOADING_STATUSES.has(status);
1095
1100
  const startOrder = react.useCallback(async ({
1096
1101
  paymentMethod,
1097
1102
  depositId,
@@ -1103,42 +1108,48 @@ function useSignalIntent({
1103
1108
  }) => {
1104
1109
  if (!client || !deposit) {
1105
1110
  setStatus("error");
1106
- setMessage("Client or deposit not available");
1111
+ setMessage(copy.clientOrDepositUnavailable);
1107
1112
  return;
1108
1113
  }
1109
- const escrow = deposit.escrow;
1110
- if (!escrow) {
1114
+ const depositEscrow = deposit.escrow;
1115
+ if (!depositEscrow) {
1111
1116
  setStatus("error");
1112
- setMessage("No escrow address found");
1117
+ setMessage(copy.noEscrowAddress);
1113
1118
  return;
1114
1119
  }
1120
+ const transaction = beginTransaction ? await beginTransaction() : void 0;
1121
+ if (transaction === null) return;
1115
1122
  setStatus("loading_intent");
1116
1123
  try {
1117
1124
  const result = await client.signalIntent({
1118
- deposit: { escrow, localId: deposit.localId ?? depositId },
1125
+ deposit: { escrow: depositEscrow, localId: deposit.localId ?? depositId },
1119
1126
  paymentMethod,
1120
1127
  tokenAmount,
1121
1128
  toAddress,
1122
1129
  fiatCurrencyCode,
1123
1130
  conversionRate,
1124
- subProvider
1131
+ subProvider,
1132
+ onAwaitingSignature: () => transaction?.awaitingSignature(),
1133
+ onTransactionHash: (hash) => transaction?.attach(hash)
1125
1134
  });
1126
1135
  setStatus("success");
1127
- setMessage("Intent signaled successfully");
1136
+ setMessage(copy.success);
1128
1137
  onSuccess(result.intentHash);
1129
1138
  } catch (error) {
1130
- if (error instanceof ProveXError && error.isRejection) {
1139
+ if (isRejection(error)) {
1140
+ transaction?.failed(error);
1131
1141
  setStatus("input");
1132
1142
  setMessage(null);
1133
1143
  return;
1134
1144
  }
1135
1145
  console.error("Failed to signal intent", error);
1146
+ transaction?.failed(error);
1136
1147
  setStatus("error");
1137
- setMessage(getUserFriendlyErrorMessage(error));
1148
+ setMessage(formatError(error));
1138
1149
  refetchDeposits?.();
1139
1150
  onFailure?.();
1140
1151
  }
1141
- }, [client, deposit, onSuccess, onFailure, refetchDeposits]);
1152
+ }, [client, deposit, beginTransaction, isRejection, formatError, onSuccess, onFailure, refetchDeposits, copy.clientOrDepositUnavailable, copy.noEscrowAddress, copy.success]);
1142
1153
  return {
1143
1154
  startOrder,
1144
1155
  status,
@@ -1146,93 +1157,167 @@ function useSignalIntent({
1146
1157
  isLoading
1147
1158
  };
1148
1159
  }
1149
- function useReputation({
1150
- address,
1151
- chainId
1160
+ function filteringTierFor(options) {
1161
+ if (options.vipStatus === "unknown") return null;
1162
+ if (options.vipStatus === "vip") return payment.tiers.PRESIDENT;
1163
+ return options.tier;
1164
+ }
1165
+
1166
+ // src/hooks/useUserReputation.ts
1167
+ function computeUserReputation({
1168
+ stats,
1169
+ pulseTimestamp,
1170
+ now = () => /* @__PURE__ */ new Date()
1152
1171
  }) {
1153
- const { indexer } = useProvex();
1154
- const queryClient = reactQuery.useQueryClient();
1155
- const isBase = chainId === chains.base.id;
1156
- const reputationQuery = reactQuery.useQuery({
1157
- queryKey: ["provex", "reputation", address, isBase ? chains.base.id : "all"],
1172
+ if (!stats) return reputation.DEFAULT_REPUTATION;
1173
+ const makerVolume = stats.fulfilledMakerVolume ?? 0n;
1174
+ const makerCount = stats.fulfilledMakerCount ?? 0;
1175
+ const combinedVolume = stats.fulfilledVolume + makerVolume;
1176
+ const combinedFulfilledCount = stats.fulfilledCount + makerCount;
1177
+ let tier = reputation.getTierFromVolume(combinedVolume);
1178
+ const lockScore = stats.lateCancellations * 50;
1179
+ const dilutedLockScore = combinedFulfilledCount > 0 ? lockScore / (1 + combinedFulfilledCount * 0.1) : lockScore;
1180
+ let tierPenalty = 0;
1181
+ if (dilutedLockScore >= 1e3) tierPenalty = 4;
1182
+ else if (dilutedLockScore >= 500) tierPenalty = 3;
1183
+ else if (dilutedLockScore >= 200) tierPenalty = 2;
1184
+ else if (dilutedLockScore >= 50) tierPenalty = 1;
1185
+ if (tierPenalty > 0) {
1186
+ const currentTierIndex = reputation.TIER_ORDER.indexOf(tier);
1187
+ const newTierIndex = Math.max(0, currentTierIndex - tierPenalty);
1188
+ tier = reputation.TIER_ORDER[newTierIndex] ?? "peasant";
1189
+ }
1190
+ const cooldownHours = reputation.TAKER_TIERS[tier].cooldownHours;
1191
+ let cooldownEndsAt = null;
1192
+ let isOnCooldown = false;
1193
+ if (cooldownHours > 0 && pulseTimestamp !== null && pulseTimestamp !== void 0) {
1194
+ const lastSignaledTime = Number(pulseTimestamp) * 1e3;
1195
+ const cooldownMs = cooldownHours * 60 * 60 * 1e3;
1196
+ cooldownEndsAt = new Date(lastSignaledTime + cooldownMs);
1197
+ isOnCooldown = cooldownEndsAt > now();
1198
+ }
1199
+ return {
1200
+ tier,
1201
+ fulfilledVolume: combinedVolume,
1202
+ lockScore: dilutedLockScore,
1203
+ cooldownEndsAt,
1204
+ isOnCooldown
1205
+ };
1206
+ }
1207
+ var useIndexerReputationSource = ({ address }) => {
1208
+ const provex = useOptionalProvex();
1209
+ if (!provex) {
1210
+ throw new Error("useUserReputation needs a <ProvexProvider> or a `source` option");
1211
+ }
1212
+ const { indexer } = provex;
1213
+ const query = reactQuery.useQuery({
1214
+ queryKey: ["provex", "reputation", address, chains.pulsechain.id],
1158
1215
  queryFn: async () => {
1159
1216
  if (!address) return null;
1160
- return indexer.getUserReputation({
1161
- address,
1162
- chainId: isBase ? chains.base.id : void 0
1163
- });
1217
+ const data = await indexer.getUserReputation({ address, chainId: chains.pulsechain.id });
1218
+ return {
1219
+ fulfilledVolume: data.fulfilledVolume,
1220
+ fulfilledCount: data.fulfilledCount,
1221
+ lateCancellations: data.lateCancellations,
1222
+ lastSignaledAt: data.lastSignaledAt[chains.pulsechain.id] ?? null
1223
+ };
1164
1224
  },
1165
1225
  enabled: !!address,
1166
1226
  staleTime: 6e4,
1167
1227
  refetchInterval: 12e4
1168
1228
  });
1229
+ const refetch = react.useCallback(() => query.refetch(), [query]);
1230
+ const row = query.data ?? null;
1231
+ return {
1232
+ stats: row,
1233
+ lastSignaledTimestamp: row ? row.lastSignaledAt : null,
1234
+ isLoading: query.isLoading,
1235
+ error: query.error,
1236
+ refetch
1237
+ };
1238
+ };
1239
+ function useUserReputation({
1240
+ address,
1241
+ source = useIndexerReputationSource,
1242
+ vipStatus = "normal",
1243
+ disabled
1244
+ }) {
1245
+ const provex = useOptionalProvex();
1246
+ const isDisabled = disabled ?? provex?.config.reputationDisabled ?? false;
1247
+ const { stats, lastSignaledTimestamp, isLoading: sourceLoading, error, refetch: refetchSource } = source({ address });
1248
+ const canBypassCooldown = vipStatus === "vip";
1169
1249
  const reputation$1 = react.useMemo(() => {
1170
- const data = reputationQuery.data;
1171
- if (!data) return reputation.DEFAULT_REPUTATION;
1172
- let tier = reputation.getTierFromVolume(data.fulfilledVolume);
1173
- const lockScore = data.lateCancellations * 50;
1174
- const dilutedLockScore = data.fulfilledCount > 0 ? lockScore / (1 + data.fulfilledCount * 0.1) : lockScore;
1175
- let tierPenalty = 0;
1176
- if (dilutedLockScore >= 1e3) tierPenalty = 4;
1177
- else if (dilutedLockScore >= 500) tierPenalty = 3;
1178
- else if (dilutedLockScore >= 200) tierPenalty = 2;
1179
- else if (dilutedLockScore >= 50) tierPenalty = 1;
1180
- if (tierPenalty > 0) {
1181
- const currentTierIndex = reputation.TIER_ORDER.indexOf(tier);
1182
- const newTierIndex = Math.max(0, currentTierIndex - tierPenalty);
1183
- tier = reputation.TIER_ORDER[newTierIndex] ?? "peasant";
1184
- }
1185
- const cooldownHours = reputation.TAKER_TIERS[tier].cooldownHours;
1186
- let cooldownEndsAt = null;
1187
- let isOnCooldown = false;
1188
- const baseTimestamp = data.lastSignaledAt[chains.base.id] ?? null;
1189
- const pulseTimestamp = data.lastSignaledAt[369] ?? null;
1190
- const candidateTimestamps = isBase ? [baseTimestamp] : [baseTimestamp, pulseTimestamp];
1191
- const lastSignaledTimestamp = candidateTimestamps.filter((ts) => ts !== null).sort((a, b) => Number(b) - Number(a))[0] ?? null;
1192
- if (cooldownHours > 0 && lastSignaledTimestamp) {
1193
- const lastSignaledTime = Number(lastSignaledTimestamp) * 1e3;
1194
- const cooldownMs = cooldownHours * 60 * 60 * 1e3;
1195
- cooldownEndsAt = new Date(lastSignaledTime + cooldownMs);
1196
- isOnCooldown = cooldownEndsAt > /* @__PURE__ */ new Date();
1197
- }
1198
- return {
1199
- tier,
1200
- fulfilledVolume: data.fulfilledVolume,
1201
- lockScore: dilutedLockScore,
1202
- cooldownEndsAt,
1203
- isOnCooldown
1204
- };
1205
- }, [reputationQuery.data, isBase]);
1206
- const hasLoadedData = reputationQuery.data !== void 0;
1207
- const isLoading = reputationQuery.isLoading && !hasLoadedData;
1208
- const refetch = react.useCallback(() => {
1209
- queryClient.invalidateQueries({
1210
- queryKey: ["provex", "reputation", address]
1211
- });
1212
- }, [queryClient, address]);
1250
+ if (isDisabled) return reputation.DEFAULT_REPUTATION;
1251
+ const computed = computeUserReputation({ stats, pulseTimestamp: lastSignaledTimestamp });
1252
+ if (!canBypassCooldown) return computed;
1253
+ return { ...computed, tier: payment.tiers.PRESIDENT, isOnCooldown: false, cooldownEndsAt: null };
1254
+ }, [stats, lastSignaledTimestamp, canBypassCooldown, isDisabled]);
1255
+ const hasLoadedData = stats !== null;
1256
+ const isLoading = sourceLoading && !hasLoadedData;
1257
+ const refetch = react.useCallback(async () => {
1258
+ await refetchSource();
1259
+ }, [refetchSource]);
1260
+ const lastSignaledIntentMs = react.useMemo(
1261
+ () => lastSignaledTimestamp === null || lastSignaledTimestamp === void 0 ? null : Number(lastSignaledTimestamp) * 1e3,
1262
+ [lastSignaledTimestamp]
1263
+ );
1264
+ const filteringTier = isDisabled ? null : filteringTierFor({ vipStatus, tier: reputation$1.tier });
1213
1265
  return {
1214
1266
  reputation: reputation$1,
1267
+ lastSignaledIntentMs,
1268
+ vipStatus,
1269
+ filteringTier,
1215
1270
  isLoading,
1216
- error: reputationQuery.error,
1271
+ error,
1217
1272
  refetch
1218
1273
  };
1219
1274
  }
1275
+ var NO_LIMITS = {
1276
+ hasLimits: false,
1277
+ noCooldownProviders: [],
1278
+ selectedProviderCooldownHours: 0,
1279
+ cooldownRemaining: null,
1280
+ isOnCooldown: false,
1281
+ effectiveCap: null,
1282
+ amountExceedsCap: false,
1283
+ capExceededMessage: null,
1284
+ tierName: null,
1285
+ tierBaseCapUsd: null,
1286
+ capMultiplier: null,
1287
+ effectiveCapUsd: null,
1288
+ providerName: null
1289
+ };
1290
+ var COOLDOWN_TICK_MS = 6e4;
1220
1291
  function useReputationLimits({
1221
1292
  chainId,
1222
1293
  selectedPaymentMethod,
1223
1294
  tier,
1224
1295
  decimals,
1296
+ orderTokenAmount,
1225
1297
  amountInInt,
1226
- cooldownEndsAt
1298
+ cooldownEndsAt,
1299
+ disabled,
1300
+ clock
1227
1301
  }) {
1228
- const hasLimits = true;
1302
+ const provex = useOptionalProvex();
1303
+ const providerClock = useClock();
1304
+ const activeClock = clock ?? providerClock;
1305
+ const isDisabled = disabled ?? provex?.config.reputationDisabled ?? false;
1306
+ const hasLimits = !isDisabled;
1307
+ const orderAmount = orderTokenAmount ?? amountInInt ?? null;
1308
+ const [tick, setTick] = react.useState(0);
1309
+ react.useEffect(() => {
1310
+ if (!hasLimits || !cooldownEndsAt) return;
1311
+ const id = activeClock.setInterval(() => setTick((count) => count + 1), COOLDOWN_TICK_MS);
1312
+ return () => activeClock.clearInterval(id);
1313
+ }, [hasLimits, cooldownEndsAt, activeClock]);
1229
1314
  const { cooldownRemaining, isOnCooldown } = react.useMemo(() => {
1230
- const providerCooldownHours = reputation.getCooldownHours({ tier, providerKey: selectedPaymentMethod }) ;
1315
+ const providerCooldownHours = hasLimits ? reputation.getCooldownHours({ tier, providerKey: selectedPaymentMethod }) : 0;
1231
1316
  const providerHasCooldown = providerCooldownHours > 0;
1232
1317
  if (!cooldownEndsAt || !providerHasCooldown) {
1233
1318
  return { cooldownRemaining: null, isOnCooldown: false };
1234
1319
  }
1235
- const remainingMs = cooldownEndsAt.getTime() - Date.now();
1320
+ const remainingMs = cooldownEndsAt.getTime() - activeClock.now();
1236
1321
  if (remainingMs <= 0) {
1237
1322
  return { cooldownRemaining: null, isOnCooldown: false };
1238
1323
  }
@@ -1240,8 +1325,9 @@ function useReputationLimits({
1240
1325
  const minutes = Math.floor(remainingMs % (1e3 * 60 * 60) / (1e3 * 60));
1241
1326
  const formatted = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
1242
1327
  return { cooldownRemaining: formatted, isOnCooldown: true };
1243
- }, [cooldownEndsAt, hasLimits, tier, selectedPaymentMethod]);
1328
+ }, [cooldownEndsAt, hasLimits, tier, selectedPaymentMethod, tick, activeClock]);
1244
1329
  const noCooldownProviders = react.useMemo(() => {
1330
+ if (!hasLimits) return [];
1245
1331
  const available = payment.verifiablePaymentMethods(chainId);
1246
1332
  return available.filter((key) => {
1247
1333
  if (key === selectedPaymentMethod) return false;
@@ -1251,22 +1337,43 @@ function useReputationLimits({
1251
1337
  });
1252
1338
  }, [hasLimits, chainId, selectedPaymentMethod, tier]);
1253
1339
  const selectedProviderCooldownHours = react.useMemo(() => {
1340
+ if (!hasLimits) return 0;
1254
1341
  return reputation.getCooldownHours({ tier, providerKey: selectedPaymentMethod });
1255
1342
  }, [hasLimits, tier, selectedPaymentMethod]);
1256
1343
  const effectiveCap = react.useMemo(() => {
1344
+ if (!hasLimits) return null;
1257
1345
  return reputation.getEffectiveCap({ tier, providerKey: selectedPaymentMethod, decimals });
1258
1346
  }, [hasLimits, tier, selectedPaymentMethod, decimals]);
1259
1347
  const amountExceedsCap = react.useMemo(
1260
- () => reputation.isOverOrderCap({ orderTokenAmount: amountInInt ?? null, cap: effectiveCap }),
1261
- [effectiveCap, amountInInt]
1348
+ () => reputation.isOverOrderCap({ orderTokenAmount: orderAmount, cap: effectiveCap }),
1349
+ [effectiveCap, orderAmount]
1262
1350
  );
1263
- const capExceededMessage = react.useMemo(() => {
1264
- if (!amountExceedsCap || effectiveCap === null) return null;
1351
+ const tierName = react.useMemo(
1352
+ () => hasLimits ? reputation.TAKER_TIERS[tier].name : null,
1353
+ [hasLimits, tier]
1354
+ );
1355
+ const providerName = react.useMemo(
1356
+ () => hasLimits ? payment.providerConfigs(chainId)[selectedPaymentMethod]?.name ?? selectedPaymentMethod : null,
1357
+ [hasLimits, chainId, selectedPaymentMethod]
1358
+ );
1359
+ const tierBaseCapUsd = react.useMemo(
1360
+ () => hasLimits && effectiveCap !== null && effectiveCap !== reputation.UNLIMITED_CAP_BASE_UNITS ? reputation.TAKER_TIERS[tier].baseCap : null,
1361
+ [hasLimits, tier, effectiveCap]
1362
+ );
1363
+ const capMultiplier = react.useMemo(
1364
+ () => hasLimits && effectiveCap !== null && effectiveCap !== reputation.UNLIMITED_CAP_BASE_UNITS ? payment.getPlatformRisk(chainId, selectedPaymentMethod).capMultiplier ?? 1 : null,
1365
+ [hasLimits, chainId, selectedPaymentMethod, effectiveCap]
1366
+ );
1367
+ const effectiveCapUsd = react.useMemo(() => {
1368
+ if (!hasLimits || effectiveCap === null) return null;
1265
1369
  if (effectiveCap === reputation.UNLIMITED_CAP_BASE_UNITS) return null;
1266
- const capUsd = reputation.getEffectiveCapDisplayUsd(effectiveCap, decimals) ?? 0;
1267
- const providerName = payment.providerConfigs(chainId)[selectedPaymentMethod]?.name ?? selectedPaymentMethod;
1268
- return `Amount exceeds your $${capUsd.toLocaleString()} limit for ${providerName}`;
1269
- }, [amountExceedsCap, effectiveCap, chainId, selectedPaymentMethod, decimals]);
1370
+ return reputation.getEffectiveCapDisplayUsd(effectiveCap, decimals) ?? null;
1371
+ }, [hasLimits, effectiveCap, decimals]);
1372
+ const capExceededMessage = react.useMemo(() => {
1373
+ if (!amountExceedsCap || effectiveCapUsd === null || providerName === null) return null;
1374
+ return `Amount exceeds your $${effectiveCapUsd.toLocaleString()} limit for ${providerName}`;
1375
+ }, [amountExceedsCap, effectiveCapUsd, providerName]);
1376
+ if (!hasLimits) return NO_LIMITS;
1270
1377
  return {
1271
1378
  hasLimits,
1272
1379
  noCooldownProviders,
@@ -1275,7 +1382,12 @@ function useReputationLimits({
1275
1382
  isOnCooldown,
1276
1383
  effectiveCap,
1277
1384
  amountExceedsCap,
1278
- capExceededMessage
1385
+ capExceededMessage,
1386
+ tierName,
1387
+ tierBaseCapUsd,
1388
+ capMultiplier,
1389
+ effectiveCapUsd,
1390
+ providerName
1279
1391
  };
1280
1392
  }
1281
1393
  function usePayeeDetails({
@@ -1312,7 +1424,8 @@ function usePayeeDetails({
1312
1424
  }, [indexedIntent]);
1313
1425
  const expiryTime = indexedIntent?.expiryTime ?? null;
1314
1426
  const depositQuery = reactQuery.useQuery({
1315
- queryKey: ["provex", "deposit", indexedIntent?.escrow, indexedIntent?.depositLocalId, chainId],
1427
+ // Query keys are hashed with JSON.stringify, which throws on bigint.
1428
+ queryKey: ["provex", "deposit", indexedIntent?.escrow, indexedIntent?.depositLocalId.toString(), chainId],
1316
1429
  queryFn: () => indexer.getDepositWithVerifiers({
1317
1430
  escrow: indexedIntent.escrow,
1318
1431
  localId: indexedIntent.depositLocalId,
@@ -1660,36 +1773,100 @@ function useNullifierRegistry({
1660
1773
  checkNullifierUsed
1661
1774
  };
1662
1775
  }
1663
- function useProvexProving(deps) {
1776
+ function encodeOrchestratorFulfill(attestation, verificationData, intentHash) {
1777
+ const args = [
1778
+ {
1779
+ paymentProof: utils.encodeV3PaymentProof(attestation),
1780
+ intentHash,
1781
+ verificationData,
1782
+ postIntentHookData: "0x"
1783
+ }
1784
+ ];
1785
+ return {
1786
+ args,
1787
+ calldata: viem.encodeFunctionData({ abi: abis.OrchestratorAbi, functionName: "fulfillIntent", args }),
1788
+ abi: abis.OrchestratorAbi
1789
+ };
1790
+ }
1791
+ function createWalletProvingClient(options) {
1664
1792
  const {
1665
- proveXClient,
1793
+ orchestratorAddress,
1794
+ chainId,
1795
+ writeContract,
1796
+ publicClient: publicClient2,
1666
1797
  checkNullifierUsed,
1798
+ getGasInputs,
1799
+ encodeFulfill = encodeOrchestratorFulfill,
1800
+ onPrefill
1801
+ } = options;
1802
+ return {
1803
+ chainId,
1804
+ async fulfillIntent(params) {
1805
+ const context = { chainId, request: void 0 };
1806
+ const gasInputs = await getGasInputs();
1807
+ const { args, calldata, abi } = encodeFulfill(
1808
+ params.attestation,
1809
+ params.verificationData,
1810
+ params.intentHash
1811
+ );
1812
+ params.onAwaitingSignature?.(context);
1813
+ const hash = await writeContract({
1814
+ address: orchestratorAddress,
1815
+ abi,
1816
+ functionName: "fulfillIntent",
1817
+ args,
1818
+ chainId,
1819
+ ...gasInputs
1820
+ });
1821
+ onPrefill?.(
1822
+ {
1823
+ to: orchestratorAddress,
1824
+ data: calldata,
1825
+ value: 0n,
1826
+ originalTip: gasInputs.maxPriorityFeePerGas
1827
+ },
1828
+ gasInputs,
1829
+ hash
1830
+ );
1831
+ params.onTransactionHash?.({ ...context, hash });
1832
+ const receipt = publicClient2 ? await publicClient2.waitForTransactionReceipt({ hash }) : void 0;
1833
+ return { hash, receipt };
1834
+ },
1835
+ async checkNullifierUsed({ nullifier }) {
1836
+ return checkNullifierUsed(nullifier);
1837
+ }
1838
+ };
1839
+ }
1840
+
1841
+ // src/hooks/useProvingFlow.ts
1842
+ function useProvingFlow(options) {
1843
+ const {
1844
+ rail,
1667
1845
  request,
1668
1846
  paymentMethodId,
1669
1847
  captureAdapter,
1670
1848
  attestationBaseUrl,
1849
+ attestationServiceUrl,
1671
1850
  onAwaitingSignature,
1672
1851
  onTransactionHash,
1673
1852
  onComplete
1674
- } = deps;
1675
- const client = react.useMemo(
1676
- () => ({
1677
- // Pin the rail to the intent's chain. request.chainId is the single chain
1678
- // that drives both useNullifierRegistry (the read backing checkNullifierUsed)
1679
- // and ProveXClient.fulfillIntent here, so useProving's chain-pin assertion
1680
- // is a tautology in correct wiring and a tripwire if they ever diverge.
1681
- chainId: request.chainId,
1682
- fulfillIntent: proveXClient.fulfillIntent,
1683
- checkNullifierUsed: ({ nullifier }) => checkNullifierUsed(nullifier)
1684
- }),
1685
- [proveXClient, checkNullifierUsed, request.chainId]
1686
- );
1853
+ } = options;
1854
+ const chainId = options.chainId ?? request.chainId;
1855
+ const client = react.useMemo(() => {
1856
+ if (rail.kind === "wallet") return createWalletProvingClient({ ...rail, chainId });
1857
+ return {
1858
+ chainId,
1859
+ fulfillIntent: rail.fulfillIntent,
1860
+ checkNullifierUsed: ({ nullifier }) => rail.checkNullifierUsed(nullifier)
1861
+ };
1862
+ }, [rail, chainId]);
1687
1863
  const resolvedCaptureAdapter = react.useMemo(
1688
1864
  () => captureAdapter ?? extensionReact.createExtensionEnclaveAdapter({
1689
1865
  getPeer: () => window.peer,
1690
- attestationBaseUrl
1866
+ attestationBaseUrl,
1867
+ ...attestationServiceUrl ? { attestationServiceUrl } : {}
1691
1868
  }),
1692
- [captureAdapter, attestationBaseUrl]
1869
+ [captureAdapter, attestationBaseUrl, attestationServiceUrl]
1693
1870
  );
1694
1871
  const manualSelect = proving.useManualTransactionSelect();
1695
1872
  const { state, authenticate, fulfill, retryVerification, retrySubmission } = proving.useProving({
@@ -1705,6 +1882,17 @@ function useProvexProving(deps) {
1705
1882
  return { state, authenticate, fulfill, retryVerification, retrySubmission, manualSelect };
1706
1883
  }
1707
1884
 
1885
+ // src/hooks/useProvexProving.ts
1886
+ function useProvexProving(deps) {
1887
+ const { proveXClient, checkNullifierUsed, ...flowOptions } = deps;
1888
+ const { fulfillIntent } = proveXClient;
1889
+ const rail = react.useMemo(
1890
+ () => ({ kind: "client", fulfillIntent, checkNullifierUsed }),
1891
+ [fulfillIntent, checkNullifierUsed]
1892
+ );
1893
+ return useProvingFlow({ rail, ...flowOptions });
1894
+ }
1895
+
1708
1896
  // src/useProvexBuy.ts
1709
1897
  function parseAmountSafe(value, decimals) {
1710
1898
  if (!value || value === "." || value === "0.") return null;
@@ -1746,10 +1934,8 @@ function useProvexBuy({
1746
1934
  paymentMethod: activePaymentMethod,
1747
1935
  currency
1748
1936
  });
1749
- const { reputation: reputation$1 } = useReputation({
1750
- address: wallet.address,
1751
- chainId
1752
- });
1937
+ const { reputation } = useUserReputation({
1938
+ address: wallet.address});
1753
1939
  const amountBigInt = react.useMemo(
1754
1940
  () => parseAmountSafe(amount, currency.decimals),
1755
1941
  [amount, currency.decimals]
@@ -1785,12 +1971,11 @@ function useProvexBuy({
1785
1971
  refetchAllRef.current = payeeResult.refetchAll;
1786
1972
  react.useEffect(() => {
1787
1973
  if (phase !== "committed") return;
1788
- if (!intentHash) return;
1789
1974
  const interval = setInterval(() => {
1790
1975
  refetchAllRef.current();
1791
1976
  }, 5e3);
1792
1977
  return () => clearInterval(interval);
1793
- }, [phase, intentHash]);
1978
+ }, [phase]);
1794
1979
  react.useEffect(() => {
1795
1980
  if (!payeeResult.isPruned) return;
1796
1981
  if (phase === "browse") return;
@@ -1830,6 +2015,7 @@ function useProvexBuy({
1830
2015
  payeeDetailsHash,
1831
2016
  token,
1832
2017
  intent: {
2018
+ owner: intent.owner,
1833
2019
  amount: intent.amount,
1834
2020
  timestamp: intent.timestamp,
1835
2021
  paymentMethod: intent.paymentMethod,
@@ -1848,6 +2034,7 @@ function useProvexBuy({
1848
2034
  token: token ?? { decimals: 18 },
1849
2035
  intent: {
1850
2036
  intentHash: intentHash ?? "0x",
2037
+ owner: "0x",
1851
2038
  amount: "0",
1852
2039
  timestampMs: "0",
1853
2040
  paymentMethod: "0x",
@@ -1909,10 +2096,10 @@ function useProvexBuy({
1909
2096
  const limits = useReputationLimits({
1910
2097
  chainId,
1911
2098
  selectedPaymentMethod: activePaymentMethod,
1912
- tier: reputation$1.tier,
2099
+ tier: reputation.tier,
1913
2100
  decimals: tokenDecimals,
1914
- amountInInt: tokenAmount,
1915
- cooldownEndsAt: reputation$1.cooldownEndsAt
2101
+ orderTokenAmount: tokenAmount,
2102
+ cooldownEndsAt: reputation.cooldownEndsAt
1916
2103
  });
1917
2104
  const rateDisplay = react.useMemo(() => {
1918
2105
  if (!bestRate || !token) return null;
@@ -1998,7 +2185,7 @@ function useProvexBuy({
1998
2185
  tokenAmountDisplay,
1999
2186
  token,
2000
2187
  currency,
2001
- reputation: reputation$1 ?? reputation.DEFAULT_REPUTATION,
2188
+ reputation,
2002
2189
  limits,
2003
2190
  feePercentage: feeInfo?.percentage ?? null,
2004
2191
  payeeDetails: payeeResult.payeeDetails,