@t402/react 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 +158 -0
- package/dist/cjs/index.cjs +259 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +305 -1
- package/dist/esm/index.d.ts +305 -1
- package/dist/esm/index.js +257 -1
- package/dist/esm/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# @t402/react
|
|
2
|
+
|
|
3
|
+
React components and hooks for the t402 Payment Protocol. Build payment-aware UIs with pre-built components and reactive hooks.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @t402/react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { PaymentProvider, PaymentButton, usePaymentRequired } from '@t402/react'
|
|
15
|
+
|
|
16
|
+
function App() {
|
|
17
|
+
return (
|
|
18
|
+
<PaymentProvider>
|
|
19
|
+
<ProtectedContent />
|
|
20
|
+
</PaymentProvider>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function ProtectedContent() {
|
|
25
|
+
const { paymentRequired, requirements, pay, status } = usePaymentRequired('/api/data')
|
|
26
|
+
|
|
27
|
+
if (paymentRequired) {
|
|
28
|
+
return <PaymentButton requirements={requirements} onPay={pay} status={status} />
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return <div>Premium content loaded!</div>
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Components
|
|
36
|
+
|
|
37
|
+
### PaymentButton
|
|
38
|
+
|
|
39
|
+
Renders a payment action button with loading and status states.
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { PaymentButton } from '@t402/react'
|
|
43
|
+
;<PaymentButton requirements={requirements} onPay={handlePay} status={status} />
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### PaymentDetails
|
|
47
|
+
|
|
48
|
+
Displays payment requirements (amount, network, recipient).
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { PaymentDetails } from '@t402/react'
|
|
52
|
+
;<PaymentDetails requirements={requirements} />
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### PaymentStatusDisplay
|
|
56
|
+
|
|
57
|
+
Shows the current payment status with appropriate UI feedback.
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { PaymentStatusDisplay } from '@t402/react'
|
|
61
|
+
;<PaymentStatusDisplay status={status} />
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### AddressDisplay
|
|
65
|
+
|
|
66
|
+
Renders a blockchain address with truncation and copy functionality.
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { AddressDisplay } from '@t402/react'
|
|
70
|
+
;<AddressDisplay address="0x1234...5678" />
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Hooks
|
|
74
|
+
|
|
75
|
+
### usePaymentRequired
|
|
76
|
+
|
|
77
|
+
Detects 402 responses and extracts payment requirements.
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
const {
|
|
81
|
+
paymentRequired, // boolean - whether payment is needed
|
|
82
|
+
requirements, // PaymentRequirements[] from 402 response
|
|
83
|
+
pay, // () => Promise<void> - trigger payment
|
|
84
|
+
status, // PaymentStatus - current state
|
|
85
|
+
} = usePaymentRequired(url, options)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### usePaymentStatus
|
|
89
|
+
|
|
90
|
+
Tracks the lifecycle of a payment (idle, pending, confirming, complete, error).
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
const { status, error, reset } = usePaymentStatus()
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### useAsyncPayment
|
|
97
|
+
|
|
98
|
+
Manages async payment flows with automatic retry and status tracking.
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
const { execute, status, error } = useAsyncPayment(paymentFn)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Provider
|
|
105
|
+
|
|
106
|
+
### PaymentProvider
|
|
107
|
+
|
|
108
|
+
Wraps your app to provide payment context to all child components.
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
import { PaymentProvider } from '@t402/react'
|
|
112
|
+
;<PaymentProvider>
|
|
113
|
+
<App />
|
|
114
|
+
</PaymentProvider>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Access context directly with `usePaymentContext()`.
|
|
118
|
+
|
|
119
|
+
## Utilities
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import {
|
|
123
|
+
isEvmNetwork,
|
|
124
|
+
isSvmNetwork,
|
|
125
|
+
isTonNetwork,
|
|
126
|
+
getNetworkDisplayName,
|
|
127
|
+
truncateAddress,
|
|
128
|
+
formatTokenAmount,
|
|
129
|
+
choosePaymentRequirement,
|
|
130
|
+
} from '@t402/react'
|
|
131
|
+
|
|
132
|
+
// Network detection
|
|
133
|
+
isEvmNetwork('eip155:8453') // true
|
|
134
|
+
|
|
135
|
+
// Display helpers
|
|
136
|
+
getNetworkDisplayName('eip155:8453') // "Base"
|
|
137
|
+
truncateAddress('0x1234567890abcdef') // "0x1234...cdef"
|
|
138
|
+
formatTokenAmount(1500000n, 6) // "1.50"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Peer Dependencies
|
|
142
|
+
|
|
143
|
+
- `react` ^18.0.0 || ^19.0.0
|
|
144
|
+
- `react-dom` ^18.0.0 || ^19.0.0
|
|
145
|
+
|
|
146
|
+
## Development
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
pnpm build # Build the package
|
|
150
|
+
pnpm test # Run unit tests
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Related Packages
|
|
154
|
+
|
|
155
|
+
- `@t402/core` - Core protocol types
|
|
156
|
+
- `@t402/vue` - Vue components & composables
|
|
157
|
+
- `@t402/paywall` - Universal paywall UI
|
|
158
|
+
- `@t402/fetch` - Fetch wrapper with automatic payment
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -628,6 +628,262 @@ function useAsyncPayment(options) {
|
|
|
628
628
|
reset
|
|
629
629
|
};
|
|
630
630
|
}
|
|
631
|
+
function useGaslessPayment(options) {
|
|
632
|
+
const { payFn, onSuccess, onError, autoWait = true } = options;
|
|
633
|
+
const [status, setStatus] = react.useState("idle");
|
|
634
|
+
const [userOpHash, setUserOpHash] = react.useState(null);
|
|
635
|
+
const [txHash, setTxHash] = react.useState(null);
|
|
636
|
+
const [sponsored, setSponsored] = react.useState(null);
|
|
637
|
+
const [error, setError] = react.useState(null);
|
|
638
|
+
const isMountedRef = react.useRef(true);
|
|
639
|
+
const pay = react.useCallback(
|
|
640
|
+
async (params) => {
|
|
641
|
+
setStatus("loading");
|
|
642
|
+
setError(null);
|
|
643
|
+
setUserOpHash(null);
|
|
644
|
+
setTxHash(null);
|
|
645
|
+
setSponsored(null);
|
|
646
|
+
try {
|
|
647
|
+
const result = await payFn(params);
|
|
648
|
+
if (isMountedRef.current) {
|
|
649
|
+
setUserOpHash(result.userOpHash);
|
|
650
|
+
setSponsored(result.sponsored);
|
|
651
|
+
}
|
|
652
|
+
if (autoWait) {
|
|
653
|
+
const receipt = await result.wait();
|
|
654
|
+
if (isMountedRef.current) {
|
|
655
|
+
setTxHash(receipt.txHash);
|
|
656
|
+
setStatus("success");
|
|
657
|
+
onSuccess?.(receipt);
|
|
658
|
+
}
|
|
659
|
+
} else {
|
|
660
|
+
if (isMountedRef.current) {
|
|
661
|
+
setStatus("success");
|
|
662
|
+
onSuccess?.({ txHash: result.userOpHash, success: true });
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
} catch (err) {
|
|
666
|
+
const errorMessage = err instanceof Error ? err.message : "Gasless payment failed";
|
|
667
|
+
if (isMountedRef.current) {
|
|
668
|
+
setError(errorMessage);
|
|
669
|
+
setStatus("error");
|
|
670
|
+
}
|
|
671
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
[payFn, onSuccess, onError, autoWait]
|
|
675
|
+
);
|
|
676
|
+
const reset = react.useCallback(() => {
|
|
677
|
+
setStatus("idle");
|
|
678
|
+
setUserOpHash(null);
|
|
679
|
+
setTxHash(null);
|
|
680
|
+
setSponsored(null);
|
|
681
|
+
setError(null);
|
|
682
|
+
}, []);
|
|
683
|
+
return {
|
|
684
|
+
pay,
|
|
685
|
+
status,
|
|
686
|
+
userOpHash,
|
|
687
|
+
txHash,
|
|
688
|
+
sponsored,
|
|
689
|
+
error,
|
|
690
|
+
isLoading: status === "loading",
|
|
691
|
+
isSuccess: status === "success",
|
|
692
|
+
isError: status === "error",
|
|
693
|
+
reset
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
function useBridgePayment(options) {
|
|
697
|
+
const { bridgeFn, autoBridgeFn, onSuccess, onError, autoWaitForDelivery = false } = options;
|
|
698
|
+
const [status, setStatus] = react.useState("idle");
|
|
699
|
+
const [txHash, setTxHash] = react.useState(null);
|
|
700
|
+
const [messageGuid, setMessageGuid] = react.useState(null);
|
|
701
|
+
const [dstTxHash, setDstTxHash] = react.useState(null);
|
|
702
|
+
const [error, setError] = react.useState(null);
|
|
703
|
+
const isMountedRef = react.useRef(true);
|
|
704
|
+
const executeBridge = react.useCallback(
|
|
705
|
+
async (bridgeCall) => {
|
|
706
|
+
setStatus("bridging");
|
|
707
|
+
setError(null);
|
|
708
|
+
setTxHash(null);
|
|
709
|
+
setMessageGuid(null);
|
|
710
|
+
setDstTxHash(null);
|
|
711
|
+
try {
|
|
712
|
+
const result = await bridgeCall();
|
|
713
|
+
if (isMountedRef.current) {
|
|
714
|
+
setTxHash(result.txHash);
|
|
715
|
+
setMessageGuid(result.messageGuid);
|
|
716
|
+
}
|
|
717
|
+
if (autoWaitForDelivery) {
|
|
718
|
+
if (isMountedRef.current) setStatus("waiting");
|
|
719
|
+
const delivery = await result.waitForDelivery();
|
|
720
|
+
if (isMountedRef.current) {
|
|
721
|
+
if (delivery.dstTxHash) setDstTxHash(delivery.dstTxHash);
|
|
722
|
+
setStatus("success");
|
|
723
|
+
onSuccess?.({
|
|
724
|
+
txHash: result.txHash,
|
|
725
|
+
dstTxHash: delivery.dstTxHash,
|
|
726
|
+
fromChain: result.fromChain,
|
|
727
|
+
toChain: result.toChain
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
} else {
|
|
731
|
+
if (isMountedRef.current) {
|
|
732
|
+
setStatus("success");
|
|
733
|
+
onSuccess?.({
|
|
734
|
+
txHash: result.txHash,
|
|
735
|
+
fromChain: result.fromChain,
|
|
736
|
+
toChain: result.toChain
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
} catch (err) {
|
|
741
|
+
const errorMessage = err instanceof Error ? err.message : "Bridge operation failed";
|
|
742
|
+
if (isMountedRef.current) {
|
|
743
|
+
setError(errorMessage);
|
|
744
|
+
setStatus("error");
|
|
745
|
+
}
|
|
746
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
747
|
+
}
|
|
748
|
+
},
|
|
749
|
+
[onSuccess, onError, autoWaitForDelivery]
|
|
750
|
+
);
|
|
751
|
+
const bridge = react.useCallback(
|
|
752
|
+
async (params) => {
|
|
753
|
+
await executeBridge(() => bridgeFn(params));
|
|
754
|
+
},
|
|
755
|
+
[bridgeFn, executeBridge]
|
|
756
|
+
);
|
|
757
|
+
const autoBridge = react.useCallback(
|
|
758
|
+
async (params) => {
|
|
759
|
+
const fn = autoBridgeFn ?? (() => {
|
|
760
|
+
throw new Error("autoBridgeFn not provided");
|
|
761
|
+
});
|
|
762
|
+
await executeBridge(() => fn(params));
|
|
763
|
+
},
|
|
764
|
+
[autoBridgeFn, executeBridge]
|
|
765
|
+
);
|
|
766
|
+
const reset = react.useCallback(() => {
|
|
767
|
+
setStatus("idle");
|
|
768
|
+
setTxHash(null);
|
|
769
|
+
setMessageGuid(null);
|
|
770
|
+
setDstTxHash(null);
|
|
771
|
+
setError(null);
|
|
772
|
+
}, []);
|
|
773
|
+
return {
|
|
774
|
+
bridge,
|
|
775
|
+
autoBridge,
|
|
776
|
+
status,
|
|
777
|
+
txHash,
|
|
778
|
+
messageGuid,
|
|
779
|
+
dstTxHash,
|
|
780
|
+
error,
|
|
781
|
+
isLoading: status === "bridging" || status === "quoting" || status === "waiting",
|
|
782
|
+
isSuccess: status === "success",
|
|
783
|
+
isError: status === "error",
|
|
784
|
+
reset
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
function useMultiSigPayment(options) {
|
|
788
|
+
const { initiateFn, submitFn, onSuccess, onError, autoWait = true } = options;
|
|
789
|
+
const [status, setStatus] = react.useState("idle");
|
|
790
|
+
const [requestId, setRequestId] = react.useState(null);
|
|
791
|
+
const [userOpHash, setUserOpHash] = react.useState(null);
|
|
792
|
+
const [txHash, setTxHash] = react.useState(null);
|
|
793
|
+
const [threshold, setThreshold] = react.useState(0);
|
|
794
|
+
const [collectedCount, setCollectedCount] = react.useState(0);
|
|
795
|
+
const [isReady, setIsReady] = react.useState(false);
|
|
796
|
+
const [error, setError] = react.useState(null);
|
|
797
|
+
const isMountedRef = react.useRef(true);
|
|
798
|
+
const initiate = react.useCallback(
|
|
799
|
+
async (params) => {
|
|
800
|
+
setStatus("initiating");
|
|
801
|
+
setError(null);
|
|
802
|
+
setRequestId(null);
|
|
803
|
+
setUserOpHash(null);
|
|
804
|
+
setTxHash(null);
|
|
805
|
+
try {
|
|
806
|
+
const result = await initiateFn(params);
|
|
807
|
+
if (isMountedRef.current) {
|
|
808
|
+
setRequestId(result.requestId);
|
|
809
|
+
setUserOpHash(result.userOpHash);
|
|
810
|
+
setThreshold(result.threshold);
|
|
811
|
+
setCollectedCount(result.collectedCount);
|
|
812
|
+
setIsReady(result.isReady);
|
|
813
|
+
setStatus("collecting");
|
|
814
|
+
}
|
|
815
|
+
} catch (err) {
|
|
816
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to initiate multi-sig payment";
|
|
817
|
+
if (isMountedRef.current) {
|
|
818
|
+
setError(errorMessage);
|
|
819
|
+
setStatus("error");
|
|
820
|
+
}
|
|
821
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
822
|
+
}
|
|
823
|
+
},
|
|
824
|
+
[initiateFn, onError]
|
|
825
|
+
);
|
|
826
|
+
const submit = react.useCallback(async () => {
|
|
827
|
+
if (!requestId) {
|
|
828
|
+
const err = new Error("No active request to submit");
|
|
829
|
+
setError(err.message);
|
|
830
|
+
setStatus("error");
|
|
831
|
+
onError?.(err);
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
setStatus("submitting");
|
|
835
|
+
setError(null);
|
|
836
|
+
try {
|
|
837
|
+
const result = await submitFn(requestId);
|
|
838
|
+
if (autoWait) {
|
|
839
|
+
const receipt = await result.wait();
|
|
840
|
+
if (isMountedRef.current) {
|
|
841
|
+
setTxHash(receipt.txHash);
|
|
842
|
+
setStatus("success");
|
|
843
|
+
onSuccess?.(receipt);
|
|
844
|
+
}
|
|
845
|
+
} else {
|
|
846
|
+
if (isMountedRef.current) {
|
|
847
|
+
setStatus("success");
|
|
848
|
+
onSuccess?.({ txHash: result.userOpHash, success: true });
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
} catch (err) {
|
|
852
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to submit multi-sig transaction";
|
|
853
|
+
if (isMountedRef.current) {
|
|
854
|
+
setError(errorMessage);
|
|
855
|
+
setStatus("error");
|
|
856
|
+
}
|
|
857
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
858
|
+
}
|
|
859
|
+
}, [requestId, submitFn, onSuccess, onError, autoWait]);
|
|
860
|
+
const reset = react.useCallback(() => {
|
|
861
|
+
setStatus("idle");
|
|
862
|
+
setRequestId(null);
|
|
863
|
+
setUserOpHash(null);
|
|
864
|
+
setTxHash(null);
|
|
865
|
+
setThreshold(0);
|
|
866
|
+
setCollectedCount(0);
|
|
867
|
+
setIsReady(false);
|
|
868
|
+
setError(null);
|
|
869
|
+
}, []);
|
|
870
|
+
return {
|
|
871
|
+
initiate,
|
|
872
|
+
submit,
|
|
873
|
+
status,
|
|
874
|
+
requestId,
|
|
875
|
+
userOpHash,
|
|
876
|
+
txHash,
|
|
877
|
+
threshold,
|
|
878
|
+
collectedCount,
|
|
879
|
+
isReady,
|
|
880
|
+
error,
|
|
881
|
+
isLoading: status === "initiating" || status === "submitting",
|
|
882
|
+
isSuccess: status === "success",
|
|
883
|
+
isError: status === "error",
|
|
884
|
+
reset
|
|
885
|
+
};
|
|
886
|
+
}
|
|
631
887
|
var initialState = {
|
|
632
888
|
status: "idle",
|
|
633
889
|
error: null,
|
|
@@ -738,6 +994,9 @@ exports.isTronNetwork = isTronNetwork;
|
|
|
738
994
|
exports.normalizePaymentRequirements = normalizePaymentRequirements;
|
|
739
995
|
exports.truncateAddress = truncateAddress;
|
|
740
996
|
exports.useAsyncPayment = useAsyncPayment;
|
|
997
|
+
exports.useBridgePayment = useBridgePayment;
|
|
998
|
+
exports.useGaslessPayment = useGaslessPayment;
|
|
999
|
+
exports.useMultiSigPayment = useMultiSigPayment;
|
|
741
1000
|
exports.usePaymentContext = usePaymentContext;
|
|
742
1001
|
exports.usePaymentRequired = usePaymentRequired;
|
|
743
1002
|
exports.usePaymentStatus = usePaymentStatus;
|