Skip to content

Commit 106e168

Browse files
committed
switch to coinbase
1 parent ae45964 commit 106e168

File tree

3 files changed

+140
-8
lines changed

3 files changed

+140
-8
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import {
2+
PaymentPayload,
3+
PaymentRequirements,
4+
SettleResponse,
5+
VerifyResponse,
6+
} from './x402-types';
7+
import { toJsonSafe } from './toJsonSafe';
8+
import logger, { logMetric } from '../../logger';
9+
import { env } from '../../env';
10+
import { generateCdpJwt } from './facilitatorService';
11+
12+
const DEFAULT_FACILITATOR_URL =
13+
env.COINBASE_FACILITATOR_BASE_URL || 'https://api.cdp.coinbase.com';
14+
const facilitatorTimeout = env.FACILITATOR_REQUEST_TIMEOUT || 20000;
15+
16+
type FacilitatorMethod = 'verify' | 'settle';
17+
18+
async function fetchWithTimeout(
19+
url: string,
20+
options: RequestInit,
21+
timeoutMs: number,
22+
method: FacilitatorMethod
23+
): Promise<Response> {
24+
const abortController = new AbortController();
25+
const timeoutId = setTimeout(() => {
26+
abortController.abort();
27+
logger.warn(
28+
`Coinbase facilitator ${method} request timed out after ${timeoutMs}ms`
29+
);
30+
}, Number(timeoutMs));
31+
32+
try {
33+
const res = await fetch(url, {
34+
...options,
35+
signal: abortController.signal,
36+
});
37+
clearTimeout(timeoutId);
38+
return res;
39+
} catch (error) {
40+
clearTimeout(timeoutId);
41+
logMetric('coinbase_facilitator_failure', 1, {
42+
method,
43+
error: error instanceof Error ? error.message : 'unknown',
44+
});
45+
throw error;
46+
}
47+
}
48+
49+
/**
50+
* Executes a facilitator request directly to Coinbase's facilitator API
51+
*
52+
* @param method - The facilitator method to call ('verify' or 'settle')
53+
* @param payload - The payment payload
54+
* @param paymentRequirements - The payment requirements
55+
* @returns A promise that resolves to the facilitator response
56+
* @throws Error if the request fails
57+
*/
58+
export async function coinbaseFacilitator<
59+
T extends VerifyResponse | SettleResponse,
60+
>(
61+
method: FacilitatorMethod,
62+
payload: PaymentPayload,
63+
paymentRequirements: PaymentRequirements
64+
): Promise<T> {
65+
if (!env.CDP_API_KEY_ID || !env.CDP_API_KEY_SECRET) {
66+
throw new Error(
67+
'CDP_API_KEY_ID and CDP_API_KEY_SECRET must be set to use Coinbase facilitator'
68+
);
69+
}
70+
71+
logMetric('coinbase_facilitator_attempt', 1, {
72+
method,
73+
});
74+
75+
const url = DEFAULT_FACILITATOR_URL;
76+
const requestPath = `/platform/v2/x402/${method}`;
77+
const jwt = await generateCdpJwt({
78+
requestMethod: 'POST',
79+
requestPath,
80+
requestHost: 'api.cdp.coinbase.com',
81+
});
82+
83+
const headers: Record<string, string> = {
84+
'Content-Type': 'application/json',
85+
Authorization: `Bearer ${jwt}`,
86+
};
87+
88+
const requestBody = {
89+
x402Version: 1,
90+
paymentPayload: toJsonSafe(payload),
91+
paymentRequirements: toJsonSafe(paymentRequirements),
92+
};
93+
94+
const res = await fetchWithTimeout(
95+
`${url}${requestPath}`,
96+
{
97+
method: 'POST',
98+
headers,
99+
body: JSON.stringify(requestBody),
100+
},
101+
facilitatorTimeout,
102+
method
103+
);
104+
105+
if (!res.ok) {
106+
const errorText = await res.text().catch(() => 'Unknown error');
107+
logger.error(`Coinbase facilitator ${method} failed`, {
108+
method,
109+
status: res.status,
110+
error: errorText,
111+
});
112+
logMetric('coinbase_facilitator_failure', 1, {
113+
method,
114+
status: res.status,
115+
});
116+
throw new Error(
117+
`Coinbase facilitator ${method} failed: ${res.status} ${errorText}`
118+
);
119+
}
120+
121+
const data = await res.json();
122+
logger.info(`Coinbase facilitator ${method} succeeded`, {
123+
method,
124+
});
125+
logMetric('coinbase_facilitator_success', 1, {
126+
method,
127+
});
128+
129+
return data as T;
130+
}

packages/app/server/src/services/facilitator/facilitatorService.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@ import { generateJwt } from '@coinbase/cdp-sdk/auth';
88
import { useFacilitator } from './useFacilitator';
99
import { env } from '../../env';
1010

11+
12+
13+
1114
interface GenerateCdpJwtInput {
1215
requestMethod: 'POST' | 'GET' | 'PUT' | 'DELETE';
1316
requestHost?: string;
1417
requestPath: string;
1518
expiresIn?: number;
1619
}
1720

18-
const generateCdpJwt = async ({
21+
export const generateCdpJwt = async ({
1922
requestMethod,
2023
requestPath,
2124
requestHost = 'api.cdp.coinbase.com',

packages/app/server/src/services/facilitator/useFacilitator.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ import {
44
SettleResponse,
55
VerifyResponse,
66
} from './x402-types';
7-
import { facilitatorProxy } from './facilitatorProxy';
7+
import { coinbaseFacilitator } from './coinbaseFacilitator';
88

99
/**
1010
* Creates a facilitator client for interacting with the X402 payment facilitator service
11+
* Uses Coinbase's facilitator API directly
1112
*
1213
* @returns An object containing verify and settle functions for interacting with the facilitator
1314
*/
1415
export function useFacilitator() {
1516
/**
16-
* Verifies a payment payload with the facilitator service
17-
* Automatically retries with fallover to backup facilitators on failure
17+
* Verifies a payment payload with the Coinbase facilitator service
1818
*
1919
* @param payload - The payment payload to verify
2020
* @param paymentRequirements - The payment requirements to verify against
@@ -24,16 +24,15 @@ export function useFacilitator() {
2424
payload: PaymentPayload,
2525
paymentRequirements: PaymentRequirements
2626
): Promise<VerifyResponse> {
27-
return facilitatorProxy<VerifyResponse>(
27+
return coinbaseFacilitator<VerifyResponse>(
2828
'verify',
2929
payload,
3030
paymentRequirements
3131
);
3232
}
3333

3434
/**
35-
* Settles a payment with the facilitator service
36-
* Automatically retries with fallover to backup facilitators on failure
35+
* Settles a payment with the Coinbase facilitator service
3736
*
3837
* @param payload - The payment payload to settle
3938
* @param paymentRequirements - The payment requirements for the settlement
@@ -43,7 +42,7 @@ export function useFacilitator() {
4342
payload: PaymentPayload,
4443
paymentRequirements: PaymentRequirements
4544
): Promise<SettleResponse> {
46-
return facilitatorProxy<SettleResponse>(
45+
return coinbaseFacilitator<SettleResponse>(
4746
'settle',
4847
payload,
4948
paymentRequirements

0 commit comments

Comments
 (0)