@alleyboss/micropay-solana-x402-paywall 3.3.7 → 3.3.8
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.cjs +211 -0
- package/dist/index.d.cts +93 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +213 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -528,6 +528,216 @@ function useFormatPrice(lamports) {
|
|
|
528
528
|
isLoading: false
|
|
529
529
|
};
|
|
530
530
|
}
|
|
531
|
+
var LocalSvmFacilitator = class {
|
|
532
|
+
scheme = "exact";
|
|
533
|
+
caipFamily = "solana:*";
|
|
534
|
+
connection;
|
|
535
|
+
constructor(rpcUrl) {
|
|
536
|
+
console.log("[LocalSvmFacilitator] Initialized with RPC:", rpcUrl);
|
|
537
|
+
this.connection = new web3_js.Connection(rpcUrl, "confirmed");
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Get supported payment kinds
|
|
541
|
+
* Mocking the response of the /supported endpoint
|
|
542
|
+
*/
|
|
543
|
+
/**
|
|
544
|
+
* Network Constants - CAIP-2 format for x402 v2
|
|
545
|
+
* These match the official Solana chain IDs used by x402 protocol
|
|
546
|
+
*/
|
|
547
|
+
NETWORKS = {
|
|
548
|
+
DEVNET: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
|
|
549
|
+
MAINNET: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
|
|
550
|
+
};
|
|
551
|
+
/**
|
|
552
|
+
* DUMMY_FEE_PAYER Explanation (for auditors):
|
|
553
|
+
*
|
|
554
|
+
* In a HOSTED facilitator (like x402.org), the `feePayer` field in the
|
|
555
|
+
* SupportedResponse.extra specifies which address will pay transaction fees
|
|
556
|
+
* when the facilitator submits transactions on behalf of users.
|
|
557
|
+
*
|
|
558
|
+
* In LOCAL/SELF-SOVEREIGN mode (this implementation):
|
|
559
|
+
* - The USER pays their own transaction fees directly
|
|
560
|
+
* - The facilitator NEVER submits transactions - it only VERIFIES them
|
|
561
|
+
* - Therefore, no fee payer address is actually used
|
|
562
|
+
*
|
|
563
|
+
* However, the x402 protocol REQUIRES this field in the response schema.
|
|
564
|
+
* We use the System Program address (all 1s) as a placeholder because:
|
|
565
|
+
* 1. It's clearly not a real wallet (obvious placeholder)
|
|
566
|
+
* 2. It cannot receive funds or sign transactions
|
|
567
|
+
* 3. It signals to developers that fee paying is handled differently
|
|
568
|
+
*
|
|
569
|
+
* SECURITY NOTE: This is NOT a security risk because:
|
|
570
|
+
* - The fee payer is never used in verify() or settle() methods
|
|
571
|
+
* - Users sign and pay for their own transactions
|
|
572
|
+
* - The address is just a protocol-required placeholder
|
|
573
|
+
*
|
|
574
|
+
* @see https://docs.x402.org for protocol specification
|
|
575
|
+
*/
|
|
576
|
+
DUMMY_FEE_PAYER = "11111111111111111111111111111111";
|
|
577
|
+
/**
|
|
578
|
+
* Get supported payment kinds
|
|
579
|
+
* Returns x402 v2 compatible response with CAIP-2 network identifiers
|
|
580
|
+
*
|
|
581
|
+
* NOTE: The feePayer in extra.feePayer is a placeholder only.
|
|
582
|
+
* In self-sovereign mode, users pay their own transaction fees.
|
|
583
|
+
*/
|
|
584
|
+
async getSupported(_extensionKeys = []) {
|
|
585
|
+
const supported = {
|
|
586
|
+
kinds: [
|
|
587
|
+
{
|
|
588
|
+
x402Version: 2,
|
|
589
|
+
scheme: "exact",
|
|
590
|
+
network: this.NETWORKS.DEVNET,
|
|
591
|
+
extra: { feePayer: this.DUMMY_FEE_PAYER }
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
x402Version: 2,
|
|
595
|
+
scheme: "exact",
|
|
596
|
+
network: this.NETWORKS.MAINNET,
|
|
597
|
+
extra: { feePayer: this.DUMMY_FEE_PAYER }
|
|
598
|
+
}
|
|
599
|
+
],
|
|
600
|
+
extensions: [],
|
|
601
|
+
signers: {
|
|
602
|
+
// Placeholder - in self-sovereign mode, users are their own signers
|
|
603
|
+
"solana:*": [this.DUMMY_FEE_PAYER]
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
return supported;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Get mechanism-specific extra data
|
|
610
|
+
*/
|
|
611
|
+
getExtra(_network) {
|
|
612
|
+
return void 0;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Get default signers (not used for local verification usually, but required by interface)
|
|
616
|
+
*/
|
|
617
|
+
getSigners(_network) {
|
|
618
|
+
return [];
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Enable debug logging (disable in production)
|
|
622
|
+
*/
|
|
623
|
+
debug = process.env.NODE_ENV === "development";
|
|
624
|
+
/**
|
|
625
|
+
* Verify a payment on-chain
|
|
626
|
+
*/
|
|
627
|
+
async verify(payload, requirements) {
|
|
628
|
+
try {
|
|
629
|
+
const signature = payload.payload.signature;
|
|
630
|
+
if (!signature) {
|
|
631
|
+
return { isValid: false, invalidReason: "Missing signature in payment payload" };
|
|
632
|
+
}
|
|
633
|
+
const payTo = requirements.payTo;
|
|
634
|
+
const amountVal = requirements.amount || requirements.maxAmountRequired || "0";
|
|
635
|
+
const requiredAmount = BigInt(amountVal);
|
|
636
|
+
if (this.debug) {
|
|
637
|
+
console.log(`[LocalSvmFacilitator] Verifying tx: ${signature.slice(0, 8)}...`);
|
|
638
|
+
}
|
|
639
|
+
const tx = await this.fetchTransactionWithRetry(signature, 3);
|
|
640
|
+
if (!tx) {
|
|
641
|
+
return { isValid: false, invalidReason: "Transaction not found or not confirmed" };
|
|
642
|
+
}
|
|
643
|
+
const instructions = tx.transaction.message.instructions;
|
|
644
|
+
let paidAmount = 0n;
|
|
645
|
+
let payer = void 0;
|
|
646
|
+
for (const ix of instructions) {
|
|
647
|
+
if ("program" in ix && ix.program === "system") {
|
|
648
|
+
const parsed = ix.parsed;
|
|
649
|
+
if (parsed?.type === "transfer" && parsed.info?.destination === payTo) {
|
|
650
|
+
paidAmount += BigInt(parsed.info.lamports);
|
|
651
|
+
if (!payer) payer = parsed.info.source;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if ("program" in ix && (ix.program === "spl-token" || ix.program === "spl-token-2022")) {
|
|
655
|
+
const parsed = ix.parsed;
|
|
656
|
+
if (parsed?.type === "transferChecked" || parsed?.type === "transfer") {
|
|
657
|
+
if (this.debug) {
|
|
658
|
+
console.log(`[LocalSvmFacilitator] Found SPL transfer`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (paidAmount >= requiredAmount) {
|
|
664
|
+
if (this.debug) {
|
|
665
|
+
console.log(`[LocalSvmFacilitator] Verification SUCCESS for tx: ${signature.slice(0, 8)}...`);
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
isValid: true,
|
|
669
|
+
payer: payer || tx.transaction.message.accountKeys[0].pubkey.toBase58()
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
isValid: false,
|
|
674
|
+
invalidReason: "Insufficient payment amount",
|
|
675
|
+
payer
|
|
676
|
+
};
|
|
677
|
+
} catch (error) {
|
|
678
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
679
|
+
if (this.debug) {
|
|
680
|
+
console.error("[LocalSvmFacilitator] Verify error:", errorMessage);
|
|
681
|
+
}
|
|
682
|
+
throw new types.VerifyError(500, {
|
|
683
|
+
isValid: false,
|
|
684
|
+
invalidReason: errorMessage
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Fetch transaction with exponential backoff retry
|
|
690
|
+
*/
|
|
691
|
+
async fetchTransactionWithRetry(signature, maxRetries = 3) {
|
|
692
|
+
let lastError;
|
|
693
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
694
|
+
try {
|
|
695
|
+
const tx = await this.connection.getParsedTransaction(signature, {
|
|
696
|
+
maxSupportedTransactionVersion: 0,
|
|
697
|
+
commitment: "confirmed"
|
|
698
|
+
});
|
|
699
|
+
if (tx) return tx;
|
|
700
|
+
if (attempt < maxRetries - 1) {
|
|
701
|
+
await this.sleep(Math.pow(2, attempt) * 1e3);
|
|
702
|
+
}
|
|
703
|
+
} catch (error) {
|
|
704
|
+
lastError = error instanceof Error ? error : new Error("RPC error");
|
|
705
|
+
if (attempt < maxRetries - 1) {
|
|
706
|
+
await this.sleep(Math.pow(2, attempt) * 1e3);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if (lastError) throw lastError;
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Sleep helper
|
|
715
|
+
*/
|
|
716
|
+
sleep(ms) {
|
|
717
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Settle a payment (not applicable for direct chain verification, usually)
|
|
721
|
+
* But we must implement it. For 'exact', settlement is just verification + finality.
|
|
722
|
+
*/
|
|
723
|
+
async settle(payload, requirements) {
|
|
724
|
+
const verifyResult = await this.verify(payload, requirements);
|
|
725
|
+
if (!verifyResult.isValid) {
|
|
726
|
+
throw new types.SettleError(400, {
|
|
727
|
+
success: false,
|
|
728
|
+
errorReason: verifyResult.invalidReason || "Verification failed",
|
|
729
|
+
transaction: payload.payload.signature,
|
|
730
|
+
network: requirements.network
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
return {
|
|
734
|
+
success: true,
|
|
735
|
+
payer: verifyResult.payer,
|
|
736
|
+
transaction: payload.payload.signature,
|
|
737
|
+
network: requirements.network
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
};
|
|
531
741
|
var DEFAULT_COMPUTE_UNITS = 2e5;
|
|
532
742
|
var DEFAULT_MICRO_LAMPORTS = 1e3;
|
|
533
743
|
function createPriorityFeeInstructions(config = {}) {
|
|
@@ -870,6 +1080,7 @@ async function getRemainingCredits(token, secret) {
|
|
|
870
1080
|
};
|
|
871
1081
|
}
|
|
872
1082
|
|
|
1083
|
+
exports.LocalSvmFacilitator = LocalSvmFacilitator;
|
|
873
1084
|
exports.addCredits = addCredits;
|
|
874
1085
|
exports.buildSolanaPayUrl = buildSolanaPayUrl;
|
|
875
1086
|
exports.clearPriceCache = clearPriceCache;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from '@x402/core';
|
|
2
|
+
import { PaymentPayload, PaymentRequirements, VerifyResponse, SettleResponse, SupportedResponse, Network } from '@x402/core/types';
|
|
2
3
|
export * from '@x402/core/types';
|
|
3
4
|
export * from '@x402/core/client';
|
|
4
5
|
export * from '@x402/svm';
|
|
@@ -7,3 +8,95 @@ export { AgentPaymentResult, CreditSessionClaims, CreditSessionConfig, CreditSes
|
|
|
7
8
|
export { CustomPriceProvider, PriceConfig, PriceData, clearPriceCache, configurePricing, formatPriceDisplay, formatPriceSync, getProviders, getSolPrice, lamportsToSol, lamportsToUsd, usdToLamports } from './pricing/index.cjs';
|
|
8
9
|
import '@solana/web3.js';
|
|
9
10
|
import './types-BWYQMw03.cjs';
|
|
11
|
+
|
|
12
|
+
interface FacilitatorClient {
|
|
13
|
+
verify(paymentPayload: PaymentPayload, paymentRequirements: PaymentRequirements): Promise<VerifyResponse>;
|
|
14
|
+
settle(paymentPayload: PaymentPayload, paymentRequirements: PaymentRequirements): Promise<SettleResponse>;
|
|
15
|
+
getSupported(extensionKeys?: string[]): Promise<SupportedResponse>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Local SVM Facilitator
|
|
19
|
+
*
|
|
20
|
+
* Verifies and settles Solana payments locally using a direct RPC connection,
|
|
21
|
+
* bypassing the need for a hosted facilitator service.
|
|
22
|
+
*/
|
|
23
|
+
declare class LocalSvmFacilitator implements FacilitatorClient {
|
|
24
|
+
readonly scheme = "exact";
|
|
25
|
+
readonly caipFamily = "solana:*";
|
|
26
|
+
private connection;
|
|
27
|
+
constructor(rpcUrl: string);
|
|
28
|
+
/**
|
|
29
|
+
* Get supported payment kinds
|
|
30
|
+
* Mocking the response of the /supported endpoint
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Network Constants - CAIP-2 format for x402 v2
|
|
34
|
+
* These match the official Solana chain IDs used by x402 protocol
|
|
35
|
+
*/
|
|
36
|
+
private readonly NETWORKS;
|
|
37
|
+
/**
|
|
38
|
+
* DUMMY_FEE_PAYER Explanation (for auditors):
|
|
39
|
+
*
|
|
40
|
+
* In a HOSTED facilitator (like x402.org), the `feePayer` field in the
|
|
41
|
+
* SupportedResponse.extra specifies which address will pay transaction fees
|
|
42
|
+
* when the facilitator submits transactions on behalf of users.
|
|
43
|
+
*
|
|
44
|
+
* In LOCAL/SELF-SOVEREIGN mode (this implementation):
|
|
45
|
+
* - The USER pays their own transaction fees directly
|
|
46
|
+
* - The facilitator NEVER submits transactions - it only VERIFIES them
|
|
47
|
+
* - Therefore, no fee payer address is actually used
|
|
48
|
+
*
|
|
49
|
+
* However, the x402 protocol REQUIRES this field in the response schema.
|
|
50
|
+
* We use the System Program address (all 1s) as a placeholder because:
|
|
51
|
+
* 1. It's clearly not a real wallet (obvious placeholder)
|
|
52
|
+
* 2. It cannot receive funds or sign transactions
|
|
53
|
+
* 3. It signals to developers that fee paying is handled differently
|
|
54
|
+
*
|
|
55
|
+
* SECURITY NOTE: This is NOT a security risk because:
|
|
56
|
+
* - The fee payer is never used in verify() or settle() methods
|
|
57
|
+
* - Users sign and pay for their own transactions
|
|
58
|
+
* - The address is just a protocol-required placeholder
|
|
59
|
+
*
|
|
60
|
+
* @see https://docs.x402.org for protocol specification
|
|
61
|
+
*/
|
|
62
|
+
private readonly DUMMY_FEE_PAYER;
|
|
63
|
+
/**
|
|
64
|
+
* Get supported payment kinds
|
|
65
|
+
* Returns x402 v2 compatible response with CAIP-2 network identifiers
|
|
66
|
+
*
|
|
67
|
+
* NOTE: The feePayer in extra.feePayer is a placeholder only.
|
|
68
|
+
* In self-sovereign mode, users pay their own transaction fees.
|
|
69
|
+
*/
|
|
70
|
+
getSupported(_extensionKeys?: string[]): Promise<SupportedResponse>;
|
|
71
|
+
/**
|
|
72
|
+
* Get mechanism-specific extra data
|
|
73
|
+
*/
|
|
74
|
+
getExtra(_network: Network): Record<string, unknown> | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Get default signers (not used for local verification usually, but required by interface)
|
|
77
|
+
*/
|
|
78
|
+
getSigners(_network: string): string[];
|
|
79
|
+
/**
|
|
80
|
+
* Enable debug logging (disable in production)
|
|
81
|
+
*/
|
|
82
|
+
private debug;
|
|
83
|
+
/**
|
|
84
|
+
* Verify a payment on-chain
|
|
85
|
+
*/
|
|
86
|
+
verify(payload: PaymentPayload, requirements: PaymentRequirements): Promise<VerifyResponse>;
|
|
87
|
+
/**
|
|
88
|
+
* Fetch transaction with exponential backoff retry
|
|
89
|
+
*/
|
|
90
|
+
private fetchTransactionWithRetry;
|
|
91
|
+
/**
|
|
92
|
+
* Sleep helper
|
|
93
|
+
*/
|
|
94
|
+
private sleep;
|
|
95
|
+
/**
|
|
96
|
+
* Settle a payment (not applicable for direct chain verification, usually)
|
|
97
|
+
* But we must implement it. For 'exact', settlement is just verification + finality.
|
|
98
|
+
*/
|
|
99
|
+
settle(payload: PaymentPayload, requirements: PaymentRequirements): Promise<SettleResponse>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { LocalSvmFacilitator };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from '@x402/core';
|
|
2
|
+
import { PaymentPayload, PaymentRequirements, VerifyResponse, SettleResponse, SupportedResponse, Network } from '@x402/core/types';
|
|
2
3
|
export * from '@x402/core/types';
|
|
3
4
|
export * from '@x402/core/client';
|
|
4
5
|
export * from '@x402/svm';
|
|
@@ -7,3 +8,95 @@ export { AgentPaymentResult, CreditSessionClaims, CreditSessionConfig, CreditSes
|
|
|
7
8
|
export { CustomPriceProvider, PriceConfig, PriceData, clearPriceCache, configurePricing, formatPriceDisplay, formatPriceSync, getProviders, getSolPrice, lamportsToSol, lamportsToUsd, usdToLamports } from './pricing/index.js';
|
|
8
9
|
import '@solana/web3.js';
|
|
9
10
|
import './types-BWYQMw03.js';
|
|
11
|
+
|
|
12
|
+
interface FacilitatorClient {
|
|
13
|
+
verify(paymentPayload: PaymentPayload, paymentRequirements: PaymentRequirements): Promise<VerifyResponse>;
|
|
14
|
+
settle(paymentPayload: PaymentPayload, paymentRequirements: PaymentRequirements): Promise<SettleResponse>;
|
|
15
|
+
getSupported(extensionKeys?: string[]): Promise<SupportedResponse>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Local SVM Facilitator
|
|
19
|
+
*
|
|
20
|
+
* Verifies and settles Solana payments locally using a direct RPC connection,
|
|
21
|
+
* bypassing the need for a hosted facilitator service.
|
|
22
|
+
*/
|
|
23
|
+
declare class LocalSvmFacilitator implements FacilitatorClient {
|
|
24
|
+
readonly scheme = "exact";
|
|
25
|
+
readonly caipFamily = "solana:*";
|
|
26
|
+
private connection;
|
|
27
|
+
constructor(rpcUrl: string);
|
|
28
|
+
/**
|
|
29
|
+
* Get supported payment kinds
|
|
30
|
+
* Mocking the response of the /supported endpoint
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Network Constants - CAIP-2 format for x402 v2
|
|
34
|
+
* These match the official Solana chain IDs used by x402 protocol
|
|
35
|
+
*/
|
|
36
|
+
private readonly NETWORKS;
|
|
37
|
+
/**
|
|
38
|
+
* DUMMY_FEE_PAYER Explanation (for auditors):
|
|
39
|
+
*
|
|
40
|
+
* In a HOSTED facilitator (like x402.org), the `feePayer` field in the
|
|
41
|
+
* SupportedResponse.extra specifies which address will pay transaction fees
|
|
42
|
+
* when the facilitator submits transactions on behalf of users.
|
|
43
|
+
*
|
|
44
|
+
* In LOCAL/SELF-SOVEREIGN mode (this implementation):
|
|
45
|
+
* - The USER pays their own transaction fees directly
|
|
46
|
+
* - The facilitator NEVER submits transactions - it only VERIFIES them
|
|
47
|
+
* - Therefore, no fee payer address is actually used
|
|
48
|
+
*
|
|
49
|
+
* However, the x402 protocol REQUIRES this field in the response schema.
|
|
50
|
+
* We use the System Program address (all 1s) as a placeholder because:
|
|
51
|
+
* 1. It's clearly not a real wallet (obvious placeholder)
|
|
52
|
+
* 2. It cannot receive funds or sign transactions
|
|
53
|
+
* 3. It signals to developers that fee paying is handled differently
|
|
54
|
+
*
|
|
55
|
+
* SECURITY NOTE: This is NOT a security risk because:
|
|
56
|
+
* - The fee payer is never used in verify() or settle() methods
|
|
57
|
+
* - Users sign and pay for their own transactions
|
|
58
|
+
* - The address is just a protocol-required placeholder
|
|
59
|
+
*
|
|
60
|
+
* @see https://docs.x402.org for protocol specification
|
|
61
|
+
*/
|
|
62
|
+
private readonly DUMMY_FEE_PAYER;
|
|
63
|
+
/**
|
|
64
|
+
* Get supported payment kinds
|
|
65
|
+
* Returns x402 v2 compatible response with CAIP-2 network identifiers
|
|
66
|
+
*
|
|
67
|
+
* NOTE: The feePayer in extra.feePayer is a placeholder only.
|
|
68
|
+
* In self-sovereign mode, users pay their own transaction fees.
|
|
69
|
+
*/
|
|
70
|
+
getSupported(_extensionKeys?: string[]): Promise<SupportedResponse>;
|
|
71
|
+
/**
|
|
72
|
+
* Get mechanism-specific extra data
|
|
73
|
+
*/
|
|
74
|
+
getExtra(_network: Network): Record<string, unknown> | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Get default signers (not used for local verification usually, but required by interface)
|
|
77
|
+
*/
|
|
78
|
+
getSigners(_network: string): string[];
|
|
79
|
+
/**
|
|
80
|
+
* Enable debug logging (disable in production)
|
|
81
|
+
*/
|
|
82
|
+
private debug;
|
|
83
|
+
/**
|
|
84
|
+
* Verify a payment on-chain
|
|
85
|
+
*/
|
|
86
|
+
verify(payload: PaymentPayload, requirements: PaymentRequirements): Promise<VerifyResponse>;
|
|
87
|
+
/**
|
|
88
|
+
* Fetch transaction with exponential backoff retry
|
|
89
|
+
*/
|
|
90
|
+
private fetchTransactionWithRetry;
|
|
91
|
+
/**
|
|
92
|
+
* Sleep helper
|
|
93
|
+
*/
|
|
94
|
+
private sleep;
|
|
95
|
+
/**
|
|
96
|
+
* Settle a payment (not applicable for direct chain verification, usually)
|
|
97
|
+
* But we must implement it. For 'exact', settlement is just verification + finality.
|
|
98
|
+
*/
|
|
99
|
+
settle(payload: PaymentPayload, requirements: PaymentRequirements): Promise<SettleResponse>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { LocalSvmFacilitator };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export * from '@x402/core';
|
|
2
|
+
import { VerifyError, SettleError } from '@x402/core/types';
|
|
2
3
|
export * from '@x402/core/types';
|
|
3
4
|
export * from '@x402/core/client';
|
|
4
5
|
export * from '@x402/svm';
|
|
5
6
|
import { decodePaymentRequiredHeader, encodePaymentSignatureHeader } from '@x402/core/http';
|
|
6
|
-
import { PublicKey, Transaction, SystemProgram, LAMPORTS_PER_SOL, Keypair, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } from '@solana/web3.js';
|
|
7
|
+
import { PublicKey, Transaction, SystemProgram, LAMPORTS_PER_SOL, Connection, Keypair, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } from '@solana/web3.js';
|
|
7
8
|
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
8
9
|
import bs58 from 'bs58';
|
|
9
10
|
import { SignJWT, jwtVerify } from 'jose';
|
|
@@ -522,6 +523,216 @@ function useFormatPrice(lamports) {
|
|
|
522
523
|
isLoading: false
|
|
523
524
|
};
|
|
524
525
|
}
|
|
526
|
+
var LocalSvmFacilitator = class {
|
|
527
|
+
scheme = "exact";
|
|
528
|
+
caipFamily = "solana:*";
|
|
529
|
+
connection;
|
|
530
|
+
constructor(rpcUrl) {
|
|
531
|
+
console.log("[LocalSvmFacilitator] Initialized with RPC:", rpcUrl);
|
|
532
|
+
this.connection = new Connection(rpcUrl, "confirmed");
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Get supported payment kinds
|
|
536
|
+
* Mocking the response of the /supported endpoint
|
|
537
|
+
*/
|
|
538
|
+
/**
|
|
539
|
+
* Network Constants - CAIP-2 format for x402 v2
|
|
540
|
+
* These match the official Solana chain IDs used by x402 protocol
|
|
541
|
+
*/
|
|
542
|
+
NETWORKS = {
|
|
543
|
+
DEVNET: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
|
|
544
|
+
MAINNET: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
|
|
545
|
+
};
|
|
546
|
+
/**
|
|
547
|
+
* DUMMY_FEE_PAYER Explanation (for auditors):
|
|
548
|
+
*
|
|
549
|
+
* In a HOSTED facilitator (like x402.org), the `feePayer` field in the
|
|
550
|
+
* SupportedResponse.extra specifies which address will pay transaction fees
|
|
551
|
+
* when the facilitator submits transactions on behalf of users.
|
|
552
|
+
*
|
|
553
|
+
* In LOCAL/SELF-SOVEREIGN mode (this implementation):
|
|
554
|
+
* - The USER pays their own transaction fees directly
|
|
555
|
+
* - The facilitator NEVER submits transactions - it only VERIFIES them
|
|
556
|
+
* - Therefore, no fee payer address is actually used
|
|
557
|
+
*
|
|
558
|
+
* However, the x402 protocol REQUIRES this field in the response schema.
|
|
559
|
+
* We use the System Program address (all 1s) as a placeholder because:
|
|
560
|
+
* 1. It's clearly not a real wallet (obvious placeholder)
|
|
561
|
+
* 2. It cannot receive funds or sign transactions
|
|
562
|
+
* 3. It signals to developers that fee paying is handled differently
|
|
563
|
+
*
|
|
564
|
+
* SECURITY NOTE: This is NOT a security risk because:
|
|
565
|
+
* - The fee payer is never used in verify() or settle() methods
|
|
566
|
+
* - Users sign and pay for their own transactions
|
|
567
|
+
* - The address is just a protocol-required placeholder
|
|
568
|
+
*
|
|
569
|
+
* @see https://docs.x402.org for protocol specification
|
|
570
|
+
*/
|
|
571
|
+
DUMMY_FEE_PAYER = "11111111111111111111111111111111";
|
|
572
|
+
/**
|
|
573
|
+
* Get supported payment kinds
|
|
574
|
+
* Returns x402 v2 compatible response with CAIP-2 network identifiers
|
|
575
|
+
*
|
|
576
|
+
* NOTE: The feePayer in extra.feePayer is a placeholder only.
|
|
577
|
+
* In self-sovereign mode, users pay their own transaction fees.
|
|
578
|
+
*/
|
|
579
|
+
async getSupported(_extensionKeys = []) {
|
|
580
|
+
const supported = {
|
|
581
|
+
kinds: [
|
|
582
|
+
{
|
|
583
|
+
x402Version: 2,
|
|
584
|
+
scheme: "exact",
|
|
585
|
+
network: this.NETWORKS.DEVNET,
|
|
586
|
+
extra: { feePayer: this.DUMMY_FEE_PAYER }
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
x402Version: 2,
|
|
590
|
+
scheme: "exact",
|
|
591
|
+
network: this.NETWORKS.MAINNET,
|
|
592
|
+
extra: { feePayer: this.DUMMY_FEE_PAYER }
|
|
593
|
+
}
|
|
594
|
+
],
|
|
595
|
+
extensions: [],
|
|
596
|
+
signers: {
|
|
597
|
+
// Placeholder - in self-sovereign mode, users are their own signers
|
|
598
|
+
"solana:*": [this.DUMMY_FEE_PAYER]
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
return supported;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Get mechanism-specific extra data
|
|
605
|
+
*/
|
|
606
|
+
getExtra(_network) {
|
|
607
|
+
return void 0;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Get default signers (not used for local verification usually, but required by interface)
|
|
611
|
+
*/
|
|
612
|
+
getSigners(_network) {
|
|
613
|
+
return [];
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Enable debug logging (disable in production)
|
|
617
|
+
*/
|
|
618
|
+
debug = process.env.NODE_ENV === "development";
|
|
619
|
+
/**
|
|
620
|
+
* Verify a payment on-chain
|
|
621
|
+
*/
|
|
622
|
+
async verify(payload, requirements) {
|
|
623
|
+
try {
|
|
624
|
+
const signature = payload.payload.signature;
|
|
625
|
+
if (!signature) {
|
|
626
|
+
return { isValid: false, invalidReason: "Missing signature in payment payload" };
|
|
627
|
+
}
|
|
628
|
+
const payTo = requirements.payTo;
|
|
629
|
+
const amountVal = requirements.amount || requirements.maxAmountRequired || "0";
|
|
630
|
+
const requiredAmount = BigInt(amountVal);
|
|
631
|
+
if (this.debug) {
|
|
632
|
+
console.log(`[LocalSvmFacilitator] Verifying tx: ${signature.slice(0, 8)}...`);
|
|
633
|
+
}
|
|
634
|
+
const tx = await this.fetchTransactionWithRetry(signature, 3);
|
|
635
|
+
if (!tx) {
|
|
636
|
+
return { isValid: false, invalidReason: "Transaction not found or not confirmed" };
|
|
637
|
+
}
|
|
638
|
+
const instructions = tx.transaction.message.instructions;
|
|
639
|
+
let paidAmount = 0n;
|
|
640
|
+
let payer = void 0;
|
|
641
|
+
for (const ix of instructions) {
|
|
642
|
+
if ("program" in ix && ix.program === "system") {
|
|
643
|
+
const parsed = ix.parsed;
|
|
644
|
+
if (parsed?.type === "transfer" && parsed.info?.destination === payTo) {
|
|
645
|
+
paidAmount += BigInt(parsed.info.lamports);
|
|
646
|
+
if (!payer) payer = parsed.info.source;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if ("program" in ix && (ix.program === "spl-token" || ix.program === "spl-token-2022")) {
|
|
650
|
+
const parsed = ix.parsed;
|
|
651
|
+
if (parsed?.type === "transferChecked" || parsed?.type === "transfer") {
|
|
652
|
+
if (this.debug) {
|
|
653
|
+
console.log(`[LocalSvmFacilitator] Found SPL transfer`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
if (paidAmount >= requiredAmount) {
|
|
659
|
+
if (this.debug) {
|
|
660
|
+
console.log(`[LocalSvmFacilitator] Verification SUCCESS for tx: ${signature.slice(0, 8)}...`);
|
|
661
|
+
}
|
|
662
|
+
return {
|
|
663
|
+
isValid: true,
|
|
664
|
+
payer: payer || tx.transaction.message.accountKeys[0].pubkey.toBase58()
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
isValid: false,
|
|
669
|
+
invalidReason: "Insufficient payment amount",
|
|
670
|
+
payer
|
|
671
|
+
};
|
|
672
|
+
} catch (error) {
|
|
673
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
674
|
+
if (this.debug) {
|
|
675
|
+
console.error("[LocalSvmFacilitator] Verify error:", errorMessage);
|
|
676
|
+
}
|
|
677
|
+
throw new VerifyError(500, {
|
|
678
|
+
isValid: false,
|
|
679
|
+
invalidReason: errorMessage
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Fetch transaction with exponential backoff retry
|
|
685
|
+
*/
|
|
686
|
+
async fetchTransactionWithRetry(signature, maxRetries = 3) {
|
|
687
|
+
let lastError;
|
|
688
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
689
|
+
try {
|
|
690
|
+
const tx = await this.connection.getParsedTransaction(signature, {
|
|
691
|
+
maxSupportedTransactionVersion: 0,
|
|
692
|
+
commitment: "confirmed"
|
|
693
|
+
});
|
|
694
|
+
if (tx) return tx;
|
|
695
|
+
if (attempt < maxRetries - 1) {
|
|
696
|
+
await this.sleep(Math.pow(2, attempt) * 1e3);
|
|
697
|
+
}
|
|
698
|
+
} catch (error) {
|
|
699
|
+
lastError = error instanceof Error ? error : new Error("RPC error");
|
|
700
|
+
if (attempt < maxRetries - 1) {
|
|
701
|
+
await this.sleep(Math.pow(2, attempt) * 1e3);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (lastError) throw lastError;
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Sleep helper
|
|
710
|
+
*/
|
|
711
|
+
sleep(ms) {
|
|
712
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Settle a payment (not applicable for direct chain verification, usually)
|
|
716
|
+
* But we must implement it. For 'exact', settlement is just verification + finality.
|
|
717
|
+
*/
|
|
718
|
+
async settle(payload, requirements) {
|
|
719
|
+
const verifyResult = await this.verify(payload, requirements);
|
|
720
|
+
if (!verifyResult.isValid) {
|
|
721
|
+
throw new SettleError(400, {
|
|
722
|
+
success: false,
|
|
723
|
+
errorReason: verifyResult.invalidReason || "Verification failed",
|
|
724
|
+
transaction: payload.payload.signature,
|
|
725
|
+
network: requirements.network
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
return {
|
|
729
|
+
success: true,
|
|
730
|
+
payer: verifyResult.payer,
|
|
731
|
+
transaction: payload.payload.signature,
|
|
732
|
+
network: requirements.network
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
};
|
|
525
736
|
var DEFAULT_COMPUTE_UNITS = 2e5;
|
|
526
737
|
var DEFAULT_MICRO_LAMPORTS = 1e3;
|
|
527
738
|
function createPriorityFeeInstructions(config = {}) {
|
|
@@ -864,4 +1075,4 @@ async function getRemainingCredits(token, secret) {
|
|
|
864
1075
|
};
|
|
865
1076
|
}
|
|
866
1077
|
|
|
867
|
-
export { addCredits, buildSolanaPayUrl, clearPriceCache, configurePricing, createCreditSession, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, executeAgentPayment, formatPriceDisplay, formatPriceSync, generateAgentKeypair, getAgentBalance, getProviders, getRemainingCredits, getSolPrice, hasAgentSufficientBalance, keypairFromBase58, lamportsToSol, lamportsToUsd, sendSolanaPayment, usdToLamports, useCredit, useFormatPrice, useLamportsToUsd, useMicropay, usePaywallResource, usePricing, validateCreditSession };
|
|
1078
|
+
export { LocalSvmFacilitator, addCredits, buildSolanaPayUrl, clearPriceCache, configurePricing, createCreditSession, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, executeAgentPayment, formatPriceDisplay, formatPriceSync, generateAgentKeypair, getAgentBalance, getProviders, getRemainingCredits, getSolPrice, hasAgentSufficientBalance, keypairFromBase58, lamportsToSol, lamportsToUsd, sendSolanaPayment, usdToLamports, useCredit, useFormatPrice, useLamportsToUsd, useMicropay, usePaywallResource, usePricing, validateCreditSession };
|
package/package.json
CHANGED