Skip to content

Commit e5a2208

Browse files
committed
feat: cross-SDK testing
1 parent 8e4367a commit e5a2208

45 files changed

Lines changed: 1227 additions & 227 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

examples/go/clients/batch-settlement-concurrent/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ EVM_PRIVATE_KEY=0x...
33
# Optional: dedicated voucher-signing EOA (faster verification, recommended for smart wallets)
44
EVM_VOUCHER_SIGNER_PRIVATE_KEY=
55

6+
# RPC endpoint used to read channel state when local storage is empty.
7+
EVM_RPC_URL=https://sepolia.base.org
8+
69
RESOURCE_SERVER_URL=http://localhost:4021
710
ENDPOINT_PATH=/api/generate
811

examples/go/clients/batch-settlement-concurrent/main.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"sync"
1414
"time"
1515

16+
"github.com/ethereum/go-ethereum/ethclient"
1617
"github.com/joho/godotenv"
1718
x402 "github.com/x402-foundation/x402/go"
1819
x402http "github.com/x402-foundation/x402/go/http"
@@ -38,12 +39,21 @@ func main() {
3839
endpointPath := envOr("ENDPOINT_PATH", "/api/generate")
3940
url := baseURL + endpointPath
4041

42+
rpcURL := envOr("EVM_RPC_URL", "https://sepolia.base.org")
4143
baseSalt := envOr("CHANNEL_SALT", batchedclient.DefaultSalt)
4244
storageDir := os.Getenv("STORAGE_DIR")
4345
concurrency := atoiOr("CONCURRENCY", 3)
4446
numberOfChannels := atoiOr("NUMBER_OF_CHANNELS", 3)
4547

46-
signer, err := evmsigners.NewClientSignerFromPrivateKey(evmPrivateKey)
48+
// RPC required so the signer can recover channel state when local storage is empty.
49+
ethClient, err := ethclient.Dial(rpcURL)
50+
if err != nil {
51+
fmt.Printf("Failed to dial EVM RPC %s: %v\n", rpcURL, err)
52+
os.Exit(1)
53+
}
54+
defer ethClient.Close()
55+
56+
signer, err := evmsigners.NewClientSignerFromPrivateKeyWithClient(evmPrivateKey, ethClient)
4757
if err != nil {
4858
fmt.Printf("Failed to create signer: %v\n", err)
4959
os.Exit(1)

examples/go/clients/batch-settlement-streaming/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ EVM_PRIVATE_KEY=0x...
33
# Optional: dedicated voucher-signing EOA (faster verification, recommended for smart wallets)
44
EVM_VOUCHER_SIGNER_PRIVATE_KEY=
55

6+
# RPC endpoint used to read channel state when local storage is empty.
7+
EVM_RPC_URL=https://sepolia.base.org
8+
69
RESOURCE_SERVER_URL=http://localhost:4021
710

811
# 32-byte hex channel salt

examples/go/clients/batch-settlement-streaming/main.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"os"
1515
"strings"
1616

17+
"github.com/ethereum/go-ethereum/ethclient"
1718
"github.com/joho/godotenv"
1819
x402 "github.com/x402-foundation/x402/go"
1920
x402http "github.com/x402-foundation/x402/go/http"
@@ -50,10 +51,19 @@ func main() {
5051
os.Exit(1)
5152
}
5253
baseURL := envOr("RESOURCE_SERVER_URL", "http://localhost:4021")
54+
rpcURL := envOr("EVM_RPC_URL", "https://sepolia.base.org")
5355
channelSalt := envOr("CHANNEL_SALT", batchedclient.DefaultSalt)
5456
storageDir := os.Getenv("STORAGE_DIR")
5557

56-
signer, err := evmsigners.NewClientSignerFromPrivateKey(evmPrivateKey)
58+
// RPC required so the signer can recover channel state when local storage is empty.
59+
ethClient, err := ethclient.Dial(rpcURL)
60+
if err != nil {
61+
fmt.Printf("Failed to dial EVM RPC %s: %v\n", rpcURL, err)
62+
os.Exit(1)
63+
}
64+
defer ethClient.Close()
65+
66+
signer, err := evmsigners.NewClientSignerFromPrivateKeyWithClient(evmPrivateKey, ethClient)
5767
if err != nil {
5868
fmt.Printf("Failed to create signer: %v\n", err)
5969
os.Exit(1)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
EVM_PRIVATE_KEY=0x...
2+
3+
# Optional: dedicated voucher-signing EOA (faster verification, recommended for smart wallets)
4+
EVM_VOUCHER_SIGNER_PRIVATE_KEY=
5+
6+
# RPC endpoint used to read channel state when local storage is empty (cold-start
7+
# recovery so the client picks up an existing channel's totalClaimed instead of
8+
# signing a fresh voucher that the facilitator would reject).
9+
EVM_RPC_URL=https://sepolia.base.org
10+
11+
RESOURCE_SERVER_URL=http://localhost:4021
12+
ENDPOINT_PATH=/api/generate
13+
14+
# 32-byte hex; differentiates channels with the same payer/payee/token tuple
15+
CHANNEL_SALT=0x0000000000000000000000000000000000000000000000000000000000000000
16+
17+
# Optional: persist session state across runs
18+
STORAGE_DIR=
19+
20+
NUMBER_OF_REQUESTS=3
21+
22+
# After the request loop completes, request a cooperative refund.
23+
REFUND_AFTER_REQUESTS=false
24+
# Optional partial refund amount (base units). Empty drains the remaining channel balance.
25+
REFUND_AMOUNT=

examples/go/clients/batch-settlement/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@ that update cumulative claimable amount.
88

99
```bash
1010
cp .env.example .env
11-
# fill in EVM_PRIVATE_KEY and (optional) FILE_STORAGE_DIR
11+
# fill in EVM_PRIVATE_KEY and optional STORAGE_DIR
1212

1313
go run .
1414
```
1515

1616
The companion server is in `examples/go/servers/batch-settlement` and the
1717
facilitator is in `examples/go/facilitator/batch-settlement`.
1818

19+
For **cross-SDK** runs (Go server + TypeScript client, etc.), use the same
20+
variable names as `examples/typescript/clients/batch-settlement/.env-example`
21+
— they are aligned with this file so one `.env` can drive either stack.
22+
1923
## Environment
2024

2125
| Variable | Description |

examples/go/clients/batch-settlement/main.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strconv"
1111
"time"
1212

13+
"github.com/ethereum/go-ethereum/ethclient"
1314
"github.com/joho/godotenv"
1415
x402 "github.com/x402-foundation/x402/go"
1516
x402http "github.com/x402-foundation/x402/go/http"
@@ -34,13 +35,25 @@ func main() {
3435
endpointPath := envOr("ENDPOINT_PATH", "/api/generate")
3536
url := baseURL + endpointPath
3637

38+
rpcURL := envOr("EVM_RPC_URL", "https://sepolia.base.org")
3739
channelSalt := envOr("CHANNEL_SALT", batchedclient.DefaultSalt)
3840
storageDir := os.Getenv("STORAGE_DIR")
3941
numberOfRequests := atoiOr("NUMBER_OF_REQUESTS", 3)
4042
refundAfterRequests := os.Getenv("REFUND_AFTER_REQUESTS") == "true"
4143
refundAmount := os.Getenv("REFUND_AMOUNT")
4244

43-
signer, err := evmsigners.NewClientSignerFromPrivateKey(evmPrivateKey)
45+
// Dial an RPC client so the signer can read on-chain channel state when
46+
// local storage is cold. Without this, a fresh client run against a channel
47+
// that already has on-chain totalClaimed would sign vouchers with a stale
48+
// cumulative base and the facilitator would reject them.
49+
ethClient, err := ethclient.Dial(rpcURL)
50+
if err != nil {
51+
fmt.Printf("Failed to dial EVM RPC %s: %v\n", rpcURL, err)
52+
os.Exit(1)
53+
}
54+
defer ethClient.Close()
55+
56+
signer, err := evmsigners.NewClientSignerFromPrivateKeyWithClient(evmPrivateKey, ethClient)
4457
if err != nil {
4558
fmt.Printf("Failed to create signer: %v\n", err)
4659
os.Exit(1)
@@ -95,11 +108,18 @@ func main() {
95108
os.Exit(1)
96109
}
97110

98-
body, _ := readJSON(resp)
99-
fmt.Printf("Request %d — RESPONSE\n%s\n", i+1, indent(body))
111+
fmt.Printf("Request %d — %s\n", i+1, resp.Status)
112+
body, errBody := readJSON(resp)
113+
if errBody != nil {
114+
fmt.Printf(" body: <not JSON: %v>\n", errBody)
115+
} else {
116+
fmt.Printf("Request %d — RESPONSE\n%s\n", i+1, indent(body))
117+
}
100118

101119
if settle, _ := extractSettleResponse(resp); settle != nil {
102120
fmt.Println(indent(settle))
121+
} else if resp.StatusCode != http.StatusOK {
122+
fmt.Printf(" no PAYMENT-RESPONSE (%s) — payment did not settle\n", resp.Status)
103123
}
104124

105125
_ = resp.Body.Close()
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
EVM_PRIVATE_KEY=0x...
2+
EVM_RPC_URL=https://sepolia.base.org
3+
4+
# Optional dedicated authorizer key (defaults to EVM_PRIVATE_KEY)
5+
EVM_RECEIVER_AUTHORIZER_PRIVATE_KEY=
6+
7+
# Optional (default 4022). Same as examples/typescript/facilitator/batch-settlement.
8+
PORT=

examples/go/facilitator/batch-settlement/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ cp .env.example .env
2323
go run .
2424
```
2525

26-
Listens on `http://localhost:4022` by default.
26+
Listens on `http://localhost:4022` by default (`PORT` overrides; same as the
27+
TypeScript facilitator example).
2728

2829
## Environment
2930

@@ -32,3 +33,4 @@ Listens on `http://localhost:4022` by default.
3233
| `EVM_PRIVATE_KEY` (required) | Facilitator wallet — signs and submits on-chain transactions |
3334
| `EVM_RECEIVER_AUTHORIZER_PRIVATE_KEY` | Optional dedicated authorizer key. Defaults to `EVM_PRIVATE_KEY`. |
3435
| `EVM_RPC_URL` | Default `https://sepolia.base.org` |
36+
| `PORT` | Listen port (default `4022`) |

examples/go/facilitator/batch-settlement/main.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ const defaultPort = "4022"
1818
func main() {
1919
_ = godotenv.Load()
2020

21+
port := envOr("PORT", defaultPort)
22+
2123
evmPrivateKey := os.Getenv("EVM_PRIVATE_KEY")
2224
if evmPrivateKey == "" {
2325
fmt.Println("EVM_PRIVATE_KEY environment variable is required")
@@ -100,8 +102,8 @@ func main() {
100102
writeJSON(w, http.StatusOK, result)
101103
})
102104

103-
fmt.Printf("Facilitator listening on http://localhost:%s\n", defaultPort)
104-
if err := http.ListenAndServe(":"+defaultPort, mux); err != nil {
105+
fmt.Printf("Facilitator listening on http://localhost:%s\n", port)
106+
if err := http.ListenAndServe(":"+port, mux); err != nil {
105107
fmt.Printf("Server error: %v\n", err)
106108
os.Exit(1)
107109
}

0 commit comments

Comments
 (0)