Java SDK 参考#
适配 Java 17+,三件套发布到 Maven Central:com.okx:x402-java-core(servlet-agnostic 核心)、com.okx:x402-java-jakarta(Jakarta EE 9+ / Spring Boot 3)、com.okx:x402-java-javax(Java EE 8 / Spring Boot 2)。源码:github.com/okx/payments/tree/main/java。
1. 包#
| 包 | 说明 |
|---|---|
com.okx:x402-java-core | 核心:OKXFacilitatorClient、PaymentProcessor、PaymentHooks、AcceptOption、AssetRegistry、OKXEvmSigner、模型层(PaymentRequirements / PaymentPayload / VerifyResponse / SettleResponse 等)。无 servlet 依赖。 |
com.okx:x402-java-jakarta | Jakarta EE 9+ / Spring Boot 3 适配:PaymentFilter(jakarta.servlet.Filter)、PaymentInterceptor(Spring 6 HandlerInterceptor)。 |
com.okx:x402-java-javax | Java EE 8 / Spring Boot 2 适配:同上但基于 javax.servlet.* + Spring 5。 |
二选一安装 jakarta 或 javax —— 两者暴露相同包名会冲突。
<dependency>
<groupId>com.okx</groupId>
<artifactId>x402-java-jakarta</artifactId> <!-- or x402-java-javax -->
<version>1.0.0</version>
</dependency>
非 servlet 框架(Vert.x / Play / Micronaut Netty)仅依赖 x402-java-core 并实现 X402Request / X402Response SPI(约 50 行)。
2. 核心类型#
Network#
CAIP-2 字符串。当前仅支持 eip155:196(X Layer 主网)。
String network = "eip155:196";
Price / 资产#
Java SDK 没有独立的 Money / Price / AssetAmount 类型 —— RouteConfig.price 是 String,三种取值约定:
| 形式 | 例子 | 行为 |
|---|---|---|
| USD 字符串 | "$0.01" | 自动按 AssetRegistry 换算为对应 token 原子单位 |
| 数字字符串 | "0.01" | 同 USD 字符串 |
| 原子单位字符串 | "10000"(route.asset 必须显式给出) | 直接作为 token amount,不二次换算 |
如需多币种 / 多 scheme,使用 AcceptOption 列表(见 §3)。
ResourceInfo#
public class ResourceInfo {
public String url; // 资源 URL
public String description; // 描述
public String mimeType; // MIME
}
PaymentRequirements#
402 envelope 中 accepts[] 的每一项。
public class PaymentRequirements {
public String scheme; // "exact" | "aggr_deferred"
public String network; // "eip155:196"
public String amount; // 原子单位字符串
public String payTo; // 收款 EOA
public int maxTimeoutSeconds; // 签名有效期
public String asset; // token 合约地址
public Map<String, Object> extra; // scheme 专属字段
}
extra 在 exact(EIP-3009)下包含的字段:
| key | 含义 |
|---|---|
name | EIP-712 domain name (e.g. USD₮0) |
version | EIP-712 domain version (e.g. 1) |
transferMethod | eip3009 |
PaymentRequired(402 响应体)#
public class PaymentRequired {
public int x402Version = 2;
public String error;
public ResourceInfo resource;
public List<PaymentRequirements> accepts;
public Map<String, Object> extensions;
}
PaymentPayload(PAYMENT-SIGNATURE 头携带)#
public class PaymentPayload {
public int x402Version = 2;
public ResourceInfo resource;
public PaymentRequirements accepted; // 选中的 accepts[i]
public Map<String, Object> payload; // scheme 专属签名载荷
public Map<String, Object> extensions;
public String toHeader(); // base64(JSON)
public static PaymentPayload fromHeader(String); // 反向解码
}
exact(EIP-3009)的 payload map 形态:
{
"signature": "0x...",
"authorization": {
"from": "0xBuyerEOA",
"to": "0xSellerEOA",
"value": "10000",
"validAfter": "0",
"validBefore": "1700000000",
"nonce": "0x..."
}
}
aggr_deferred 的 payload map 形态:
{
"signature": "0x...",
"authorization": {
"from": "0xAAWalletAddress",
"to": "0xSellerEOA",
"value": "10000",
"validAfter": "0",
"validBefore": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
"nonce": "0x..."
}
}
accepted.extra.sessionCert 在 aggr_deferred 下携带 OKX Wallet TEE 颁发的 session 证书(Base64)。
VerifyResponse#
public class VerifyResponse {
public boolean isValid;
public String invalidReason;
public String invalidMessage;
public String payer;
public Map<String, Object> extensions;
}
SettleResponse#
public class SettleResponse {
public boolean success;
public String errorReason;
public String errorMessage;
public String payer;
public String transaction; // exact: tx hash;aggr_deferred: ""
public String network;
public String amount; // upto scheme 实际结算金额
public String status; // "pending" | "success" | "timeout"
public Map<String, Object> extensions;
}
SupportedKind / SupportedResponse#
public class SupportedKind {
public int x402Version = 2;
public String scheme;
public String network;
public Map<String, Object> extra;
}
public class SupportedResponse {
public List<SupportedKind> kinds;
public List<String> extensions;
public Map<String, List<String>> signers;
}
3. 服务端 API(PaymentInterceptor / PaymentFilter)#
服务端入口是 PaymentInterceptor(Spring MVC HandlerInterceptor 适配)或 PaymentFilter(servlet Filter 适配)。两者底层都驱动同一个 PaymentProcessor(servlet-agnostic 编排器,负责 verify → 业务 handler → settle 全链路),通过 interceptor.processor() / filter.processor() 取到 PaymentProcessor 来挂钩子、注入 settleExecutor。
构造(推荐 —— Spring Boot 3 / Jakarta)#
import com.okx.x402.server.PaymentInterceptor;
import com.okx.x402.server.PaymentProcessor;
PaymentInterceptor interceptor = PaymentInterceptor.create(
facilitator, // FacilitatorClient
Map.of("GET /api/data", route)); // routes
// 取底层 processor 配置钩子 / 线程池
interceptor.processor()
.settleExecutor(settlePool)
.onAfterSettle((p, r, resp) -> auditLog.write(resp));
备选:
PaymentFilter.create(facilitator, routes)。当业务路由是@RestController/@ResponseBody且需要PAYMENT-RESPONSE证明头时必须用PaymentFilter(详见下文 PaymentInterceptor 注意事项)。
低阶 API:直接 new PaymentProcessor(facilitator, routes) 仅用于非 servlet 框架(Vert.x / Play / Netty 等,见 §4)。
RouteConfig#
public static class RouteConfig {
public String scheme = "exact"; // "exact" | "aggr_deferred"
public String network; // REQUIRED: "eip155:196"
public String payTo; // REQUIRED: 收款 EOA
public String price; // "$0.01" 或 "10000"
public String asset; // 留空走 AssetRegistry 默认 (USDT0)
public int maxTimeoutSeconds = 86400; // 签名有效期,默认 1 天
public DynamicPrice priceFunction; // 动态定价(每请求计算)
public List<AcceptOption> accepts; // 多币种 / 多 scheme 时用,覆盖 scheme/price/asset
public boolean syncSettle; // 等链上确认再返回
public boolean asyncSettle; // 后台 settle,要求注入 settleExecutor
}
DynamicPrice 函数式接口:
@FunctionalInterface
public interface DynamicPrice {
String resolve(X402Request request); // 返回 USD/原子单位字符串
}
AcceptOption#
多币种 / 多 scheme 时填 route.accepts,每个 AcceptOption 转成 402 envelope 的一项。
public class AcceptOption {
public String scheme;
public String network; // 留空继承 route.network
public String payTo; // 留空继承 route.payTo
public String price;
public DynamicPrice priceFunction;
public String asset; // 留空走 AssetRegistry 默认
public int maxTimeoutSeconds;
public Map<String, Object> extra;
public static Builder builder(); // 链式
}
// 示例
AcceptOption.builder()
.scheme("exact").price("$0.01")
.asset("0x4ae46a509f6b1d9056937ba4500cb143933d2dc8") // USDG
.build();
轮询参数#
processor.pollInterval(Duration.ofSeconds(1)); // settle 状态轮询间隔,默认 1s
processor.pollDeadline(Duration.ofSeconds(5)); // settle 状态轮询超时,默认 5s
settleExecutor(开启 asyncSettle 时必填)#
ExecutorService settlePool = Executors.newFixedThreadPool(16, r -> {
Thread t = new Thread(r, "x402-settle"); t.setDaemon(true); return t;
});
processor.settleExecutor(settlePool);
未注入而 route.asyncSettle = true → 运行时抛 IllegalStateException。SDK 不偷偷创建后台线程。
服务端生命周期钩子#
钩子结果类型在 com.okx.x402.server.PaymentHooks 内部类下:PaymentHooks.AbortResult / PaymentHooks.RecoverResult<T> / PaymentHooks.ProtectedRequestResult / PaymentHooks.SettlementTimeoutResult。下面示例假设已 import static com.okx.x402.server.PaymentHooks.*;。
| 钩子 | 签名 | 返回值含义 |
|---|---|---|
onBeforeVerify | (PaymentPayload, PaymentRequirements) -> AbortResult | proceed() 继续;abort(reason) 跳过 verify、HTTP 402 |
onAfterVerify | (PaymentPayload, PaymentRequirements, VerifyResponse) -> void | 仅观察,做 metrics / audit |
onVerifyFailure | (PaymentPayload, PaymentRequirements, Exception) -> RecoverResult<VerifyResponse> | notRecovered() 抛出;recovered(VerifyResponse) 接管返回值 |
onBeforeSettle | (PaymentPayload, PaymentRequirements) -> AbortResult | 同 onBeforeVerify |
onAfterSettle | (PaymentPayload, PaymentRequirements, SettleResponse) -> void | 仅观察 |
onSettleFailure | (PaymentPayload, PaymentRequirements, Exception) -> RecoverResult<SettleResponse> | 同 onVerifyFailure |
onAsyncSettleComplete | (PaymentPayload, PaymentRequirements, SettleResponse, Throwable) -> void | 仅在 asyncSettle=true 时被回调 |
processor
.onBeforeVerify((p, r) -> AbortResult.proceed())
.onAfterVerify((p, r, resp) -> metrics.verifyOk())
.onVerifyFailure((p, r, e) -> RecoverResult.<VerifyResponse>notRecovered())
.onBeforeSettle((p, r) -> AbortResult.proceed())
.onAfterSettle((p, r, resp) -> auditLog.write(resp))
.onSettleFailure((p, r, e) -> RecoverResult.<SettleResponse>notRecovered());
HTTP 层钩子:onProtectedRequest(hook)#
在路由匹配后、读 payment 头之前触发。可用来跳过付费(白名单)或直接拒绝(限流)。
import static com.okx.x402.server.PaymentHooks.ProtectedRequestResult;
processor.onProtectedRequest((request, routeConfig) -> {
if ("internal".equals(request.getHeader("x-api-key"))) {
return ProtectedRequestResult.grantAccess(); // 跳过付款,直接走业务
}
if (rateLimiter.isThrottled(request)) {
return ProtectedRequestResult.abort("rate_limited"); // HTTP 403, {"error":"rate_limited"}
}
return ProtectedRequestResult.proceed(); // 正常付费流程
});
多个钩子按注册顺序执行,第一个返回 grantAccess() / abort(...) 的胜出。
兜底钩子:onSettlementTimeout(hook)#
facilitator settle-status 轮询超过 pollDeadline 还没拿到终态时触发(单钩子:后注册覆盖先注册)。可在自有 RPC 上做链上兜底确认。
import static com.okx.x402.server.PaymentHooks.SettlementTimeoutResult;
processor.onSettlementTimeout((txHash, network) -> {
TransactionReceipt r = web3j.ethGetTransactionReceipt(txHash)
.send().getTransactionReceipt().orElse(null);
return (r != null && r.isStatusOK())
? SettlementTimeoutResult.confirmed() // 链上已确认 → 当 success
: SettlementTimeoutResult.notConfirmed(); // 走原超时 402 流程
});
PaymentInterceptor vs PaymentFilter#
两者签名等价(都是 create(facilitator, routes) + .processor() 取底层 PaymentProcessor),路由 key 格式都是 "METHOD /path",但响应处理时机不同:
| 适配器 | 时机 | PAYMENT-RESPONSE 证明头 | 适用场景 |
|---|---|---|---|
PaymentInterceptor | Spring MVC postHandle | @Controller 返回视图名时正常;@RestController / @ResponseBody 路径会被静默丢弃(响应已提交) | 业务用 view template,或不需要证明头 |
PaymentFilter | servlet Filter,包了 BufferedHttpServletResponse | 始终保留 | @RestController JSON API、需要证明头 |
实践指引:Spring REST API 默认用
PaymentFilter;用PaymentInterceptor仅当宿主已有 interceptor 链且确认不依赖证明头。
4. 中间件参考#
四种宿主框架的接入方式。所有方式底层共用 PaymentFilter.create(...) / PaymentInterceptor.create(...)。
Spring Boot 3(Jakarta)#
通过 FilterRegistrationBean 注册,便于和 billing / auth 等 filter 排序。
@Bean
FilterRegistrationBean<PaymentFilter> x402Filter(OKXFacilitatorClient facilitator) {
PaymentProcessor.RouteConfig route = new PaymentProcessor.RouteConfig();
route.network = "eip155:196";
route.payTo = System.getenv("PAY_TO_ADDRESS");
route.price = "$0.01";
FilterRegistrationBean<PaymentFilter> reg = new FilterRegistrationBean<>(
PaymentFilter.create(facilitator, Map.of("GET /api/data", route)));
reg.addUrlPatterns("/api/*");
reg.setOrder(20); // billing filter 在 10
return reg;
}
Spring Boot 2(Javax)#
源码完全相同,把 dependency 换成 com.okx:x402-java-javax。包名 com.okx.x402.server.PaymentFilter 不变。
Spring MVC HandlerInterceptor#
宿主已用 interceptor 链时优先使用 —— InterceptorRegistry.order() 比"filter + interceptor"混排更直观。
@Configuration
class X402Config implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry r) {
r.addInterceptor(billingInterceptor).order(10);
r.addInterceptor(PaymentInterceptor.create(facilitator, routes))
.order(20)
.addPathPatterns("/api/**");
}
}
⚠ @RestController / @ResponseBody 路径会丢失 PAYMENT-RESPONSE 证明头(详见 §3「PaymentInterceptor vs PaymentFilter」对照表);返回视图名的 @Controller、以及尚未提交响应的异步 / 流式控制器不受影响。
裸 Servlet(Jetty / Tomcat)#
public class App implements ServletContextInitializer {
@Override
public void onStartup(ServletContext ctx) {
ctx.addFilter("x402", PaymentFilter.create(facilitator, routes))
.addMappingForUrlPatterns(null, false, "/api/*");
}
}
嵌入式 Jetty 直接 ServletContextHandler.addFilter(...),Tomcat 用 Context.addFilterDef + addFilterMap。
非 Servlet(Vert.x / Play / Netty)#
仅依赖 x402-java-core,实现两条 SPI:
class VertxX402Request implements X402Request { /* ~25 lines */ }
class VertxX402Response implements X402Response { /* ~25 lines */ }
PaymentProcessor processor = new PaymentProcessor(facilitator, routes);
// preHandle 返回 null = 响应已写入(402 / 500),调用方应短路
// 返回非 null 但 !isVerified() = 不是付费路由(PASS_THROUGH),落到业务 handler
PaymentProcessor.VerifyResult vr = processor.preHandle(xReq, xRes);
if (vr == null) return; // 已写 402 / 500
// ... 业务 handler ...
if (vr.isVerified()) {
processor.postHandle(vr, xReq, xRes); // 触发 settle + 写 PAYMENT-RESPONSE
}
jakarta 适配总长不到 100 行,可作为参考实现。
5. 机制类型(EVM Schemes)#
Java SDK 中 scheme 是字符串,没有独立的 ExactEvmScheme / AggrDeferredEvmScheme 类。所有 scheme 行为通过 RouteConfig.scheme + facilitator 端的实现共同决定。
exact(即时单次结算)#
EOA 私钥签 EIP-3009 TransferWithAuthorization,facilitator 立刻上链。
| 字段 | 值 |
|---|---|
RouteConfig.scheme | "exact" |
payload.authorization.from | Buyer EOA 地址 |
payload.authorization.validBefore | now + maxTimeoutSeconds |
SettleResponse.transaction | 真实 tx hash |
SettleResponse.status | "success" / "pending" / "timeout" |
Buyer 侧用 OKXEvmSigner(EIP-3009 + EIP-712 签名,web3j 实现):
OKXEvmSigner signer = new OKXEvmSigner(System.getenv("PRIVATE_KEY"));
OKXHttpClient client = new OKXHttpClient(signer, "eip155:196");
HttpResponse<String> resp = client.get(URI.create("https://seller/api/data"));
// SDK 自动完成 402 → 签名 → 重放 → 200
aggr_deferred(批量延迟结算)#
Buyer 用 session 私钥 签名(不是 EOA),OKX Facilitator TEE 把 N 笔压成 1 笔上链,适合 AI Agent 高频微支付。
| 字段 | 值 |
|---|---|
RouteConfig.scheme | "aggr_deferred" |
payload.authorization.from | AA 钱包地址(不是 session key 地址) |
payload.authorization.validBefore | uint256.max(不过期) |
accepted.extra.sessionCert | OKX Wallet TEE 颁发的 Base64 session 证书 |
SettleResponse.transaction | ""(空字符串,TEE 异步合并上链) |
SettleResponse.status | "success"(表示进入批次) |
Seller 侧:与 exact 完全一致,仅 route.scheme = "aggr_deferred"。
Buyer 侧:OKXEvmSigner 仅支持 EOA 私钥,不直接支持 aggr_deferred。需对接 OKX Wallet 团队拿到一个实现 EvmSigner 接口的 session signer。
资产配置(AssetRegistry / AssetConfig)#
X Layer USDT0 默认预注册(0x779ded0c9e1022225f8e0630b35a9b54be713736,6 decimals,EIP-712 name USD₮0 U+20AE)。其它 EIP-3009 资产显式注册:
AssetRegistry.register("eip155:196", AssetConfig.builder()
.symbol("USDG")
.contractAddress("0x4ae46a509f6b1d9056937ba4500cb143933d2dc8")
.decimals(6)
.eip712Name("USDG")
.eip712Version("1")
.transferMethod("eip3009")
.build());
自定义资产必须在
PaymentFilter.create(...)/PaymentInterceptor.create(...)之前注册。