Skip to content
Merged

prod #704

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
2 changes: 1 addition & 1 deletion packages/app/control/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
"mermaid": "^11.12.0",
"motion": "^12.23.12",
"nanoid": "^5.1.5",
"next": "15.5.2",
"next": "15.5.9",
"next-auth": "5.0.0-beta.29",
"next-path-matcher": "^1.0.2",
"next-themes": "^0.4.6",
Expand Down
1 change: 1 addition & 0 deletions packages/app/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"google-auth-library": "^10.3.0",
"jose": "^6.0.11",
"multer": "^2.0.2",
"next": "^15.5.9",
"openai": "^6.2.0",
"prisma": "6.16.0",
"typescript": "^5.3.3",
Expand Down
130 changes: 130 additions & 0 deletions packages/app/server/src/services/facilitator/coinbaseFacilitator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import {
PaymentPayload,
PaymentRequirements,
SettleResponse,
VerifyResponse,
} from './x402-types';
import { toJsonSafe } from './toJsonSafe';
import logger, { logMetric } from '../../logger';
import { env } from '../../env';
import { generateCdpJwt } from './facilitatorService';

const DEFAULT_FACILITATOR_URL =
env.COINBASE_FACILITATOR_BASE_URL || 'https://api.cdp.coinbase.com';
const facilitatorTimeout = env.FACILITATOR_REQUEST_TIMEOUT || 20000;

type FacilitatorMethod = 'verify' | 'settle';

async function fetchWithTimeout(
url: string,
options: RequestInit,
timeoutMs: number,
method: FacilitatorMethod
): Promise<Response> {
const abortController = new AbortController();
const timeoutId = setTimeout(() => {
abortController.abort();
logger.warn(
`Coinbase facilitator ${method} request timed out after ${timeoutMs}ms`
);
}, Number(timeoutMs));

try {
const res = await fetch(url, {
...options,
signal: abortController.signal,
});
clearTimeout(timeoutId);
return res;
} catch (error) {
clearTimeout(timeoutId);
logMetric('coinbase_facilitator_failure', 1, {
method,
error: error instanceof Error ? error.message : 'unknown',
});
throw error;
}
}

/**
* Executes a facilitator request directly to Coinbase's facilitator API
*
* @param method - The facilitator method to call ('verify' or 'settle')
* @param payload - The payment payload
* @param paymentRequirements - The payment requirements
* @returns A promise that resolves to the facilitator response
* @throws Error if the request fails
*/
export async function coinbaseFacilitator<
T extends VerifyResponse | SettleResponse,
>(
method: FacilitatorMethod,
payload: PaymentPayload,
paymentRequirements: PaymentRequirements
): Promise<T> {
if (!env.CDP_API_KEY_ID || !env.CDP_API_KEY_SECRET) {
throw new Error(
'CDP_API_KEY_ID and CDP_API_KEY_SECRET must be set to use Coinbase facilitator'
);
}

logMetric('coinbase_facilitator_attempt', 1, {
method,
});

const url = DEFAULT_FACILITATOR_URL;
const requestPath = `/platform/v2/x402/${method}`;
const jwt = await generateCdpJwt({
requestMethod: 'POST',
requestPath,
requestHost: 'api.cdp.coinbase.com',
});

const headers: Record<string, string> = {
'Content-Type': 'application/json',
Authorization: `Bearer ${jwt}`,
};

const requestBody = {
x402Version: 1,
paymentPayload: toJsonSafe(payload),
paymentRequirements: toJsonSafe(paymentRequirements),
};

const res = await fetchWithTimeout(
`${url}${requestPath}`,
{
method: 'POST',
headers,
body: JSON.stringify(requestBody),
},
facilitatorTimeout,
method
);

if (!res.ok) {
const errorText = await res.text().catch(() => 'Unknown error');
logger.error(`Coinbase facilitator ${method} failed`, {
method,
status: res.status,
error: errorText,
});
logMetric('coinbase_facilitator_failure', 1, {
method,
status: res.status,
});
throw new Error(
`Coinbase facilitator ${method} failed: ${res.status} ${errorText}`
);
}

const data = await res.json();
logger.info(`Coinbase facilitator ${method} succeeded`, {
method,
});
logMetric('coinbase_facilitator_success', 1, {
method,
});

return data as T;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import { generateJwt } from '@coinbase/cdp-sdk/auth';
import { useFacilitator } from './useFacilitator';
import { env } from '../../env';




interface GenerateCdpJwtInput {
requestMethod: 'POST' | 'GET' | 'PUT' | 'DELETE';
requestHost?: string;
requestPath: string;
expiresIn?: number;
}

const generateCdpJwt = async ({
export const generateCdpJwt = async ({
requestMethod,
requestPath,
requestHost = 'api.cdp.coinbase.com',
Expand Down
13 changes: 6 additions & 7 deletions packages/app/server/src/services/facilitator/useFacilitator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ import {
SettleResponse,
VerifyResponse,
} from './x402-types';
import { facilitatorProxy } from './facilitatorProxy';
import { coinbaseFacilitator } from './coinbaseFacilitator';

/**
* Creates a facilitator client for interacting with the X402 payment facilitator service
* Uses Coinbase's facilitator API directly
*
* @returns An object containing verify and settle functions for interacting with the facilitator
*/
export function useFacilitator() {
/**
* Verifies a payment payload with the facilitator service
* Automatically retries with fallover to backup facilitators on failure
* Verifies a payment payload with the Coinbase facilitator service
*
* @param payload - The payment payload to verify
* @param paymentRequirements - The payment requirements to verify against
Expand All @@ -24,16 +24,15 @@ export function useFacilitator() {
payload: PaymentPayload,
paymentRequirements: PaymentRequirements
): Promise<VerifyResponse> {
return facilitatorProxy<VerifyResponse>(
return coinbaseFacilitator<VerifyResponse>(
'verify',
payload,
paymentRequirements
);
}

/**
* Settles a payment with the facilitator service
* Automatically retries with fallover to backup facilitators on failure
* Settles a payment with the Coinbase facilitator service
*
* @param payload - The payment payload to settle
* @param paymentRequirements - The payment requirements for the settlement
Expand All @@ -43,7 +42,7 @@ export function useFacilitator() {
payload: PaymentPayload,
paymentRequirements: PaymentRequirements
): Promise<SettleResponse> {
return facilitatorProxy<SettleResponse>(
return coinbaseFacilitator<SettleResponse>(
'settle',
payload,
paymentRequirements
Expand Down
4 changes: 2 additions & 2 deletions packages/sdk/component-registry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.487.0",
"next": "15.1.4",
"next": "15.1.11",
"next-themes": "^0.4.6",
"react": "19.1.0",
"react-dom": "19.1.0",
Expand All @@ -39,7 +39,7 @@
"@types/react": "19.1.2",
"@types/react-dom": "19.1.2",
"eslint": "^9.32.0",
"eslint-config-next": "^15.1.4",
"eslint-config-next": "^15.1.11",
"tailwindcss": "^4.1.11",
"typescript": "^5.9.2"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/examples/next-402-chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"dotenv": "^17.2.3",
"embla-carousel-react": "^8.6.0",
"lucide-react": "^0.542.0",
"next": "15.5.2",
"next": "15.5.9",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-syntax-highlighter": "^15.6.6",
Expand Down
4 changes: 2 additions & 2 deletions packages/sdk/examples/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"@ai-sdk/react": "^2.0.47",
"@merit-systems/echo-next-sdk": "workspace:*",
"ai": "^5.0.47",
"next": "15.4.7",
"next": "15.4.10",
"react": "19.1.0",
"react-dom": "19.1.0"
},
Expand All @@ -23,7 +23,7 @@
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.4.7",
"eslint-config-next": "15.4.10",
"tailwindcss": "^4",
"typescript": "^5"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"@typescript-eslint/eslint-plugin": "^8.34.1",
"@typescript-eslint/parser": "^8.34.1",
"eslint": "^9.29.0",
"next": "^15.1.4",
"next": "^15.1.11",
"tsup": "^8.5.0",
"typescript": "^5.8.3",
"vitest": "^3.2.3",
Expand Down
8 changes: 8 additions & 0 deletions packages/sdk/ts/src/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ export type CreateStripePaymentLinkBody = {
successUrl?: string | undefined;
};

export type GetUserReferralResponse = { success: boolean; message: string };

export type GetUserReferralQuery = { echoAppId: string };

export type CreateUserReferralResponse = { success: boolean; message: string };

export type CreateUserReferralBody = { echoAppId: string; code: string };
Expand Down Expand Up @@ -210,6 +214,10 @@ export type ApiRoutes = {
response: CreateStripePaymentLinkResponse;
body: CreateStripePaymentLinkBody;
};
'GET /user/referral': {
response: GetUserReferralResponse;
query: GetUserReferralQuery;
};
'POST /user/referral': {
response: CreateUserReferralResponse;
body: CreateUserReferralBody;
Expand Down
Loading
Loading