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