Skip to content

Commit 96404da

Browse files
committed
docs(java): add solana x402 payment flow example
1 parent 7ed8a11 commit 96404da

File tree

1 file changed

+186
-0
lines changed

1 file changed

+186
-0
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package com.coinbase.cdp.examples.solana;
2+
3+
import com.coinbase.cdp.CdpClient;
4+
import com.coinbase.cdp.examples.utils.EnvLoader;
5+
import com.coinbase.cdp.openapi.api.X402FacilitatorApi;
6+
import com.coinbase.cdp.openapi.model.CreateSolanaAccountRequest;
7+
import com.coinbase.cdp.openapi.model.InlineObject;
8+
import com.coinbase.cdp.openapi.model.InlineObject2;
9+
import com.coinbase.cdp.openapi.model.SignSolanaTransaction200Response;
10+
import com.coinbase.cdp.openapi.model.SignSolanaTransactionRequest;
11+
import com.coinbase.cdp.openapi.model.SolanaAccount;
12+
import com.coinbase.cdp.openapi.model.VerifyX402PaymentRequest;
13+
import com.coinbase.cdp.openapi.model.X402ExactSolanaPayload;
14+
import com.coinbase.cdp.openapi.model.X402PaymentPayload;
15+
import com.coinbase.cdp.openapi.model.X402PaymentRequirements;
16+
import com.coinbase.cdp.openapi.model.X402SupportedPaymentKind;
17+
import com.coinbase.cdp.openapi.model.X402V1PaymentPayload;
18+
import com.coinbase.cdp.openapi.model.X402V1PaymentPayloadPayload;
19+
import com.coinbase.cdp.openapi.model.X402V1PaymentRequirements;
20+
import com.coinbase.cdp.openapi.model.X402Version;
21+
import com.coinbase.cdp.utils.SolanaTransactionBuilder;
22+
import java.math.BigInteger;
23+
import org.p2p.solanaj.core.PublicKey;
24+
import org.p2p.solanaj.rpc.RpcClient;
25+
26+
/**
27+
* Demonstrates the x402 payment flow on Solana using the CDP SDK.
28+
*
29+
* <p>x402 is a protocol for HTTP-native payments. The payer constructs and signs a transaction but
30+
* does NOT broadcast it directly. Instead, the signed transaction is included as a payment payload
31+
* in HTTP headers. The x402 facilitator verifies and settles the transaction on behalf of the
32+
* resource provider.
33+
*
34+
* <p>This example shows the full flow:
35+
*
36+
* <ol>
37+
* <li>Query supported x402 payment kinds from the facilitator
38+
* <li>Create a CDP-managed Solana account (the payer)
39+
* <li>Construct an unsigned SOL transfer transaction via the SDK's SolanaTransactionBuilder
40+
* <li>Sign the transaction via CDP (sign-only, no broadcast)
41+
* <li>Assemble the x402 payment payload and verify it with the facilitator
42+
* </ol>
43+
*
44+
* <p>Note: This example does not fund the account via faucet. The verify call will likely return
45+
* invalid due to insufficient funds, but the full API flow is demonstrated. In production, the
46+
* payer account would be funded before constructing the payment transaction.
47+
*/
48+
public class SignTransactionX402 {
49+
50+
private static final String SOLANA_DEVNET_RPC = "https://api.devnet.solana.com";
51+
52+
// Recipient address representing the x402 payee (resource provider or facilitator).
53+
private static final String PAYEE_ADDRESS = "3KzDtddx4i53FBkvCzuDmRbaMozTZoJBb1TToWhz3JfE";
54+
55+
// Transfer amount in lamports (0.0001 SOL).
56+
private static final BigInteger TRANSFER_LAMPORTS = BigInteger.valueOf(100_000);
57+
58+
public static void main(String[] args) throws Exception {
59+
EnvLoader.load();
60+
61+
try (CdpClient cdp = CdpClient.create()) {
62+
63+
// ---------------------------------------------------------------
64+
// Step 1: Query supported x402 payment kinds
65+
// ---------------------------------------------------------------
66+
System.out.println("=== Step 1: Query supported x402 payment kinds ===");
67+
X402FacilitatorApi x402Api = new X402FacilitatorApi(cdp.getApiClient());
68+
InlineObject2 supported = x402Api.supportedX402PaymentKinds();
69+
70+
System.out.println("Supported payment kinds:");
71+
for (X402SupportedPaymentKind kind : supported.getKinds()) {
72+
System.out.printf(
73+
" - version=%s, scheme=%s, network=%s%n",
74+
kind.getX402Version(), kind.getScheme(), kind.getNetwork());
75+
}
76+
System.out.println("Signer networks: " + supported.getSigners().keySet());
77+
System.out.println();
78+
79+
// ---------------------------------------------------------------
80+
// Step 2: Create a CDP-managed Solana account (the payer)
81+
// ---------------------------------------------------------------
82+
System.out.println("=== Step 2: Create a Solana account ===");
83+
SolanaAccount account =
84+
cdp.solana().createAccount(new CreateSolanaAccountRequest().name("x402-payer-example"));
85+
String payerAddress = account.getAddress();
86+
System.out.println("Payer address: " + payerAddress);
87+
System.out.println();
88+
89+
// ---------------------------------------------------------------
90+
// Step 3: Build an unsigned SOL transfer transaction
91+
// ---------------------------------------------------------------
92+
// In an x402 flow, this transaction represents the payment to the
93+
// resource provider. The amount and recipient would come from the
94+
// server's 402 Payment Required response (X-Payment header).
95+
System.out.println("=== Step 3: Build unsigned Solana transfer transaction ===");
96+
RpcClient rpcClient = new RpcClient(SOLANA_DEVNET_RPC);
97+
PublicKey fromKey = new PublicKey(payerAddress);
98+
PublicKey toKey = new PublicKey(PAYEE_ADDRESS);
99+
100+
String unsignedTxBase64 =
101+
SolanaTransactionBuilder.buildNativeTransfer(rpcClient, fromKey, toKey, TRANSFER_LAMPORTS);
102+
103+
System.out.println("Unsigned transaction (base64): " + unsignedTxBase64.substring(0, 40) + "...");
104+
System.out.println();
105+
106+
// ---------------------------------------------------------------
107+
// Step 4: Sign the transaction via CDP (sign-only, no broadcast)
108+
// ---------------------------------------------------------------
109+
// The key x402 distinction: we sign but do NOT send. The signed
110+
// transaction becomes the payment payload that accompanies the
111+
// HTTP request to the paid resource. The x402 facilitator settles
112+
// it after verifying the payment.
113+
System.out.println("=== Step 4: Sign transaction via CDP ===");
114+
SignSolanaTransaction200Response signResponse =
115+
cdp.solana()
116+
.signTransaction(
117+
payerAddress,
118+
new SignSolanaTransactionRequest().transaction(unsignedTxBase64));
119+
120+
String signedTxBase64 = signResponse.getSignedTransaction();
121+
System.out.println("Signed transaction (base64): " + signedTxBase64.substring(0, 40) + "...");
122+
System.out.println();
123+
124+
// ---------------------------------------------------------------
125+
// Step 5: Assemble the x402 payment payload and verify
126+
// ---------------------------------------------------------------
127+
// In a real x402 flow:
128+
// 1. Client requests a paid resource and receives 402 + payment requirements
129+
// 2. Client constructs and signs the payment transaction (steps 3-4 above)
130+
// 3. Client retries the request with the signed tx in the X-Payment header
131+
// 4. The server (resource provider) calls verify/settle via the facilitator
132+
//
133+
// Here we call verify directly to demonstrate the facilitator API.
134+
System.out.println("=== Step 5: Verify x402 payment ===");
135+
136+
// Wrap the signed transaction in the Solana-specific payload
137+
X402ExactSolanaPayload solanaPayload =
138+
new X402ExactSolanaPayload().transaction(signedTxBase64);
139+
140+
// Build the v1 payment payload
141+
X402V1PaymentPayload paymentPayload =
142+
new X402V1PaymentPayload()
143+
.x402Version(X402Version.NUMBER_1)
144+
.scheme(X402V1PaymentPayload.SchemeEnum.EXACT)
145+
.network(X402V1PaymentPayload.NetworkEnum.SOLANA_DEVNET)
146+
.payload(new X402V1PaymentPayloadPayload(solanaPayload));
147+
148+
// Build the payment requirements (mirroring what a server would provide)
149+
X402V1PaymentRequirements paymentRequirements =
150+
new X402V1PaymentRequirements()
151+
.scheme(X402V1PaymentRequirements.SchemeEnum.EXACT)
152+
.network(X402V1PaymentRequirements.NetworkEnum.SOLANA_DEVNET)
153+
.maxAmountRequired(TRANSFER_LAMPORTS.toString())
154+
.resource("https://example.com/api/paid-resource")
155+
.description("Example x402 Solana payment")
156+
.mimeType("application/json")
157+
.payTo(PAYEE_ADDRESS)
158+
.maxTimeoutSeconds(300)
159+
.asset("sol");
160+
161+
// Verify the payment with the x402 facilitator
162+
InlineObject verifyResponse =
163+
x402Api.verifyX402Payment(
164+
new VerifyX402PaymentRequest()
165+
.x402Version(X402Version.NUMBER_1)
166+
.paymentPayload(new X402PaymentPayload(paymentPayload))
167+
.paymentRequirements(new X402PaymentRequirements(paymentRequirements)));
168+
169+
System.out.println("Verification result:");
170+
System.out.println(" Valid: " + verifyResponse.getIsValid());
171+
System.out.println(" Payer: " + verifyResponse.getPayer());
172+
if (!verifyResponse.getIsValid()) {
173+
System.out.println(" Reason: " + verifyResponse.getInvalidReason());
174+
System.out.println(" Message: " + verifyResponse.getInvalidMessage());
175+
System.out.println();
176+
System.out.println(
177+
"Note: The payment is expected to be invalid in this example because the");
178+
System.out.println(
179+
"account was not funded. In production, the payer account would have");
180+
System.out.println("sufficient SOL before constructing the payment transaction.");
181+
}
182+
System.out.println();
183+
System.out.println("x402 Solana sign transaction example complete.");
184+
}
185+
}
186+
}

0 commit comments

Comments
 (0)