Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/atxp-server/src/atxpContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ export function setDetectedCredential(credential: DetectedCredential): void {
}
}

/**
* Clear the payment credential from the ATXP context after successful settlement.
* Prevents a second requirePayment() call in the same request from trying to
* settle the same already-consumed credential.
*/
export function clearDetectedCredential(): void {
const context = contextStorage.getStore();
if (context) {
context.detectedCredential = undefined;
}
}

/**
* Get the pending payment challenge (set by omniChallengeMcpError).
* Used by atxpExpress to rewrite wrapped tool errors into JSON-RPC errors.
Expand Down
5 changes: 4 additions & 1 deletion packages/atxp-server/src/requirePayment.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { RequirePaymentConfig, extractNetworkFromAccountId, extractAddressFromAccountId, Network, AuthorizationServerUrl } from "@atxp/common";
import { McpError } from "@modelcontextprotocol/sdk/types.js";
import { BigNumber } from "bignumber.js";
import { getATXPConfig, atxpAccountId, atxpToken, getDetectedCredential, setPendingPaymentChallenge } from "./atxpContext.js";
import { getATXPConfig, atxpAccountId, atxpToken, getDetectedCredential, clearDetectedCredential, setPendingPaymentChallenge } from "./atxpContext.js";
import { buildPaymentOptions, omniChallengeMcpError } from "./omniChallenge.js";
import { getATXPResource } from "./atxpContext.js";
import { ProtocolSettlement, type SettlementContext } from "./protocol.js";
Expand Down Expand Up @@ -50,6 +50,9 @@ export async function requirePayment(paymentConfig: RequirePaymentConfig): Promi
const detectedCredential = getDetectedCredential();
if (detectedCredential) {
await settleDetectedCredential(config, detectedCredential, charge, destinationAccountId, paymentAmount);
// Clear the credential so a second requirePayment() call in the same request
// (e.g., image server's post-generation charge) doesn't try to re-settle it.
clearDetectedCredential();
// After settlement, the ledger should be credited. Fall through to charge below.
}

Expand Down
Loading