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