@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/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
- externalUserId,
112
- getBearerToken,
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", externalUserId],
117
+ queryKey: ["chipi-wallet", trimmedUserId],
121
118
  queryFn: async () => {
122
- if (!externalUserId) return null;
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 (!externalUserId) throw new Error("External user ID is required");
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", externalUserId]
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
- if (storedSession) {
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
- mutationFn: async (params = {}) => {
296
- if (!encryptKey) throw new Error("Encryption key (PIN) is required");
297
- if (!supportsSession) throw new Error("Wallet does not support sessions. Only CHIPI wallets support session keys.");
298
- const sessionData = chipiSDK.sessions.createSessionKey({
299
- encryptKey,
300
- durationSeconds: params.durationSeconds ?? defaultDurationSeconds
301
- });
302
- return sessionData;
303
- },
304
- onSuccess: async (sessionData) => {
305
- setLocalSession(sessionData);
306
- setError(null);
307
- if (onSessionCreated) {
308
- await onSessionCreated(sessionData);
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
- wallet.encryptedPrivateKey,
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
- mutationFn: async (input) => {
553
- let encryptKey = input.params.encryptKey;
554
- if (input.params.usePasskey) {
555
- if (!input.params.externalUserId) {
556
- throw new Error(
557
- "externalUserId is required when usePasskey is true"
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
- try {
561
- const key = await passkeyAdapter.getWalletEncryptKey({
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
- ...params.params,
626
- amount: String(params.params.amount),
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?.query),
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?.code) {
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 new Error(
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
- ...params.params,
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) throw new Error("Input is required");
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