@t402/vue 2.3.0 → 2.4.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/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # @t402/vue
2
+
3
+ Vue components and composables for the t402 Payment Protocol. Build payment-aware UIs with pre-built components and reactive composables.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @t402/vue
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```vue
14
+ <script setup lang="ts">
15
+ import { PaymentButton, usePaymentRequired } from '@t402/vue'
16
+
17
+ const { paymentRequired, requirements, pay, status } = usePaymentRequired('/api/data')
18
+ </script>
19
+
20
+ <template>
21
+ <div v-if="paymentRequired">
22
+ <PaymentButton :requirements="requirements" :status="status" @pay="pay" />
23
+ </div>
24
+ <div v-else>Premium content loaded!</div>
25
+ </template>
26
+ ```
27
+
28
+ ## Components
29
+
30
+ ### PaymentButton
31
+
32
+ Renders a payment action button with loading and status states.
33
+
34
+ ```vue
35
+ <PaymentButton :requirements="requirements" :status="status" @pay="handlePay" />
36
+ ```
37
+
38
+ ### PaymentDetails
39
+
40
+ Displays payment requirements (amount, network, recipient).
41
+
42
+ ```vue
43
+ <PaymentDetails :requirements="requirements" />
44
+ ```
45
+
46
+ ### PaymentStatusDisplay
47
+
48
+ Shows the current payment status with appropriate UI feedback.
49
+
50
+ ```vue
51
+ <PaymentStatusDisplay :status="status" />
52
+ ```
53
+
54
+ ### AddressDisplay
55
+
56
+ Renders a blockchain address with truncation and copy functionality.
57
+
58
+ ```vue
59
+ <AddressDisplay address="0x1234...5678" />
60
+ ```
61
+
62
+ ## Composables
63
+
64
+ ### usePaymentRequired
65
+
66
+ Detects 402 responses and extracts payment requirements.
67
+
68
+ ```typescript
69
+ const {
70
+ paymentRequired, // Ref<boolean>
71
+ requirements, // Ref<PaymentRequirements[]>
72
+ pay, // () => Promise<void>
73
+ status, // Ref<PaymentStatus>
74
+ } = usePaymentRequired(url, options)
75
+ ```
76
+
77
+ ### usePaymentStatus
78
+
79
+ Tracks the lifecycle of a payment (idle, pending, confirming, complete, error).
80
+
81
+ ```typescript
82
+ const { status, error, reset } = usePaymentStatus()
83
+ ```
84
+
85
+ ### useAsyncPayment
86
+
87
+ Manages async payment flows with automatic retry and status tracking.
88
+
89
+ ```typescript
90
+ const { execute, status, error } = useAsyncPayment(paymentFn)
91
+ ```
92
+
93
+ ## Utilities
94
+
95
+ ```typescript
96
+ import {
97
+ isEvmNetwork,
98
+ isSvmNetwork,
99
+ isTonNetwork,
100
+ getNetworkDisplayName,
101
+ truncateAddress,
102
+ formatTokenAmount,
103
+ choosePaymentRequirement,
104
+ } from '@t402/vue'
105
+
106
+ // Network detection
107
+ isEvmNetwork('eip155:8453') // true
108
+
109
+ // Display helpers
110
+ getNetworkDisplayName('eip155:8453') // "Base"
111
+ truncateAddress('0x1234567890abcdef') // "0x1234...cdef"
112
+ formatTokenAmount(1500000n, 6) // "1.50"
113
+ ```
114
+
115
+ ## Peer Dependencies
116
+
117
+ - `vue` ^3.3.0
118
+
119
+ ## Development
120
+
121
+ ```bash
122
+ pnpm build # Build the package
123
+ pnpm test # Run unit tests
124
+ ```
125
+
126
+ ## Related Packages
127
+
128
+ - `@t402/core` - Core protocol types
129
+ - `@t402/react` - React components & hooks
130
+ - `@t402/paywall` - Universal paywall UI
131
+ - `@t402/fetch` - Fetch wrapper with automatic payment
@@ -294,17 +294,17 @@ function getNetworkDisplayName(network) {
294
294
  return EVM_CHAIN_NAMES[chainId] ?? `Chain ${chainId}`;
295
295
  }
296
296
  if (network.startsWith("solana:")) {
297
- const ref6 = network.split(":")[1];
298
- return ref6 === SOLANA_NETWORK_REFS.DEVNET ? "Solana Devnet" : "Solana";
297
+ const ref9 = network.split(":")[1];
298
+ return ref9 === SOLANA_NETWORK_REFS.DEVNET ? "Solana Devnet" : "Solana";
299
299
  }
300
300
  if (network.startsWith("ton:")) {
301
- const ref6 = network.split(":")[1];
302
- return ref6 === TON_NETWORK_REFS.TESTNET ? "TON Testnet" : "TON";
301
+ const ref9 = network.split(":")[1];
302
+ return ref9 === TON_NETWORK_REFS.TESTNET ? "TON Testnet" : "TON";
303
303
  }
304
304
  if (network.startsWith("tron:")) {
305
- const ref6 = network.split(":")[1];
306
- if (ref6 === TRON_NETWORK_REFS.NILE) return "TRON Nile";
307
- if (ref6 === TRON_NETWORK_REFS.SHASTA) return "TRON Shasta";
305
+ const ref9 = network.split(":")[1];
306
+ if (ref9 === TRON_NETWORK_REFS.NILE) return "TRON Nile";
307
+ if (ref9 === TRON_NETWORK_REFS.SHASTA) return "TRON Shasta";
308
308
  return "TRON";
309
309
  }
310
310
  return network;
@@ -316,16 +316,16 @@ function isTestnetNetwork(network) {
316
316
  return TESTNET_CHAIN_IDS.has(chainId);
317
317
  }
318
318
  if (network.startsWith("solana:")) {
319
- const ref6 = network.split(":")[1];
320
- return ref6 === SOLANA_NETWORK_REFS.DEVNET;
319
+ const ref9 = network.split(":")[1];
320
+ return ref9 === SOLANA_NETWORK_REFS.DEVNET;
321
321
  }
322
322
  if (network.startsWith("ton:")) {
323
- const ref6 = network.split(":")[1];
324
- return ref6 === TON_NETWORK_REFS.TESTNET;
323
+ const ref9 = network.split(":")[1];
324
+ return ref9 === TON_NETWORK_REFS.TESTNET;
325
325
  }
326
326
  if (network.startsWith("tron:")) {
327
- const ref6 = network.split(":")[1];
328
- return ref6 === TRON_NETWORK_REFS.NILE || ref6 === TRON_NETWORK_REFS.SHASTA;
327
+ const ref9 = network.split(":")[1];
328
+ return ref9 === TRON_NETWORK_REFS.NILE || ref9 === TRON_NETWORK_REFS.SHASTA;
329
329
  }
330
330
  return false;
331
331
  }
@@ -716,6 +716,229 @@ function useAsyncPayment(options) {
716
716
  reset
717
717
  };
718
718
  }
719
+ function useGaslessPayment(options) {
720
+ const { payFn, onSuccess, onError, autoWait = true } = options;
721
+ const status = vue.ref("idle");
722
+ const userOpHash = vue.ref(null);
723
+ const txHash = vue.ref(null);
724
+ const sponsored = vue.ref(null);
725
+ const error = vue.ref(null);
726
+ const isLoading = vue.computed(() => status.value === "loading");
727
+ const isSuccess = vue.computed(() => status.value === "success");
728
+ const isError = vue.computed(() => status.value === "error");
729
+ const pay = async (params) => {
730
+ status.value = "loading";
731
+ error.value = null;
732
+ userOpHash.value = null;
733
+ txHash.value = null;
734
+ sponsored.value = null;
735
+ try {
736
+ const result = await payFn(params);
737
+ userOpHash.value = result.userOpHash;
738
+ sponsored.value = result.sponsored;
739
+ if (autoWait) {
740
+ const receipt = await result.wait();
741
+ txHash.value = receipt.txHash;
742
+ status.value = "success";
743
+ onSuccess?.(receipt);
744
+ } else {
745
+ status.value = "success";
746
+ onSuccess?.({ txHash: result.userOpHash, success: true });
747
+ }
748
+ } catch (err) {
749
+ const errorMessage = err instanceof Error ? err.message : "Gasless payment failed";
750
+ error.value = errorMessage;
751
+ status.value = "error";
752
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
753
+ }
754
+ };
755
+ const reset = () => {
756
+ status.value = "idle";
757
+ userOpHash.value = null;
758
+ txHash.value = null;
759
+ sponsored.value = null;
760
+ error.value = null;
761
+ };
762
+ return {
763
+ pay,
764
+ status,
765
+ userOpHash,
766
+ txHash,
767
+ sponsored,
768
+ error,
769
+ isLoading,
770
+ isSuccess,
771
+ isError,
772
+ reset
773
+ };
774
+ }
775
+ function useBridgePayment(options) {
776
+ const { bridgeFn, autoBridgeFn, onSuccess, onError, autoWaitForDelivery = false } = options;
777
+ const status = vue.ref("idle");
778
+ const txHash = vue.ref(null);
779
+ const messageGuid = vue.ref(null);
780
+ const dstTxHash = vue.ref(null);
781
+ const error = vue.ref(null);
782
+ const isLoading = vue.computed(
783
+ () => status.value === "bridging" || status.value === "quoting" || status.value === "waiting"
784
+ );
785
+ const isSuccess = vue.computed(() => status.value === "success");
786
+ const isError = vue.computed(() => status.value === "error");
787
+ const executeBridge = async (bridgeCall) => {
788
+ status.value = "bridging";
789
+ error.value = null;
790
+ txHash.value = null;
791
+ messageGuid.value = null;
792
+ dstTxHash.value = null;
793
+ try {
794
+ const result = await bridgeCall();
795
+ txHash.value = result.txHash;
796
+ messageGuid.value = result.messageGuid;
797
+ if (autoWaitForDelivery) {
798
+ status.value = "waiting";
799
+ const delivery = await result.waitForDelivery();
800
+ if (delivery.dstTxHash) dstTxHash.value = delivery.dstTxHash;
801
+ status.value = "success";
802
+ onSuccess?.({
803
+ txHash: result.txHash,
804
+ dstTxHash: delivery.dstTxHash,
805
+ fromChain: result.fromChain,
806
+ toChain: result.toChain
807
+ });
808
+ } else {
809
+ status.value = "success";
810
+ onSuccess?.({
811
+ txHash: result.txHash,
812
+ fromChain: result.fromChain,
813
+ toChain: result.toChain
814
+ });
815
+ }
816
+ } catch (err) {
817
+ const errorMessage = err instanceof Error ? err.message : "Bridge operation failed";
818
+ error.value = errorMessage;
819
+ status.value = "error";
820
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
821
+ }
822
+ };
823
+ const bridge = async (params) => {
824
+ await executeBridge(() => bridgeFn(params));
825
+ };
826
+ const autoBridge = async (params) => {
827
+ const fn = autoBridgeFn ?? (() => {
828
+ throw new Error("autoBridgeFn not provided");
829
+ });
830
+ await executeBridge(() => fn(params));
831
+ };
832
+ const reset = () => {
833
+ status.value = "idle";
834
+ txHash.value = null;
835
+ messageGuid.value = null;
836
+ dstTxHash.value = null;
837
+ error.value = null;
838
+ };
839
+ return {
840
+ bridge,
841
+ autoBridge,
842
+ status,
843
+ txHash,
844
+ messageGuid,
845
+ dstTxHash,
846
+ error,
847
+ isLoading,
848
+ isSuccess,
849
+ isError,
850
+ reset
851
+ };
852
+ }
853
+ function useMultiSigPayment(options) {
854
+ const { initiateFn, submitFn, onSuccess, onError, autoWait = true } = options;
855
+ const status = vue.ref("idle");
856
+ const requestId = vue.ref(null);
857
+ const userOpHash = vue.ref(null);
858
+ const txHash = vue.ref(null);
859
+ const threshold = vue.ref(0);
860
+ const collectedCount = vue.ref(0);
861
+ const isReady = vue.ref(false);
862
+ const error = vue.ref(null);
863
+ const isLoading = vue.computed(() => status.value === "initiating" || status.value === "submitting");
864
+ const isSuccess = vue.computed(() => status.value === "success");
865
+ const isError = vue.computed(() => status.value === "error");
866
+ const initiate = async (params) => {
867
+ status.value = "initiating";
868
+ error.value = null;
869
+ requestId.value = null;
870
+ userOpHash.value = null;
871
+ txHash.value = null;
872
+ try {
873
+ const result = await initiateFn(params);
874
+ requestId.value = result.requestId;
875
+ userOpHash.value = result.userOpHash;
876
+ threshold.value = result.threshold;
877
+ collectedCount.value = result.collectedCount;
878
+ isReady.value = result.isReady;
879
+ status.value = "collecting";
880
+ } catch (err) {
881
+ const errorMessage = err instanceof Error ? err.message : "Failed to initiate multi-sig payment";
882
+ error.value = errorMessage;
883
+ status.value = "error";
884
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
885
+ }
886
+ };
887
+ const submit = async () => {
888
+ if (!requestId.value) {
889
+ const err = new Error("No active request to submit");
890
+ error.value = err.message;
891
+ status.value = "error";
892
+ onError?.(err);
893
+ return;
894
+ }
895
+ status.value = "submitting";
896
+ error.value = null;
897
+ try {
898
+ const result = await submitFn(requestId.value);
899
+ if (autoWait) {
900
+ const receipt = await result.wait();
901
+ txHash.value = receipt.txHash;
902
+ status.value = "success";
903
+ onSuccess?.(receipt);
904
+ } else {
905
+ status.value = "success";
906
+ onSuccess?.({ txHash: result.userOpHash, success: true });
907
+ }
908
+ } catch (err) {
909
+ const errorMessage = err instanceof Error ? err.message : "Failed to submit multi-sig transaction";
910
+ error.value = errorMessage;
911
+ status.value = "error";
912
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
913
+ }
914
+ };
915
+ const reset = () => {
916
+ status.value = "idle";
917
+ requestId.value = null;
918
+ userOpHash.value = null;
919
+ txHash.value = null;
920
+ threshold.value = 0;
921
+ collectedCount.value = 0;
922
+ isReady.value = false;
923
+ error.value = null;
924
+ };
925
+ return {
926
+ initiate,
927
+ submit,
928
+ status,
929
+ requestId,
930
+ userOpHash,
931
+ txHash,
932
+ threshold,
933
+ collectedCount,
934
+ isReady,
935
+ error,
936
+ isLoading,
937
+ isSuccess,
938
+ isError,
939
+ reset
940
+ };
941
+ }
719
942
 
720
943
  exports.AddressDisplay = AddressDisplay;
721
944
  exports.EVM_CHAIN_IDS = EVM_CHAIN_IDS;
@@ -740,6 +963,9 @@ exports.normalizePaymentRequirements = normalizePaymentRequirements;
740
963
  exports.spinnerStyles = spinnerStyles;
741
964
  exports.truncateAddress = truncateAddress;
742
965
  exports.useAsyncPayment = useAsyncPayment;
966
+ exports.useBridgePayment = useBridgePayment;
967
+ exports.useGaslessPayment = useGaslessPayment;
968
+ exports.useMultiSigPayment = useMultiSigPayment;
743
969
  exports.usePaymentRequired = usePaymentRequired;
744
970
  exports.usePaymentStatus = usePaymentStatus;
745
971
  //# sourceMappingURL=index.cjs.map