Skip to content

Commit d37048c

Browse files
✨ feat: initialize settler-proxy application with dependencies
Add main application logic, proxy setup, and configuration for the settler-proxy module. Include necessary go.mod and go.sum files for dependency management. Implement middleware for handling specific proxy requests and add tests to ensure functionality.
1 parent 65f607a commit d37048c

8 files changed

Lines changed: 345 additions & 2 deletions

File tree

apps/settler-proxy/go.mod

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
module github.com/nathfavour/settlerengine/apps/settler-proxy
2+
3+
go 1.25.5
4+
5+
require (
6+
github.com/ethereum/go-ethereum v1.16.8
7+
github.com/nathfavour/settlerengine/pkg v0.0.0-20260216073052-65f607ab2c63
8+
)
9+
10+
require (
11+
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect
12+
github.com/bits-and-blooms/bitset v1.20.0 // indirect
13+
github.com/consensys/gnark-crypto v0.18.0 // indirect
14+
github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect
15+
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect
16+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
17+
github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect
18+
github.com/ethereum/go-verkle v0.2.2 // indirect
19+
github.com/holiman/uint256 v1.3.2 // indirect
20+
github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe // indirect
21+
golang.org/x/crypto v0.36.0 // indirect
22+
golang.org/x/sync v0.12.0 // indirect
23+
golang.org/x/sys v0.36.0 // indirect
24+
)

apps/settler-proxy/go.sum

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 h1:1zYrtlhrZ6/b6SAjLSfKzWtdgqK0U+HtH/VcBWh1BaU=
2+
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
3+
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
4+
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
5+
github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU=
6+
github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
7+
github.com/consensys/gnark-crypto v0.18.0 h1:vIye/FqI50VeAr0B3dx+YjeIvmc3LWz4yEfbWBpTUf0=
8+
github.com/consensys/gnark-crypto v0.18.0/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c=
9+
github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg=
10+
github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
11+
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg=
12+
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM=
13+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
14+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
15+
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
16+
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
17+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
18+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
19+
github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A=
20+
github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
21+
github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3aXiHh5s=
22+
github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs=
23+
github.com/ethereum/go-ethereum v1.16.8 h1:LLLfkZWijhR5m6yrAXbdlTeXoqontH+Ga2f9igY7law=
24+
github.com/ethereum/go-ethereum v1.16.8/go.mod h1:Fs6QebQbavneQTYcA39PEKv2+zIjX7rPUZ14DER46wk=
25+
github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8=
26+
github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
27+
github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY=
28+
github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg=
29+
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
30+
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
31+
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
32+
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
33+
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
34+
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
35+
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
36+
github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
37+
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
38+
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
39+
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
40+
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
41+
github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4=
42+
github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
43+
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
44+
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
45+
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
46+
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
47+
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
48+
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
49+
github.com/nathfavour/settlerengine/pkg v0.0.0-20260216073052-65f607ab2c63 h1:BHRSY8Ut62Z/DDFyn6jkZVhmLWKLbTHNSDXsXJnTh2c=
50+
github.com/nathfavour/settlerengine/pkg v0.0.0-20260216073052-65f607ab2c63/go.mod h1:iWWCRck/phA+mcziLeZZ+3cyfvbJRwinyMZxKJZqjW0=
51+
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
52+
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
53+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
54+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
55+
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
56+
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
57+
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
58+
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
59+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
60+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
61+
github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe h1:nbdqkIGOGfUAD54q1s2YBcBz/WcsxCO9HUQ4aGV5hUw=
62+
github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
63+
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
64+
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
65+
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
66+
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
67+
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
68+
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
69+
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
70+
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
71+
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
72+
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
73+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
74+
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
75+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
76+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

apps/settler-proxy/main.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"log"
6+
"math/big"
7+
"net/http"
8+
"net/http/httputil"
9+
"net/url"
10+
"time"
11+
12+
"github.com/ethereum/go-ethereum/common"
13+
"github.com/nathfavour/settlerengine/pkg/crypto"
14+
"github.com/nathfavour/settlerengine/pkg/x402"
15+
)
16+
17+
func main() {
18+
target := flag.String("target", "http://localhost:8081", "Target URL to proxy to")
19+
listen := flag.String("listen", ":8080", "Listen address")
20+
recipient := flag.String("recipient", "0x1234567890AbcdEF1234567890aBcdef12345678", "Merchant recipient address")
21+
chainID := flag.Int64("chain-id", 84532, "Chain ID (default Base Sepolia)")
22+
asset := flag.String("asset", "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "Asset address (USDC)")
23+
amount := flag.String("amount", "1000000", "Amount in atomic units")
24+
25+
flag.Parse()
26+
27+
targetURL, err := url.Parse(*target)
28+
if err != nil {
29+
log.Fatalf("Invalid target URL: %v", err)
30+
}
31+
32+
proxy := httputil.NewSingleHostReverseProxy(targetURL)
33+
34+
cfg := x402.Config{
35+
DomainParams: crypto.DomainParams{
36+
ChainID: big.NewInt(*chainID),
37+
VerifyingContract: common.HexToAddress("0x0000000000000000000000000000000000000000"),
38+
},
39+
NonceExpiry: 5 * time.Minute,
40+
Recipient: *recipient,
41+
Asset: *asset,
42+
Amount: *amount,
43+
}
44+
45+
mw := x402.NewMiddleware(cfg)
46+
47+
handler := mw.Handler(proxy)
48+
49+
log.Printf("🚀 SettlerProxy: Listening on %s", *listen)
50+
log.Printf("🔗 Proxying to: %s", *target)
51+
log.Printf("💰 Policy: %s %s on Chain %d", *amount, *asset, *chainID)
52+
53+
if err := http.ListenAndServe(*listen, handler); err != nil {
54+
log.Fatal(err)
55+
}
56+
}

go.work

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
go 1.25.5
22

33
use (
4+
./apps/settler-proxy
45
./apps/settlerd
56
./core
67
./pkg

pkg/crypto/eip712.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,11 @@ func VerifyIntentToPay(intent IntentToPay, signature string, params DomainParams
7070
return common.Address{}, fmt.Errorf("failed to hash message: %w", err)
7171
}
7272

73-
rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))
73+
rawData := make([]byte, 2+32+32)
74+
rawData[0] = 0x19
75+
rawData[1] = 0x01
76+
copy(rawData[2:34], domainSeparator)
77+
copy(rawData[34:66], typedDataHash)
7478
sighash := crypto.Keccak256(rawData)
7579

7680
sig, err := hexutil.Decode(signature)

pkg/x402/header.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"encoding/json"
55
"fmt"
66
"net/http"
7-
"strings"
87

98
"github.com/nathfavour/settlerengine/pkg/crypto"
109
)

pkg/x402/middleware.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package x402
33
import (
44
"encoding/json"
55
"net/http"
6+
"sync"
67
"time"
78

89
"github.com/nathfavour/settlerengine/pkg/crypto"

pkg/x402/middleware_test.go

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
package x402
2+
3+
import (
4+
"encoding/json"
5+
"math/big"
6+
"net/http"
7+
"net/http/httptest"
8+
"testing"
9+
"time"
10+
11+
"github.com/ethereum/go-ethereum/common"
12+
"github.com/ethereum/go-ethereum/common/math"
13+
"github.com/ethereum/go-ethereum/crypto"
14+
"github.com/ethereum/go-ethereum/signer/core/apitypes"
15+
crypto2 "github.com/nathfavour/settlerengine/pkg/crypto"
16+
)
17+
18+
func TestMiddleware_402Challenge(t *testing.T) {
19+
cfg := Config{
20+
DomainParams: crypto2.DomainParams{
21+
ChainID: big.NewInt(84532),
22+
VerifyingContract: common.HexToAddress("0x0"),
23+
},
24+
NonceExpiry: 1 * time.Minute,
25+
Recipient: "0x123",
26+
Asset: "0x456",
27+
Amount: "100",
28+
}
29+
mw := NewMiddleware(cfg)
30+
31+
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
32+
w.WriteHeader(http.StatusOK)
33+
w.Write([]byte("OK"))
34+
})
35+
36+
handler := mw.Handler(nextHandler)
37+
38+
req := httptest.NewRequest("GET", "/", nil)
39+
rr := httptest.NewRecorder()
40+
41+
handler.ServeHTTP(rr, req)
42+
43+
if rr.Code != http.StatusPaymentRequired {
44+
t.Errorf("expected status 402, got %d", rr.Code)
45+
}
46+
47+
var resp ChallengeResponse
48+
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
49+
t.Fatalf("failed to unmarshal response: %v", err)
50+
}
51+
52+
if resp.Payment.Amount != "100" {
53+
t.Errorf("expected amount 100, got %s", resp.Payment.Amount)
54+
}
55+
56+
if resp.Payment.Nonce == "" {
57+
t.Error("expected nonce in response")
58+
}
59+
}
60+
61+
func TestMiddleware_Authorized(t *testing.T) {
62+
privateKey, _ := crypto.GenerateKey()
63+
agentAddr := crypto.PubkeyToAddress(privateKey.PublicKey)
64+
65+
chainID := big.NewInt(84532)
66+
verifyingContract := common.HexToAddress("0x0")
67+
68+
cfg := Config{
69+
DomainParams: crypto2.DomainParams{
70+
ChainID: chainID,
71+
VerifyingContract: verifyingContract,
72+
},
73+
NonceExpiry: 1 * time.Minute,
74+
Recipient: agentAddr.Hex(), // Just for testing
75+
Asset: "0x456",
76+
Amount: "100",
77+
}
78+
mw := NewMiddleware(cfg)
79+
80+
// First request to get a nonce
81+
req1 := httptest.NewRequest("GET", "/", nil)
82+
rr1 := httptest.NewRecorder()
83+
mw.Handler(nil).ServeHTTP(rr1, req1)
84+
85+
var challenge ChallengeResponse
86+
json.Unmarshal(rr1.Body.Bytes(), &challenge)
87+
nonce := challenge.Payment.Nonce
88+
89+
// Sign the intent
90+
intent := crypto2.IntentToPay{
91+
Recipient: cfg.Recipient,
92+
Amount: cfg.Amount,
93+
Asset: cfg.Asset,
94+
Nonce: nonce,
95+
Deadline: uint64(time.Now().Add(1 * time.Hour).Unix()),
96+
}
97+
98+
typedData := apitypes.TypedData{
99+
Types: apitypes.Types{
100+
"EIP712Domain": []apitypes.Type{
101+
{Name: "name", Type: "string"},
102+
{Name: "version", Type: "string"},
103+
{Name: "chainId", Type: "uint256"},
104+
{Name: "verifyingContract", Type: "address"},
105+
},
106+
"IntentToPay": []apitypes.Type{
107+
{Name: "recipient", Type: "address"},
108+
{Name: "amount", Type: "uint256"},
109+
{Name: "asset", Type: "address"},
110+
{Name: "nonce", Type: "string"},
111+
{Name: "deadline", Type: "uint256"},
112+
},
113+
},
114+
PrimaryType: "IntentToPay",
115+
Domain: apitypes.TypedDataDomain{
116+
Name: "SettlerEngine",
117+
Version: "1",
118+
ChainId: (*math.HexOrDecimal256)(chainID),
119+
VerifyingContract: verifyingContract.Hex(),
120+
},
121+
Message: apitypes.TypedDataMessage{
122+
"recipient": intent.Recipient,
123+
"amount": intent.Amount,
124+
"asset": intent.Asset,
125+
"nonce": intent.Nonce,
126+
"deadline": (*math.HexOrDecimal256)(big.NewInt(int64(intent.Deadline))),
127+
},
128+
}
129+
130+
domainSeparator, _ := typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
131+
typedDataHash, _ := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
132+
rawData := make([]byte, 2+32+32)
133+
rawData[0] = 0x19
134+
rawData[1] = 0x01
135+
copy(rawData[2:34], domainSeparator)
136+
copy(rawData[34:66], typedDataHash)
137+
sighash := crypto.Keccak256(rawData)
138+
139+
signature, _ := crypto.Sign(sighash, privateKey)
140+
signature[64] += 27 // Transform V
141+
sigHex := "0x" + common.Bytes2Hex(signature)
142+
143+
payload := PaymentPayload{
144+
Intent: intent,
145+
Signature: sigHex,
146+
}
147+
payloadJSON, _ := json.Marshal(payload)
148+
149+
// Second request with X-Payment header
150+
req2 := httptest.NewRequest("GET", "/", nil)
151+
req2.Header.Set("X-Payment", string(payloadJSON))
152+
rr2 := httptest.NewRecorder()
153+
154+
nextCalled := false
155+
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
156+
nextCalled = true
157+
w.WriteHeader(http.StatusOK)
158+
})
159+
160+
mw.Handler(nextHandler).ServeHTTP(rr2, req2)
161+
162+
if rr2.Code != http.StatusOK {
163+
t.Errorf("expected status 200, got %d. Body: %s", rr2.Code, rr2.Body.String())
164+
}
165+
if !nextCalled {
166+
t.Error("next handler was not called")
167+
}
168+
169+
// Third request (Idempotency check)
170+
req3 := httptest.NewRequest("GET", "/", nil)
171+
req3.Header.Set("X-Payment", string(payloadJSON))
172+
rr3 := httptest.NewRecorder()
173+
nextCalled = false
174+
mw.Handler(nextHandler).ServeHTTP(rr3, req3)
175+
176+
if rr3.Code != http.StatusOK {
177+
t.Errorf("expected status 200 (cached), got %d", rr3.Code)
178+
}
179+
if !nextCalled {
180+
t.Error("next handler was not called (cached)")
181+
}
182+
}

0 commit comments

Comments
 (0)