@chipi-stack/chipi-react 13.13.0 → 14.1.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/LICENSE +21 -0
- package/README.md +51 -33
- package/dist/hooks.d.mts +460 -11
- package/dist/hooks.d.ts +460 -11
- package/dist/hooks.js +456 -115
- package/dist/hooks.js.map +1 -1
- package/dist/hooks.mjs +456 -118
- package/dist/hooks.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +457 -120
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +457 -123
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -5
package/dist/hooks.js
CHANGED
|
@@ -7,6 +7,7 @@ var chipiPasskey = require('@chipi-stack/chipi-passkey');
|
|
|
7
7
|
require('react/jsx-runtime');
|
|
8
8
|
var shared = require('@chipi-stack/shared');
|
|
9
9
|
var types = require('@chipi-stack/types');
|
|
10
|
+
var core = require('@chipi-stack/core');
|
|
10
11
|
|
|
11
12
|
// src/hooks/useCreateWallet.ts
|
|
12
13
|
var ChipiContext = react.createContext(null);
|
|
@@ -40,6 +41,9 @@ function useCreateWallet() {
|
|
|
40
41
|
throw new Error("Failed to create passkey for wallet");
|
|
41
42
|
}
|
|
42
43
|
}
|
|
44
|
+
if (!encryptKey) {
|
|
45
|
+
throw new Error("encryptKey is required when usePasskey is false");
|
|
46
|
+
}
|
|
43
47
|
return chipiSDK.createWallet({
|
|
44
48
|
params: {
|
|
45
49
|
...input.params,
|
|
@@ -66,8 +70,7 @@ function useGetWallet(input) {
|
|
|
66
70
|
const query = reactQuery.useQuery({
|
|
67
71
|
queryKey: ["wallet", input?.params?.externalUserId],
|
|
68
72
|
queryFn: async () => {
|
|
69
|
-
if (!input || !input.params || !input.getBearerToken)
|
|
70
|
-
throw new Error("Input is required");
|
|
73
|
+
if (!input || !input.params || !input.getBearerToken) throw new Error("Input is required");
|
|
71
74
|
const bearerToken = await input.getBearerToken();
|
|
72
75
|
if (!bearerToken) throw new Error("Bearer token is required");
|
|
73
76
|
return chipiSDK.getWallet(input.params, bearerToken);
|
|
@@ -109,22 +112,16 @@ function useGetWallet(input) {
|
|
|
109
112
|
function useChipiWallet(config) {
|
|
110
113
|
const { chipiSDK } = useChipiContext();
|
|
111
114
|
const queryClient = reactQuery.useQueryClient();
|
|
112
|
-
const {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
defaultToken = "USDC",
|
|
116
|
-
enabled = true
|
|
117
|
-
} = config;
|
|
118
|
-
const isEnabled = Boolean(
|
|
119
|
-
enabled && externalUserId && externalUserId.trim() !== ""
|
|
120
|
-
);
|
|
115
|
+
const { externalUserId, getBearerToken, defaultToken = "USDC", enabled = true } = config;
|
|
116
|
+
const trimmedUserId = externalUserId?.trim() || null;
|
|
117
|
+
const isEnabled = Boolean(enabled && trimmedUserId);
|
|
121
118
|
const walletQuery = reactQuery.useQuery({
|
|
122
|
-
queryKey: ["chipi-wallet",
|
|
119
|
+
queryKey: ["chipi-wallet", trimmedUserId],
|
|
123
120
|
queryFn: async () => {
|
|
124
|
-
if (!
|
|
121
|
+
if (!trimmedUserId) return null;
|
|
125
122
|
const bearerToken = await getBearerToken();
|
|
126
123
|
if (!bearerToken) throw new Error("Bearer token is required");
|
|
127
|
-
return chipiSDK.getWallet({ externalUserId }, bearerToken);
|
|
124
|
+
return chipiSDK.getWallet({ externalUserId: trimmedUserId }, bearerToken);
|
|
128
125
|
},
|
|
129
126
|
enabled: isEnabled,
|
|
130
127
|
retry: (failureCount, error) => {
|
|
@@ -150,7 +147,7 @@ function useChipiWallet(config) {
|
|
|
150
147
|
bearerToken
|
|
151
148
|
);
|
|
152
149
|
},
|
|
153
|
-
enabled: Boolean(walletPublicKey),
|
|
150
|
+
enabled: Boolean(isEnabled && walletPublicKey),
|
|
154
151
|
retry: (failureCount, error) => {
|
|
155
152
|
if (error instanceof shared.ChipiApiError || error?.status) {
|
|
156
153
|
return false;
|
|
@@ -160,14 +157,14 @@ function useChipiWallet(config) {
|
|
|
160
157
|
});
|
|
161
158
|
const createWalletMutation = reactQuery.useMutation({
|
|
162
159
|
mutationFn: async ({ encryptKey, walletType = "CHIPI" }) => {
|
|
163
|
-
if (!
|
|
160
|
+
if (!trimmedUserId) throw new Error("External user ID is required");
|
|
164
161
|
const bearerToken = await getBearerToken();
|
|
165
162
|
if (!bearerToken) throw new Error("Bearer token is required");
|
|
166
163
|
return chipiSDK.createWallet({
|
|
167
164
|
params: {
|
|
168
165
|
chain: types.Chain.STARKNET,
|
|
169
166
|
encryptKey,
|
|
170
|
-
externalUserId,
|
|
167
|
+
externalUserId: trimmedUserId,
|
|
171
168
|
walletType
|
|
172
169
|
},
|
|
173
170
|
bearerToken
|
|
@@ -175,7 +172,7 @@ function useChipiWallet(config) {
|
|
|
175
172
|
},
|
|
176
173
|
onSuccess: () => {
|
|
177
174
|
queryClient.invalidateQueries({
|
|
178
|
-
queryKey: ["chipi-wallet",
|
|
175
|
+
queryKey: ["chipi-wallet", trimmedUserId]
|
|
179
176
|
});
|
|
180
177
|
}
|
|
181
178
|
});
|
|
@@ -250,14 +247,10 @@ function useChipiSession(config) {
|
|
|
250
247
|
defaultMaxCalls = 1e3,
|
|
251
248
|
autoCheckStatus = true
|
|
252
249
|
} = config;
|
|
253
|
-
const [localSession, setLocalSession] = react.useState(
|
|
254
|
-
storedSession ?? null
|
|
255
|
-
);
|
|
250
|
+
const [localSession, setLocalSession] = react.useState(storedSession ?? null);
|
|
256
251
|
const [error, setError] = react.useState(null);
|
|
257
252
|
react.useEffect(() => {
|
|
258
|
-
|
|
259
|
-
setLocalSession(storedSession);
|
|
260
|
-
}
|
|
253
|
+
setLocalSession(storedSession ?? null);
|
|
261
254
|
}, [storedSession]);
|
|
262
255
|
const supportsSession = react.useMemo(() => {
|
|
263
256
|
if (!wallet) return false;
|
|
@@ -293,27 +286,32 @@ function useChipiSession(config) {
|
|
|
293
286
|
const hasActiveSession = sessionState === "active";
|
|
294
287
|
const isSessionExpired = sessionState === "expired";
|
|
295
288
|
const remainingCalls = sessionStatusQuery.data?.remainingCalls;
|
|
296
|
-
const createSessionMutation = reactQuery.useMutation(
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
289
|
+
const createSessionMutation = reactQuery.useMutation(
|
|
290
|
+
{
|
|
291
|
+
mutationFn: async (params = {}) => {
|
|
292
|
+
if (!encryptKey) throw new Error("Encryption key (PIN) is required");
|
|
293
|
+
if (!supportsSession)
|
|
294
|
+
throw new Error(
|
|
295
|
+
"Wallet does not support sessions. Only CHIPI wallets support session keys."
|
|
296
|
+
);
|
|
297
|
+
const sessionData = chipiSDK.sessions.createSessionKey({
|
|
298
|
+
encryptKey,
|
|
299
|
+
durationSeconds: params.durationSeconds ?? defaultDurationSeconds
|
|
300
|
+
});
|
|
301
|
+
return sessionData;
|
|
302
|
+
},
|
|
303
|
+
onSuccess: async (sessionData) => {
|
|
304
|
+
setLocalSession(sessionData);
|
|
305
|
+
setError(null);
|
|
306
|
+
if (onSessionCreated) {
|
|
307
|
+
await onSessionCreated(sessionData);
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
onError: (err) => {
|
|
311
|
+
setError(err);
|
|
311
312
|
}
|
|
312
|
-
},
|
|
313
|
-
onError: (err) => {
|
|
314
|
-
setError(err);
|
|
315
313
|
}
|
|
316
|
-
|
|
314
|
+
);
|
|
317
315
|
const registerSessionMutation = reactQuery.useMutation({
|
|
318
316
|
mutationFn: async (sessionConfig = {}) => {
|
|
319
317
|
if (!wallet) throw new Error("Wallet is required");
|
|
@@ -474,22 +472,19 @@ function useMigrateWalletToPasskey() {
|
|
|
474
472
|
mutationFn: async (input) => {
|
|
475
473
|
const { wallet, oldEncryptKey, externalUserId, bearerToken } = input;
|
|
476
474
|
try {
|
|
477
|
-
const passkeyResult = await chipiPasskey.createWalletPasskey(
|
|
478
|
-
externalUserId,
|
|
479
|
-
externalUserId
|
|
480
|
-
// Using externalUserId as userName
|
|
481
|
-
);
|
|
482
475
|
let decryptedPrivateKey;
|
|
483
476
|
try {
|
|
484
|
-
decryptedPrivateKey = backend.decryptPrivateKey(
|
|
485
|
-
|
|
486
|
-
oldEncryptKey
|
|
487
|
-
);
|
|
488
|
-
} catch (error) {
|
|
477
|
+
decryptedPrivateKey = backend.decryptPrivateKey(wallet.encryptedPrivateKey, oldEncryptKey);
|
|
478
|
+
} catch {
|
|
489
479
|
throw new Error(
|
|
490
480
|
"Failed to decrypt wallet with provided encryptKey. Please verify your PIN/password is correct."
|
|
491
481
|
);
|
|
492
482
|
}
|
|
483
|
+
const passkeyResult = await chipiPasskey.createWalletPasskey(
|
|
484
|
+
externalUserId,
|
|
485
|
+
externalUserId
|
|
486
|
+
// Using externalUserId as userName
|
|
487
|
+
);
|
|
493
488
|
const newEncryptedPrivateKey = backend.encryptPrivateKey(
|
|
494
489
|
decryptedPrivateKey,
|
|
495
490
|
passkeyResult.encryptKey
|
|
@@ -549,44 +544,49 @@ function useUpdateWalletEncryption() {
|
|
|
549
544
|
}
|
|
550
545
|
function useTransfer() {
|
|
551
546
|
const { chipiSDK, passkeyAdapter } = useChipiContext();
|
|
552
|
-
const mutation = reactQuery.useMutation(
|
|
553
|
-
{
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
if (input.params.
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
547
|
+
const mutation = reactQuery.useMutation({
|
|
548
|
+
mutationFn: async (input) => {
|
|
549
|
+
let encryptKey = input.params.encryptKey;
|
|
550
|
+
if (input.params.usePasskey) {
|
|
551
|
+
if (!input.params.externalUserId) {
|
|
552
|
+
throw new Error("externalUserId is required when usePasskey is true");
|
|
553
|
+
}
|
|
554
|
+
try {
|
|
555
|
+
const key = await passkeyAdapter.getWalletEncryptKey({
|
|
556
|
+
externalUserId: input.params.externalUserId
|
|
557
|
+
});
|
|
558
|
+
if (!key) {
|
|
559
|
+
throw new Error("Passkey authentication was cancelled");
|
|
560
|
+
}
|
|
561
|
+
encryptKey = key;
|
|
562
|
+
} catch (error) {
|
|
563
|
+
if (error instanceof DOMException && error.name === "NotAllowedError") {
|
|
564
|
+
throw new Error("Passkey authentication was cancelled by the user");
|
|
561
565
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
externalUserId: input.params.externalUserId
|
|
565
|
-
});
|
|
566
|
-
if (!key) {
|
|
567
|
-
throw new Error("Passkey authentication was cancelled");
|
|
568
|
-
}
|
|
569
|
-
encryptKey = key;
|
|
570
|
-
} catch (error) {
|
|
571
|
-
if (error instanceof Error) {
|
|
572
|
-
throw new Error(
|
|
573
|
-
`Passkey authentication failed: ${error.message}`
|
|
574
|
-
);
|
|
575
|
-
}
|
|
576
|
-
throw new Error("Failed to authenticate with passkey");
|
|
566
|
+
if (error instanceof Error) {
|
|
567
|
+
throw error;
|
|
577
568
|
}
|
|
569
|
+
throw new Error("Failed to authenticate with passkey");
|
|
578
570
|
}
|
|
579
|
-
return chipiSDK.transfer({
|
|
580
|
-
params: {
|
|
581
|
-
...input.params,
|
|
582
|
-
amount: String(input.params.amount),
|
|
583
|
-
encryptKey
|
|
584
|
-
},
|
|
585
|
-
bearerToken: input.bearerToken
|
|
586
|
-
});
|
|
587
571
|
}
|
|
572
|
+
if (!encryptKey) {
|
|
573
|
+
throw new Error("encryptKey is required when usePasskey is false");
|
|
574
|
+
}
|
|
575
|
+
const {
|
|
576
|
+
usePasskey: _usePasskey,
|
|
577
|
+
externalUserId: _externalUserId,
|
|
578
|
+
...transferParams
|
|
579
|
+
} = input.params;
|
|
580
|
+
return chipiSDK.transfer({
|
|
581
|
+
params: {
|
|
582
|
+
...transferParams,
|
|
583
|
+
amount: String(transferParams.amount),
|
|
584
|
+
encryptKey
|
|
585
|
+
},
|
|
586
|
+
bearerToken: input.bearerToken
|
|
587
|
+
});
|
|
588
588
|
}
|
|
589
|
-
);
|
|
589
|
+
});
|
|
590
590
|
return {
|
|
591
591
|
transfer: mutation.mutate,
|
|
592
592
|
transferAsync: mutation.mutateAsync,
|
|
@@ -622,10 +622,20 @@ function useApprove() {
|
|
|
622
622
|
throw new Error("Failed to authenticate with passkey");
|
|
623
623
|
}
|
|
624
624
|
}
|
|
625
|
+
if (!encryptKey) {
|
|
626
|
+
throw new Error(
|
|
627
|
+
"encryptKey is required when usePasskey is false. Provide a PIN or enable usePasskey."
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
const {
|
|
631
|
+
usePasskey: _usePasskey,
|
|
632
|
+
externalUserId: _externalUserId,
|
|
633
|
+
...approveParams
|
|
634
|
+
} = params.params;
|
|
625
635
|
return chipiSDK.approve({
|
|
626
636
|
params: {
|
|
627
|
-
...
|
|
628
|
-
amount: String(
|
|
637
|
+
...approveParams,
|
|
638
|
+
amount: String(approveParams.amount),
|
|
629
639
|
encryptKey
|
|
630
640
|
},
|
|
631
641
|
bearerToken: params.bearerToken
|
|
@@ -675,9 +685,8 @@ function useGetTransactionList(input) {
|
|
|
675
685
|
});
|
|
676
686
|
const fetchTransactionList = async (newInput) => {
|
|
677
687
|
return queryClient.fetchQuery({
|
|
678
|
-
queryKey: buildQueryKey(newInput
|
|
688
|
+
queryKey: buildQueryKey(newInput.query),
|
|
679
689
|
queryFn: async () => {
|
|
680
|
-
if (!newInput?.getBearerToken) throw new Error("getBearerToken is required");
|
|
681
690
|
const bearerToken = await newInput.getBearerToken();
|
|
682
691
|
if (!bearerToken) throw new Error("Bearer token is required");
|
|
683
692
|
return chipiSDK.getTransactionList(newInput.query, bearerToken);
|
|
@@ -695,6 +704,104 @@ function useGetTransactionList(input) {
|
|
|
695
704
|
fetchTransactionList
|
|
696
705
|
};
|
|
697
706
|
}
|
|
707
|
+
function useGetTransaction(input) {
|
|
708
|
+
const { chipiSDK } = useChipiContext();
|
|
709
|
+
const queryClient = reactQuery.useQueryClient();
|
|
710
|
+
const buildQueryKey = (hashOrId) => ["transaction", hashOrId ?? null];
|
|
711
|
+
const query = reactQuery.useQuery({
|
|
712
|
+
queryKey: buildQueryKey(input?.hashOrId),
|
|
713
|
+
queryFn: async () => {
|
|
714
|
+
if (!input?.hashOrId) throw new Error("hashOrId is required");
|
|
715
|
+
if (!input?.getBearerToken) throw new Error("getBearerToken is required");
|
|
716
|
+
const bearerToken = await input.getBearerToken();
|
|
717
|
+
if (!bearerToken) throw new Error("Bearer token is required");
|
|
718
|
+
return chipiSDK.getTransaction(input.hashOrId, bearerToken);
|
|
719
|
+
},
|
|
720
|
+
enabled: Boolean(input?.hashOrId && input?.getBearerToken),
|
|
721
|
+
retry: (failureCount, error) => {
|
|
722
|
+
if (error instanceof shared.ChipiApiError || error?.status) {
|
|
723
|
+
return false;
|
|
724
|
+
}
|
|
725
|
+
return failureCount < 3;
|
|
726
|
+
},
|
|
727
|
+
...input?.queryOptions
|
|
728
|
+
});
|
|
729
|
+
const fetchTransaction = async (newInput) => {
|
|
730
|
+
return queryClient.fetchQuery({
|
|
731
|
+
queryKey: buildQueryKey(newInput.hashOrId),
|
|
732
|
+
queryFn: async () => {
|
|
733
|
+
const bearerToken = await newInput.getBearerToken();
|
|
734
|
+
if (!bearerToken) throw new Error("Bearer token is required");
|
|
735
|
+
return chipiSDK.getTransaction(newInput.hashOrId, bearerToken);
|
|
736
|
+
},
|
|
737
|
+
retry: (failureCount, error) => {
|
|
738
|
+
if (error instanceof shared.ChipiApiError || error?.status) {
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
return failureCount < 3;
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
};
|
|
745
|
+
return {
|
|
746
|
+
...query,
|
|
747
|
+
fetchTransaction
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
var TERMINAL_STATUSES = [
|
|
751
|
+
types.OnChainTxStatus.ACCEPTED_ON_L1,
|
|
752
|
+
types.OnChainTxStatus.REJECTED,
|
|
753
|
+
types.OnChainTxStatus.REVERTED
|
|
754
|
+
];
|
|
755
|
+
function useGetTransactionStatus(input) {
|
|
756
|
+
const { chipiSDK } = useChipiContext();
|
|
757
|
+
const queryClient = reactQuery.useQueryClient();
|
|
758
|
+
const buildQueryKey = (hash) => ["transaction-status", hash ?? null];
|
|
759
|
+
const query = reactQuery.useQuery({
|
|
760
|
+
queryKey: buildQueryKey(input?.hash),
|
|
761
|
+
queryFn: async () => {
|
|
762
|
+
if (!input?.hash) throw new Error("hash is required");
|
|
763
|
+
if (!input?.getBearerToken) throw new Error("getBearerToken is required");
|
|
764
|
+
const bearerToken = await input.getBearerToken();
|
|
765
|
+
if (!bearerToken) throw new Error("Bearer token is required");
|
|
766
|
+
return chipiSDK.getTransactionStatus(input.hash, bearerToken);
|
|
767
|
+
},
|
|
768
|
+
enabled: Boolean(input?.hash && input?.getBearerToken),
|
|
769
|
+
refetchInterval: (query2) => {
|
|
770
|
+
const status = query2.state.data?.status;
|
|
771
|
+
if (status && TERMINAL_STATUSES.includes(status)) {
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
return input?.refetchInterval ?? 3e3;
|
|
775
|
+
},
|
|
776
|
+
retry: (failureCount, error) => {
|
|
777
|
+
if (error instanceof shared.ChipiApiError || error?.status) {
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
return failureCount < 3;
|
|
781
|
+
},
|
|
782
|
+
...input?.queryOptions
|
|
783
|
+
});
|
|
784
|
+
const fetchTransactionStatus = async (newInput) => {
|
|
785
|
+
return queryClient.fetchQuery({
|
|
786
|
+
queryKey: buildQueryKey(newInput.hash),
|
|
787
|
+
queryFn: async () => {
|
|
788
|
+
const bearerToken = await newInput.getBearerToken();
|
|
789
|
+
if (!bearerToken) throw new Error("Bearer token is required");
|
|
790
|
+
return chipiSDK.getTransactionStatus(newInput.hash, bearerToken);
|
|
791
|
+
},
|
|
792
|
+
retry: (failureCount, error) => {
|
|
793
|
+
if (error instanceof shared.ChipiApiError || error?.status) {
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
return failureCount < 3;
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
};
|
|
800
|
+
return {
|
|
801
|
+
...query,
|
|
802
|
+
fetchTransactionStatus
|
|
803
|
+
};
|
|
804
|
+
}
|
|
698
805
|
function usePurchaseSku() {
|
|
699
806
|
const { chipiSDK } = useChipiContext();
|
|
700
807
|
const mutation = reactQuery.useMutation({
|
|
@@ -734,8 +841,7 @@ function useGetSkuPurchase(input) {
|
|
|
734
841
|
queryKey: ["sku-purchase", newInput?.id],
|
|
735
842
|
queryFn: async () => {
|
|
736
843
|
if (!newInput?.id) throw new Error("id is required");
|
|
737
|
-
if (!newInput?.getBearerToken)
|
|
738
|
-
throw new Error("getBearerToken is required");
|
|
844
|
+
if (!newInput?.getBearerToken) throw new Error("getBearerToken is required");
|
|
739
845
|
const bearerToken = await newInput.getBearerToken();
|
|
740
846
|
if (!bearerToken) throw new Error("Bearer token is required");
|
|
741
847
|
return chipiSDK.getSkuPurchase(newInput.id, bearerToken);
|
|
@@ -809,9 +915,7 @@ function useGetSku(input) {
|
|
|
809
915
|
if (!bearerToken) throw new Error("Bearer token is required");
|
|
810
916
|
return chipiSDK.getSku(input.id, bearerToken);
|
|
811
917
|
},
|
|
812
|
-
enabled: Boolean(
|
|
813
|
-
input?.id && input?.getBearerToken && input.id.trim() !== ""
|
|
814
|
-
),
|
|
918
|
+
enabled: Boolean(input?.id && input?.getBearerToken && input.id.trim() !== ""),
|
|
815
919
|
retry: (failureCount, error) => {
|
|
816
920
|
if (error instanceof shared.ChipiApiError || error?.status) {
|
|
817
921
|
return false;
|
|
@@ -824,14 +928,13 @@ function useGetSku(input) {
|
|
|
824
928
|
return queryClient.fetchQuery({
|
|
825
929
|
queryKey: ["sku", input2?.id],
|
|
826
930
|
queryFn: async () => {
|
|
827
|
-
if (!input2?.getBearerToken)
|
|
828
|
-
throw new Error("getBearerToken is required");
|
|
931
|
+
if (!input2?.getBearerToken) throw new Error("getBearerToken is required");
|
|
829
932
|
const bearerToken = await input2.getBearerToken();
|
|
830
933
|
if (!bearerToken) throw new Error("Bearer token is required");
|
|
831
934
|
return chipiSDK.getSku(input2.id, bearerToken);
|
|
832
935
|
},
|
|
833
936
|
retry: (failureCount, error) => {
|
|
834
|
-
if (error instanceof shared.ChipiApiError || error?.
|
|
937
|
+
if (error instanceof shared.ChipiApiError || error?.status) {
|
|
835
938
|
return false;
|
|
836
939
|
}
|
|
837
940
|
return failureCount < 3;
|
|
@@ -850,9 +953,7 @@ function useCallAnyContract() {
|
|
|
850
953
|
let encryptKey = params.params.encryptKey;
|
|
851
954
|
if (params.params.usePasskey) {
|
|
852
955
|
if (!params.params.externalUserId) {
|
|
853
|
-
throw new Error(
|
|
854
|
-
"externalUserId is required when usePasskey is true"
|
|
855
|
-
);
|
|
956
|
+
throw new Error("externalUserId is required when usePasskey is true");
|
|
856
957
|
}
|
|
857
958
|
try {
|
|
858
959
|
const key = await passkeyAdapter.getWalletEncryptKey({
|
|
@@ -863,17 +964,26 @@ function useCallAnyContract() {
|
|
|
863
964
|
}
|
|
864
965
|
encryptKey = key;
|
|
865
966
|
} catch (error) {
|
|
967
|
+
if (error instanceof DOMException && error.name === "NotAllowedError") {
|
|
968
|
+
throw new Error("Passkey authentication was cancelled by the user");
|
|
969
|
+
}
|
|
866
970
|
if (error instanceof Error) {
|
|
867
|
-
throw
|
|
868
|
-
`Passkey authentication failed: ${error.message}`
|
|
869
|
-
);
|
|
971
|
+
throw error;
|
|
870
972
|
}
|
|
871
973
|
throw new Error("Failed to authenticate with passkey");
|
|
872
974
|
}
|
|
873
975
|
}
|
|
976
|
+
if (!encryptKey) {
|
|
977
|
+
throw new Error("encryptKey is required when usePasskey is false");
|
|
978
|
+
}
|
|
979
|
+
const {
|
|
980
|
+
usePasskey: _usePasskey,
|
|
981
|
+
externalUserId: _externalUserId,
|
|
982
|
+
...contractParams
|
|
983
|
+
} = params.params;
|
|
874
984
|
return chipiSDK.callAnyContract({
|
|
875
985
|
params: {
|
|
876
|
-
...
|
|
986
|
+
...contractParams,
|
|
877
987
|
encryptKey
|
|
878
988
|
},
|
|
879
989
|
bearerToken: params.bearerToken
|
|
@@ -941,7 +1051,8 @@ function useGetTokenBalance(input) {
|
|
|
941
1051
|
return queryClient.fetchQuery({
|
|
942
1052
|
queryKey: buildQueryKey(newInput?.params),
|
|
943
1053
|
queryFn: async () => {
|
|
944
|
-
if (!newInput || !newInput.getBearerToken || !newInput.params)
|
|
1054
|
+
if (!newInput || !newInput.getBearerToken || !newInput.params)
|
|
1055
|
+
throw new Error("Input is required");
|
|
945
1056
|
const bearerToken = await newInput.getBearerToken();
|
|
946
1057
|
if (!bearerToken) throw new Error("Bearer token is required");
|
|
947
1058
|
return chipiSDK.getTokenBalance(newInput.params, bearerToken);
|
|
@@ -978,10 +1089,7 @@ function useCreateSessionKey() {
|
|
|
978
1089
|
function useAddSessionKeyToContract() {
|
|
979
1090
|
const { chipiSDK } = useChipiContext();
|
|
980
1091
|
const mutation = reactQuery.useMutation({
|
|
981
|
-
mutationFn: (input) => chipiSDK.sessions.addSessionKeyToContract(
|
|
982
|
-
input.params,
|
|
983
|
-
input.bearerToken
|
|
984
|
-
)
|
|
1092
|
+
mutationFn: (input) => chipiSDK.sessions.addSessionKeyToContract(input.params, input.bearerToken)
|
|
985
1093
|
});
|
|
986
1094
|
return {
|
|
987
1095
|
addSessionKeyToContract: mutation.mutate,
|
|
@@ -1013,11 +1121,7 @@ function useRevokeSessionKey() {
|
|
|
1013
1121
|
function useGetSessionData(params, options) {
|
|
1014
1122
|
const { chipiSDK } = useChipiContext();
|
|
1015
1123
|
const query = reactQuery.useQuery({
|
|
1016
|
-
queryKey: [
|
|
1017
|
-
"sessionData",
|
|
1018
|
-
params?.walletAddress,
|
|
1019
|
-
params?.sessionPublicKey
|
|
1020
|
-
],
|
|
1124
|
+
queryKey: ["sessionData", params?.walletAddress, params?.sessionPublicKey],
|
|
1021
1125
|
queryFn: () => {
|
|
1022
1126
|
if (!params) {
|
|
1023
1127
|
throw new Error("Session data params are required");
|
|
@@ -1054,6 +1158,240 @@ function useExecuteWithSession() {
|
|
|
1054
1158
|
reset: mutation.reset
|
|
1055
1159
|
};
|
|
1056
1160
|
}
|
|
1161
|
+
function useX402Payment(config) {
|
|
1162
|
+
const { chipiSDK } = useChipiContext();
|
|
1163
|
+
const {
|
|
1164
|
+
wallet,
|
|
1165
|
+
encryptKey,
|
|
1166
|
+
getBearerToken: getBearerTokenFn,
|
|
1167
|
+
bearerToken: staticBearerToken,
|
|
1168
|
+
maxPaymentAmount: maxPaymentAmountProp,
|
|
1169
|
+
maxAmount,
|
|
1170
|
+
allowedRecipients,
|
|
1171
|
+
session,
|
|
1172
|
+
onPaymentComplete
|
|
1173
|
+
} = config;
|
|
1174
|
+
const maxPaymentAmount = maxPaymentAmountProp ?? maxAmount;
|
|
1175
|
+
const getBearerToken = react.useMemo(
|
|
1176
|
+
() => getBearerTokenFn ?? (staticBearerToken ? () => Promise.resolve(staticBearerToken) : () => Promise.resolve(void 0)),
|
|
1177
|
+
[getBearerTokenFn, staticBearerToken]
|
|
1178
|
+
);
|
|
1179
|
+
const [lastTxHash, setLastTxHash] = react.useState(null);
|
|
1180
|
+
const [lastAmount, setLastAmount] = react.useState(null);
|
|
1181
|
+
const [lastPayment, setLastPayment] = react.useState(null);
|
|
1182
|
+
const [error, setError] = react.useState(null);
|
|
1183
|
+
const paymentCountRef = react.useRef(0);
|
|
1184
|
+
const [paymentCount, setPaymentCount] = react.useState(0);
|
|
1185
|
+
const totalSpentRef = react.useRef(0n);
|
|
1186
|
+
const [totalSpent, setTotalSpent] = react.useState("0.000000");
|
|
1187
|
+
const parsePaymentRequired = react.useCallback((response) => {
|
|
1188
|
+
const headerValue = response.headers.get("payment-required");
|
|
1189
|
+
if (!headerValue) {
|
|
1190
|
+
throw new Error(
|
|
1191
|
+
"Server returned 402 but no PAYMENT-REQUIRED header. The server may not support x402 or is misconfigured."
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
try {
|
|
1195
|
+
return JSON.parse(headerValue);
|
|
1196
|
+
} catch {
|
|
1197
|
+
throw new Error("Malformed PAYMENT-REQUIRED header: invalid JSON");
|
|
1198
|
+
}
|
|
1199
|
+
}, []);
|
|
1200
|
+
const validateRequirement = react.useCallback(
|
|
1201
|
+
(requirement) => {
|
|
1202
|
+
if (requirement.scheme !== "exact") {
|
|
1203
|
+
throw new Error(
|
|
1204
|
+
`Unsupported payment scheme: "${requirement.scheme}". Only "exact" is supported.`
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
if (requirement.network !== "starknet-mainnet") {
|
|
1208
|
+
throw new Error(
|
|
1209
|
+
`Unsupported network: "${requirement.network}". Only "starknet-mainnet" is supported.`
|
|
1210
|
+
);
|
|
1211
|
+
}
|
|
1212
|
+
const USDC_MAINNET = "0x033068F6539f8e6e6b131e6B2B814e6c34A5224bC66947c47DaB9dFeE93b35fb";
|
|
1213
|
+
const normalizeAddr = (a) => a.toLowerCase().replace(/^0x0+/, "0x");
|
|
1214
|
+
if (normalizeAddr(requirement.asset) !== normalizeAddr(USDC_MAINNET)) {
|
|
1215
|
+
throw new Error(`Unsupported asset: ${requirement.asset}. Only USDC is supported.`);
|
|
1216
|
+
}
|
|
1217
|
+
if (!requirement.payTo) {
|
|
1218
|
+
throw new Error("Invalid payment requirement: missing payTo address");
|
|
1219
|
+
}
|
|
1220
|
+
const timeoutSeconds = Number(requirement.maxTimeoutSeconds);
|
|
1221
|
+
if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) {
|
|
1222
|
+
throw new Error("Invalid payment requirement: maxTimeoutSeconds must be a positive number");
|
|
1223
|
+
}
|
|
1224
|
+
let requiredAmount;
|
|
1225
|
+
try {
|
|
1226
|
+
requiredAmount = BigInt(requirement.maxAmountRequired);
|
|
1227
|
+
} catch {
|
|
1228
|
+
throw new Error("Invalid payment requirement: maxAmountRequired must be an integer string");
|
|
1229
|
+
}
|
|
1230
|
+
if (requiredAmount <= 0n) {
|
|
1231
|
+
throw new Error("Invalid payment requirement: maxAmountRequired must be > 0");
|
|
1232
|
+
}
|
|
1233
|
+
if (maxPaymentAmount) {
|
|
1234
|
+
const trimmed = maxPaymentAmount.trim();
|
|
1235
|
+
if (!/^\d+(\.\d{1,6})?$/.test(trimmed)) {
|
|
1236
|
+
throw new Error(`Invalid maxPaymentAmount format: "${maxPaymentAmount}"`);
|
|
1237
|
+
}
|
|
1238
|
+
const [whole, frac = ""] = trimmed.split(".");
|
|
1239
|
+
const fracPadded = (frac + "000000").slice(0, 6);
|
|
1240
|
+
const maxBaseUnits = BigInt((whole || "0") + fracPadded);
|
|
1241
|
+
if (requiredAmount > maxBaseUnits) {
|
|
1242
|
+
const requiredHuman = (Number(requiredAmount) / 1e6).toFixed(6);
|
|
1243
|
+
throw new Error(
|
|
1244
|
+
`Payment amount ${requiredHuman} USDC exceeds maximum allowed ${maxPaymentAmount} USDC`
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
if (allowedRecipients && allowedRecipients.length > 0) {
|
|
1249
|
+
const normalize = (a) => a.toLowerCase().replace(/^0x0+/, "0x");
|
|
1250
|
+
const normalized = normalize(requirement.payTo);
|
|
1251
|
+
const isAllowed = allowedRecipients.some((addr) => normalize(addr) === normalized);
|
|
1252
|
+
if (!isAllowed) {
|
|
1253
|
+
throw new Error(`Recipient ${requirement.payTo} is not in the allowed recipients list`);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
},
|
|
1257
|
+
[maxPaymentAmount, allowedRecipients]
|
|
1258
|
+
);
|
|
1259
|
+
const signPayment = react.useCallback(
|
|
1260
|
+
async (requirement) => {
|
|
1261
|
+
if (!wallet) {
|
|
1262
|
+
throw new Error("Wallet is required for x402 payments");
|
|
1263
|
+
}
|
|
1264
|
+
if (!encryptKey) {
|
|
1265
|
+
throw new Error("Encryption key (PIN) is required for x402 payments");
|
|
1266
|
+
}
|
|
1267
|
+
validateRequirement(requirement);
|
|
1268
|
+
const nonce = crypto.randomUUID();
|
|
1269
|
+
const validUntil = Math.floor(Date.now() / 1e3) + requirement.maxTimeoutSeconds;
|
|
1270
|
+
if (session) {
|
|
1271
|
+
const bearerToken = await getBearerToken();
|
|
1272
|
+
if (!bearerToken) throw new Error("Bearer token is required");
|
|
1273
|
+
const txHash = await chipiSDK.executeTransactionWithSession({
|
|
1274
|
+
params: {
|
|
1275
|
+
encryptKey,
|
|
1276
|
+
wallet,
|
|
1277
|
+
session,
|
|
1278
|
+
calls: [
|
|
1279
|
+
{
|
|
1280
|
+
contractAddress: requirement.asset,
|
|
1281
|
+
entrypoint: "transfer",
|
|
1282
|
+
calldata: [requirement.payTo, requirement.maxAmountRequired, "0x0"]
|
|
1283
|
+
}
|
|
1284
|
+
]
|
|
1285
|
+
},
|
|
1286
|
+
bearerToken
|
|
1287
|
+
});
|
|
1288
|
+
return {
|
|
1289
|
+
x402Version: 1,
|
|
1290
|
+
scheme: "exact",
|
|
1291
|
+
network: "starknet-mainnet",
|
|
1292
|
+
payload: {
|
|
1293
|
+
signature: { r: txHash, s: "session" },
|
|
1294
|
+
fromAddress: wallet.publicKey,
|
|
1295
|
+
toAddress: requirement.payTo,
|
|
1296
|
+
amount: requirement.maxAmountRequired,
|
|
1297
|
+
asset: requirement.asset,
|
|
1298
|
+
validUntil,
|
|
1299
|
+
nonce
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
const typedData = core.buildX402TypedData({
|
|
1304
|
+
from_address: wallet.publicKey,
|
|
1305
|
+
to_address: requirement.payTo,
|
|
1306
|
+
amount: requirement.maxAmountRequired,
|
|
1307
|
+
asset: requirement.asset,
|
|
1308
|
+
valid_until: String(validUntil),
|
|
1309
|
+
nonce
|
|
1310
|
+
});
|
|
1311
|
+
const signature = await chipiSDK.signTypedData({
|
|
1312
|
+
params: {
|
|
1313
|
+
encryptKey,
|
|
1314
|
+
wallet,
|
|
1315
|
+
typedData
|
|
1316
|
+
}
|
|
1317
|
+
});
|
|
1318
|
+
return {
|
|
1319
|
+
x402Version: 1,
|
|
1320
|
+
scheme: "exact",
|
|
1321
|
+
network: "starknet-mainnet",
|
|
1322
|
+
payload: {
|
|
1323
|
+
signature,
|
|
1324
|
+
fromAddress: wallet.publicKey,
|
|
1325
|
+
toAddress: requirement.payTo,
|
|
1326
|
+
amount: requirement.maxAmountRequired,
|
|
1327
|
+
asset: requirement.asset,
|
|
1328
|
+
validUntil,
|
|
1329
|
+
nonce
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
},
|
|
1333
|
+
[wallet, encryptKey, session, getBearerToken, chipiSDK, validateRequirement]
|
|
1334
|
+
);
|
|
1335
|
+
const paymentMutation = reactQuery.useMutation(
|
|
1336
|
+
{
|
|
1337
|
+
mutationFn: async ({ url, init }) => {
|
|
1338
|
+
const response = await globalThis.fetch(url, init);
|
|
1339
|
+
if (response.status !== 402) {
|
|
1340
|
+
return response;
|
|
1341
|
+
}
|
|
1342
|
+
const requirement = parsePaymentRequired(response);
|
|
1343
|
+
const payment = await signPayment(requirement);
|
|
1344
|
+
const humanAmount = (Number(BigInt(requirement.maxAmountRequired)) / 1e6).toFixed(6);
|
|
1345
|
+
const isSessionPayment = payment.payload.signature.s === "session";
|
|
1346
|
+
const txHash = isSessionPayment ? payment.payload.signature.r : null;
|
|
1347
|
+
setLastAmount(humanAmount);
|
|
1348
|
+
setLastTxHash(txHash);
|
|
1349
|
+
setLastPayment({ txHash, amount: humanAmount, timestamp: Date.now() });
|
|
1350
|
+
totalSpentRef.current += BigInt(requirement.maxAmountRequired);
|
|
1351
|
+
setTotalSpent((Number(totalSpentRef.current) / 1e6).toFixed(6));
|
|
1352
|
+
paymentCountRef.current += 1;
|
|
1353
|
+
setPaymentCount(paymentCountRef.current);
|
|
1354
|
+
const headers = new Headers(init?.headers);
|
|
1355
|
+
headers.set("X-PAYMENT", JSON.stringify(payment));
|
|
1356
|
+
const paidResponse = await globalThis.fetch(url, { ...init, headers });
|
|
1357
|
+
if (paidResponse.status === 402) {
|
|
1358
|
+
throw new Error(
|
|
1359
|
+
"Server returned 402 after payment was submitted. Payment may have failed verification."
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
if (onPaymentComplete) {
|
|
1363
|
+
void Promise.resolve(onPaymentComplete(txHash ?? "", humanAmount)).catch(() => {
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
return paidResponse;
|
|
1367
|
+
},
|
|
1368
|
+
onError: (err) => {
|
|
1369
|
+
setError(err);
|
|
1370
|
+
},
|
|
1371
|
+
onSuccess: () => {
|
|
1372
|
+
setError(null);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
);
|
|
1376
|
+
const x402Fetch = react.useCallback(
|
|
1377
|
+
async (url, init) => {
|
|
1378
|
+
return paymentMutation.mutateAsync({ url, init });
|
|
1379
|
+
},
|
|
1380
|
+
[paymentMutation]
|
|
1381
|
+
);
|
|
1382
|
+
return {
|
|
1383
|
+
x402Fetch,
|
|
1384
|
+
payFetch: x402Fetch,
|
|
1385
|
+
signPayment,
|
|
1386
|
+
isPaying: paymentMutation.isPending,
|
|
1387
|
+
lastTxHash,
|
|
1388
|
+
lastAmount,
|
|
1389
|
+
lastPayment,
|
|
1390
|
+
totalSpent,
|
|
1391
|
+
error: error || paymentMutation.error || null,
|
|
1392
|
+
paymentCount
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1057
1395
|
|
|
1058
1396
|
exports.useAddSessionKeyToContract = useAddSessionKeyToContract;
|
|
1059
1397
|
exports.useApprove = useApprove;
|
|
@@ -1068,7 +1406,9 @@ exports.useGetSku = useGetSku;
|
|
|
1068
1406
|
exports.useGetSkuList = useGetSkuList;
|
|
1069
1407
|
exports.useGetSkuPurchase = useGetSkuPurchase;
|
|
1070
1408
|
exports.useGetTokenBalance = useGetTokenBalance;
|
|
1409
|
+
exports.useGetTransaction = useGetTransaction;
|
|
1071
1410
|
exports.useGetTransactionList = useGetTransactionList;
|
|
1411
|
+
exports.useGetTransactionStatus = useGetTransactionStatus;
|
|
1072
1412
|
exports.useGetWallet = useGetWallet;
|
|
1073
1413
|
exports.useMigrateWalletToPasskey = useMigrateWalletToPasskey;
|
|
1074
1414
|
exports.usePurchaseSku = usePurchaseSku;
|
|
@@ -1076,5 +1416,6 @@ exports.useRecordSendTransaction = useRecordSendTransaction;
|
|
|
1076
1416
|
exports.useRevokeSessionKey = useRevokeSessionKey;
|
|
1077
1417
|
exports.useTransfer = useTransfer;
|
|
1078
1418
|
exports.useUpdateWalletEncryption = useUpdateWalletEncryption;
|
|
1419
|
+
exports.useX402Payment = useX402Payment;
|
|
1079
1420
|
//# sourceMappingURL=hooks.js.map
|
|
1080
1421
|
//# sourceMappingURL=hooks.js.map
|