@oasisprotocol/privana-sdk 0.4.0 → 0.4.1

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/on-ramp.cjs CHANGED
@@ -1,10 +1,11 @@
1
1
  "use client";
2
2
  'use strict';
3
3
 
4
- var chunkRDMJGMI3_cjs = require('./chunk-RDMJGMI3.cjs');
4
+ var chunkCRJNDCDT_cjs = require('./chunk-CRJNDCDT.cjs');
5
5
  var react = require('react');
6
6
  var viem = require('viem');
7
7
  var wagmi = require('wagmi');
8
+ var lucideReact = require('lucide-react');
8
9
  var moonpayReact = require('@moonpay/moonpay-react');
9
10
  var jsxRuntime = require('react/jsx-runtime');
10
11
 
@@ -20,8 +21,8 @@ function useFiatOnRamp(options) {
20
21
  const deliveryPollInterval = options.deliveryPollInterval ?? 3e3;
21
22
  const verificationTimeout = options.verificationTimeout ?? DEFAULT_VERIFICATION_TIMEOUT_MS;
22
23
  const finalityRetryInterval = options.finalityRetryInterval ?? DEFAULT_FINALITY_RETRY_INTERVAL_MS;
23
- const { client, enabledTokens } = chunkRDMJGMI3_cjs.usePrivanaContext();
24
- const { executePrivateRead, privateReadReady } = chunkRDMJGMI3_cjs.usePrivateReadRequest();
24
+ const { client, enabledTokens } = chunkCRJNDCDT_cjs.usePrivanaContext();
25
+ const { executePrivateRead, privateReadReady } = chunkCRJNDCDT_cjs.usePrivateReadRequest();
25
26
  const wagmiConfig = wagmi.useConfig();
26
27
  const selectedToken = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
27
28
  const [status, setStatus] = react.useState("idle");
@@ -30,6 +31,7 @@ function useFiatOnRamp(options) {
30
31
  const [depositAddress, setDepositAddress] = react.useState();
31
32
  const [minDepositBaseUnits, setMinDepositBaseUnits] = react.useState();
32
33
  const [activeIntentId, setActiveIntentId] = react.useState(null);
34
+ const [finalityProgress, setFinalityProgress] = react.useState({});
33
35
  const onCreditedRef = react.useRef(onCredited);
34
36
  const onErrorRef = react.useRef(onError);
35
37
  const onDebugEventRef = react.useRef(onDebugEvent);
@@ -39,6 +41,7 @@ function useFiatOnRamp(options) {
39
41
  const activeVerificationKeyRef = react.useRef(null);
40
42
  const triggeredVerificationKeysRef = react.useRef(/* @__PURE__ */ new Set());
41
43
  const closeReconcilePromiseRef = react.useRef(null);
44
+ const purchaseInitiatedRef = react.useRef(false);
42
45
  react.useEffect(() => {
43
46
  onCreditedRef.current = onCredited;
44
47
  onErrorRef.current = onError;
@@ -130,17 +133,36 @@ function useFiatOnRamp(options) {
130
133
  activeVerificationKeyRef.current = null;
131
134
  activeVerificationRecordRef.current = null;
132
135
  }, []);
133
- const { verify } = chunkRDMJGMI3_cjs.useDepositVerification({
136
+ const { verify } = chunkCRJNDCDT_cjs.useDepositVerification({
134
137
  pollTimeout: verificationTimeout,
135
138
  pollInterval: options.verificationPollInterval,
136
139
  finalityRetryInterval,
140
+ onCheckRetry: (message) => {
141
+ const record = activeVerificationRecordRef.current;
142
+ if (!record) return;
143
+ emitDebug("verification:check-retry", {
144
+ message,
145
+ record: summariseOnRampRecord(record)
146
+ });
147
+ setFinalityProgress((prev) => ({ ...prev, [record.transaction_id]: message }));
148
+ },
137
149
  onCredited: (depositTxHash) => {
138
150
  const record = activeVerificationRecordRef.current;
139
151
  emitDebug("verification:credited", {
140
152
  depositTxHash,
141
153
  record: record ? summariseOnRampRecord(record) : null
142
154
  });
143
- setStatus("credited");
155
+ if (record && activeIntentIdRef.current === record.transaction_id) {
156
+ setStatus("credited");
157
+ }
158
+ if (record) {
159
+ setFinalityProgress((prev) => {
160
+ if (!(record.transaction_id in prev)) return prev;
161
+ const next = { ...prev };
162
+ delete next[record.transaction_id];
163
+ return next;
164
+ });
165
+ }
144
166
  void (async () => {
145
167
  try {
146
168
  if (record && depositTxHash.startsWith("0x")) {
@@ -172,21 +194,27 @@ function useFiatOnRamp(options) {
172
194
  onCreditedRef.current?.(depositTxHash);
173
195
  },
174
196
  onCheckTimeout: (depositTxHash) => {
197
+ const record = activeVerificationRecordRef.current;
175
198
  const err = new Error(
176
199
  "Privana verification is still pending. Retry from the pending on-ramp list if it does not complete."
177
200
  );
178
201
  emitDebug("verification:timeout", { depositTxHash, message: err.message });
179
202
  clearActiveVerification();
180
- setStatus("failed");
181
- setError(err);
203
+ if (!record || activeIntentIdRef.current === record.transaction_id) {
204
+ setStatus("failed");
205
+ setError(err);
206
+ }
182
207
  void refreshPending();
183
208
  onErrorRef.current?.(err);
184
209
  },
185
210
  onError: (err) => {
211
+ const record = activeVerificationRecordRef.current;
186
212
  emitDebug("verification:error", errorPayload(err));
187
213
  clearActiveVerification();
188
- setStatus("failed");
189
- setError(err);
214
+ if (!record || activeIntentIdRef.current === record.transaction_id) {
215
+ setStatus("failed");
216
+ setError(err);
217
+ }
190
218
  onErrorRef.current?.(err);
191
219
  }
192
220
  });
@@ -198,6 +226,7 @@ function useFiatOnRamp(options) {
198
226
  }) => {
199
227
  try {
200
228
  setError(null);
229
+ purchaseInitiatedRef.current = false;
201
230
  const token = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
202
231
  if (!token) throw new Error(`Unknown token: ${tokenId}`);
203
232
  if (!depositAddress) throw new Error("Privana deposit address is not ready");
@@ -280,6 +309,7 @@ function useFiatOnRamp(options) {
280
309
  const handleTransactionCreated = react.useCallback(
281
310
  async (props) => {
282
311
  emitDebug("moonpay:onTransactionCreated", summariseMoonPayEventProps(props));
312
+ purchaseInitiatedRef.current = true;
283
313
  await registerOnRampTokenMapping(props.id);
284
314
  },
285
315
  [emitDebug, registerOnRampTokenMapping]
@@ -358,6 +388,12 @@ function useFiatOnRamp(options) {
358
388
  triggeredVerificationKeysRef.current.add(verificationKey);
359
389
  activeVerificationKeyRef.current = verificationKey;
360
390
  activeVerificationRecordRef.current = record;
391
+ setFinalityProgress((prev) => {
392
+ if (!(record.transaction_id in prev)) return prev;
393
+ const next = { ...prev };
394
+ delete next[record.transaction_id];
395
+ return next;
396
+ });
361
397
  emitDebug("verification:start", {
362
398
  verificationKey,
363
399
  record: summariseOnRampRecord(record)
@@ -398,7 +434,9 @@ function useFiatOnRamp(options) {
398
434
  `Delivered amount (${record.quote_currency_amount}) is below the minimum deposit.`
399
435
  );
400
436
  }
401
- setStatus("verifying");
437
+ if (activeIntentIdRef.current === record.transaction_id) {
438
+ setStatus("verifying");
439
+ }
402
440
  emitDebug("verification:check-deposit-request", {
403
441
  hash: record.on_chain_tx_hash,
404
442
  chainId: record.chain_id,
@@ -466,6 +504,17 @@ function useFiatOnRamp(options) {
466
504
  await refreshPending();
467
505
  return;
468
506
  }
507
+ if (!purchaseInitiatedRef.current) {
508
+ emitDebug("moonpay:widget-closed-without-purchase", {
509
+ previousStatus,
510
+ transactionId
511
+ });
512
+ await refreshPending();
513
+ if (previousStatus === "awaiting-purchase" || previousStatus === "awaiting-delivery") {
514
+ setStatus("idle");
515
+ }
516
+ return;
517
+ }
469
518
  try {
470
519
  setStatus("awaiting-delivery");
471
520
  const record = await waitForOnChainHash(transactionId);
@@ -504,15 +553,39 @@ function useFiatOnRamp(options) {
504
553
  setStatus("failed");
505
554
  setError(e);
506
555
  onErrorRef.current?.(e);
556
+ throw e;
507
557
  }
508
558
  },
509
559
  [emitDebug, triggerVerification]
510
560
  );
561
+ const triggerVerificationRef = react.useRef(triggerVerification);
562
+ react.useEffect(() => {
563
+ triggerVerificationRef.current = triggerVerification;
564
+ }, [triggerVerification]);
565
+ react.useEffect(() => {
566
+ let cancelled = false;
567
+ void (async () => {
568
+ for (const record of pending) {
569
+ if (cancelled) break;
570
+ if (!record.on_chain_tx_hash || !record.quote_currency_amount) continue;
571
+ const key = getOnRampVerificationKey(record);
572
+ if (triggeredVerificationKeysRef.current.has(key)) continue;
573
+ try {
574
+ await triggerVerificationRef.current(record);
575
+ } catch {
576
+ }
577
+ }
578
+ })();
579
+ return () => {
580
+ cancelled = true;
581
+ };
582
+ }, [pending]);
511
583
  return {
512
584
  status,
513
585
  activeIntentId,
514
586
  pending,
515
587
  error,
588
+ finalityProgress,
516
589
  depositAddress,
517
590
  minDepositBaseUnits,
518
591
  selectedToken,
@@ -564,9 +637,11 @@ async function resolveDeliveredAmount({
564
637
  }
565
638
  let receiptError;
566
639
  try {
567
- const receipt = await chunkRDMJGMI3_cjs.getTransactionReceipt(wagmiConfig, {
640
+ const receipt = await chunkCRJNDCDT_cjs.waitForTransactionReceipt(wagmiConfig, {
568
641
  hash: onChainTxHash,
569
- chainId
642
+ chainId,
643
+ timeout: 6e4,
644
+ pollingInterval: 4e3
570
645
  });
571
646
  let delivered = 0n;
572
647
  for (const log of receipt.logs) {
@@ -671,11 +746,13 @@ function FiatOnRampForm({
671
746
  const { address } = wagmi.useAccount();
672
747
  const [visible, setVisible] = react.useState(false);
673
748
  const [isPreparing, setIsPreparing] = react.useState(false);
749
+ const [rowError, setRowError] = react.useState(null);
674
750
  const {
675
751
  status,
676
752
  activeIntentId,
677
753
  pending,
678
754
  error,
755
+ finalityProgress,
679
756
  depositAddress,
680
757
  minDepositBaseUnits,
681
758
  selectedToken,
@@ -702,15 +779,17 @@ function FiatOnRampForm({
702
779
  );
703
780
  const minFiatGate = minDepositBaseUnits !== void 0 && decimals !== void 0 ? Number(viem.formatUnits(minDepositBaseUnits, decimals)) * 1.05 : void 0;
704
781
  const isBelowMin = minFiatGate !== void 0 && Number(defaultBaseCurrencyAmount) < minFiatGate;
705
- const isBusy = isPreparing || status === "awaiting-purchase" || status === "awaiting-delivery" || status === "verifying";
782
+ const isBusy = isPreparing || status === "awaiting-purchase";
783
+ const isInitializing = !!address && !depositAddress;
706
784
  const blockReasons = react.useMemo(
707
785
  () => [
708
786
  !address ? "wallet-not-connected" : null,
709
787
  !depositAddress ? "deposit-address-not-loaded" : null,
710
788
  isBusy ? `busy:${isPreparing ? "preparing" : status}` : null,
789
+ visible ? "widget-open" : null,
711
790
  isBelowMin ? "below-minimum" : null
712
791
  ].filter((reason) => Boolean(reason)),
713
- [address, depositAddress, isBelowMin, isBusy, isPreparing, status]
792
+ [address, depositAddress, isBelowMin, isBusy, isPreparing, status, visible]
714
793
  );
715
794
  const canBuy = blockReasons.length === 0;
716
795
  const handleOpen = react.useCallback(async () => {
@@ -785,11 +864,10 @@ function FiatOnRampForm({
785
864
  const handleReady = react.useCallback(async () => {
786
865
  emitFormDebug("moonpay:onReady");
787
866
  }, [emitFormDebug]);
788
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4", children: [
789
- /* @__PURE__ */ jsxRuntime.jsx(chunkRDMJGMI3_cjs.Button, { type: "button", onClick: handleOpen, children: isPreparing ? "Preparing MoonPay\u2026" : isBusy ? statusLabel(status) : `Buy ${displaySymbol}` }),
790
- !canBuy && !isBusy && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground text-xs", children: [
791
- "Button blocked: ",
792
- blockReasons.join(", ")
867
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-privana": true, className: "flex flex-col gap-4", children: [
868
+ isInitializing ? /* @__PURE__ */ jsxRuntime.jsx(chunkCRJNDCDT_cjs.Skeleton, { className: "h-9 w-full rounded-md" }) : /* @__PURE__ */ jsxRuntime.jsxs(chunkCRJNDCDT_cjs.Button, { type: "button", onClick: handleOpen, disabled: !canBuy, children: [
869
+ (isBusy || visible) && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "animate-spin", "aria-hidden": true }),
870
+ "Buy"
793
871
  ] }),
794
872
  error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-destructive text-sm", role: "alert", children: error.message }),
795
873
  isBelowMin && minFiatGate !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-destructive text-sm", role: "alert", children: [
@@ -798,24 +876,54 @@ function FiatOnRampForm({
798
876
  "."
799
877
  ] }),
800
878
  pending.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [
801
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: "Finish verification" }),
802
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-xs", children: "MoonPay delivered your purchase, but Privana hasn't verified it yet. Resume verification now to credit your balance." }),
803
- pending.map((record) => /* @__PURE__ */ jsxRuntime.jsxs(
804
- chunkRDMJGMI3_cjs.Button,
805
- {
806
- type: "button",
807
- variant: "outline",
808
- size: "sm",
809
- disabled: isBusy,
810
- onClick: () => finishPendingVerification(record),
811
- children: [
812
- record.quote_currency_amount ?? "?",
813
- " ",
814
- displaySymbol
815
- ]
816
- },
817
- record.transaction_id
818
- ))
879
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: "Pending payments" }),
880
+ pending.map((record) => {
881
+ const progress = parseFinalityProgress(finalityProgress[record.transaction_id]);
882
+ const hasProgress = !!finalityProgress[record.transaction_id];
883
+ const isStalled = !hasProgress && Date.now() / 1e3 - (record.updated_at ?? 0) > 60;
884
+ const showRetry = rowError?.id === record.transaction_id || isStalled;
885
+ return /* @__PURE__ */ jsxRuntime.jsxs(
886
+ "div",
887
+ {
888
+ className: "border-border flex flex-col gap-1 rounded-md border p-2",
889
+ children: [
890
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
891
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground flex items-center gap-1 text-xs", children: [
892
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-3 animate-spin", "aria-hidden": true }),
893
+ progress ?? "Verifying\u2026"
894
+ ] }),
895
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground text-xs", children: [
896
+ record.quote_currency_amount ?? "?",
897
+ " ",
898
+ displaySymbol
899
+ ] })
900
+ ] }),
901
+ rowError?.id === record.transaction_id && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-destructive text-xs", children: rowError.message }),
902
+ showRetry && /* @__PURE__ */ jsxRuntime.jsx(
903
+ chunkCRJNDCDT_cjs.Button,
904
+ {
905
+ type: "button",
906
+ variant: "outline",
907
+ size: "sm",
908
+ onClick: async () => {
909
+ setRowError(null);
910
+ try {
911
+ await finishPendingVerification(record);
912
+ } catch (err) {
913
+ setRowError({
914
+ id: record.transaction_id,
915
+ message: err instanceof Error ? err.message : "Verification failed"
916
+ });
917
+ }
918
+ },
919
+ children: "Retry"
920
+ }
921
+ )
922
+ ]
923
+ },
924
+ record.transaction_id
925
+ );
926
+ })
819
927
  ] }),
820
928
  visible && depositAddress && activeIntentId && /* @__PURE__ */ jsxRuntime.jsx(
821
929
  moonpayReact.MoonPayBuyWidget,
@@ -838,21 +946,10 @@ function FiatOnRampForm({
838
946
  )
839
947
  ] });
840
948
  }
841
- function statusLabel(status) {
842
- switch (status) {
843
- case "awaiting-purchase":
844
- return "Complete your purchase\u2026";
845
- case "awaiting-delivery":
846
- return "Waiting for on-chain delivery\u2026";
847
- case "verifying":
848
- return "Verifying with Privana\u2026";
849
- case "credited":
850
- return "Credited";
851
- case "failed":
852
- return "Failed";
853
- default:
854
- return "";
855
- }
949
+ function parseFinalityProgress(message) {
950
+ if (!message) return null;
951
+ const match = message.match(/(\d+\/\d+)\s+confirmations/i);
952
+ return match ? `${match[1]} confirmations` : null;
856
953
  }
857
954
 
858
955
  exports.FiatOnRampForm = FiatOnRampForm;