@emdash-cms/x402 패키지는 서버 렌더링되는 Astro 사이트에 x402 결제 프로토콜 지원을 추가합니다. 독립적으로 동작하는 Astro 통합이므로 결제를 강제하는 데 EmDash가 필요하지 않습니다. 사이트가 EmDash도 사용한다면 콘텐츠 필드로 항목마다 가격을 지정할 수 있습니다.
x402는 HTTP 기반의 결제 프로토콜입니다. 클라이언트가 결제 없이 유료 리소스를 요청하면 서버는 402 Payment Required와 기계가 읽을 수 있는 결제 안내를 응답합니다. x402를 이해하는 에이전트와 브라우저는 자동으로 결제를 완료하고 요청을 다시 시도할 수 있습니다.
적용 모드 선택
라우트에 대한 모든 요청이 유효한 결제를 제시해야 한다면 일반 적용 모드를 사용하세요. 이 모드는 Cloudflare 전용 요청 메타데이터에 의존하지 않습니다.
사이트가 Bot Management를 활성화한 Cloudflare Workers에서 실행되고, 점수가 낮은 요청에만 결제를 요구하려면 봇 전용 모드를 사용하세요. 패키지는 request.cf.botManagement.score를 읽으며, 이 값이 없으면 요청을 사람으로 간주하고 적용을 건너뜁니다. 봇 데이터가 없을 때 안전하게 실패(fail closed)해야 한다면 봇 전용 모드를 사용하지 마세요.
hasPayment()는 표시 전용인 세 번째 동작을 제공합니다. 요청에 결제 헤더가 있는지 알려 주지만 결제를 검증하거나 정산하지는 않습니다.
패키지 설치
사용 중인 패키지 매니저로 패키지를 설치합니다.
pnpm
pnpm add @emdash-cms/x402 npm
npm install @emdash-cms/x402 yarn
yarn add @emdash-cms/x402 통합 구성
함께 동작하는 지갑, 네트워크, facilitator를 선택하세요. 이 통합은 기본적으로 Ethereum Virtual Machine(EVM) 네트워크를 지원하지만, facilitator도 구성한 네트워크와 자산을 지원해야 합니다. 다음 예시는 Base 메인넷을 사용하고 모든 요청에 결제를 강제합니다.
import { defineConfig } from "astro/config";
import { x402 } from "@emdash-cms/x402";
export default defineConfig({
integrations: [
x402({
payTo: "0xYourWalletAddress",
network: "eip155:8453", // Base mainnet
defaultPrice: "$0.01",
}),
],
});
TypeScript가 Astro.locals.x402를 인식하도록 타입 참조를 추가합니다.
/// <reference types="@emdash-cms/x402/locals" />
라우트에서 결제 강제
통합은 Astro.locals.x402에 enforcer를 배치합니다. 페이지 프런트매터에서 enforce()를 호출하면 콘텐츠를 결제 뒤에 둘 수 있습니다.
---
const { x402 } = Astro.locals;
const result = await x402.enforce(Astro.request, {
price: "$0.05",
description: "Premium article",
});
// If the request has no valid payment, enforce() returns a 402 Response.
// Return it directly to send payment instructions to the client.
if (result instanceof Response) return result;
// Payment verified (or skipped in botOnly mode). Apply response headers
// so the client gets settlement proof.
x402.applyHeaders(result, Astro.response);
---
<article>
<h1>Premium content</h1>
</article>
enforce() 메서드는 다음 중 하나를 반환합니다.
Response(402) — 클라이언트가 결제해야 합니다. 그대로 반환하세요.EnforceResult— 요청을 계속 진행해야 합니다. 콘텐츠에 대해 결제가 이루어졌거나 적용을 건너뛰었습니다(botOnly 모드의 사람).
봇 전용 모드 활성화
통합 구성에서 botOnly를 활성화합니다.
x402({
payTo: "0xYourWalletAddress",
network: "eip155:8453",
defaultPrice: "$0.01",
botOnly: true,
botScoreThreshold: 30,
});
통합은 Cloudflare의 request.cf.botManagement.score를 읽어 요청을 분류합니다.
- 점수가 임계값 미만(기본값 30) -> 봇으로 간주, 결제 강제
- 점수가 임계값 이상 -> 사람으로 간주, 적용 건너뜀
- Bot Management 데이터 없음(로컬 개발, Cloudflare가 아닌 배포) -> 사람으로 간주
EnforceResult에는 skipped 플래그가 있어 “결제할 필요가 없었던 경우”와 “결제한 경우”를 구분할 수 있습니다.
---
const result = await x402.enforce(Astro.request, { price: "$0.01" });
if (result instanceof Response) return result;
x402.applyHeaders(result, Astro.response);
// result.paid — true if payment was verified
// result.skipped — true if enforcement was skipped (human in botOnly mode)
// result.payer — wallet address of payer (if paid)
---
EmDash에서 가격 읽기
EmDash를 사용할 때는 페이지별 가격 책정을 위해 컬렉션에 일반 number 필드를 추가하고, 요청 시점에 이를 읽습니다.
---
import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;
const { entry } = await getEmDashEntry("posts", slug);
if (!entry) return Astro.rewrite("/404");
const { x402 } = Astro.locals;
// Use the price from the CMS, falling back only when it is absent.
const result = await x402.enforce(Astro.request, {
price: entry.data.price ?? "$0.01",
description: entry.data.title,
});
if (result instanceof Response) return result;
x402.applyHeaders(result, Astro.response);
---
<article>
<h1>{entry.data.title}</h1>
</article>
강제하지 않고 결제 헤더 확인
hasPayment()를 사용하면 요청에 결제 헤더가 포함되어 있는지 검증이나 강제 없이 확인할 수 있습니다. 안내 문구나 로그인 메시지를 바꾸는 데는 쓸 수 있지만, 보호된 콘텐츠를 열어 주는 데 사용해서는 안 됩니다.
---
const { x402 } = Astro.locals;
const hasPaymentHeader = x402.hasPayment(Astro.request);
---
{hasPaymentHeader ? (
<p>Payment supplied. Verification is still required.</p>
) : (
<p>This page requires payment.</p>
)}
구성 참조
| 옵션 | 타입 | 기본값 | 설명 |
|---|---|---|---|
payTo | string | 필수 | 수신 지갑 주소 |
network | string | 필수 | CAIP-2 네트워크 식별자(예: eip155:8453) |
defaultPrice | Price | — | 기본 가격. 페이지별로 재정의 가능 |
facilitatorUrl | string | https://x402.org/facilitator | 결제 facilitator URL |
scheme | string | "exact" | 결제 스킴 |
maxTimeoutSeconds | number | 60 | 결제 서명의 최대 타임아웃 |
evm | boolean | true | Ethereum Virtual Machine 네트워크 지원 활성화 |
svm | boolean | false | Solana Virtual Machine 지원 활성화(@x402/svm 필요) |
botOnly | boolean | false | 봇에게만 결제 강제 |
botScoreThreshold | number | 30 | 봇 점수 임계값(1-99, 낮을수록 봇일 가능성이 높음) |
가격 형식
가격은 여러 형식으로 지정할 수 있습니다.
- 달러 문자열 —
"$0.10"($접두사는 제거되고 값은 그대로 전달됩니다) - 숫자 문자열 —
"0.10" - 숫자 —
0.10 - 객체 —
{ amount: "100000", asset: "0x...", extra: {} }. 자산과 금액을 명시적으로 지정할 때 사용합니다
네트워크 식별자
CAIP-2는 eip155:8453(Base 메인넷)처럼 블록체인 네트워크에 모호하지 않은 식별자를 부여합니다. facilitator가 공개한 식별자를 그대로 사용하세요. 패키지는 EVM 지원이 활성화되면 eip155:* 계열을, Solana Virtual Machine(SVM) 지원이 활성화되면 solana:* 계열을 받아들입니다. 다만 facilitator가 해당 계열의 모든 네트워크를 받아들인다는 보장은 없습니다.
단일 요청 재정의
특정 페이지에 대해 구성의 기본값을 재정의합니다.
await x402.enforce(Astro.request, {
price: "$0.25", // Override price
payTo: "0xDifferentWallet", // Override wallet
network: "eip155:1", // Override network
description: "Article: How x402 Works", // Resource description
mimeType: "text/html", // MIME type hint
});
Solana 지원 활성화
Solana는 선택 사항입니다. @x402/svm을 설치하고 구성에서 활성화합니다.
pnpm add @x402/svm
svm: true를 설정하고 facilitator가 지원하는 CAIP-2 형식의 Solana 식별자를 사용하세요. 사이트가 Solana 결제만 받는다면 EVM을 비활성화합니다. 예를 들어 @x402/svm 2.8이 사용하는 Solana 메인넷 식별자는 solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp입니다.
x402({
payTo: "YourSolanaAddress",
network: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
svm: true,
evm: false, // Disable EVM if only using Solana
});
요청 흐름
- 페이지가 실행되기 전에 통합이 결제 enforcer를
Astro.locals.x402에 배치합니다. enforce()가payment-signature헤더를 확인합니다.- 결제 헤더가 없으면
enforce()는402 Payment Required응답을 반환하며, 응답 본문과PAYMENT-REQUIRED헤더에 허용되는 결제 방식이 기술됩니다. - 일치하는 결제가 있으면 구성된 facilitator가 이를 검증하고 정산합니다.
enforce()는 결제자와 정산 결과를 반환합니다. 페이지 응답에 facilitator의PAYMENT-RESPONSE증빙이 포함되도록applyHeaders()를 호출하세요.