@chipi-stack/chipi-react 14.0.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.d.mts +447 -2
- package/dist/hooks.d.ts +447 -2
- package/dist/hooks.js +336 -0
- package/dist/hooks.js.map +1 -1
- package/dist/hooks.mjs +336 -3
- package/dist/hooks.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +336 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +336 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -4
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);
|
|
@@ -739,6 +740,104 @@ function useGetTransactionList(input) {
|
|
|
739
740
|
fetchTransactionList
|
|
740
741
|
};
|
|
741
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
|
+
}
|
|
742
841
|
function usePurchaseSku() {
|
|
743
842
|
const { chipiSDK } = useChipiContext();
|
|
744
843
|
const mutation = useMutation({
|
|
@@ -1095,7 +1194,241 @@ function useExecuteWithSession() {
|
|
|
1095
1194
|
reset: mutation.reset
|
|
1096
1195
|
};
|
|
1097
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
|
+
}
|
|
1098
1431
|
|
|
1099
|
-
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 };
|
|
1100
1433
|
//# sourceMappingURL=index.mjs.map
|
|
1101
1434
|
//# sourceMappingURL=index.mjs.map
|