SDK 参考

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核心:OKXFacilitatorClientPaymentProcessorPaymentHooksAcceptOptionAssetRegistryOKXEvmSigner、模型层(PaymentRequirements / PaymentPayload / VerifyResponse / SettleResponse 等)。无 servlet 依赖。
com.okx:x402-java-jakartaJakarta EE 9+ / Spring Boot 3 适配:PaymentFilterjakarta.servlet.Filter)、PaymentInterceptor(Spring 6 HandlerInterceptor)。
com.okx:x402-java-javaxJava EE 8 / Spring Boot 2 适配:同上但基于 javax.servlet.* + Spring 5。

二选一安装 jakarta 或 javax —— 两者暴露相同包名会冲突。

xml
<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 主网)。

java
String network = "eip155:196";

Price / 资产#

Java SDK 没有独立的 Money / Price / AssetAmount 类型 —— RouteConfig.priceString,三种取值约定:

形式例子行为
USD 字符串"$0.01"自动按 AssetRegistry 换算为对应 token 原子单位
数字字符串"0.01"同 USD 字符串
原子单位字符串"10000"route.asset 必须显式给出)直接作为 token amount,不二次换算

如需多币种 / 多 scheme,使用 AcceptOption 列表(见 §3)。

ResourceInfo#

java
public class ResourceInfo {
    public String url;          // 资源 URL
    public String description;  // 描述
    public String mimeType;     // MIME
}

PaymentRequirements#

402 envelope 中 accepts[] 的每一项。

java
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 专属字段
}

extraexact(EIP-3009)下包含的字段:

key含义
nameEIP-712 domain name (e.g. USD₮0)
versionEIP-712 domain version (e.g. 1)
transferMethodeip3009

PaymentRequired(402 响应体)#

java
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 头携带)#

java
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 形态:

json
{
  "signature": "0x...",
  "authorization": {
    "from": "0xBuyerEOA",
    "to":   "0xSellerEOA",
    "value": "10000",
    "validAfter":  "0",
    "validBefore": "1700000000",
    "nonce": "0x..."
  }
}

aggr_deferredpayload map 形态:

json
{
  "signature": "0x...",
  "authorization": {
    "from": "0xAAWalletAddress",
    "to":   "0xSellerEOA",
    "value": "10000",
    "validAfter":  "0",
    "validBefore": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
    "nonce": "0x..."
  }
}

accepted.extra.sessionCertaggr_deferred 下携带 OKX Wallet TEE 颁发的 session 证书(Base64)。

VerifyResponse#

java
public class VerifyResponse {
    public boolean isValid;
    public String  invalidReason;
    public String  invalidMessage;
    public String  payer;
    public Map<String, Object> extensions;
}

SettleResponse#

java
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#

java
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)#

java
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#

java
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 函数式接口:

java
@FunctionalInterface
public interface DynamicPrice {
    String resolve(X402Request request);   // 返回 USD/原子单位字符串
}

AcceptOption#

多币种 / 多 scheme 时填 route.accepts,每个 AcceptOption 转成 402 envelope 的一项。

java
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();

轮询参数#

java
processor.pollInterval(Duration.ofSeconds(1));   // settle 状态轮询间隔,默认 1s
processor.pollDeadline(Duration.ofSeconds(5));   // settle 状态轮询超时,默认 5s

settleExecutor(开启 asyncSettle 时必填)#

java
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) -> AbortResultproceed() 继续;abort(reason) 跳过 verify、HTTP 402
onAfterVerify(PaymentPayload, PaymentRequirements, VerifyResponse) -> void仅观察,做 metrics / audit
onVerifyFailure(PaymentPayload, PaymentRequirements, Exception) -> RecoverResult<VerifyResponse>notRecovered() 抛出;recovered(VerifyResponse) 接管返回值
onBeforeSettle(PaymentPayload, PaymentRequirements) -> AbortResultonBeforeVerify
onAfterSettle(PaymentPayload, PaymentRequirements, SettleResponse) -> void仅观察
onSettleFailure(PaymentPayload, PaymentRequirements, Exception) -> RecoverResult<SettleResponse>onVerifyFailure
onAsyncSettleComplete(PaymentPayload, PaymentRequirements, SettleResponse, Throwable) -> void仅在 asyncSettle=true 时被回调
java
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 头之前触发。可用来跳过付费(白名单)或直接拒绝(限流)。

java
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 上做链上兜底确认。

java
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 证明头适用场景
PaymentInterceptorSpring MVC postHandle@Controller 返回视图名时正常;@RestController / @ResponseBody 路径会被静默丢弃(响应已提交)业务用 view template,或不需要证明头
PaymentFilterservlet Filter,包了 BufferedHttpServletResponse始终保留@RestController JSON API、需要证明头

实践指引:Spring REST API 默认用 PaymentFilter;用 PaymentInterceptor 仅当宿主已有 interceptor 链且确认不依赖证明头。

4. 中间件参考#

四种宿主框架的接入方式。所有方式底层共用 PaymentFilter.create(...) / PaymentInterceptor.create(...)

Spring Boot 3(Jakarta)#

通过 FilterRegistrationBean 注册,便于和 billing / auth 等 filter 排序。

java
@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"混排更直观。

java
@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)#

java
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:

java
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.fromBuyer EOA 地址
payload.authorization.validBeforenow + maxTimeoutSeconds
SettleResponse.transaction真实 tx hash
SettleResponse.status"success" / "pending" / "timeout"

Buyer 侧用 OKXEvmSigner(EIP-3009 + EIP-712 签名,web3j 实现):

java
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.fromAA 钱包地址(不是 session key 地址)
payload.authorization.validBeforeuint256.max(不过期)
accepted.extra.sessionCertOKX 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 资产显式注册:

java
AssetRegistry.register("eip155:196", AssetConfig.builder()
        .symbol("USDG")
        .contractAddress("0x4ae46a509f6b1d9056937ba4500cb143933d2dc8")
        .decimals(6)
        .eip712Name("USDG")
        .eip712Version("1")
        .transferMethod("eip3009")
        .build());

自定义资产必须PaymentFilter.create(...) / PaymentInterceptor.create(...) 之前注册。