@mcpaid/sdk 2.0.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.
Files changed (111) hide show
  1. package/LICENSE +176 -0
  2. package/NPM_RESTRUCTURE.md +86 -0
  3. package/OPERATOR-LICENSE.md +18 -0
  4. package/README.md +380 -0
  5. package/SELF-HOSTING.md +108 -0
  6. package/dist/circuit-breaker/circuit-breaker.d.ts +26 -0
  7. package/dist/circuit-breaker/circuit-breaker.d.ts.map +1 -0
  8. package/dist/circuit-breaker/circuit-breaker.js +128 -0
  9. package/dist/circuit-breaker/circuit-breaker.js.map +1 -0
  10. package/dist/cli.d.ts +21 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +1560 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/contracts/ToolPayRouter.d.ts +288 -0
  15. package/dist/contracts/ToolPayRouter.d.ts.map +1 -0
  16. package/dist/contracts/ToolPayRouter.js +370 -0
  17. package/dist/contracts/ToolPayRouter.js.map +1 -0
  18. package/dist/contracts/router-registry.d.ts +19 -0
  19. package/dist/contracts/router-registry.d.ts.map +1 -0
  20. package/dist/contracts/router-registry.js +28 -0
  21. package/dist/contracts/router-registry.js.map +1 -0
  22. package/dist/fee/fee-calculator.d.ts +35 -0
  23. package/dist/fee/fee-calculator.d.ts.map +1 -0
  24. package/dist/fee/fee-calculator.js +57 -0
  25. package/dist/fee/fee-calculator.js.map +1 -0
  26. package/dist/fee/fee-ledger.d.ts +66 -0
  27. package/dist/fee/fee-ledger.d.ts.map +1 -0
  28. package/dist/fee/fee-ledger.js +181 -0
  29. package/dist/fee/fee-ledger.js.map +1 -0
  30. package/dist/gateway/config-loader.d.ts +38 -0
  31. package/dist/gateway/config-loader.d.ts.map +1 -0
  32. package/dist/gateway/config-loader.js +125 -0
  33. package/dist/gateway/config-loader.js.map +1 -0
  34. package/dist/gateway/server-registry.d.ts +25 -0
  35. package/dist/gateway/server-registry.d.ts.map +1 -0
  36. package/dist/gateway/server-registry.js +59 -0
  37. package/dist/gateway/server-registry.js.map +1 -0
  38. package/dist/gateway/toolpay-gateway.d.ts +57 -0
  39. package/dist/gateway/toolpay-gateway.d.ts.map +1 -0
  40. package/dist/gateway/toolpay-gateway.js +320 -0
  41. package/dist/gateway/toolpay-gateway.js.map +1 -0
  42. package/dist/index.d.ts +36 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +37 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/sdk/client-agent.d.ts +47 -0
  47. package/dist/sdk/client-agent.d.ts.map +1 -0
  48. package/dist/sdk/client-agent.js +158 -0
  49. package/dist/sdk/client-agent.js.map +1 -0
  50. package/dist/sdk/server-sdk.d.ts +51 -0
  51. package/dist/sdk/server-sdk.d.ts.map +1 -0
  52. package/dist/sdk/server-sdk.js +170 -0
  53. package/dist/sdk/server-sdk.js.map +1 -0
  54. package/dist/settlement/base-contract-client.d.ts +156 -0
  55. package/dist/settlement/base-contract-client.d.ts.map +1 -0
  56. package/dist/settlement/base-contract-client.js +156 -0
  57. package/dist/settlement/base-contract-client.js.map +1 -0
  58. package/dist/settlement/nonce-store.d.ts +54 -0
  59. package/dist/settlement/nonce-store.d.ts.map +1 -0
  60. package/dist/settlement/nonce-store.js +125 -0
  61. package/dist/settlement/nonce-store.js.map +1 -0
  62. package/dist/settlement/onchain-verifier.d.ts +41 -0
  63. package/dist/settlement/onchain-verifier.d.ts.map +1 -0
  64. package/dist/settlement/onchain-verifier.js +114 -0
  65. package/dist/settlement/onchain-verifier.js.map +1 -0
  66. package/dist/settlement/payment-orchestrator.d.ts +59 -0
  67. package/dist/settlement/payment-orchestrator.d.ts.map +1 -0
  68. package/dist/settlement/payment-orchestrator.js +282 -0
  69. package/dist/settlement/payment-orchestrator.js.map +1 -0
  70. package/dist/settlement/permit-verifier.d.ts +60 -0
  71. package/dist/settlement/permit-verifier.d.ts.map +1 -0
  72. package/dist/settlement/permit-verifier.js +80 -0
  73. package/dist/settlement/permit-verifier.js.map +1 -0
  74. package/dist/settlement/session-wallet.d.ts +60 -0
  75. package/dist/settlement/session-wallet.d.ts.map +1 -0
  76. package/dist/settlement/session-wallet.js +105 -0
  77. package/dist/settlement/session-wallet.js.map +1 -0
  78. package/dist/transports/sse-transport.d.ts +27 -0
  79. package/dist/transports/sse-transport.d.ts.map +1 -0
  80. package/dist/transports/sse-transport.js +65 -0
  81. package/dist/transports/sse-transport.js.map +1 -0
  82. package/dist/transports/stdio-bridge.d.ts +29 -0
  83. package/dist/transports/stdio-bridge.d.ts.map +1 -0
  84. package/dist/transports/stdio-bridge.js +133 -0
  85. package/dist/transports/stdio-bridge.js.map +1 -0
  86. package/dist/types/circuit-breaker.d.ts +22 -0
  87. package/dist/types/circuit-breaker.d.ts.map +1 -0
  88. package/dist/types/circuit-breaker.js +5 -0
  89. package/dist/types/circuit-breaker.js.map +1 -0
  90. package/dist/types/ledger.d.ts +57 -0
  91. package/dist/types/ledger.d.ts.map +1 -0
  92. package/dist/types/ledger.js +5 -0
  93. package/dist/types/ledger.js.map +1 -0
  94. package/dist/types/mcp.d.ts +67 -0
  95. package/dist/types/mcp.d.ts.map +1 -0
  96. package/dist/types/mcp.js +5 -0
  97. package/dist/types/mcp.js.map +1 -0
  98. package/dist/types/pricing.d.ts +68 -0
  99. package/dist/types/pricing.d.ts.map +1 -0
  100. package/dist/types/pricing.js +5 -0
  101. package/dist/types/pricing.js.map +1 -0
  102. package/dist/types/x402.d.ts +76 -0
  103. package/dist/types/x402.d.ts.map +1 -0
  104. package/dist/types/x402.js +5 -0
  105. package/dist/types/x402.js.map +1 -0
  106. package/dist/utils/units.d.ts +45 -0
  107. package/dist/utils/units.d.ts.map +1 -0
  108. package/dist/utils/units.js +141 -0
  109. package/dist/utils/units.js.map +1 -0
  110. package/package.json +72 -0
  111. package/src/contracts/ToolPayRouter.json +371 -0
@@ -0,0 +1,125 @@
1
+ /**
2
+ * ToolPay Nonce Store & Replay Prevention Engine
3
+ * Manages atomic challenge nonces, expiration TTLs, and prevents replay attacks.
4
+ */
5
+ import { randomUUID } from 'node:crypto';
6
+ export const DEFAULT_CHALLENGE_TTL_SECONDS = 120; // 2 minutes
7
+ export const MAX_STORED_NONCES = 10_000;
8
+ export class NonceStore {
9
+ store = new Map();
10
+ ttlSeconds;
11
+ opCount = 0;
12
+ constructor(ttlSeconds = DEFAULT_CHALLENGE_TTL_SECONDS) {
13
+ this.ttlSeconds = ttlSeconds;
14
+ }
15
+ /**
16
+ * Generates a unique, cryptographically random challenge nonce and stores the challenge data.
17
+ */
18
+ createChallenge(challengeDataOmitNonce, customTtlSeconds, nowSec = Math.floor(Date.now() / 1000)) {
19
+ this.opCount++;
20
+ // Auto-prune periodically (every 50 creations) or if capacity reached
21
+ if (this.opCount % 50 === 0 || this.store.size >= MAX_STORED_NONCES) {
22
+ this.pruneExpired(nowSec);
23
+ // If still over capacity, evict oldest 100 entries to prevent memory exhaustion
24
+ if (this.store.size >= MAX_STORED_NONCES) {
25
+ const iter = this.store.keys();
26
+ for (let i = 0; i < 100; i++) {
27
+ const next = iter.next();
28
+ if (next.done)
29
+ break;
30
+ this.store.delete(next.value);
31
+ }
32
+ }
33
+ }
34
+ const nonce = randomUUID();
35
+ const ttl = customTtlSeconds ?? this.ttlSeconds;
36
+ const expiresAt = nowSec + ttl;
37
+ const challengeData = {
38
+ ...challengeDataOmitNonce,
39
+ challengeNonce: nonce,
40
+ expiresAt,
41
+ };
42
+ this.store.set(nonce, {
43
+ data: challengeData,
44
+ status: 'PENDING',
45
+ createdAt: Date.now(),
46
+ });
47
+ return challengeData;
48
+ }
49
+ /**
50
+ * Checks if a challenge nonce is present in memory.
51
+ */
52
+ hasChallenge(nonce) {
53
+ return this.store.has(nonce);
54
+ }
55
+ /**
56
+ * Records or restores an existing challenge into the store (e.g. from KV across isolates).
57
+ */
58
+ recordChallenge(challenge) {
59
+ this.store.set(challenge.challengeNonce, {
60
+ data: challenge,
61
+ status: 'PENDING',
62
+ createdAt: Date.now(),
63
+ });
64
+ }
65
+ /**
66
+ * Validates that a nonce exists, is pending, and has not expired without consuming it.
67
+ */
68
+ peekChallenge(nonce, nowSec = Math.floor(Date.now() / 1000)) {
69
+ const record = this.store.get(nonce);
70
+ if (!record) {
71
+ throw new Error(`Invalid challenge nonce: "${nonce}" not found`);
72
+ }
73
+ if (record.status === 'EXPENDED') {
74
+ throw new Error(`Replay attack detected: challenge nonce "${nonce}" has already been used`);
75
+ }
76
+ if (record.status === 'EXPIRED' || nowSec > record.data.expiresAt) {
77
+ record.status = 'EXPIRED';
78
+ throw new Error(`Challenge nonce "${nonce}" has expired (expired at ${record.data.expiresAt}, current ${nowSec})`);
79
+ }
80
+ return record.data;
81
+ }
82
+ /**
83
+ * Consumes a validated nonce, transitioning it to EXPENDED.
84
+ */
85
+ consumeChallenge(nonce) {
86
+ const record = this.store.get(nonce);
87
+ if (!record) {
88
+ throw new Error(`Invalid challenge nonce: "${nonce}" not found`);
89
+ }
90
+ if (record.status === 'EXPENDED') {
91
+ throw new Error(`Replay attack detected: challenge nonce "${nonce}" has already been used`);
92
+ }
93
+ record.status = 'EXPENDED';
94
+ record.expendedAt = Date.now();
95
+ }
96
+ /**
97
+ * Verifies that a nonce exists, is pending, and has not expired.
98
+ * Atomically marks it as EXPENDED.
99
+ */
100
+ verifyAndConsume(nonce, nowSec = Math.floor(Date.now() / 1000)) {
101
+ const data = this.peekChallenge(nonce, nowSec);
102
+ this.consumeChallenge(nonce);
103
+ return data;
104
+ }
105
+ /**
106
+ * Checks the status of a nonce without consuming it.
107
+ */
108
+ getChallenge(nonce) {
109
+ return this.store.get(nonce);
110
+ }
111
+ /**
112
+ * Garbage collects expired nonces.
113
+ */
114
+ pruneExpired(nowSec = Math.floor(Date.now() / 1000)) {
115
+ let pruned = 0;
116
+ for (const [nonce, record] of this.store.entries()) {
117
+ if (nowSec > record.data.expiresAt + 300) { // keep 5 mins post-expiration for audit
118
+ this.store.delete(nonce);
119
+ pruned += 1;
120
+ }
121
+ }
122
+ return pruned;
123
+ }
124
+ }
125
+ //# sourceMappingURL=nonce-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nonce-store.js","sourceRoot":"","sources":["../../src/settlement/nonce-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * ToolPay On-Chain Settlement Verifier
3
+ * Verifies direct on-chain crypto transfers (e.g. USDC on Base L2) against 402 challenge parameters.
4
+ */
5
+ import { X402ChallengeData } from '../types/x402.js';
6
+ export interface OnChainTransactionData {
7
+ txHash: string;
8
+ from: string;
9
+ to: string;
10
+ amountMicro: bigint;
11
+ tokenAddress?: string;
12
+ chainId: number;
13
+ confirmed: boolean;
14
+ blockTimestampSec: number;
15
+ }
16
+ export interface OnChainRpcProvider {
17
+ getTransaction(txHash: string): Promise<OnChainTransactionData | null>;
18
+ }
19
+ export declare class OnChainVerifier {
20
+ private rpcProvider;
21
+ private claimedTxHashes;
22
+ private maxBlockAgeSec;
23
+ constructor(rpcProvider: OnChainRpcProvider, maxBlockAgeSec?: number, // max 3 minutes old
24
+ claimedTxHashes?: Set<string>);
25
+ isTxClaimed(txHash: string): boolean;
26
+ claimTx(txHash: string): void;
27
+ /**
28
+ * Validates an on-chain transaction against a challenge.
29
+ */
30
+ verifyTransaction(params: {
31
+ txHash: string;
32
+ signerWallet: string;
33
+ challenge: X402ChallengeData;
34
+ nowSec?: number;
35
+ }): Promise<{
36
+ valid: boolean;
37
+ error?: string;
38
+ txData?: OnChainTransactionData;
39
+ }>;
40
+ }
41
+ //# sourceMappingURL=onchain-verifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onchain-verifier.d.ts","sourceRoot":"","sources":["../../src/settlement/onchain-verifier.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAAC;CACxE;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,eAAe,CAA0B;IACjD,OAAO,CAAC,cAAc,CAAS;gBAG7B,WAAW,EAAE,kBAAkB,EAC/B,cAAc,GAAE,MAAY,EAAE,oBAAoB;IAClD,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC;IASxB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIpC,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIpC;;OAEG;IACU,iBAAiB,CAAC,MAAM,EAAE;QACrC,MAAM,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,iBAAiB,CAAC;QAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,sBAAsB,CAAA;KAAE,CAAC;CAiGjF"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * ToolPay On-Chain Settlement Verifier
3
+ * Verifies direct on-chain crypto transfers (e.g. USDC on Base L2) against 402 challenge parameters.
4
+ */
5
+ export class OnChainVerifier {
6
+ rpcProvider;
7
+ claimedTxHashes = new Set();
8
+ maxBlockAgeSec;
9
+ constructor(rpcProvider, maxBlockAgeSec = 180, // max 3 minutes old
10
+ claimedTxHashes) {
11
+ this.rpcProvider = rpcProvider;
12
+ this.maxBlockAgeSec = maxBlockAgeSec;
13
+ if (claimedTxHashes) {
14
+ this.claimedTxHashes = claimedTxHashes;
15
+ }
16
+ }
17
+ isTxClaimed(txHash) {
18
+ return this.claimedTxHashes.has(txHash.toLowerCase());
19
+ }
20
+ claimTx(txHash) {
21
+ this.claimedTxHashes.add(txHash.toLowerCase());
22
+ }
23
+ /**
24
+ * Validates an on-chain transaction against a challenge.
25
+ */
26
+ async verifyTransaction(params) {
27
+ const now = params.nowSec || Math.floor(Date.now() / 1000);
28
+ const txHashNormalized = params.txHash.toLowerCase();
29
+ // 1. Check double-spend / txHash replay & atomically claim before async fetch
30
+ if (this.claimedTxHashes.has(txHashNormalized)) {
31
+ return {
32
+ valid: false,
33
+ error: `Transaction ${params.txHash} has already been claimed for another payment`,
34
+ };
35
+ }
36
+ this.claimedTxHashes.add(txHashNormalized);
37
+ try {
38
+ // 2. Fetch transaction from RPC
39
+ const tx = await this.rpcProvider.getTransaction(params.txHash);
40
+ if (!tx) {
41
+ this.claimedTxHashes.delete(txHashNormalized);
42
+ return { valid: false, error: `Transaction ${params.txHash} not found on network` };
43
+ }
44
+ // 3. Verify confirmation
45
+ if (!tx.confirmed) {
46
+ this.claimedTxHashes.delete(txHashNormalized);
47
+ return { valid: false, error: `Transaction ${params.txHash} is still pending confirmation` };
48
+ }
49
+ // 4. Verify chain ID
50
+ if (tx.chainId !== params.challenge.chainId) {
51
+ this.claimedTxHashes.delete(txHashNormalized);
52
+ return {
53
+ valid: false,
54
+ error: `Transaction chain ID mismatch (expected ${params.challenge.chainId}, got ${tx.chainId})`,
55
+ };
56
+ }
57
+ // 5. Verify token address if specified in challenge
58
+ if (params.challenge.tokenAddress && tx.tokenAddress) {
59
+ if (tx.tokenAddress.toLowerCase() !== params.challenge.tokenAddress.toLowerCase()) {
60
+ this.claimedTxHashes.delete(txHashNormalized);
61
+ return {
62
+ valid: false,
63
+ error: `Transaction token mismatch (expected ${params.challenge.tokenAddress}, got ${tx.tokenAddress})`,
64
+ };
65
+ }
66
+ }
67
+ // 6. Verify recipient
68
+ if (tx.to.toLowerCase() !== params.challenge.recipientWallet.toLowerCase()) {
69
+ this.claimedTxHashes.delete(txHashNormalized);
70
+ return {
71
+ valid: false,
72
+ error: `Transaction recipient mismatch (expected ${params.challenge.recipientWallet}, got ${tx.to})`,
73
+ };
74
+ }
75
+ // 7. Verify sender
76
+ if (tx.from.toLowerCase() !== params.signerWallet.toLowerCase()) {
77
+ this.claimedTxHashes.delete(txHashNormalized);
78
+ return {
79
+ valid: false,
80
+ error: `Transaction sender mismatch (expected ${params.signerWallet}, got ${tx.from})`,
81
+ };
82
+ }
83
+ // 8. Verify amount
84
+ if (tx.amountMicro < params.challenge.amountMicro) {
85
+ this.claimedTxHashes.delete(txHashNormalized);
86
+ return {
87
+ valid: false,
88
+ error: `Insufficient transaction amount (expected ${params.challenge.amountMicro} micro-units, received ${tx.amountMicro})`,
89
+ };
90
+ }
91
+ // 9. Verify timestamp bounds (not in future beyond clock skew, and not too old)
92
+ if (tx.blockTimestampSec > now + 300) {
93
+ this.claimedTxHashes.delete(txHashNormalized);
94
+ return {
95
+ valid: false,
96
+ error: `Transaction block timestamp is in the future (${tx.blockTimestampSec} > ${now})`,
97
+ };
98
+ }
99
+ if (now - tx.blockTimestampSec > this.maxBlockAgeSec) {
100
+ this.claimedTxHashes.delete(txHashNormalized);
101
+ return {
102
+ valid: false,
103
+ error: `Transaction is too old (${now - tx.blockTimestampSec}s > ${this.maxBlockAgeSec}s allowed)`,
104
+ };
105
+ }
106
+ return { valid: true, txData: tx };
107
+ }
108
+ catch (err) {
109
+ this.claimedTxHashes.delete(txHashNormalized);
110
+ return { valid: false, error: `RPC provider error during verification: ${err.message}` };
111
+ }
112
+ }
113
+ }
114
+ //# sourceMappingURL=onchain-verifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onchain-verifier.js","sourceRoot":"","sources":["../../src/settlement/onchain-verifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * ToolPay Payment Orchestrator
3
+ * Coordinates challenges, multi-scheme verification, replay defense, and financial ledger settlement.
4
+ */
5
+ import { NonceStore } from './nonce-store.js';
6
+ import { PermitVerifier } from './permit-verifier.js';
7
+ import { OnChainVerifier } from './onchain-verifier.js';
8
+ import { BaseContractClient } from './base-contract-client.js';
9
+ import { SessionWalletManager } from './session-wallet.js';
10
+ import { FeeCalculator } from '../fee/fee-calculator.js';
11
+ import { FeeLedger } from '../fee/fee-ledger.js';
12
+ import { X402ChallengeData, X402ChallengeHeaders, X402ErrorResponse, PaymentProof } from '../types/x402.js';
13
+ import { ServerPricingConfig, ToolPricingRule } from '../types/pricing.js';
14
+ import { TransactionRecord } from '../types/ledger.js';
15
+ export declare class PaymentOrchestrator {
16
+ nonceStore: NonceStore;
17
+ permitVerifier: PermitVerifier;
18
+ onChainVerifier: OnChainVerifier;
19
+ baseContractClient?: BaseContractClient;
20
+ routerContractAddress?: string;
21
+ sessionWalletManager: SessionWalletManager;
22
+ feeCalculator: FeeCalculator;
23
+ feeLedger: FeeLedger;
24
+ constructor(params: {
25
+ nonceStore: NonceStore;
26
+ permitVerifier: PermitVerifier;
27
+ onChainVerifier: OnChainVerifier;
28
+ baseContractClient?: BaseContractClient;
29
+ routerContractAddress?: string;
30
+ sessionWalletManager: SessionWalletManager;
31
+ feeCalculator: FeeCalculator;
32
+ feeLedger: FeeLedger;
33
+ });
34
+ getLedger(): FeeLedger;
35
+ /**
36
+ * Generates a payment challenge for a paid MCP tool call.
37
+ * Calculates gross price, platform cut, developer payout, and creates a unique nonce.
38
+ */
39
+ generateChallenge(serverConfig: ServerPricingConfig, toolRule: ToolPricingRule, resolvedGrossMicro: bigint): {
40
+ challengeData: X402ChallengeData;
41
+ headers: X402ChallengeHeaders;
42
+ errorResponse: X402ErrorResponse;
43
+ };
44
+ /**
45
+ * Verifies an incoming payment proof and commits the settlement to the ledger.
46
+ * Validates toolName, serverId, and gross price bindings before settlement.
47
+ * Uses verify-first, consume-last ordering so transient errors do not burn nonces.
48
+ */
49
+ verifyAndSettle(proof: PaymentProof, nowSec?: number, expected?: {
50
+ serverId?: string;
51
+ toolName?: string;
52
+ minAmountMicro?: bigint;
53
+ }): Promise<{
54
+ success: boolean;
55
+ record?: TransactionRecord;
56
+ error?: string;
57
+ }>;
58
+ }
59
+ //# sourceMappingURL=payment-orchestrator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-orchestrator.d.ts","sourceRoot":"","sources":["../../src/settlement/payment-orchestrator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,YAAY,EACb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAIvD,qBAAa,mBAAmB;IACvB,UAAU,EAAE,UAAU,CAAC;IACvB,cAAc,EAAE,cAAc,CAAC;IAC/B,eAAe,EAAE,eAAe,CAAC;IACjC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,oBAAoB,EAAE,oBAAoB,CAAC;IAC3C,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,SAAS,CAAC;gBAEhB,MAAM,EAAE;QAClB,UAAU,EAAE,UAAU,CAAC;QACvB,cAAc,EAAE,cAAc,CAAC;QAC/B,eAAe,EAAE,eAAe,CAAC;QACjC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;QACxC,qBAAqB,CAAC,EAAE,MAAM,CAAC;QAC/B,oBAAoB,EAAE,oBAAoB,CAAC;QAC3C,aAAa,EAAE,aAAa,CAAC;QAC7B,SAAS,EAAE,SAAS,CAAC;KACtB;IAWM,SAAS,IAAI,SAAS;IAI7B;;;OAGG;IACI,iBAAiB,CACtB,YAAY,EAAE,mBAAmB,EACjC,QAAQ,EAAE,eAAe,EACzB,kBAAkB,EAAE,MAAM,GACzB;QAAE,aAAa,EAAE,iBAAiB,CAAC;QAAC,OAAO,EAAE,oBAAoB,CAAC;QAAC,aAAa,EAAE,iBAAiB,CAAA;KAAE;IAyHxG;;;;OAIG;IACU,eAAe,CAC1B,KAAK,EAAE,YAAY,EACnB,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE;QACT,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,GACA,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CA8I7E"}
@@ -0,0 +1,282 @@
1
+ /**
2
+ * ToolPay Payment Orchestrator
3
+ * Coordinates challenges, multi-scheme verification, replay defense, and financial ledger settlement.
4
+ */
5
+ import { getChainIdForNetwork } from '../utils/units.js';
6
+ import { randomUUID } from 'node:crypto';
7
+ export class PaymentOrchestrator {
8
+ nonceStore;
9
+ permitVerifier;
10
+ onChainVerifier;
11
+ baseContractClient;
12
+ routerContractAddress;
13
+ sessionWalletManager;
14
+ feeCalculator;
15
+ feeLedger;
16
+ constructor(params) {
17
+ this.nonceStore = params.nonceStore;
18
+ this.permitVerifier = params.permitVerifier;
19
+ this.onChainVerifier = params.onChainVerifier;
20
+ this.baseContractClient = params.baseContractClient;
21
+ this.routerContractAddress = params.routerContractAddress;
22
+ this.sessionWalletManager = params.sessionWalletManager;
23
+ this.feeCalculator = params.feeCalculator;
24
+ this.feeLedger = params.feeLedger;
25
+ }
26
+ getLedger() {
27
+ return this.feeLedger;
28
+ }
29
+ /**
30
+ * Generates a payment challenge for a paid MCP tool call.
31
+ * Calculates gross price, platform cut, developer payout, and creates a unique nonce.
32
+ */
33
+ generateChallenge(serverConfig, toolRule, resolvedGrossMicro) {
34
+ const feeConfig = serverConfig.platformFee;
35
+ const split = this.feeCalculator.calculateSplit(resolvedGrossMicro, serverConfig.payoutWallet, feeConfig);
36
+ const chainId = getChainIdForNetwork(serverConfig.network);
37
+ // If on Base and router address is configured, route payments through the ToolPayRouter contract
38
+ const recipientWallet = (serverConfig.network === 'base' || serverConfig.network === 'base-sepolia') && this.routerContractAddress
39
+ ? this.routerContractAddress
40
+ : serverConfig.payoutWallet;
41
+ const challengeData = this.nonceStore.createChallenge({
42
+ serverId: serverConfig.serverId,
43
+ toolName: toolRule.toolName,
44
+ priceUsd: split.grossUsd,
45
+ amountMicro: split.grossMicro,
46
+ currency: serverConfig.currency,
47
+ recipientWallet,
48
+ verifyingContract: (serverConfig.network === 'base' || serverConfig.network === 'base-sepolia') && this.routerContractAddress
49
+ ? this.routerContractAddress
50
+ : undefined,
51
+ developerWallet: serverConfig.payoutWallet,
52
+ platformTreasuryWallet: split.platformTreasuryWallet,
53
+ platformFeeBasisPoints: split.platformFeeBasisPoints,
54
+ platformCutMicro: split.platformFeeMicro,
55
+ developerCutMicro: split.developerPayoutMicro,
56
+ network: serverConfig.network,
57
+ chainId,
58
+ tokenAddress: (serverConfig.currency === 'USDC' && chainId === 8453)
59
+ ? '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'
60
+ : undefined,
61
+ });
62
+ const headers = {
63
+ 'X-Payment-Required': 'true',
64
+ 'X-Payment-Amount': challengeData.priceUsd,
65
+ 'X-Payment-Currency': challengeData.currency,
66
+ 'X-Payment-Recipient': challengeData.recipientWallet,
67
+ 'X-Payment-Network': challengeData.network,
68
+ 'X-Payment-Chain-Id': challengeData.chainId.toString(),
69
+ 'X-Payment-Nonce': challengeData.challengeNonce,
70
+ 'X-Payment-Expires': challengeData.expiresAt.toString(),
71
+ 'X-Payment-Platform-Fee-Bps': challengeData.platformFeeBasisPoints.toString(),
72
+ };
73
+ const isTestnet = challengeData.network === 'base-sepolia';
74
+ const fundingHint = isTestnet
75
+ ? ' Claim free testnet USDC at https://faucet.circle.com (select Base Sepolia).'
76
+ : ' To fund on Base Mainnet, send native USDC (0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) via Coinbase or https://bridge.base.org.';
77
+ const errorResponse = {
78
+ status: 402,
79
+ headers: headers,
80
+ body: {
81
+ error: {
82
+ code: 402,
83
+ message: `Payment Required: Tool "${toolRule.toolName}" costs ${challengeData.priceUsd} ${challengeData.currency} on Base (${challengeData.network}). ` +
84
+ `To authorize payments for this agent, set AGENT_PRIVATE_KEY=0x... in your project .env file (or run the MCP bridge with --key <private-key>).` +
85
+ fundingHint +
86
+ ` Once configured, your agent will automatically sign zero-friction micro-permits and execute paid tools seamlessly.`,
87
+ data: {
88
+ serverId: challengeData.serverId,
89
+ toolName: challengeData.toolName,
90
+ priceUsd: challengeData.priceUsd,
91
+ amountMicro: challengeData.amountMicro.toString(),
92
+ currency: challengeData.currency,
93
+ recipientWallet: challengeData.recipientWallet,
94
+ developerWallet: challengeData.developerWallet || challengeData.recipientWallet,
95
+ platformTreasuryWallet: challengeData.platformTreasuryWallet,
96
+ platformFeeBasisPoints: challengeData.platformFeeBasisPoints,
97
+ platformCutMicro: challengeData.platformCutMicro.toString(),
98
+ developerCutMicro: challengeData.developerCutMicro.toString(),
99
+ network: challengeData.network,
100
+ chainId: challengeData.chainId,
101
+ tokenAddress: challengeData.tokenAddress,
102
+ challengeNonce: challengeData.challengeNonce,
103
+ expiresAt: challengeData.expiresAt,
104
+ instructions: {
105
+ summary: `Tool "${toolRule.toolName}" is monetized and requires ${challengeData.priceUsd} ${challengeData.currency} on Base (${challengeData.network}).`,
106
+ steps: [
107
+ 'Set AGENT_PRIVATE_KEY=0x... in your project .env file (or pass --key <private-key> to the mcpaid bridge).',
108
+ isTestnet
109
+ ? 'Fund your agent wallet with free testnet USDC: https://faucet.circle.com (select Base Sepolia).'
110
+ : 'Fund your agent wallet with native USDC on Base (Chain ID 8453): withdraw directly from Coinbase (selecting Base network) or bridge at https://bridge.base.org.',
111
+ 'Re-run your query; the agent bridge will automatically sign zero-gas EIP-712 permits to authorize and execute the tool.',
112
+ ],
113
+ network: challengeData.network,
114
+ chainId: challengeData.chainId,
115
+ currency: challengeData.currency,
116
+ cost: `${challengeData.priceUsd} ${challengeData.currency}`,
117
+ fundingGuide: {
118
+ network: isTestnet ? 'Base Sepolia Testnet (84532)' : 'Base Mainnet (8453)',
119
+ usdcTokenAddress: isTestnet
120
+ ? '0x036CbD53842c5426634e7929541eC2318f3dCF7e'
121
+ : '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
122
+ methods: isTestnet
123
+ ? [
124
+ 'Claim 10 free testnet USDC at https://faucet.circle.com (select Base Sepolia).',
125
+ 'Optional testnet gas from Superchain Faucet: https://console.optimism.io/faucet.',
126
+ ]
127
+ : [
128
+ 'Coinbase Direct: Withdraw USDC directly to your agent wallet using the "Base" network (0 gas fee).',
129
+ 'Official Base Bridge: Bridge USDC or ETH from Ethereum Mainnet at https://bridge.base.org.',
130
+ 'DEX Swap: Swap ETH/tokens to USDC on Aerodrome or Uniswap on Base.',
131
+ ],
132
+ },
133
+ },
134
+ },
135
+ },
136
+ },
137
+ };
138
+ return { challengeData, headers, errorResponse };
139
+ }
140
+ /**
141
+ * Verifies an incoming payment proof and commits the settlement to the ledger.
142
+ * Validates toolName, serverId, and gross price bindings before settlement.
143
+ * Uses verify-first, consume-last ordering so transient errors do not burn nonces.
144
+ */
145
+ async verifyAndSettle(proof, nowSec, expected) {
146
+ let challenge;
147
+ // 1. Peek nonce from store without consuming it yet (anti-replay check)
148
+ try {
149
+ challenge = this.nonceStore.peekChallenge(proof.challengeNonce, nowSec);
150
+ }
151
+ catch (err) {
152
+ return { success: false, error: err.message };
153
+ }
154
+ // 1b. Bind payment proof to expected context (Critical 1)
155
+ if (expected?.serverId && challenge.serverId !== expected.serverId) {
156
+ return {
157
+ success: false,
158
+ error: `Server ID mismatch: challenge issued for server "${challenge.serverId}", but request is for "${expected.serverId}"`,
159
+ };
160
+ }
161
+ if (expected?.toolName && challenge.toolName !== expected.toolName) {
162
+ return {
163
+ success: false,
164
+ error: `Tool name mismatch: challenge issued for tool "${challenge.toolName}", but request is for "${expected.toolName}"`,
165
+ };
166
+ }
167
+ if (expected?.minAmountMicro !== undefined && challenge.amountMicro < expected.minAmountMicro) {
168
+ return {
169
+ success: false,
170
+ error: `Underpayment: challenge amount ${challenge.amountMicro} is less than required tool price ${expected.minAmountMicro}`,
171
+ };
172
+ }
173
+ // 2. Route verification by scheme
174
+ let settlementRef;
175
+ if (proof.scheme === 'eip712-permit') {
176
+ if (!proof.signature || proof.deadline === undefined) {
177
+ return { success: false, error: 'Missing EIP-712 signature or deadline in payment proof' };
178
+ }
179
+ try {
180
+ const verifyRes = await this.permitVerifier.verifyPermit({
181
+ signerWallet: proof.signerWallet,
182
+ signature: proof.signature,
183
+ challenge,
184
+ deadlineSec: proof.deadline,
185
+ amountMicro: proof.maxAmountMicro ?? challenge.amountMicro,
186
+ nowSec,
187
+ });
188
+ if (!verifyRes.valid) {
189
+ return { success: false, error: verifyRes.error };
190
+ }
191
+ settlementRef = proof.signature;
192
+ }
193
+ catch (err) {
194
+ return { success: false, error: `Permit verification error: ${err.message}` };
195
+ }
196
+ }
197
+ else if (proof.scheme === 'onchain-tx') {
198
+ if (!proof.txHash) {
199
+ return { success: false, error: 'Missing txHash in onchain-tx payment proof' };
200
+ }
201
+ try {
202
+ let verifyRes;
203
+ if (this.baseContractClient &&
204
+ this.routerContractAddress &&
205
+ challenge.recipientWallet.toLowerCase() === this.routerContractAddress.toLowerCase()) {
206
+ const contractRes = await this.baseContractClient.verifyContractPayment({
207
+ txHash: proof.txHash,
208
+ challenge,
209
+ signerWallet: proof.signerWallet,
210
+ });
211
+ verifyRes = { valid: contractRes.valid, error: contractRes.error };
212
+ }
213
+ else {
214
+ verifyRes = await this.onChainVerifier.verifyTransaction({
215
+ txHash: proof.txHash,
216
+ signerWallet: proof.signerWallet,
217
+ challenge,
218
+ nowSec,
219
+ });
220
+ }
221
+ if (!verifyRes.valid) {
222
+ return { success: false, error: verifyRes.error };
223
+ }
224
+ settlementRef = proof.txHash;
225
+ }
226
+ catch (err) {
227
+ return { success: false, error: `On-chain verification error: ${err.message}` };
228
+ }
229
+ }
230
+ else if (proof.scheme === 'session-wallet') {
231
+ if (!proof.sessionId) {
232
+ return { success: false, error: 'Missing sessionId in session-wallet payment proof' };
233
+ }
234
+ try {
235
+ const deductRes = this.sessionWalletManager.deductPayment({
236
+ sessionId: proof.sessionId,
237
+ agentWallet: proof.signerWallet,
238
+ amountMicro: challenge.amountMicro,
239
+ });
240
+ if (!deductRes.success) {
241
+ return { success: false, error: deductRes.error };
242
+ }
243
+ settlementRef = proof.sessionId;
244
+ }
245
+ catch (err) {
246
+ return { success: false, error: `Session deduction error: ${err.message}` };
247
+ }
248
+ }
249
+ else {
250
+ return { success: false, error: `Unsupported payment scheme: ${proof.scheme}` };
251
+ }
252
+ // 3. Atomically consume the challenge nonce ONLY after verification succeeds (Critical 5)
253
+ try {
254
+ this.nonceStore.consumeChallenge(proof.challengeNonce);
255
+ }
256
+ catch (err) {
257
+ return { success: false, error: err.message };
258
+ }
259
+ // 4. Commit transaction to financial ledger with exact platform cut
260
+ const record = this.feeLedger.recordTransaction({
261
+ transactionId: `tx_${randomUUID()}`,
262
+ serverId: challenge.serverId,
263
+ toolName: challenge.toolName,
264
+ agentWallet: proof.signerWallet,
265
+ developerWallet: challenge.developerWallet || challenge.recipientWallet,
266
+ platformTreasuryWallet: challenge.platformTreasuryWallet,
267
+ scheme: proof.scheme,
268
+ grossAmountMicro: challenge.amountMicro,
269
+ platformFeeMicro: challenge.platformCutMicro,
270
+ developerPayoutMicro: challenge.developerCutMicro,
271
+ platformFeeBasisPoints: challenge.platformFeeBasisPoints,
272
+ settlementRef,
273
+ idempotencyKey: proof.idempotencyKey,
274
+ timestamp: (nowSec ? nowSec * 1000 : undefined),
275
+ });
276
+ return {
277
+ success: true,
278
+ record,
279
+ };
280
+ }
281
+ }
282
+ //# sourceMappingURL=payment-orchestrator.js.map