@chipi-stack/backend 14.0.0 → 14.1.1
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/README.md +9 -0
- package/dist/index.d.mts +228 -2
- package/dist/index.d.ts +228 -2
- package/dist/index.js +651 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +650 -28
- package/dist/index.mjs.map +1 -1
- package/dist/skuPurchases.js +67 -16
- package/dist/skuPurchases.js.map +1 -1
- package/dist/skuPurchases.mjs +67 -16
- package/dist/skuPurchases.mjs.map +1 -1
- package/dist/transactions.d.mts +9 -1
- package/dist/transactions.d.ts +9 -1
- package/dist/transactions.js +67 -16
- package/dist/transactions.js.map +1 -1
- package/dist/transactions.mjs +67 -16
- package/dist/transactions.mjs.map +1 -1
- package/dist/wallets.d.mts +10 -1
- package/dist/wallets.d.ts +10 -1
- package/dist/wallets.js +25 -3
- package/dist/wallets.js.map +1 -1
- package/dist/wallets.mjs +25 -3
- package/dist/wallets.mjs.map +1 -1
- package/package.json +38 -10
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { isValidApiKey, ChipiAuthError, STARKNET_NETWORKS, API_VERSIONING, validateErrorResponse, ChipiApiError, handleApiError, WALLET_RPC_ENDPOINTS, WALLET_CLASS_HASHES, API_ENDPOINTS, ChipiTransactionError, formatAmount, SKU_CONTRACTS, CARRIER_IDS, SERVICE_TYPES, ChipiSessionError, SESSION_ERRORS, SESSION_DEFAULTS, SESSION_ENTRYPOINTS } from '@chipi-stack/shared';
|
|
1
|
+
import { isValidApiKey, ChipiAuthError, STARKNET_NETWORKS, API_VERSIONING, validateErrorResponse, ChipiApiError, handleApiError, WALLET_RPC_ENDPOINTS, WALLET_CLASS_HASHES, API_ENDPOINTS, ChipiTransactionError, getWalletTypeFromClassHash, formatAmount, SKU_CONTRACTS, CARRIER_IDS, SERVICE_TYPES, ChipiSessionError, SESSION_ERRORS, SESSION_DEFAULTS, SESSION_ENTRYPOINTS, CONTRACT_ADDRESSES, TOKEN_DECIMALS } from '@chipi-stack/shared';
|
|
2
2
|
import CryptoES from 'crypto-es';
|
|
3
|
-
import { RpcProvider, ec, hash, Account, num, CairoCustomEnum, CairoOption, CairoOptionVariant, CallData, cairo, typedData } from 'starknet';
|
|
3
|
+
import { RpcProvider, ec, hash, Account, num, CairoCustomEnum, CairoOption, CairoOptionVariant, CallData, cairo, typedData, uint256 } from 'starknet';
|
|
4
4
|
import { STARKNET_CONTRACTS, Chain } from '@chipi-stack/types';
|
|
5
5
|
export { Chain, ChainToken } from '@chipi-stack/types';
|
|
6
|
+
import { buildX402TypedData } from '@chipi-stack/core';
|
|
6
7
|
|
|
7
8
|
// src/chipi-sdk.ts
|
|
8
9
|
var ChipiClient = class {
|
|
@@ -28,7 +29,7 @@ var ChipiClient = class {
|
|
|
28
29
|
const cleanUrl = this.customAlphaUrl.replace(/\/v\d+\/?$/, "").replace(/\/$/, "");
|
|
29
30
|
return `${cleanUrl}/${version}`;
|
|
30
31
|
}
|
|
31
|
-
return `https://
|
|
32
|
+
return `https://api.chipipay.com/${version}`;
|
|
32
33
|
}
|
|
33
34
|
/**
|
|
34
35
|
* Add API version query parameters to track the API version being used
|
|
@@ -236,6 +237,7 @@ var ChipiWallets = class {
|
|
|
236
237
|
userId,
|
|
237
238
|
walletType = "CHIPI",
|
|
238
239
|
// Default to CHIPI wallet
|
|
240
|
+
classHash: customClassHash,
|
|
239
241
|
usePasskey,
|
|
240
242
|
chain
|
|
241
243
|
} = params;
|
|
@@ -251,8 +253,8 @@ var ChipiWallets = class {
|
|
|
251
253
|
const provider = new RpcProvider({ nodeUrl: rpcUrl });
|
|
252
254
|
const privateKeyAX = this.getPrivateKeyAX();
|
|
253
255
|
const starkKeyPubAX = ec.starkCurve.getStarkKey(privateKeyAX);
|
|
254
|
-
const accountClassHash = walletType === "READY" ? WALLET_CLASS_HASHES.READY : WALLET_CLASS_HASHES.CHIPI;
|
|
255
256
|
const constructorCallData = this.buildConstructorCallData(walletType, starkKeyPubAX);
|
|
257
|
+
const accountClassHash = customClassHash ?? (walletType === "READY" ? WALLET_CLASS_HASHES.READY : WALLET_CLASS_HASHES.CHIPI);
|
|
256
258
|
const publicKey = hash.calculateContractAddressFromHash(
|
|
257
259
|
starkKeyPubAX,
|
|
258
260
|
accountClassHash,
|
|
@@ -267,8 +269,8 @@ var ChipiWallets = class {
|
|
|
267
269
|
chain,
|
|
268
270
|
publicKey,
|
|
269
271
|
walletType,
|
|
270
|
-
starkKeyPubAX
|
|
271
|
-
|
|
272
|
+
starkKeyPubAX,
|
|
273
|
+
...customClassHash && { classHash: customClassHash }
|
|
272
274
|
}
|
|
273
275
|
});
|
|
274
276
|
const { typedData: typedData2, accountClassHash: accountClassHashResponse } = typedDataResponse;
|
|
@@ -392,7 +394,121 @@ var ChipiWallets = class {
|
|
|
392
394
|
});
|
|
393
395
|
return getBalanceResponse;
|
|
394
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* Prepare a wallet upgrade (e.g., READY → CHIPI or CHIPI v29 → v33).
|
|
399
|
+
* Returns typed data that must be signed by the wallet owner.
|
|
400
|
+
*/
|
|
401
|
+
async prepareWalletUpgrade(params, bearerToken) {
|
|
402
|
+
return this.client.post({
|
|
403
|
+
endpoint: `${API_ENDPOINTS.CHIPI_WALLETS}/prepare-upgrade`,
|
|
404
|
+
bearerToken,
|
|
405
|
+
body: params
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Execute a wallet upgrade after the owner has signed the typed data.
|
|
410
|
+
*/
|
|
411
|
+
async executeWalletUpgrade(params, bearerToken) {
|
|
412
|
+
return this.client.post({
|
|
413
|
+
endpoint: `${API_ENDPOINTS.CHIPI_WALLETS}/upgrade`,
|
|
414
|
+
bearerToken,
|
|
415
|
+
body: params
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
function unwrapPrepareResponse(response) {
|
|
420
|
+
if (!response || typeof response !== "object") {
|
|
421
|
+
throw new Error(
|
|
422
|
+
"Invalid response from /transactions/prepare-typed-data: response is not an object"
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
if ("typedData" in response) {
|
|
426
|
+
const resp = response;
|
|
427
|
+
if (!resp.typedData || typeof resp.typedData !== "object" || !("types" in resp.typedData) || !("message" in resp.typedData)) {
|
|
428
|
+
throw new Error(
|
|
429
|
+
"Invalid response from /transactions/prepare-typed-data: typedData structure is invalid"
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
return { typedData: resp.typedData, walletType: resp.walletType };
|
|
433
|
+
}
|
|
434
|
+
if ("types" in response && "message" in response) {
|
|
435
|
+
return { typedData: response };
|
|
436
|
+
}
|
|
437
|
+
const apiMessage = typeof response.message === "string" ? response.message : void 0;
|
|
438
|
+
throw new Error(
|
|
439
|
+
apiMessage ?? "Invalid response from /transactions/prepare-typed-data: typedData is missing"
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
function normalizeSignature(signature) {
|
|
443
|
+
if (Array.isArray(signature)) {
|
|
444
|
+
if (signature.length < 2) {
|
|
445
|
+
throw new Error(`Array signature must have at least 2 elements, got ${signature.length}`);
|
|
446
|
+
}
|
|
447
|
+
const r = signature[0];
|
|
448
|
+
const s = signature[1];
|
|
449
|
+
if (r == null || s == null) {
|
|
450
|
+
throw new Error(`Signature elements must not be null/undefined. Got r=${r}, s=${s}`);
|
|
451
|
+
}
|
|
452
|
+
return { r: String(r), s: String(s) };
|
|
453
|
+
}
|
|
454
|
+
if (signature && typeof signature === "object" && "r" in signature && "s" in signature) {
|
|
455
|
+
const sig = signature;
|
|
456
|
+
if (sig.r == null || sig.s == null) {
|
|
457
|
+
throw new Error(`Object signature has null/undefined fields. Got r=${sig.r}, s=${sig.s}`);
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
r: String(sig.r),
|
|
461
|
+
s: String(sig.s)
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
throw new Error("Unrecognized signature format from account.signMessage()");
|
|
465
|
+
}
|
|
466
|
+
var ChipiPaymasterAdapter = class {
|
|
467
|
+
constructor(client, bearerToken) {
|
|
468
|
+
this.client = client;
|
|
469
|
+
this.bearerToken = bearerToken;
|
|
470
|
+
}
|
|
471
|
+
async execute({ account, calls }) {
|
|
472
|
+
const classHash = await account.getClassHashAt(account.address);
|
|
473
|
+
const walletType = getWalletTypeFromClassHash(classHash);
|
|
474
|
+
const accountClassHash = walletType ? WALLET_CLASS_HASHES[walletType] : classHash;
|
|
475
|
+
const prepareResponse = await this.client.post({
|
|
476
|
+
endpoint: "/transactions/prepare-typed-data",
|
|
477
|
+
bearerToken: this.bearerToken,
|
|
478
|
+
body: {
|
|
479
|
+
publicKey: account.address,
|
|
480
|
+
calls,
|
|
481
|
+
accountClassHash
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
const { typedData: typedData2, walletType: resolvedWalletType } = unwrapPrepareResponse(prepareResponse);
|
|
485
|
+
const signature = await account.signMessage(typedData2);
|
|
486
|
+
const normalizedSig = normalizeSignature(signature);
|
|
487
|
+
const result = await this.client.post({
|
|
488
|
+
endpoint: "/transactions/execute-sponsored-transaction",
|
|
489
|
+
bearerToken: this.bearerToken,
|
|
490
|
+
body: {
|
|
491
|
+
publicKey: account.address,
|
|
492
|
+
typedData: typedData2,
|
|
493
|
+
userSignature: normalizedSig,
|
|
494
|
+
walletType: resolvedWalletType ?? walletType,
|
|
495
|
+
saveToDatabase: true
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
if (!result || typeof result !== "object") {
|
|
499
|
+
throw new Error(
|
|
500
|
+
"Invalid response from /transactions/execute-sponsored-transaction: response is not an object"
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
const apiMessage = typeof result.message === "string" ? result.message : void 0;
|
|
504
|
+
if (!result.transactionHash) {
|
|
505
|
+
throw new Error(apiMessage ?? "Response does not contain transaction hash");
|
|
506
|
+
}
|
|
507
|
+
return result.transactionHash;
|
|
508
|
+
}
|
|
395
509
|
};
|
|
510
|
+
|
|
511
|
+
// src/execute-paymaster-transaction.ts
|
|
396
512
|
var executePaymasterTransaction = async ({
|
|
397
513
|
params,
|
|
398
514
|
bearerToken,
|
|
@@ -405,8 +521,10 @@ var executePaymasterTransaction = async ({
|
|
|
405
521
|
usePasskey ? "encryptKey is required when using passkey. The passkey authentication should have provided the encryptKey." : "encryptKey is required for transaction execution"
|
|
406
522
|
);
|
|
407
523
|
}
|
|
408
|
-
const
|
|
409
|
-
const
|
|
524
|
+
const walletType = wallet.walletType ?? "READY";
|
|
525
|
+
const isKnownType = Object.prototype.hasOwnProperty.call(WALLET_RPC_ENDPOINTS, walletType) && Object.prototype.hasOwnProperty.call(WALLET_CLASS_HASHES, walletType);
|
|
526
|
+
const rpcUrl = isKnownType ? WALLET_RPC_ENDPOINTS[walletType] : WALLET_RPC_ENDPOINTS["CHIPI"];
|
|
527
|
+
const accountClassHash = isKnownType ? WALLET_CLASS_HASHES[walletType] : WALLET_CLASS_HASHES["CHIPI"];
|
|
410
528
|
let privateKeyDecrypted;
|
|
411
529
|
try {
|
|
412
530
|
privateKeyDecrypted = decryptPrivateKey(wallet.encryptedPrivateKey, encryptKey);
|
|
@@ -434,42 +552,47 @@ var executePaymasterTransaction = async ({
|
|
|
434
552
|
);
|
|
435
553
|
}
|
|
436
554
|
let typedData2;
|
|
437
|
-
let
|
|
555
|
+
let resolvedWalletType;
|
|
438
556
|
if ("typedData" in prepareResponse) {
|
|
439
557
|
const resp = prepareResponse;
|
|
440
558
|
typedData2 = resp.typedData;
|
|
441
|
-
walletType
|
|
559
|
+
if (resp.walletType && Object.prototype.hasOwnProperty.call(WALLET_CLASS_HASHES, resp.walletType)) {
|
|
560
|
+
resolvedWalletType = resp.walletType;
|
|
561
|
+
}
|
|
442
562
|
} else if (prepareResponse && "types" in prepareResponse && "message" in prepareResponse) {
|
|
443
563
|
typedData2 = prepareResponse;
|
|
444
564
|
} else {
|
|
445
|
-
const
|
|
565
|
+
const apiMessage2 = prepareResponse && typeof prepareResponse.message === "string" ? prepareResponse.message : void 0;
|
|
446
566
|
throw new Error(
|
|
447
|
-
|
|
567
|
+
apiMessage2 ?? "Invalid response from /transactions/prepare-typed-data: typedData is missing. This may occur for older STARKNET wallets that don't support paymaster transactions. Please ensure the wallet is a CHIPI wallet type."
|
|
448
568
|
);
|
|
449
569
|
}
|
|
450
|
-
if (!typedData2 || typeof typedData2 !== "object" || !("message" in typedData2)) {
|
|
570
|
+
if (!typedData2 || typeof typedData2 !== "object" || !("types" in typedData2) || !("message" in typedData2)) {
|
|
451
571
|
throw new Error(
|
|
452
|
-
"Invalid typedData received from backend: missing 'message' field. This may occur for older STARKNET wallets that don't support paymaster transactions."
|
|
572
|
+
"Invalid typedData received from backend: missing 'types' or 'message' field. This may occur for older STARKNET wallets that don't support paymaster transactions."
|
|
453
573
|
);
|
|
454
574
|
}
|
|
455
575
|
const userSignature = await account.signMessage(typedData2);
|
|
576
|
+
const normalizedSig = normalizeSignature(userSignature);
|
|
456
577
|
const result = await client.post({
|
|
457
578
|
endpoint: "/transactions/execute-sponsored-transaction",
|
|
458
579
|
bearerToken,
|
|
459
580
|
body: {
|
|
460
581
|
publicKey: wallet.publicKey,
|
|
461
582
|
typedData: typedData2,
|
|
462
|
-
userSignature:
|
|
463
|
-
r: userSignature.r.toString(),
|
|
464
|
-
s: userSignature.s.toString(),
|
|
465
|
-
recovery: userSignature.recovery
|
|
466
|
-
},
|
|
583
|
+
userSignature: normalizedSig,
|
|
467
584
|
saveToDatabase,
|
|
468
|
-
walletType
|
|
585
|
+
walletType: resolvedWalletType ?? walletType
|
|
469
586
|
}
|
|
470
587
|
});
|
|
471
|
-
if (!result
|
|
472
|
-
throw new Error(
|
|
588
|
+
if (!result || typeof result !== "object") {
|
|
589
|
+
throw new Error(
|
|
590
|
+
"Invalid response from /transactions/execute-sponsored-transaction: response is not an object"
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
const apiMessage = typeof result.message === "string" ? result.message : void 0;
|
|
594
|
+
if (typeof result.transactionHash !== "string" || result.transactionHash.trim().length === 0) {
|
|
595
|
+
throw new Error(apiMessage ?? "Response does not contain transaction hash");
|
|
473
596
|
}
|
|
474
597
|
return result.transactionHash;
|
|
475
598
|
} catch (error) {
|
|
@@ -535,7 +658,7 @@ var executePaymasterTransactionWithSession = async ({
|
|
|
535
658
|
);
|
|
536
659
|
}
|
|
537
660
|
const accountClassHash = WALLET_CLASS_HASHES.CHIPI;
|
|
538
|
-
const
|
|
661
|
+
const prepareResponse = await client.post({
|
|
539
662
|
endpoint: "/transactions/prepare-typed-data",
|
|
540
663
|
bearerToken,
|
|
541
664
|
body: {
|
|
@@ -545,7 +668,31 @@ var executePaymasterTransactionWithSession = async ({
|
|
|
545
668
|
walletType: "CHIPI"
|
|
546
669
|
}
|
|
547
670
|
});
|
|
548
|
-
|
|
671
|
+
if (!prepareResponse || typeof prepareResponse !== "object") {
|
|
672
|
+
throw new ChipiSessionError(
|
|
673
|
+
"Invalid response from /transactions/prepare-typed-data: response is not an object",
|
|
674
|
+
SESSION_ERRORS.SESSION_INVALID_RESPONSE
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
let sessionTypedData;
|
|
678
|
+
if ("typedData" in prepareResponse && prepareResponse.typedData) {
|
|
679
|
+
const candidate = prepareResponse.typedData;
|
|
680
|
+
if (!candidate || typeof candidate !== "object" || !("types" in candidate) || !("message" in candidate)) {
|
|
681
|
+
throw new ChipiSessionError(
|
|
682
|
+
"Invalid response from /transactions/prepare-typed-data: typedData structure is invalid",
|
|
683
|
+
SESSION_ERRORS.SESSION_INVALID_RESPONSE
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
sessionTypedData = candidate;
|
|
687
|
+
} else if ("types" in prepareResponse && "message" in prepareResponse) {
|
|
688
|
+
sessionTypedData = prepareResponse;
|
|
689
|
+
} else {
|
|
690
|
+
throw new ChipiSessionError(
|
|
691
|
+
"Invalid response from /transactions/prepare-typed-data: typedData is missing",
|
|
692
|
+
SESSION_ERRORS.SESSION_INVALID_RESPONSE
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
const msgHash = typedData.getMessageHash(sessionTypedData, wallet.publicKey);
|
|
549
696
|
const { r, s } = ec.starkCurve.sign(msgHash, sessionPrivateKey);
|
|
550
697
|
const sessionSignature = [
|
|
551
698
|
session.publicKey,
|
|
@@ -558,7 +705,7 @@ var executePaymasterTransactionWithSession = async ({
|
|
|
558
705
|
bearerToken,
|
|
559
706
|
body: {
|
|
560
707
|
publicKey: wallet.publicKey,
|
|
561
|
-
|
|
708
|
+
typedData: sessionTypedData,
|
|
562
709
|
// Session signature format - array of 4 hex strings
|
|
563
710
|
userSignature: sessionSignature,
|
|
564
711
|
saveToDatabase,
|
|
@@ -567,11 +714,21 @@ var executePaymasterTransactionWithSession = async ({
|
|
|
567
714
|
// Flag to indicate session signature format
|
|
568
715
|
}
|
|
569
716
|
});
|
|
570
|
-
if (!result
|
|
717
|
+
if (!result || typeof result !== "object") {
|
|
718
|
+
throw new ChipiSessionError(
|
|
719
|
+
"Invalid response from /transactions/execute-sponsored-transaction: response is not an object",
|
|
720
|
+
SESSION_ERRORS.SESSION_INVALID_RESPONSE
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
if (typeof result.transactionHash !== "string" || result.transactionHash.trim().length === 0) {
|
|
724
|
+
const apiMessage = typeof result.message === "string" ? result.message : void 0;
|
|
571
725
|
console.error("[ChipiSDK:Session:Execute] No transaction hash in response", {
|
|
572
726
|
result
|
|
573
727
|
});
|
|
574
|
-
throw new
|
|
728
|
+
throw new ChipiSessionError(
|
|
729
|
+
apiMessage ?? "Response does not contain transaction hash",
|
|
730
|
+
SESSION_ERRORS.SESSION_INVALID_RESPONSE
|
|
731
|
+
);
|
|
575
732
|
}
|
|
576
733
|
return result.transactionHash;
|
|
577
734
|
} catch (error) {
|
|
@@ -709,6 +866,24 @@ var ChipiTransactions = class {
|
|
|
709
866
|
});
|
|
710
867
|
return response;
|
|
711
868
|
}
|
|
869
|
+
/**
|
|
870
|
+
* Get a single transaction by hash or internal ID
|
|
871
|
+
*/
|
|
872
|
+
async getTransaction(hashOrId, bearerToken) {
|
|
873
|
+
return this.client.get({
|
|
874
|
+
endpoint: `${API_ENDPOINTS.TRANSACTIONS}/${hashOrId}`,
|
|
875
|
+
bearerToken
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Get on-chain transaction status
|
|
880
|
+
*/
|
|
881
|
+
async getTransactionStatus(hash3, bearerToken) {
|
|
882
|
+
return this.client.get({
|
|
883
|
+
endpoint: `${API_ENDPOINTS.TRANSACTIONS}/${hash3}/status`,
|
|
884
|
+
bearerToken
|
|
885
|
+
});
|
|
886
|
+
}
|
|
712
887
|
};
|
|
713
888
|
var ChipiExchanges = class {
|
|
714
889
|
constructor(client) {
|
|
@@ -1245,6 +1420,240 @@ var ChipiSessions = class {
|
|
|
1245
1420
|
});
|
|
1246
1421
|
}
|
|
1247
1422
|
};
|
|
1423
|
+
var InMemoryNonceStore = class {
|
|
1424
|
+
constructor() {
|
|
1425
|
+
this.used = /* @__PURE__ */ new Set();
|
|
1426
|
+
}
|
|
1427
|
+
has(nonce) {
|
|
1428
|
+
return this.used.has(nonce);
|
|
1429
|
+
}
|
|
1430
|
+
consume(nonce) {
|
|
1431
|
+
if (this.used.has(nonce)) return false;
|
|
1432
|
+
this.used.add(nonce);
|
|
1433
|
+
return true;
|
|
1434
|
+
}
|
|
1435
|
+
};
|
|
1436
|
+
var X402Facilitator = class _X402Facilitator {
|
|
1437
|
+
constructor(client, bearerToken, config) {
|
|
1438
|
+
this.client = client;
|
|
1439
|
+
this.bearerToken = bearerToken;
|
|
1440
|
+
this.nonceStore = config?.nonceStore ?? new InMemoryNonceStore();
|
|
1441
|
+
this.provider = config?.provider;
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Factory method for simpler instantiation.
|
|
1445
|
+
*
|
|
1446
|
+
* @example
|
|
1447
|
+
* ```typescript
|
|
1448
|
+
* const facilitator = X402Facilitator.create({
|
|
1449
|
+
* apiKey: process.env.CHIPI_API_KEY!,
|
|
1450
|
+
* bearerToken: process.env.CHIPI_BEARER_TOKEN!,
|
|
1451
|
+
* });
|
|
1452
|
+
* ```
|
|
1453
|
+
*/
|
|
1454
|
+
static create(options) {
|
|
1455
|
+
const client = new ChipiClient({ apiPublicKey: options.apiKey });
|
|
1456
|
+
return new _X402Facilitator(client, options.bearerToken, {
|
|
1457
|
+
nonceStore: options.nonceStore,
|
|
1458
|
+
provider: options.provider
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* Verify a payment signature is valid (doesn't execute the transfer).
|
|
1463
|
+
*
|
|
1464
|
+
* Checks:
|
|
1465
|
+
* 1. Nonce has not been used (replay protection)
|
|
1466
|
+
* 2. Payment has not expired (validUntil > now)
|
|
1467
|
+
* 3. Asset is USDC mainnet
|
|
1468
|
+
* 4. Amount is positive
|
|
1469
|
+
* 5. Addresses are non-empty
|
|
1470
|
+
*/
|
|
1471
|
+
async verify(payment) {
|
|
1472
|
+
const { payload } = payment;
|
|
1473
|
+
if (payment.x402Version !== 1) {
|
|
1474
|
+
return { isValid: false, invalidReason: `Unsupported x402 version: ${payment.x402Version}` };
|
|
1475
|
+
}
|
|
1476
|
+
if (payment.network !== "starknet-mainnet") {
|
|
1477
|
+
return { isValid: false, invalidReason: `Unsupported network: ${payment.network}` };
|
|
1478
|
+
}
|
|
1479
|
+
if (await this.nonceStore.has(payload.nonce)) {
|
|
1480
|
+
return { isValid: false, invalidReason: "Nonce already used (replay)" };
|
|
1481
|
+
}
|
|
1482
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1483
|
+
if (payload.validUntil <= now) {
|
|
1484
|
+
return { isValid: false, invalidReason: "Payment has expired (validUntil in the past)" };
|
|
1485
|
+
}
|
|
1486
|
+
const normalizedAsset = payload.asset.toLowerCase().replace(/^0x0+/, "0x");
|
|
1487
|
+
const normalizedUsdc = CONTRACT_ADDRESSES.USDC_MAINNET.toLowerCase().replace(/^0x0+/, "0x");
|
|
1488
|
+
if (normalizedAsset !== normalizedUsdc) {
|
|
1489
|
+
return {
|
|
1490
|
+
isValid: false,
|
|
1491
|
+
invalidReason: `Unsupported asset: ${payload.asset}. Only USDC is accepted.`
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
let amount;
|
|
1495
|
+
try {
|
|
1496
|
+
amount = BigInt(payload.amount);
|
|
1497
|
+
} catch {
|
|
1498
|
+
return { isValid: false, invalidReason: "Invalid payment amount format" };
|
|
1499
|
+
}
|
|
1500
|
+
if (amount <= 0n) {
|
|
1501
|
+
return { isValid: false, invalidReason: "Payment amount must be positive" };
|
|
1502
|
+
}
|
|
1503
|
+
if (!payload.fromAddress || !payload.toAddress) {
|
|
1504
|
+
return { isValid: false, invalidReason: "Missing fromAddress or toAddress" };
|
|
1505
|
+
}
|
|
1506
|
+
const sigResult = await this.verifySignature(payment);
|
|
1507
|
+
if (!sigResult.valid) {
|
|
1508
|
+
return { isValid: false, invalidReason: sigResult.reason };
|
|
1509
|
+
}
|
|
1510
|
+
return { isValid: true };
|
|
1511
|
+
}
|
|
1512
|
+
/**
|
|
1513
|
+
* Verify the SNIP-12 signature on a payment payload.
|
|
1514
|
+
*
|
|
1515
|
+
* If an RPC provider is configured, reconstructs the typed data, computes the
|
|
1516
|
+
* message hash, and calls `is_valid_signature()` on the sender's account contract.
|
|
1517
|
+
* Otherwise falls back to a presence-only check (backwards compatible).
|
|
1518
|
+
*/
|
|
1519
|
+
async verifySignature(payment) {
|
|
1520
|
+
if (!this.provider) {
|
|
1521
|
+
if (!payment.payload?.signature?.r || !payment.payload?.signature?.s) {
|
|
1522
|
+
return { valid: false, reason: "Missing signature" };
|
|
1523
|
+
}
|
|
1524
|
+
return { valid: true };
|
|
1525
|
+
}
|
|
1526
|
+
const payload = payment.payload;
|
|
1527
|
+
if (!payload?.signature?.r || !payload?.signature?.s) {
|
|
1528
|
+
return { valid: false, reason: "Missing signature" };
|
|
1529
|
+
}
|
|
1530
|
+
const paymentTypedData = buildX402TypedData({
|
|
1531
|
+
from_address: payload.fromAddress,
|
|
1532
|
+
to_address: payload.toAddress,
|
|
1533
|
+
amount: payload.amount,
|
|
1534
|
+
asset: payload.asset,
|
|
1535
|
+
valid_until: String(payload.validUntil),
|
|
1536
|
+
nonce: payload.nonce
|
|
1537
|
+
});
|
|
1538
|
+
const msgHash = typedData.getMessageHash(paymentTypedData, payload.fromAddress);
|
|
1539
|
+
try {
|
|
1540
|
+
const result = await this.provider.callContract({
|
|
1541
|
+
contractAddress: payload.fromAddress,
|
|
1542
|
+
entrypoint: "is_valid_signature",
|
|
1543
|
+
calldata: [msgHash, "2", payload.signature.r, payload.signature.s]
|
|
1544
|
+
});
|
|
1545
|
+
const VALID = "0x56414c4944";
|
|
1546
|
+
if (result[0] === VALID || result[0] === BigInt(VALID).toString()) {
|
|
1547
|
+
return { valid: true };
|
|
1548
|
+
}
|
|
1549
|
+
return { valid: false, reason: "Invalid SNIP-12 signature" };
|
|
1550
|
+
} catch (err) {
|
|
1551
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1552
|
+
return { valid: false, reason: `Signature verification failed: ${message}` };
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Settle a verified payment by executing the USDC transfer via paymaster.
|
|
1557
|
+
*
|
|
1558
|
+
* IMPORTANT: Mark nonce as consumed BEFORE executing to prevent concurrent replays.
|
|
1559
|
+
* If the transfer fails after nonce consumption, the nonce is still burned
|
|
1560
|
+
* (caller should implement idempotency at a higher level if needed).
|
|
1561
|
+
*/
|
|
1562
|
+
async settle(payment) {
|
|
1563
|
+
const { payload } = payment;
|
|
1564
|
+
const verification = await this.verify(payment);
|
|
1565
|
+
if (!verification.isValid) {
|
|
1566
|
+
return { success: false, errorReason: verification.invalidReason };
|
|
1567
|
+
}
|
|
1568
|
+
const consumed = await this.nonceStore.consume(payload.nonce);
|
|
1569
|
+
if (!consumed) {
|
|
1570
|
+
return { success: false, errorReason: "Nonce already consumed (concurrent replay)" };
|
|
1571
|
+
}
|
|
1572
|
+
try {
|
|
1573
|
+
const amountU256 = uint256.bnToUint256(BigInt(payload.amount));
|
|
1574
|
+
const transferCall = {
|
|
1575
|
+
contractAddress: payload.asset,
|
|
1576
|
+
entrypoint: "transfer",
|
|
1577
|
+
calldata: [payload.toAddress, amountU256.low.toString(), amountU256.high.toString()]
|
|
1578
|
+
};
|
|
1579
|
+
const prepareResponse = await this.client.post({
|
|
1580
|
+
endpoint: "/transactions/prepare-typed-data",
|
|
1581
|
+
bearerToken: this.bearerToken,
|
|
1582
|
+
body: {
|
|
1583
|
+
publicKey: payload.fromAddress,
|
|
1584
|
+
calls: [transferCall]
|
|
1585
|
+
}
|
|
1586
|
+
});
|
|
1587
|
+
const result = await this.client.post({
|
|
1588
|
+
endpoint: "/transactions/execute-sponsored-transaction",
|
|
1589
|
+
bearerToken: this.bearerToken,
|
|
1590
|
+
body: {
|
|
1591
|
+
publicKey: payload.fromAddress,
|
|
1592
|
+
typedData: prepareResponse.typedData,
|
|
1593
|
+
userSignature: payload.signature,
|
|
1594
|
+
walletType: prepareResponse.walletType,
|
|
1595
|
+
saveToDatabase: true
|
|
1596
|
+
}
|
|
1597
|
+
});
|
|
1598
|
+
if (typeof result?.transactionHash !== "string" || !result.transactionHash.trim()) {
|
|
1599
|
+
return {
|
|
1600
|
+
success: false,
|
|
1601
|
+
errorReason: result?.message ?? "No transaction hash returned from paymaster"
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
return {
|
|
1605
|
+
success: true,
|
|
1606
|
+
transactionHash: result.transactionHash,
|
|
1607
|
+
networkId: "starknet-mainnet"
|
|
1608
|
+
};
|
|
1609
|
+
} catch (error) {
|
|
1610
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1611
|
+
return {
|
|
1612
|
+
success: false,
|
|
1613
|
+
errorReason: `Settlement failed: ${message}`
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
};
|
|
1618
|
+
var signTypedData = async ({
|
|
1619
|
+
params
|
|
1620
|
+
}) => {
|
|
1621
|
+
try {
|
|
1622
|
+
const { encryptKey, wallet, typedData: typedData2 } = params;
|
|
1623
|
+
if (!encryptKey) {
|
|
1624
|
+
throw new Error("encryptKey is required for signing typed data");
|
|
1625
|
+
}
|
|
1626
|
+
if (!wallet || !wallet.encryptedPrivateKey || !wallet.publicKey) {
|
|
1627
|
+
throw new Error("wallet with publicKey and encryptedPrivateKey is required");
|
|
1628
|
+
}
|
|
1629
|
+
if (!typedData2) {
|
|
1630
|
+
throw new Error("typedData is required for signing");
|
|
1631
|
+
}
|
|
1632
|
+
const walletType = wallet.walletType ?? "READY";
|
|
1633
|
+
if (!Object.prototype.hasOwnProperty.call(WALLET_RPC_ENDPOINTS, walletType) || !Object.prototype.hasOwnProperty.call(WALLET_CLASS_HASHES, walletType)) {
|
|
1634
|
+
throw new Error(`Unsupported wallet type: "${String(walletType)}"`);
|
|
1635
|
+
}
|
|
1636
|
+
const rpcUrl = WALLET_RPC_ENDPOINTS[walletType];
|
|
1637
|
+
let privateKeyDecrypted;
|
|
1638
|
+
try {
|
|
1639
|
+
privateKeyDecrypted = decryptPrivateKey(wallet.encryptedPrivateKey, encryptKey);
|
|
1640
|
+
} catch {
|
|
1641
|
+
throw new Error("Failed to decrypt private key. Verify the encryptKey is correct.");
|
|
1642
|
+
}
|
|
1643
|
+
const provider = new RpcProvider({ nodeUrl: rpcUrl });
|
|
1644
|
+
const account = new Account({
|
|
1645
|
+
provider,
|
|
1646
|
+
address: wallet.publicKey,
|
|
1647
|
+
signer: privateKeyDecrypted
|
|
1648
|
+
});
|
|
1649
|
+
const signature = await account.signMessage(typedData2);
|
|
1650
|
+
const normalizedSig = normalizeSignature(signature);
|
|
1651
|
+
return normalizedSig;
|
|
1652
|
+
} catch (error) {
|
|
1653
|
+
console.error("Error signing typed data", error);
|
|
1654
|
+
throw error;
|
|
1655
|
+
}
|
|
1656
|
+
};
|
|
1248
1657
|
|
|
1249
1658
|
// src/chipi-sdk.ts
|
|
1250
1659
|
var ChipiSDK = class {
|
|
@@ -1267,6 +1676,9 @@ var ChipiSDK = class {
|
|
|
1267
1676
|
this.recordSendTransaction = this.recordSendTransaction.bind(this);
|
|
1268
1677
|
this.purchaseSku = this.purchaseSku.bind(this);
|
|
1269
1678
|
this.getUsdAmount = this.getUsdAmount.bind(this);
|
|
1679
|
+
this.signTypedData = this.signTypedData.bind(this);
|
|
1680
|
+
this.createPaymasterAdapter = this.createPaymasterAdapter.bind(this);
|
|
1681
|
+
this.createX402Facilitator = this.createX402Facilitator.bind(this);
|
|
1270
1682
|
}
|
|
1271
1683
|
/**
|
|
1272
1684
|
* Resolve bearer token - uses provided token or falls back to apiSecretKey
|
|
@@ -1280,6 +1692,43 @@ var ChipiSDK = class {
|
|
|
1280
1692
|
}
|
|
1281
1693
|
return token;
|
|
1282
1694
|
}
|
|
1695
|
+
/**
|
|
1696
|
+
* Create a PaymasterAdapter for use with TxBuilder.sendSponsored().
|
|
1697
|
+
*
|
|
1698
|
+
* @example
|
|
1699
|
+
* ```typescript
|
|
1700
|
+
* const sdk = new ChipiServerSDK({ apiPublicKey: "pk_...", apiSecretKey: "sk_..." });
|
|
1701
|
+
* const paymaster = sdk.createPaymasterAdapter();
|
|
1702
|
+
*
|
|
1703
|
+
* const txHash = await new TxBuilder(account, { paymaster })
|
|
1704
|
+
* .transfer(USDC, [{ to, amount }])
|
|
1705
|
+
* .sendSponsored(); // gasless!
|
|
1706
|
+
* ```
|
|
1707
|
+
*/
|
|
1708
|
+
createPaymasterAdapter(bearerToken) {
|
|
1709
|
+
const token = this.resolveBearerToken(bearerToken);
|
|
1710
|
+
return new ChipiPaymasterAdapter(this.client, token);
|
|
1711
|
+
}
|
|
1712
|
+
/**
|
|
1713
|
+
* Create an X402Facilitator for verifying and settling x402 payments.
|
|
1714
|
+
*
|
|
1715
|
+
* @example
|
|
1716
|
+
* ```typescript
|
|
1717
|
+
* const facilitator = sdk.createX402Facilitator();
|
|
1718
|
+
*
|
|
1719
|
+
* // With custom nonce store (Redis)
|
|
1720
|
+
* const facilitator = sdk.createX402Facilitator(bearerToken, {
|
|
1721
|
+
* nonceStore: {
|
|
1722
|
+
* has: async (nonce) => (await redis.sismember("x402:nonces", nonce)) === 1,
|
|
1723
|
+
* consume: async (nonce) => (await redis.sadd("x402:nonces", nonce)) === 1,
|
|
1724
|
+
* },
|
|
1725
|
+
* });
|
|
1726
|
+
* ```
|
|
1727
|
+
*/
|
|
1728
|
+
createX402Facilitator(bearerToken, config) {
|
|
1729
|
+
const token = this.resolveBearerToken(bearerToken);
|
|
1730
|
+
return new X402Facilitator(this.client, token, config);
|
|
1731
|
+
}
|
|
1283
1732
|
/**
|
|
1284
1733
|
* Execute a gasless transaction
|
|
1285
1734
|
*/
|
|
@@ -1335,6 +1784,29 @@ var ChipiSDK = class {
|
|
|
1335
1784
|
this.resolveBearerToken(bearerToken)
|
|
1336
1785
|
);
|
|
1337
1786
|
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Sign SNIP-12 typed data without executing a transaction.
|
|
1789
|
+
*
|
|
1790
|
+
* Purely local operation: decrypt private key → sign → return { r, s }.
|
|
1791
|
+
* No API calls are made. Useful for x402 payment signatures and other
|
|
1792
|
+
* off-chain signing flows.
|
|
1793
|
+
*
|
|
1794
|
+
* @example
|
|
1795
|
+
* ```typescript
|
|
1796
|
+
* const { r, s } = await sdk.signTypedData({
|
|
1797
|
+
* params: {
|
|
1798
|
+
* encryptKey: pin,
|
|
1799
|
+
* wallet,
|
|
1800
|
+
* typedData: snip12TypedData,
|
|
1801
|
+
* },
|
|
1802
|
+
* });
|
|
1803
|
+
* ```
|
|
1804
|
+
*/
|
|
1805
|
+
async signTypedData({
|
|
1806
|
+
params
|
|
1807
|
+
}) {
|
|
1808
|
+
return signTypedData({ params });
|
|
1809
|
+
}
|
|
1338
1810
|
/**
|
|
1339
1811
|
* Transfer tokens
|
|
1340
1812
|
*/
|
|
@@ -1442,6 +1914,33 @@ var ChipiSDK = class {
|
|
|
1442
1914
|
async getSku(id, bearerToken) {
|
|
1443
1915
|
return this.skus.getSku(id, this.resolveBearerToken(bearerToken));
|
|
1444
1916
|
}
|
|
1917
|
+
/**
|
|
1918
|
+
* Get a single transaction by hash or internal ID
|
|
1919
|
+
*/
|
|
1920
|
+
async getTransaction(hashOrId, bearerToken) {
|
|
1921
|
+
return this.transactions.getTransaction(hashOrId, this.resolveBearerToken(bearerToken));
|
|
1922
|
+
}
|
|
1923
|
+
/**
|
|
1924
|
+
* Get on-chain transaction status (supports polling for confirmation)
|
|
1925
|
+
*/
|
|
1926
|
+
async getTransactionStatus(hash3, bearerToken) {
|
|
1927
|
+
return this.transactions.getTransactionStatus(hash3, this.resolveBearerToken(bearerToken));
|
|
1928
|
+
}
|
|
1929
|
+
/**
|
|
1930
|
+
* Prepare a wallet upgrade (e.g., READY → CHIPI or CHIPI v29 → v33).
|
|
1931
|
+
* Returns typed data that must be signed by the wallet owner.
|
|
1932
|
+
* Server-only: requires API secret key.
|
|
1933
|
+
*/
|
|
1934
|
+
async prepareWalletUpgrade(params, bearerToken) {
|
|
1935
|
+
return this.wallets.prepareWalletUpgrade(params, this.resolveBearerToken(bearerToken));
|
|
1936
|
+
}
|
|
1937
|
+
/**
|
|
1938
|
+
* Execute a wallet upgrade after signing.
|
|
1939
|
+
* Server-only: requires API secret key.
|
|
1940
|
+
*/
|
|
1941
|
+
async executeWalletUpgrade(params, bearerToken) {
|
|
1942
|
+
return this.wallets.executeWalletUpgrade(params, this.resolveBearerToken(bearerToken));
|
|
1943
|
+
}
|
|
1445
1944
|
async getUsdAmount(currencyAmount, currency, bearerToken) {
|
|
1446
1945
|
return this.exchanges.getUsdAmount(
|
|
1447
1946
|
currencyAmount,
|
|
@@ -1473,7 +1972,130 @@ var ChipiBrowserSDK = class extends ChipiSDK {
|
|
|
1473
1972
|
// All methods are inherited from ChipiSDK
|
|
1474
1973
|
// bearerToken parameter is required for each authenticated request
|
|
1475
1974
|
};
|
|
1975
|
+
function amountToBaseUnits(amount, decimals) {
|
|
1976
|
+
const [whole, frac = ""] = amount.split(".");
|
|
1977
|
+
const fracPadded = (frac + "0".repeat(decimals)).slice(0, decimals);
|
|
1978
|
+
return String(BigInt(whole + fracPadded));
|
|
1979
|
+
}
|
|
1980
|
+
function buildPaymentRequirement(config, resource) {
|
|
1981
|
+
const amountBaseUnits = amountToBaseUnits(config.amount, TOKEN_DECIMALS.USDC);
|
|
1982
|
+
return {
|
|
1983
|
+
scheme: "exact",
|
|
1984
|
+
network: "starknet-mainnet",
|
|
1985
|
+
maxAmountRequired: amountBaseUnits,
|
|
1986
|
+
resource,
|
|
1987
|
+
description: config.description,
|
|
1988
|
+
payTo: config.payTo,
|
|
1989
|
+
maxTimeoutSeconds: 300,
|
|
1990
|
+
asset: CONTRACT_ADDRESSES.USDC_MAINNET
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
function isFlatConfig(config) {
|
|
1994
|
+
return "amount" in config && "recipient" in config;
|
|
1995
|
+
}
|
|
1996
|
+
function resolveConfig(config) {
|
|
1997
|
+
if (isFlatConfig(config)) {
|
|
1998
|
+
return {
|
|
1999
|
+
facilitator: config.facilitator,
|
|
2000
|
+
paymentConfig: {
|
|
2001
|
+
amount: config.amount,
|
|
2002
|
+
payTo: config.recipient,
|
|
2003
|
+
description: config.description
|
|
2004
|
+
},
|
|
2005
|
+
verifyOnly: config.verifyOnly
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
return config;
|
|
2009
|
+
}
|
|
2010
|
+
function x402Middleware(config) {
|
|
2011
|
+
const resolved = resolveConfig(config);
|
|
2012
|
+
return async (req, res, next) => {
|
|
2013
|
+
if (req.method === "OPTIONS") {
|
|
2014
|
+
return next();
|
|
2015
|
+
}
|
|
2016
|
+
const paymentHeader = req.headers?.["x-payment"] ?? req.headers?.["X-PAYMENT"];
|
|
2017
|
+
if (!paymentHeader) {
|
|
2018
|
+
const resource = req.protocol && req.get ? `${req.protocol}://${req.get("host")}${req.originalUrl}` : req.url ?? "";
|
|
2019
|
+
const requirement2 = buildPaymentRequirement(resolved.paymentConfig, resource);
|
|
2020
|
+
return res.status(402).set("Payment-Required", JSON.stringify(requirement2)).json({
|
|
2021
|
+
error: "Payment Required",
|
|
2022
|
+
paymentRequirement: requirement2
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
2025
|
+
let payment;
|
|
2026
|
+
try {
|
|
2027
|
+
payment = typeof paymentHeader === "string" ? JSON.parse(paymentHeader) : paymentHeader;
|
|
2028
|
+
} catch {
|
|
2029
|
+
return res.status(400).json({ error: "Malformed X-PAYMENT header: invalid JSON" });
|
|
2030
|
+
}
|
|
2031
|
+
const payload = payment?.payload;
|
|
2032
|
+
const toAddress = typeof payload?.toAddress === "string" ? payload.toAddress : null;
|
|
2033
|
+
const amount = typeof payload?.amount === "string" ? payload.amount : null;
|
|
2034
|
+
const asset = typeof payload?.asset === "string" ? payload.asset : null;
|
|
2035
|
+
if (!toAddress || !amount || !asset) {
|
|
2036
|
+
return res.status(400).json({ error: "Malformed X-PAYMENT payload: missing required fields" });
|
|
2037
|
+
}
|
|
2038
|
+
const requirement = buildPaymentRequirement(resolved.paymentConfig, "");
|
|
2039
|
+
const normalizeAddr = (a) => a.toLowerCase().replace(/^0x0+/, "0x");
|
|
2040
|
+
if (normalizeAddr(toAddress) !== normalizeAddr(resolved.paymentConfig.payTo)) {
|
|
2041
|
+
return res.status(402).json({
|
|
2042
|
+
error: "Payment recipient mismatch",
|
|
2043
|
+
reason: `Expected ${resolved.paymentConfig.payTo}, got ${toAddress}`
|
|
2044
|
+
});
|
|
2045
|
+
}
|
|
2046
|
+
let paidAmount;
|
|
2047
|
+
try {
|
|
2048
|
+
paidAmount = BigInt(amount);
|
|
2049
|
+
} catch {
|
|
2050
|
+
return res.status(400).json({ error: "Malformed X-PAYMENT payload", reason: "amount must be an integer string" });
|
|
2051
|
+
}
|
|
2052
|
+
if (paidAmount < BigInt(requirement.maxAmountRequired)) {
|
|
2053
|
+
return res.status(402).json({
|
|
2054
|
+
error: "Payment amount insufficient",
|
|
2055
|
+
reason: `Required ${requirement.maxAmountRequired} base units, got ${amount}`
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
if (normalizeAddr(asset) !== normalizeAddr(requirement.asset)) {
|
|
2059
|
+
return res.status(402).json({
|
|
2060
|
+
error: "Payment asset mismatch",
|
|
2061
|
+
reason: `Expected USDC, got ${asset}`
|
|
2062
|
+
});
|
|
2063
|
+
}
|
|
2064
|
+
try {
|
|
2065
|
+
const verified = await resolved.facilitator.verify(payment);
|
|
2066
|
+
if (!verified.isValid) {
|
|
2067
|
+
return res.status(402).json({
|
|
2068
|
+
error: "Payment verification failed",
|
|
2069
|
+
reason: verified.invalidReason
|
|
2070
|
+
});
|
|
2071
|
+
}
|
|
2072
|
+
if (!resolved.verifyOnly) {
|
|
2073
|
+
const settled = await resolved.facilitator.settle(payment);
|
|
2074
|
+
if (!settled.success) {
|
|
2075
|
+
return res.status(402).json({
|
|
2076
|
+
error: "Payment settlement failed",
|
|
2077
|
+
reason: settled.errorReason
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
req.x402 = {
|
|
2081
|
+
txHash: settled.transactionHash,
|
|
2082
|
+
payment,
|
|
2083
|
+
networkId: settled.networkId
|
|
2084
|
+
};
|
|
2085
|
+
} else {
|
|
2086
|
+
req.x402 = { payment };
|
|
2087
|
+
}
|
|
2088
|
+
} catch (err) {
|
|
2089
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2090
|
+
return res.status(500).json({
|
|
2091
|
+
error: "Payment processing error",
|
|
2092
|
+
reason: message
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
next();
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
1476
2098
|
|
|
1477
|
-
export { ChipiBrowserSDK, ChipiClient, ChipiSDK, ChipiServerSDK, ChipiSessions, ChipiSkuPurchases, ChipiSkus, ChipiTransactions, ChipiWallets, decryptPrivateKey, encryptPrivateKey };
|
|
2099
|
+
export { ChipiBrowserSDK, ChipiClient, ChipiPaymasterAdapter, ChipiSDK, ChipiServerSDK, ChipiSessions, ChipiSkuPurchases, ChipiSkus, ChipiTransactions, ChipiWallets, X402Facilitator, decryptPrivateKey, encryptPrivateKey, signTypedData, x402Middleware };
|
|
1478
2100
|
//# sourceMappingURL=index.mjs.map
|
|
1479
2101
|
//# sourceMappingURL=index.mjs.map
|