Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,33 @@ describe("X402ActionProvider", () => {
expect(parsedResult.registeredServices).toBeDefined();
});

it("should not silently flip the HTTP method on 404", async () => {
mockFetch.mockResolvedValue(
createMockResponse({
status: 404,
data: { error: "not found" },
headers: { "content-type": "application/json" },
}),
);

const result = await provider.makeHttpRequest(makeMockWalletProvider("base-sepolia"), {
url: "https://api.example.com/items/123",
method: "GET",
headers: null,
queryParams: null,
body: JSON.stringify({ role: "admin" }),
});

// Exactly one request, with the method the caller chose. A silent
// GET->POST retry would turn an intended read into a write.
expect(mockFetch).toHaveBeenCalledTimes(1);
expect(mockFetch.mock.calls[0][1]?.method).toBe("GET");
const parsed = JSON.parse(result);
expect(parsed.status).toBe(404);
expect(parsed.method).toBe("GET");
expect(parsed.hint).toContain("explicitly");
});

it("should handle successful non-payment requests", async () => {
mockFetch.mockResolvedValue(
createMockResponse({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,24 +217,33 @@ If you receive a 402 Payment Required response, use retry_http_request_with_x402
}

const finalUrl = buildUrlWithParams(args.url, args.queryParams);
let method = args.method;
let canHaveBody = ["POST", "PUT", "PATCH"].includes(method);
const method = args.method;
const canHaveBody = ["POST", "PUT", "PATCH"].includes(method);

let response = await fetch(finalUrl, {
const response = await fetch(finalUrl, {
method,
headers: args.headers ?? undefined,
body: canHaveBody && args.body ? JSON.stringify(args.body) : undefined,
});

// Retry with other http method for 404 status code
// Never silently retry with a different HTTP method: flipping GET to
// POST turns an intended read into a possible write on services that
// map both methods to the same path. Surface the 404 with a hint and
// let the agent choose explicitly.
if (response.status === 404) {
method = method === "GET" ? "POST" : "GET";
canHaveBody = ["POST", "PUT", "PATCH"].includes(method);
response = await fetch(finalUrl, {
method,
headers: args.headers ?? undefined,
body: canHaveBody && args.body ? JSON.stringify(args.body) : undefined,
});
const data = await this.parseResponseData(response);
return JSON.stringify(
{
success: false,
url: finalUrl,
method,
status: 404,
data,
hint: `The service returned 404 for ${method}. If it expects a different method, call this action again with that method explicitly.`,
},
null,
2,
);
}

if (response.status !== 402) {
Expand Down
Loading