@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/index.mjs CHANGED
@@ -1,11 +1,12 @@
1
- import React, { createContext, useContext, useMemo, useCallback, useState, useEffect } from 'react';
1
+ import React, { createContext, useContext, useMemo, useCallback, useState, useEffect, useRef } from 'react';
2
2
  import { QueryClient, QueryClientProvider, useMutation, useQueryClient, useQuery } from '@tanstack/react-query';
3
3
  import { ChipiSDK, decryptPrivateKey, encryptPrivateKey } from '@chipi-stack/backend';
4
4
  import { getWalletEncryptKey, createWalletPasskey } from '@chipi-stack/chipi-passkey';
5
5
  import { jsx } from '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
8
  export { Chain, ChainToken } from '@chipi-stack/types';
9
+ import { buildX402TypedData } from '@chipi-stack/core';
9
10
 
10
11
  // src/context/ChipiProvider.tsx
11
12
  var ChipiContext = createContext(null);
@@ -32,11 +33,7 @@ var createDefaultQueryClient = () => new QueryClient({
32
33
  }
33
34
  }
34
35
  });
35
- function ChipiProvider({
36
- children,
37
- config,
38
- passkeyAdapter
39
- }) {
36
+ function ChipiProvider({ children, config, passkeyAdapter }) {
40
37
  const queryClient = React.useMemo(() => createDefaultQueryClient(), []);
41
38
  const chipiSDK = React.useMemo(() => new ChipiSDK(config), [config]);
42
39
  const resolvedPasskeyAdapter = passkeyAdapter ?? defaultPasskeyAdapter;
@@ -80,6 +77,9 @@ function useCreateWallet() {
80
77
  throw new Error("Failed to create passkey for wallet");
81
78
  }
82
79
  }
80
+ if (!encryptKey) {
81
+ throw new Error("encryptKey is required when usePasskey is false");
82
+ }
83
83
  return chipiSDK.createWallet({
84
84
  params: {
85
85
  ...input.params,
@@ -106,8 +106,7 @@ function useGetWallet(input) {
106
106
  const query = useQuery({
107
107
  queryKey: ["wallet", input?.params?.externalUserId],
108
108
  queryFn: async () => {
109
- if (!input || !input.params || !input.getBearerToken)
110
- throw new Error("Input is required");
109
+ if (!input || !input.params || !input.getBearerToken) throw new Error("Input is required");
111
110
  const bearerToken = await input.getBearerToken();
112
111
  if (!bearerToken) throw new Error("Bearer token is required");
113
112
  return chipiSDK.getWallet(input.params, bearerToken);
@@ -149,22 +148,16 @@ function useGetWallet(input) {
149
148
  function useChipiWallet(config) {
150
149
  const { chipiSDK } = useChipiContext();
151
150
  const queryClient = useQueryClient();
152
- const {
153
- externalUserId,
154
- getBearerToken,
155
- defaultToken = "USDC",
156
- enabled = true
157
- } = config;
158
- const isEnabled = Boolean(
159
- enabled && externalUserId && externalUserId.trim() !== ""
160
- );
151
+ const { externalUserId, getBearerToken, defaultToken = "USDC", enabled = true } = config;
152
+ const trimmedUserId = externalUserId?.trim() || null;
153
+ const isEnabled = Boolean(enabled && trimmedUserId);
161
154
  const walletQuery = useQuery({
162
- queryKey: ["chipi-wallet", externalUserId],
155
+ queryKey: ["chipi-wallet", trimmedUserId],
163
156
  queryFn: async () => {
164
- if (!externalUserId) return null;
157
+ if (!trimmedUserId) return null;
165
158
  const bearerToken = await getBearerToken();
166
159
  if (!bearerToken) throw new Error("Bearer token is required");
167
- return chipiSDK.getWallet({ externalUserId }, bearerToken);
160
+ return chipiSDK.getWallet({ externalUserId: trimmedUserId }, bearerToken);
168
161
  },
169
162
  enabled: isEnabled,
170
163
  retry: (failureCount, error) => {
@@ -190,7 +183,7 @@ function useChipiWallet(config) {
190
183
  bearerToken
191
184
  );
192
185
  },
193
- enabled: Boolean(walletPublicKey),
186
+ enabled: Boolean(isEnabled && walletPublicKey),
194
187
  retry: (failureCount, error) => {
195
188
  if (error instanceof ChipiApiError || error?.status) {
196
189
  return false;
@@ -200,14 +193,14 @@ function useChipiWallet(config) {
200
193
  });
201
194
  const createWalletMutation = useMutation({
202
195
  mutationFn: async ({ encryptKey, walletType = "CHIPI" }) => {
203
- if (!externalUserId) throw new Error("External user ID is required");
196
+ if (!trimmedUserId) throw new Error("External user ID is required");
204
197
  const bearerToken = await getBearerToken();
205
198
  if (!bearerToken) throw new Error("Bearer token is required");
206
199
  return chipiSDK.createWallet({
207
200
  params: {
208
201
  chain: Chain.STARKNET,
209
202
  encryptKey,
210
- externalUserId,
203
+ externalUserId: trimmedUserId,
211
204
  walletType
212
205
  },
213
206
  bearerToken
@@ -215,7 +208,7 @@ function useChipiWallet(config) {
215
208
  },
216
209
  onSuccess: () => {
217
210
  queryClient.invalidateQueries({
218
- queryKey: ["chipi-wallet", externalUserId]
211
+ queryKey: ["chipi-wallet", trimmedUserId]
219
212
  });
220
213
  }
221
214
  });
@@ -290,14 +283,10 @@ function useChipiSession(config) {
290
283
  defaultMaxCalls = 1e3,
291
284
  autoCheckStatus = true
292
285
  } = config;
293
- const [localSession, setLocalSession] = useState(
294
- storedSession ?? null
295
- );
286
+ const [localSession, setLocalSession] = useState(storedSession ?? null);
296
287
  const [error, setError] = useState(null);
297
288
  useEffect(() => {
298
- if (storedSession) {
299
- setLocalSession(storedSession);
300
- }
289
+ setLocalSession(storedSession ?? null);
301
290
  }, [storedSession]);
302
291
  const supportsSession = useMemo(() => {
303
292
  if (!wallet) return false;
@@ -333,27 +322,32 @@ function useChipiSession(config) {
333
322
  const hasActiveSession = sessionState === "active";
334
323
  const isSessionExpired = sessionState === "expired";
335
324
  const remainingCalls = sessionStatusQuery.data?.remainingCalls;
336
- const createSessionMutation = useMutation({
337
- mutationFn: async (params = {}) => {
338
- if (!encryptKey) throw new Error("Encryption key (PIN) is required");
339
- if (!supportsSession) throw new Error("Wallet does not support sessions. Only CHIPI wallets support session keys.");
340
- const sessionData = chipiSDK.sessions.createSessionKey({
341
- encryptKey,
342
- durationSeconds: params.durationSeconds ?? defaultDurationSeconds
343
- });
344
- return sessionData;
345
- },
346
- onSuccess: async (sessionData) => {
347
- setLocalSession(sessionData);
348
- setError(null);
349
- if (onSessionCreated) {
350
- await onSessionCreated(sessionData);
325
+ const createSessionMutation = useMutation(
326
+ {
327
+ mutationFn: async (params = {}) => {
328
+ if (!encryptKey) throw new Error("Encryption key (PIN) is required");
329
+ if (!supportsSession)
330
+ throw new Error(
331
+ "Wallet does not support sessions. Only CHIPI wallets support session keys."
332
+ );
333
+ const sessionData = chipiSDK.sessions.createSessionKey({
334
+ encryptKey,
335
+ durationSeconds: params.durationSeconds ?? defaultDurationSeconds
336
+ });
337
+ return sessionData;
338
+ },
339
+ onSuccess: async (sessionData) => {
340
+ setLocalSession(sessionData);
341
+ setError(null);
342
+ if (onSessionCreated) {
343
+ await onSessionCreated(sessionData);
344
+ }
345
+ },
346
+ onError: (err) => {
347
+ setError(err);
351
348
  }
352
- },
353
- onError: (err) => {
354
- setError(err);
355
349
  }
356
- });
350
+ );
357
351
  const registerSessionMutation = useMutation({
358
352
  mutationFn: async (sessionConfig = {}) => {
359
353
  if (!wallet) throw new Error("Wallet is required");
@@ -514,22 +508,19 @@ function useMigrateWalletToPasskey() {
514
508
  mutationFn: async (input) => {
515
509
  const { wallet, oldEncryptKey, externalUserId, bearerToken } = input;
516
510
  try {
517
- const passkeyResult = await createWalletPasskey(
518
- externalUserId,
519
- externalUserId
520
- // Using externalUserId as userName
521
- );
522
511
  let decryptedPrivateKey;
523
512
  try {
524
- decryptedPrivateKey = decryptPrivateKey(
525
- wallet.encryptedPrivateKey,
526
- oldEncryptKey
527
- );
528
- } catch (error) {
513
+ decryptedPrivateKey = decryptPrivateKey(wallet.encryptedPrivateKey, oldEncryptKey);
514
+ } catch {
529
515
  throw new Error(
530
516
  "Failed to decrypt wallet with provided encryptKey. Please verify your PIN/password is correct."
531
517
  );
532
518
  }
519
+ const passkeyResult = await createWalletPasskey(
520
+ externalUserId,
521
+ externalUserId
522
+ // Using externalUserId as userName
523
+ );
533
524
  const newEncryptedPrivateKey = encryptPrivateKey(
534
525
  decryptedPrivateKey,
535
526
  passkeyResult.encryptKey
@@ -589,44 +580,49 @@ function useUpdateWalletEncryption() {
589
580
  }
590
581
  function useTransfer() {
591
582
  const { chipiSDK, passkeyAdapter } = useChipiContext();
592
- const mutation = useMutation(
593
- {
594
- mutationFn: async (input) => {
595
- let encryptKey = input.params.encryptKey;
596
- if (input.params.usePasskey) {
597
- if (!input.params.externalUserId) {
598
- throw new Error(
599
- "externalUserId is required when usePasskey is true"
600
- );
583
+ const mutation = useMutation({
584
+ mutationFn: async (input) => {
585
+ let encryptKey = input.params.encryptKey;
586
+ if (input.params.usePasskey) {
587
+ if (!input.params.externalUserId) {
588
+ throw new Error("externalUserId is required when usePasskey is true");
589
+ }
590
+ try {
591
+ const key = await passkeyAdapter.getWalletEncryptKey({
592
+ externalUserId: input.params.externalUserId
593
+ });
594
+ if (!key) {
595
+ throw new Error("Passkey authentication was cancelled");
596
+ }
597
+ encryptKey = key;
598
+ } catch (error) {
599
+ if (error instanceof DOMException && error.name === "NotAllowedError") {
600
+ throw new Error("Passkey authentication was cancelled by the user");
601
601
  }
602
- try {
603
- const key = await passkeyAdapter.getWalletEncryptKey({
604
- externalUserId: input.params.externalUserId
605
- });
606
- if (!key) {
607
- throw new Error("Passkey authentication was cancelled");
608
- }
609
- encryptKey = key;
610
- } catch (error) {
611
- if (error instanceof Error) {
612
- throw new Error(
613
- `Passkey authentication failed: ${error.message}`
614
- );
615
- }
616
- throw new Error("Failed to authenticate with passkey");
602
+ if (error instanceof Error) {
603
+ throw error;
617
604
  }
605
+ throw new Error("Failed to authenticate with passkey");
618
606
  }
619
- return chipiSDK.transfer({
620
- params: {
621
- ...input.params,
622
- amount: String(input.params.amount),
623
- encryptKey
624
- },
625
- bearerToken: input.bearerToken
626
- });
627
607
  }
608
+ if (!encryptKey) {
609
+ throw new Error("encryptKey is required when usePasskey is false");
610
+ }
611
+ const {
612
+ usePasskey: _usePasskey,
613
+ externalUserId: _externalUserId,
614
+ ...transferParams
615
+ } = input.params;
616
+ return chipiSDK.transfer({
617
+ params: {
618
+ ...transferParams,
619
+ amount: String(transferParams.amount),
620
+ encryptKey
621
+ },
622
+ bearerToken: input.bearerToken
623
+ });
628
624
  }
629
- );
625
+ });
630
626
  return {
631
627
  transfer: mutation.mutate,
632
628
  transferAsync: mutation.mutateAsync,
@@ -662,10 +658,20 @@ function useApprove() {
662
658
  throw new Error("Failed to authenticate with passkey");
663
659
  }
664
660
  }
661
+ if (!encryptKey) {
662
+ throw new Error(
663
+ "encryptKey is required when usePasskey is false. Provide a PIN or enable usePasskey."
664
+ );
665
+ }
666
+ const {
667
+ usePasskey: _usePasskey,
668
+ externalUserId: _externalUserId,
669
+ ...approveParams
670
+ } = params.params;
665
671
  return chipiSDK.approve({
666
672
  params: {
667
- ...params.params,
668
- amount: String(params.params.amount),
673
+ ...approveParams,
674
+ amount: String(approveParams.amount),
669
675
  encryptKey
670
676
  },
671
677
  bearerToken: params.bearerToken
@@ -715,9 +721,8 @@ function useGetTransactionList(input) {
715
721
  });
716
722
  const fetchTransactionList = async (newInput) => {
717
723
  return queryClient.fetchQuery({
718
- queryKey: buildQueryKey(newInput?.query),
724
+ queryKey: buildQueryKey(newInput.query),
719
725
  queryFn: async () => {
720
- if (!newInput?.getBearerToken) throw new Error("getBearerToken is required");
721
726
  const bearerToken = await newInput.getBearerToken();
722
727
  if (!bearerToken) throw new Error("Bearer token is required");
723
728
  return chipiSDK.getTransactionList(newInput.query, bearerToken);
@@ -735,6 +740,104 @@ function useGetTransactionList(input) {
735
740
  fetchTransactionList
736
741
  };
737
742
  }
743
+ function useGetTransaction(input) {
744
+ const { chipiSDK } = useChipiContext();
745
+ const queryClient = useQueryClient();
746
+ const buildQueryKey = (hashOrId) => ["transaction", hashOrId ?? null];
747
+ const query = useQuery({
748
+ queryKey: buildQueryKey(input?.hashOrId),
749
+ queryFn: async () => {
750
+ if (!input?.hashOrId) throw new Error("hashOrId is required");
751
+ if (!input?.getBearerToken) throw new Error("getBearerToken is required");
752
+ const bearerToken = await input.getBearerToken();
753
+ if (!bearerToken) throw new Error("Bearer token is required");
754
+ return chipiSDK.getTransaction(input.hashOrId, bearerToken);
755
+ },
756
+ enabled: Boolean(input?.hashOrId && input?.getBearerToken),
757
+ retry: (failureCount, error) => {
758
+ if (error instanceof ChipiApiError || error?.status) {
759
+ return false;
760
+ }
761
+ return failureCount < 3;
762
+ },
763
+ ...input?.queryOptions
764
+ });
765
+ const fetchTransaction = async (newInput) => {
766
+ return queryClient.fetchQuery({
767
+ queryKey: buildQueryKey(newInput.hashOrId),
768
+ queryFn: async () => {
769
+ const bearerToken = await newInput.getBearerToken();
770
+ if (!bearerToken) throw new Error("Bearer token is required");
771
+ return chipiSDK.getTransaction(newInput.hashOrId, bearerToken);
772
+ },
773
+ retry: (failureCount, error) => {
774
+ if (error instanceof ChipiApiError || error?.status) {
775
+ return false;
776
+ }
777
+ return failureCount < 3;
778
+ }
779
+ });
780
+ };
781
+ return {
782
+ ...query,
783
+ fetchTransaction
784
+ };
785
+ }
786
+ var TERMINAL_STATUSES = [
787
+ OnChainTxStatus.ACCEPTED_ON_L1,
788
+ OnChainTxStatus.REJECTED,
789
+ OnChainTxStatus.REVERTED
790
+ ];
791
+ function useGetTransactionStatus(input) {
792
+ const { chipiSDK } = useChipiContext();
793
+ const queryClient = useQueryClient();
794
+ const buildQueryKey = (hash) => ["transaction-status", hash ?? null];
795
+ const query = useQuery({
796
+ queryKey: buildQueryKey(input?.hash),
797
+ queryFn: async () => {
798
+ if (!input?.hash) throw new Error("hash is required");
799
+ if (!input?.getBearerToken) throw new Error("getBearerToken is required");
800
+ const bearerToken = await input.getBearerToken();
801
+ if (!bearerToken) throw new Error("Bearer token is required");
802
+ return chipiSDK.getTransactionStatus(input.hash, bearerToken);
803
+ },
804
+ enabled: Boolean(input?.hash && input?.getBearerToken),
805
+ refetchInterval: (query2) => {
806
+ const status = query2.state.data?.status;
807
+ if (status && TERMINAL_STATUSES.includes(status)) {
808
+ return false;
809
+ }
810
+ return input?.refetchInterval ?? 3e3;
811
+ },
812
+ retry: (failureCount, error) => {
813
+ if (error instanceof ChipiApiError || error?.status) {
814
+ return false;
815
+ }
816
+ return failureCount < 3;
817
+ },
818
+ ...input?.queryOptions
819
+ });
820
+ const fetchTransactionStatus = async (newInput) => {
821
+ return queryClient.fetchQuery({
822
+ queryKey: buildQueryKey(newInput.hash),
823
+ queryFn: async () => {
824
+ const bearerToken = await newInput.getBearerToken();
825
+ if (!bearerToken) throw new Error("Bearer token is required");
826
+ return chipiSDK.getTransactionStatus(newInput.hash, bearerToken);
827
+ },
828
+ retry: (failureCount, error) => {
829
+ if (error instanceof ChipiApiError || error?.status) {
830
+ return false;
831
+ }
832
+ return failureCount < 3;
833
+ }
834
+ });
835
+ };
836
+ return {
837
+ ...query,
838
+ fetchTransactionStatus
839
+ };
840
+ }
738
841
  function usePurchaseSku() {
739
842
  const { chipiSDK } = useChipiContext();
740
843
  const mutation = useMutation({
@@ -774,8 +877,7 @@ function useGetSkuPurchase(input) {
774
877
  queryKey: ["sku-purchase", newInput?.id],
775
878
  queryFn: async () => {
776
879
  if (!newInput?.id) throw new Error("id is required");
777
- if (!newInput?.getBearerToken)
778
- throw new Error("getBearerToken is required");
880
+ if (!newInput?.getBearerToken) throw new Error("getBearerToken is required");
779
881
  const bearerToken = await newInput.getBearerToken();
780
882
  if (!bearerToken) throw new Error("Bearer token is required");
781
883
  return chipiSDK.getSkuPurchase(newInput.id, bearerToken);
@@ -849,9 +951,7 @@ function useGetSku(input) {
849
951
  if (!bearerToken) throw new Error("Bearer token is required");
850
952
  return chipiSDK.getSku(input.id, bearerToken);
851
953
  },
852
- enabled: Boolean(
853
- input?.id && input?.getBearerToken && input.id.trim() !== ""
854
- ),
954
+ enabled: Boolean(input?.id && input?.getBearerToken && input.id.trim() !== ""),
855
955
  retry: (failureCount, error) => {
856
956
  if (error instanceof ChipiApiError || error?.status) {
857
957
  return false;
@@ -864,14 +964,13 @@ function useGetSku(input) {
864
964
  return queryClient.fetchQuery({
865
965
  queryKey: ["sku", input2?.id],
866
966
  queryFn: async () => {
867
- if (!input2?.getBearerToken)
868
- throw new Error("getBearerToken is required");
967
+ if (!input2?.getBearerToken) throw new Error("getBearerToken is required");
869
968
  const bearerToken = await input2.getBearerToken();
870
969
  if (!bearerToken) throw new Error("Bearer token is required");
871
970
  return chipiSDK.getSku(input2.id, bearerToken);
872
971
  },
873
972
  retry: (failureCount, error) => {
874
- if (error instanceof ChipiApiError || error?.code) {
973
+ if (error instanceof ChipiApiError || error?.status) {
875
974
  return false;
876
975
  }
877
976
  return failureCount < 3;
@@ -890,9 +989,7 @@ function useCallAnyContract() {
890
989
  let encryptKey = params.params.encryptKey;
891
990
  if (params.params.usePasskey) {
892
991
  if (!params.params.externalUserId) {
893
- throw new Error(
894
- "externalUserId is required when usePasskey is true"
895
- );
992
+ throw new Error("externalUserId is required when usePasskey is true");
896
993
  }
897
994
  try {
898
995
  const key = await passkeyAdapter.getWalletEncryptKey({
@@ -903,17 +1000,26 @@ function useCallAnyContract() {
903
1000
  }
904
1001
  encryptKey = key;
905
1002
  } catch (error) {
1003
+ if (error instanceof DOMException && error.name === "NotAllowedError") {
1004
+ throw new Error("Passkey authentication was cancelled by the user");
1005
+ }
906
1006
  if (error instanceof Error) {
907
- throw new Error(
908
- `Passkey authentication failed: ${error.message}`
909
- );
1007
+ throw error;
910
1008
  }
911
1009
  throw new Error("Failed to authenticate with passkey");
912
1010
  }
913
1011
  }
1012
+ if (!encryptKey) {
1013
+ throw new Error("encryptKey is required when usePasskey is false");
1014
+ }
1015
+ const {
1016
+ usePasskey: _usePasskey,
1017
+ externalUserId: _externalUserId,
1018
+ ...contractParams
1019
+ } = params.params;
914
1020
  return chipiSDK.callAnyContract({
915
1021
  params: {
916
- ...params.params,
1022
+ ...contractParams,
917
1023
  encryptKey
918
1024
  },
919
1025
  bearerToken: params.bearerToken
@@ -981,7 +1087,8 @@ function useGetTokenBalance(input) {
981
1087
  return queryClient.fetchQuery({
982
1088
  queryKey: buildQueryKey(newInput?.params),
983
1089
  queryFn: async () => {
984
- if (!newInput || !newInput.getBearerToken || !newInput.params) throw new Error("Input is required");
1090
+ if (!newInput || !newInput.getBearerToken || !newInput.params)
1091
+ throw new Error("Input is required");
985
1092
  const bearerToken = await newInput.getBearerToken();
986
1093
  if (!bearerToken) throw new Error("Bearer token is required");
987
1094
  return chipiSDK.getTokenBalance(newInput.params, bearerToken);
@@ -1018,10 +1125,7 @@ function useCreateSessionKey() {
1018
1125
  function useAddSessionKeyToContract() {
1019
1126
  const { chipiSDK } = useChipiContext();
1020
1127
  const mutation = useMutation({
1021
- mutationFn: (input) => chipiSDK.sessions.addSessionKeyToContract(
1022
- input.params,
1023
- input.bearerToken
1024
- )
1128
+ mutationFn: (input) => chipiSDK.sessions.addSessionKeyToContract(input.params, input.bearerToken)
1025
1129
  });
1026
1130
  return {
1027
1131
  addSessionKeyToContract: mutation.mutate,
@@ -1053,11 +1157,7 @@ function useRevokeSessionKey() {
1053
1157
  function useGetSessionData(params, options) {
1054
1158
  const { chipiSDK } = useChipiContext();
1055
1159
  const query = useQuery({
1056
- queryKey: [
1057
- "sessionData",
1058
- params?.walletAddress,
1059
- params?.sessionPublicKey
1060
- ],
1160
+ queryKey: ["sessionData", params?.walletAddress, params?.sessionPublicKey],
1061
1161
  queryFn: () => {
1062
1162
  if (!params) {
1063
1163
  throw new Error("Session data params are required");
@@ -1094,7 +1194,241 @@ function useExecuteWithSession() {
1094
1194
  reset: mutation.reset
1095
1195
  };
1096
1196
  }
1197
+ function useX402Payment(config) {
1198
+ const { chipiSDK } = useChipiContext();
1199
+ const {
1200
+ wallet,
1201
+ encryptKey,
1202
+ getBearerToken: getBearerTokenFn,
1203
+ bearerToken: staticBearerToken,
1204
+ maxPaymentAmount: maxPaymentAmountProp,
1205
+ maxAmount,
1206
+ allowedRecipients,
1207
+ session,
1208
+ onPaymentComplete
1209
+ } = config;
1210
+ const maxPaymentAmount = maxPaymentAmountProp ?? maxAmount;
1211
+ const getBearerToken = useMemo(
1212
+ () => getBearerTokenFn ?? (staticBearerToken ? () => Promise.resolve(staticBearerToken) : () => Promise.resolve(void 0)),
1213
+ [getBearerTokenFn, staticBearerToken]
1214
+ );
1215
+ const [lastTxHash, setLastTxHash] = useState(null);
1216
+ const [lastAmount, setLastAmount] = useState(null);
1217
+ const [lastPayment, setLastPayment] = useState(null);
1218
+ const [error, setError] = useState(null);
1219
+ const paymentCountRef = useRef(0);
1220
+ const [paymentCount, setPaymentCount] = useState(0);
1221
+ const totalSpentRef = useRef(0n);
1222
+ const [totalSpent, setTotalSpent] = useState("0.000000");
1223
+ const parsePaymentRequired = useCallback((response) => {
1224
+ const headerValue = response.headers.get("payment-required");
1225
+ if (!headerValue) {
1226
+ throw new Error(
1227
+ "Server returned 402 but no PAYMENT-REQUIRED header. The server may not support x402 or is misconfigured."
1228
+ );
1229
+ }
1230
+ try {
1231
+ return JSON.parse(headerValue);
1232
+ } catch {
1233
+ throw new Error("Malformed PAYMENT-REQUIRED header: invalid JSON");
1234
+ }
1235
+ }, []);
1236
+ const validateRequirement = useCallback(
1237
+ (requirement) => {
1238
+ if (requirement.scheme !== "exact") {
1239
+ throw new Error(
1240
+ `Unsupported payment scheme: "${requirement.scheme}". Only "exact" is supported.`
1241
+ );
1242
+ }
1243
+ if (requirement.network !== "starknet-mainnet") {
1244
+ throw new Error(
1245
+ `Unsupported network: "${requirement.network}". Only "starknet-mainnet" is supported.`
1246
+ );
1247
+ }
1248
+ const USDC_MAINNET = "0x033068F6539f8e6e6b131e6B2B814e6c34A5224bC66947c47DaB9dFeE93b35fb";
1249
+ const normalizeAddr = (a) => a.toLowerCase().replace(/^0x0+/, "0x");
1250
+ if (normalizeAddr(requirement.asset) !== normalizeAddr(USDC_MAINNET)) {
1251
+ throw new Error(`Unsupported asset: ${requirement.asset}. Only USDC is supported.`);
1252
+ }
1253
+ if (!requirement.payTo) {
1254
+ throw new Error("Invalid payment requirement: missing payTo address");
1255
+ }
1256
+ const timeoutSeconds = Number(requirement.maxTimeoutSeconds);
1257
+ if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) {
1258
+ throw new Error("Invalid payment requirement: maxTimeoutSeconds must be a positive number");
1259
+ }
1260
+ let requiredAmount;
1261
+ try {
1262
+ requiredAmount = BigInt(requirement.maxAmountRequired);
1263
+ } catch {
1264
+ throw new Error("Invalid payment requirement: maxAmountRequired must be an integer string");
1265
+ }
1266
+ if (requiredAmount <= 0n) {
1267
+ throw new Error("Invalid payment requirement: maxAmountRequired must be > 0");
1268
+ }
1269
+ if (maxPaymentAmount) {
1270
+ const trimmed = maxPaymentAmount.trim();
1271
+ if (!/^\d+(\.\d{1,6})?$/.test(trimmed)) {
1272
+ throw new Error(`Invalid maxPaymentAmount format: "${maxPaymentAmount}"`);
1273
+ }
1274
+ const [whole, frac = ""] = trimmed.split(".");
1275
+ const fracPadded = (frac + "000000").slice(0, 6);
1276
+ const maxBaseUnits = BigInt((whole || "0") + fracPadded);
1277
+ if (requiredAmount > maxBaseUnits) {
1278
+ const requiredHuman = (Number(requiredAmount) / 1e6).toFixed(6);
1279
+ throw new Error(
1280
+ `Payment amount ${requiredHuman} USDC exceeds maximum allowed ${maxPaymentAmount} USDC`
1281
+ );
1282
+ }
1283
+ }
1284
+ if (allowedRecipients && allowedRecipients.length > 0) {
1285
+ const normalize = (a) => a.toLowerCase().replace(/^0x0+/, "0x");
1286
+ const normalized = normalize(requirement.payTo);
1287
+ const isAllowed = allowedRecipients.some((addr) => normalize(addr) === normalized);
1288
+ if (!isAllowed) {
1289
+ throw new Error(`Recipient ${requirement.payTo} is not in the allowed recipients list`);
1290
+ }
1291
+ }
1292
+ },
1293
+ [maxPaymentAmount, allowedRecipients]
1294
+ );
1295
+ const signPayment = useCallback(
1296
+ async (requirement) => {
1297
+ if (!wallet) {
1298
+ throw new Error("Wallet is required for x402 payments");
1299
+ }
1300
+ if (!encryptKey) {
1301
+ throw new Error("Encryption key (PIN) is required for x402 payments");
1302
+ }
1303
+ validateRequirement(requirement);
1304
+ const nonce = crypto.randomUUID();
1305
+ const validUntil = Math.floor(Date.now() / 1e3) + requirement.maxTimeoutSeconds;
1306
+ if (session) {
1307
+ const bearerToken = await getBearerToken();
1308
+ if (!bearerToken) throw new Error("Bearer token is required");
1309
+ const txHash = await chipiSDK.executeTransactionWithSession({
1310
+ params: {
1311
+ encryptKey,
1312
+ wallet,
1313
+ session,
1314
+ calls: [
1315
+ {
1316
+ contractAddress: requirement.asset,
1317
+ entrypoint: "transfer",
1318
+ calldata: [requirement.payTo, requirement.maxAmountRequired, "0x0"]
1319
+ }
1320
+ ]
1321
+ },
1322
+ bearerToken
1323
+ });
1324
+ return {
1325
+ x402Version: 1,
1326
+ scheme: "exact",
1327
+ network: "starknet-mainnet",
1328
+ payload: {
1329
+ signature: { r: txHash, s: "session" },
1330
+ fromAddress: wallet.publicKey,
1331
+ toAddress: requirement.payTo,
1332
+ amount: requirement.maxAmountRequired,
1333
+ asset: requirement.asset,
1334
+ validUntil,
1335
+ nonce
1336
+ }
1337
+ };
1338
+ }
1339
+ const typedData = buildX402TypedData({
1340
+ from_address: wallet.publicKey,
1341
+ to_address: requirement.payTo,
1342
+ amount: requirement.maxAmountRequired,
1343
+ asset: requirement.asset,
1344
+ valid_until: String(validUntil),
1345
+ nonce
1346
+ });
1347
+ const signature = await chipiSDK.signTypedData({
1348
+ params: {
1349
+ encryptKey,
1350
+ wallet,
1351
+ typedData
1352
+ }
1353
+ });
1354
+ return {
1355
+ x402Version: 1,
1356
+ scheme: "exact",
1357
+ network: "starknet-mainnet",
1358
+ payload: {
1359
+ signature,
1360
+ fromAddress: wallet.publicKey,
1361
+ toAddress: requirement.payTo,
1362
+ amount: requirement.maxAmountRequired,
1363
+ asset: requirement.asset,
1364
+ validUntil,
1365
+ nonce
1366
+ }
1367
+ };
1368
+ },
1369
+ [wallet, encryptKey, session, getBearerToken, chipiSDK, validateRequirement]
1370
+ );
1371
+ const paymentMutation = useMutation(
1372
+ {
1373
+ mutationFn: async ({ url, init }) => {
1374
+ const response = await globalThis.fetch(url, init);
1375
+ if (response.status !== 402) {
1376
+ return response;
1377
+ }
1378
+ const requirement = parsePaymentRequired(response);
1379
+ const payment = await signPayment(requirement);
1380
+ const humanAmount = (Number(BigInt(requirement.maxAmountRequired)) / 1e6).toFixed(6);
1381
+ const isSessionPayment = payment.payload.signature.s === "session";
1382
+ const txHash = isSessionPayment ? payment.payload.signature.r : null;
1383
+ setLastAmount(humanAmount);
1384
+ setLastTxHash(txHash);
1385
+ setLastPayment({ txHash, amount: humanAmount, timestamp: Date.now() });
1386
+ totalSpentRef.current += BigInt(requirement.maxAmountRequired);
1387
+ setTotalSpent((Number(totalSpentRef.current) / 1e6).toFixed(6));
1388
+ paymentCountRef.current += 1;
1389
+ setPaymentCount(paymentCountRef.current);
1390
+ const headers = new Headers(init?.headers);
1391
+ headers.set("X-PAYMENT", JSON.stringify(payment));
1392
+ const paidResponse = await globalThis.fetch(url, { ...init, headers });
1393
+ if (paidResponse.status === 402) {
1394
+ throw new Error(
1395
+ "Server returned 402 after payment was submitted. Payment may have failed verification."
1396
+ );
1397
+ }
1398
+ if (onPaymentComplete) {
1399
+ void Promise.resolve(onPaymentComplete(txHash ?? "", humanAmount)).catch(() => {
1400
+ });
1401
+ }
1402
+ return paidResponse;
1403
+ },
1404
+ onError: (err) => {
1405
+ setError(err);
1406
+ },
1407
+ onSuccess: () => {
1408
+ setError(null);
1409
+ }
1410
+ }
1411
+ );
1412
+ const x402Fetch = useCallback(
1413
+ async (url, init) => {
1414
+ return paymentMutation.mutateAsync({ url, init });
1415
+ },
1416
+ [paymentMutation]
1417
+ );
1418
+ return {
1419
+ x402Fetch,
1420
+ payFetch: x402Fetch,
1421
+ signPayment,
1422
+ isPaying: paymentMutation.isPending,
1423
+ lastTxHash,
1424
+ lastAmount,
1425
+ lastPayment,
1426
+ totalSpent,
1427
+ error: error || paymentMutation.error || null,
1428
+ paymentCount
1429
+ };
1430
+ }
1097
1431
 
1098
- export { ChipiProvider, useAddSessionKeyToContract, useApprove, useCallAnyContract, useChipiContext, useChipiSession, useChipiWallet, useCreateSessionKey, useCreateWallet, useExecuteWithSession, useGetSessionData, useGetSku, useGetSkuList, useGetSkuPurchase, useGetTokenBalance, useGetTransactionList, useGetWallet, useMigrateWalletToPasskey, usePurchaseSku, useRecordSendTransaction, useRevokeSessionKey, useTransfer, useUpdateWalletEncryption };
1432
+ export { ChipiProvider, useAddSessionKeyToContract, useApprove, useCallAnyContract, useChipiContext, useChipiSession, useChipiWallet, useCreateSessionKey, useCreateWallet, useExecuteWithSession, useGetSessionData, useGetSku, useGetSkuList, useGetSkuPurchase, useGetTokenBalance, useGetTransaction, useGetTransactionList, useGetTransactionStatus, useGetWallet, useMigrateWalletToPasskey, usePurchaseSku, useRecordSendTransaction, useRevokeSessionKey, useTransfer, useUpdateWalletEncryption, useX402Payment };
1099
1433
  //# sourceMappingURL=index.mjs.map
1100
1434
  //# sourceMappingURL=index.mjs.map