From f77eb81be082a3b2b1af93670a9194080b7cac8d Mon Sep 17 00:00:00 2001 From: bussyjd Date: Tue, 14 Jul 2026 15:26:58 +0400 Subject: [PATCH 01/31] fix(network): stop reth liveness probe from killing consistency recovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upstream ethereum-node chart's reth subchart defaults to a tcp liveness probe on p2p (30303) with failureThreshold 3 (~12 min grace). Reth keeps that port closed while running post-crash check_consistency recovery, which can take far longer on a large datadir — so the kubelet SIGKILLs it mid-recovery, each kill adds more drift to heal on the next start, and the node crash-loops permanently (observed: 637 restarts, ~129k blocks of StoragesHistory drift). Override the probe with failureThreshold 10000 so it effectively never fires; a genuinely dead reth exits the container on its own. Scoped to reth only — geth/nethermind/besu probe http-rpc and erigon probes metrics, none of which close during recovery. Claude-Session: https://claude.ai/code/session_01VLQSsnH9WdAsnVYGTd2omr --- .../embed/networks/ethereum/helmfile.yaml.gotmpl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/internal/embed/networks/ethereum/helmfile.yaml.gotmpl b/internal/embed/networks/ethereum/helmfile.yaml.gotmpl index 048f8388..24422bc2 100644 --- a/internal/embed/networks/ethereum/helmfile.yaml.gotmpl +++ b/internal/embed/networks/ethereum/helmfile.yaml.gotmpl @@ -105,6 +105,20 @@ releases: # renovate: datasource=github-releases depName=erigontech/erigon tag: v3.5.1 {{- end }} + {{- if eq .Values.executionClient "reth" }} + # reth keeps p2p (30303) closed while running post-crash consistency + # recovery, which can exceed any bounded probe window on a large + # datadir. The chart's default tcp liveness probe then SIGKILLs it + # mid-recovery, forcing a longer recovery on the next start — a + # permanent crash loop. A genuinely dead process exits the container + # on its own, so make the probe effectively never fire. + livenessProbe: + tcpSocket: + port: p2p-tcp + initialDelaySeconds: 60 + periodSeconds: 120 + failureThreshold: 10000 + {{- end }} persistence: enabled: true size: {{ if (index .Values "executionStorageSize" | default "") }}{{ index .Values "executionStorageSize" }}{{ else }}{{ if eq .Values.network "mainnet" }}{{ if eq .Values.mode "archive" }}4500Gi{{ else }}500Gi{{ end }}{{ else }}{{ if eq .Values.mode "archive" }}300Gi{{ else }}100Gi{{ end }}{{ end }}{{ end }} From d419620b784fc5d887796f114200e85c67312861 Mon Sep 17 00:00:00 2001 From: bussyjd Date: Tue, 14 Jul 2026 21:44:39 +0400 Subject: [PATCH 02/31] fix(x402): default 402 challenge resource.url to https on public hosts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit buildResourceURL keyed the scheme off X-Forwarded-Proto alone, so behind a TLS-terminating tunnel (edge https -> plaintext to Traefik -> verifier) any route without an explicit X-Forwarded-Proto:https RequestHeaderModifier produced http:// resource URLs in 402 challenges. The controller's host-bound so--host routes carry that filter but the shared-origin so- routes do not — and being more path-specific for /services/, they win the match even on dedicated-hostname origins, so challenges on those origins advertised http:// resources on an https endpoint (strict v2 payment clients and discovery crawlers see a scheme mismatch; observed live on 5 of 8 host-bound offers, #679). resolveSiteURL already solved exactly this for 402-page links: default https, downgrade only for hosts the stack serves locally over plain HTTP (obol.stack, loopback, *.localhost/*.local), explicit signals always win. Extract that resolution into resolveScheme and use it in both places, so challenge resource URLs and page links can never disagree again. Refs #679 Claude-Session: https://claude.ai/code/session_014YjPMViNrZ7zBVgUQzwEKk --- internal/x402/forwardauth.go | 12 ++++++---- internal/x402/forwardauth_test.go | 40 +++++++++++++++++++++++++++++++ internal/x402/paymentrequired.go | 15 +++++++++--- 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/internal/x402/forwardauth.go b/internal/x402/forwardauth.go index e477a4b4..6e514006 100644 --- a/internal/x402/forwardauth.go +++ b/internal/x402/forwardauth.go @@ -598,14 +598,18 @@ func facilitatorSettle(ctx context.Context, client *http.Client, facilitatorURL } func buildResourceURL(r *http.Request) string { - scheme := "http" - if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" { - scheme = "https" - } host := r.Host if forwardedHost := r.Header.Get("X-Forwarded-Host"); forwardedHost != "" { host = forwardedHost } + // resolveScheme, not X-Forwarded-Proto alone: behind the Cloudflare + // tunnel the ForwardAuth hop sees plaintext (XFP=http), which rendered + // http:// resource URLs in 402 challenges on public hostnames whenever a + // route lacked the X-Forwarded-Proto:https RequestHeaderModifier — the + // shared-origin so- routes don't carry it (#679). Public hosts + // default to https; only local plain-HTTP hosts (obol.stack, loopback, + // *.localhost/*.local) stay http. + scheme := resolveScheme(r, host) uri := r.RequestURI if forwardedURI := r.Header.Get("X-Forwarded-Uri"); forwardedURI != "" { uri = forwardedURI diff --git a/internal/x402/forwardauth_test.go b/internal/x402/forwardauth_test.go index be68d887..ac144bc2 100644 --- a/internal/x402/forwardauth_test.go +++ b/internal/x402/forwardauth_test.go @@ -949,3 +949,43 @@ func TestForwardAuth_402CarriesCatalogLinkHeader(t *testing.T) { } }) } + +func TestBuildResourceURL_Scheme(t *testing.T) { + cases := []struct { + name string + host string + xfHost string + xfp string + xfURI string + want string + }{ + // Public hosts default to https even when the TLS-terminating tunnel + // forwards plaintext (XFP=http) and the route carries no + // X-Forwarded-Proto filter — the shared-origin so- case (#679). + {"public host, no forwarded proto", "svc.example.org", "", "", "", "https://svc.example.org/services/x"}, + {"public host, xfp http from tunnel", "svc.example.org", "", "http", "", "https://svc.example.org/services/x"}, + {"forwarded public host", "10.42.0.5:8000", "svc.example.org", "", "", "https://svc.example.org/services/x"}, + {"local obol.stack stays http", "obol.stack:8080", "", "", "", "http://obol.stack:8080/services/x"}, + {"localhost stays http", "localhost:3000", "", "", "", "http://localhost:3000/services/x"}, + {"local host, explicit https honored", "obol.stack:8080", "", "https", "", "https://obol.stack:8080/services/x"}, + {"forwarded uri used", "svc.example.org", "", "", "/services/x/v1/chat", "https://svc.example.org/services/x/v1/chat"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + r := httptest.NewRequest("POST", "/services/x", nil) + r.Host = tc.host + if tc.xfHost != "" { + r.Header.Set("X-Forwarded-Host", tc.xfHost) + } + if tc.xfp != "" { + r.Header.Set("X-Forwarded-Proto", tc.xfp) + } + if tc.xfURI != "" { + r.Header.Set("X-Forwarded-Uri", tc.xfURI) + } + if got := buildResourceURL(r); got != tc.want { + t.Errorf("buildResourceURL() = %q, want %q", got, tc.want) + } + }) + } +} diff --git a/internal/x402/paymentrequired.go b/internal/x402/paymentrequired.go index 2461a8c7..087c0c35 100644 --- a/internal/x402/paymentrequired.go +++ b/internal/x402/paymentrequired.go @@ -190,11 +190,20 @@ func resolveSiteURL(r *http.Request) string { if forwarded := r.Header.Get("X-Forwarded-Host"); forwarded != "" { host = forwarded } - scheme := "https" + return resolveScheme(r, host) + "://" + host +} + +// resolveScheme is the single source of truth for the public scheme of a +// request that may have crossed a TLS-terminating tunnel. Default https; +// downgrade to http only for hosts the stack serves locally over plain HTTP. +// An explicit https signal (direct TLS or X-Forwarded-Proto: https) still +// forces https for any host. Shared by resolveSiteURL (402 page links) and +// buildResourceURL (challenge resource.url) so both stay consistent. +func resolveScheme(r *http.Request, host string) string { if r.TLS == nil && r.Header.Get("X-Forwarded-Proto") != "https" && isLocalHost(host) { - scheme = "http" + return "http" } - return scheme + "://" + host + return "https" } // isLocalHost reports whether host (optionally with :port) is one the stack From 440c59f1b14997ed9f5a87e731783cf96a048ec2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 17:29:18 +0000 Subject: [PATCH 03/31] chore(deps): update dependency kubernetes-sigs/gateway-api to v1.6.1 --- internal/embed/infrastructure/helmfile.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/embed/infrastructure/helmfile.yaml b/internal/embed/infrastructure/helmfile.yaml index f9440a6b..98fdff4f 100644 --- a/internal/embed/infrastructure/helmfile.yaml +++ b/internal/embed/infrastructure/helmfile.yaml @@ -17,7 +17,7 @@ repositories: # Single source of truth: change this to switch networks values: - network: mainnet - - gatewayApiVersion: v1.6.0 + - gatewayApiVersion: v1.6.1 # Default the cloudflared release to enabled. `obol stack up` overrides via # `--state-values-set cloudflared.enabled=false` when it detects a running # quick tunnel that should be preserved across the sync. From a04c05d12cc6b8a99d8373893673402f3795e04f Mon Sep 17 00:00:00 2001 From: bussyjd Date: Sat, 18 Jul 2026 17:45:13 +0400 Subject: [PATCH 04/31] fix(ui): honor OBOL_NONINTERACTIVE on a real TTY Reported by a teammate during v0.14.0-rc0 field testing. https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v --- internal/ui/prompt.go | 4 ++-- internal/ui/prompt_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 internal/ui/prompt_test.go diff --git a/internal/ui/prompt.go b/internal/ui/prompt.go index 0b463ba5..e265fdb1 100644 --- a/internal/ui/prompt.go +++ b/internal/ui/prompt.go @@ -11,9 +11,9 @@ import ( ) // isInteractive returns true if prompts should be shown. -// Returns false in JSON mode or when stdin is not a terminal. +// Returns false in JSON mode, when not a TTY, or when OBOL_NONINTERACTIVE=true. func (u *UI) isInteractive() bool { - return !u.IsJSON() && u.IsTTY() + return !u.IsJSON() && u.IsTTY() && os.Getenv("OBOL_NONINTERACTIVE") != "true" } // Confirm asks a yes/no question, returns true for "y"/"yes". diff --git a/internal/ui/prompt_test.go b/internal/ui/prompt_test.go new file mode 100644 index 00000000..827bbf77 --- /dev/null +++ b/internal/ui/prompt_test.go @@ -0,0 +1,33 @@ +package ui + +import ( + "bytes" + "testing" +) + +func TestIsInteractive_OBOLNonInteractive(t *testing.T) { + u := NewForTest(&bytes.Buffer{}, &bytes.Buffer{}) + u.isTTY = true // force TTY so only the env guard can suppress interactivity + + if !u.isInteractive() { + t.Fatal("isInteractive() = false with TTY, human output, and OBOL_NONINTERACTIVE unset; want true") + } + + t.Setenv("OBOL_NONINTERACTIVE", "true") + if u.isInteractive() { + t.Fatal("isInteractive() = true with OBOL_NONINTERACTIVE=true; want false") + } +} + +func TestConfirm_OBOLNonInteractiveReturnsDefault(t *testing.T) { + u := NewForTest(&bytes.Buffer{}, &bytes.Buffer{}) + u.isTTY = true + t.Setenv("OBOL_NONINTERACTIVE", "true") + + if got := u.Confirm("proceed?", true); !got { + t.Fatalf("Confirm(defaultYes=true) = %v; want true without reading stdin", got) + } + if got := u.Confirm("proceed?", false); got { + t.Fatalf("Confirm(defaultYes=false) = %v; want false without reading stdin", got) + } +} From f5c880b33a862c94c1c56cfff39e14abf6441375 Mon Sep 17 00:00:00 2001 From: bussyjd Date: Sat, 18 Jul 2026 17:48:12 +0400 Subject: [PATCH 05/31] fix(agentcrd): strip server-managed metadata before ResumeAll re-apply Reported by a teammate during v0.14.0-rc0 field testing. https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v --- internal/agentcrd/manifest.go | 17 +++++++++- internal/agentcrd/manifest_test.go | 53 ++++++++++++++++++++++++++++++ internal/stackbackup/cluster.go | 10 ++++-- 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/internal/agentcrd/manifest.go b/internal/agentcrd/manifest.go index 2d3c8507..32ee28a8 100644 --- a/internal/agentcrd/manifest.go +++ b/internal/agentcrd/manifest.go @@ -8,6 +8,7 @@ import ( "github.com/ObolNetwork/obol-stack/internal/config" "github.com/ObolNetwork/obol-stack/internal/kubectl" + "github.com/ObolNetwork/obol-stack/internal/stackbackup" "github.com/ObolNetwork/obol-stack/internal/ui" "gopkg.in/yaml.v3" ) @@ -89,6 +90,20 @@ func ResumeAll(cfg *config.Config, u *ui.UI) { u.Warnf("Could not read recorded agent %s: %v", name, err) continue } + // Persisted manifests may include server-managed metadata (resourceVersion, + // uid, managedFields, ...) captured after apply. Strip them so kubectl + // apply does not fail with "resourceVersion: Invalid value: 0". + var doc map[string]any + if err := yaml.Unmarshal(data, &doc); err != nil { + u.Warnf("Could not parse recorded agent %s: %v", name, err) + continue + } + stackbackup.StripServerManagedMetadata(doc) + stripped, err := yaml.Marshal(doc) + if err != nil { + u.Warnf("Could not re-marshal recorded agent %s: %v", name, err) + continue + } warnIfWalletWouldRegenerate(cfg, name, data, u) nsErr := kubectl.PipeCommands(bin, kubeconfig, []string{"create", "namespace", Namespace(name), "--dry-run=client", "-o", "yaml"}, @@ -96,7 +111,7 @@ func ResumeAll(cfg *config.Config, u *ui.UI) { if nsErr != nil { u.Warnf("Could not ensure namespace for agent %s: %v", name, nsErr) } - if err := kubectl.Apply(bin, kubeconfig, data); err != nil { + if err := kubectl.Apply(bin, kubeconfig, stripped); err != nil { u.Warnf("Could not re-apply agent %s (run 'obol agent new %s' to recreate): %v", name, name, err) continue } diff --git a/internal/agentcrd/manifest_test.go b/internal/agentcrd/manifest_test.go index 76af527b..bc1cfcee 100644 --- a/internal/agentcrd/manifest_test.go +++ b/internal/agentcrd/manifest_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/ObolNetwork/obol-stack/internal/config" + "github.com/ObolNetwork/obol-stack/internal/stackbackup" "gopkg.in/yaml.v3" ) @@ -64,3 +65,55 @@ func TestManifestStoreRoundTrip(t *testing.T) { t.Fatalf("after remove: %v", names) } } + +// TestStripServerManagedMetadataOnAgentManifest pins the ResumeAll strip path: +// persisted Agent YAML may carry server-managed metadata; stripping must drop +// those fields while keeping name/namespace and spec intact. +func TestStripServerManagedMetadataOnAgentManifest(t *testing.T) { + manifest := map[string]any{ + "apiVersion": "obol.org/v1alpha1", + "kind": "Agent", + "metadata": map[string]any{ + "name": "quant", + "namespace": "agent-quant", + "resourceVersion": "12345", + "uid": "abc-123", + "creationTimestamp": "2024-01-01T00:00:00Z", + "managedFields": []any{map[string]any{"manager": "kubectl"}}, + }, + "spec": map[string]any{ + "model": "qwen3.5:9b", + "skills": []any{"gas"}, + "wallet": map[string]any{"create": true}, + }, + } + data, err := yaml.Marshal(manifest) + if err != nil { + t.Fatal(err) + } + var doc map[string]any + if err := yaml.Unmarshal(data, &doc); err != nil { + t.Fatal(err) + } + stackbackup.StripServerManagedMetadata(doc) + + meta, ok := doc["metadata"].(map[string]any) + if !ok { + t.Fatalf("metadata missing or wrong type: %T", doc["metadata"]) + } + for _, k := range []string{"resourceVersion", "uid", "creationTimestamp", "managedFields"} { + if _, present := meta[k]; present { + t.Errorf("server-managed field %q still present after strip", k) + } + } + if meta["name"] != "quant" || meta["namespace"] != "agent-quant" { + t.Fatalf("identity fields altered: %v", meta) + } + spec, ok := doc["spec"].(map[string]any) + if !ok { + t.Fatalf("spec missing or wrong type: %T", doc["spec"]) + } + if spec["model"] != "qwen3.5:9b" { + t.Fatalf("spec.model altered: %v", spec["model"]) + } +} diff --git a/internal/stackbackup/cluster.go b/internal/stackbackup/cluster.go index 24097f69..32f7f3f1 100644 --- a/internal/stackbackup/cluster.go +++ b/internal/stackbackup/cluster.go @@ -211,16 +211,20 @@ func StripK8sJSON(data []byte) ([]byte, error) { } for _, it := range items { if obj, ok := it.(map[string]any); ok { - stripObject(obj) + StripServerManagedMetadata(obj) } } } else { - stripObject(doc) + StripServerManagedMetadata(doc) } return json.MarshalIndent(doc, "", " ") } -func stripObject(obj map[string]any) { +// StripServerManagedMetadata strips server-managed fields from one decoded +// Kubernetes object (map from json.Unmarshal or yaml.Unmarshal) so it can be +// re-applied. Used by StripK8sJSON (export/import dumps) and by +// internal/agentcrd.ResumeAll (persisted Agent manifests on stack up). +func StripServerManagedMetadata(obj map[string]any) { delete(obj, "status") meta, ok := obj["metadata"].(map[string]any) if !ok { From c70634a1c5570b3be7f1a8cae3ec9b2f04b65338 Mon Sep 17 00:00:00 2001 From: bussyjd Date: Sat, 18 Jul 2026 17:50:30 +0400 Subject: [PATCH 06/31] fix(serviceoffercontroller): align well-known/x402 resource path with openapi for inference/agent Reported by a teammate during v0.14.0-rc0 field testing. https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v --- .../serviceoffercontroller/hostoffer_test.go | 66 +++++++++++++++++++ .../serviceoffercontroller/offerbundle.go | 16 ++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/internal/serviceoffercontroller/hostoffer_test.go b/internal/serviceoffercontroller/hostoffer_test.go index 1f47ff85..e5b439b4 100644 --- a/internal/serviceoffercontroller/hostoffer_test.go +++ b/internal/serviceoffercontroller/hostoffer_test.go @@ -183,6 +183,72 @@ func TestBuildOfferBundles(t *testing.T) { } } +// TestBuildOfferBundles_InferenceOfferAgreesWithOpenAPI pins that a +// hostname-bound inference offer (no custom Spec.Routes — synthesized +// root catch-all) advertises the same paid path on both discovery +// surfaces: openapi.json paths and /.well-known/x402 resources. +// Before the openAPIRelPathForOfferRoute fix, x402 collapsed "/*" to +// the bare origin while openapi hard-coded /v1/chat/completions. +func TestBuildOfferBundles_InferenceOfferAgreesWithOpenAPI(t *testing.T) { + profile := schemas.StorefrontProfile{DisplayName: "Acme"} + offer := &monetizeapi.ServiceOffer{ + ObjectMeta: metav1.ObjectMeta{Name: "chat", Namespace: "llm"}, + Spec: monetizeapi.ServiceOfferSpec{ + Type: "inference", + Hostname: "chat.v1337.example", + Upstream: monetizeapi.ServiceOfferUpstream{Service: "gateway", Namespace: "llm", Port: 8080}, + Payment: monetizeapi.ServiceOfferPayment{ + Network: "base-sepolia", + PayTo: "0x1111111111111111111111111111111111111111", + Price: monetizeapi.ServiceOfferPriceTable{PerRequest: "0.1"}, + }, + // Spec.Routes intentionally empty: EffectiveRoutes synthesizes /*. + }, + Status: monetizeapi.ServiceOfferStatus{ + Conditions: []monetizeapi.Condition{ + {Type: "ModelReady", Status: "True"}, + {Type: "UpstreamHealthy", Status: "True"}, + {Type: "PaymentGateReady", Status: "True"}, + {Type: "RoutePublished", Status: "True"}, + }, + }, + } + + bundles := buildOfferBundles([]*monetizeapi.ServiceOffer{offer}, profile, noUpstreamOpenAPI) + byPath := map[string]string{} + for _, f := range bundles { + byPath[f.Path] = f.Content + } + + var doc map[string]any + if err := json.Unmarshal([]byte(byPath["offers/llm/chat/openapi.json"]), &doc); err != nil { + t.Fatalf("openapi bundle: %v", err) + } + paths := doc["paths"].(map[string]any) + if len(paths) != 1 { + t.Fatalf("paths = %v, want exactly one key", mapKeys(paths)) + } + if _, ok := paths["/v1/chat/completions"]; !ok { + t.Fatalf("paths = %v, want /v1/chat/completions", mapKeys(paths)) + } + + var wk struct { + Resources []struct { + Resource string `json:"resource"` + } `json:"resources"` + } + if err := json.Unmarshal([]byte(byPath["offers/llm/chat/x402.json"]), &wk); err != nil { + t.Fatalf("x402 bundle: %v", err) + } + if len(wk.Resources) != 1 { + t.Fatalf("len(resources) = %d, want 1", len(wk.Resources)) + } + want := "https://chat.v1337.example/v1/chat/completions" + if wk.Resources[0].Resource != want { + t.Errorf("resources[0].resource = %q, want %q (must agree with openapi.json path)", wk.Resources[0].Resource, want) + } +} + // TestBuildOfferBundles_BrandingOverride pins the per-origin identity merge: // spec.branding fields override the storefront profile on the dedicated // origin's surfaces, empty fields inherit. diff --git a/internal/serviceoffercontroller/offerbundle.go b/internal/serviceoffercontroller/offerbundle.go index ba9c34ba..84786e41 100644 --- a/internal/serviceoffercontroller/offerbundle.go +++ b/internal/serviceoffercontroller/offerbundle.go @@ -197,6 +197,20 @@ func rerootAuthInfo(item map[string]any) map[string]any { return out } +// openAPIRelPathForOfferRoute resolves the OpenAPI-relative path a single +// route maps to, matching openAPIPathsForOffer's per-type path selection so +// /.well-known/x402 and openapi.json agree on the same paid operation path. +// Inference/agent offers always expose the fixed OpenAI-compatible endpoint +// regardless of the declared route table (openAPIPathsForOffer ignores +// spec.routes for these types); every other type maps the route's own path +// through openAPIRelPathForRoute exactly as the OpenAPI builder does. +func openAPIRelPathForOfferRoute(offer *monetizeapi.ServiceOffer, routePath string) string { + if offer.IsInference() || offer.IsAgent() { + return "/v1/chat/completions" + } + return openAPIRelPathForRoute(routePath) +} + // buildOfferWellKnownX402 renders the /.well-known/x402 discovery document: // one resource entry per paid route, each carrying the signable payment // requirements (mirrors the 402 accepts[] fields so a crawler can price the @@ -228,7 +242,7 @@ func buildOfferWellKnownX402(offer *monetizeapi.ServiceOffer) string { desc = offerDescription(offer, "x402 payment-gated service.") } resources = append(resources, map[string]any{ - "resource": origin + joinOpenAPIPath("/", openAPIRelPathForRoute(rt.Path)), + "resource": origin + joinOpenAPIPath("/", openAPIRelPathForOfferRoute(offer, rt.Path)), "type": "http", "method": method, "description": desc, From 58c7db2d8b4882a786c3e16179f495d1aa3f946f Mon Sep 17 00:00:00 2001 From: bussyjd Date: Sat, 18 Jul 2026 18:14:34 +0400 Subject: [PATCH 07/31] fix(stack): don't treat own cluster's ports as conflicts under --force Reported by a teammate during v0.14.0-rc0 field testing. https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v --- internal/stack/backend.go | 7 +++-- internal/stack/backend_k3d.go | 54 ++++++++++++++++++++++++++++++---- internal/stack/backend_k3s.go | 2 +- internal/stack/backend_test.go | 4 +-- internal/stack/stack.go | 2 +- internal/stack/stack_test.go | 37 +++++++++++++++++++++-- 6 files changed, 92 insertions(+), 14 deletions(-) diff --git a/internal/stack/backend.go b/internal/stack/backend.go index c3719504..cb272d6b 100644 --- a/internal/stack/backend.go +++ b/internal/stack/backend.go @@ -24,8 +24,11 @@ type Backend interface { // Name returns the backend identifier (e.g., "k3d", "k3s") Name() string - // Init generates backend-specific cluster configuration files - Init(cfg *config.Config, u *ui.UI, stackID string) error + // Init generates backend-specific cluster configuration files. + // force is true when the caller is about to tear down and recreate the + // existing cluster (obol stack init --force); backends may use it to + // treat ports currently held by that cluster as non-conflicting. + Init(cfg *config.Config, u *ui.UI, stackID string, force bool) error // Up creates or starts the cluster and returns kubeconfig contents Up(cfg *config.Config, u *ui.UI, stackID string) (kubeconfigData []byte, err error) diff --git a/internal/stack/backend_k3d.go b/internal/stack/backend_k3d.go index 9a8baa70..bf46e03d 100644 --- a/internal/stack/backend_k3d.go +++ b/internal/stack/backend_k3d.go @@ -52,7 +52,7 @@ func (b *K3dBackend) Prerequisites(cfg *config.Config) error { return nil } -func (b *K3dBackend) Init(cfg *config.Config, u *ui.UI, stackID string) error { +func (b *K3dBackend) Init(cfg *config.Config, u *ui.UI, stackID string, force bool) error { absDataDir, err := filepath.Abs(cfg.DataDir) if err != nil { return fmt.Errorf("failed to get absolute path for data directory: %w", err) @@ -63,6 +63,8 @@ func (b *K3dBackend) Init(cfg *config.Config, u *ui.UI, stackID string) error { return fmt.Errorf("failed to get absolute path for config directory: %w", err) } + stackName := "obol-stack-" + stackID + // Template k3d config with actual values k3dConfig := embed.K3dConfig k3dConfig = strings.ReplaceAll(k3dConfig, "{{STACK_ID}}", stackID) @@ -71,7 +73,8 @@ func (b *K3dBackend) Init(cfg *config.Config, u *ui.UI, stackID string) error { // Rewrite occupied host-port mappings so k3d cluster create won't fail // when another local stack is already bound to the default ingress ports. - k3dConfig = stripConflictingPorts(k3dConfig, u) + // Under --force, ports held by this cluster's own serverlb are not conflicts. + k3dConfig = stripConflictingPorts(k3dConfig, u, force, stackName) k3dConfigPath := filepath.Join(cfg.ConfigDir, k3dConfigFile) if err := os.WriteFile(k3dConfigPath, []byte(k3dConfig), 0o600); err != nil { @@ -273,8 +276,14 @@ func portBlock(host, container int) string { // stripConflictingPorts removes occupied default k3d ingress mappings. If all // default host ports for a container port are occupied, it adds an ephemeral // host-port mapping so multiple dev stacks can coexist on the same machine. -func stripConflictingPorts(k3dConfig string, u *ui.UI) string { - return rewriteConflictingPorts(k3dConfig, u, hostPortAvailable, pickAvailableHostPort) +// When force is true, ports published by the existing clusterName load-balancer +// are treated as owned (not foreign conflicts). +func stripConflictingPorts(k3dConfig string, u *ui.UI, force bool, clusterName string) string { + owned := map[int]bool{} + if force { + owned = k3dOwnedHostPorts(clusterName) + } + return rewriteConflictingPorts(k3dConfig, u, hostPortAvailable, pickAvailableHostPort, owned) } func rewriteConflictingPorts( @@ -282,6 +291,7 @@ func rewriteConflictingPorts( u *ui.UI, available func(int) bool, pickPort func() (int, error), + owned map[int]bool, ) string { hasMapping := map[int]bool{} @@ -294,7 +304,7 @@ func rewriteConflictingPorts( if !strings.Contains(k3dConfig, block) { continue } - if available(c.hostPort) { + if available(c.hostPort) || owned[c.hostPort] { hasMapping[c.containerPort] = true continue } @@ -368,6 +378,36 @@ func hostPortAvailable(port int) bool { return checkPortsAvailable([]int{port}) == nil } +// k3dOwnedHostPorts returns the host ports currently published by the +// existing obol-managed k3d cluster's load-balancer container — the +// cluster that "--force" is about to tear down and recreate. Used so +// rewriteConflictingPorts does not treat a port as a foreign conflict when +// it is actually held by the very cluster being recreated. Returns an +// empty map (no exceptions) if the container can't be found/inspected — +// callers fall back to the normal availability check in that case. +func k3dOwnedHostPorts(clusterName string) map[int]bool { + out, err := exec.Command("docker", "port", "k3d-"+clusterName+"-serverlb").Output() + if err != nil { + return map[int]bool{} + } + + owned := map[int]bool{} + for _, line := range strings.Split(string(out), "\n") { + line = strings.TrimSpace(line) + + idx := strings.LastIndex(line, ":") + if idx == -1 { + continue + } + + if port, err := strconv.Atoi(strings.TrimSpace(line[idx+1:])); err == nil { + owned[port] = true + } + } + + return owned +} + func pickAvailableHostPort() (int, error) { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { @@ -403,7 +443,9 @@ func ensureK3dPortsAvailable(configPath string, u *ui.UI) { } original := string(data) - updated := stripConflictingPorts(original, u) + // force=false: this runs from Up() right before a fresh k3d cluster create, + // which only happens when IsRunning() is false — any prior instance is gone. + updated := stripConflictingPorts(original, u, false, "") if updated != original { if err := os.WriteFile(configPath, []byte(updated), 0o600); err != nil { diff --git a/internal/stack/backend_k3s.go b/internal/stack/backend_k3s.go index c8e56be5..b536cea7 100644 --- a/internal/stack/backend_k3s.go +++ b/internal/stack/backend_k3s.go @@ -53,7 +53,7 @@ func (b *K3sBackend) Prerequisites(cfg *config.Config) error { return nil } -func (b *K3sBackend) Init(cfg *config.Config, u *ui.UI, stackID string) error { +func (b *K3sBackend) Init(cfg *config.Config, u *ui.UI, stackID string, force bool) error { absDataDir, err := filepath.Abs(cfg.DataDir) if err != nil { return fmt.Errorf("failed to get absolute path for data directory: %w", err) diff --git a/internal/stack/backend_test.go b/internal/stack/backend_test.go index 5ff99ab7..a9b96454 100644 --- a/internal/stack/backend_test.go +++ b/internal/stack/backend_test.go @@ -219,7 +219,7 @@ func TestK3dBackendInit(t *testing.T) { b := &K3dBackend{} u := ui.New(false) - if err := b.Init(cfg, u, "test-stack"); err != nil { + if err := b.Init(cfg, u, "test-stack", false); err != nil { t.Fatalf("K3dBackend.Init() error: %v", err) } @@ -267,7 +267,7 @@ func TestK3sBackendInit(t *testing.T) { b := &K3sBackend{} u := ui.New(false) - if err := b.Init(cfg, u, "my-cluster"); err != nil { + if err := b.Init(cfg, u, "my-cluster", false); err != nil { t.Fatalf("K3sBackend.Init() error: %v", err) } diff --git a/internal/stack/stack.go b/internal/stack/stack.go index 9ef3fcc6..6c1b5464 100644 --- a/internal/stack/stack.go +++ b/internal/stack/stack.go @@ -117,7 +117,7 @@ func Init(cfg *config.Config, u *ui.UI, force bool, backendName string, skipConf } // Generate backend-specific config - if err := backend.Init(cfg, u, stackID); err != nil { + if err := backend.Init(cfg, u, stackID, force); err != nil { return err } diff --git a/internal/stack/stack_test.go b/internal/stack/stack_test.go index 2d4866e8..cb78df55 100644 --- a/internal/stack/stack_test.go +++ b/internal/stack/stack_test.go @@ -175,7 +175,7 @@ func TestRewriteConflictingPorts_PreservesAvailableFallbacks(t *testing.T) { }, func() (int, error) { t.Fatal("should not pick an ephemeral port when fallbacks are available") return 0, nil - }) + }, nil) for _, unexpected := range []string{"- port: 80:80", "- port: 443:443"} { if strings.Contains(got, unexpected) { @@ -207,7 +207,7 @@ func TestRewriteConflictingPorts_PicksEphemeralWhenAllDefaultsBusy(t *testing.T) port := picks[0] picks = picks[1:] return port, nil - }) + }, nil) for _, unexpected := range []string{"- port: 80:80", "- port: 8080:80", "- port: 443:443", "- port: 8443:443"} { if strings.Contains(got, unexpected) { @@ -224,6 +224,39 @@ func TestRewriteConflictingPorts_PicksEphemeralWhenAllDefaultsBusy(t *testing.T) } } +func TestRewriteConflictingPorts_ForceSkipsOwnedPorts(t *testing.T) { + fullConfig := "ports:\n" + + portBlock(80, 80) + + portBlock(8080, 80) + + portBlock(443, 443) + + portBlock(8443, 443) + + "options:\n" + + // Every default host port reads as occupied. 80 and 443 are "owned" — + // held by the existing obol cluster that --force is about to recreate + // — and must be kept. 8080/8443 are genuinely foreign occupants (not + // owned) and must still be stripped. + got := rewriteConflictingPorts(fullConfig, ui.New(false), + func(int) bool { return false }, + func() (int, error) { + t.Fatal("should not need an ephemeral port: both container ports resolve via the owned mapping") + return 0, nil + }, + map[int]bool{80: true, 443: true}, + ) + + for _, expected := range []string{"- port: 80:80", "- port: 443:443"} { + if !strings.Contains(got, expected) { + t.Fatalf("expected owned mapping %s to be preserved under force:\n%s", expected, got) + } + } + for _, unexpected := range []string{"- port: 8080:80", "- port: 8443:443"} { + if strings.Contains(got, unexpected) { + t.Fatalf("expected genuinely foreign-occupied mapping %s to still be stripped:\n%s", unexpected, got) + } + } +} + func TestEnsureK3dPortsAvailable_NoDefaultMappings(t *testing.T) { // Verify the file read/write path stays a no-op for configs that do not // contain the default ingress mappings. From 8427f101fd91614b7e6ba974687cf2f58f44f1fd Mon Sep 17 00:00:00 2001 From: bussyjd Date: Sat, 18 Jul 2026 18:16:58 +0400 Subject: [PATCH 08/31] fix(tunnel): make hostname add idempotent for already-bound hosts Reported by a teammate during v0.14.0-rc0 field testing. https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v --- internal/tunnel/hostnames.go | 25 +++++++++++++++++++++++-- internal/tunnel/hostnames_test.go | 25 ++++++++++++++++++++----- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/internal/tunnel/hostnames.go b/internal/tunnel/hostnames.go index b512ac03..00b1685b 100644 --- a/internal/tunnel/hostnames.go +++ b/internal/tunnel/hostnames.go @@ -43,7 +43,7 @@ type HostnameListResult struct { // HostnameMutationResult is the JSON-serialisable result of Add/RemoveHostname. type HostnameMutationResult struct { Hostname string `json:"hostname"` - Action string `json:"action"` // "added" | "removed" + Action string `json:"action"` // "added" | "removed" | "unchanged" ManagementMode string `json:"management_mode"` Hostnames []HostnameInfo `json:"hostnames"` } @@ -98,7 +98,28 @@ func AddHostname(cfg *config.Config, u *ui.UI, opts AddHostnameOptions) (*Hostna existing := st.HostnameSet() for _, h := range existing { if h == hostname { - return nil, fmt.Errorf("%s is already a tunnel hostname; nothing to do", hostname) + // Idempotent: this hostname is already bound. Still re-render + // the storefront catch-all over the current set — a caller may + // be retrying `tunnel hostname add --offer ns/name` + // after the offer bind, and the catch-all must be swept to + // skip a hostname that has since become offer-bound, or a + // stale catch-all route keeps shadowing the offer's + // dedicated-origin route (Gateway API breaks the PathPrefix-/ + // tie by route age). + if err := CreateStorefront(cfg, existing...); err != nil { + u.Warnf("could not refresh storefront: %v", err) + } + + u.Blank() + u.Successf("Hostname already present: https://%s", hostname) + u.Dim(" No changes needed — this is a no-op.") + + return &HostnameMutationResult{ + Hostname: hostname, + Action: "unchanged", + ManagementMode: st.Management(), + Hostnames: hostnameInfos(existing), + }, nil } } diff --git a/internal/tunnel/hostnames_test.go b/internal/tunnel/hostnames_test.go index 3178ea58..748bf3fb 100644 --- a/internal/tunnel/hostnames_test.go +++ b/internal/tunnel/hostnames_test.go @@ -182,6 +182,8 @@ func TestHostnameInfos_PrimaryAndURL(t *testing.T) { } // --- AddHostname guards (all error before any cluster call) ---------------- +// Exception: TestAddHostname_DuplicateIsIdempotent succeeds as a no-op and +// fail-opens through CreateStorefront (no real cluster required). func TestAddHostname_RejectsEmptyHostname(t *testing.T) { cfg := newHostnameTestConfig(t) @@ -211,16 +213,29 @@ func TestAddHostname_RejectsWithoutPersistentTunnel(t *testing.T) { } } -func TestAddHostname_RejectsDuplicate(t *testing.T) { +func TestAddHostname_DuplicateIsIdempotent(t *testing.T) { cfg := newHostnameTestConfig(t) writeFakeKubeconfig(t, cfg) if err := saveTunnelState(cfg, persistentLocalState("a.example.com")); err != nil { t.Fatalf("save: %v", err) } - // Mixed-case duplicate must still be rejected (normalized match). - _, err := AddHostname(cfg, ui.New(false), AddHostnameOptions{Hostname: "A.Example.com"}) - if err == nil || !strings.Contains(err.Error(), "already a tunnel hostname") { - t.Fatalf("expected duplicate rejection, got %v", err) + // Mixed-case duplicate must still resolve to the same tracked hostname + // (normalized match) and succeed as a no-op instead of erroring. This + // exercises the storefront re-render path too (CreateStorefront fails + // open when kubectl/cluster access isn't available, so it does not + // turn this into an error in a unit-test environment). + result, err := AddHostname(cfg, ui.New(false), AddHostnameOptions{Hostname: "A.Example.com"}) + if err != nil { + t.Fatalf("expected idempotent success for already-bound hostname, got error: %v", err) + } + if result == nil { + t.Fatal("expected a non-nil result") + } + if result.Action != "unchanged" { + t.Fatalf("Action = %q, want %q", result.Action, "unchanged") + } + if len(result.Hostnames) != 1 || result.Hostnames[0].Hostname != "a.example.com" { + t.Fatalf("expected result to report the existing hostname set, got %+v", result.Hostnames) } } From 89082412c7d90b6cfad263fbb39ab96dae0812e6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 01:34:49 +0000 Subject: [PATCH 09/31] chore(deps): update cloudflare/cloudflared docker tag to v2026.7.2 --- internal/embed/infrastructure/cloudflared/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/embed/infrastructure/cloudflared/values.yaml b/internal/embed/infrastructure/cloudflared/values.yaml index 6caca2a4..fbe89d48 100644 --- a/internal/embed/infrastructure/cloudflared/values.yaml +++ b/internal/embed/infrastructure/cloudflared/values.yaml @@ -5,7 +5,7 @@ transport: image: repository: cloudflare/cloudflared - tag: "2026.7.1@sha256:188bb03589a32affed3cf4d0590565ffe67b78866e6b5582574afab2b705bafe" + tag: "2026.7.2@sha256:4f6655284ab3d252b7f28fedb19fe6c8fc82ee5b1295c20ac74d475e5398a52d" metrics: address: "0.0.0.0:2000" From c3afc6ed98a5545ab3faedd39dd808fd634846f4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 01:35:16 +0000 Subject: [PATCH 10/31] chore(deps): update dependency @scalar/api-reference to v1.62.9 --- internal/serviceoffercontroller/scalar_html.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/serviceoffercontroller/scalar_html.go b/internal/serviceoffercontroller/scalar_html.go index 505efbcf..0270fd67 100644 --- a/internal/serviceoffercontroller/scalar_html.go +++ b/internal/serviceoffercontroller/scalar_html.go @@ -14,7 +14,7 @@ import ( // payload never drifts silently. After a bump, refresh the SRI hash below // with scripts/update-scalar-sri.sh (the renovate PR body links it too). // renovate: datasource=npm depName=@scalar/api-reference -const scalarBundleVersion = "1.62.5" +const scalarBundleVersion = "1.62.9" // scalarBundleSRI is the Subresource Integrity hash for the pinned bundle. // The /api page is served over the public tunnel, so the third-party Scalar @@ -24,7 +24,7 @@ const scalarBundleVersion = "1.62.5" // bundle and rewrites this constant). The hash is taken over the exact // (jsdelivr-minified) bytes that the pinned URL serves; it must be refreshed // in lockstep with scalarBundleVersion or the browser will block the script. -const scalarBundleSRI = "sha384-jVBCKhcCfx34USN27x4iQK1SBNdL/HxKq3KuBAxTS4WPaP5w80K4fjpwB+DezJL5" +const scalarBundleSRI = "sha384-oBiNNPts22DP4xagXD0sZE4A/PyTMc+sYcxwx+v692dheBqr5zHQTp58ufiOqJH2" // scalarHTML returns the static HTML shell served at /api. It loads the // pinned @scalar/api-reference bundle from jsdelivr, points it at the From c632485213a52899f2035d3cf9d29ea473ee8a95 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 03:01:59 +0000 Subject: [PATCH 11/31] chore(deps): update ethereum el/cl client updates --- internal/embed/networks/ethereum/helmfile.yaml.gotmpl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/embed/networks/ethereum/helmfile.yaml.gotmpl b/internal/embed/networks/ethereum/helmfile.yaml.gotmpl index 048f8388..8c91f07c 100644 --- a/internal/embed/networks/ethereum/helmfile.yaml.gotmpl +++ b/internal/embed/networks/ethereum/helmfile.yaml.gotmpl @@ -91,19 +91,19 @@ releases: image: {{- if eq .Values.executionClient "reth" }} # renovate: datasource=github-releases depName=paradigmxyz/reth - tag: v2.3.0 + tag: v2.4.1 {{- else if eq .Values.executionClient "geth" }} # renovate: datasource=github-releases depName=ethereum/go-ethereum tag: v1.17.4 {{- else if eq .Values.executionClient "nethermind" }} # renovate: datasource=github-releases depName=NethermindEth/nethermind - tag: "1.39.0" + tag: "1.39.1" {{- else if eq .Values.executionClient "besu" }} # renovate: datasource=github-releases depName=hyperledger/besu tag: "26.7.0" {{- else if eq .Values.executionClient "erigon" }} # renovate: datasource=github-releases depName=erigontech/erigon - tag: v3.5.1 + tag: v3.5.2 {{- end }} persistence: enabled: true @@ -120,7 +120,7 @@ releases: tag: v8.2.0 {{- else if eq .Values.consensusClient "prysm" }} # renovate: datasource=github-releases depName=OffchainLabs/prysm - tag: v7.1.6 + tag: v7.1.7 {{- else if eq .Values.consensusClient "teku" }} # renovate: datasource=github-releases depName=Consensys/teku tag: "26.7.1" From 947c843d92721c85a4b2e58d9d2d9cdb64c2231e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 03:02:08 +0000 Subject: [PATCH 12/31] chore(deps): update obolup.sh dependency updates --- obolup.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/obolup.sh b/obolup.sh index 6f64d8d2..a2efc550 100755 --- a/obolup.sh +++ b/obolup.sh @@ -61,13 +61,13 @@ readonly HELM_VERSION="3.21.3" # renovate: datasource=github-releases depName=k3d-io/k3d readonly K3D_VERSION="5.9.0" # renovate: datasource=github-releases depName=helmfile/helmfile -readonly HELMFILE_VERSION="1.7.0" +readonly HELMFILE_VERSION="1.7.1" # renovate: datasource=github-releases depName=derailed/k9s readonly K9S_VERSION="0.51.0" # renovate: datasource=github-releases depName=databus23/helm-diff readonly HELM_DIFF_VERSION="3.15.10" # renovate: datasource=github-releases depName=ollama/ollama -readonly OLLAMA_VERSION="0.31.2" +readonly OLLAMA_VERSION="0.32.1" # Must match internal/openclaw/OPENCLAW_VERSION (without "v" prefix). # Tested by TestOpenClawVersionConsistency. readonly OPENCLAW_VERSION="2026.4.21" From aa74352ced389bd356d810462227102dfbb008db Mon Sep 17 00:00:00 2001 From: bussyjd Date: Mon, 20 Jul 2026 18:24:31 +0400 Subject: [PATCH 13/31] feat(x402): auth-capture unlock gate + fee revenue metrics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inline first-message fee capture for gate:auth offers: the SIWX session is minted by settling an x402 auth-capture charge (EIP-3009 single-shot, autoCapture) instead of a plain signature; subsequent requests ride the session cookie free. The escrow enforces the client-signed fee split (feeBps -> feeRecipient) on-chain at charge() time. - internal/x402/authcapture.go: auth-capture requirement builder + signed payload validation (validateSignedUnlockRequirement pins every economic field of the client-signed requirement against config) - internal/x402/unlockgate.go: unlock flow — 402 (x402Version 2) -> verify+settle against the signed payload -> mint SIWX cookie - internal/x402/metrics.go: obol_x402_verifier_fee_revenue_atomic_total + settled_volume_atomic_total (network/asset/fee_recipient), excluded from route-pruning - config: global authCaptureUnlock block (offerPrefix, price, payTo, feeRecipient, min/maxFeeBps, captureAuthorizer) Config-gated, off by default. Settlement must use the client's signed 'accepted' requirement verbatim (PaymentInfo hash commits server-issued deadlines; rebuilding drifts them and breaks the signature). Proven end-to-end on Base Sepolia and Base mainnet (on-chain fee split, cookie mint, free-ride, metrics materialization). Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v --- internal/monetizeapi/types.go | 1 + internal/x402/authcapture.go | 123 ++++++++ internal/x402/authcapture_test.go | 486 ++++++++++++++++++++++++++++++ internal/x402/authgate.go | 8 + internal/x402/config.go | 3 + internal/x402/metrics.go | 30 +- internal/x402/unlockgate.go | 281 +++++++++++++++++ internal/x402/verifier.go | 4 + 8 files changed, 930 insertions(+), 6 deletions(-) create mode 100644 internal/x402/authcapture.go create mode 100644 internal/x402/authcapture_test.go create mode 100644 internal/x402/unlockgate.go diff --git a/internal/monetizeapi/types.go b/internal/monetizeapi/types.go index 91f851e5..b2142dec 100644 --- a/internal/monetizeapi/types.go +++ b/internal/monetizeapi/types.go @@ -304,6 +304,7 @@ type ServiceOfferPayment struct { // x402 payment scheme. // +kubebuilder:default="exact" // +kubebuilder:validation:Enum=exact + // TODO(auth-capture): regenerate CRD manifests + wire per-offer unlock config Scheme string `json:"scheme,omitempty"` // Chain identifier for payments (human-friendly). Reconciler resolves // to CAIP-2 format (e.g., "base-sepolia" → "eip155:84532"). diff --git a/internal/x402/authcapture.go b/internal/x402/authcapture.go new file mode 100644 index 00000000..1c18e9da --- /dev/null +++ b/internal/x402/authcapture.go @@ -0,0 +1,123 @@ +package x402 + +import ( + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + x402types "github.com/x402-foundation/x402/go/v2/types" +) + +const ( + defaultCaptureDeadlineSecs = 900 + defaultRefundDeadlineSecs = 1800 +) + +// AuthCaptureUnlockConfig configures the single offer whose SIWX session is +// minted only after an auth-capture payment verifies and settles. +type AuthCaptureUnlockConfig struct { + Enabled bool `yaml:"enabled"` + OfferPrefix string `yaml:"offerPrefix"` + Price string `yaml:"price"` + Network string `yaml:"network"` + PayTo string `yaml:"payTo"` + Asset string `yaml:"asset"` + FeeRecipient string `yaml:"feeRecipient"` + MinFeeBps uint16 `yaml:"minFeeBps"` + MaxFeeBps uint16 `yaml:"maxFeeBps"` + CaptureAuthorizer string `yaml:"captureAuthorizer"` + CaptureDeadlineSecs uint64 `yaml:"captureDeadlineSecs"` + RefundDeadlineSecs uint64 `yaml:"refundDeadlineSecs"` +} + +// Validate applies deadline defaults and rejects unusable auth-capture +// configuration before a payment is sent to the facilitator. +func (c *AuthCaptureUnlockConfig) Validate() error { + if c == nil { + return fmt.Errorf("authCaptureUnlock config is nil") + } + if c.CaptureDeadlineSecs == 0 { + c.CaptureDeadlineSecs = defaultCaptureDeadlineSecs + } + if c.RefundDeadlineSecs == 0 { + c.RefundDeadlineSecs = defaultRefundDeadlineSecs + } + + if c.Enabled { + if c.OfferPrefix == "" { + return fmt.Errorf("offerPrefix must be non-empty when enabled") + } + if c.FeeRecipient == "" { + return fmt.Errorf("feeRecipient must be non-empty when enabled") + } + if c.CaptureAuthorizer == "" { + return fmt.Errorf("captureAuthorizer must be non-empty when enabled") + } + } + if c.MinFeeBps > c.MaxFeeBps { + return fmt.Errorf("minFeeBps %d exceeds maxFeeBps %d", c.MinFeeBps, c.MaxFeeBps) + } + if c.MaxFeeBps > 10000 { + return fmt.Errorf("maxFeeBps %d exceeds 10000", c.MaxFeeBps) + } + if c.MaxFeeBps > 0 && !validNonZeroAddress(c.FeeRecipient) { + return fmt.Errorf("feeRecipient must be a valid non-zero 0x address when maxFeeBps is positive") + } + if !validNonZeroAddress(c.CaptureAuthorizer) { + return fmt.Errorf("captureAuthorizer must be a valid non-zero 0x address") + } + if c.CaptureDeadlineSecs <= 6 { + return fmt.Errorf("captureDeadlineSecs must be greater than 6, got %d", c.CaptureDeadlineSecs) + } + if c.RefundDeadlineSecs < c.CaptureDeadlineSecs { + return fmt.Errorf("refundDeadlineSecs %d is less than captureDeadlineSecs %d", c.RefundDeadlineSecs, c.CaptureDeadlineSecs) + } + + price, _, err := new(big.Float).SetPrec(128).Parse(c.Price, 10) + if err != nil { + return fmt.Errorf("price %q is not a valid decimal: %w", c.Price, err) + } + if price == nil || price.Sign() <= 0 { + return fmt.Errorf("price must be a positive decimal, got %q", c.Price) + } + + return nil +} + +func validNonZeroAddress(address string) bool { + return common.IsHexAddress(address) && common.HexToAddress(address) != (common.Address{}) +} + +// BuildAuthCaptureRequirement builds the strict auth-capture wire shape +// expected by the facilitator. +func BuildAuthCaptureRequirement(chain ChainInfo, asset AssetInfo, c *AuthCaptureUnlockConfig, payTo string, now time.Time) (x402types.PaymentRequirements, error) { + if err := c.Validate(); err != nil { + return x402types.PaymentRequirements{}, fmt.Errorf("validate auth-capture config: %w", err) + } + atomicAmount, err := decimalToAtomic(c.Price, asset.Decimals) + if err != nil { + return x402types.PaymentRequirements{}, fmt.Errorf("invalid price %q: %w", c.Price, err) + } + + return x402types.PaymentRequirements{ + Scheme: "auth-capture", + Network: chain.CAIP2Network, + Asset: asset.Address, + Amount: atomicAmount, + PayTo: payTo, + MaxTimeoutSeconds: int(ClampMaxTimeoutSeconds(int64(c.CaptureDeadlineSecs))), + Extra: map[string]interface{}{ + "name": asset.EIP712Name, + "version": asset.EIP712Version, + "captureAuthorizer": c.CaptureAuthorizer, + "captureDeadline": now.Unix() + int64(c.CaptureDeadlineSecs), + "refundDeadline": now.Unix() + int64(c.RefundDeadlineSecs), + "feeRecipient": c.FeeRecipient, + "minFeeBps": c.MinFeeBps, + "maxFeeBps": c.MaxFeeBps, + "autoCapture": true, + "assetTransferMethod": asset.TransferMethod, + }, + }, nil +} diff --git a/internal/x402/authcapture_test.go b/internal/x402/authcapture_test.go new file mode 100644 index 00000000..bcef5a31 --- /dev/null +++ b/internal/x402/authcapture_test.go @@ -0,0 +1,486 @@ +package x402 + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "sync/atomic" + "testing" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" + x402types "github.com/x402-foundation/x402/go/v2/types" +) + +const ( + testFeeRecipient = "0x1111111111111111111111111111111111111111" + testCaptureAuthorizer = "0x2222222222222222222222222222222222222222" + testUnlockPayer = "0xAbCdEfabcdefABCDefAbcdefabCDefABcDefAbCd" +) + +func validAuthCaptureConfig() AuthCaptureUnlockConfig { + return AuthCaptureUnlockConfig{ + Enabled: true, + OfferPrefix: "/services/agent", + Price: "1.00", + FeeRecipient: testFeeRecipient, + MinFeeBps: 100, + MaxFeeBps: 250, + CaptureAuthorizer: testCaptureAuthorizer, + } +} + +func TestBuildAuthCaptureRequirement_ExtraShape(t *testing.T) { + now := time.Unix(1_700_000_000, 0) + config := validAuthCaptureConfig() + asset := AssetInfo{ + Address: ChainBaseSepolia.USDCAddress, + Symbol: "USDC", + Decimals: 6, + TransferMethod: "eip3009", + EIP712Name: "USDC", + EIP712Version: "2", + } + req, err := BuildAuthCaptureRequirement( + ChainBaseSepolia, + asset, + &config, + "0x3333333333333333333333333333333333333333", + now, + ) + if err != nil { + t.Fatalf("BuildAuthCaptureRequirement: %v", err) + } + if req.Scheme != "auth-capture" { + t.Errorf("Scheme = %q, want auth-capture", req.Scheme) + } + if req.Network != ChainBaseSepolia.CAIP2Network { + t.Errorf("Network = %q, want %q", req.Network, ChainBaseSepolia.CAIP2Network) + } + if req.Amount != "1000000" { + t.Errorf("Amount = %q, want 1000000", req.Amount) + } + if len(req.Extra) != 10 { + t.Fatalf("Extra has %d keys, want 10: %#v", len(req.Extra), req.Extra) + } + + stringValues := map[string]string{ + "name": asset.EIP712Name, + "version": asset.EIP712Version, + "captureAuthorizer": testCaptureAuthorizer, + "feeRecipient": testFeeRecipient, + "assetTransferMethod": asset.TransferMethod, + } + for key, want := range stringValues { + got, ok := req.Extra[key].(string) + if !ok || got != want { + t.Errorf("Extra[%q] = %#v (%T), want string %q", key, req.Extra[key], req.Extra[key], want) + } + } + captureDeadline, ok := req.Extra["captureDeadline"].(int64) + if !ok { + t.Fatalf("captureDeadline = %#v (%T), want int64", req.Extra["captureDeadline"], req.Extra["captureDeadline"]) + } + refundDeadline, ok := req.Extra["refundDeadline"].(int64) + if !ok { + t.Fatalf("refundDeadline = %#v (%T), want int64", req.Extra["refundDeadline"], req.Extra["refundDeadline"]) + } + if captureDeadline <= now.Unix() { + t.Errorf("captureDeadline = %d, want after %d", captureDeadline, now.Unix()) + } + if refundDeadline < captureDeadline { + t.Errorf("refundDeadline = %d, want >= captureDeadline %d", refundDeadline, captureDeadline) + } + if got, ok := req.Extra["minFeeBps"].(uint16); !ok || got != config.MinFeeBps { + t.Errorf("minFeeBps = %#v (%T), want uint16(%d)", req.Extra["minFeeBps"], req.Extra["minFeeBps"], config.MinFeeBps) + } + if got, ok := req.Extra["maxFeeBps"].(uint16); !ok || got != config.MaxFeeBps { + t.Errorf("maxFeeBps = %#v (%T), want uint16(%d)", req.Extra["maxFeeBps"], req.Extra["maxFeeBps"], config.MaxFeeBps) + } + if got, ok := req.Extra["autoCapture"].(bool); !ok || !got { + t.Errorf("autoCapture = %#v (%T), want bool(true)", req.Extra["autoCapture"], req.Extra["autoCapture"]) + } +} + +func TestAuthCaptureUnlockConfig_Validate(t *testing.T) { + tests := []struct { + name string + mutate func(*AuthCaptureUnlockConfig) + wantErr bool + }{ + { + name: "rejects min fee above max fee", + mutate: func(c *AuthCaptureUnlockConfig) { + c.MinFeeBps = 251 + c.MaxFeeBps = 250 + }, + wantErr: true, + }, + { + name: "rejects max fee above 10000", + mutate: func(c *AuthCaptureUnlockConfig) { + c.MaxFeeBps = 10001 + }, + wantErr: true, + }, + { + name: "rejects positive max fee with empty recipient", + mutate: func(c *AuthCaptureUnlockConfig) { + c.FeeRecipient = "" + }, + wantErr: true, + }, + { + name: "rejects zero capture authorizer", + mutate: func(c *AuthCaptureUnlockConfig) { + c.CaptureAuthorizer = "0x0000000000000000000000000000000000000000" + }, + wantErr: true, + }, + { + name: "rejects refund deadline before capture deadline", + mutate: func(c *AuthCaptureUnlockConfig) { + c.CaptureDeadlineSecs = 1000 + c.RefundDeadlineSecs = 500 + }, + wantErr: true, + }, + { + name: "accepts good config and applies defaults", + mutate: func(*AuthCaptureUnlockConfig) {}, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config := validAuthCaptureConfig() + tt.mutate(&config) + wantCaptureDeadline := config.CaptureDeadlineSecs + if wantCaptureDeadline == 0 { + wantCaptureDeadline = defaultCaptureDeadlineSecs + } + wantRefundDeadline := config.RefundDeadlineSecs + if wantRefundDeadline == 0 { + wantRefundDeadline = defaultRefundDeadlineSecs + } + err := config.Validate() + if (err != nil) != tt.wantErr { + t.Fatalf("Validate() error = %v, wantErr %v", err, tt.wantErr) + } + if config.CaptureDeadlineSecs != wantCaptureDeadline { + t.Errorf("CaptureDeadlineSecs = %d, want %d", config.CaptureDeadlineSecs, wantCaptureDeadline) + } + if config.RefundDeadlineSecs != wantRefundDeadline { + t.Errorf("RefundDeadlineSecs = %d, want %d", config.RefundDeadlineSecs, wantRefundDeadline) + } + }) + } +} + +func TestPaidUnlock_InlineFirstMessage(t *testing.T) { + var facilitatorCalls atomic.Int32 + mux := http.NewServeMux() + mux.HandleFunc("/verify", func(w http.ResponseWriter, r *http.Request) { + facilitatorCalls.Add(1) + w.Header().Set("Content-Type", "application/json") + fmt.Fprintf(w, `{"isValid":true,"payer":%q}`, testUnlockPayer) + }) + mux.HandleFunc("/settle", func(w http.ResponseWriter, r *http.Request) { + facilitatorCalls.Add(1) + w.Header().Set("Content-Type", "application/json") + fmt.Fprintf(w, `{"success":true,"payer":%q,"transaction":"0xabc","network":"eip155:84532"}`, testUnlockPayer) + }) + facilitator := httptest.NewServer(mux) + t.Cleanup(facilitator.Close) + upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("CHAT_OK")) + })) + t.Cleanup(upstream.Close) + + unlockConfig := validAuthCaptureConfig() + v, err := NewVerifier(&PricingConfig{ + Wallet: "0x3333333333333333333333333333333333333333", + Chain: "base-sepolia", + FacilitatorURL: facilitator.URL, + Routes: []RouteRule{{ + Pattern: "/services/agent/*", + Gate: "auth", + StripPrefix: unlockConfig.OfferPrefix, + UpstreamURL: upstream.URL, + OfferNamespace: "test", + OfferName: "agent", + }}, + AuthCaptureUnlock: &unlockConfig, + }) + if err != nil { + t.Fatalf("NewVerifier: %v", err) + } + + path := unlockConfig.OfferPrefix + "/chat" + req := httptest.NewRequest(http.MethodGet, path, nil) + w := httptest.NewRecorder() + v.HandleProxy(w, req) + if w.Code != http.StatusPaymentRequired { + t.Fatalf("challenge status = %d, want 402; body: %s", w.Code, w.Body.String()) + } + var challenge struct { + X402Version int `json:"x402Version"` + Accepts []x402types.PaymentRequirements `json:"accepts"` + } + if err := json.Unmarshal(w.Body.Bytes(), &challenge); err != nil { + t.Fatalf("decode challenge: %v", err) + } + if challenge.X402Version != 2 { + t.Errorf("x402Version = %d, want 2", challenge.X402Version) + } + if len(challenge.Accepts) != 1 || challenge.Accepts[0].Scheme != "auth-capture" { + t.Errorf("challenge accepts = %#v, want one auth-capture requirement", challenge.Accepts) + } + if got := facilitatorCalls.Load(); got != 0 { + t.Fatalf("facilitator calls after challenge = %d, want 0", got) + } + + // A real v2 wire PaymentPayload carries the signed requirement in `accepted` + // (scheme/network are read from it); the verifier settles against that, so it + // must pass validateSignedUnlockRequirement (which it does — it IS the + // challenge requirement the verifier just issued). + wireJSON, _ := json.Marshal(map[string]any{ + "x402Version": 2, + "payload": map[string]any{"stub": true}, + "accepted": challenge.Accepts[0], + }) + paymentHeader := base64.StdEncoding.EncodeToString(wireJSON) + req = httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set("X-PAYMENT", paymentHeader) + w = httptest.NewRecorder() + v.HandleProxy(w, req) + if w.Code != http.StatusOK { + t.Fatalf("paid message status = %d, want 200; body: %s", w.Code, w.Body.String()) + } + if !strings.Contains(w.Body.String(), "CHAT_OK") { + t.Fatalf("paid message body = %q, want CHAT_OK", w.Body.String()) + } + + var sessionCookie *http.Cookie + for _, cookie := range w.Result().Cookies() { + if cookie.Name == SIWXSessionCookie { + sessionCookie = cookie + break + } + } + if sessionCookie == nil { + t.Fatal("unlock response did not set obol_siwx cookie") + } + wallet, err := v.siwx.VerifySession(sessionCookie.Value, time.Now()) + if err != nil { + t.Fatalf("VerifySession: %v", err) + } + if want := strings.ToLower(testUnlockPayer); wallet != want { + t.Errorf("session wallet = %q, want %q", wallet, want) + } + labels := prometheus.Labels{ + "network": challenge.Accepts[0].Network, + "asset": challenge.Accepts[0].Asset, + "fee_recipient": testFeeRecipient, + } + if got := testutil.ToFloat64(v.metrics.feeRevenueAtomic.With(labels)); got != 25000 { + t.Errorf("fee revenue atomic = %v, want 25000", got) + } + if got := testutil.ToFloat64(v.metrics.settledVolumeAtomic.With(labels)); got != 1000000 { + t.Errorf("settled volume atomic = %v, want 1000000", got) + } + + paidCalls := facilitatorCalls.Load() + if paidCalls == 0 { + t.Fatal("paid message did not call facilitator") + } + + req = httptest.NewRequest(http.MethodGet, path, nil) + req.AddCookie(sessionCookie) + w = httptest.NewRecorder() + v.HandleProxy(w, req) + if w.Code != http.StatusOK { + t.Fatalf("free-ride status = %d, want 200; body: %s", w.Code, w.Body.String()) + } + if !strings.Contains(w.Body.String(), "CHAT_OK") { + t.Fatalf("free-ride body = %q, want CHAT_OK", w.Body.String()) + } + if got := facilitatorCalls.Load(); got != paidCalls { + t.Errorf("facilitator calls after free ride = %d, want unchanged %d", got, paidCalls) + } +} + +// TestValidateSignedUnlockRequirement guards the money path: the verifier +// settles the requirement the CLIENT signed (payload.accepted), so a client +// must not be able to redirect the fee, underpay, or swap the authorizer. Each +// case takes a genuine requirement, JSON-round-trips it (so Extra numbers are +// float64 exactly as they arrive via x402types.ToPaymentPayload — this is what +// exercises the ex* coercion), flips one field, and asserts rejection. The +// permitted exception is deadline drift (the server reissues fresh deadlines on +// each 402, so the signed ones legitimately differ from `expected`). +func TestValidateSignedUnlockRequirement(t *testing.T) { + now := time.Unix(1_790_000_000, 0) + cfg := validAuthCaptureConfig() + asset := AssetInfo{ + Address: ChainBaseSepolia.USDCAddress, + Symbol: "USDC", + Decimals: 6, + TransferMethod: "eip3009", + EIP712Name: "USDC", + EIP712Version: "2", + } + const payTo = "0x3333333333333333333333333333333333333333" + const attacker = "0x9999999999999999999999999999999999999999" + expected, err := BuildAuthCaptureRequirement(ChainBaseSepolia, asset, &cfg, payTo, now) + if err != nil { + t.Fatalf("BuildAuthCaptureRequirement: %v", err) + } + // signedFrom returns `expected` after a JSON round-trip (Extra numbers -> + // float64), then applies mutate — mirroring the real client->ToPaymentPayload + // decode path so the ex* coercion is actually exercised. + signedFrom := func(mutate func(*x402types.PaymentRequirements)) x402types.PaymentRequirements { + raw, _ := json.Marshal(expected) + var s x402types.PaymentRequirements + _ = json.Unmarshal(raw, &s) + if mutate != nil { + mutate(&s) + } + return s + } + captureDeadline := func(s *x402types.PaymentRequirements) int64 { + return int64(s.Extra["captureDeadline"].(float64)) + } + + tests := []struct { + name string + mutate func(*x402types.PaymentRequirements) + wantErr string // substring; "" means expect nil + }{ + {"accepts genuine requirement", nil, ""}, + {"accepts deadline drift within bounds", func(s *x402types.PaymentRequirements) { + s.Extra["captureDeadline"] = float64(now.Unix() + 7) + }, ""}, + {"rejects wrong scheme", func(s *x402types.PaymentRequirements) { s.Scheme = "exact" }, "scheme"}, + {"rejects wrong network", func(s *x402types.PaymentRequirements) { s.Network = "eip155:1" }, "network"}, + {"rejects wrong asset", func(s *x402types.PaymentRequirements) { s.Asset = attacker }, "asset"}, + {"rejects redirected payTo", func(s *x402types.PaymentRequirements) { s.PayTo = attacker }, "payTo"}, + {"rejects reduced amount", func(s *x402types.PaymentRequirements) { s.Amount = "1" }, "amount"}, + {"rejects swapped captureAuthorizer", func(s *x402types.PaymentRequirements) { s.Extra["captureAuthorizer"] = attacker }, "captureAuthorizer"}, + {"rejects redirected feeRecipient", func(s *x402types.PaymentRequirements) { s.Extra["feeRecipient"] = attacker }, "feeRecipient"}, + {"rejects lowered maxFeeBps", func(s *x402types.PaymentRequirements) { s.Extra["maxFeeBps"] = float64(1) }, "maxFeeBps"}, + {"rejects altered minFeeBps", func(s *x402types.PaymentRequirements) { s.Extra["minFeeBps"] = float64(0) }, "minFeeBps"}, + {"rejects autoCapture false", func(s *x402types.PaymentRequirements) { s.Extra["autoCapture"] = false }, "autoCapture"}, + {"rejects wrong assetTransferMethod", func(s *x402types.PaymentRequirements) { s.Extra["assetTransferMethod"] = "permit2" }, "assetTransferMethod"}, + {"rejects missing extra", func(s *x402types.PaymentRequirements) { s.Extra = nil }, "extra"}, + {"rejects expired captureDeadline", func(s *x402types.PaymentRequirements) { s.Extra["captureDeadline"] = float64(now.Unix() + 6) }, "captureDeadline"}, + {"rejects inverted refundDeadline", func(s *x402types.PaymentRequirements) { s.Extra["refundDeadline"] = float64(captureDeadline(s) - 1) }, "refundDeadline"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + signed := signedFrom(tt.mutate) + err := validateSignedUnlockRequirement(signed, expected, cfg, asset, now.Unix()) + if tt.wantErr == "" { + if err != nil { + t.Fatalf("want nil, got %v", err) + } + return + } + if err == nil { + t.Fatalf("want error containing %q, got nil", tt.wantErr) + } + if !strings.Contains(err.Error(), tt.wantErr) { + t.Errorf("error = %q, want substring %q", err.Error(), tt.wantErr) + } + }) + } +} + +// TestPaidUnlock_RejectsTamperedPayment is the end-to-end guard: a client that +// resubmits a payment whose signed `accepted` redirects the fee to itself must +// be rejected BEFORE the facilitator is called and MUST NOT mint a session. +func TestPaidUnlock_RejectsTamperedPayment(t *testing.T) { + var facilitatorCalls atomic.Int32 + mux := http.NewServeMux() + mux.HandleFunc("/verify", func(w http.ResponseWriter, r *http.Request) { + facilitatorCalls.Add(1) + fmt.Fprint(w, `{"isValid":true}`) + }) + mux.HandleFunc("/settle", func(w http.ResponseWriter, r *http.Request) { + facilitatorCalls.Add(1) + fmt.Fprint(w, `{"success":true}`) + }) + facilitator := httptest.NewServer(mux) + t.Cleanup(facilitator.Close) + upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte("CHAT_OK")) + })) + t.Cleanup(upstream.Close) + + unlockConfig := validAuthCaptureConfig() + v, err := NewVerifier(&PricingConfig{ + Wallet: "0x3333333333333333333333333333333333333333", + Chain: "base-sepolia", + FacilitatorURL: facilitator.URL, + Routes: []RouteRule{{ + Pattern: "/services/agent/*", + Gate: "auth", + StripPrefix: unlockConfig.OfferPrefix, + UpstreamURL: upstream.URL, + OfferNamespace: "test", + OfferName: "agent", + }}, + AuthCaptureUnlock: &unlockConfig, + }) + if err != nil { + t.Fatalf("NewVerifier: %v", err) + } + + path := unlockConfig.OfferPrefix + "/chat" + cw := httptest.NewRecorder() + v.HandleProxy(cw, httptest.NewRequest(http.MethodGet, path, nil)) + var challenge struct { + Accepts []x402types.PaymentRequirements `json:"accepts"` + } + if err := json.Unmarshal(cw.Body.Bytes(), &challenge); err != nil { + t.Fatalf("decode challenge: %v", err) + } + + // Redirect the fee to an attacker in the signed requirement. + tampered := challenge.Accepts[0] + ex := make(map[string]any, len(tampered.Extra)) + for k, val := range tampered.Extra { + ex[k] = val + } + ex["feeRecipient"] = "0x9999999999999999999999999999999999999999" + tampered.Extra = ex + wireJSON, _ := json.Marshal(map[string]any{ + "x402Version": 2, + "payload": map[string]any{"stub": true}, + "accepted": tampered, + }) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set("X-PAYMENT", base64.StdEncoding.EncodeToString(wireJSON)) + w := httptest.NewRecorder() + v.HandleProxy(w, req) + + if w.Code != http.StatusPaymentRequired { + t.Fatalf("status = %d, want 402; body: %s", w.Code, w.Body.String()) + } + if !strings.Contains(w.Body.String(), "payment_policy_mismatch") { + t.Errorf("body = %q, want payment_policy_mismatch", w.Body.String()) + } + if got := facilitatorCalls.Load(); got != 0 { + t.Errorf("facilitator called %d times on tampered payment, want 0 (rejected pre-forward)", got) + } + for _, c := range w.Result().Cookies() { + if c.Name == SIWXSessionCookie { + t.Error("tampered payment minted a session cookie") + } + } +} diff --git a/internal/x402/authgate.go b/internal/x402/authgate.go index 30321a04..96e110d7 100644 --- a/internal/x402/authgate.go +++ b/internal/x402/authgate.go @@ -281,6 +281,14 @@ func (v *Verifier) handleAuthEndpoints(w http.ResponseWriter, r *http.Request) b default: return false } + cfg := v.config.Load() + if cfg != nil && cfg.AuthCaptureUnlock != nil && cfg.AuthCaptureUnlock.Enabled && + strings.TrimSuffix(prefix, "/") == strings.TrimSuffix(cfg.AuthCaptureUnlock.OfferPrefix, "/") { + // ponytail: unlock-gated offer: paid /unlock is the only mint path; free SIWX signin + // suppressed here. Ceiling: one unlock offer (global config); per-offer via CRD is the + // follow-up. + return false + } // Only offers that actually declare an auth route get sign-in // endpoints — everything else falls through to normal route matching diff --git a/internal/x402/config.go b/internal/x402/config.go index 7864c8c9..7281c101 100644 --- a/internal/x402/config.go +++ b/internal/x402/config.go @@ -29,6 +29,9 @@ type PricingConfig struct { // Routes defines per-route pricing rules. First match wins. Routes []RouteRule `yaml:"routes"` + + // AuthCaptureUnlock configures the optional paid agent-unlock endpoint. + AuthCaptureUnlock *AuthCaptureUnlockConfig `yaml:"authCaptureUnlock,omitempty"` } // RoutePayment is one accepted payment option for a route — a single diff --git a/internal/x402/metrics.go b/internal/x402/metrics.go index 69c70c9f..d288f5e9 100644 --- a/internal/x402/metrics.go +++ b/internal/x402/metrics.go @@ -10,12 +10,14 @@ import ( type verifierMetrics struct { registry *prometheus.Registry - requestsTotal *prometheus.CounterVec - paymentRequired *prometheus.CounterVec - paymentVerified *prometheus.CounterVec - paymentFailed *prometheus.CounterVec - chargedRequests *prometheus.CounterVec - lastPaymentSuccess *prometheus.GaugeVec + requestsTotal *prometheus.CounterVec + paymentRequired *prometheus.CounterVec + paymentVerified *prometheus.CounterVec + paymentFailed *prometheus.CounterVec + chargedRequests *prometheus.CounterVec + feeRevenueAtomic *prometheus.CounterVec + settledVolumeAtomic *prometheus.CounterVec + lastPaymentSuccess *prometheus.GaugeVec // paymentFailureReasons splits paymentFailed by WHY (payment_invalid, // facilitator_unreachable, settlement_failed, ...). paymentFailed alone @@ -69,6 +71,20 @@ func newVerifierMetrics() *verifierMetrics { }, []string{"offer_namespace", "offer_name", "chain", "asset_symbol"}, ), + feeRevenueAtomic: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "obol_x402_verifier_fee_revenue_atomic_total", + Help: "Platform fee captured via auth-capture unlock, in atomic token units (fixed-rate: amount*maxFeeBps/10000).", + }, + []string{"network", "asset", "fee_recipient"}, + ), + settledVolumeAtomic: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "obol_x402_verifier_settled_volume_atomic_total", + Help: "Total settled unlock volume, atomic token units.", + }, + []string{"network", "asset", "fee_recipient"}, + ), lastPaymentSuccess: prometheus.NewGaugeVec( prometheus.GaugeOpts{ Name: "obol_x402_verifier_last_payment_success_seconds", @@ -98,6 +114,8 @@ func newVerifierMetrics() *verifierMetrics { m.paymentVerified, m.paymentFailed, m.chargedRequests, + m.feeRevenueAtomic, + m.settledVolumeAtomic, m.lastPaymentSuccess, m.paymentFailureReasons, m.upstreamFailedAfterVerify, diff --git a/internal/x402/unlockgate.go b/internal/x402/unlockgate.go new file mode 100644 index 00000000..bce2ae63 --- /dev/null +++ b/internal/x402/unlockgate.go @@ -0,0 +1,281 @@ +package x402 + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "log" + "math/big" + "net/http" + "strings" + "time" + + "github.com/prometheus/client_golang/prometheus" + x402types "github.com/x402-foundation/x402/go/v2/types" +) + +// The unlock offer is a gate:auth route whose session is minted only by the +// inline auth-capture payment on its first request; handleAuthEndpoints +// suppresses its free SIWX sign-in endpoints. + +// isUnlockOffer reports whether rule is the configured auth-capture unlock offer. +func (v *Verifier) isUnlockOffer(cfg *PricingConfig, rule *RouteRule) bool { + return cfg != nil && cfg.AuthCaptureUnlock != nil && cfg.AuthCaptureUnlock.Enabled && + strings.TrimSuffix(rule.StripPrefix, "/") == strings.TrimSuffix(cfg.AuthCaptureUnlock.OfferPrefix, "/") +} + +// handlePaidUnlock runs the auth-capture pay->settle->mint flow for the unlock +// offer and, on success, proxies the request to the upstream (the first paid +// message's answer). It fully handles the response in all branches. +func (v *Verifier) handlePaidUnlock(w http.ResponseWriter, r *http.Request, rule *RouteRule) { + cfg := v.config.Load() + if cfg == nil || cfg.AuthCaptureUnlock == nil || !cfg.AuthCaptureUnlock.Enabled { + log.Printf("x402-verifier: auth-capture unlock config invalid: offer is disabled or missing") + writeUnlockJSON(w, http.StatusInternalServerError, map[string]any{"error": "unlock_misconfigured"}) + return + } + uc := *cfg.AuthCaptureUnlock + + if err := uc.Validate(); err != nil { + log.Printf("x402-verifier: auth-capture unlock config invalid: %v", err) + writeUnlockJSON(w, http.StatusInternalServerError, map[string]any{"error": "unlock_misconfigured"}) + return + } + + chainName := uc.Network + if chainName == "" { + chainName = cfg.Chain + } + chains := v.chains.Load() + if chains == nil { + log.Printf("x402-verifier: auth-capture unlock chain registry is not loaded") + writeUnlockJSON(w, http.StatusInternalServerError, map[string]any{"error": "unlock_misconfigured"}) + return + } + chain, ok := (*chains)[chainName] + if !ok { + log.Printf("x402-verifier: auth-capture unlock chain %q is not pre-resolved", chainName) + writeUnlockJSON(w, http.StatusInternalServerError, map[string]any{"error": "unlock_misconfigured"}) + return + } + + asset := chain.DefaultAsset() + if uc.Asset != "" { + asset = ResolveAssetInfoForPayment(chain, RoutePayment{AssetAddress: uc.Asset}) + } + payTo := uc.PayTo + if payTo == "" { + payTo = cfg.Wallet + } + req, err := BuildAuthCaptureRequirement(chain, asset, &uc, payTo, time.Now()) + if err != nil { + log.Printf("x402-verifier: build auth-capture unlock requirement: %v", err) + writeUnlockJSON(w, http.StatusInternalServerError, map[string]any{"error": "unlock_misconfigured"}) + return + } + + paymentHeader := r.Header.Get("X-PAYMENT") + if paymentHeader == "" { + paymentHeader = r.Header.Get("PAYMENT-SIGNATURE") + } + if paymentHeader == "" { + writeUnlockJSON(w, http.StatusPaymentRequired, struct { + X402Version int `json:"x402Version"` + Accepts []x402types.PaymentRequirements `json:"accepts"` + Error string `json:"error"` + }{ + // auth-capture is a v2 scheme (the verifier settles it against the + // facilitator with x402Version 2); the challenge MUST advertise v2 so + // a standard @x402 client builds a v2 payload (its auth-capture scheme + // rejects x402Version != 2). + X402Version: 2, + Accepts: []x402types.PaymentRequirements{req}, + Error: "payment required to unlock this agent", + }) + return + } + + payloadBytes, err := base64.StdEncoding.DecodeString(paymentHeader) + if err != nil { + writeUnlockJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid_payment_header"}) + return + } + // Settle against the requirement the client actually SIGNED + // (payload.accepted), not the freshly rebuilt `req`: auth-capture's signed + // PaymentInfo hash commits the server-issued captureDeadline/refundDeadline, + // which drift between the 402 and this request (both call time.Now) and would + // invalidate the signature. Validate the client did not tamper with the + // economically-sensitive fields against our policy (`req`, built from config) + // before forwarding — the facilitator does not know obol's intended + // feeRecipient/payTo/amount, so a blind forward would let a client redirect + // the fee or underpay. + payload, err := x402types.ToPaymentPayload(payloadBytes) + if err != nil { + writeUnlockJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid_payment_header"}) + return + } + signedReq := payload.Accepted + if err := validateSignedUnlockRequirement(signedReq, req, uc, asset, time.Now().Unix()); err != nil { + log.Printf("x402-verifier: unlock payment policy mismatch: %v", err) + writeUnlockJSON(w, http.StatusPaymentRequired, map[string]any{"error": "payment_policy_mismatch", "detail": err.Error()}) + return + } + verifyResp, err := facilitatorVerify(r.Context(), &http.Client{Timeout: facilitatorVerifyTimeout}, cfg.FacilitatorURL, payloadBytes, signedReq) + if err != nil { + writeUnlockJSON(w, http.StatusPaymentRequired, map[string]any{"error": "payment_invalid", "detail": err.Error()}) + return + } + if !verifyResp.IsValid { + writeUnlockJSON(w, http.StatusPaymentRequired, map[string]any{ + "error": "payment_invalid", + "detail": facilitatorDetail(verifyResp.InvalidReason, verifyResp.InvalidMessage), + }) + return + } + + settleResp, err := facilitatorSettle(r.Context(), &http.Client{Timeout: facilitatorSettleTimeout}, cfg.FacilitatorURL, payloadBytes, signedReq) + if err != nil { + writeUnlockJSON(w, http.StatusBadGateway, map[string]any{"error": "settle_failed", "detail": err.Error()}) + return + } + if !settleResp.Success { + writeUnlockJSON(w, http.StatusBadGateway, map[string]any{ + "error": "settle_failed", + "detail": facilitatorDetail(settleResp.ErrorReason, settleResp.ErrorMessage), + }) + return + } + + amount, ok := new(big.Int).SetString(req.Amount, 10) + if !ok { + log.Printf("x402-verifier: parse auth-capture unlock amount %q for metrics", req.Amount) + } else { + feeBps := uc.MaxFeeBps + fee := new(big.Int).Div(new(big.Int).Mul(amount, big.NewInt(int64(feeBps))), big.NewInt(10000)) + labels := prometheus.Labels{"network": req.Network, "asset": req.Asset, "fee_recipient": uc.FeeRecipient} + v.metrics.settledVolumeAtomic.With(labels).Add(bigIntToFloat(amount)) + v.metrics.feeRevenueAtomic.With(labels).Add(bigIntToFloat(fee)) + } + + wallet := settleResp.Payer + if wallet == "" { + wallet = verifyResp.Payer + } + token := v.siwx.MintSession(wallet, time.Now()) + http.SetCookie(w, &http.Cookie{ + Name: SIWXSessionCookie, + Value: token, + Path: "/", + MaxAge: int(DefaultSIWXSessionTTL.Seconds()), + HttpOnly: true, + Secure: true, + SameSite: http.SameSiteStrictMode, + }) + r.Header.Set(HeaderVerifiedWallet, wallet) + r.Header.Set(HeaderPaymentPayer, wallet) + proxy, err := buildUpstreamProxy(rule) + if err != nil { + log.Printf("x402-verifier: build upstream proxy for %s/%s: %v", rule.OfferNamespace, rule.OfferName, err) + writeUnlockJSON(w, http.StatusInternalServerError, map[string]any{"error": "upstream_unavailable"}) + return + } + proxy.ServeHTTP(&statusRecorder{ResponseWriter: w, status: http.StatusOK}, r) +} + +func bigIntToFloat(x *big.Int) float64 { + f, _ := new(big.Float).SetInt(x).Float64() + return f +} + +// validateSignedUnlockRequirement checks the requirement the client signed +// (payload.accepted) against the offer's policy (expected, built from config). +// It permits the client's server-issued deadlines to differ from `expected`'s +// freshly rebuilt ones (only sanity-checking them), but pins every +// economically-sensitive field so a client cannot redirect the fee, underpay, +// or swap the authorizer. Returns nil when the signed requirement is safe to +// settle. +func validateSignedUnlockRequirement(signed, expected x402types.PaymentRequirements, uc AuthCaptureUnlockConfig, asset AssetInfo, now int64) error { + if signed.Scheme != "auth-capture" { + return fmt.Errorf("scheme %q, want auth-capture", signed.Scheme) + } + if signed.Network != expected.Network { + return fmt.Errorf("network %q, want %q", signed.Network, expected.Network) + } + if !strings.EqualFold(signed.Asset, expected.Asset) { + return fmt.Errorf("asset %q, want %q", signed.Asset, expected.Asset) + } + if !strings.EqualFold(signed.PayTo, expected.PayTo) { + return fmt.Errorf("payTo %q, want %q", signed.PayTo, expected.PayTo) + } + if signed.Amount != expected.Amount { + return fmt.Errorf("amount %q, want %q", signed.Amount, expected.Amount) + } + ex := signed.Extra + if ex == nil { + return fmt.Errorf("missing extra") + } + if !strings.EqualFold(exStr(ex, "captureAuthorizer"), uc.CaptureAuthorizer) { + return fmt.Errorf("captureAuthorizer mismatch") + } + if !strings.EqualFold(exStr(ex, "feeRecipient"), uc.FeeRecipient) { + return fmt.Errorf("feeRecipient mismatch") + } + if exU64(ex, "minFeeBps") != uint64(uc.MinFeeBps) { + return fmt.Errorf("minFeeBps %d, want %d", exU64(ex, "minFeeBps"), uc.MinFeeBps) + } + if exU64(ex, "maxFeeBps") != uint64(uc.MaxFeeBps) { + return fmt.Errorf("maxFeeBps %d, want %d", exU64(ex, "maxFeeBps"), uc.MaxFeeBps) + } + if b, _ := ex["autoCapture"].(bool); !b { + return fmt.Errorf("autoCapture not true") + } + if exStr(ex, "assetTransferMethod") != asset.TransferMethod { + return fmt.Errorf("assetTransferMethod %q, want %q", exStr(ex, "assetTransferMethod"), asset.TransferMethod) + } + cd, rd := exI64(ex, "captureDeadline"), exI64(ex, "refundDeadline") + if cd <= now+6 { + return fmt.Errorf("captureDeadline %d not > now+6 (%d)", cd, now+6) + } + if rd < cd { + return fmt.Errorf("refundDeadline %d < captureDeadline %d", rd, cd) + } + return nil +} + +// exStr/exF64/exU64/exI64 coerce a JSON-decoded Extra value (numbers arrive as +// float64) to a concrete type; missing/wrong-typed keys yield the zero value. +func exStr(m map[string]interface{}, k string) string { s, _ := m[k].(string); return s } + +func exF64(m map[string]interface{}, k string) float64 { + switch v := m[k].(type) { + case float64: + return v + case json.Number: + f, _ := v.Float64() + return f + case int64: + return float64(v) + case int: + return float64(v) + } + return 0 +} + +func exU64(m map[string]interface{}, k string) uint64 { return uint64(exF64(m, k)) } +func exI64(m map[string]interface{}, k string) int64 { return int64(exF64(m, k)) } + +func facilitatorDetail(reason, message string) string { + detail := strings.TrimSpace(strings.TrimSpace(reason) + " " + strings.TrimSpace(message)) + if detail == "" { + return "facilitator rejected the request" + } + return detail +} + +func writeUnlockJSON(w http.ResponseWriter, status int, body any) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + if err := json.NewEncoder(w).Encode(body); err != nil { + log.Printf("x402-verifier: encode auth-capture unlock response: %v", err) + } +} diff --git a/internal/x402/verifier.go b/internal/x402/verifier.go index 7873837e..efb5afd4 100644 --- a/internal/x402/verifier.go +++ b/internal/x402/verifier.go @@ -385,6 +385,10 @@ func (v *Verifier) HandleProxy(w http.ResponseWriter, r *http.Request) { if rule.IsAuth() { wallet, err := v.siwx.Authenticate(r, requestHost(r), time.Now()) if err != nil { + if v.isUnlockOffer(cfg, rule) { + v.handlePaidUnlock(w, r, rule) + return + } v.writeSIWXChallenge(w, r, rule, err) return } From 0e157e2c8125806d3953d45f29d75531f52c6859 Mon Sep 17 00:00:00 2001 From: bussyjd <145845+bussyjd@users.noreply.github.com> Date: Fri, 26 Jun 2026 12:18:02 +0400 Subject: [PATCH 14/31] fix(x402): drop the model from agent 402 copy + pay-agent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An Obol Agent (type=agent) runs its own model, skills, and memory — the buyer never selects one, and Hermes ignores the chat-completions `model` field (resolved from its own config). Surfacing the underlying model in the 402 page is noise and exposes an implementation detail, and pay-agent's required `--model` flag had no effect on agent calls. - paymentrequired.go agentCopy: remove '(running )', the '"model":' line in the example body, and '--model ' from the pay-agent example. The agent copy is now model-free. - buy-x402 buy.py: pay-agent no longer accepts/sends --model (synthesised body is just {messages, stream}); usage strings updated. - buy-x402 SKILL.md: pay-agent documented without --model. go build + x402/buyer/embed tests green. Claude-Session: https://claude.ai/code/session_01XgUndZjSoxr2jNNGG5sVYD (cherry picked from commit e605f55cc07eab4899180ad270fe86d733d8b77c) --- internal/buyprompts/buyprompts.go | 11 ++++++---- internal/embed/skills/buy-x402/SKILL.md | 4 ++-- internal/embed/skills/buy-x402/scripts/buy.py | 22 +++++++++---------- internal/x402/paymentrequired.go | 12 +++++----- 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/internal/buyprompts/buyprompts.go b/internal/buyprompts/buyprompts.go index dbd498d6..4ce782d6 100644 --- a/internal/buyprompts/buyprompts.go +++ b/internal/buyprompts/buyprompts.go @@ -158,7 +158,10 @@ func modelOr(in Input, placeholder string) string { } func agentBlock(in Input) Block { - modelFlag := modelOr(in, "") + // Deliberately no model anywhere: an Obol Agent runs its own pinned + // model, skills, and memory — the buyer never picks one, pay-agent takes + // no --model, and the agent ignores the chat-completions `model` field, + // so the wire example omits it too. return Block{ CallShape: CallShape{ Method: "POST", @@ -171,8 +174,8 @@ func agentBlock(in Input) Block { "Use the buy-x402 skill's `pay-agent` command to buy one round of work from this "+ "Obol Agent — it has its own skills, tools, and memory, not just a model. Edit the "+ "message, then run:\n\n"+ - "pay-agent %s --model %q --message %q", - in.URL, modelFlag, task(in), + "pay-agent %s --message %q", + in.URL, task(in), ), PromptGenericLLM: fmt.Sprintf( "Help me call the Obol Agent at %s — it's an autonomous agent (tools + skills + memory), "+ @@ -187,7 +190,7 @@ func agentBlock(in Input) Block { in.URL, task(in), ), }, - Example: ChatExample(in.URL, in.Model, in.TaskExample), + Example: ChatExample(in.URL, "", in.TaskExample), } } diff --git a/internal/embed/skills/buy-x402/SKILL.md b/internal/embed/skills/buy-x402/SKILL.md index ecaf385b..f63c2142 100644 --- a/internal/embed/skills/buy-x402/SKILL.md +++ b/internal/embed/skills/buy-x402/SKILL.md @@ -21,7 +21,7 @@ python3 ${OBOL_SKILLS_DIR:-/data/.openclaw/skills}/buy-x402/scripts/buy.py go `** — single-shot. Probe the URL, sign **one** payment authorization, attach `X-PAYMENT`, send the request, return the response. Stateless. Use for `type:http` services and any one-off purchase. Max loss = price of one request. Settlement normally lands only after the request succeeds — but a facilitator can submit the settle tx on-chain and *then* fail the request. When that happens the failure report prints `⚠️ SETTLEMENT MAY HAVE COMPLETED ON-CHAIN` with the tx hash: verify with `balance --chain ` before retrying (mechanism: docs/observability.md, "Verify settlement against the chain"). Applies to `pay-agent` too. -- **`pay-agent --model `** — single-shot paid **streaming** agent call. Same payment shape as `pay` (one auth, X-PAYMENT, max-loss = price), but POSTs to `/v1/chat/completions` with `stream: true` and forwards every SSE event verbatim to stdout as it arrives. Use this for `type:agent` ServiceOffers when the calling agent wants to consume the response *itself* (memory, tool-call traces, partial results) instead of routing it through LiteLLM as a paid alias. Default HTTP read timeout is **1 hour** — agent calls can legitimately run for many minutes; override with `--timeout `. +- **`pay-agent `** — single-shot paid **streaming** agent call. Same payment shape as `pay` (one auth, X-PAYMENT, max-loss = price), but POSTs to `/v1/chat/completions` with `stream: true` and forwards every SSE event verbatim to stdout as it arrives. No `--model`: a `type:agent` offer runs its own model (the request `model` field is ignored), so you only send a prompt. Use this for `type:agent` ServiceOffers when the calling agent wants to consume the response *itself* (memory, tool-call traces, partial results) instead of routing it through LiteLLM as a paid alias. Default HTTP read timeout is **1 hour** — agent calls can legitimately run for many minutes; override with `--timeout `. - **`buy `** — pre-authorize a budget. Sign **N** authorizations up front (the buyer pays nothing yet), declare them in a `PurchaseRequest` CR, let the `x402-buyer` sidecar redeem them transparently as the agent calls the model through LiteLLM at `paid/`. Use for long-running paid inference. Max loss = N × price (only as vouchers are spent); runtime path holds zero signer access. - **`buy --model --set-default`** — same as `buy` above, then adopt `paid/` as the agent's **own primary model**, in-pod, by itself: an atomic `hermes config set model.default` that Hermes re-reads per request (effective next chat turn, **no restart**, no host-side `obol model prefer`/`obol model sync`). Refuses if the model isn't selectable in LiteLLM. Pair with `--auto-refill` so the primary model doesn't brick when the pre-authorized budget runs out. @@ -224,7 +224,7 @@ python3 ${OBOL_SKILLS_DIR:-/data/.openclaw/skills}/buy-x402/scripts/buy.py maint | `go [--message ] [--data ] [--method GET\|POST]` | Probe, detect offer type (agent / chat inference / http), dispatch to the right flow | | `probe [--model ] [--type http\|inference\|agent] [--method GET\|POST]` | Send request without payment, parse 402 response for pricing | | `pay [--type http\|inference] [--method GET\|POST] [--data ]` | Single-shot paid request: sign 1 auth, attach X-PAYMENT, send | -| `pay-agent --model [--message \| --data ] [--timeout ]` | Single-shot paid streaming agent call: SSE events flush to stdout as they arrive (default timeout 1h) | +| `pay-agent [--message \| --data ] [--timeout ]` | Single-shot paid streaming agent call (no `--model` — the agent runs its own): SSE events flush to stdout as they arrive (default timeout 1h) | | `buy --endpoint --model [--budget ] [--count N]` | Pre-sign auths, create/update `PurchaseRequest`, expose `paid/`. `--budget` takes atomic units or token units (`'1.5 USDC'`) | | `buy --endpoint --model --set-default [--auto-refill]` | As above, then set `paid/` as the agent's own primary model in-pod (no restart, no host CLI) | | `process \| --all` | Reconcile `autoRefill` policies against live `x402-buyer` status | diff --git a/internal/embed/skills/buy-x402/scripts/buy.py b/internal/embed/skills/buy-x402/scripts/buy.py index f2929df6..9a560a16 100644 --- a/internal/embed/skills/buy-x402/scripts/buy.py +++ b/internal/embed/skills/buy-x402/scripts/buy.py @@ -2647,7 +2647,7 @@ def cmd_pay(url, method="GET", data=None, kind="http", network=None, timeout=Non sys.exit(1) -def cmd_pay_agent(url, messages=None, model_id=None, network=None, timeout=None, body=None, token=None, payment_option=None): +def cmd_pay_agent(url, messages=None, network=None, timeout=None, body=None, token=None, payment_option=None): """Single-shot paid streaming agent call: probe -> sign one auth -> SSE-stream. Sibling of `cmd_pay` for `type=agent` ServiceOffers. Differences from @@ -2689,27 +2689,24 @@ def cmd_pay_agent(url, messages=None, model_id=None, network=None, timeout=None, # Force streaming on. cmd_pay handles non-streaming; cmd_pay_agent # exists precisely to stream. parsed_body["stream"] = True - if model_id and not parsed_body.get("model"): - parsed_body["model"] = model_id else: if not messages: print( "Error: --message (or --data ) is required for `pay-agent`.\n" - "Example: pay-agent --model qwen3.5:9b --message 'summarize the docs'", + "Example: pay-agent --message 'summarize the docs'", file=sys.stderr, ) sys.exit(1) - if not model_id: - print("Error: --model is required when using --message.", file=sys.stderr) - sys.exit(1) + # type=agent ServiceOffers run their own model — there is nothing to + # select and the agent ignores any `model` field — so pay-agent sends + # only the prompt. parsed_body = { - "model": model_id, "messages": [{"role": "user", "content": messages}], "stream": True, } print(f"Probing {url} ...") - pricing = _probe_endpoint(url, model_id=model_id or "test", kind="inference") + pricing = _probe_endpoint(url, model_id="probe", kind="inference") if not pricing: print("Failed to get x402 pricing.", file=sys.stderr) sys.exit(1) @@ -3095,7 +3092,9 @@ def usage(): print(" pay [--type http|inference] [--method GET|POST] [--data ''] [--timeout ]") print(" [--token ] [--network ] [--payment-option ]") print(" Single-shot paid request (sign 1 auth, attach X-PAYMENT)") - print(" pay-agent --model [--message '' | --data ''] [--timeout ]") + print(" Multi-currency offers: pick which asset/price to pay with") + print(" --token/--network/--payment-option (probe to see options)") + print(" pay-agent [--message '' | --data ''] [--timeout ]") print(" [--token ] [--network ] [--payment-option ]") print(" Single-shot paid streaming agent call (POST /v1/chat/completions,") print(" stream: true). Each SSE event flushes to stdout. Default timeout 1h.") @@ -3190,7 +3189,7 @@ def usage(): positional, opts = parse_flags(rest) if not positional: print( - "Usage: pay-agent --model [--message '' | --data ''] " + "Usage: pay-agent [--message '' | --data ''] " "[--network ] [--timeout ]", file=sys.stderr, ) @@ -3207,7 +3206,6 @@ def usage(): cmd_pay_agent( positional[0], messages=opts.get("message"), - model_id=opts.get("model"), network=opts.get("network"), timeout=timeout, body=opts.get("data"), diff --git a/internal/x402/paymentrequired.go b/internal/x402/paymentrequired.go index 087c0c35..2aed32f1 100644 --- a/internal/x402/paymentrequired.go +++ b/internal/x402/paymentrequired.go @@ -457,17 +457,15 @@ func inferenceCopy(url, siteURL string, d PaymentDisplay) typeCopy { // Other-AI-Agent prompt cards drive the action, and a chat-completions // example sits next to the raw x402 JSON in the Pay-manually card to // make the wire shape obvious to readers walking the spec by hand. -func agentCopy(url, siteURL string, d PaymentDisplay) typeCopy { - // pay-agent requires a --model value, but an agent runs its own pinned - // model server-side and ignores the field, so we don't editorialize about - // which model the seller uses — buyprompts fills the required flag (the - // seller's model when known, a placeholder otherwise) and hands the buyer - // a command that runs as-is with a concrete example task they can edit. +func agentCopy(url, siteURL string, _ PaymentDisplay) typeCopy { + // Deliberately no model: an Obol Agent runs its own pinned model, skills, + // and memory — the buyer never picks one, and the agent ignores the + // chat-completions `model` field. buyprompts' agent block omits the model + // from the pay-agent command and the wire example entirely. block := buyprompts.Build(buyprompts.Input{ Type: "agent", URL: url, SiteURL: siteURL, - Model: sanitizeDisplayToken(d.Model, ""), }) return typeCopy{ From 35ee8f0d700d2881ebfc8bbdeab6c9b51ad5f5bd Mon Sep 17 00:00:00 2001 From: bussyjd <145845+bussyjd@users.noreply.github.com> Date: Fri, 26 Jun 2026 12:46:47 +0400 Subject: [PATCH 15/31] fix(x402): stop surfacing agentModel in the 402 extra MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mergeAgentExtras no longer adds extra.agentModel — an Obol Agent runs its own model and the buyer never selects one, so the model id is an internal detail, not buyer-facing info (it also rendered in the HTML 402 page's raw-JSON card). agentSkills/agentRuntime still surface so clients can tell it's an agent. Claude-Session: https://claude.ai/code/session_01XgUndZjSoxr2jNNGG5sVYD (cherry picked from commit 92716d69b331b5218185e0a56684bf6f1b7745f0) --- internal/x402/agent_extras_test.go | 15 +++++++++------ internal/x402/verifier.go | 13 ++++++------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/internal/x402/agent_extras_test.go b/internal/x402/agent_extras_test.go index a2babec1..402ca76a 100644 --- a/internal/x402/agent_extras_test.go +++ b/internal/x402/agent_extras_test.go @@ -24,7 +24,7 @@ func TestMergeAgentExtras_Noop_NonAgentRule(t *testing.T) { } } -func TestMergeAgentExtras_AddsAllAgentFields(t *testing.T) { +func TestMergeAgentExtras_AddsAgentFieldsButNotModel(t *testing.T) { req := x402types.PaymentRequirements{Extra: map[string]any{}} rule := &RouteRule{ AgentModel: "qwen3.5:9b", @@ -34,8 +34,8 @@ func TestMergeAgentExtras_AddsAllAgentFields(t *testing.T) { mergeAgentExtras(&req, rule) - if got := req.Extra["agentModel"]; got != "qwen3.5:9b" { - t.Errorf("agentModel = %v, want qwen3.5:9b", got) + if _, ok := req.Extra["agentModel"]; ok { + t.Error("agentModel must not be surfaced — the underlying model is an internal detail, not buyer-facing") } if got := req.Extra["agentRuntime"]; got != "hermes" { t.Errorf("agentRuntime = %v", got) @@ -55,14 +55,17 @@ func TestMergeAgentExtras_InitialisesNilExtra(t *testing.T) { // mergeAgentExtras must still cope with a nil map for callers that // build PaymentRequirements directly (e.g. tests). req := x402types.PaymentRequirements{} - rule := &RouteRule{AgentModel: "qwen3.5:9b"} + rule := &RouteRule{AgentRuntime: "hermes"} mergeAgentExtras(&req, rule) if req.Extra == nil { t.Fatal("Extra not initialised") } - if req.Extra["agentModel"] != "qwen3.5:9b" { - t.Errorf("agentModel missing: %+v", req.Extra) + if _, ok := req.Extra["agentModel"]; ok { + t.Error("agentModel must not be surfaced") + } + if req.Extra["agentRuntime"] != "hermes" { + t.Errorf("agentRuntime missing: %+v", req.Extra) } } diff --git a/internal/x402/verifier.go b/internal/x402/verifier.go index efb5afd4..f9c719b9 100644 --- a/internal/x402/verifier.go +++ b/internal/x402/verifier.go @@ -669,19 +669,18 @@ func patternToPrefix(pattern string) string { return strings.TrimSuffix(pattern, "*") } -// mergeAgentExtras adds the agent fields from a RouteRule to the -// requirement's Extra map so buyers probing a 402 see which model and -// skills are powering the offer. No-op for non-agent rules. +// mergeAgentExtras adds agent metadata from a RouteRule to the requirement's +// Extra map so buyers probing a 402 can tell it's an agent. The underlying +// model is intentionally NOT surfaced: an Obol Agent runs its own model and +// the buyer never selects one, so the model id is an internal detail, not +// buyer-facing info. No-op for non-agent rules. func mergeAgentExtras(req *x402types.PaymentRequirements, rule *RouteRule) { - if rule.AgentModel == "" && len(rule.AgentSkills) == 0 && rule.AgentRuntime == "" { + if len(rule.AgentSkills) == 0 && rule.AgentRuntime == "" { return } if req.Extra == nil { req.Extra = make(map[string]interface{}) } - if rule.AgentModel != "" { - req.Extra["agentModel"] = rule.AgentModel - } if len(rule.AgentSkills) > 0 { // Materialise as []any so JSON marshalling produces a proper array // regardless of whether the source loaded it from yaml or From 8fafdbb4df2ebdd999b18350e8655a20f1a3617c Mon Sep 17 00:00:00 2001 From: bussyjd <145845+bussyjd@users.noreply.github.com> Date: Fri, 26 Jun 2026 12:54:49 +0400 Subject: [PATCH 16/31] fix(x402): keep the internal model out of the bazaar example for agent offers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For agent offers the buyer never selects a model — the agent runs its own and ignores the chat-completions `model` field — so the bazaar discovery example now seeds the neutral 'your-model-id' placeholder instead of the real upstream id (which also rendered in the HTML 402 page's embedded raw-JSON card). Inference offers are unchanged: there the model IS buyer-selectable, so the real id stays. Claude-Session: https://claude.ai/code/session_01XgUndZjSoxr2jNNGG5sVYD (cherry picked from commit 0f86017059dd68f71f1d42fef42647c346ae2bb6) --- internal/x402/bazaar.go | 9 ++++++++- internal/x402/bazaar_test.go | 2 +- internal/x402/paymentrequired_test.go | 14 +++++++++++--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/internal/x402/bazaar.go b/internal/x402/bazaar.go index db3bf8ee..29382e2d 100644 --- a/internal/x402/bazaar.go +++ b/internal/x402/bazaar.go @@ -54,8 +54,15 @@ func WithBazaar(extensions map[string]any, offerType, model string) map[string]a // gets the generic operator-defined JSON shape. func BuildBazaarExtension(offerType, model string) map[string]any { switch normalizeOfferType(offerType) { - case "inference", "agent": + case "inference": + // The buyer selects the model (paid/), so the real id + // is buyer-facing and correct to advertise in the example. return bazaarChatCompletions(model) + case "agent": + // An agent runs its own model and ignores the request `model` field, + // so the model id is an internal detail, not buyer-facing. Seed the + // chat example with the neutral placeholder rather than the real id. + return bazaarChatCompletions("") default: return bazaarGenericJSON() } diff --git a/internal/x402/bazaar_test.go b/internal/x402/bazaar_test.go index e1269af0..0d53b9da 100644 --- a/internal/x402/bazaar_test.go +++ b/internal/x402/bazaar_test.go @@ -33,7 +33,7 @@ func TestBuildBazaarExtension(t *testing.T) { wantModel string }{ {"inference", "llama-3-70b", "llama-3-70b"}, - {"agent", "qwen3.5:9b", "qwen3.5:9b"}, + {"agent", "qwen3.5:9b", "your-model-id"}, // agent model is internal — placeholder, never the real id {"inference", "", "your-model-id"}, {"http", "", ""}, {"", "", ""}, // static config routes fall back to the generic shape diff --git a/internal/x402/paymentrequired_test.go b/internal/x402/paymentrequired_test.go index ef969f47..e0c98a76 100644 --- a/internal/x402/paymentrequired_test.go +++ b/internal/x402/paymentrequired_test.go @@ -252,9 +252,10 @@ func TestHTMLAware_AgentShowsChatCompletionsInPayManually(t *testing.T) { } mustContain(t, body, "Pay manually (raw HTTP 402)") mustContain(t, body, "Obol Agents accept OpenAI-style chat-completions bodies") - // Example chat-completions body (JSON snippet inside
; html/template
-	// escapes the quotes).
-	mustContain(t, body, `"model": "qwen3.5:9b"`)
+	// The agent runs its own model — the real id must never leak into the
+	// 402 page (neither the hand-written example nor the embedded bazaar
+	// JSON). The bazaar example carries a neutral placeholder instead.
+	mustNotContain(t, body, "qwen3.5:9b")
 	mustContain(t, body, `"messages":`)
 
 	// Lede uses the operator-facing copy and links to docs.obol.org.
@@ -387,6 +388,13 @@ func mustContain(t *testing.T, haystack, needle string) {
 	}
 }
 
+func mustNotContain(t *testing.T, haystack, needle string) {
+	t.Helper()
+	if strings.Contains(haystack, needle) {
+		t.Errorf("body unexpectedly contains %q", needle)
+	}
+}
+
 // sanitizeDisplayToken must pass real model ids / offer names through
 // untouched while collapsing anything carrying shell metacharacters to the
 // placeholder — the values land in copy-pasteable commands on the public

From 2cddee078434437ce22deb386fa551550413b058 Mon Sep 17 00:00:00 2001
From: bussyjd <145845+bussyjd@users.noreply.github.com>
Date: Fri, 26 Jun 2026 13:08:01 +0400
Subject: [PATCH 17/31] fix(serviceoffer-controller): drop the internal model
 from /skill.md for agent offers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The /skill.md catalog (and its Service Details section) showed the agent's
underlying model in the Model column / **Model** bullet. An agent runs its own
model and ignores the request `model` field, so the id is an internal detail —
agent rows now render '—' and omit the **Model** bullet. Inference offers keep
their model (there the buyer selects it). Mirrors the 402 page/extra/bazaar
model-strip in internal/x402. The /api/services.json feed still resolves the
agent model on purpose (drives the storefront UI) — left untouched.

Claude-Session: https://claude.ai/code/session_01XgUndZjSoxr2jNNGG5sVYD
(cherry picked from commit 2a88c3fe882010cbf98eae9d26f4f45764f3e54e)
---
 internal/serviceoffercontroller/render.go     | 26 ++++----
 .../serviceoffercontroller/render_test.go     | 63 +++++++++++++++++--
 2 files changed, 69 insertions(+), 20 deletions(-)

diff --git a/internal/serviceoffercontroller/render.go b/internal/serviceoffercontroller/render.go
index f6829e88..5746feea 100644
--- a/internal/serviceoffercontroller/render.go
+++ b/internal/serviceoffercontroller/render.go
@@ -1334,7 +1334,7 @@ func buildSkillMarkdown(offers []*monetizeapi.ServiceOffer, baseURL string, expl
 	lines = append(lines, "| Service | Type | Model | Pay with | Status | Endpoint |")
 	lines = append(lines, "|---------|------|-------|----------|--------|----------|")
 	for _, offer := range ready {
-		modelName := offer.Spec.Model.Name
+		modelName := catalogModelName(offer)
 		if modelName == "" {
 			modelName = "—"
 		}
@@ -1359,7 +1359,7 @@ func buildSkillMarkdown(offers []*monetizeapi.ServiceOffer, baseURL string, expl
 	}
 	lines = append(lines, "", "## Service Details", "")
 	for _, offer := range ready {
-		modelName := offer.Spec.Model.Name
+		modelName := catalogModelName(offer)
 		endpoint := baseURL + offer.EffectivePath()
 		if origin := offer.EffectiveOrigin(); origin != "" {
 			endpoint = origin
@@ -1449,22 +1449,18 @@ func skillMarkdownHowToPay(baseURL string) []string {
 	}
 }
 
-// catalogModelName resolves the model id a buyer should put in a paid
-// chat-completions body. type=agent offers leave spec.model empty by design
-// (the model lives on the linked Agent), so fall back to the controller's
-// resolved view. Shared by /api/services.json and the /skill.md worked
-// examples so both surfaces advertise the same id.
+// catalogModelName returns the model id to surface in catalog surfaces
+// (/api/services.json, /skill.md worked examples, buyprompts), or "" to omit
+// it. Agent offers run their own model and ignore the request `model` field,
+// so their id is an internal detail and is never surfaced — mirrors the 402
+// page / extra / bazaar model-strip in internal/x402 (and it goes stale the
+// moment the operator swaps the backing model). Inference (and other
+// model-bearing) offers keep their id, since there the buyer selects it.
 func catalogModelName(offer *monetizeapi.ServiceOffer) string {
-	if offer == nil {
+	if offer == nil || offer.IsAgent() {
 		return ""
 	}
-	if offer.Spec.Model.Name != "" {
-		return offer.Spec.Model.Name
-	}
-	if offer.Status.AgentResolution != nil {
-		return offer.Status.AgentResolution.Model
-	}
-	return ""
+	return offer.Spec.Model.Name
 }
 
 // skillMarkdownTryIt renders the per-offer "Try it" subsection: one curl that
diff --git a/internal/serviceoffercontroller/render_test.go b/internal/serviceoffercontroller/render_test.go
index 2f4209b8..a96178d3 100644
--- a/internal/serviceoffercontroller/render_test.go
+++ b/internal/serviceoffercontroller/render_test.go
@@ -792,6 +792,56 @@ func TestBuildSkillMarkdown_DrainAdditiveDetail(t *testing.T) {
 	}
 }
 
+// TestBuildSkillMarkdown_AgentModelStripped locks in that agent offers
+// never surface their underlying model in the catalog (the agent runs its own
+// model and ignores the request `model` field — it's an internal detail), while
+// inference offers keep it (there the buyer selects the model). Mirrors the
+// 402 page / extra / bazaar model-strip in internal/x402.
+func TestBuildSkillMarkdown_AgentModelStripped(t *testing.T) {
+	readyCond := []monetizeapi.Condition{{Type: "Ready", Status: "True"}}
+	agentOffer := &monetizeapi.ServiceOffer{
+		ObjectMeta: metav1.ObjectMeta{Name: "analyst", Namespace: "agent-analyst"},
+		Spec: monetizeapi.ServiceOfferSpec{
+			Type:  "agent",
+			Model: monetizeapi.ServiceOfferModel{Name: "gemma4-aeon-uncensored"},
+			Payment: monetizeapi.ServiceOfferPayment{
+				Network: "base-sepolia",
+				PayTo:   "0x1111111111111111111111111111111111111111",
+				Price:   monetizeapi.ServiceOfferPriceTable{PerRequest: "0.01"},
+			},
+		},
+		Status: monetizeapi.ServiceOfferStatus{Conditions: readyCond},
+	}
+	inferenceOffer := &monetizeapi.ServiceOffer{
+		ObjectMeta: metav1.ObjectMeta{Name: "raw-llm", Namespace: "llm"},
+		Spec: monetizeapi.ServiceOfferSpec{
+			Type:  "inference",
+			Model: monetizeapi.ServiceOfferModel{Name: "qwen36-deep"},
+			Payment: monetizeapi.ServiceOfferPayment{
+				Network: "base-sepolia",
+				PayTo:   "0x2222222222222222222222222222222222222222",
+				Price:   monetizeapi.ServiceOfferPriceTable{PerRequest: "0.001"},
+			},
+		},
+		Status: monetizeapi.ServiceOfferStatus{Conditions: readyCond},
+	}
+
+	content := buildSkillMarkdown(
+		[]*monetizeapi.ServiceOffer{agentOffer, inferenceOffer},
+		"https://example.com",
+		nil,
+	)
+
+	// Agent: model never appears (table column is "—", no **Model** detail).
+	if strings.Contains(content, "gemma4-aeon-uncensored") {
+		t.Errorf("agent offer leaked its internal model into the catalog:\n%s", content)
+	}
+	// Inference: model is buyer-facing and must stay (table + detail bullet).
+	if !strings.Contains(content, "- **Model**: qwen36-deep") {
+		t.Errorf("inference offer dropped its (buyer-selectable) model bullet:\n%s", content)
+	}
+}
+
 func TestBuildStaticSiteHTTPRoute(t *testing.T) {
 	route := buildStaticSiteHTTPRoute()
 	if route.GetName() != staticSiteRouteName {
@@ -1001,7 +1051,7 @@ func TestBuildServiceCatalogJSON_Empty(t *testing.T) {
 	}
 }
 
-func TestBuildServiceCatalogJSON_AgentOfferUsesResolvedModel(t *testing.T) {
+func TestBuildServiceCatalogJSON_AgentOfferOmitsModel(t *testing.T) {
 	offer := &monetizeapi.ServiceOffer{
 		ObjectMeta: metav1.ObjectMeta{Name: "demo-quant", Namespace: "agent-demo-quant"},
 		Spec: monetizeapi.ServiceOfferSpec{
@@ -1043,8 +1093,11 @@ func TestBuildServiceCatalogJSON_AgentOfferUsesResolvedModel(t *testing.T) {
 	if svc.Type != "agent" {
 		t.Errorf("type = %q, want agent", svc.Type)
 	}
-	if svc.Model != "qwen3.5:9b" {
-		t.Errorf("model = %q, want qwen3.5:9b", svc.Model)
+	// Agent offers never surface their internal model — it's an operator
+	// detail (and goes stale on model swaps); the agent ignores the request
+	// `model` field anyway. Mirrors the 402/extra/bazaar model-strip.
+	if svc.Model != "" {
+		t.Errorf("model = %q, want \"\" (agent model is internal)", svc.Model)
 	}
 	if svc.Price != "10 OBOL/request" {
 		t.Errorf("price = %q, want 10 OBOL/request", svc.Price)
@@ -1849,8 +1902,8 @@ func TestBuildSkillMarkdown_TryIt(t *testing.T) {
 	if !strings.Contains(content, `"model": "qwen36-deep"`) {
 		t.Errorf("inference Try it missing real model id qwen36-deep:\n%s", content)
 	}
-	if !strings.Contains(content, `"model": "qwen3.5:9b"`) {
-		t.Errorf("agent Try it missing AgentResolution model qwen3.5:9b:\n%s", content)
+	if strings.Contains(content, "qwen3.5:9b") {
+		t.Errorf("agent Try it leaked the internal AgentResolution model:\n%s", content)
 	}
 	if !strings.Contains(content, "X-PAYMENT: ") {
 		t.Errorf("Try it examples missing X-PAYMENT placeholder:\n%s", content)

From fc5080e1027d578a07e5129b86a4a5b26219e7bf Mon Sep 17 00:00:00 2001
From: bussyjd <145845+bussyjd@users.noreply.github.com>
Date: Sat, 27 Jun 2026 23:10:38 +0400
Subject: [PATCH 18/31] fix(serviceoffer-controller): drop internal model from
 /api/services.json for agent offers

The catalog JSON builder set the entry model from spec.model / the resolved
agent model, bypassing catalogModelName -- so /api/services.json still leaked
the internal model id for type=agent offers even though skill.md, the 402
page/extra, and the bazaar example already strip it (#673). Route the JSON
builder through catalogModelName so all discovery surfaces agree: agent offers
omit the model (they run their own and ignore the request `model`), inference
offers keep it. Invert the test that pinned the old leaky behaviour.

Also folds in pre-existing #673 housekeeping that was still uncommitted:
- buy-x402 SKILL.md + buy.py: drop stale --model residue
- agentcrd contract test: fix stale lifetime_seconds 90 -> 180 drift

Claude-Session: https://claude.ai/code/session_01XgUndZjSoxr2jNNGG5sVYD
(cherry picked from commit f8c0d8e4379e4aa78977d12dc54edb1f609fde94)
---
 .../agentcrd/agent_contract_integration_test.go    |  6 +++---
 internal/embed/skills/buy-x402/SKILL.md            |  2 +-
 internal/embed/skills/buy-x402/scripts/buy.py      |  7 ++++---
 internal/serviceoffercontroller/render.go          |  5 +++++
 internal/serviceoffercontroller/render_test.go     | 14 +++++++++-----
 5 files changed, 22 insertions(+), 12 deletions(-)

diff --git a/internal/agentcrd/agent_contract_integration_test.go b/internal/agentcrd/agent_contract_integration_test.go
index 146b28a2..66e097ea 100644
--- a/internal/agentcrd/agent_contract_integration_test.go
+++ b/internal/agentcrd/agent_contract_integration_test.go
@@ -40,8 +40,8 @@ import (
 //	    (agentcrd.HostNoBundledSkillsMarkerPath), so Hermes' installer/sync
 //	    skips seeding its ~80 bundled skills;
 //	(2) the rendered hermes-config ConfigMap in the agent's namespace carries
-//	    the capped knobs: lifetime_seconds: 90, max_turns: 30,
-//	    reasoning_effort: low, and disabled_toolsets {memory, web};
+//	    the capped knobs: lifetime_seconds: 180, max_turns: 30,
+//	    reasoning_effort: low, and disabled_toolsets {memory, web, code_execution};
 //	(3) a BEHAVIORAL signal that bundled skills were actually skipped — see
 //	    assertBundledSkillsSkippedInPod for why we assert pod filesystem state
 //	    rather than grep a log line.
@@ -233,7 +233,7 @@ func getHermesConfigYAML(t *testing.T, cfg *config.Config, ns string) string {
 func assertHermesConfigCaps(t *testing.T, cfgYAML string) {
 	t.Helper()
 	for _, want := range []string{
-		"lifetime_seconds: 90",
+		"lifetime_seconds: 180",
 		"max_turns: 30",
 		"reasoning_effort: low",
 		"disabled_toolsets:",
diff --git a/internal/embed/skills/buy-x402/SKILL.md b/internal/embed/skills/buy-x402/SKILL.md
index f63c2142..b6bee739 100644
--- a/internal/embed/skills/buy-x402/SKILL.md
+++ b/internal/embed/skills/buy-x402/SKILL.md
@@ -170,7 +170,7 @@ python3 ${OBOL_SKILLS_DIR:-/data/.openclaw/skills}/buy-x402/scripts/buy.py pay h
 # One-shot paid STREAMING agent call (SSE events flushed to stdout as they arrive)
 python3 ${OBOL_SKILLS_DIR:-/data/.openclaw/skills}/buy-x402/scripts/buy.py pay-agent \
     https://seller.example.com/services/demo-quant \
-    --model qwen3.5:9b --message 'summarize the latest research on staking'
+    --message 'summarize the latest research on staking'
 
 # Pay-agent with a full OpenAI-compatible body (stream:true is forced on)
 python3 ${OBOL_SKILLS_DIR:-/data/.openclaw/skills}/buy-x402/scripts/buy.py pay-agent \
diff --git a/internal/embed/skills/buy-x402/scripts/buy.py b/internal/embed/skills/buy-x402/scripts/buy.py
index 9a560a16..57d81d87 100644
--- a/internal/embed/skills/buy-x402/scripts/buy.py
+++ b/internal/embed/skills/buy-x402/scripts/buy.py
@@ -2667,9 +2667,10 @@ def cmd_pay_agent(url, messages=None, network=None, timeout=None, body=None, tok
         alias.
 
     `body` is an optional JSON-encoded request body. When omitted, `messages`
-    + `model_id` are required and a `{model, messages, stream:true}` body is
-    synthesized. When provided, the body is parsed and `"stream": true` is
-    forced onto whatever the caller passed.
+    is required and a `{messages, stream:true}` body is synthesized — NO `model`
+    field: a type=agent offer runs its own model and ignores any `model` sent.
+    When provided, the body is parsed and `"stream": true` is forced onto
+    whatever the caller passed.
     """
     if timeout is None or float(timeout) <= 0:
         timeout = 3600.0
diff --git a/internal/serviceoffercontroller/render.go b/internal/serviceoffercontroller/render.go
index 5746feea..32dc2e2e 100644
--- a/internal/serviceoffercontroller/render.go
+++ b/internal/serviceoffercontroller/render.go
@@ -1634,6 +1634,11 @@ func buildServiceCatalogJSON(offers []*monetizeapi.ServiceOffer, baseURL string,
 		if desc == "" {
 			desc = fmt.Sprintf("x402 payment-gated %s service", fallbackOfferType(offer))
 		}
+		// Agent offers run their own model and ignore the request `model`
+		// field, so the id is an internal detail and is never surfaced in
+		// the catalog — mirrors skill.md, the 402 page / extra, and the
+		// bazaar example. Inference (and other model-bearing) offers keep
+		// their id, since there the buyer selects it.
 		modelName := catalogModelName(offer)
 
 		drainEndsAt := ""
diff --git a/internal/serviceoffercontroller/render_test.go b/internal/serviceoffercontroller/render_test.go
index a96178d3..f55d4a8f 100644
--- a/internal/serviceoffercontroller/render_test.go
+++ b/internal/serviceoffercontroller/render_test.go
@@ -1051,7 +1051,7 @@ func TestBuildServiceCatalogJSON_Empty(t *testing.T) {
 	}
 }
 
-func TestBuildServiceCatalogJSON_AgentOfferOmitsModel(t *testing.T) {
+func TestBuildServiceCatalogJSON_AgentOfferOmitsInternalModel(t *testing.T) {
 	offer := &monetizeapi.ServiceOffer{
 		ObjectMeta: metav1.ObjectMeta{Name: "demo-quant", Namespace: "agent-demo-quant"},
 		Spec: monetizeapi.ServiceOfferSpec{
@@ -1093,11 +1093,15 @@ func TestBuildServiceCatalogJSON_AgentOfferOmitsModel(t *testing.T) {
 	if svc.Type != "agent" {
 		t.Errorf("type = %q, want agent", svc.Type)
 	}
-	// Agent offers never surface their internal model — it's an operator
-	// detail (and goes stale on model swaps); the agent ignores the request
-	// `model` field anyway. Mirrors the 402/extra/bazaar model-strip.
+	// An agent runs its own model and ignores the request `model` field, so
+	// the id is an internal detail and must never be surfaced in the catalog
+	// (mirrors skill.md, the 402 page/extra, and the bazaar example) — and it
+	// goes stale the moment the operator swaps the backing model.
 	if svc.Model != "" {
-		t.Errorf("model = %q, want \"\" (agent model is internal)", svc.Model)
+		t.Errorf("model = %q, want empty (internal model must not leak for agent offers)", svc.Model)
+	}
+	if strings.Contains(jsonStr, "qwen3.5:9b") {
+		t.Errorf("internal model leaked into catalog JSON:\n%s", jsonStr)
 	}
 	if svc.Price != "10 OBOL/request" {
 		t.Errorf("price = %q, want 10 OBOL/request", svc.Price)

From 84ab5f300c3b7cff0e51125d0043dc144ddf3427 Mon Sep 17 00:00:00 2001
From: bussyjd <145845+bussyjd@users.noreply.github.com>
Date: Fri, 26 Jun 2026 13:19:13 +0400
Subject: [PATCH 19/31] fix(serviceoffer-controller): serve catalog as UTF-8 to
 stop em-dash mojibake
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The skill-catalog busybox httpd served .md/.html with a bare text/* Content-Type
(no charset), so clients fell back to Latin-1/CP1252 and rendered UTF-8 em dashes
(the catalog's '—' placeholders, accented operator descriptions) as '—'. Add
charset=utf-8 to the text MIME mappings. JSON stays clean (always UTF-8 per RFC 8259).

Claude-Session: https://claude.ai/code/session_01XgUndZjSoxr2jNNGG5sVYD
(cherry picked from commit 0229b8fb53554b231992faae83c9aa40050ddb71)
---
 internal/serviceoffercontroller/render.go               | 7 ++++++-
 internal/serviceoffercontroller/render_builders_test.go | 5 +++++
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/internal/serviceoffercontroller/render.go b/internal/serviceoffercontroller/render.go
index 32dc2e2e..b2ee70dd 100644
--- a/internal/serviceoffercontroller/render.go
+++ b/internal/serviceoffercontroller/render.go
@@ -270,7 +270,12 @@ func buildStaticSiteConfigMap(content, servicesJSON, openAPIJSON, apiDocsHTML st
 		"services.json": servicesJSON,
 		"openapi.json":  openAPIJSON,
 		"api.html":      apiDocsHTML,
-		"httpd.conf":    ".md:text/markdown\n.json:application/json\n.html:text/html\n.js:text/javascript\n",
+		// charset=utf-8 on the text types so UTF-8 content (em dashes in the
+		// catalog, accented operator descriptions, …) renders correctly
+		// instead of mojibake — busybox httpd otherwise sends a bare text/*
+		// type and clients fall back to Latin-1/CP1252. JSON is always UTF-8
+		// by spec (RFC 8259), so it carries no charset param.
+		"httpd.conf": ".md:text/markdown; charset=utf-8\n.json:application/json\n.html:text/html; charset=utf-8\n.js:text/javascript; charset=utf-8\n",
 	}
 	for _, f := range bundles {
 		data[f.Key] = f.Content
diff --git a/internal/serviceoffercontroller/render_builders_test.go b/internal/serviceoffercontroller/render_builders_test.go
index a650ffc5..03cab663 100644
--- a/internal/serviceoffercontroller/render_builders_test.go
+++ b/internal/serviceoffercontroller/render_builders_test.go
@@ -127,6 +127,11 @@ func TestBuildStaticSiteConfigMap(t *testing.T) {
 	if conf, _ := data["httpd.conf"].(string); !strings.Contains(conf, ".md:text/markdown") || !strings.Contains(conf, ".json:application/json") || !strings.Contains(conf, ".html:text/html") {
 		t.Errorf("httpd.conf missing required mime mappings: %q", conf)
 	}
+	// Text types must declare charset=utf-8 or UTF-8 content (em dashes,
+	// accented descriptions) renders as Latin-1 mojibake.
+	if conf, _ := data["httpd.conf"].(string); !strings.Contains(conf, ".md:text/markdown; charset=utf-8") || !strings.Contains(conf, ".html:text/html; charset=utf-8") {
+		t.Errorf("httpd.conf text types missing charset=utf-8: %q", conf)
+	}
 	// Managed-by label so the controller owns cleanup on uninstall.
 	lbls, _ := cm.Object["metadata"].(map[string]any)["labels"].(map[string]any)
 	if lbls["obol.org/managed-by"] != "serviceoffer-controller" {

From d65c2ed32ffb387d9b68c093f311f732adeea56a Mon Sep 17 00:00:00 2001
From: bussyjd 
Date: Mon, 20 Jul 2026 19:10:06 +0400
Subject: [PATCH 20/31] Revert "revert(storefront): carve chat widget out of
 v0.14.0-rc0"

This reverts commit 78c78a86526dbf914f08ac51f120dc19d6de3eaf.
---
 .../serviceoffercontroller/assets/README.md   |  36 ++
 .../assets/chat-vendor.js                     |  57 +++
 .../serviceoffercontroller/assets/chat.html   | 444 ++++++++++++++++++
 internal/serviceoffercontroller/catalog.go    |   9 +-
 .../serviceoffercontroller/catalog_test.go    |  18 +
 internal/serviceoffercontroller/chatwidget.go |  63 +++
 .../serviceoffercontroller/hostoffer_test.go  | 168 ++++++-
 .../serviceoffercontroller/offerbundle.go     |  15 +-
 internal/serviceoffercontroller/render.go     |  51 +-
 .../templates/offer_landing.html              |  11 +
 .../src/components/ServiceCard.tsx            |   2 +-
 11 files changed, 861 insertions(+), 13 deletions(-)
 create mode 100644 internal/serviceoffercontroller/assets/README.md
 create mode 100644 internal/serviceoffercontroller/assets/chat-vendor.js
 create mode 100644 internal/serviceoffercontroller/assets/chat.html
 create mode 100644 internal/serviceoffercontroller/chatwidget.go

diff --git a/internal/serviceoffercontroller/assets/README.md b/internal/serviceoffercontroller/assets/README.md
new file mode 100644
index 00000000..0c071647
--- /dev/null
+++ b/internal/serviceoffercontroller/assets/README.md
@@ -0,0 +1,36 @@
+# Agent chat widget assets
+
+`chat.html` — the self-contained agent chat page served at `/chat` on every
+agent-type offer's dedicated origin. Hand-maintained; no build step. It
+derives the agent name from the hostname and price/model/network/asset from
+the live 402 challenge, so the same file works for every agent offer on any
+stack and network (Base mainnet `eip155:8453` and Base Sepolia
+`eip155:84532` are supported).
+
+`chat-vendor.js` — generated single-file ESM bundle of the widget's
+dependencies. Do not edit by hand. Rebuild:
+
+```sh
+npm init -y && npm i viem@2.21.25 @x402/fetch@2.18.0 @x402/evm@2.18.0
+cat > vendor-entry.mjs <<'EOF'
+export { createWalletClient, createPublicClient, custom, http, erc20Abi,
+         formatUnits, parseUnits, keccak256 } from "viem";
+export { privateKeyToAccount } from "viem/accounts";
+export { base, baseSepolia } from "viem/chains";
+export { wrapFetchWithPayment, x402Client } from "@x402/fetch";
+export { ExactEvmScheme, toClientEvmSigner } from "@x402/evm";
+EOF
+npx esbuild vendor-entry.mjs --bundle --format=esm --minify --target=es2022 \
+  --outfile=chat-vendor.js
+```
+
+When the bundle is rebuilt, bump the `?v=` cache-buster on the
+`chat-vendor.js` import in `chat.html` to the new sha256's first 8 hex
+chars — intermediaries (e.g. Cloudflare) cache `.js` aggressively.
+
+sha256 of the committed bundle:
+`895fd923aa84d7cf80e2b1df299068aa38dba7307a9a380526c0b5426489724d`
+
+The pinned versions are the exact pair validated end-to-end against the
+x402-verifier with real on-chain settlements (X-PAYMENT v1 and
+PAYMENT-SIGNATURE v2 flows both accepted since #690).
diff --git a/internal/serviceoffercontroller/assets/chat-vendor.js b/internal/serviceoffercontroller/assets/chat-vendor.js
new file mode 100644
index 00000000..513c6128
--- /dev/null
+++ b/internal/serviceoffercontroller/assets/chat-vendor.js
@@ -0,0 +1,57 @@
+var v1=Object.defineProperty;var _=(e,t,r)=>()=>{if(r)throw r[0];try{return e&&(t=e(e=0)),t}catch(n){throw r=[n],n}};var Qa=(e,t)=>{for(var r in t)v1(e,r,{get:t[r],enumerable:!0})};var xl,bl=_(()=>{xl="1.2.3"});var Te,es=_(()=>{bl();Te=class e extends Error{constructor(t,r={}){let n=r.cause instanceof e?r.cause.details:r.cause?.message?r.cause.message:r.details,o=r.cause instanceof e&&r.cause.docsPath||r.docsPath,s=[t||"An error occurred.","",...r.metaMessages?[...r.metaMessages,""]:[],...o?[`Docs: https://abitype.dev${o}`]:[],...n?[`Details: ${n}`]:[],`Version: abitype@${xl}`].join(`
+`);super(s),Object.defineProperty(this,"details",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"docsPath",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"metaMessages",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"shortMessage",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"AbiTypeError"}),r.cause&&(this.cause=r.cause),this.details=n,this.docsPath=o,this.metaMessages=r.metaMessages,this.shortMessage=t}}});function Tt(e,t){return e.exec(t)?.groups}var Md,zd,sc,ei=_(()=>{Md=/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/,zd=/^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/,sc=/^\(.+?\).*?$/});function ac(e){let t=e.type;if(gl.test(e.type)&&"components"in e){t="(";let r=e.components.length;for(let o=0;o{ei();gl=/^tuple(?(\[(\d*)\])*)$/});function Rr(e){let t="",r=e.length;for(let n=0;n{vl()});function Xn(e){return e.type==="function"?`function ${e.name}(${Rr(e.inputs)})${e.stateMutability&&e.stateMutability!=="nonpayable"?` ${e.stateMutability}`:""}${e.outputs?.length?` returns (${Rr(e.outputs)})`:""}`:e.type==="event"?`event ${e.name}(${Rr(e.inputs)})`:e.type==="error"?`error ${e.name}(${Rr(e.inputs)})`:e.type==="constructor"?`constructor(${Rr(e.inputs)})${e.stateMutability==="payable"?" payable":""}`:e.type==="fallback"?`fallback() external${e.stateMutability==="payable"?" payable":""}`:"receive() external payable"}var wl=_(()=>{Fd()});function Tl(e){return El.test(e)}function Al(e){return Tt(El,e)}function Sl(e){return Pl.test(e)}function _l(e){return Tt(Pl,e)}function Bl(e){return Il.test(e)}function kl(e){return Tt(Il,e)}function pn(e){return Cl.test(e)}function Rl(e){return Tt(Cl,e)}function Ml(e){return Nl.test(e)}function zl(e){return Tt(Nl,e)}function Ol(e){return Fl.test(e)}function $l(e){return Tt(Fl,e)}function Hl(e){return w1.test(e)}var El,Pl,Il,Cl,Nl,Fl,w1,Od,Dl,ic,ts=_(()=>{ei();El=/^error (?[a-zA-Z$_][a-zA-Z0-9$_]*)\((?.*?)\)$/;Pl=/^event (?[a-zA-Z$_][a-zA-Z0-9$_]*)\((?.*?)\)$/;Il=/^function (?[a-zA-Z$_][a-zA-Z0-9$_]*)\((?.*?)\)(?: (?external|public{1}))?(?: (?pure|view|nonpayable|payable{1}))?(?: returns\s?\((?.*?)\))?$/;Cl=/^struct (?[a-zA-Z$_][a-zA-Z0-9$_]*) \{(?.*?)\}$/;Nl=/^constructor\((?.*?)\)(?:\s(?payable{1}))?$/;Fl=/^fallback\(\) external(?:\s(?payable{1}))?$/;w1=/^receive\(\) external payable$/;Od=new Set(["memory","indexed","storage","calldata"]),Dl=new Set(["indexed"]),ic=new Set(["calldata","memory","storage"])});var cc,uc,fc,dc=_(()=>{es();cc=class extends Te{constructor({signature:t}){super("Failed to parse ABI item.",{details:`parseAbiItem(${JSON.stringify(t,null,2)})`,docsPath:"/api/human#parseabiitem-1"}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"InvalidAbiItemError"})}},uc=class extends Te{constructor({type:t}){super("Unknown type.",{metaMessages:[`Type "${t}" is not a valid ABI type. Perhaps you forgot to include a struct signature?`]}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"UnknownTypeError"})}},fc=class extends Te{constructor({type:t}){super("Unknown type.",{metaMessages:[`Type "${t}" is not a valid ABI type.`]}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"UnknownSolidityTypeError"})}}});var pc,mc,lc,hc,yc,xc,bc=_(()=>{es();pc=class extends Te{constructor({params:t}){super("Failed to parse ABI parameters.",{details:`parseAbiParameters(${JSON.stringify(t,null,2)})`,docsPath:"/api/human#parseabiparameters-1"}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"InvalidAbiParametersError"})}},mc=class extends Te{constructor({param:t}){super("Invalid ABI parameter.",{details:t}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"InvalidParameterError"})}},lc=class extends Te{constructor({param:t,name:r}){super("Invalid ABI parameter.",{details:t,metaMessages:[`"${r}" is a protected Solidity keyword. More info: https://docs.soliditylang.org/en/latest/cheatsheet.html`]}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"SolidityProtectedKeywordError"})}},hc=class extends Te{constructor({param:t,type:r,modifier:n}){super("Invalid ABI parameter.",{details:t,metaMessages:[`Modifier "${n}" not allowed${r?` in "${r}" type`:""}.`]}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"InvalidModifierError"})}},yc=class extends Te{constructor({param:t,type:r,modifier:n}){super("Invalid ABI parameter.",{details:t,metaMessages:[`Modifier "${n}" not allowed${r?` in "${r}" type`:""}.`,`Data location can only be specified for array, struct, or mapping types, but "${n}" was given.`]}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"InvalidFunctionModifierError"})}},xc=class extends Te{constructor({abiParameter:t}){super("Invalid ABI parameter.",{details:JSON.stringify(t,null,2),metaMessages:["ABI parameter type is invalid."]}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"InvalidAbiTypeParameterError"})}}});var ur,gc,vc,$d=_(()=>{es();ur=class extends Te{constructor({signature:t,type:r}){super(`Invalid ${r} signature.`,{details:t}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"InvalidSignatureError"})}},gc=class extends Te{constructor({signature:t}){super("Unknown signature.",{details:t}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"UnknownSignatureError"})}},vc=class extends Te{constructor({signature:t}){super("Invalid struct signature.",{details:t,metaMessages:["No properties exist."]}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"InvalidStructSignatureError"})}}});var wc,Ul=_(()=>{es();wc=class extends Te{constructor({type:t}){super("Circular reference detected.",{metaMessages:[`Struct "${t}" is a circular reference.`]}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"CircularReferenceError"})}}});var Ec,Ll=_(()=>{es();Ec=class extends Te{constructor({current:t,depth:r}){super("Unbalanced parentheses.",{metaMessages:[`"${t.trim()}" has too many ${r>0?"opening":"closing"} parentheses.`],details:`Depth "${r}"`}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"InvalidParenthesisError"})}}});function jl(e,t,r){let n="";if(r)for(let o of Object.entries(r)){if(!o)continue;let s="";for(let a of o[1])s+=`[${a.type}${a.name?`:${a.name}`:""}]`;n+=`(${o[0]}{${s}})`}return t?`${t}:${e}${n}`:`${e}${n}`}var Tc,Vl=_(()=>{Tc=new Map([["address",{type:"address"}],["bool",{type:"bool"}],["bytes",{type:"bytes"}],["bytes32",{type:"bytes32"}],["int",{type:"int256"}],["int256",{type:"int256"}],["string",{type:"string"}],["uint",{type:"uint256"}],["uint8",{type:"uint8"}],["uint16",{type:"uint16"}],["uint24",{type:"uint24"}],["uint32",{type:"uint32"}],["uint64",{type:"uint64"}],["uint96",{type:"uint96"}],["uint112",{type:"uint112"}],["uint160",{type:"uint160"}],["uint192",{type:"uint192"}],["uint256",{type:"uint256"}],["address owner",{type:"address",name:"owner"}],["address to",{type:"address",name:"to"}],["bool approved",{type:"bool",name:"approved"}],["bytes _data",{type:"bytes",name:"_data"}],["bytes data",{type:"bytes",name:"data"}],["bytes signature",{type:"bytes",name:"signature"}],["bytes32 hash",{type:"bytes32",name:"hash"}],["bytes32 r",{type:"bytes32",name:"r"}],["bytes32 root",{type:"bytes32",name:"root"}],["bytes32 s",{type:"bytes32",name:"s"}],["string name",{type:"string",name:"name"}],["string symbol",{type:"string",name:"symbol"}],["string tokenURI",{type:"string",name:"tokenURI"}],["uint tokenId",{type:"uint256",name:"tokenId"}],["uint8 v",{type:"uint8",name:"v"}],["uint256 balance",{type:"uint256",name:"balance"}],["uint256 tokenId",{type:"uint256",name:"tokenId"}],["uint256 value",{type:"uint256",name:"value"}],["event:address indexed from",{type:"address",name:"from",indexed:!0}],["event:address indexed to",{type:"address",name:"to",indexed:!0}],["event:uint indexed tokenId",{type:"uint256",name:"tokenId",indexed:!0}],["event:uint256 indexed tokenId",{type:"uint256",name:"tokenId",indexed:!0}]])});function ti(e,t={}){if(Bl(e))return E1(e,t);if(Sl(e))return T1(e,t);if(Tl(e))return A1(e,t);if(Ml(e))return P1(e,t);if(Ol(e))return S1(e);if(Hl(e))return{type:"receive",stateMutability:"payable"};throw new gc({signature:e})}function E1(e,t={}){let r=kl(e);if(!r)throw new ur({signature:e,type:"function"});let n=dt(r.parameters),o=[],s=n.length;for(let i=0;i{ei();dc();bc();$d();Ll();Vl();ts();_1=/^(?[a-zA-Z$_][a-zA-Z0-9$_]*(?:\spayable)?)(?(?:\[\d*?\])+?)?(?:\s(?calldata|indexed|memory|storage{1}))?(?:\s(?[a-zA-Z$_][a-zA-Z0-9$_]*))?$/,I1=/^\((?.+?)\)(?(?:\[\d*?\])+?)?(?:\s(?calldata|indexed|memory|storage{1}))?(?:\s(?[a-zA-Z$_][a-zA-Z0-9$_]*))?$/,B1=/^u?int$/;k1=/^(?:after|alias|anonymous|apply|auto|byte|calldata|case|catch|constant|copyof|default|defined|error|event|external|false|final|function|immutable|implements|in|indexed|inline|internal|let|mapping|match|memory|mutable|null|of|override|partial|private|promise|public|pure|reference|relocatable|return|returns|sizeof|static|storage|struct|super|supports|switch|this|true|try|typedef|typeof|var|view|virtual)$/});function ns(e){let t={},r=e.length;for(let a=0;a{ei();dc();bc();$d();Ul();ts();rs();N1=/^(?[a-zA-Z$_][a-zA-Z0-9$_]*)(?(?:\[\d*?\])+?)?$/});function Pc(e){let t=ns(e),r=[],n=e.length;for(let o=0;o{ts();Ac();rs()});function Sc(e){let t;if(typeof e=="string")t=ti(e);else{let r=ns(e),n=e.length;for(let o=0;o{dc();ts();Ac();rs()});function _c(e){let t=[];if(typeof e=="string"){let r=dt(e),n=r.length;for(let o=0;o{bc();ts();Ac();rs();rs()});var ri=_(()=>{wl();Fd();Gl();Wl();Zl()});function Ne(e,{includeName:t=!1}={}){if(e.type!=="function"&&e.type!=="event"&&e.type!=="error")throw new Ic(e.type);return`${e.name}(${ni(e.inputs,{includeName:t})})`}function ni(e,{includeName:t=!1}={}){return e?e.map(r=>M1(r,{includeName:t})).join(t?", ":","):""}function M1(e,{includeName:t}){return e.type.startsWith("tuple")?`(${ni(e.components,{includeName:t})})${e.type.slice(5)}`:e.type+(t&&e.name?` ${e.name}`:"")}var Nr=_(()=>{Ae()});function we(e,{strict:t=!0}={}){return!e||typeof e!="string"?!1:t?/^0x[0-9a-fA-F]*$/.test(e):e.startsWith("0x")}var Rt=_(()=>{});function ee(e){return we(e,{strict:!1})?Math.ceil((e.length-2)/2):e.length}var pt=_(()=>{Rt()});var Dd,Yl=_(()=>{Dd="2.55.1"});function Jl(e,t){return t?.(e)?e:e&&typeof e=="object"&&"cause"in e&&e.cause!==void 0?Jl(e.cause,t):t?null:e}var Ud,g,J=_(()=>{Yl();Ud={getDocsUrl:({docsBaseUrl:e,docsPath:t="",docsSlug:r})=>t?`${e??"https://viem.sh"}${t}${r?`#${r}`:""}`:void 0,version:`viem@${Dd}`},g=class e extends Error{constructor(t,r={}){let n=r.cause instanceof e?r.cause.details:r.cause?.message?r.cause.message:r.details,o=r.cause instanceof e&&r.cause.docsPath||r.docsPath,s=Ud.getDocsUrl?.({...r,docsPath:o}),a=[t||"An error occurred.","",...r.metaMessages?[...r.metaMessages,""]:[],...s?[`Docs: ${s}`]:[],...n?[`Details: ${n}`]:[],...Ud.version?[`Version: ${Ud.version}`]:[]].join(`
+`);super(a,r.cause?{cause:r.cause}:void 0),Object.defineProperty(this,"details",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"docsPath",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"metaMessages",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"shortMessage",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"version",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"BaseError"}),this.details=n,this.docsPath=o,this.metaMessages=r.metaMessages,this.name=r.name??this.name,this.shortMessage=t,this.version=Dd}walk(t){return Jl(this,t)}}});var Bc,oi,os,Nt,kc,Cc,Rc,Nc,si,ss,Mc,zc,ai,Xt,as,Fc,Oc,$c,Mr,mn,Hc,Dc,is,Ic,Ae=_(()=>{Nr();pt();J();Bc=class extends g{constructor({docsPath:t}){super(["A constructor was not found on the ABI.","Make sure you are using the correct ABI and that the constructor exists on it."].join(`
+`),{docsPath:t,name:"AbiConstructorNotFoundError"})}},oi=class extends g{constructor({docsPath:t}){super(["Constructor arguments were provided (`args`), but a constructor parameters (`inputs`) were not found on the ABI.","Make sure you are using the correct ABI, and that the `inputs` attribute on the constructor exists."].join(`
+`),{docsPath:t,name:"AbiConstructorParamsNotFoundError"})}},os=class extends g{constructor({data:t,params:r,size:n}){super([`Data size of ${n} bytes is too small for given parameters.`].join(`
+`),{metaMessages:[`Params: (${ni(r,{includeName:!0})})`,`Data:   ${t} (${n} bytes)`],name:"AbiDecodingDataSizeTooSmallError"}),Object.defineProperty(this,"data",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"params",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"size",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.data=t,this.params=r,this.size=n}},Nt=class extends g{constructor({cause:t}={}){super('Cannot decode zero data ("0x") with ABI parameters.',{name:"AbiDecodingZeroDataError",cause:t})}},kc=class extends g{constructor({expectedLength:t,givenLength:r,type:n}){super([`ABI encoding array length mismatch for type ${n}.`,`Expected length: ${t}`,`Given length: ${r}`].join(`
+`),{name:"AbiEncodingArrayLengthMismatchError"})}},Cc=class extends g{constructor({expectedSize:t,value:r}){super(`Size of bytes "${r}" (bytes${ee(r)}) does not match expected size (bytes${t}).`,{name:"AbiEncodingBytesSizeMismatchError"})}},Rc=class extends g{constructor({expectedLength:t,givenLength:r}){super(["ABI encoding params/values length mismatch.",`Expected length (params): ${t}`,`Given length (values): ${r}`].join(`
+`),{name:"AbiEncodingLengthMismatchError"})}},Nc=class extends g{constructor(t,{docsPath:r}){super([`Arguments (\`args\`) were provided to "${t}", but "${t}" on the ABI does not contain any parameters (\`inputs\`).`,"Cannot encode error result without knowing what the parameter types are.","Make sure you are using the correct ABI and that the inputs exist on it."].join(`
+`),{docsPath:r,name:"AbiErrorInputsNotFoundError"})}},si=class extends g{constructor(t,{docsPath:r}={}){super([`Error ${t?`"${t}" `:""}not found on ABI.`,"Make sure you are using the correct ABI and that the error exists on it."].join(`
+`),{docsPath:r,name:"AbiErrorNotFoundError"})}},ss=class extends g{constructor(t,{docsPath:r,cause:n}){super([`Encoded error signature "${t}" not found on ABI.`,"Make sure you are using the correct ABI and that the error exists on it.",`You can look up the decoded signature here: https://4byte.sourcify.dev/?q=${t}.`].join(`
+`),{docsPath:r,name:"AbiErrorSignatureNotFoundError",cause:n}),Object.defineProperty(this,"signature",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.signature=t}},Mc=class extends g{constructor({docsPath:t}){super("Cannot extract event signature from empty topics.",{docsPath:t,name:"AbiEventSignatureEmptyTopicsError"})}},zc=class extends g{constructor(t,{docsPath:r}){super([`Encoded event signature "${t}" not found on ABI.`,"Make sure you are using the correct ABI and that the event exists on it.",`You can look up the signature here: https://4byte.sourcify.dev/?q=${t}.`].join(`
+`),{docsPath:r,name:"AbiEventSignatureNotFoundError"})}},ai=class extends g{constructor(t,{docsPath:r}={}){super([`Event ${t?`"${t}" `:""}not found on ABI.`,"Make sure you are using the correct ABI and that the event exists on it."].join(`
+`),{docsPath:r,name:"AbiEventNotFoundError"})}},Xt=class extends g{constructor(t,{docsPath:r}={}){super([`Function ${t?`"${t}" `:""}not found on ABI.`,"Make sure you are using the correct ABI and that the function exists on it."].join(`
+`),{docsPath:r,name:"AbiFunctionNotFoundError"})}},as=class extends g{constructor(t,{docsPath:r}){super([`Function "${t}" does not contain any \`outputs\` on ABI.`,"Cannot decode function result without knowing what the parameter types are.","Make sure you are using the correct ABI and that the function exists on it."].join(`
+`),{docsPath:r,name:"AbiFunctionOutputsNotFoundError"})}},Fc=class extends g{constructor(t,{docsPath:r}){super([`Encoded function signature "${t}" not found on ABI.`,"Make sure you are using the correct ABI and that the function exists on it.",`You can look up the signature here: https://4byte.sourcify.dev/?q=${t}.`].join(`
+`),{docsPath:r,name:"AbiFunctionSignatureNotFoundError"})}},Oc=class extends g{constructor(t,r){super("Found ambiguous types in overloaded ABI items.",{metaMessages:[`\`${t.type}\` in \`${Ne(t.abiItem)}\`, and`,`\`${r.type}\` in \`${Ne(r.abiItem)}\``,"","These types encode differently and cannot be distinguished at runtime.","Remove one of the ambiguous items in the ABI."],name:"AbiItemAmbiguityError"})}},$c=class extends g{constructor({expectedSize:t,givenSize:r}){super(`Expected bytes${t}, got bytes${r}.`,{name:"BytesSizeMismatchError"})}},Mr=class extends g{constructor({abiItem:t,data:r,params:n,size:o}){super([`Data size of ${o} bytes is too small for non-indexed event parameters.`].join(`
+`),{metaMessages:[`Params: (${ni(n,{includeName:!0})})`,`Data:   ${r} (${o} bytes)`],name:"DecodeLogDataMismatch"}),Object.defineProperty(this,"abiItem",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"data",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"params",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"size",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.abiItem=t,this.data=r,this.params=n,this.size=o}},mn=class extends g{constructor({abiItem:t,param:r}){super([`Expected a topic for indexed event parameter${r.name?` "${r.name}"`:""} on event "${Ne(t,{includeName:!0})}".`].join(`
+`),{name:"DecodeLogTopicsMismatch"}),Object.defineProperty(this,"abiItem",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.abiItem=t}},Hc=class extends g{constructor(t,{docsPath:r}){super([`Type "${t}" is not a valid encoding type.`,"Please provide a valid ABI type."].join(`
+`),{docsPath:r,name:"InvalidAbiEncodingType"})}},Dc=class extends g{constructor(t,{docsPath:r}){super([`Type "${t}" is not a valid decoding type.`,"Please provide a valid ABI type."].join(`
+`),{docsPath:r,name:"InvalidAbiDecodingType"})}},is=class extends g{constructor(t){super([`Value "${t}" is not a valid array.`].join(`
+`),{name:"InvalidArrayError"})}},Ic=class extends g{constructor(t){super([`"${t}" is not a valid definition type.`,'Valid types: "function", "event", "error"'].join(`
+`),{name:"InvalidDefinitionTypeError"})}}});var ii,ci,ui,Lc=_(()=>{J();ii=class extends g{constructor({offset:t,position:r,size:n}){super(`Slice ${r==="start"?"starting":"ending"} at offset "${t}" is out-of-bounds (size: ${n}).`,{name:"SliceOffsetOutOfBoundsError"})}},ci=class extends g{constructor({size:t,targetSize:r,type:n}){super(`${n.charAt(0).toUpperCase()}${n.slice(1).toLowerCase()} size (${t}) exceeds padding size (${r}).`,{name:"SizeExceedsPaddingSizeError"})}},ui=class extends g{constructor({size:t,targetSize:r,type:n}){super(`${n.charAt(0).toUpperCase()}${n.slice(1).toLowerCase()} is expected to be ${r} ${n} long, but is ${t} ${n} long.`,{name:"InvalidBytesLengthError"})}}});function ln(e,{dir:t,size:r=32}={}){return typeof e=="string"?zr(e,{dir:t,size:r}):z1(e,{dir:t,size:r})}function zr(e,{dir:t,size:r=32}={}){if(r===null)return e;let n=e.replace("0x","");if(n.length>r*2)throw new ci({size:Math.ceil(n.length/2),targetSize:r,type:"hex"});return`0x${n[t==="right"?"padEnd":"padStart"](r*2,"0")}`}function z1(e,{dir:t,size:r=32}={}){if(r===null)return e;if(e.length>r)throw new ci({size:e.length,targetSize:r,type:"bytes"});let n=new Uint8Array(r);for(let o=0;o{Lc()});var hn,Vc,qc,Gc,fi=_(()=>{J();hn=class extends g{constructor({max:t,min:r,signed:n,size:o,value:s}){super(`Number "${s}" is not in safe ${o?`${o*8}-bit ${n?"signed":"unsigned"} `:""}integer range ${t?`(${r} to ${t})`:`(above ${r})`}`,{name:"IntegerOutOfRangeError"})}},Vc=class extends g{constructor(t){super(`Bytes value "${t}" is not a valid boolean. The bytes array must contain a single byte of either a 0 or 1 value.`,{name:"InvalidBytesBooleanError"})}},qc=class extends g{constructor(t){super(`Hex value "${t}" is not a valid boolean. The hex value must be "0x0" (false) or "0x1" (true).`,{name:"InvalidHexBooleanError"})}},Gc=class extends g{constructor({givenSize:t,maxSize:r}){super(`Size cannot exceed ${r} bytes. Given size: ${t} bytes.`,{name:"SizeOverflowError"})}}});function Me(e,{dir:t="left"}={}){let r=typeof e=="string"?e.replace("0x",""):e,n=0;for(let o=0;o{});function mt(e,{size:t}){if(ee(e)>t)throw new Gc({givenSize:ee(e),maxSize:t})}function pe(e,t={}){let{signed:r}=t;t.size&&mt(e,{size:t.size});let n=BigInt(e);if(!r)return n;let o=(e.length-2)/2,s=(1n<{fi();pt();Qn()});function ce(e,t={}){return typeof e=="number"||typeof e=="bigint"?I(e,t):typeof e=="string"?Fr(e,t):typeof e=="boolean"?Wc(e,t):ae(e,t)}function Wc(e,t={}){let r=`0x${Number(e)}`;return typeof t.size=="number"?(mt(r,{size:t.size}),ln(r,{size:t.size})):r}function ae(e,t={}){let r="";for(let o=0;os||o{fi();jc();ze();F1=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,"0"));O1=new TextEncoder});function it(e,t={}){return typeof e=="number"||typeof e=="bigint"?e0(e,t):typeof e=="boolean"?Ql(e,t):we(e)?ke(e,t):Mt(e,t)}function Ql(e,t={}){let r=new Uint8Array(1);return r[0]=Number(e),typeof t.size=="number"?(mt(r,{size:t.size}),ln(r,{size:t.size})):r}function Xl(e){if(e>=Or.zero&&e<=Or.nine)return e-Or.zero;if(e>=Or.A&&e<=Or.F)return e-(Or.A-10);if(e>=Or.a&&e<=Or.f)return e-(Or.a-10)}function ke(e,t={}){let r=e;t.size&&(mt(r,{size:t.size}),r=ln(r,{dir:"right",size:t.size}));let n=r.slice(2);n.length%2&&(n=`0${n}`);let o=n.length/2,s=new Uint8Array(o);for(let a=0,i=0;a{J();Rt();jc();ze();Z();$1=new TextEncoder;Or={zero:48,nine:57,A:65,F:70,a:97,f:102}});function H1(e,t=!1){return t?{h:Number(e&Zc),l:Number(e>>t0&Zc)}:{h:Number(e>>t0&Zc)|0,l:Number(e&Zc)|0}}function r0(e,t=!1){let r=e.length,n=new Uint32Array(r),o=new Uint32Array(r);for(let s=0;s{Zc=BigInt(4294967295),t0=BigInt(32);n0=(e,t,r)=>e<>>32-r,o0=(e,t,r)=>t<>>32-r,s0=(e,t,r)=>t<>>64-r,a0=(e,t,r)=>e<>>64-r});var eo,c0=_(()=>{eo=typeof globalThis=="object"&&"crypto"in globalThis?globalThis.crypto:void 0});function D1(e){return e instanceof Uint8Array||ArrayBuffer.isView(e)&&e.constructor.name==="Uint8Array"}function to(e){if(!Number.isSafeInteger(e)||e<0)throw new Error("positive integer expected, got "+e)}function fr(e,...t){if(!D1(e))throw new Error("Uint8Array expected");if(t.length>0&&!t.includes(e.length))throw new Error("Uint8Array expected of length "+t+", got length="+e.length)}function u0(e){if(typeof e!="function"||typeof e.create!="function")throw new Error("Hash should be wrapped by utils.createHasher");to(e.outputLen),to(e.blockLen)}function $r(e,t=!0){if(e.destroyed)throw new Error("Hash instance has been destroyed");if(t&&e.finished)throw new Error("Hash#digest() has already been called")}function Kc(e,t){fr(e);let r=t.outputLen;if(e.length>>t}function L1(e){return e<<24&4278190080|e<<8&16711680|e>>>8&65280|e>>>24&255}function j1(e){for(let t=0;te().update(ro(n)).digest(),r=e();return t.outputLen=r.outputLen,t.blockLen=r.blockLen,t.create=()=>e(),t}function Xc(e=32){if(eo&&typeof eo.getRandomValues=="function")return eo.getRandomValues(new Uint8Array(e));if(eo&&typeof eo.randomBytes=="function")return Uint8Array.from(eo.randomBytes(e));throw new Error("crypto.getRandomValues must be defined")}var U1,jd,yn,xn=_(()=>{c0();U1=new Uint8Array(new Uint32Array([287454020]).buffer)[0]===68;jd=U1?e=>e:j1;yn=class{}});function X1(e,t=24){let r=new Uint32Array(10);for(let n=24-t;n<24;n++){for(let a=0;a<10;a++)r[a]=e[a]^e[a+10]^e[a+20]^e[a+30]^e[a+40];for(let a=0;a<10;a+=2){let i=(a+8)%10,c=(a+2)%10,u=r[c],f=r[c+1],d=p0(u,f,1)^r[i],p=m0(u,f,1)^r[i+1];for(let m=0;m<50;m+=10)e[a+m]^=d,e[a+m+1]^=p}let o=e[2],s=e[3];for(let a=0;a<24;a++){let i=h0[a],c=p0(o,s,i),u=m0(o,s,i),f=l0[a];o=e[f],s=e[f+1],e[f]=c,e[f+1]=u}for(let a=0;a<50;a+=10){for(let i=0;i<10;i++)r[i]=e[a+i];for(let i=0;i<10;i++)e[a+i]^=~r[(i+2)%10]&r[(i+4)%10]}e[0]^=Y1[n],e[1]^=J1[n]}dr(r)}var q1,di,G1,W1,Z1,K1,l0,h0,y0,x0,Y1,J1,p0,m0,Vd,Q1,Qc,qd=_(()=>{i0();xn();q1=BigInt(0),di=BigInt(1),G1=BigInt(2),W1=BigInt(7),Z1=BigInt(256),K1=BigInt(113),l0=[],h0=[],y0=[];for(let e=0,t=di,r=1,n=0;e<24;e++){[r,n]=[n,(2*r+3*n)%5],l0.push(2*(5*n+r)),h0.push((e+1)*(e+2)/2%64);let o=q1;for(let s=0;s<7;s++)t=(t<>W1)*K1)%Z1,t&G1&&(o^=di<<(di<r>32?s0(e,t,r):n0(e,t,r),m0=(e,t,r)=>r>32?a0(e,t,r):o0(e,t,r);Vd=class e extends yn{constructor(t,r,n,o=!1,s=24){if(super(),this.pos=0,this.posOut=0,this.finished=!1,this.destroyed=!1,this.enableXOF=!1,this.blockLen=t,this.suffix=r,this.outputLen=n,this.enableXOF=o,this.rounds=s,to(n),!(0=n&&this.keccak();let a=Math.min(n-this.posOut,s-o);t.set(r.subarray(this.posOut,this.posOut+a),o),this.posOut+=a,o+=a}return t}xofInto(t){if(!this.enableXOF)throw new Error("XOF is not possible for this instance");return this.writeInto(t)}xof(t){return to(t),this.xofInto(new Uint8Array(t))}digestInto(t){if(Kc(t,this),this.finished)throw new Error("digest() was already called");return this.writeInto(t),this.destroy(),t}digest(){return this.digestInto(new Uint8Array(this.outputLen))}destroy(){this.destroyed=!0,dr(this.state)}_cloneInto(t){let{blockLen:r,suffix:n,outputLen:o,rounds:s,enableXOF:a}=this;return t||(t=new e(r,n,o,a,s)),t.state32.set(this.state32),t.pos=this.pos,t.posOut=this.posOut,t.finished=this.finished,t.rounds=s,t.suffix=n,t.outputLen=o,t.enableXOF=a,t.destroyed=this.destroyed,t}},Q1=(e,t,r)=>Jc(()=>new Vd(t,e,r)),Qc=Q1(1,136,256/8)});function oe(e,t){let r=t||"hex",n=Qc(we(e,{strict:!1})?it(e):e);return r==="bytes"?n:ce(n)}var At=_(()=>{qd();Rt();Fe();Z()});function b0(e){return eg(e)}var eg,g0=_(()=>{Fe();At();eg=e=>oe(it(e))});function v0(e){let t=!0,r="",n=0,o="",s=!1;for(let a=0;a{J()});var E0,T0=_(()=>{ri();w0();E0=e=>{let t=typeof e=="string"?e:Xn(e);return v0(t)}});function eu(e){return b0(E0(e))}var Gd=_(()=>{g0();T0()});var bn,pi=_(()=>{Gd();bn=eu});var me,er=_(()=>{J();me=class extends g{constructor({address:t}){super(`Address "${t}" is invalid.`,{metaMessages:["- Address must be a hex value of 20 bytes (40 hex characters).","- Address must match its checksum counterpart."],name:"InvalidAddressError"})}}});var lt,no=_(()=>{lt=class extends Map{constructor(t){super(),Object.defineProperty(this,"maxSize",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.maxSize=t}get(t){let r=super.get(t);return super.has(t)&&(super.delete(t),super.set(t,r)),r}set(t,r){if(super.has(t)&&super.delete(t),super.set(t,r),this.maxSize&&this.size>this.maxSize){let n=super.keys().next().value;n!==void 0&&super.delete(n)}return this}}});function pr(e,t){if(Wd.has(`${e}.${t}`))return Wd.get(`${e}.${t}`);let r=t?`${t}${e.toLowerCase()}`:e.substring(2).toLowerCase(),n=oe(Mt(r),"bytes"),o=(t?r.substring(`${t}0x`.length):r).split("");for(let a=0;a<40;a+=2)n[a>>1]>>4>=8&&o[a]&&(o[a]=o[a].toUpperCase()),(n[a>>1]&15)>=8&&o[a+1]&&(o[a+1]=o[a+1].toUpperCase());let s=`0x${o.join("")}`;return Wd.set(`${e}.${t}`,s),s}function de(e,t){if(!re(e,{strict:!1}))throw new me({address:e});return pr(e,t)}var Wd,tr=_(()=>{er();Fe();At();no();ht();Wd=new lt(8192)});function re(e,t){let{strict:r=!0}=t??{},n=`${e}.${r}`;if(Zd.has(n))return Zd.get(n);let o=tg.test(e)?e.toLowerCase()===e?!0:r?pr(e)===e:!0:!1;return Zd.set(n,o),o}var tg,Zd,ht=_(()=>{no();tr();tg=/^0x[a-fA-F0-9]{40}$/,Zd=new lt(8192)});function Oe(e){return typeof e[0]=="string"?xe(e):rg(e)}function rg(e){let t=0;for(let o of e)t+=o.length;let r=new Uint8Array(t),n=0;for(let o of e)r.set(o,n),n+=o.length;return r}function xe(e){return`0x${e.reduce((t,r)=>t+r.replace("0x",""),"")}`}var qe=_(()=>{});function yt(e,t,r,{strict:n}={}){return we(e,{strict:!1})?tu(e,t,r,{strict:n}):Kd(e,t,r,{strict:n})}function A0(e,t){if(typeof t=="number"&&t>0&&t>ee(e)-1)throw new ii({offset:t,position:"start",size:ee(e)})}function P0(e,t,r){if(typeof t=="number"&&typeof r=="number"&&ee(e)!==r-t)throw new ii({offset:r,position:"end",size:ee(e)})}function Kd(e,t,r,{strict:n}={}){A0(e,t);let o=e.slice(t,r);return n&&P0(o,t,r),o}function tu(e,t,r,{strict:n}={}){A0(e,t);let o=`0x${e.replace("0x","").slice((t??0)*2,(r??e.length)*2)}`;return n&&P0(o,t,r),o}var Hr=_(()=>{Lc();Rt();pt()});var S0,ru,Yd=_(()=>{S0=/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/,ru=/^(u?int)(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/});function Je(e,t){if(e.length!==t.length)throw new Rc({expectedLength:e.length,givenLength:t.length});let r=ng({params:e,values:t});return Qd(r)}function ng({params:e,values:t}){let r=[];for(let n=0;na))}}function ag(e,{param:t}){let[,r]=t.type.split("bytes"),n=ee(e);if(!r){let o=e;return n%32!==0&&(o=zr(o,{dir:"right",size:Math.ceil((e.length-2)/2/32)*32})),{dynamic:!0,encoded:xe([zr(I(n,{size:32})),o])}}if(n!==Number.parseInt(r,10))throw new Cc({expectedSize:Number.parseInt(r,10),value:e});return{dynamic:!1,encoded:zr(e,{dir:"right"})}}function ig(e){if(typeof e!="boolean")throw new g(`Invalid boolean value: "${e}" (type: ${typeof e}). Expected: \`true\` or \`false\`.`);return{dynamic:!1,encoded:zr(Wc(e))}}function cg(e,{signed:t,size:r=256}){if(typeof r=="number"){let n=2n**(BigInt(r)-(t?1n:0n))-1n,o=t?-n-1n:0n;if(e>n||eo))}}function mi(e){let t=e.match(/^(.*)\[(\d+)?\]$/);return t?[t[2]?Number(t[2]):null,t[1]]:void 0}function Jd(e){let{type:t}=e;if(t==="string"||t==="bytes"||t.endsWith("[]"))return!0;if(t==="tuple")return e.components.some(Jd);let r=mi(t);return r?Jd({...e,type:r[1]}):!1}var Dr=_(()=>{Ae();er();J();fi();ht();qe();jc();pt();Hr();Z();Yd()});var mr,cs=_(()=>{Hr();Gd();mr=e=>yt(eu(e),0,4)});function xt(e){let{abi:t,args:r=[],name:n}=e,o=we(n,{strict:!1}),s=t.filter(i=>o?i.type==="function"?mr(i)===n:i.type==="event"?bn(i)===n:!1:"name"in i&&i.name===n);if(s.length===0)return;if(s.length===1)return s[0];let a;for(let i of s){if(!("inputs"in i))continue;if(!r||r.length===0){if(!i.inputs||i.inputs.length===0)return i;continue}if(!i.inputs||i.inputs.length===0||i.inputs.length!==r.length)continue;if(r.every((u,f)=>{let d="inputs"in i&&i.inputs[f];return d?ep(u,d):!1})){if(a&&"inputs"in a&&a.inputs){let u=_0(i.inputs,a.inputs,r);if(u)throw new Oc({abiItem:i,type:u[0]},{abiItem:a,type:u[1]})}a=i}}return a||s[0]}function ep(e,t){let r=typeof e,n=t.type;switch(n){case"address":return re(e,{strict:!1});case"bool":return r==="boolean";case"function":return r==="string";case"string":return r==="string";default:return n==="tuple"&&"components"in t?Object.values(t.components).every((o,s)=>r==="object"&&ep(Object.values(e)[s],o)):/^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/.test(n)?r==="number"||r==="bigint":/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/.test(n)?r==="string"||e instanceof Uint8Array:/[a-z]+[1-9]{0,3}(\[[0-9]{0,}\])+$/.test(n)?Array.isArray(e)&&e.every(o=>ep(o,{...t,type:n.replace(/(\[[0-9]{0,}\])$/,"")})):!1}}function _0(e,t,r){for(let n in e){let o=e[n],s=t[n];if(o.type==="tuple"&&s.type==="tuple"&&"components"in o&&"components"in s)return _0(o.components,s.components,r[n]);let a=[o.type,s.type];if(a.includes("address")&&a.includes("bytes20")?!0:a.includes("address")&&a.includes("string")?re(r[n],{strict:!1}):a.includes("address")&&a.includes("bytes")?re(r[n],{strict:!1}):!1)return a}}var gn=_(()=>{Ae();Rt();ht();pi();cs()});function K(e){return typeof e=="string"?{address:e,type:"json-rpc"}:e}var be=_(()=>{});function C0(e){let{abi:t,args:r,functionName:n}=e,o=t[0];if(n){let s=xt({abi:t,args:r,name:n});if(!s)throw new Xt(n,{docsPath:k0});o=s}if(o.type!=="function")throw new Xt(void 0,{docsPath:k0});return{abi:[o],functionName:mr(Ne(o))}}var k0,R0=_(()=>{Ae();cs();Nr();gn();k0="/docs/contract/encodeFunctionData"});function ie(e){let{args:t}=e,{abi:r,functionName:n}=e.abi.length===1&&e.functionName?.startsWith("0x")?e:C0(e),o=r[0],s=n,a="inputs"in o&&o.inputs?Je(o.inputs,t??[]):void 0;return xe([s,a??"0x"])}var Xe=_(()=>{qe();Dr();R0()});var N0,ou,M0,su=_(()=>{N0={1:"An `assert` condition failed.",17:"Arithmetic operation resulted in underflow or overflow.",18:"Division or modulo by zero (e.g. `5 / 0` or `23 % 0`).",33:"Attempted to convert to an invalid type.",34:"Attempted to access a storage byte array that is incorrectly encoded.",49:"Performed `.pop()` on an empty array",50:"Array index is out of bounds.",65:"Allocated too much memory or created an array which is too large.",81:"Attempted to call a zero-initialized variable of internal function type."},ou={inputs:[{name:"message",type:"string"}],name:"Error",type:"error"},M0={inputs:[{name:"reason",type:"uint256"}],name:"Panic",type:"error"}});var li,us,au,tp=_(()=>{J();li=class extends g{constructor({offset:t}){super(`Offset \`${t}\` cannot be negative.`,{name:"NegativeOffsetError"})}},us=class extends g{constructor({length:t,position:r}){super(`Position \`${r}\` is out of bounds (\`0 < position < ${t}\`).`,{name:"PositionOutOfBoundsError"})}},au=class extends g{constructor({count:t,limit:r}){super(`Recursive read limit of \`${r}\` exceeded (recursive read count: \`${t}\`).`,{name:"RecursiveReadLimitExceededError"})}}});function fs(e,{recursiveReadLimit:t=8192}={}){let r=Object.create(dg);return r.bytes=e,r.dataView=new DataView(e.buffer??e,e.byteOffset,e.byteLength),r.positionReadCount=new Map,r.recursiveReadLimit=t,r}var dg,iu=_(()=>{tp();dg={bytes:new Uint8Array,dataView:new DataView(new ArrayBuffer(0)),position:0,positionReadCount:new Map,recursiveReadCount:0,recursiveReadLimit:Number.POSITIVE_INFINITY,assertReadLimit(){if(this.recursiveReadCount>=this.recursiveReadLimit)throw new au({count:this.recursiveReadCount+1,limit:this.recursiveReadLimit})},assertPosition(e){if(e<0||e>this.bytes.length-1)throw new us({length:this.bytes.length,position:e})},decrementPosition(e){if(e<0)throw new li({offset:e});let t=this.position-e;this.assertPosition(t),this.position=t},getReadCount(e){return this.positionReadCount.get(e||this.position)||0},incrementPosition(e){if(e<0)throw new li({offset:e});let t=this.position+e;this.assertPosition(t),this.position=t},inspectByte(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectBytes(e,t){let r=t??this.position;return this.assertPosition(r+e-1),this.bytes.subarray(r,r+e)},inspectUint8(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectUint16(e){let t=e??this.position;return this.assertPosition(t+1),this.dataView.getUint16(t)},inspectUint24(e){let t=e??this.position;return this.assertPosition(t+2),(this.dataView.getUint16(t)<<8)+this.dataView.getUint8(t+2)},inspectUint32(e){let t=e??this.position;return this.assertPosition(t+3),this.dataView.getUint32(t)},pushByte(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushBytes(e){this.assertPosition(this.position+e.length-1),this.bytes.set(e,this.position),this.position+=e.length},pushUint8(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushUint16(e){this.assertPosition(this.position+1),this.dataView.setUint16(this.position,e),this.position+=2},pushUint24(e){this.assertPosition(this.position+2),this.dataView.setUint16(this.position,e>>8),this.dataView.setUint8(this.position+2,e&255),this.position+=3},pushUint32(e){this.assertPosition(this.position+3),this.dataView.setUint32(this.position,e),this.position+=4},readByte(){this.assertReadLimit(),this._touch();let e=this.inspectByte();return this.position++,e},readBytes(e,t){this.assertReadLimit(),this._touch();let r=this.inspectBytes(e);return this.position+=t??e,r},readUint8(){this.assertReadLimit(),this._touch();let e=this.inspectUint8();return this.position+=1,e},readUint16(){this.assertReadLimit(),this._touch();let e=this.inspectUint16();return this.position+=2,e},readUint24(){this.assertReadLimit(),this._touch();let e=this.inspectUint24();return this.position+=3,e},readUint32(){this.assertReadLimit(),this._touch();let e=this.inspectUint32();return this.position+=4,e},get remaining(){return this.bytes.length-this.position},setPosition(e){let t=this.position;return this.assertPosition(e),this.position=e,()=>this.position=t},_touch(){if(this.recursiveReadLimit===Number.POSITIVE_INFINITY)return;let e=this.getReadCount();this.positionReadCount.set(this.position,e+1),e>0&&this.recursiveReadCount++}}});function z0(e,t={}){typeof t.size<"u"&&mt(e,{size:t.size});let r=ae(e,t);return pe(r,t)}function F0(e,t={}){let r=e;if(typeof t.size<"u"&&(mt(r,{size:t.size}),r=Me(r)),r.length>1||r[0]>1)throw new Vc(r);return!!r[0]}function hr(e,t={}){typeof t.size<"u"&&mt(e,{size:t.size});let r=ae(e,t);return ye(r,t)}function O0(e,t={}){let r=e;return typeof t.size<"u"&&(mt(r,{size:t.size}),r=Me(r,{dir:"right"})),new TextDecoder().decode(r)}var $0=_(()=>{fi();Qn();ze();Z()});function Ur(e,t){let r=typeof t=="string"?ke(t):t,n=fs(r);if(ee(r)===0&&e.length>0)throw new Nt;if(ee(t)&&ee(t)<32)throw new os({data:typeof t=="string"?t:ae(t),params:e,size:ee(t)});let o=0,s=[];for(let a=0;a48?z0(o,{signed:r}):hr(o,{signed:r}),32]}function xg(e,t,{staticPosition:r}){let n=t.components.length===0||t.components.some(({name:a})=>!a),o=n?[]:{},s=0;if(hi(t)){let a=hr(e.readBytes(rp)),i=r+a;for(let c=0;c{Ae();tr();iu();pt();Hr();Qn();$0();Fe();Z();Dr();H0=32,rp=32});function cu(e){let{abi:t,data:r,cause:n}=e,o=yt(r,0,4);if(o==="0x")throw new Nt({cause:n});let a=[...t||[],ou,M0].find(i=>i.type==="error"&&o===mr(Ne(i)));if(!a)throw new ss(o,{docsPath:"/docs/contract/decodeErrorResult",cause:n});return{abiItem:a,args:"inputs"in a&&a.inputs&&a.inputs.length>0?Ur(a.inputs,yt(r,4)):void 0,errorName:a.name}}var np=_(()=>{su();Ae();Hr();cs();yi();Nr()});var ne,Qe=_(()=>{ne=(e,t,r)=>JSON.stringify(e,(n,o)=>{let s=typeof o=="bigint"?o.toString():o;return typeof t=="function"?t(n,s):s},r)});function op({abiItem:e,args:t,includeFunctionName:r=!0,includeName:n=!1}){if("name"in e&&"inputs"in e&&e.inputs)return`${r?e.name:""}(${e.inputs.map((o,s)=>`${n&&o.name?`${o.name}: `:""}${typeof t[s]=="object"?ne(t[s]):t[s]}`).join(", ")})`}var D0=_(()=>{Qe()});var U0,L0,sp=_(()=>{U0={gwei:9,wei:18},L0={ether:-9,wei:9}});function zt(e,t){let r=e.toString(),n=r.startsWith("-");n&&(r=r.slice(1)),r=r.padStart(t,"0");let[o,s]=[r.slice(0,r.length-t),r.slice(r.length-t)];return s=s.replace(/(0+)$/,""),`${n?"-":""}${o||"0"}${s?`.${s}`:""}`}var oo=_(()=>{});function ps(e,t="wei"){return zt(e,U0[t])}var uu=_(()=>{sp();oo()});function $e(e,t="wei"){return zt(e,L0[t])}var ms=_(()=>{sp();oo()});function j0(e){return e.reduce((t,{slot:r,value:n})=>`${t}        ${r}: ${n}
+`,"")}function V0(e){return e.reduce((t,{address:r,...n})=>{let o=`${t}    ${r}:
+`;return n.nonce&&(o+=`      nonce: ${n.nonce}
+`),n.balance&&(o+=`      balance: ${n.balance}
+`),n.code&&(o+=`      code: ${n.code}
+`),n.state&&(o+=`      state:
+`,o+=j0(n.state)),n.stateDiff&&(o+=`      stateDiff:
+`,o+=j0(n.stateDiff)),o},`  State Override:
+`).slice(0,-1)}var fu,du,ap=_(()=>{J();fu=class extends g{constructor({address:t}){super(`State for account "${t}" is set multiple times.`,{name:"AccountStateConflictError"})}},du=class extends g{constructor(){super("state and stateDiff are set on the same account.",{name:"StateAssignmentConflictError"})}}});function ao(e){let t=Object.entries(e).map(([n,o])=>o===void 0||o===!1?null:[n,o]).filter(Boolean),r=t.reduce((n,[o])=>Math.max(n,o.length),0);return t.map(([n,o])=>`  ${`${n}:`.padEnd(r+1)}  ${o}`).join(`
+`)}var pu,mu,lu,hu,wn,ls,so,yu,Pt=_(()=>{uu();ms();J();pu=class extends g{constructor({v:t}){super(`Invalid \`v\` value "${t}". Expected 27 or 28.`,{name:"InvalidLegacyVError"})}},mu=class extends g{constructor({transaction:t}){super("Cannot infer a transaction type from provided transaction.",{metaMessages:["Provided Transaction:","{",ao(t),"}","","To infer the type, either provide:","- a `type` to the Transaction, or","- an EIP-1559 Transaction with `maxFeePerGas`, or","- an EIP-2930 Transaction with `gasPrice` & `accessList`, or","- an EIP-4844 Transaction with `blobs`, `blobVersionedHashes`, `sidecars`, or","- an EIP-7702 Transaction with `authorizationList`, or","- a Legacy Transaction with `gasPrice`"],name:"InvalidSerializableTransactionError"})}},lu=class extends g{constructor({storageKey:t}){super(`Size for storage key "${t}" is invalid. Expected 32 bytes. Got ${Math.floor((t.length-2)/2)} bytes.`,{name:"InvalidStorageKeySizeError"})}},hu=class extends g{constructor(t,{account:r,docsPath:n,chain:o,data:s,gas:a,gasPrice:i,maxFeePerGas:c,maxPriorityFeePerGas:u,nonce:f,to:d,value:p}){let m=ao({chain:o&&`${o?.name} (id: ${o?.id})`,from:r?.address,to:d,value:typeof p<"u"&&`${ps(p)} ${o?.nativeCurrency?.symbol||"ETH"}`,data:s,gas:a,gasPrice:typeof i<"u"&&`${$e(i)} gwei`,maxFeePerGas:typeof c<"u"&&`${$e(c)} gwei`,maxPriorityFeePerGas:typeof u<"u"&&`${$e(u)} gwei`,nonce:f});super(t.shortMessage,{cause:t,docsPath:n,metaMessages:[...t.metaMessages?[...t.metaMessages," "]:[],"Request Arguments:",m].filter(Boolean),name:"TransactionExecutionError"}),Object.defineProperty(this,"cause",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.cause=t}},wn=class extends g{constructor({blockHash:t,blockNumber:r,blockTag:n,hash:o,index:s}){let a="Transaction";n&&s!==void 0&&(a=`Transaction at block time "${n}" at index "${s}"`),t&&s!==void 0&&(a=`Transaction at block hash "${t}" at index "${s}"`),r&&s!==void 0&&(a=`Transaction at block number "${r}" at index "${s}"`),o&&(a=`Transaction with hash "${o}"`),super(`${a} could not be found.`,{name:"TransactionNotFoundError"})}},ls=class extends g{constructor({hash:t}){super(`Transaction receipt with hash "${t}" could not be found. The Transaction may not be processed on a block yet.`,{name:"TransactionReceiptNotFoundError"})}},so=class extends g{constructor({receipt:t}){super(`Transaction with hash "${t.transactionHash}" reverted.`,{metaMessages:['The receipt marked the transaction as "reverted". This could mean that the function on the contract you are trying to call threw an error.'," ","You can attempt to extract the revert reason by:","- calling the `simulateContract` or `simulateCalls` Action with the `abi` and `functionName` of the contract","- using the `call` Action with raw `data`"],name:"TransactionReceiptRevertedError"}),Object.defineProperty(this,"receipt",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.receipt=t}},yu=class extends g{constructor({hash:t}){super(`Timed out while waiting for transaction with hash "${t}" to be confirmed.`,{name:"WaitForTransactionReceiptTimeoutError"})}}});function Ge(e){if(e?.reason)return e.reason;if(typeof DOMException=="function")return new DOMException("This operation was aborted","AbortError");let t=new Error("This operation was aborted");return t.name="AbortError",t}function bt(e){return typeof e=="object"&&e!==null&&"name"in e&&e.name==="AbortError"}var q0,io,rr=_(()=>{q0=e=>e;io=e=>{try{let t=new URL(e);return!t.username&&!t.password?e:(t.username="",t.password="",t.toString())}catch{return e}}});var hs,ys,co,xu,bu,yr,En=_(()=>{be();su();np();Nr();D0();gn();uu();ms();Ae();J();ap();Pt();rr();hs=class extends g{constructor(t,{account:r,docsPath:n,chain:o,data:s,gas:a,gasPrice:i,maxFeePerGas:c,maxPriorityFeePerGas:u,nonce:f,to:d,value:p,stateOverride:m}){let l=r?K(r):void 0,h=ao({from:l?.address,to:d,value:typeof p<"u"&&`${ps(p)} ${o?.nativeCurrency?.symbol||"ETH"}`,data:s,gas:a,gasPrice:typeof i<"u"&&`${$e(i)} gwei`,maxFeePerGas:typeof c<"u"&&`${$e(c)} gwei`,maxPriorityFeePerGas:typeof u<"u"&&`${$e(u)} gwei`,nonce:f});m&&(h+=`
+${V0(m)}`),super(t.shortMessage,{cause:t,docsPath:n,metaMessages:[...t.metaMessages?[...t.metaMessages," "]:[],"Raw Call Arguments:",h].filter(Boolean),name:"CallExecutionError"}),Object.defineProperty(this,"cause",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.cause=t}},ys=class extends g{constructor(t,{abi:r,args:n,contractAddress:o,docsPath:s,functionName:a,sender:i}){let c=xt({abi:r,args:n,name:a}),u=c?op({abiItem:c,args:n,includeFunctionName:!1,includeName:!1}):void 0,f=c?Ne(c,{includeName:!0}):void 0,d=ao({address:o&&q0(o),function:f,args:u&&u!=="()"&&`${[...Array(a?.length??0).keys()].map(()=>" ").join("")}${u}`,sender:i});super(t.shortMessage||`An unknown error occurred while executing the contract function "${a}".`,{cause:t,docsPath:s,metaMessages:[...t.metaMessages?[...t.metaMessages," "]:[],d&&"Contract Call:",d].filter(Boolean),name:"ContractFunctionExecutionError"}),Object.defineProperty(this,"abi",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"args",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"cause",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"contractAddress",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"formattedArgs",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"functionName",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"sender",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.abi=r,this.args=n,this.cause=t,this.contractAddress=o,this.functionName=a,this.sender=i}},co=class extends g{constructor({abi:t,data:r,functionName:n,message:o,cause:s}){let a,i,c,u;if(r&&r!=="0x")try{i=cu({abi:t,data:r,cause:s});let{abiItem:d,errorName:p,args:m}=i;if(p==="Error")u=m[0];else if(p==="Panic"){let[l]=m;u=N0[l]}else{let l=d?Ne(d,{includeName:!0}):void 0,h=d&&m?op({abiItem:d,args:m,includeFunctionName:!1,includeName:!1}):void 0;c=[l?`Error: ${l}`:"",h&&h!=="()"?`       ${[...Array(p?.length??0).keys()].map(()=>" ").join("")}${h}`:""]}}catch(d){a=d}else o&&(u=o);let f;a instanceof ss&&(f=a.signature,c=[`Unable to decode signature "${f}" as it was not found on the provided ABI.`,"Make sure you are using the correct ABI and that the error exists on it.",`You can look up the decoded signature here: https://4byte.sourcify.dev/?q=${f}.`]),super(u&&u!=="execution reverted"||f?[`The contract function "${n}" reverted with the following ${f?"signature":"reason"}:`,u||f].join(`
+`):`The contract function "${n}" reverted.`,{cause:a??s,metaMessages:c,name:"ContractFunctionRevertedError"}),Object.defineProperty(this,"data",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"raw",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"reason",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"signature",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.data=i,this.raw=r,this.reason=u,this.signature=f}},xu=class extends g{constructor({functionName:t,cause:r}){super(`The contract function "${t}" returned no data ("0x").`,{metaMessages:["This could be due to any of the following:",`  - The contract does not have the function "${t}",`,"  - The parameters passed to the contract function may be invalid, or","  - The address is not a contract."],name:"ContractFunctionZeroDataError",cause:r})}},bu=class extends g{constructor({factory:t}){super(`Deployment for counterfactual contract call failed${t?` for factory "${t}".`:""}`,{metaMessages:["Please ensure:","- The `factory` is a valid contract deployment factory (ie. Create2 Factory, ERC-4337 Factory, etc).","- The `factoryData` is a valid encoded function call for contract deployment function on the factory."],name:"CounterfactualDeploymentFailedError"})}},yr=class extends g{constructor({data:t,message:r}){super(r||"",{name:"RawContractError"}),Object.defineProperty(this,"code",{enumerable:!0,configurable:!0,writable:!0,value:3}),Object.defineProperty(this,"data",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.data=t}}});var Ft,uo,Tn,xi,fo=_(()=>{Qe();J();rr();Ft=class extends g{constructor({body:t,cause:r,details:n,headers:o,status:s,url:a}){super("HTTP request failed.",{cause:r,details:n,metaMessages:[s&&`Status: ${s}`,`URL: ${io(a)}`,t&&`Request body: ${ne(t)}`].filter(Boolean),name:"HttpRequestError"}),Object.defineProperty(this,"body",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"headers",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"status",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"url",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.body=t,this.headers=o,this.status=s,this.url=a}},uo=class extends g{constructor({maxSize:t,size:r}){super("HTTP response body exceeded the size limit.",{metaMessages:[`Max: ${t} bytes`,`Received: ${r} bytes`],name:"ResponseBodyTooLargeError"}),Object.defineProperty(this,"maxSize",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"size",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.maxSize=t,this.size=r}},Tn=class extends g{constructor({body:t,error:r,url:n}){super("RPC Request failed.",{cause:r,details:r.message,metaMessages:[`URL: ${io(n)}`,`Request body: ${ne(t)}`],name:"RpcRequestError"}),Object.defineProperty(this,"code",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"data",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"url",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.code=r.code,this.data=r.data,this.url=n}},xi=class extends g{constructor({body:t,url:r}){super("The request took too long to respond.",{details:"The request timed out.",metaMessages:[`URL: ${io(r)}`,`Request body: ${ne(t)}`],name:"TimeoutError"}),Object.defineProperty(this,"url",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.url=r}}});var gg,et,tt,xs,bs,gs,vs,Lr,Ot,ws,Es,Ts,An,po,As,mo,Ps,Ss,_s,Is,Bs,Pn,ks,Cs,Rs,Ns,Ms,Sn,zs,gu,Fs=_(()=>{J();fo();gg=-1,et=class extends g{constructor(t,{code:r,docsPath:n,metaMessages:o,name:s,shortMessage:a}){super(a,{cause:t,docsPath:n,metaMessages:o||t?.metaMessages,name:s||"RpcError"}),Object.defineProperty(this,"code",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.name=s||t.name,this.code=t instanceof Tn?t.code:r??gg}},tt=class extends et{constructor(t,r){super(t,r),Object.defineProperty(this,"data",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.data=r.data}},xs=class e extends et{constructor(t){super(t,{code:e.code,name:"ParseRpcError",shortMessage:"Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text."})}};Object.defineProperty(xs,"code",{enumerable:!0,configurable:!0,writable:!0,value:-32700});bs=class e extends et{constructor(t){super(t,{code:e.code,name:"InvalidRequestRpcError",shortMessage:"JSON is not a valid request object."})}};Object.defineProperty(bs,"code",{enumerable:!0,configurable:!0,writable:!0,value:-32600});gs=class e extends et{constructor(t,{method:r}={}){super(t,{code:e.code,name:"MethodNotFoundRpcError",shortMessage:`The method${r?` "${r}"`:""} does not exist / is not available.`})}};Object.defineProperty(gs,"code",{enumerable:!0,configurable:!0,writable:!0,value:-32601});vs=class e extends et{constructor(t){super(t,{code:e.code,name:"InvalidParamsRpcError",shortMessage:["Invalid parameters were provided to the RPC method.","Double check you have provided the correct parameters."].join(`
+`)})}};Object.defineProperty(vs,"code",{enumerable:!0,configurable:!0,writable:!0,value:-32602});Lr=class e extends et{constructor(t){super(t,{code:e.code,name:"InternalRpcError",shortMessage:"An internal error was received."})}};Object.defineProperty(Lr,"code",{enumerable:!0,configurable:!0,writable:!0,value:-32603});Ot=class e extends et{constructor(t){super(t,{code:e.code,name:"InvalidInputRpcError",shortMessage:["Missing or invalid parameters.","Double check you have provided the correct parameters."].join(`
+`)})}};Object.defineProperty(Ot,"code",{enumerable:!0,configurable:!0,writable:!0,value:-32e3});ws=class e extends et{constructor(t){super(t,{code:e.code,name:"ResourceNotFoundRpcError",shortMessage:"Requested resource not found."}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"ResourceNotFoundRpcError"})}};Object.defineProperty(ws,"code",{enumerable:!0,configurable:!0,writable:!0,value:-32001});Es=class e extends et{constructor(t){super(t,{code:e.code,name:"ResourceUnavailableRpcError",shortMessage:"Requested resource not available."})}};Object.defineProperty(Es,"code",{enumerable:!0,configurable:!0,writable:!0,value:-32002});Ts=class e extends et{constructor(t){super(t,{code:e.code,name:"TransactionRejectedRpcError",shortMessage:"Transaction creation failed."})}};Object.defineProperty(Ts,"code",{enumerable:!0,configurable:!0,writable:!0,value:-32003});An=class e extends et{constructor(t,{method:r}={}){super(t,{code:e.code,name:"MethodNotSupportedRpcError",shortMessage:`Method${r?` "${r}"`:""} is not supported.`})}};Object.defineProperty(An,"code",{enumerable:!0,configurable:!0,writable:!0,value:-32004});po=class e extends et{constructor(t){super(t,{code:e.code,name:"LimitExceededRpcError",shortMessage:"Request exceeds defined limit."})}};Object.defineProperty(po,"code",{enumerable:!0,configurable:!0,writable:!0,value:-32005});As=class e extends et{constructor(t){super(t,{code:e.code,name:"JsonRpcVersionUnsupportedError",shortMessage:"Version of JSON-RPC protocol is not supported."})}};Object.defineProperty(As,"code",{enumerable:!0,configurable:!0,writable:!0,value:-32006});mo=class e extends tt{constructor(t){super(t,{code:e.code,name:"UserRejectedRequestError",shortMessage:"User rejected the request."})}};Object.defineProperty(mo,"code",{enumerable:!0,configurable:!0,writable:!0,value:4001});Ps=class e extends tt{constructor(t){super(t,{code:e.code,name:"UnauthorizedProviderError",shortMessage:"The requested method and/or account has not been authorized by the user."})}};Object.defineProperty(Ps,"code",{enumerable:!0,configurable:!0,writable:!0,value:4100});Ss=class e extends tt{constructor(t,{method:r}={}){super(t,{code:e.code,name:"UnsupportedProviderMethodError",shortMessage:`The Provider does not support the requested method${r?` " ${r}"`:""}.`})}};Object.defineProperty(Ss,"code",{enumerable:!0,configurable:!0,writable:!0,value:4200});_s=class e extends tt{constructor(t){super(t,{code:e.code,name:"ProviderDisconnectedError",shortMessage:"The Provider is disconnected from all chains."})}};Object.defineProperty(_s,"code",{enumerable:!0,configurable:!0,writable:!0,value:4900});Is=class e extends tt{constructor(t){super(t,{code:e.code,name:"ChainDisconnectedError",shortMessage:"The Provider is not connected to the requested chain."})}};Object.defineProperty(Is,"code",{enumerable:!0,configurable:!0,writable:!0,value:4901});Bs=class e extends tt{constructor(t){super(t,{code:e.code,name:"SwitchChainError",shortMessage:"An error occurred when attempting to switch chain."})}};Object.defineProperty(Bs,"code",{enumerable:!0,configurable:!0,writable:!0,value:4902});Pn=class e extends tt{constructor(t){super(t,{code:e.code,name:"UnsupportedNonOptionalCapabilityError",shortMessage:"This Wallet does not support a capability that was not marked as optional."})}};Object.defineProperty(Pn,"code",{enumerable:!0,configurable:!0,writable:!0,value:5700});ks=class e extends tt{constructor(t){super(t,{code:e.code,name:"UnsupportedChainIdError",shortMessage:"This Wallet does not support the requested chain ID."})}};Object.defineProperty(ks,"code",{enumerable:!0,configurable:!0,writable:!0,value:5710});Cs=class e extends tt{constructor(t){super(t,{code:e.code,name:"DuplicateIdError",shortMessage:"There is already a bundle submitted with this ID."})}};Object.defineProperty(Cs,"code",{enumerable:!0,configurable:!0,writable:!0,value:5720});Rs=class e extends tt{constructor(t){super(t,{code:e.code,name:"UnknownBundleIdError",shortMessage:"This bundle id is unknown / has not been submitted"})}};Object.defineProperty(Rs,"code",{enumerable:!0,configurable:!0,writable:!0,value:5730});Ns=class e extends tt{constructor(t){super(t,{code:e.code,name:"BundleTooLargeError",shortMessage:"The call bundle is too large for the Wallet to process."})}};Object.defineProperty(Ns,"code",{enumerable:!0,configurable:!0,writable:!0,value:5740});Ms=class e extends tt{constructor(t){super(t,{code:e.code,name:"AtomicReadyWalletRejectedUpgradeError",shortMessage:"The Wallet can support atomicity after an upgrade, but the user rejected the upgrade."})}};Object.defineProperty(Ms,"code",{enumerable:!0,configurable:!0,writable:!0,value:5750});Sn=class e extends tt{constructor(t){super(t,{code:e.code,name:"AtomicityNotSupportedError",shortMessage:"The wallet does not support atomic execution but the request requires it."})}};Object.defineProperty(Sn,"code",{enumerable:!0,configurable:!0,writable:!0,value:5760});zs=class e extends tt{constructor(t){super(t,{code:e.code,name:"WalletConnectSessionSettlementError",shortMessage:"WalletConnect session settlement failed."})}};Object.defineProperty(zs,"code",{enumerable:!0,configurable:!0,writable:!0,value:7e3});gu=class extends et{constructor(t){super(t,{name:"UnknownRpcError",shortMessage:"An unknown RPC error occurred."})}}});function wg(e,t,r,n){if(typeof e.setBigUint64=="function")return e.setBigUint64(t,r,n);let o=BigInt(32),s=BigInt(4294967295),a=Number(r>>o&s),i=Number(r&s),c=n?4:0,u=n?0:4;e.setUint32(t+c,a,n),e.setUint32(t+u,i,n)}function G0(e,t,r){return e&t^~e&r}function W0(e,t,r){return e&t^e&r^t&r}var wu,jr,Z0=_(()=>{xn();wu=class extends yn{constructor(t,r,n,o){super(),this.finished=!1,this.length=0,this.pos=0,this.destroyed=!1,this.blockLen=t,this.outputLen=r,this.padOffset=n,this.isLE=o,this.buffer=new Uint8Array(t),this.view=Yc(this.buffer)}update(t){$r(this),t=ro(t),fr(t);let{view:r,buffer:n,blockLen:o}=this,s=t.length;for(let a=0;ao-a&&(this.process(n,0),a=0);for(let d=a;df.length)throw new Error("_sha2: outputLen bigger than state");for(let d=0;d{Z0();xn();Eg=Uint32Array.from([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),_n=new Uint32Array(64),Eu=class extends wu{constructor(t=32){super(64,t,8,!1),this.A=jr[0]|0,this.B=jr[1]|0,this.C=jr[2]|0,this.D=jr[3]|0,this.E=jr[4]|0,this.F=jr[5]|0,this.G=jr[6]|0,this.H=jr[7]|0}get(){let{A:t,B:r,C:n,D:o,E:s,F:a,G:i,H:c}=this;return[t,r,n,o,s,a,i,c]}set(t,r,n,o,s,a,i,c){this.A=t|0,this.B=r|0,this.C=n|0,this.D=o|0,this.E=s|0,this.F=a|0,this.G=i|0,this.H=c|0}process(t,r){for(let d=0;d<16;d++,r+=4)_n[d]=t.getUint32(r,!1);for(let d=16;d<64;d++){let p=_n[d-15],m=_n[d-2],l=Qt(p,7)^Qt(p,18)^p>>>3,h=Qt(m,17)^Qt(m,19)^m>>>10;_n[d]=h+_n[d-7]+l+_n[d-16]|0}let{A:n,B:o,C:s,D:a,E:i,F:c,G:u,H:f}=this;for(let d=0;d<64;d++){let p=Qt(i,6)^Qt(i,11)^Qt(i,25),m=f+p+G0(i,c,u)+Eg[d]+_n[d]|0,h=(Qt(n,2)^Qt(n,13)^Qt(n,22))+W0(n,o,s)|0;f=u,u=c,c=i,i=a+m|0,a=s,s=o,o=n,n=m+h|0}n=n+this.A|0,o=o+this.B|0,s=s+this.C|0,a=a+this.D|0,i=i+this.E|0,c=c+this.F|0,u=u+this.G|0,f=f+this.H|0,this.set(n,o,s,a,i,c,u,f)}roundClean(){dr(_n)}destroy(){this.set(0,0,0,0,0,0,0,0),dr(this.buffer)}},lo=Jc(()=>new Eu)});var Tu,cp,K0=_(()=>{xn();Tu=class extends yn{constructor(t,r){super(),this.finished=!1,this.destroyed=!1,u0(t);let n=ro(r);if(this.iHash=t.create(),typeof this.iHash.update!="function")throw new Error("Expected instance of class which extends utils.Hash");this.blockLen=this.iHash.blockLen,this.outputLen=this.iHash.outputLen;let o=this.blockLen,s=new Uint8Array(o);s.set(n.length>o?t.create().update(n).digest():n);for(let a=0;anew Tu(e,t).update(r).digest();cp.create=(e,t)=>new Tu(e,t)});function Os(e){return e instanceof Uint8Array||ArrayBuffer.isView(e)&&e.constructor.name==="Uint8Array"}function qr(e){if(!Os(e))throw new Error("Uint8Array expected")}function $s(e,t){if(typeof t!="boolean")throw new Error(e+" boolean expected, got "+t)}function bi(e){let t=e.toString(16);return t.length&1?"0"+t:t}function X0(e){if(typeof e!="string")throw new Error("hex string expected, got "+typeof e);return e===""?pp:BigInt("0x"+e)}function Hs(e){if(qr(e),Q0)return e.toHex();let t="";for(let r=0;r=Vr._0&&e<=Vr._9)return e-Vr._0;if(e>=Vr.A&&e<=Vr.F)return e-(Vr.A-10);if(e>=Vr.a&&e<=Vr.f)return e-(Vr.a-10)}function gi(e){if(typeof e!="string")throw new Error("hex string expected, got "+typeof e);if(Q0)return Uint8Array.fromHex(e);let t=e.length,r=t/2;if(t%2)throw new Error("hex string expected, got unpadded hex of length "+t);let n=new Uint8Array(r);for(let o=0,s=0;opp;e>>=dp,t+=1);return t}function th(e,t,r){if(typeof e!="number"||e<2)throw new Error("hashLen must be a number");if(typeof t!="number"||t<2)throw new Error("qByteLen must be a number");if(typeof r!="function")throw new Error("hmacFn must be a function");let n=fp(e),o=fp(e),s=0,a=()=>{n.fill(1),o.fill(0),s=0},i=(...d)=>r(o,n,...d),c=(d=fp(0))=>{o=i(J0([0]),d),n=i(),d.length!==0&&(o=i(J0([1]),d),n=i())},u=()=>{if(s++>=1e3)throw new Error("drbg: tried 1000 values");let d=0,p=[];for(;d{a(),c(d);let m;for(;!(m=p(u()));)c();return a(),m}}function Wr(e,t,r={}){let n=(o,s,a)=>{let i=Ag[s];if(typeof i!="function")throw new Error("invalid validator function");let c=e[o];if(!(a&&c===void 0)&&!i(c,e))throw new Error("param "+String(o)+" is invalid. Expected "+s+", got "+c)};for(let[o,s]of Object.entries(t))n(o,s,!1);for(let[o,s]of Object.entries(r))n(o,s,!0);return e}function hp(e){let t=new WeakMap;return(r,...n)=>{let o=t.get(r);if(o!==void 0)return o;let s=e(r,...n);return t.set(r,s),s}}var pp,dp,Q0,Tg,Vr,up,yo,fp,J0,Ag,Ds=_(()=>{pp=BigInt(0),dp=BigInt(1);Q0=typeof Uint8Array.from([]).toHex=="function"&&typeof Uint8Array.fromHex=="function",Tg=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,"0"));Vr={_0:48,_9:57,A:65,F:70,a:97,f:102};up=e=>typeof e=="bigint"&&pp<=e;yo=e=>(dp<new Uint8Array(e),J0=e=>Uint8Array.from(e);Ag={bigint:e=>typeof e=="bigint",function:e=>typeof e=="function",boolean:e=>typeof e=="boolean",string:e=>typeof e=="string",stringOrUint8Array:e=>typeof e=="string"||Os(e),isSafeInteger:e=>Number.isSafeInteger(e),array:e=>Array.isArray(e),field:(e,t)=>t.Fp.isValid(e),hash:e=>typeof e=="function"&&Number.isSafeInteger(e.outputLen)}});function Pe(e,t){let r=e%t;return r>=ut?r:t+r}function _t(e,t,r){let n=e;for(;t-- >ut;)n*=n,n%=r;return n}function Pu(e,t){if(e===ut)throw new Error("invert: expected non-zero number");if(t<=ut)throw new Error("invert: expected positive modulus, got "+t);let r=Pe(e,t),n=t,o=ut,s=rt,a=rt,i=ut;for(;r!==ut;){let u=n/r,f=n%r,d=o-a*u,p=s-i*u;n=r,r=f,o=a,s=i,a=d,i=p}if(n!==rt)throw new Error("invert: does not exist");return Pe(o,t)}function ah(e,t){let r=(e.ORDER+rt)/nh,n=e.pow(t,r);if(!e.eql(e.sqr(n),t))throw new Error("Cannot find square root");return n}function Sg(e,t){let r=(e.ORDER-oh)/sh,n=e.mul(t,xo),o=e.pow(n,r),s=e.mul(t,o),a=e.mul(e.mul(s,xo),o),i=e.mul(s,e.sub(a,e.ONE));if(!e.eql(e.sqr(i),t))throw new Error("Cannot find square root");return i}function _g(e){if(e1e3)throw new Error("Cannot find square root: probably non-prime P");if(r===1)return ah;let s=o.pow(n,t),a=(t+rt)/xo;return function(c,u){if(c.is0(u))return u;if(rh(c,u)!==1)throw new Error("Cannot find square root");let f=r,d=c.mul(c.ONE,s),p=c.pow(u,t),m=c.pow(u,a);for(;!c.eql(p,c.ONE);){if(c.is0(p))return c.ZERO;let l=1,h=c.sqr(p);for(;!c.eql(h,c.ONE);)if(l++,h=c.sqr(h),l===f)throw new Error("Cannot find square root");let x=rt<(n[o]="function",n),t);return Wr(e,r)}function kg(e,t,r){if(rut;)r&rt&&(n=e.mul(n,o)),o=e.sqr(o),r>>=rt;return n}function Us(e,t,r=!1){let n=new Array(t.length).fill(r?e.ZERO:void 0),o=t.reduce((a,i,c)=>e.is0(i)?a:(n[c]=a,e.mul(a,i)),e.ONE),s=e.inv(o);return t.reduceRight((a,i,c)=>e.is0(i)?a:(n[c]=e.mul(a,n[c]),e.mul(a,i)),s),n}function rh(e,t){let r=(e.ORDER-rt)/xo,n=e.pow(t,r),o=e.eql(n,e.ONE),s=e.eql(n,e.ZERO),a=e.eql(n,e.neg(e.ONE));if(!o&&!s&&!a)throw new Error("invalid Legendre symbol result");return o?1:s?0:-1}function yp(e,t){t!==void 0&&to(t);let r=t!==void 0?t:e.toString(2).length,n=Math.ceil(r/8);return{nBitLength:r,nByteLength:n}}function vi(e,t,r=!1,n={}){if(e<=ut)throw new Error("invalid field: expected ORDER > 0, got "+e);let{nBitLength:o,nByteLength:s}=yp(e,t);if(s>2048)throw new Error("invalid field: expected ORDER of <= 2048 bytes");let a,i=Object.freeze({ORDER:e,isLE:r,BITS:o,BYTES:s,MASK:yo(o),ZERO:ut,ONE:rt,create:c=>Pe(c,e),isValid:c=>{if(typeof c!="bigint")throw new Error("invalid field element: expected bigint, got "+typeof c);return ut<=c&&cc===ut,isOdd:c=>(c&rt)===rt,neg:c=>Pe(-c,e),eql:(c,u)=>c===u,sqr:c=>Pe(c*c,e),add:(c,u)=>Pe(c+u,e),sub:(c,u)=>Pe(c-u,e),mul:(c,u)=>Pe(c*u,e),pow:(c,u)=>kg(i,c,u),div:(c,u)=>Pe(c*Pu(u,e),e),sqrN:c=>c*c,addN:(c,u)=>c+u,subN:(c,u)=>c-u,mulN:(c,u)=>c*u,inv:c=>Pu(c,e),sqrt:n.sqrt||(c=>(a||(a=Ig(e)),a(i,c))),toBytes:c=>r?lp(c,s):xr(c,s),fromBytes:c=>{if(c.length!==s)throw new Error("Field.fromBytes: expected "+s+" bytes, got "+c.length);return r?mp(c):gt(c)},invertBatch:c=>Us(i,c),cmov:(c,u,f)=>f?u:c});return Object.freeze(i)}function ih(e){if(typeof e!="bigint")throw new Error("field order must be bigint");let t=e.toString(2).length;return Math.ceil(t/8)}function xp(e){let t=ih(e);return t+Math.ceil(t/2)}function ch(e,t,r=!1){let n=e.length,o=ih(t),s=xp(t);if(n<16||n1024)throw new Error("expected "+s+"-1024 bytes of input, got "+n);let a=r?mp(e):gt(e),i=Pe(a,t-rt)+rt;return r?lp(i,o):xr(i,o)}var ut,rt,xo,Pg,nh,oh,sh,Bg,wi=_(()=>{xn();Ds();ut=BigInt(0),rt=BigInt(1),xo=BigInt(2),Pg=BigInt(3),nh=BigInt(4),oh=BigInt(5),sh=BigInt(8);Bg=["create","isValid","is0","neg","inv","sqrt","sqr","eql","add","sub","mul","pow","div","addN","subN","mulN","sqrN"]});function bp(e,t){let r=t.negate();return e?r:t}function dh(e,t){if(!Number.isSafeInteger(e)||e<=0||e>t)throw new Error("invalid window size, expected [1.."+t+"], got W="+e)}function gp(e,t){dh(e,t);let r=Math.ceil(t/e)+1,n=2**(e-1),o=2**e,s=yo(e),a=BigInt(e);return{windows:r,windowSize:n,mask:s,maxNumber:o,shiftBy:a}}function fh(e,t,r){let{windowSize:n,mask:o,maxNumber:s,shiftBy:a}=r,i=Number(e&o),c=e>>a;i>n&&(i-=s,c+=Ep);let u=t*n,f=u+Math.abs(i)-1,d=i===0,p=i<0,m=t%2!==0;return{nextN:c,offset:f,isZero:d,isNeg:p,isNegF:m,offsetF:u}}function Cg(e,t){if(!Array.isArray(e))throw new Error("array expected");e.forEach((r,n)=>{if(!(r instanceof t))throw new Error("invalid point at index "+n)})}function Rg(e,t){if(!Array.isArray(e))throw new Error("array of scalars expected");e.forEach((r,n)=>{if(!t.isValid(r))throw new Error("invalid scalar at index "+n)})}function wp(e){return ph.get(e)||1}function mh(e,t){return{constTimeNegate:bp,hasPrecomputes(r){return wp(r)!==1},unsafeLadder(r,n,o=e.ZERO){let s=r;for(;n>uh;)n&Ep&&(o=o.add(s)),s=s.double(),n>>=Ep;return o},precomputeWindow(r,n){let{windows:o,windowSize:s}=gp(n,t),a=[],i=r,c=i;for(let u=0;u12?c=i-3:i>4?c=i-2:i>0&&(c=2);let u=yo(c),f=new Array(Number(u)+1).fill(a),d=Math.floor((t.BITS-1)/c)*c,p=a;for(let m=d;m>=0;m-=c){f.fill(a);for(let h=0;h>BigInt(m)&u);f[b]=f[b].add(r[h])}let l=a;for(let h=f.length-1,x=a;h>0;h--)x=x.add(f[h]),l=l.add(x);if(p=p.add(l),m!==0)for(let h=0;h{wi();Ds();uh=BigInt(0),Ep=BigInt(1);vp=new WeakMap,ph=new WeakMap});function yh(e){e.lowS!==void 0&&$s("lowS",e.lowS),e.prehash!==void 0&&$s("prehash",e.prehash)}function Ng(e){let t=Tp(e);Wr(t,{a:"field",b:"field"},{allowInfinityPoint:"boolean",allowedPrivateKeyLengths:"array",clearCofactor:"function",fromBytes:"function",isTorsionFree:"function",toBytes:"function",wrapPrivateKey:"boolean"});let{endo:r,Fp:n,a:o}=t;if(r){if(!n.eql(o,n.ZERO))throw new Error("invalid endo: CURVE.a must be 0");if(typeof r!="object"||typeof r.beta!="bigint"||typeof r.splitScalar!="function")throw new Error('invalid endo: expected "beta": bigint and "splitScalar": function')}return Object.freeze({...t})}function Ap(e,t){return Hs(xr(e,t))}function Mg(e){let t=Ng(e),{Fp:r}=t,n=vi(t.n,t.nBitLength),o=t.toBytes||((v,y,w)=>{let E=y.toAffine();return ct(Uint8Array.from([4]),r.toBytes(E.x),r.toBytes(E.y))}),s=t.fromBytes||(v=>{let y=v.subarray(1),w=r.fromBytes(y.subarray(0,r.BYTES)),E=r.fromBytes(y.subarray(r.BYTES,2*r.BYTES));return{x:w,y:E}});function a(v){let{a:y,b:w}=t,E=r.sqr(v),T=r.mul(E,v);return r.add(r.add(T,r.mul(v,y)),w)}function i(v,y){let w=r.sqr(y),E=a(v);return r.eql(w,E)}if(!i(t.Gx,t.Gy))throw new Error("bad curve params: generator point");let c=r.mul(r.pow(t.a,Ei),Sp),u=r.mul(r.sqr(t.b),BigInt(27));if(r.is0(r.add(c,u)))throw new Error("bad curve params: a or b");function f(v){return ho(v,le,t.n)}function d(v){let{allowedPrivateKeyLengths:y,nByteLength:w,wrapPrivateKey:E,n:T}=t;if(y&&typeof v!="bigint"){if(Os(v)&&(v=Hs(v)),typeof v!="string"||!y.includes(v.length))throw new Error("invalid private key");v=v.padStart(w*2,"0")}let S;try{S=typeof v=="bigint"?v:gt(Ce("private key",v,w))}catch{throw new Error("invalid private key, expected hex or "+w+" bytes, got "+typeof v)}return E&&(S=Pe(S,T)),Gr("private key",S,le,T),S}function p(v){if(!(v instanceof h))throw new Error("ProjectivePoint expected")}let m=hp((v,y)=>{let{px:w,py:E,pz:T}=v;if(r.eql(T,r.ONE))return{x:w,y:E};let S=v.is0();y==null&&(y=S?r.ONE:r.inv(T));let R=r.mul(w,y),P=r.mul(E,y),z=r.mul(T,y);if(S)return{x:r.ZERO,y:r.ZERO};if(!r.eql(z,r.ONE))throw new Error("invZ was invalid");return{x:R,y:P}}),l=hp(v=>{if(v.is0()){if(t.allowInfinityPoint&&!r.is0(v.py))return;throw new Error("bad point: ZERO")}let{x:y,y:w}=v.toAffine();if(!r.isValid(y)||!r.isValid(w))throw new Error("bad point: x or y not FE");if(!i(y,w))throw new Error("bad point: equation left != right");if(!v.isTorsionFree())throw new Error("bad point: not in prime-order subgroup");return!0});class h{constructor(y,w,E){if(y==null||!r.isValid(y))throw new Error("x required");if(w==null||!r.isValid(w)||r.is0(w))throw new Error("y required");if(E==null||!r.isValid(E))throw new Error("z required");this.px=y,this.py=w,this.pz=E,Object.freeze(this)}static fromAffine(y){let{x:w,y:E}=y||{};if(!y||!r.isValid(w)||!r.isValid(E))throw new Error("invalid affine point");if(y instanceof h)throw new Error("projective point not allowed");let T=S=>r.eql(S,r.ZERO);return T(w)&&T(E)?h.ZERO:new h(w,E,r.ONE)}get x(){return this.toAffine().x}get y(){return this.toAffine().y}static normalizeZ(y){let w=Us(r,y.map(E=>E.pz));return y.map((E,T)=>E.toAffine(w[T])).map(h.fromAffine)}static fromHex(y){let w=h.fromAffine(s(Ce("pointHex",y)));return w.assertValidity(),w}static fromPrivateKey(y){return h.BASE.multiply(d(y))}static msm(y,w){return lh(h,n,y,w)}_setWindowSize(y){A.setWindowSize(this,y)}assertValidity(){l(this)}hasEvenY(){let{y}=this.toAffine();if(r.isOdd)return!r.isOdd(y);throw new Error("Field doesn't support isOdd")}equals(y){p(y);let{px:w,py:E,pz:T}=this,{px:S,py:R,pz:P}=y,z=r.eql(r.mul(w,P),r.mul(S,T)),H=r.eql(r.mul(E,P),r.mul(R,T));return z&&H}negate(){return new h(this.px,r.neg(this.py),this.pz)}double(){let{a:y,b:w}=t,E=r.mul(w,Ei),{px:T,py:S,pz:R}=this,P=r.ZERO,z=r.ZERO,H=r.ZERO,O=r.mul(T,T),q=r.mul(S,S),k=r.mul(R,R),C=r.mul(T,S);return C=r.add(C,C),H=r.mul(T,R),H=r.add(H,H),P=r.mul(y,H),z=r.mul(E,k),z=r.add(P,z),P=r.sub(q,z),z=r.add(q,z),z=r.mul(P,z),P=r.mul(C,P),H=r.mul(E,H),k=r.mul(y,k),C=r.sub(O,k),C=r.mul(y,C),C=r.add(C,H),H=r.add(O,O),O=r.add(H,O),O=r.add(O,k),O=r.mul(O,C),z=r.add(z,O),k=r.mul(S,R),k=r.add(k,k),O=r.mul(k,C),P=r.sub(P,O),H=r.mul(k,q),H=r.add(H,H),H=r.add(H,H),new h(P,z,H)}add(y){p(y);let{px:w,py:E,pz:T}=this,{px:S,py:R,pz:P}=y,z=r.ZERO,H=r.ZERO,O=r.ZERO,q=t.a,k=r.mul(t.b,Ei),C=r.mul(w,S),D=r.mul(E,R),M=r.mul(T,P),L=r.add(w,E),W=r.add(S,R);L=r.mul(L,W),W=r.add(C,D),L=r.sub(L,W),W=r.add(w,T);let se=r.add(S,P);return W=r.mul(W,se),se=r.add(C,M),W=r.sub(W,se),se=r.add(E,T),z=r.add(R,P),se=r.mul(se,z),z=r.add(D,M),se=r.sub(se,z),O=r.mul(q,W),z=r.mul(k,M),O=r.add(z,O),z=r.sub(D,O),O=r.add(D,O),H=r.mul(z,O),D=r.add(C,C),D=r.add(D,C),M=r.mul(q,M),W=r.mul(k,W),D=r.add(D,M),M=r.sub(C,M),M=r.mul(q,M),W=r.add(W,M),C=r.mul(D,W),H=r.add(H,C),C=r.mul(se,W),z=r.mul(L,z),z=r.sub(z,C),C=r.mul(L,D),O=r.mul(se,O),O=r.add(O,C),new h(z,H,O)}subtract(y){return this.add(y.negate())}is0(){return this.equals(h.ZERO)}wNAF(y){return A.wNAFCached(this,y,h.normalizeZ)}multiplyUnsafe(y){let{endo:w,n:E}=t;Gr("scalar",y,nr,E);let T=h.ZERO;if(y===nr)return T;if(this.is0()||y===le)return this;if(!w||A.hasPrecomputes(this))return A.wNAFCachedUnsafe(this,y,h.normalizeZ);let{k1neg:S,k1:R,k2neg:P,k2:z}=w.splitScalar(y),H=T,O=T,q=this;for(;R>nr||z>nr;)R&le&&(H=H.add(q)),z&le&&(O=O.add(q)),q=q.double(),R>>=le,z>>=le;return S&&(H=H.negate()),P&&(O=O.negate()),O=new h(r.mul(O.px,w.beta),O.py,O.pz),H.add(O)}multiply(y){let{endo:w,n:E}=t;Gr("scalar",y,le,E);let T,S;if(w){let{k1neg:R,k1:P,k2neg:z,k2:H}=w.splitScalar(y),{p:O,f:q}=this.wNAF(P),{p:k,f:C}=this.wNAF(H);O=A.constTimeNegate(R,O),k=A.constTimeNegate(z,k),k=new h(r.mul(k.px,w.beta),k.py,k.pz),T=O.add(k),S=q.add(C)}else{let{p:R,f:P}=this.wNAF(y);T=R,S=P}return h.normalizeZ([T,S])[0]}multiplyAndAddUnsafe(y,w,E){let T=h.BASE,S=(P,z)=>z===nr||z===le||!P.equals(T)?P.multiplyUnsafe(z):P.multiply(z),R=S(this,w).add(S(y,E));return R.is0()?void 0:R}toAffine(y){return m(this,y)}isTorsionFree(){let{h:y,isTorsionFree:w}=t;if(y===le)return!0;if(w)return w(h,this);throw new Error("isTorsionFree() has not been declared for the elliptic curve")}clearCofactor(){let{h:y,clearCofactor:w}=t;return y===le?this:w?w(h,this):this.multiplyUnsafe(t.h)}toRawBytes(y=!0){return $s("isCompressed",y),this.assertValidity(),o(h,this,y)}toHex(y=!0){return $s("isCompressed",y),Hs(this.toRawBytes(y))}}h.BASE=new h(t.Gx,t.Gy,r.ONE),h.ZERO=new h(r.ZERO,r.ONE,r.ZERO);let{endo:x,nBitLength:b}=t,A=mh(h,x?Math.ceil(b/2):b);return{CURVE:t,ProjectivePoint:h,normPrivateKeyToScalar:d,weierstrassEquation:a,isWithinCurveOrder:f}}function zg(e){let t=Tp(e);return Wr(t,{hash:"hash",hmac:"function",randomBytes:"function"},{bits2int:"function",bits2int_modN:"function",lowS:"boolean"}),Object.freeze({lowS:!0,...t})}function xh(e){let t=zg(e),{Fp:r,n,nByteLength:o,nBitLength:s}=t,a=r.BYTES+1,i=2*r.BYTES+1;function c(k){return Pe(k,n)}function u(k){return Pu(k,n)}let{ProjectivePoint:f,normPrivateKeyToScalar:d,weierstrassEquation:p,isWithinCurveOrder:m}=Mg({...t,toBytes(k,C,D){let M=C.toAffine(),L=r.toBytes(M.x),W=ct;return $s("isCompressed",D),D?W(Uint8Array.from([C.hasEvenY()?2:3]),L):W(Uint8Array.from([4]),L,r.toBytes(M.y))},fromBytes(k){let C=k.length,D=k[0],M=k.subarray(1);if(C===a&&(D===2||D===3)){let L=gt(M);if(!ho(L,le,r.ORDER))throw new Error("Point is not on curve");let W=p(L),se;try{se=r.sqrt(W)}catch(he){let Be=he instanceof Error?": "+he.message:"";throw new Error("Point is not on curve"+Be)}let Re=(se&le)===le;return(D&1)===1!==Re&&(se=r.neg(se)),{x:L,y:se}}else if(C===i&&D===4){let L=r.fromBytes(M.subarray(0,r.BYTES)),W=r.fromBytes(M.subarray(r.BYTES,2*r.BYTES));return{x:L,y:W}}else{let L=a,W=i;throw new Error("invalid Point, expected length of "+L+", or uncompressed "+W+", got "+C)}}});function l(k){let C=n>>le;return k>C}function h(k){return l(k)?c(-k):k}let x=(k,C,D)=>gt(k.slice(C,D));class b{constructor(C,D,M){Gr("r",C,le,n),Gr("s",D,le,n),this.r=C,this.s=D,M!=null&&(this.recovery=M),Object.freeze(this)}static fromCompact(C){let D=o;return C=Ce("compactSignature",C,D*2),new b(x(C,0,D),x(C,D,2*D))}static fromDER(C){let{r:D,s:M}=Zr.toSig(Ce("DER",C));return new b(D,M)}assertValidity(){}addRecoveryBit(C){return new b(this.r,this.s,C)}recoverPublicKey(C){let{r:D,s:M,recovery:L}=this,W=T(Ce("msgHash",C));if(L==null||![0,1,2,3].includes(L))throw new Error("recovery id invalid");let se=L===2||L===3?D+t.n:D;if(se>=r.ORDER)throw new Error("recovery id 2 or 3 invalid");let Re=(L&1)===0?"02":"03",ft=f.fromHex(Re+Ap(se,r.BYTES)),he=u(se),Be=c(-W*he),Jo=c(M*he),dn=f.BASE.multiplyAndAddUnsafe(ft,Be,Jo);if(!dn)throw new Error("point at infinify");return dn.assertValidity(),dn}hasHighS(){return l(this.s)}normalizeS(){return this.hasHighS()?new b(this.r,c(-this.s),this.recovery):this}toDERRawBytes(){return gi(this.toDERHex())}toDERHex(){return Zr.hexFromSig(this)}toCompactRawBytes(){return gi(this.toCompactHex())}toCompactHex(){let C=o;return Ap(this.r,C)+Ap(this.s,C)}}let A={isValidPrivateKey(k){try{return d(k),!0}catch{return!1}},normPrivateKeyToScalar:d,randomPrivateKey:()=>{let k=xp(t.n);return ch(t.randomBytes(k),t.n)},precompute(k=8,C=f.BASE){return C._setWindowSize(k),C.multiply(BigInt(3)),C}};function v(k,C=!0){return f.fromPrivateKey(k).toRawBytes(C)}function y(k){if(typeof k=="bigint")return!1;if(k instanceof f)return!0;let D=Ce("key",k).length,M=r.BYTES,L=M+1,W=2*M+1;if(!(t.allowedPrivateKeyLengths||o===L))return D===L||D===W}function w(k,C,D=!0){if(y(k)===!0)throw new Error("first arg must be private key");if(y(C)===!1)throw new Error("second arg must be public key");return f.fromHex(C).multiply(d(k)).toRawBytes(D)}let E=t.bits2int||function(k){if(k.length>8192)throw new Error("input is too large");let C=gt(k),D=k.length*8-s;return D>0?C>>BigInt(D):C},T=t.bits2int_modN||function(k){return c(E(k))},S=yo(s);function R(k){return Gr("num < 2^"+s,k,nr,S),xr(k,o)}function P(k,C,D=z){if(["recovered","canonical"].some(Yn=>Yn in D))throw new Error("sign() legacy options not supported");let{hash:M,randomBytes:L}=t,{lowS:W,prehash:se,extraEntropy:Re}=D;W==null&&(W=!0),k=Ce("msgHash",k),yh(D),se&&(k=Ce("prehashed msgHash",M(k)));let ft=T(k),he=d(C),Be=[R(he),R(ft)];if(Re!=null&&Re!==!1){let Yn=Re===!0?L(r.BYTES):Re;Be.push(Ce("extraEntropy",Yn))}let Jo=ct(...Be),dn=ft;function Rd(Yn){let Xo=E(Yn);if(!m(Xo))return;let Nd=u(Xo),Ja=f.BASE.multiply(Xo).toAffine(),Jn=c(Ja.x);if(Jn===nr)return;let Xa=c(Nd*c(dn+Jn*he));if(Xa===nr)return;let Qo=(Ja.x===Jn?0:2)|Number(Ja.y&le),yl=Xa;return W&&l(Xa)&&(yl=h(Xa),Qo^=1),new b(Jn,yl,Qo)}return{seed:Jo,k2sig:Rd}}let z={lowS:t.lowS,prehash:!1},H={lowS:t.lowS,prehash:!1};function O(k,C,D=z){let{seed:M,k2sig:L}=P(k,C,D),W=t;return th(W.hash.outputLen,W.nByteLength,W.hmac)(M,L)}f.BASE._setWindowSize(8);function q(k,C,D,M=H){let L=k;C=Ce("msgHash",C),D=Ce("publicKey",D);let{lowS:W,prehash:se,format:Re}=M;if(yh(M),"strict"in M)throw new Error("options.strict was renamed to lowS");if(Re!==void 0&&Re!=="compact"&&Re!=="der")throw new Error("format must be compact or der");let ft=typeof L=="string"||Os(L),he=!ft&&!Re&&typeof L=="object"&&L!==null&&typeof L.r=="bigint"&&typeof L.s=="bigint";if(!ft&&!he)throw new Error("invalid signature, expected Uint8Array, hex string or Signature instance");let Be,Jo;try{if(he&&(Be=new b(L.r,L.s)),ft){try{Re!=="compact"&&(Be=b.fromDER(L))}catch(Qo){if(!(Qo instanceof Zr.Err))throw Qo}!Be&&Re!=="der"&&(Be=b.fromCompact(L))}Jo=f.fromHex(D)}catch{return!1}if(!Be||W&&Be.hasHighS())return!1;se&&(C=t.hash(C));let{r:dn,s:Rd}=Be,Yn=T(C),Xo=u(Rd),Nd=c(Yn*Xo),Ja=c(dn*Xo),Jn=f.BASE.multiplyAndAddUnsafe(Jo,Nd,Ja)?.toAffine();return Jn?c(Jn.x)===dn:!1}return{CURVE:t,getPublicKey:v,getSharedSecret:w,sign:O,verify:q,ProjectivePoint:f,Signature:b,utils:A}}function Fg(e,t){let r=e.ORDER,n=nr;for(let l=r-le;l%In===nr;l/=In)n+=le;let o=n,s=In<{let x=d,b=e.pow(h,u),A=e.sqr(b);A=e.mul(A,h);let v=e.mul(l,A);v=e.pow(v,c),v=e.mul(v,b),b=e.mul(v,h),A=e.mul(v,l);let y=e.mul(A,b);v=e.pow(y,f);let w=e.eql(v,e.ONE);b=e.mul(A,p),v=e.mul(y,x),A=e.cmov(b,A,w),y=e.cmov(v,y,w);for(let E=o;E>le;E--){let T=E-In;T=In<{let A=e.sqr(b),v=e.mul(x,b);A=e.mul(A,v);let y=e.pow(A,l);y=e.mul(y,v);let w=e.mul(y,h),E=e.mul(e.sqr(y),b),T=e.eql(E,x),S=e.cmov(w,y,T);return{isValid:T,value:S}}}return m}function bh(e,t){if(Su(e),!e.isValid(t.A)||!e.isValid(t.B)||!e.isValid(t.Z))throw new Error("mapToCurveSimpleSWU: invalid opts");let r=Fg(e,t.Z);if(!e.isOdd)throw new Error("Fp.isOdd is not implemented!");return n=>{let o,s,a,i,c,u,f,d;o=e.sqr(n),o=e.mul(o,t.Z),s=e.sqr(o),s=e.add(s,o),a=e.add(s,e.ONE),a=e.mul(a,t.B),i=e.cmov(t.Z,e.neg(s),!e.eql(s,e.ZERO)),i=e.mul(i,t.A),s=e.sqr(a),u=e.sqr(i),c=e.mul(u,t.A),s=e.add(s,c),s=e.mul(s,a),u=e.mul(u,i),c=e.mul(u,t.B),s=e.add(s,c),f=e.mul(o,a);let{isValid:p,value:m}=r(s,u);d=e.mul(o,n),d=e.mul(d,m),f=e.cmov(f,a,p),d=e.cmov(d,m,p);let l=e.isOdd(n)===e.isOdd(d);d=e.cmov(e.neg(d),d,l);let h=Us(e,[i],!0)[0];return f=e.mul(f,h),{x:f,y:d}}}var Pp,Zr,nr,le,In,Ei,Sp,_p=_(()=>{hh();wi();Ds();Pp=class extends Error{constructor(t=""){super(t)}},Zr={Err:Pp,_tlv:{encode:(e,t)=>{let{Err:r}=Zr;if(e<0||e>256)throw new r("tlv.encode: wrong tag");if(t.length&1)throw new r("tlv.encode: unpadded data");let n=t.length/2,o=bi(n);if(o.length/2&128)throw new r("tlv.encode: long form length too big");let s=n>127?bi(o.length/2|128):"";return bi(e)+s+o+t},decode(e,t){let{Err:r}=Zr,n=0;if(e<0||e>256)throw new r("tlv.encode: wrong tag");if(t.length<2||t[n++]!==e)throw new r("tlv.decode: wrong tlv");let o=t[n++],s=!!(o&128),a=0;if(!s)a=o;else{let c=o&127;if(!c)throw new r("tlv.decode(long): indefinite length not supported");if(c>4)throw new r("tlv.decode(long): byte length is too big");let u=t.subarray(n,n+c);if(u.length!==c)throw new r("tlv.decode: length bytes not complete");if(u[0]===0)throw new r("tlv.decode(long): zero leftmost byte");for(let f of u)a=a<<8|f;if(n+=c,a<128)throw new r("tlv.decode(long): not minimal encoding")}let i=t.subarray(n,n+a);if(i.length!==a)throw new r("tlv.decode: wrong value length");return{v:i,l:t.subarray(n+a)}}},_int:{encode(e){let{Err:t}=Zr;if(ecp(e,t,d0(...r)),randomBytes:Xc}}function gh(e,t){let r=n=>xh({...e,...Og(n)});return{...r(t),create:r}}var vh=_(()=>{K0();xn();_p();});function Bn(e,t){if(Ti(e),Ti(t),e<0||e>=1<<8*t)throw new Error("invalid I2OSP input: "+e);let r=Array.from({length:t}).fill(0);for(let n=t-1;n>=0;n--)r[n]=e&255,e>>>=8;return new Uint8Array(r)}function Hg(e,t){let r=new Uint8Array(e.length);for(let n=0;n255&&(t=n(ct(Au("H2C-OVERSIZE-DST-"),t)));let{outputLen:o,blockLen:s}=n,a=Math.ceil(r/o);if(r>65535||a>255)throw new Error("expand_message_xmd: invalid lenInBytes");let i=ct(t,Bn(t.length,1)),c=Bn(0,s),u=Bn(r,2),f=new Array(a),d=n(ct(c,e,u,Bn(0,1),i));f[0]=n(ct(d,Bn(1,1),i));for(let m=1;m<=a;m++){let l=[Hg(d,f[m-1]),Bn(m+1,1),i];f[m]=n(ct(...l))}return ct(...f).slice(0,r)}function Ug(e,t,r,n,o){if(qr(e),qr(t),Ti(r),t.length>255){let s=Math.ceil(2*n/8);t=o.create({dkLen:s}).update(Au("H2C-OVERSIZE-DST-")).update(t).digest()}if(r>65535||t.length>255)throw new Error("expand_message_xof: invalid lenInBytes");return o.create({dkLen:r}).update(e).update(Bn(r,2)).update(t).update(Bn(t.length,1)).digest()}function wh(e,t,r){Wr(r,{DST:"stringOrUint8Array",p:"bigint",m:"isSafeInteger",k:"isSafeInteger",hash:"hash"});let{p:n,k:o,m:s,hash:a,expand:i,DST:c}=r;qr(e),Ti(t);let u=typeof c=="string"?Au(c):c,f=n.toString(2).length,d=Math.ceil((f+o)/8),p=t*s*d,m;if(i==="xmd")m=Dg(e,u,p,a);else if(i==="xof")m=Ug(e,u,p,o,a);else if(i==="_internal_pass")m=e;else throw new Error('expand must be "xmd" or "xof"');let l=new Array(t);for(let h=0;hArray.from(n).reverse());return(n,o)=>{let[s,a,i,c]=r.map(d=>d.reduce((p,m)=>e.add(e.mul(p,n),m))),[u,f]=Us(e,[a,c],!0);return n=e.mul(s,u),o=e.mul(o,e.mul(i,f)),{x:n,y:o}}}function Th(e,t,r){if(typeof t!="function")throw new Error("mapToCurve() must be defined");function n(s){return e.fromAffine(t(s))}function o(s){let a=s.clearCofactor();return a.equals(e.ZERO)?e.ZERO:(a.assertValidity(),a)}return{defaults:r,hashToCurve(s,a){let i=wh(s,2,{...r,DST:r.DST,...a}),c=n(i[0]),u=n(i[1]);return o(c.add(u))},encodeToCurve(s,a){let i=wh(s,1,{...r,DST:r.encodeDST,...a});return o(n(i[0]))},mapToCurve(s){if(!Array.isArray(s))throw new Error("expected array of bigints");for(let a of s)if(typeof a!="bigint")throw new Error("expected array of bigints");return o(n(s))}}}var $g,Ah=_(()=>{wi();Ds();$g=gt});var Ch={};Qa(Ch,{encodeToCurve:()=>Kg,hashToCurve:()=>Zg,schnorr:()=>qg,secp256k1:()=>It,secp256k1_hasher:()=>Mp});function _h(e){let t=Si,r=BigInt(3),n=BigInt(6),o=BigInt(11),s=BigInt(22),a=BigInt(23),i=BigInt(44),c=BigInt(88),u=e*e*e%t,f=u*u*e%t,d=_t(f,r,t)*f%t,p=_t(d,r,t)*f%t,m=_t(p,Iu,t)*u%t,l=_t(m,o,t)*m%t,h=_t(l,s,t)*l%t,x=_t(h,i,t)*h%t,b=_t(x,c,t)*x%t,A=_t(b,i,t)*h%t,v=_t(A,r,t)*f%t,y=_t(v,a,t)*l%t,w=_t(y,n,t)*u%t,E=_t(w,Iu,t);if(!kn.eql(kn.sqr(E),e))throw new Error("Cannot find square root");return E}function Bu(e,...t){let r=Sh[e];if(r===void 0){let n=lo(Uint8Array.from(e,o=>o.charCodeAt(0)));r=ct(n,n),Sh[e]=r}return lo(ct(r,...t))}function kp(e){let t=It.utils.normPrivateKeyToScalar(e),r=Np.fromPrivateKey(t);return{scalar:r.hasEvenY()?t:Pi(-t),bytes:Rp(r)}}function Ih(e){Gr("x",e,Ai,Si);let t=Ip(e*e),r=Ip(t*e+BigInt(7)),n=_h(r);n%Iu!==Cp&&(n=Ip(-n));let o=new Np(e,n,Ai);return o.assertValidity(),o}function Bh(...e){return Pi(Ls(Bu("BIP0340/challenge",...e)))}function jg(e){return kp(e).bytes}function Vg(e,t,r=Xc(32)){let n=Ce("message",e),{bytes:o,scalar:s}=kp(t),a=Ce("auxRand",r,32),i=Bp(s^Ls(Bu("BIP0340/aux",a))),c=Bu("BIP0340/nonce",i,o,n),u=Pi(Ls(c));if(u===Cp)throw new Error("sign failed: k is zero");let{bytes:f,scalar:d}=kp(u),p=Bh(f,o,n),m=new Uint8Array(64);if(m.set(f,0),m.set(Bp(Pi(d+p*s)),32),!kh(m,n,o))throw new Error("sign: Invalid signature produced");return m}function kh(e,t,r){let n=Ce("signature",e,64),o=Ce("message",t),s=Ce("publicKey",r,32);try{let a=Ih(Ls(s)),i=Ls(n.subarray(0,32));if(!ho(i,Ai,Si))return!1;let c=Ls(n.subarray(32,64));if(!ho(c,Ai,_u))return!1;let u=Bh(Bp(i),Rp(a),o),f=Lg(a,c,Pi(-u));return!(!f||!f.hasEvenY()||f.toAffine().x!==i)}catch{return!1}}var Si,_u,Cp,Ai,Iu,Ph,kn,It,Sh,Rp,Bp,Ip,Pi,Np,Lg,Ls,qg,Gg,Wg,Mp,Zg,Kg,js=_(()=>{ip();xn();vh();Ah();wi();Ds();_p();Si=BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"),_u=BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"),Cp=BigInt(0),Ai=BigInt(1),Iu=BigInt(2),Ph=(e,t)=>(e+t/Iu)/t;kn=vi(Si,void 0,void 0,{sqrt:_h}),It=gh({a:Cp,b:BigInt(7),Fp:kn,n:_u,Gx:BigInt("55066263022277343669578718895168534326250603453777594175500187360389116729240"),Gy:BigInt("32670510020758816978083085130507043184471273380659243275938904335757337482424"),h:BigInt(1),lowS:!0,endo:{beta:BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"),splitScalar:e=>{let t=_u,r=BigInt("0x3086d221a7d46bcde86c90e49284eb15"),n=-Ai*BigInt("0xe4437ed6010e88286f547fa90abfe4c3"),o=BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8"),s=r,a=BigInt("0x100000000000000000000000000000000"),i=Ph(s*e,t),c=Ph(-n*e,t),u=Pe(e-i*r-c*o,t),f=Pe(-i*n-c*s,t),d=u>a,p=f>a;if(d&&(u=t-u),p&&(f=t-f),u>a||f>a)throw new Error("splitScalar: Endomorphism failed, k="+e);return{k1neg:d,k1:u,k2neg:p,k2:f}}}},lo),Sh={};Rp=e=>e.toRawBytes(!0).slice(1),Bp=e=>xr(e,32),Ip=e=>Pe(e,Si),Pi=e=>Pe(e,_u),Np=It.ProjectivePoint,Lg=(e,t,r)=>Np.BASE.multiplyAndAddUnsafe(e,t,r);Ls=gt;qg={getPublicKey:jg,sign:Vg,verify:kh,utils:{randomPrivateKey:It.utils.randomPrivateKey,lift_x:Ih,pointToBytes:Rp,numberToBytesBE:xr,bytesToNumberBE:gt,taggedHash:Bu,mod:Pe}},Gg=Eh(kn,[["0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7","0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581","0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262","0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c"],["0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b","0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14","0x0000000000000000000000000000000000000000000000000000000000000001"],["0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c","0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3","0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931","0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84"],["0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b","0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573","0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f","0x0000000000000000000000000000000000000000000000000000000000000001"]].map(e=>e.map(t=>BigInt(t)))),Wg=bh(kn,{A:BigInt("0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533"),B:BigInt("1771"),Z:kn.create(BigInt("-11"))}),Mp=Th(It.ProjectivePoint,e=>{let{x:t,y:r}=Wg(kn.create(e[0]));return Gg(t,r)},{DST:"secp256k1_XMD:SHA-256_SSWU_RO_",encodeDST:"secp256k1_XMD:SHA-256_SSWU_NU_",p:kn.ORDER,m:1,k:128,expand:"xmd",hash:lo}),Zg=Mp.hashToCurve,Kg=Mp.encodeToCurve});var Kr,$t,Vs,qs,Gs,Ws,Zs,Ks,Ys,Js,br,Ht,Rn=_(()=>{ms();J();Kr=class extends g{constructor({cause:t,message:r}={}){let n=r?.replace("execution reverted: ","")?.replace("execution reverted","");super(`Execution reverted ${n?`with reason: ${n}`:"for an unknown reason"}.`,{cause:t,name:"ExecutionRevertedError"})}};Object.defineProperty(Kr,"code",{enumerable:!0,configurable:!0,writable:!0,value:3});Object.defineProperty(Kr,"nodeMessage",{enumerable:!0,configurable:!0,writable:!0,value:/execution reverted|gas required exceeds allowance/});$t=class extends g{constructor({cause:t,maxFeePerGas:r}={}){super(`The fee cap (\`maxFeePerGas\`${r?` = ${$e(r)} gwei`:""}) cannot be higher than the maximum allowed value (2^256-1).`,{cause:t,name:"FeeCapTooHighError"})}};Object.defineProperty($t,"nodeMessage",{enumerable:!0,configurable:!0,writable:!0,value:/max fee per gas higher than 2\^256-1|fee cap higher than 2\^256-1/});Vs=class extends g{constructor({cause:t,maxFeePerGas:r}={}){super(`The fee cap (\`maxFeePerGas\`${r?` = ${$e(r)}`:""} gwei) cannot be lower than the block base fee.`,{cause:t,name:"FeeCapTooLowError"})}};Object.defineProperty(Vs,"nodeMessage",{enumerable:!0,configurable:!0,writable:!0,value:/max fee per gas less than block base fee|fee cap less than block base fee|transaction is outdated/});qs=class extends g{constructor({cause:t,nonce:r}={}){super(`Nonce provided for the transaction ${r?`(${r}) `:""}is higher than the next one expected.`,{cause:t,name:"NonceTooHighError"})}};Object.defineProperty(qs,"nodeMessage",{enumerable:!0,configurable:!0,writable:!0,value:/nonce too high/});Gs=class extends g{constructor({cause:t,nonce:r}={}){super([`Nonce provided for the transaction ${r?`(${r}) `:""}is lower than the current nonce of the account.`,"Try increasing the nonce or find the latest nonce with `getTransactionCount`."].join(`
+`),{cause:t,name:"NonceTooLowError"})}};Object.defineProperty(Gs,"nodeMessage",{enumerable:!0,configurable:!0,writable:!0,value:/nonce too low|transaction already imported|already known/});Ws=class extends g{constructor({cause:t,nonce:r}={}){super(`Nonce provided for the transaction ${r?`(${r}) `:""}exceeds the maximum allowed nonce.`,{cause:t,name:"NonceMaxValueError"})}};Object.defineProperty(Ws,"nodeMessage",{enumerable:!0,configurable:!0,writable:!0,value:/nonce has max value/});Zs=class extends g{constructor({cause:t}={}){super(["The total cost (gas * gas fee + value) of executing this transaction exceeds the balance of the account."].join(`
+`),{cause:t,metaMessages:["This error could arise when the account does not have enough funds to:"," - pay for the total gas fee,"," - pay for the value to send."," ","The cost of the transaction is calculated as `gas * gas fee + value`, where:"," - `gas` is the amount of gas needed for transaction to execute,"," - `gas fee` is the gas fee,"," - `value` is the amount of ether to send to the recipient."],name:"InsufficientFundsError"})}};Object.defineProperty(Zs,"nodeMessage",{enumerable:!0,configurable:!0,writable:!0,value:/insufficient funds|exceeds transaction sender account balance/});Ks=class extends g{constructor({cause:t,gas:r}={}){super(`The amount of gas ${r?`(${r}) `:""}provided for the transaction exceeds the limit allowed for the block.`,{cause:t,name:"IntrinsicGasTooHighError"})}};Object.defineProperty(Ks,"nodeMessage",{enumerable:!0,configurable:!0,writable:!0,value:/intrinsic gas too high|gas limit reached/});Ys=class extends g{constructor({cause:t,gas:r}={}){super(`The amount of gas ${r?`(${r}) `:""}provided for the transaction is too low.`,{cause:t,name:"IntrinsicGasTooLowError"})}};Object.defineProperty(Ys,"nodeMessage",{enumerable:!0,configurable:!0,writable:!0,value:/intrinsic gas too low/});Js=class extends g{constructor({cause:t}){super("The transaction type is not supported for this chain.",{cause:t,name:"TransactionTypeNotSupportedError"})}};Object.defineProperty(Js,"nodeMessage",{enumerable:!0,configurable:!0,writable:!0,value:/transaction type not valid/});br=class extends g{constructor({cause:t,maxPriorityFeePerGas:r,maxFeePerGas:n}={}){super([`The provided tip (\`maxPriorityFeePerGas\`${r?` = ${$e(r)} gwei`:""}) cannot be higher than the fee cap (\`maxFeePerGas\`${n?` = ${$e(n)} gwei`:""}).`].join(`
+`),{cause:t,name:"TipAboveFeeCapError"})}};Object.defineProperty(br,"nodeMessage",{enumerable:!0,configurable:!0,writable:!0,value:/max priority fee per gas higher than max fee per gas|tip higher than fee cap/});Ht=class extends g{constructor({cause:t}){super(`An error occurred while executing: ${t?.shortMessage}`,{cause:t,name:"UnknownNodeError"})}}});function Nn(e,t){let r=(e.details||"").toLowerCase(),n=e instanceof g?e.walk(o=>o?.code===Kr.code):e;return n instanceof g?new Kr({cause:e,message:n.details}):Kr.nodeMessage.test(r)?new Kr({cause:e,message:e.details}):$t.nodeMessage.test(r)?new $t({cause:e,maxFeePerGas:t?.maxFeePerGas}):Vs.nodeMessage.test(r)?new Vs({cause:e,maxFeePerGas:t?.maxFeePerGas}):qs.nodeMessage.test(r)?new qs({cause:e,nonce:t?.nonce}):Gs.nodeMessage.test(r)?new Gs({cause:e,nonce:t?.nonce}):Ws.nodeMessage.test(r)?new Ws({cause:e,nonce:t?.nonce}):Zs.nodeMessage.test(r)?new Zs({cause:e}):Ks.nodeMessage.test(r)?new Ks({cause:e,gas:t?.gas}):Ys.nodeMessage.test(r)?new Ys({cause:e,gas:t?.gas}):Js.nodeMessage.test(r)?new Js({cause:e}):br.nodeMessage.test(r)?new br({cause:e,maxFeePerGas:t?.maxFeePerGas,maxPriorityFeePerGas:t?.maxPriorityFeePerGas}):new Ht({cause:e})}var Ii=_(()=>{J();Rn()});function Dt(e,{format:t}){if(!t)return{};let r={};function n(s){let a=Object.keys(s);for(let i of a)i in e&&(r[i]=e[i]),s[i]&&typeof s[i]=="object"&&!Array.isArray(s[i])&&n(s[i])}let o=t(e||{});return n(o),r}var bo=_(()=>{});function Xs(e,t){return({exclude:r,format:n})=>({exclude:r,format:(o,s)=>{let a=t(o,s);if(r)for(let i of r)delete a[i];return{...a,...n(o,s)}},type:e})}var Ru=_(()=>{});function nt(e,t){let r={};return typeof e.authorizationList<"u"&&(r.authorizationList=Qg(e.authorizationList)),typeof e.accessList<"u"&&(r.accessList=e.accessList),typeof e.blobVersionedHashes<"u"&&(r.blobVersionedHashes=e.blobVersionedHashes),typeof e.blobs<"u"&&(typeof e.blobs[0]!="string"?r.blobs=e.blobs.map(n=>ae(n)):r.blobs=e.blobs),typeof e.data<"u"&&(r.data=e.data),e.account&&(r.from=e.account.address),typeof e.from<"u"&&(r.from=e.from),typeof e.gas<"u"&&(r.gas=I(e.gas)),typeof e.gasPrice<"u"&&(r.gasPrice=I(e.gasPrice)),typeof e.maxFeePerBlobGas<"u"&&(r.maxFeePerBlobGas=I(e.maxFeePerBlobGas)),typeof e.maxFeePerGas<"u"&&(r.maxFeePerGas=I(e.maxFeePerGas)),typeof e.maxPriorityFeePerGas<"u"&&(r.maxPriorityFeePerGas=I(e.maxPriorityFeePerGas)),typeof e.nonce<"u"&&(r.nonce=I(e.nonce)),typeof e.to<"u"&&(r.to=e.to),typeof e.type<"u"&&(r.type=Xg[e.type]),typeof e.value<"u"&&(r.value=I(e.value)),r}function Qg(e){return e.map(t=>({address:t.address,r:t.r?I(BigInt(t.r)):t.r,s:t.s?I(BigInt(t.s)):t.s,chainId:I(t.chainId),nonce:I(t.nonce),...typeof t.yParity<"u"?{yParity:I(t.yParity)}:{},...typeof t.v<"u"&&typeof t.yParity>"u"?{v:I(t.v)}:{}}))}var Xg,Yr=_(()=>{Z();Xg={legacy:"0x0",eip2930:"0x1",eip1559:"0x2",eip4844:"0x3",eip7702:"0x4"}});function Oh(e){if(!(!e||e.length===0))return e.reduce((t,{slot:r,value:n})=>{if(r.length!==66)throw new ui({size:r.length,targetSize:66,type:"hex"});if(n.length!==66)throw new ui({size:n.length,targetSize:66,type:"hex"});return t[r]=n,t},{})}function e6(e){let{balance:t,nonce:r,state:n,stateDiff:o,code:s}=e,a={};if(s!==void 0&&(a.code=s),t!==void 0&&(a.balance=I(t)),r!==void 0&&(a.nonce=I(r)),n!==void 0&&(a.state=Oh(n)),o!==void 0){if(a.state)throw new du;a.stateDiff=Oh(o)}return a}function Qs(e){if(!e)return;let t={};for(let{address:r,...n}of e){if(!re(r,{strict:!1}))throw new me({address:r});if(t[r])throw new fu({address:r});t[r]=e6(n)}return t}var Nu=_(()=>{er();Lc();ap();ht();Z()});var t6,r6,n6,o6,s6,a6,i6,c6,u6,f6,d6,p6,m6,l6,h6,y6,x6,b6,g6,v6,w6,E6,T6,A6,P6,S6,_6,I6,B6,k6,C6,R6,N6,M6,z6,F6,O6,$6,H6,D6,U6,L6,j6,V6,q6,G6,W6,Z6,K6,Y6,J6,X6,Q6,e2,t2,r2,n2,o2,s2,a2,i2,c2,u2,f2,d2,p2,m2,l2,h2,y2,x2,b2,g2,v2,w2,E2,T2,A2,P2,S2,_2,I2,B2,k2,C2,R2,N2,M2,z2,F2,O2,$2,H2,D2,U2,gr,Mu=_(()=>{t6=2n**(8n-1n)-1n,r6=2n**(16n-1n)-1n,n6=2n**(24n-1n)-1n,o6=2n**(32n-1n)-1n,s6=2n**(40n-1n)-1n,a6=2n**(48n-1n)-1n,i6=2n**(56n-1n)-1n,c6=2n**(64n-1n)-1n,u6=2n**(72n-1n)-1n,f6=2n**(80n-1n)-1n,d6=2n**(88n-1n)-1n,p6=2n**(96n-1n)-1n,m6=2n**(104n-1n)-1n,l6=2n**(112n-1n)-1n,h6=2n**(120n-1n)-1n,y6=2n**(128n-1n)-1n,x6=2n**(136n-1n)-1n,b6=2n**(144n-1n)-1n,g6=2n**(152n-1n)-1n,v6=2n**(160n-1n)-1n,w6=2n**(168n-1n)-1n,E6=2n**(176n-1n)-1n,T6=2n**(184n-1n)-1n,A6=2n**(192n-1n)-1n,P6=2n**(200n-1n)-1n,S6=2n**(208n-1n)-1n,_6=2n**(216n-1n)-1n,I6=2n**(224n-1n)-1n,B6=2n**(232n-1n)-1n,k6=2n**(240n-1n)-1n,C6=2n**(248n-1n)-1n,R6=2n**(256n-1n)-1n,N6=-(2n**(8n-1n)),M6=-(2n**(16n-1n)),z6=-(2n**(24n-1n)),F6=-(2n**(32n-1n)),O6=-(2n**(40n-1n)),$6=-(2n**(48n-1n)),H6=-(2n**(56n-1n)),D6=-(2n**(64n-1n)),U6=-(2n**(72n-1n)),L6=-(2n**(80n-1n)),j6=-(2n**(88n-1n)),V6=-(2n**(96n-1n)),q6=-(2n**(104n-1n)),G6=-(2n**(112n-1n)),W6=-(2n**(120n-1n)),Z6=-(2n**(128n-1n)),K6=-(2n**(136n-1n)),Y6=-(2n**(144n-1n)),J6=-(2n**(152n-1n)),X6=-(2n**(160n-1n)),Q6=-(2n**(168n-1n)),e2=-(2n**(176n-1n)),t2=-(2n**(184n-1n)),r2=-(2n**(192n-1n)),n2=-(2n**(200n-1n)),o2=-(2n**(208n-1n)),s2=-(2n**(216n-1n)),a2=-(2n**(224n-1n)),i2=-(2n**(232n-1n)),c2=-(2n**(240n-1n)),u2=-(2n**(248n-1n)),f2=-(2n**(256n-1n)),d2=2n**8n-1n,p2=2n**16n-1n,m2=2n**24n-1n,l2=2n**32n-1n,h2=2n**40n-1n,y2=2n**48n-1n,x2=2n**56n-1n,b2=2n**64n-1n,g2=2n**72n-1n,v2=2n**80n-1n,w2=2n**88n-1n,E2=2n**96n-1n,T2=2n**104n-1n,A2=2n**112n-1n,P2=2n**120n-1n,S2=2n**128n-1n,_2=2n**136n-1n,I2=2n**144n-1n,B2=2n**152n-1n,k2=2n**160n-1n,C2=2n**168n-1n,R2=2n**176n-1n,N2=2n**184n-1n,M2=2n**192n-1n,z2=2n**200n-1n,F2=2n**208n-1n,O2=2n**216n-1n,$2=2n**224n-1n,H2=2n**232n-1n,D2=2n**240n-1n,U2=2n**248n-1n,gr=2n**256n-1n});function He(e){let{account:t,maxFeePerGas:r,maxPriorityFeePerGas:n,to:o}=e,s=t?K(t):void 0;if(s&&!re(s.address))throw new me({address:s.address});if(o&&!re(o))throw new me({address:o});if(r&&r>gr)throw new $t({maxFeePerGas:r});if(n&&r&&n>r)throw new br({maxFeePerGas:r,maxPriorityFeePerGas:n})}var vr=_(()=>{be();Mu();er();Rn();ht()});function Bt(e){let{blockHash:t,blockNumber:r,blockTag:n,requireCanonical:o}=e;if(o!==void 0&&!t)throw new g("`requireCanonical` can only be provided when `blockHash` is set.");return t?o?{blockHash:t,requireCanonical:o}:{blockHash:t}:typeof r=="bigint"?I(r):n??"latest"}var go=_(()=>{J();Z()});function Le(e,t){if(!re(e,{strict:!1}))throw new me({address:e});if(!re(t,{strict:!1}))throw new me({address:t});return e.toLowerCase()===t.toLowerCase()}var Xr=_(()=>{er();ht()});function ot(e){let{abi:t,args:r,functionName:n,data:o}=e,s=t[0];if(n){let i=xt({abi:t,args:r,name:n});if(!i)throw new Xt(n,{docsPath:$p});s=i}if(s.type!=="function")throw new Xt(void 0,{docsPath:$p});if(!s.outputs)throw new as(s.name,{docsPath:$p});let a=Ur(s.outputs,o);if(a&&a.length>1)return a;if(a&&a.length===1)return a[0]}var $p,Qr=_(()=>{Ae();yi();gn();$p="/docs/contract/decodeFunctionResult"});var Zh,Kh=_(()=>{Zh="0.1.1"});function Yh(){return Zh}var Jh=_(()=>{Kh()});function Xh(e,t){return t?.(e)?e:e&&typeof e=="object"&&"cause"in e&&e.cause?Xh(e.cause,t):t?null:e}var j,vt=_(()=>{Jh();j=class e extends Error{static setStaticOptions(t){e.prototype.docsOrigin=t.docsOrigin,e.prototype.showVersion=t.showVersion,e.prototype.version=t.version}constructor(t,r={}){let n=(()=>{if(r.cause instanceof e){if(r.cause.details)return r.cause.details;if(r.cause.shortMessage)return r.cause.shortMessage}return r.cause&&"details"in r.cause&&typeof r.cause.details=="string"?r.cause.details:r.cause?.message?r.cause.message:r.details})(),o=r.cause instanceof e&&r.cause.docsPath||r.docsPath,s=r.docsOrigin??e.prototype.docsOrigin,a=`${s}${o??""}`,i=!!(r.version??e.prototype.showVersion),c=r.version??e.prototype.version,u=[t||"An error occurred.",...r.metaMessages?["",...r.metaMessages]:[],...n||o||i?["",n?`Details: ${n}`:void 0,o?`See: ${a}`:void 0,i?`Version: ${c}`:void 0]:[]].filter(f=>typeof f=="string").join(`
+`);super(u,r.cause?{cause:r.cause}:void 0),Object.defineProperty(this,"details",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"docs",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"docsOrigin",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"docsPath",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"shortMessage",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"showVersion",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"version",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"cause",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"BaseError"}),this.cause=r.cause,this.details=n,this.docs=a,this.docsOrigin=s,this.docsPath=o,this.shortMessage=t,this.showVersion=i,this.version=c}walk(t){return Xh(this,t)}};Object.defineProperty(j,"defaultStaticOptions",{enumerable:!0,configurable:!0,writable:!0,value:{docsOrigin:"https://oxlib.sh",showVersion:!1,version:`ox@${Yh()}`}});j.setStaticOptions(j.defaultStaticOptions)});function fa(e,t){if(Ut(e)>t)throw new Vu({givenSize:Ut(e),maxSize:t})}function Qh(e,t){if(typeof t=="number"&&t>0&&t>Ut(e)-1)throw new Ci({offset:t,position:"start",size:Ut(e)})}function ey(e,t,r){if(typeof t=="number"&&typeof r=="number"&&Ut(e)!==r-t)throw new Ci({offset:r,position:"end",size:Ut(e)})}function Hp(e){if(e>=en.zero&&e<=en.nine)return e-en.zero;if(e>=en.A&&e<=en.F)return e-(en.A-10);if(e>=en.a&&e<=en.f)return e-(en.a-10)}function ty(e,t={}){let{dir:r,size:n=32}=t;if(n===0)return e;if(e.length>n)throw new qu({size:e.length,targetSize:n,type:"Bytes"});let o=new Uint8Array(n);for(let s=0;s{On();en={zero:48,nine:57,A:65,F:70,a:97,f:102}});function da(e,t){if(ge(e)>t)throw new Gu({givenSize:ge(e),maxSize:t})}function ny(e,t){if(typeof t=="number"&&t>0&&t>ge(e)-1)throw new Ri({offset:t,position:"start",size:ge(e)})}function oy(e,t,r){if(typeof t=="number"&&typeof r=="number"&&ge(e)!==r-t)throw new Ri({offset:r,position:"end",size:ge(e)})}function Up(e,t={}){let{dir:r,size:n=32}=t;if(n===0)return e;let o=e.replace("0x","");if(o.length>n*2)throw new Wu({size:Math.ceil(o.length/2),targetSize:n,type:"Hex"});return`0x${o[r==="right"?"padEnd":"padStart"](n*2,"0")}`}function sy(e,t={}){let{dir:r="left"}=t,n=e.replace("0x",""),o=0;for(let s=0;s{Ve()});function $n(e,t,r){return JSON.stringify(e,(n,o)=>typeof t=="function"?t(n,o):typeof o=="bigint"?o.toString()+Z2:o,r)}var Z2,Ni=_(()=>{Z2="#__bigint"});function J2(e){if(!(e instanceof Uint8Array)){if(!e)throw new pa(e);if(typeof e!="object")throw new pa(e);if(!("BYTES_PER_ELEMENT"in e))throw new pa(e);if(e.BYTES_PER_ELEMENT!==1||e.constructor.name!=="Uint8Array")throw new pa(e)}}function iy(e){return e instanceof Uint8Array?e:typeof e=="string"?ma(e):X2(e)}function X2(e){return e instanceof Uint8Array?e:new Uint8Array(e)}function ma(e,t={}){let{size:r}=t,n=e;r&&(da(e,r),n=Ar(e,r));let o=n.slice(2);o.length%2&&(o=`0${o}`);let s=o.length/2,a=new Uint8Array(s);for(let i=0,c=0;i1||n[0]>1)throw new jp(n);return!!n[0]}function Tr(e,t={}){let{size:r}=t;typeof r<"u"&&fa(e,r);let n=Se(e,t);return Ku(n,t)}function py(e,t={}){let{size:r}=t,n=e;return typeof r<"u"&&(fa(n,r),n=e5(n)),K2.decode(n)}function Vp(e){return Dp(e,{dir:"left"})}function e5(e){return Dp(e,{dir:"right"})}function my(e){try{return J2(e),!0}catch{return!1}}var K2,Y2,jp,pa,Vu,Ci,qu,On=_(()=>{vt();Ve();ry();Lp();Ni();K2=new TextDecoder,Y2=new TextEncoder;jp=class extends j{constructor(t){super(`Bytes value \`${t}\` is not a valid boolean.`,{metaMessages:["The bytes array must contain a single byte of either a `0` or `1` value."]}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Bytes.InvalidBytesBooleanError"})}},pa=class extends j{constructor(t){super(`Value \`${typeof t=="object"?$n(t):t}\` of type \`${typeof t}\` is an invalid Bytes value.`,{metaMessages:["Bytes values must be of type `Bytes`."]}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Bytes.InvalidBytesTypeError"})}},Vu=class extends j{constructor({givenSize:t,maxSize:r}){super(`Size cannot exceed \`${r}\` bytes. Given size: \`${t}\` bytes.`),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Bytes.SizeOverflowError"})}},Ci=class extends j{constructor({offset:t,position:r,size:n}){super(`Slice ${r==="start"?"starting":"ending"} at offset \`${t}\` is out-of-bounds (size: \`${n}\`).`),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Bytes.SliceOffsetOutOfBoundsError"})}},qu=class extends j{constructor({size:t,targetSize:r,type:n}){super(`${n.charAt(0).toUpperCase()}${n.slice(1).toLowerCase()} size (\`${t}\`) exceeds padding size (\`${r}\`).`),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Bytes.SizeExceedsPaddingSizeError"})}}});function n5(e,t={}){let{strict:r=!1}=t;if(!e)throw new Yu(e);if(typeof e!="string")throw new Yu(e);if(r&&!/^0x[0-9a-fA-F]*$/.test(e))throw new Ju(e);if(!e.startsWith("0x"))throw new Ju(e)}function Ee(...e){return`0x${e.reduce((t,r)=>t+r.replace("0x",""),"")}`}function la(e){return e instanceof Uint8Array?Se(e):Array.isArray(e)?Se(new Uint8Array(e)):e}function Xu(e,t={}){let r=`0x${Number(e)}`;return typeof t.size=="number"?(da(r,t.size),tn(r,t.size)):r}function Se(e,t={}){let r="";for(let o=0;os||o>1n;return n<=a?n:n-s-1n}function Ku(e,t={}){let{signed:r,size:n}=t;return Number(!r&&!n?e:qp(e,t))}function zi(e,t={}){let{strict:r=!1}=t;try{return n5(e,{strict:r}),!0}catch{return!1}}var t5,r5,Mi,Yu,Ju,Gu,Ri,Wu,Ve=_(()=>{vt();Lp();Ni();t5=new TextEncoder,r5=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,"0"));Mi=class extends j{constructor({max:t,min:r,signed:n,size:o,value:s}){super(`Number \`${s}\` is not in safe${o?` ${o*8}-bit`:""}${n?" signed":" unsigned"} integer range ${t?`(\`${r}\` to \`${t}\`)`:`(above \`${r}\`)`}`),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Hex.IntegerOutOfRangeError"})}},Yu=class extends j{constructor(t){super(`Value \`${typeof t=="object"?$n(t):t}\` of type \`${typeof t}\` is an invalid hex type.`,{metaMessages:['Hex types must be represented as `"0x${string}"`.']}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Hex.InvalidHexTypeError"})}},Ju=class extends j{constructor(t){super(`Value \`${t}\` is an invalid hex value.`,{metaMessages:['Hex values must start with `"0x"` and contain only hexadecimal characters (0-9, a-f, A-F).']}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Hex.InvalidHexValueError"})}},Gu=class extends j{constructor({givenSize:t,maxSize:r}){super(`Size cannot exceed \`${r}\` bytes. Given size: \`${t}\` bytes.`),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Hex.SizeOverflowError"})}},Ri=class extends j{constructor({offset:t,position:r,size:n}){super(`Slice ${r==="start"?"starting":"ending"} at offset \`${t}\` is out-of-bounds (size: \`${n}\`).`),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Hex.SliceOffsetOutOfBoundsError"})}},Wu=class extends j{constructor({size:t,targetSize:r,type:n}){super(`${n.charAt(0).toUpperCase()}${n.slice(1).toLowerCase()} size (\`${t}\`) exceeds padding size (\`${r}\`).`),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Hex.SizeExceedsPaddingSizeError"})}}});function ly(e){return{address:e.address,amount:ue(e.amount),index:ue(e.index),validatorIndex:ue(e.validatorIndex)}}var hy=_(()=>{Ve()});function Qu(e){return{...typeof e.baseFeePerGas=="bigint"&&{baseFeePerGas:ue(e.baseFeePerGas)},...typeof e.blobBaseFee=="bigint"&&{blobBaseFee:ue(e.blobBaseFee)},...typeof e.feeRecipient=="string"&&{feeRecipient:e.feeRecipient},...typeof e.gasLimit=="bigint"&&{gasLimit:ue(e.gasLimit)},...typeof e.number=="bigint"&&{number:ue(e.number)},...typeof e.prevRandao=="bigint"&&{prevRandao:ue(e.prevRandao)},...typeof e.time=="bigint"&&{time:ue(e.time)},...e.withdrawals&&{withdrawals:e.withdrawals.map(ly)}}}var Wp=_(()=>{Ve();hy()});var sr,ef,xy,tf,by,Zp,Kp,Yp,Fi,_e,Ze=_(()=>{sr=[{inputs:[{components:[{name:"target",type:"address"},{name:"allowFailure",type:"bool"},{name:"callData",type:"bytes"}],name:"calls",type:"tuple[]"}],name:"aggregate3",outputs:[{components:[{name:"success",type:"bool"},{name:"returnData",type:"bytes"}],name:"returnData",type:"tuple[]"}],stateMutability:"view",type:"function"},{inputs:[{name:"addr",type:"address"}],name:"getEthBalance",outputs:[{name:"balance",type:"uint256"}],stateMutability:"view",type:"function"},{inputs:[],name:"getCurrentBlockTimestamp",outputs:[{internalType:"uint256",name:"timestamp",type:"uint256"}],stateMutability:"view",type:"function"}],ef=[{name:"query",type:"function",stateMutability:"view",inputs:[{type:"tuple[]",name:"queries",components:[{type:"address",name:"sender"},{type:"string[]",name:"urls"},{type:"bytes",name:"data"}]}],outputs:[{type:"bool[]",name:"failures"},{type:"bytes[]",name:"responses"}]},{name:"HttpError",type:"error",inputs:[{type:"uint16",name:"status"},{type:"string",name:"message"}]}],xy=[{inputs:[{name:"dns",type:"bytes"}],name:"DNSDecodingFailed",type:"error"},{inputs:[{name:"ens",type:"string"}],name:"DNSEncodingFailed",type:"error"},{inputs:[],name:"EmptyAddress",type:"error"},{inputs:[{name:"status",type:"uint16"},{name:"message",type:"string"}],name:"HttpError",type:"error"},{inputs:[],name:"InvalidBatchGatewayResponse",type:"error"},{inputs:[{name:"errorData",type:"bytes"}],name:"ResolverError",type:"error"},{inputs:[{name:"name",type:"bytes"},{name:"resolver",type:"address"}],name:"ResolverNotContract",type:"error"},{inputs:[{name:"name",type:"bytes"}],name:"ResolverNotFound",type:"error"},{inputs:[{name:"primary",type:"string"},{name:"primaryAddress",type:"bytes"}],name:"ReverseAddressMismatch",type:"error"},{inputs:[{internalType:"bytes4",name:"selector",type:"bytes4"}],name:"UnsupportedResolverProfile",type:"error"}],tf=[...xy,{name:"resolveWithGateways",type:"function",stateMutability:"view",inputs:[{name:"name",type:"bytes"},{name:"data",type:"bytes"},{name:"gateways",type:"string[]"}],outputs:[{name:"",type:"bytes"},{name:"address",type:"address"}]}],by=[...xy,{name:"reverseWithGateways",type:"function",stateMutability:"view",inputs:[{type:"bytes",name:"reverseName"},{type:"uint256",name:"coinType"},{type:"string[]",name:"gateways"}],outputs:[{type:"string",name:"resolvedName"},{type:"address",name:"resolver"},{type:"address",name:"reverseResolver"}]}],Zp=[{name:"text",type:"function",stateMutability:"view",inputs:[{name:"name",type:"bytes32"},{name:"key",type:"string"}],outputs:[{name:"",type:"string"}]}],Kp=[{name:"addr",type:"function",stateMutability:"view",inputs:[{name:"name",type:"bytes32"}],outputs:[{name:"",type:"address"}]},{name:"addr",type:"function",stateMutability:"view",inputs:[{name:"name",type:"bytes32"},{name:"coinType",type:"uint256"}],outputs:[{name:"",type:"bytes"}]}],Yp=[{name:"isValidSignature",type:"function",stateMutability:"view",inputs:[{name:"hash",type:"bytes32"},{name:"signature",type:"bytes"}],outputs:[{name:"",type:"bytes4"}]}],Fi=[{inputs:[{name:"_signer",type:"address"},{name:"_hash",type:"bytes32"},{name:"_signature",type:"bytes"}],stateMutability:"nonpayable",type:"constructor"},{inputs:[{name:"_signer",type:"address"},{name:"_hash",type:"bytes32"},{name:"_signature",type:"bytes"}],outputs:[{type:"bool"}],stateMutability:"nonpayable",type:"function",name:"isValidSig"}],_e=[{type:"event",name:"Approval",inputs:[{indexed:!0,name:"owner",type:"address"},{indexed:!0,name:"spender",type:"address"},{indexed:!1,name:"value",type:"uint256"}]},{type:"event",name:"Transfer",inputs:[{indexed:!0,name:"from",type:"address"},{indexed:!0,name:"to",type:"address"},{indexed:!1,name:"value",type:"uint256"}]},{type:"function",name:"allowance",stateMutability:"view",inputs:[{name:"owner",type:"address"},{name:"spender",type:"address"}],outputs:[{type:"uint256"}]},{type:"function",name:"approve",stateMutability:"nonpayable",inputs:[{name:"spender",type:"address"},{name:"amount",type:"uint256"}],outputs:[{type:"bool"}]},{type:"function",name:"balanceOf",stateMutability:"view",inputs:[{name:"account",type:"address"}],outputs:[{type:"uint256"}]},{type:"function",name:"decimals",stateMutability:"view",inputs:[],outputs:[{type:"uint8"}]},{type:"function",name:"name",stateMutability:"view",inputs:[],outputs:[{type:"string"}]},{type:"function",name:"symbol",stateMutability:"view",inputs:[],outputs:[{type:"string"}]},{type:"function",name:"totalSupply",stateMutability:"view",inputs:[],outputs:[{type:"uint256"}]},{type:"function",name:"transfer",stateMutability:"nonpayable",inputs:[{name:"recipient",type:"address"},{name:"amount",type:"uint256"}],outputs:[{type:"bool"}]},{type:"function",name:"transferFrom",stateMutability:"nonpayable",inputs:[{name:"sender",type:"address"},{name:"recipient",type:"address"},{name:"amount",type:"uint256"}],outputs:[{type:"bool"}]}]});var gy,vy=_(()=>{gy="0x82ad56cb"});var rf,wy,Ey,ya,Oi=_(()=>{rf="0x608060405234801561001057600080fd5b5060405161018e38038061018e83398101604081905261002f91610124565b6000808351602085016000f59050803b61004857600080fd5b6000808351602085016000855af16040513d6000823e81610067573d81fd5b3d81f35b634e487b7160e01b600052604160045260246000fd5b600082601f83011261009257600080fd5b81516001600160401b038111156100ab576100ab61006b565b604051601f8201601f19908116603f011681016001600160401b03811182821017156100d9576100d961006b565b6040528181528382016020018510156100f157600080fd5b60005b82811015610110576020818601810151838301820152016100f4565b506000918101602001919091529392505050565b6000806040838503121561013757600080fd5b82516001600160401b0381111561014d57600080fd5b61015985828601610081565b602085015190935090506001600160401b0381111561017757600080fd5b61018385828601610081565b915050925092905056fe",wy="0x608060405234801561001057600080fd5b506040516102c03803806102c083398101604081905261002f916101e6565b836001600160a01b03163b6000036100e457600080836001600160a01b03168360405161005c9190610270565b6000604051808303816000865af19150503d8060008114610099576040519150601f19603f3d011682016040523d82523d6000602084013e61009e565b606091505b50915091508115806100b857506001600160a01b0386163b155b156100e1578060405163101bb98d60e01b81526004016100d8919061028c565b60405180910390fd5b50505b6000808451602086016000885af16040513d6000823e81610103573d81fd5b3d81f35b80516001600160a01b038116811461011e57600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561015457818101518382015260200161013c565b50506000910152565b600082601f83011261016e57600080fd5b81516001600160401b0381111561018757610187610123565b604051601f8201601f19908116603f011681016001600160401b03811182821017156101b5576101b5610123565b6040528181528382016020018510156101cd57600080fd5b6101de826020830160208701610139565b949350505050565b600080600080608085870312156101fc57600080fd5b61020585610107565b60208601519094506001600160401b0381111561022157600080fd5b61022d8782880161015d565b93505061023c60408601610107565b60608601519092506001600160401b0381111561025857600080fd5b6102648782880161015d565b91505092959194509250565b60008251610282818460208701610139565b9190910192915050565b60208152600082518060208401526102ab816040850160208701610139565b601f01601f1916919091016040019291505056fe",Ey="0x608060405234801561001057600080fd5b5060405161069438038061069483398101604081905261002f9161051e565b600061003c848484610048565b9050806000526001601ff35b60007f64926492649264926492649264926492649264926492649264926492649264926100748361040c565b036101e7576000606080848060200190518101906100929190610577565b60405192955090935091506000906001600160a01b038516906100b69085906105dd565b6000604051808303816000865af19150503d80600081146100f3576040519150601f19603f3d011682016040523d82523d6000602084013e6100f8565b606091505b50509050876001600160a01b03163b60000361016057806101605760405162461bcd60e51b815260206004820152601e60248201527f5369676e617475726556616c696461746f723a206465706c6f796d656e74000060448201526064015b60405180910390fd5b604051630b135d3f60e11b808252906001600160a01b038a1690631626ba7e90610190908b9087906004016105f9565b602060405180830381865afa1580156101ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d19190610633565b6001600160e01b03191614945050505050610405565b6001600160a01b0384163b1561027a57604051630b135d3f60e11b808252906001600160a01b03861690631626ba7e9061022790879087906004016105f9565b602060405180830381865afa158015610244573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102689190610633565b6001600160e01b031916149050610405565b81516041146102df5760405162461bcd60e51b815260206004820152603a602482015260008051602061067483398151915260448201527f3a20696e76616c6964207369676e6174757265206c656e6774680000000000006064820152608401610157565b6102e7610425565b5060208201516040808401518451859392600091859190811061030c5761030c61065d565b016020015160f81c9050601b811480159061032b57508060ff16601c14155b1561038c5760405162461bcd60e51b815260206004820152603b602482015260008051602061067483398151915260448201527f3a20696e76616c6964207369676e617475726520762076616c756500000000006064820152608401610157565b60408051600081526020810180835289905260ff83169181019190915260608101849052608081018390526001600160a01b0389169060019060a0016020604051602081039080840390855afa1580156103ea573d6000803e3d6000fd5b505050602060405103516001600160a01b0316149450505050505b9392505050565b600060208251101561041d57600080fd5b508051015190565b60405180606001604052806003906020820280368337509192915050565b6001600160a01b038116811461045857600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561048c578181015183820152602001610474565b50506000910152565b600082601f8301126104a657600080fd5b81516001600160401b038111156104bf576104bf61045b565b604051601f8201601f19908116603f011681016001600160401b03811182821017156104ed576104ed61045b565b60405281815283820160200185101561050557600080fd5b610516826020830160208701610471565b949350505050565b60008060006060848603121561053357600080fd5b835161053e81610443565b6020850151604086015191945092506001600160401b0381111561056157600080fd5b61056d86828701610495565b9150509250925092565b60008060006060848603121561058c57600080fd5b835161059781610443565b60208501519093506001600160401b038111156105b357600080fd5b6105bf86828701610495565b604086015190935090506001600160401b0381111561056157600080fd5b600082516105ef818460208701610471565b9190910192915050565b828152604060208201526000825180604084015261061e816060850160208701610471565b601f01601f1916919091016060019392505050565b60006020828403121561064557600080fd5b81516001600160e01b03198116811461040557600080fd5b634e487b7160e01b600052603260045260246000fdfe5369676e617475726556616c696461746f72237265636f7665725369676e6572",ya="0x608060405234801561001057600080fd5b506115b9806100206000396000f3fe6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e14610325578063bce38bd714610350578063c3077fa914610380578063ee82ac5e146103b2576100f3565b80634d2301cc1461026257806372425d9d1461029f57806382ad56cb146102ca57806386d516e8146102fa576100f3565b80633408e470116100c65780633408e470146101af578063399542e9146101da5780633e64a6961461020c57806342cbb15c14610237576100f3565b80630f28c97d146100f8578063174dea7114610123578063252dba421461015357806327e86d6e14610184575b600080fd5b34801561010457600080fd5b5061010d6103ef565b60405161011a9190610c0a565b60405180910390f35b61013d60048036038101906101389190610c94565b6103f7565b60405161014a9190610e94565b60405180910390f35b61016d60048036038101906101689190610f0c565b610615565b60405161017b92919061101b565b60405180910390f35b34801561019057600080fd5b506101996107ab565b6040516101a69190611064565b60405180910390f35b3480156101bb57600080fd5b506101c46107b7565b6040516101d19190610c0a565b60405180910390f35b6101f460048036038101906101ef91906110ab565b6107bf565b6040516102039392919061110b565b60405180910390f35b34801561021857600080fd5b506102216107e1565b60405161022e9190610c0a565b60405180910390f35b34801561024357600080fd5b5061024c6107e9565b6040516102599190610c0a565b60405180910390f35b34801561026e57600080fd5b50610289600480360381019061028491906111a7565b6107f1565b6040516102969190610c0a565b60405180910390f35b3480156102ab57600080fd5b506102b4610812565b6040516102c19190610c0a565b60405180910390f35b6102e460048036038101906102df919061122a565b61081a565b6040516102f19190610e94565b60405180910390f35b34801561030657600080fd5b5061030f6109e4565b60405161031c9190610c0a565b60405180910390f35b34801561033157600080fd5b5061033a6109ec565b6040516103479190611286565b60405180910390f35b61036a600480360381019061036591906110ab565b6109f4565b6040516103779190610e94565b60405180910390f35b61039a60048036038101906103959190610f0c565b610ba6565b6040516103a99392919061110b565b60405180910390f35b3480156103be57600080fd5b506103d960048036038101906103d491906112cd565b610bca565b6040516103e69190611064565b60405180910390f35b600042905090565b60606000808484905090508067ffffffffffffffff81111561041c5761041b6112fa565b5b60405190808252806020026020018201604052801561045557816020015b610442610bd5565b81526020019060019003908161043a5790505b5092503660005b828110156105c957600085828151811061047957610478611329565b5b6020026020010151905087878381811061049657610495611329565b5b90506020028101906104a89190611367565b925060008360400135905080860195508360000160208101906104cb91906111a7565b73ffffffffffffffffffffffffffffffffffffffff16818580606001906104f2919061138f565b604051610500929190611431565b60006040518083038185875af1925050503d806000811461053d576040519150601f19603f3d011682016040523d82523d6000602084013e610542565b606091505b5083600001846020018290528215151515815250505081516020850135176105bc577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b826001019250505061045c565b5082341461060c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610603906114a7565b60405180910390fd5b50505092915050565b6000606043915060008484905090508067ffffffffffffffff81111561063e5761063d6112fa565b5b60405190808252806020026020018201604052801561067157816020015b606081526020019060019003908161065c5790505b5091503660005b828110156107a157600087878381811061069557610694611329565b5b90506020028101906106a791906114c7565b92508260000160208101906106bc91906111a7565b73ffffffffffffffffffffffffffffffffffffffff168380602001906106e2919061138f565b6040516106f0929190611431565b6000604051808303816000865af19150503d806000811461072d576040519150601f19603f3d011682016040523d82523d6000602084013e610732565b606091505b5086848151811061074657610745611329565b5b60200260200101819052819250505080610795576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161078c9061153b565b60405180910390fd5b81600101915050610678565b5050509250929050565b60006001430340905090565b600046905090565b6000806060439250434091506107d68686866109f4565b905093509350939050565b600048905090565b600043905090565b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600044905090565b606060008383905090508067ffffffffffffffff81111561083e5761083d6112fa565b5b60405190808252806020026020018201604052801561087757816020015b610864610bd5565b81526020019060019003908161085c5790505b5091503660005b828110156109db57600084828151811061089b5761089a611329565b5b602002602001015190508686838181106108b8576108b7611329565b5b90506020028101906108ca919061155b565b92508260000160208101906108df91906111a7565b73ffffffffffffffffffffffffffffffffffffffff16838060400190610905919061138f565b604051610913929190611431565b6000604051808303816000865af19150503d8060008114610950576040519150601f19603f3d011682016040523d82523d6000602084013e610955565b606091505b5082600001836020018290528215151515815250505080516020840135176109cf577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b8160010191505061087e565b50505092915050565b600045905090565b600041905090565b606060008383905090508067ffffffffffffffff811115610a1857610a176112fa565b5b604051908082528060200260200182016040528015610a5157816020015b610a3e610bd5565b815260200190600190039081610a365790505b5091503660005b82811015610b9c576000848281518110610a7557610a74611329565b5b60200260200101519050868683818110610a9257610a91611329565b5b9050602002810190610aa491906114c7565b9250826000016020810190610ab991906111a7565b73ffffffffffffffffffffffffffffffffffffffff16838060200190610adf919061138f565b604051610aed929190611431565b6000604051808303816000865af19150503d8060008114610b2a576040519150601f19603f3d011682016040523d82523d6000602084013e610b2f565b606091505b508260000183602001829052821515151581525050508715610b90578060000151610b8f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b869061153b565b60405180910390fd5b5b81600101915050610a58565b5050509392505050565b6000806060610bb7600186866107bf565b8093508194508295505050509250925092565b600081409050919050565b6040518060400160405280600015158152602001606081525090565b6000819050919050565b610c0481610bf1565b82525050565b6000602082019050610c1f6000830184610bfb565b92915050565b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b60008083601f840112610c5457610c53610c2f565b5b8235905067ffffffffffffffff811115610c7157610c70610c34565b5b602083019150836020820283011115610c8d57610c8c610c39565b5b9250929050565b60008060208385031215610cab57610caa610c25565b5b600083013567ffffffffffffffff811115610cc957610cc8610c2a565b5b610cd585828601610c3e565b92509250509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b60008115159050919050565b610d2281610d0d565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610d62578082015181840152602081019050610d47565b83811115610d71576000848401525b50505050565b6000601f19601f8301169050919050565b6000610d9382610d28565b610d9d8185610d33565b9350610dad818560208601610d44565b610db681610d77565b840191505092915050565b6000604083016000830151610dd96000860182610d19565b5060208301518482036020860152610df18282610d88565b9150508091505092915050565b6000610e0a8383610dc1565b905092915050565b6000602082019050919050565b6000610e2a82610ce1565b610e348185610cec565b935083602082028501610e4685610cfd565b8060005b85811015610e825784840389528151610e638582610dfe565b9450610e6e83610e12565b925060208a01995050600181019050610e4a565b50829750879550505050505092915050565b60006020820190508181036000830152610eae8184610e1f565b905092915050565b60008083601f840112610ecc57610ecb610c2f565b5b8235905067ffffffffffffffff811115610ee957610ee8610c34565b5b602083019150836020820283011115610f0557610f04610c39565b5b9250929050565b60008060208385031215610f2357610f22610c25565b5b600083013567ffffffffffffffff811115610f4157610f40610c2a565b5b610f4d85828601610eb6565b92509250509250929050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b6000610f918383610d88565b905092915050565b6000602082019050919050565b6000610fb182610f59565b610fbb8185610f64565b935083602082028501610fcd85610f75565b8060005b858110156110095784840389528151610fea8582610f85565b9450610ff583610f99565b925060208a01995050600181019050610fd1565b50829750879550505050505092915050565b60006040820190506110306000830185610bfb565b81810360208301526110428184610fa6565b90509392505050565b6000819050919050565b61105e8161104b565b82525050565b60006020820190506110796000830184611055565b92915050565b61108881610d0d565b811461109357600080fd5b50565b6000813590506110a58161107f565b92915050565b6000806000604084860312156110c4576110c3610c25565b5b60006110d286828701611096565b935050602084013567ffffffffffffffff8111156110f3576110f2610c2a565b5b6110ff86828701610eb6565b92509250509250925092565b60006060820190506111206000830186610bfb565b61112d6020830185611055565b818103604083015261113f8184610e1f565b9050949350505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061117482611149565b9050919050565b61118481611169565b811461118f57600080fd5b50565b6000813590506111a18161117b565b92915050565b6000602082840312156111bd576111bc610c25565b5b60006111cb84828501611192565b91505092915050565b60008083601f8401126111ea576111e9610c2f565b5b8235905067ffffffffffffffff81111561120757611206610c34565b5b60208301915083602082028301111561122357611222610c39565b5b9250929050565b6000806020838503121561124157611240610c25565b5b600083013567ffffffffffffffff81111561125f5761125e610c2a565b5b61126b858286016111d4565b92509250509250929050565b61128081611169565b82525050565b600060208201905061129b6000830184611277565b92915050565b6112aa81610bf1565b81146112b557600080fd5b50565b6000813590506112c7816112a1565b92915050565b6000602082840312156112e3576112e2610c25565b5b60006112f1848285016112b8565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600080fd5b600080fd5b600080fd5b60008235600160800383360303811261138357611382611358565b5b80830191505092915050565b600080833560016020038436030381126113ac576113ab611358565b5b80840192508235915067ffffffffffffffff8211156113ce576113cd61135d565b5b6020830192506001820236038313156113ea576113e9611362565b5b509250929050565b600081905092915050565b82818337600083830152505050565b600061141883856113f2565b93506114258385846113fd565b82840190509392505050565b600061143e82848661140c565b91508190509392505050565b600082825260208201905092915050565b7f4d756c746963616c6c333a2076616c7565206d69736d61746368000000000000600082015250565b6000611491601a8361144a565b915061149c8261145b565b602082019050919050565b600060208201905081810360008301526114c081611484565b9050919050565b6000823560016040038336030381126114e3576114e2611358565b5b80830191505092915050565b7f4d756c746963616c6c333a2063616c6c206661696c6564000000000000000000600082015250565b600061152560178361144a565b9150611530826114ef565b602082019050919050565b6000602082019050818103600083015261155481611518565b9050919050565b60008235600160600383360303811261157757611576611358565b5b8083019150509291505056fea264697066735822122020c1bc9aacf8e4a6507193432a895a8e77094f45a1395583f07b24e860ef06cd64736f6c634300080c0033"});var Eo,nf,of,$i,To,Hi=_(()=>{J();Eo=class extends g{constructor({blockNumber:t,chain:r,contract:n}){super(`Chain "${r.name}" does not support contract "${n.name}".`,{metaMessages:["This could be due to any of the following:",...t&&n.blockCreated&&n.blockCreated>t?[`- The contract "${n.name}" was not deployed until block ${n.blockCreated} (current block ${t}).`]:[`- The chain does not have the contract "${n.name}" configured.`]],name:"ChainDoesNotSupportContract"})}},nf=class extends g{constructor({chain:t,currentChainId:r}){super(`The current chain of the wallet (id: ${r}) does not match the target chain for the transaction (id: ${t.id} \u2013 ${t.name}).`,{metaMessages:[`Current Chain ID:  ${r}`,`Expected Chain ID: ${t.id} \u2013 ${t.name}`],name:"ChainMismatchError"})}},of=class extends g{constructor(){super(["No chain was provided to the request.","Please provide a chain with the `chain` argument on the Action, or by supplying a `chain` to WalletClient."].join(`
+`),{name:"ChainNotFoundError"})}},$i=class extends g{constructor(){super("No chain was provided to the Client.",{name:"ClientChainNotConfiguredError"})}},To=class extends g{constructor({chainId:t}){super(typeof t=="number"?`Chain ID "${t}" is invalid.`:"Chain ID is invalid.",{name:"InvalidChainIdError"})}}});function Ao(e){let{abi:t,args:r,bytecode:n}=e;if(!r||r.length===0)return n;let o=t.find(a=>"type"in a&&a.type==="constructor");if(!o)throw new Bc({docsPath:Jp});if(!("inputs"in o))throw new oi({docsPath:Jp});if(!o.inputs||o.inputs.length===0)throw new oi({docsPath:Jp});let s=Je(o.inputs,r);return xe([n,s])}var Jp,sf=_(()=>{Ae();qe();Dr();Jp="/docs/contract/encodeDeployData"});function Lt({blockNumber:e,chain:t,contract:r}){let n=t?.contracts?.[r];if(!n)throw new Eo({chain:t,contract:{name:r}});if(e&&n.blockCreated&&n.blockCreated>e)throw new Eo({blockNumber:e,chain:t,contract:{name:r,blockCreated:n.blockCreated}});return n.address}var Po=_(()=>{Hi()});function af(e,{docsPath:t,...r}){let n=(()=>{let o=Nn(e,r);return o instanceof Ht?e:o})();return new hs(n,{docsPath:t,...r})}var Xp=_(()=>{En();Rn();Ii()});function xa(){let e=()=>{},t=()=>{};return{promise:new Promise((n,o)=>{e=n,t=o}),resolve:e,reject:t}}var cf=_(()=>{});function uf({fn:e,id:t,shouldSplitBatch:r,wait:n=0,sort:o}){let s=async()=>{let f=c();a();let d=f.map(({args:p})=>p);d.length!==0&&e(d).then(p=>{o&&Array.isArray(p)&&p.sort(o);for(let m=0;m{for(let m=0;mQp.delete(t),i=()=>c().map(({args:f})=>f),c=()=>Qp.get(t)||[],u=f=>Qp.set(t,[...c(),f]);return{flush:a,async schedule(f){let{promise:d,resolve:p,reject:m}=xa();return r?.([...i(),f])&&s(),c().length>0?(u({args:f,resolve:p,reject:m}),d):(u({args:f,resolve:p,reject:m}),setTimeout(s,n),d)}}}var Qp,em=_(()=>{cf();Qp=new Map});var ff,df,pf,Ty=_(()=>{Qe();J();rr();ff=class extends g{constructor({callbackSelector:t,cause:r,data:n,extraData:o,sender:s,urls:a}){super(r.shortMessage||"An error occurred while fetching for an offchain result.",{cause:r,metaMessages:[...r.metaMessages||[],r.metaMessages?.length?"":[],"Offchain Gateway Call:",a&&["  Gateway URL(s):",...a.map(i=>`    ${io(i)}`)],`  Sender: ${s}`,`  Data: ${n}`,`  Callback selector: ${t}`,`  Extra data: ${o}`].flat(),name:"OffchainLookupError"})}},df=class extends g{constructor({result:t,url:r}){super("Offchain gateway response is malformed. Response data must be a hex value.",{metaMessages:[`Gateway URL: ${io(r)}`,`Response: ${ne(t)}`],name:"OffchainLookupResponseMalformedError"})}},pf=class extends g{constructor({sender:t,to:r}){super("Reverted sender address does not match target contract address (`to`).",{metaMessages:[`Contract address: ${r}`,`OffchainLookup sender address: ${t}`],name:"OffchainLookupSenderMismatchError"})}}});function Ay(e){let{abi:t,data:r}=e,n=yt(r,0,4),o=t.find(s=>s.type==="function"&&n===mr(Ne(s)));if(!o)throw new Fc(n,{docsPath:"/docs/contract/decodeFunctionData"});return{functionName:o.name,args:"inputs"in o&&o.inputs&&o.inputs.length>0?Ur(o.inputs,yt(r,4)):void 0}}var Py=_(()=>{Ae();Hr();cs();yi();Nr()});function rm(e){let{abi:t,errorName:r,args:n}=e,o=t[0];if(r){let c=xt({abi:t,args:n,name:r});if(!c)throw new si(r,{docsPath:tm});o=c}if(o.type!=="error")throw new si(void 0,{docsPath:tm});let s=Ne(o),a=mr(s),i="0x";if(n&&n.length>0){if(!o.inputs)throw new Nc(o.name,{docsPath:tm});i=Je(o.inputs,n)}return xe([a,i])}var tm,Sy=_(()=>{Ae();qe();cs();Dr();Nr();gn();tm="/docs/contract/encodeErrorResult"});function _y(e){let{abi:t,functionName:r,result:n}=e,o=t[0];if(r){let a=xt({abi:t,name:r});if(!a)throw new Xt(r,{docsPath:nm});o=a}if(o.type!=="function")throw new Xt(void 0,{docsPath:nm});if(!o.outputs)throw new as(o.name,{docsPath:nm});let s=(()=>{if(o.outputs.length===0)return[];if(o.outputs.length===1)return[n];if(Array.isArray(n))return n;throw new is(n)})();return Je(o.outputs,s)}var nm,Iy=_(()=>{Ae();Dr();gn();nm="/docs/contract/encodeFunctionResult"});async function om(e){let{data:t,ccipRequest:r}=e,{args:[n]}=Ay({abi:ef,data:t}),o=[],s=[];return await Promise.all(n.map(async(a,i)=>{try{s[i]=a.urls.includes(rn)?await om({data:a.data,ccipRequest:r}):await r(a),o[i]=!1}catch(c){o[i]=!0,s[i]=s5(c)}})),_y({abi:ef,functionName:"query",result:[o,s]})}function s5(e){return e.name==="HttpRequestError"&&e.status?rm({abi:ef,errorName:"HttpError",args:[e.status,e.shortMessage]}):rm({abi:[ou],errorName:"Error",args:["shortMessage"in e?e.shortMessage:e.message]})}var rn,Di=_(()=>{Ze();su();Py();Sy();Iy();rn="x-batch-gateway:true"});var Cy={};Qa(Cy,{ccipRequest:()=>ky,offchainLookup:()=>i5,offchainLookupAbiItem:()=>By,offchainLookupSignature:()=>a5});async function i5(e,{blockNumber:t,blockTag:r,data:n,requestOptions:o,to:s}){let{args:a}=cu({data:n,abi:[By]}),[i,c,u,f,d]=a,{ccipRead:p}=e,m=p&&typeof p?.request=="function"?p.request:ky;try{if(!Le(s,i))throw new pf({sender:i,to:s});let l=c.includes(rn)?await om({data:u,ccipRequest:x=>m({...x,requestOptions:o})}):await m({data:u,requestOptions:o,sender:i,urls:c}),{data:h}=await jt(e,{blockNumber:t,blockTag:r,data:Oe([f,Je([{type:"bytes"},{type:"bytes"}],[l,d])]),requestOptions:o,to:s});return h}catch(l){throw o?.signal?.aborted?Ge(o.signal):bt(l)?l:new ff({callbackSelector:f,cause:l,data:n,extraData:d,sender:i,urls:c})}}async function ky({data:e,requestOptions:t,sender:r,urls:n}){let o=new Error("An unknown error occurred.");for(let s=0;s{So();Ty();fo();rr();np();Dr();Xr();qe();Rt();Di();Qe();a5="0x556f1830",By={name:"OffchainLookup",type:"error",inputs:[{name:"sender",type:"address"},{name:"urls",type:"string[]"},{name:"callData",type:"bytes"},{name:"callbackFunction",type:"bytes4"},{name:"extraData",type:"bytes"}]}});async function jt(e,t){let{account:r=e.account,authorizationList:n,batch:o=!!e.batch?.multicall,blockHash:s,blockNumber:a,blockTag:i=e.experimental_blockTag??"latest",requireCanonical:c,accessList:u,blobs:f,blockOverrides:d,code:p,data:m,factory:l,factoryData:h,gas:x,gasPrice:b,maxFeePerBlobGas:A,maxFeePerGas:v,maxPriorityFeePerGas:y,nonce:w,requestOptions:E,to:T,value:S,stateOverride:R,...P}=t,z=r?K(r):void 0;if(p&&(l||h))throw new g("Cannot provide both `code` & `factory`/`factoryData` as parameters.");if(p&&T)throw new g("Cannot provide both `code` & `to` as parameters.");let H=p&&m,O=l&&h&&T&&m,q=H||O,k=H?zy({code:p,data:m}):O?m5({data:m,factory:l,factoryData:h,to:T}):m;try{He(t);let C=Bt({blockHash:s,blockNumber:a,blockTag:i,requireCanonical:c}),D=d?Qu(d):void 0,M=Qs(R),L=e.chain?.formatters?.transactionRequest?.format,se=(L||nt)({...Dt(P,{format:L}),accessList:u,account:z,authorizationList:n,blobs:f,data:k,gas:x,gasPrice:b,maxFeePerBlobGas:A,maxFeePerGas:v,maxPriorityFeePerGas:y,nonce:w,to:q?void 0:T,value:S},"call");if(o&&c5({request:se})&&!D&&s===void 0)try{let{deployless:he=!1}=typeof e.batch?.multicall=="object"?e.batch.multicall:{},Be=My(e,{blockNumber:a,deployless:he});if(!Be||!p5(M,Be))return await d5(e,{...se,blockHash:s,blockNumber:a,blockTag:i,multicallAddress:Be,requestOptions:E,requireCanonical:c,rpcStateOverride:M})}catch(he){if(!(he instanceof $i)&&!(he instanceof Eo))throw he}let Re=(()=>{let he=[se,C];return M&&D?[...he,M,D]:M?[...he,M]:D?[...he,{},D]:he})(),ft=await e.request({method:"eth_call",params:Re},E);return ft==="0x"?{data:void 0}:{data:ft}}catch(C){if(E?.signal?.aborted)throw Ge(E.signal);if(bt(C))throw C;let D=l5(C),{offchainLookup:M,offchainLookupSignature:L}=await Promise.resolve().then(()=>(Ry(),Cy));if(e.ccipRead!==!1&&D?.slice(0,10)===L&&T)return{data:await M(e,{data:D,requestOptions:E,to:T})};throw q&&D?.slice(0,10)==="0x101bb98d"?new bu({factory:l}):af(C,{...t,account:z,chain:e.chain})}}function c5({request:e}){let{data:t,to:r,...n}=e;return!(!t||t.startsWith(gy)||!r||Object.values(n).filter(o=>typeof o<"u").length>0)}function f5(e){if(!e)return"default";let t=Ny.get(e);if(t!==void 0)return t;let r=u5++;return Ny.set(e,r),r}async function d5(e,t){let{batchSize:r=1024,deployless:n=!1,wait:o=0}=typeof e.batch?.multicall=="object"?e.batch.multicall:{},{blockHash:s,blockNumber:a,blockTag:i=e.experimental_blockTag??"latest",requireCanonical:c,data:u,multicallAddress:f,requestOptions:d,rpcStateOverride:p,to:m}=t,l=f!==void 0?f:My(e,{blockNumber:a,deployless:n}),h=Bt({blockHash:s,blockNumber:a,blockTag:i,requireCanonical:c}),x=typeof h=="string"?h:JSON.stringify(h),b=p?`.${JSON.stringify(p)}`:"",{schedule:A}=uf({id:`${e.uid}.${x}.${f5(d)}${b}`,wait:o,shouldSplitBatch(w){return w.reduce((T,{data:S})=>T+(S.length-2),0)>r*2},fn:async w=>{let E=w.map(P=>({allowFailure:!0,callData:P.data,target:P.to})),T=ie({abi:sr,args:[E],functionName:"aggregate3"}),S={...l===null?{data:zy({code:ya,data:T})}:{to:l,data:T}},R=await e.request({method:"eth_call",params:p?[S,h,p]:[S,h]},d);return ot({abi:sr,args:[E],functionName:"aggregate3",data:R||"0x"})}}),[{returnData:v,success:y}]=await A({data:u,to:m});if(!y)throw new yr({data:v});return v==="0x"?{data:void 0}:{data:v}}function My(e,t){let{blockNumber:r,deployless:n}=t;if(n)return null;if(e.chain)return Lt({blockNumber:r,chain:e.chain,contract:"multicall3"});throw new $i}function p5(e,t){return e?Object.keys(e).some(r=>Le(r,t)):!1}function zy(e){let{code:t,data:r}=e;return Ao({abi:Pc(["constructor(bytes, bytes)"]),bytecode:rf,args:[t,r]})}function m5(e){let{data:t,factory:r,factoryData:n,to:o}=e;return Ao({abi:Pc(["constructor(address, bytes, address, bytes)"]),bytecode:wy,args:[o,t,r,n]})}function l5(e){if(!(e instanceof g))return;let t=e.walk();return typeof t?.data=="object"?t.data?.data:t.data}var u5,Ny,So=_(()=>{ri();Wp();be();Ze();vy();Oi();J();Hi();En();rr();Qr();sf();Xe();Xr();go();Po();Xp();bo();Yr();em();Nu();vr();u5=0,Ny=new WeakMap});function B(e,t,r){let n=e[t.name];if(typeof n=="function")return n;let o=e[r];return typeof o=="function"?o:s=>t(e,s)}Ae();J();var Uc=class extends g{constructor(t){super(`Filter type "${t}" is not supported.`,{name:"FilterTypeNotSupportedError"})}};Fe();At();pi();Dr();Nr();gn();var I0="/docs/contract/encodeEventTopics";function lr(e){let{abi:t,eventName:r,args:n}=e,o=t[0];if(r){let c=xt({abi:t,name:r});if(!c)throw new ai(r,{docsPath:I0});o=c}if(o.type!=="event")throw new ai(void 0,{docsPath:I0});let s=[];if(n&&"inputs"in o){let c=o.inputs?.filter(f=>"indexed"in f&&f.indexed),u=Array.isArray(n)?n:Object.values(n).length>0?c?.map(f=>n[f.name])??[]:[];u.length>0&&(s=c?.map((f,d)=>Array.isArray(u[d])?u[d].map((p,m)=>B0({param:f,value:u[d][m]})):typeof u[d]<"u"&&u[d]!==null?B0({param:f,value:u[d]}):null)??[])}if(o.anonymous)return s;let a=Ne(o);return[bn(a),...s]}function B0({param:e,value:t}){if(e.type==="string"||e.type==="bytes")return oe(it(t));if(e.type==="tuple"||e.type.match(/^(.*)\[(\d+)?\]$/))throw new Uc(e.type);return Je([e],[t])}Z();function vn(e,{method:t}){let r={};return e.transport.type==="fallback"&&e.transport.onResponse?.(({method:n,response:o,status:s,transport:a})=>{s==="success"&&t===n&&(r[o]=a.request)}),(n=>r[n]||e.request)}async function nu(e,t){let{address:r,abi:n,args:o,eventName:s,fromBlock:a,strict:i,toBlock:c}=t,u=vn(e,{method:"eth_newFilter"}),f=s?lr({abi:n,args:o,eventName:s}):void 0,d=await e.request({method:"eth_newFilter",params:[{address:r,fromBlock:typeof a=="bigint"?I(a):a,toBlock:typeof c=="bigint"?I(c):c,topics:f}]});return{abi:n,args:o,eventName:s,id:d,request:u(d),strict:!!i,type:"event"}}be();Xe();Ae();J();En();fo();Fs();var vg=3;function St(e,{abi:t,address:r,args:n,docsPath:o,functionName:s,sender:a}){let i=e instanceof yr?e:e instanceof g?e.walk(l=>"data"in l)||e.walk():{},{code:c,data:u,details:f,message:d,shortMessage:p}=i,m=e instanceof Nt?new xu({functionName:s,cause:e}):[vg,Lr.code].includes(c)&&(u||f||d||p)||c===Ot.code&&f==="execution reverted"&&u?new co({abi:t,data:typeof u=="object"?u.data:u,functionName:s,message:i instanceof Tn?f:p??d,cause:e}):e;return new ys(m,{abi:t,args:n,contractAddress:r,docsPath:o,functionName:s,sender:a})}be();J();tr();At();function vu(e){let t=oe(`0x${e.substring(4)}`).substring(26);return pr(`0x${t}`)}Rt();pt();ze();Z();async function Nh({hash:e,signature:t}){let r=we(e)?e:ce(e),{secp256k1:n}=await Promise.resolve().then(()=>(js(),Ch));return`0x${(()=>{if(typeof t=="object"&&"r"in t&&"s"in t){let{r:u,s:f,v:d,yParity:p}=t,m=Number(p??d),l=Rh(m);return new n.Signature(pe(u),pe(f)).addRecoveryBit(l)}let a=we(t)?t:ce(t);if(ee(a)!==65)throw new Error("invalid signature length");let i=ye(`0x${a.slice(130)}`),c=Rh(i);return n.Signature.fromCompact(a.substring(2,130)).addRecoveryBit(c)})().recoverPublicKey(r.substring(2)).toHex(!1)}`}function Rh(e){if(e===0||e===1)return e;if(e===27)return 0;if(e===28)return 1;throw new Error("Invalid yParityOrV value")}async function _i({hash:e,signature:t}){return vu(await Nh({hash:e,signature:t}))}qe();Fe();Z();J();iu();Fe();Z();function or(e,t="hex"){let r=Mh(e),n=fs(new Uint8Array(r.length));return r.encode(n),t==="hex"?ae(n.bytes):n.bytes}function Mh(e){return Array.isArray(e)?Yg(e.map(t=>Mh(t))):Jg(e)}function Yg(e){let t=e.reduce((o,s)=>o+s.length,0),r=zh(t);return{length:t<=55?1+t:1+r+t,encode(o){t<=55?o.pushByte(192+t):(o.pushByte(247+r),r===1?o.pushUint8(t):r===2?o.pushUint16(t):r===3?o.pushUint24(t):o.pushUint32(t));for(let{encode:s}of e)s(o)}}}function Jg(e){let t=typeof e=="string"?ke(e):e,r=zh(t.length);return{length:t.length===1&&t[0]<128?1:t.length<=55?1+t.length:1+r+t.length,encode(o){t.length===1&&t[0]<128?o.pushBytes(t):t.length<=55?(o.pushByte(128+t.length),o.pushBytes(t)):(o.pushByte(183+r),r===1?o.pushUint8(t.length):r===2?o.pushUint16(t.length):r===3?o.pushUint24(t.length):o.pushUint32(t.length),o.pushBytes(t))}}}function zh(e){if(e<2**8)return 1;if(e<2**16)return 2;if(e<2**24)return 3;if(e<2**32)return 4;throw new g("Length is too large.")}At();function ku(e){let{chainId:t,nonce:r,to:n}=e,o=e.contractAddress??e.address,s=oe(xe(["0x05",or([t?I(t):"0x",o,r?I(r):"0x"])]));return n==="bytes"?ke(s):s}async function Cn(e){let{authorization:t,signature:r}=e;return _i({hash:ku(t),signature:r??t})}Z();uu();ms();J();Pt();var Cu=class extends g{constructor(t,{account:r,docsPath:n,chain:o,data:s,gas:a,gasPrice:i,maxFeePerGas:c,maxPriorityFeePerGas:u,nonce:f,to:d,value:p}){let m=ao({from:r?.address,to:d,value:typeof p<"u"&&`${ps(p)} ${o?.nativeCurrency?.symbol||"ETH"}`,data:s,gas:a,gasPrice:typeof i<"u"&&`${$e(i)} gwei`,maxFeePerGas:typeof c<"u"&&`${$e(c)} gwei`,maxPriorityFeePerGas:typeof u<"u"&&`${$e(u)} gwei`,nonce:f});super(t.shortMessage,{cause:t,docsPath:n,metaMessages:[...t.metaMessages?[...t.metaMessages," "]:[],"Estimate Gas Arguments:",m].filter(Boolean),name:"EstimateGasExecutionError"}),Object.defineProperty(this,"cause",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.cause=t}};Rn();Ii();function Fh(e,{docsPath:t,...r}){let n=(()=>{let o=Nn(e,r);return o instanceof Ht?e:o})();return new Cu(n,{docsPath:t,...r})}bo();Yr();Nu();vr();be();ms();J();var ea=class extends g{constructor(){super("`baseFeeMultiplier` must be greater than 1.",{name:"BaseFeeScalarError"})}},Mn=class extends g{constructor(){super("Chain does not support EIP-1559 fees.",{name:"Eip1559FeesNotSupportedError"})}},zu=class extends g{constructor({maxPriorityFeePerGas:t}){super(`\`maxFeePerGas\` cannot be less than the \`maxPriorityFeePerGas\` (${$e(t)} gwei).`,{name:"MaxFeePerGasTooLowError"})}};ze();J();var zn=class extends g{constructor({blockHash:t,blockNumber:r}){let n="Block";t&&(n=`Block at hash "${t}"`),r&&(n=`Block at number "${r}"`),super(`${n} could not be found.`,{name:"BlockNotFoundError"})}};Z();Ru();ze();Ru();var zp={"0x0":"legacy","0x1":"eip2930","0x2":"eip1559","0x3":"eip4844","0x4":"eip7702"};function Jr(e,t){let r={...e,blockHash:e.blockHash?e.blockHash:null,blockNumber:e.blockNumber?BigInt(e.blockNumber):null,...e.blockTimestamp!=null&&{blockTimestamp:BigInt(e.blockTimestamp)},chainId:e.chainId?ye(e.chainId):void 0,gas:e.gas?BigInt(e.gas):void 0,gasPrice:e.gasPrice?BigInt(e.gasPrice):void 0,maxFeePerBlobGas:e.maxFeePerBlobGas?BigInt(e.maxFeePerBlobGas):void 0,maxFeePerGas:e.maxFeePerGas?BigInt(e.maxFeePerGas):void 0,maxPriorityFeePerGas:e.maxPriorityFeePerGas?BigInt(e.maxPriorityFeePerGas):void 0,nonce:e.nonce?ye(e.nonce):void 0,to:e.to?e.to:null,transactionIndex:e.transactionIndex?Number(e.transactionIndex):null,type:e.type?zp[e.type]:void 0,typeHex:e.type?e.type:void 0,value:e.value?BigInt(e.value):void 0,v:e.v?BigInt(e.v):void 0};return e.authorizationList&&(r.authorizationList=L2(e.authorizationList)),r.yParity=(()=>{if(e.yParity)return Number(e.yParity);if(typeof r.v=="bigint"){if(r.v===0n||r.v===27n)return 0;if(r.v===1n||r.v===28n)return 1;if(r.v>=35n)return r.v%2n===0n?1:0}})(),r.type==="legacy"&&(delete r.accessList,delete r.maxFeePerBlobGas,delete r.maxFeePerGas,delete r.maxPriorityFeePerGas,delete r.yParity),r.type==="eip2930"&&(delete r.maxFeePerBlobGas,delete r.maxFeePerGas,delete r.maxPriorityFeePerGas),r.type==="eip1559"&&delete r.maxFeePerBlobGas,r}var $h=Xs("transaction",Jr);function L2(e){return e.map(t=>({address:t.address,chainId:Number(t.chainId),nonce:Number(t.nonce),r:t.r,s:t.s,yParity:Number(t.yParity)}))}function Bi(e,t){let r=(e.transactions??[]).map(n=>typeof n=="string"?n:Jr(n));return{...e,baseFeePerGas:e.baseFeePerGas?BigInt(e.baseFeePerGas):null,blobGasUsed:e.blobGasUsed?BigInt(e.blobGasUsed):void 0,difficulty:e.difficulty?BigInt(e.difficulty):void 0,excessBlobGas:e.excessBlobGas?BigInt(e.excessBlobGas):void 0,gasLimit:e.gasLimit?BigInt(e.gasLimit):void 0,gasUsed:e.gasUsed?BigInt(e.gasUsed):void 0,hash:e.hash?e.hash:null,logsBloom:e.logsBloom?e.logsBloom:null,nonce:e.nonce?e.nonce:null,number:e.number?BigInt(e.number):null,size:e.size?BigInt(e.size):void 0,timestamp:e.timestamp?BigInt(e.timestamp):void 0,transactions:r,totalDifficulty:e.totalDifficulty?BigInt(e.totalDifficulty):null}}var Hh=Xs("block",Bi);async function De(e,{blockHash:t,blockNumber:r,blockTag:n=e.experimental_blockTag??"latest",includeTransactions:o}={}){let s=o??!1,a=r!==void 0?I(r):void 0,i=null;if(t?i=await e.request({method:"eth_getBlockByHash",params:[t,s]},{dedupe:!0}):i=await e.request({method:"eth_getBlockByNumber",params:[a||n,s]},{dedupe:!!a}),!i)throw new zn({blockHash:t,blockNumber:r});return(e.chain?.formatters?.block?.format||Bi)(i,"getBlock")}async function ta(e){let t=await e.request({method:"eth_gasPrice"});return BigInt(t)}async function Dh(e,t){return Fp(e,t)}async function Fp(e,t){let{block:r,chain:n=e.chain,request:o}=t||{};try{let s=n?.fees?.maxPriorityFeePerGas??n?.fees?.defaultPriorityFee;if(typeof s=="function"){let i=r||await B(e,De,"getBlock")({}),c=await s({block:i,client:e,request:o});if(c===null)throw new Error;return c}if(typeof s<"u")return s;let a=await e.request({method:"eth_maxPriorityFeePerGas"});return pe(a)}catch{let[s,a]=await Promise.all([r?Promise.resolve(r):B(e,De,"getBlock")({}),B(e,ta,"getGasPrice")({})]);if(typeof s.baseFeePerGas!="bigint")throw new Mn;let i=a-s.baseFeePerGas;return i<0n?0n:i}}async function Uh(e,t){return Fu(e,t)}async function Fu(e,t){let{block:r,chain:n=e.chain,request:o,type:s="eip1559"}=t||{},a=await(async()=>typeof n?.fees?.baseFeeMultiplier=="function"?n.fees.baseFeeMultiplier({block:r,client:e,request:o}):n?.fees?.baseFeeMultiplier??1.2)();if(a<1)throw new ea;let c=10**(a.toString().split(".")[1]?.length??0),u=p=>p*BigInt(Math.ceil(a*c))/BigInt(c),f=r||await B(e,De,"getBlock")({});if(typeof n?.fees?.estimateFeesPerGas=="function"){let p=await n.fees.estimateFeesPerGas({block:r,client:e,multiply:u,request:o,type:s});if(p!==null)return p}if(s==="eip1559"){if(typeof f.baseFeePerGas!="bigint")throw new Mn;let p=typeof o?.maxPriorityFeePerGas=="bigint"?o.maxPriorityFeePerGas:await Fp(e,{block:f,chain:n,request:o}),m=u(f.baseFeePerGas);return{maxFeePerGas:o?.maxFeePerGas??m+p,maxPriorityFeePerGas:p}}return{gasPrice:o?.gasPrice??u(await B(e,ta,"getGasPrice")({}))}}go();ze();async function ra(e,{address:t,blockHash:r,blockNumber:n,blockTag:o="latest",requireCanonical:s}){let a=Bt({blockHash:r,blockNumber:n,blockTag:o,requireCanonical:s}),i=await e.request({method:"eth_getTransactionCount",params:[t,a]},{dedupe:typeof n=="bigint"||r!==void 0});return ye(i)}Fe();Z();function na(e){let{kzg:t}=e,r=e.to??(typeof e.blobs[0]=="string"?"hex":"bytes"),n=typeof e.blobs[0]=="string"?e.blobs.map(s=>ke(s)):e.blobs,o=[];for(let s of n)o.push(Uint8Array.from(t.blobToKzgCommitment(s)));return r==="bytes"?o:o.map(s=>ae(s))}Fe();Z();function oa(e){let{kzg:t}=e,r=e.to??(typeof e.blobs[0]=="string"?"hex":"bytes"),n=typeof e.blobs[0]=="string"?e.blobs.map(a=>ke(a)):e.blobs,o=typeof e.commitments[0]=="string"?e.commitments.map(a=>ke(a)):e.commitments,s=[];for(let a=0;aae(a))}Z();ip();var Lh=lo;Rt();Fe();Z();function jh(e,t){let r=t||"hex",n=Lh(we(e,{strict:!1})?it(e):e);return r==="bytes"?n:ce(n)}function Vh(e){let{commitment:t,version:r=1}=e,n=e.to??(typeof t=="string"?"hex":"bytes"),o=jh(t,"bytes");return o.set([r],0),n==="bytes"?o:ae(o)}function Ou(e){let{commitments:t,version:r}=e,n=e.to??(typeof t[0]=="string"?"hex":"bytes"),o=[];for(let s of t)o.push(Vh({commitment:s,to:n,version:r}));return o}J();var $u=class extends g{constructor({maxSize:t,size:r}){super("Blob size is too large.",{metaMessages:[`Max: ${t} bytes`,`Given: ${r} bytes`],name:"BlobSizeTooLargeError"})}},sa=class extends g{constructor(){super("Blob data must not be empty.",{name:"EmptyBlobError"})}},Hu=class extends g{constructor({hash:t,size:r}){super(`Versioned hash "${t}" size is invalid.`,{metaMessages:["Expected: 32",`Received: ${r}`],name:"InvalidVersionedHashSizeError"})}},Du=class extends g{constructor({hash:t,version:r}){super(`Versioned hash "${t}" version is invalid.`,{metaMessages:[`Expected: ${1}`,`Received: ${r}`],name:"InvalidVersionedHashVersionError"})}};iu();pt();Fe();Z();function qh(e){let t=e.to??(typeof e.data=="string"?"hex":"bytes"),r=typeof e.data=="string"?ke(e.data):e.data,n=ee(r);if(!n)throw new sa;if(n>761855)throw new $u({maxSize:761855,size:n});let o=[],s=!0,a=0;for(;s;){let i=fs(new Uint8Array(131072)),c=0;for(;c<4096;){let u=r.slice(a,a+31);if(i.pushByte(0),i.pushBytes(u),u.length<31){i.pushByte(128),s=!1;break}c++,a+=31}o.push(i)}return t==="bytes"?o.map(i=>i.bytes):o.map(i=>ae(i.bytes))}function Uu(e){let{data:t,kzg:r,to:n}=e,o=e.blobs??qh({data:t,to:n}),s=e.commitments??na({blobs:o,kzg:r,to:n}),a=e.proofs??oa({blobs:o,commitments:s,kzg:r,to:n}),i=[];for(let c=0;c{let o=Nn(e,r);return o instanceof Ht?e:o})();return new hu(n,{docsPath:t,...r})}bo();Yr();vr();ze();async function Ue(e){let t=await e.request({method:"eth_chainId"},{dedupe:!0});return ye(t)}async function aa(e,t){let{account:r=e.account,accessList:n,authorizationList:o,chain:s=e.chain,blobVersionedHashes:a,blobs:i,data:c,gas:u,gasPrice:f,maxFeePerBlobGas:d,maxFeePerGas:p,maxPriorityFeePerGas:m,nonce:l,nonceManager:h,to:x,type:b,value:A,...v}=t,y=await(async()=>{if(!r||!h||typeof l<"u")return l;let S=K(r),R=s?s.id:await B(e,Ue,"getChainId")({});return await h.consume({address:S.address,chainId:R,client:e})})();He(t);let w=s?.formatters?.transactionRequest?.format,T=(w||nt)({...Dt(v,{format:w}),account:r?K(r):void 0,accessList:n,authorizationList:o,blobs:i,blobVersionedHashes:a,data:c,gas:u,gasPrice:f,maxFeePerBlobGas:d,maxFeePerGas:p,maxPriorityFeePerGas:m,nonce:y,to:x,type:b,value:A},"fillTransaction");try{let S=await e.request({method:"eth_fillTransaction",params:[T]}),P=(s?.formatters?.transaction?.format||Jr)(S.tx);delete P.blockHash,delete P.blockNumber,delete P.r,delete P.s,delete P.transactionIndex,delete P.v,delete P.yParity,P.data=P.input,P.gas&&(P.gas=t.gas??P.gas),P.gasPrice&&(P.gasPrice=t.gasPrice??P.gasPrice),P.maxFeePerBlobGas&&(P.maxFeePerBlobGas=t.maxFeePerBlobGas??P.maxFeePerBlobGas),P.maxFeePerGas&&(P.maxFeePerGas=t.maxFeePerGas??P.maxFeePerGas),P.maxPriorityFeePerGas&&(P.maxPriorityFeePerGas=t.maxPriorityFeePerGas??P.maxPriorityFeePerGas),typeof P.nonce<"u"&&(P.nonce=t.nonce??P.nonce);let z=await(async()=>{if(typeof s?.fees?.baseFeeMultiplier=="function"){let k=await B(e,De,"getBlock")({});return s.fees.baseFeeMultiplier({block:k,client:e,request:t})}return s?.fees?.baseFeeMultiplier??1.2})();if(z<1)throw new ea;let O=10**(z.toString().split(".")[1]?.length??0),q=k=>k*BigInt(Math.ceil(z*O))/BigInt(O);return P.feePayerSignature||(P.maxFeePerGas&&!t.maxFeePerGas&&(P.maxFeePerGas=q(P.maxFeePerGas)),P.gasPrice&&!t.gasPrice&&(P.gasPrice=q(P.gasPrice))),{raw:S.raw,transaction:{from:T.from,...P},...S.capabilities?{capabilities:S.capabilities}:{}}}catch(S){throw Fn(S,{...t,chain:e.chain})}}var ki=["blobVersionedHashes","chainId","fees","gas","nonce","type"],Gh=new Map,Op=new lt(128);async function wr(e,t){let r=t;r.account??=e.account,r.parameters??=ki;let{account:n,chain:o=e.chain,nonceManager:s,parameters:a}=r,i=(()=>{if(typeof o?.prepareTransactionRequest=="function")return{fn:o.prepareTransactionRequest,runAt:["beforeFillTransaction"]};if(Array.isArray(o?.prepareTransactionRequest))return{fn:o.prepareTransactionRequest[0],runAt:o.prepareTransactionRequest[1].runAt}})(),c;async function u(){return c||(typeof r.chainId<"u"?r.chainId:o?o.id:(c=await B(e,Ue,"getChainId")({}),c))}let f=n&&K(n),d=r.nonce;if(a.includes("nonce")&&typeof d>"u"&&f&&s){let y=await u();d=await s.consume({address:f.address,chainId:y,client:e})}i?.fn&&i.runAt?.includes("beforeFillTransaction")&&(r=await i.fn({...r,chain:o},{client:e,phase:"beforeFillTransaction"}),d??=r.nonce);let m=((a.includes("blobVersionedHashes")||a.includes("sidecars"))&&r.kzg&&r.blobs||Op.get(e.uid)===!1||!["fees","gas"].some(w=>a.includes(w))?!1:!!(a.includes("chainId")&&typeof r.chainId!="number"||a.includes("nonce")&&typeof d!="number"||a.includes("fees")&&typeof r.gasPrice!="bigint"&&(typeof r.maxFeePerGas!="bigint"||typeof r.maxPriorityFeePerGas!="bigint")||a.includes("gas")&&typeof r.gas!="bigint"))?await B(e,aa,"fillTransaction")({...r,nonce:d}).then(y=>{let{chainId:w,from:E,gas:T,gasPrice:S,nonce:R,maxFeePerBlobGas:P,maxFeePerGas:z,maxPriorityFeePerGas:H,type:O,...q}=y.transaction,k="feeToken"in q?q.feeToken:void 0,C="feePayerSignature"in q&&q.feePayerSignature!==null&&typeof q.feePayerSignature<"u",D=typeof k<"u"&&k!==null&&(!("feeToken"in r)||C);return Op.set(e.uid,!0),{...r,...E?{from:E}:{},...O&&!r.type?{type:O}:{},...typeof w<"u"?{chainId:w}:{},...typeof T<"u"?{gas:T}:{},...typeof S<"u"?{gasPrice:S}:{},...typeof R<"u"?{nonce:R}:{},...typeof P<"u"&&r.type!=="legacy"&&r.type!=="eip2930"?{maxFeePerBlobGas:P}:{},...typeof z<"u"&&r.type!=="legacy"&&r.type!=="eip2930"?{maxFeePerGas:z}:{},...typeof H<"u"&&r.type!=="legacy"&&r.type!=="eip2930"?{maxPriorityFeePerGas:H}:{},..."nonceKey"in q&&typeof q.nonceKey<"u"?{nonceKey:q.nonceKey}:{},..."keyAuthorization"in q&&typeof q.keyAuthorization<"u"&&q.keyAuthorization!==null&&!("keyAuthorization"in r)?{keyAuthorization:q.keyAuthorization}:{},..."feePayerSignature"in q&&typeof q.feePayerSignature<"u"&&q.feePayerSignature!==null?{feePayerSignature:q.feePayerSignature}:{},...D?{feeToken:k}:{},...y.capabilities?{_capabilities:y.capabilities}:{}}}).catch(y=>{let w=y;if(w.name!=="TransactionExecutionError")return r;if(w.walk?.(S=>S.name==="ExecutionRevertedError"))throw y;return w.walk?.(S=>{let R=S;return R.name==="MethodNotFoundRpcError"||R.name==="MethodNotSupportedRpcError"||R.message?.includes("eth_fillTransaction is not available")})&&Op.set(e.uid,!1),r}):r;d??=m.nonce,r={...m,...f?{from:f?.address}:{},...typeof d<"u"?{nonce:d}:{}};let{blobs:l,gas:h,kzg:x,type:b}=r;i?.fn&&i.runAt?.includes("beforeFillParameters")&&(r=await i.fn({...r,chain:o},{client:e,phase:"beforeFillParameters"}));let A;async function v(){return A||(A=await B(e,De,"getBlock")({blockTag:"latest"}),A)}if(a.includes("nonce")&&typeof d>"u"&&f&&!s&&(r.nonce=await B(e,ra,"getTransactionCount")({address:f.address,blockTag:"pending"})),(a.includes("blobVersionedHashes")||a.includes("sidecars"))&&l&&x){let y=na({blobs:l,kzg:x});if(a.includes("blobVersionedHashes")){let w=Ou({commitments:y,to:"hex"});r.blobVersionedHashes=w}if(a.includes("sidecars")){let w=oa({blobs:l,commitments:y,kzg:x}),E=Uu({blobs:l,commitments:y,proofs:w,to:"hex"});r.sidecars=E}}if(a.includes("chainId")&&(r.chainId=await u()),(a.includes("fees")||a.includes("type"))&&typeof b>"u")try{r.type=Lu(r)}catch{let y=Gh.get(e.uid);typeof y>"u"&&(y=typeof(await v())?.baseFeePerGas=="bigint",Gh.set(e.uid,y)),r.type=y?"eip1559":"legacy"}if(a.includes("fees"))if(r.type!=="legacy"&&r.type!=="eip2930"){if(typeof r.maxFeePerGas>"u"||typeof r.maxPriorityFeePerGas>"u"){let y=await v(),{maxFeePerGas:w,maxPriorityFeePerGas:E}=await Fu(e,{block:y,chain:o,request:r});if(typeof r.maxPriorityFeePerGas>"u"&&r.maxFeePerGas&&r.maxFeePerGas"u"){let y=await v(),{gasPrice:w}=await Fu(e,{block:y,chain:o,request:r,type:"legacy"});r.gasPrice=w}}return a.includes("gas")&&typeof h>"u"&&(r.gas=await B(e,ia,"estimateGas")({...r,account:f,prepare:f?.type==="local"?[]:["blobVersionedHashes"]})),i?.fn&&i.runAt?.includes("afterFillParameters")&&(r=await i.fn({...r,chain:o},{client:e,phase:"afterFillParameters"})),He(r),delete r.parameters,r}async function ia(e,t){let{account:r=e.account,prepare:n=!0}=t,o=r?K(r):void 0,s=(()=>{if(Array.isArray(n))return n;if(o?.type!=="local")return["blobVersionedHashes"]})();try{let a=await(async()=>{if(t.to)return t.to;if(t.authorizationList&&t.authorizationList.length>0)return await Cn({authorization:t.authorizationList[0]}).catch(()=>{throw new g("`to` is required. Could not infer from `authorizationList`")})})(),{accessList:i,authorizationList:c,blobs:u,blobVersionedHashes:f,blockNumber:d,blockTag:p,data:m,gas:l,gasPrice:h,maxFeePerBlobGas:x,maxFeePerGas:b,maxPriorityFeePerGas:A,nonce:v,value:y,stateOverride:w,...E}=n?await wr(e,{...t,parameters:s,to:a}):t;if(l&&t.gas!==l)return l;let S=(typeof d=="bigint"?I(d):void 0)||p,R=Qs(w);He(t);let P=e.chain?.formatters?.transactionRequest?.format,H=(P||nt)({...Dt(E,{format:P}),account:o,accessList:i,authorizationList:c,blobs:u,blobVersionedHashes:f,data:m,gasPrice:h,maxFeePerBlobGas:x,maxFeePerGas:b,maxPriorityFeePerGas:A,nonce:v,to:a,value:y},"estimateGas");return BigInt(await e.request({method:"eth_estimateGas",params:R?[H,S??e.experimental_blockTag??"latest",R]:S?[H,S]:[H]}))}catch(a){throw Fh(a,{...t,account:o,chain:e.chain})}}async function ca(e,t){let{abi:r,address:n,args:o,functionName:s,dataSuffix:a=typeof e.dataSuffix=="string"?e.dataSuffix:e.dataSuffix?.value,...i}=t,c=ie({abi:r,args:o,functionName:s});try{return await B(e,ia,"estimateGas")({data:`${c}${a?a.replace("0x",""):""}`,to:n,...i})}catch(u){let f=i.account?K(i.account):void 0;throw St(u,{abi:r,address:n,args:o,docsPath:"/docs/contract/estimateContractGas",functionName:s,sender:f?.address})}}gn();Xr();Fe();function je(e,{args:t,eventName:r}={}){return{...e,blockHash:e.blockHash?e.blockHash:null,blockNumber:e.blockNumber?BigInt(e.blockNumber):null,blockTimestamp:e.blockTimestamp?BigInt(e.blockTimestamp):e.blockTimestamp===null?null:void 0,logIndex:e.logIndex?Number(e.logIndex):null,transactionHash:e.transactionHash?e.transactionHash:null,transactionIndex:e.transactionIndex?Number(e.transactionIndex):null,...r?{args:t,eventName:r}:{}}}At();pi();Ae();tp();pt();pi();yi();Nr();var Wh="/docs/contract/decodeEventLog";function vo(e){let{abi:t,data:r,strict:n,topics:o}=e,s=n??!0,[a,...i]=o;if(!a)throw new Mc({docsPath:Wh});let c=t.find(b=>b.type==="event"&&a===bn(Ne(b)));if(!(c&&"name"in c)||c.type!=="event")throw new zc(a,{docsPath:Wh});let{name:u,inputs:f}=c,d=f?.some(b=>!("name"in b&&b.name)),p=d?[]:{},m=f.map((b,A)=>[b,A]).filter(([b])=>"indexed"in b&&b.indexed),l=[];for(let b=0;b!("indexed"in b&&b.indexed)),x=s?h:[...l.map(([b])=>b),...h];if(x.length>0){if(r&&r!=="0x")try{let b=Ur(x,r);if(b){let A=0;if(!s)for(let[v,y]of l)p[d?y:v.name||y]=b[A++];if(d)for(let v=0;v0?p:void 0}}function q2({param:e,value:t}){return e.type==="string"||e.type==="bytes"||e.type==="tuple"||e.type.match(/^(.*)\[(\d+)?\]$/)?t:(Ur([e],t)||[])[0]}function Er(e){let{abi:t,args:r,logs:n,strict:o=!0}=e,s=(()=>{if(e.eventName)return Array.isArray(e.eventName)?e.eventName:[e.eventName]})(),a=t.filter(i=>i.type==="event").map(i=>({abi:i,selector:bn(i)}));return n.map(i=>{let c=typeof i.blockNumber=="string"?je(i):i,u=a.filter(p=>c.topics[0]===p.selector);if(u.length===0)return null;let f,d;for(let p of u)try{f=vo({...c,abi:[p.abi],strict:!0}),d=p;break}catch{}if(!f&&!o){d=u[0];try{f=vo({data:c.data,topics:c.topics,abi:[d.abi],strict:!1})}catch{let p=d.abi.inputs?.some(m=>!("name"in m&&m.name));return{...c,args:p?[]:{},eventName:d.abi.name}}}return!f||!d||s&&!s.includes(f.eventName)||!G2({args:f.args,inputs:d.abi.inputs,matchArgs:r})?null:{...f,...c}}).filter(Boolean)}function G2(e){let{args:t,inputs:r,matchArgs:n}=e;if(!n)return!0;if(!t)return!1;function o(s,a,i){try{return s.type==="address"?Le(a,i):s.type==="string"||s.type==="bytes"?oe(it(a))===i:a===i}catch{return!1}}return Array.isArray(t)&&Array.isArray(n)?n.every((s,a)=>{if(s==null)return!0;let i=r[a];return i?(Array.isArray(s)?s:[s]).some(u=>o(i,u,t[a])):!1}):typeof t=="object"&&!Array.isArray(t)&&typeof n=="object"&&!Array.isArray(n)?Object.entries(n).every(([s,a])=>{if(a==null)return!0;let i=r.find(u=>u.name===s);return i?(Array.isArray(a)?a:[a]).some(u=>o(i,u,t[s])):!1}):!1}Z();async function ua(e,{address:t,blockHash:r,fromBlock:n,toBlock:o,event:s,events:a,args:i,strict:c}={}){let u=c??!1,f=a??(s?[s]:void 0),d=[];f&&(d=[f.flatMap(h=>lr({abi:[h],eventName:h.name,args:a?void 0:i}))],s&&(d=d[0]));let p;r?p=await e.request({method:"eth_getLogs",params:[{address:t,topics:d,blockHash:r}]}):p=await e.request({method:"eth_getLogs",params:[{address:t,topics:d,fromBlock:typeof n=="bigint"?I(n):n,toBlock:typeof o=="bigint"?I(o):o}]});let m=p.map(l=>je(l));return f?Er({abi:f,args:i,logs:m,strict:u}):m}async function ju(e,t){let{abi:r,address:n,args:o,blockHash:s,eventName:a,fromBlock:i,toBlock:c,strict:u}=t,f=a?xt({abi:r,name:a}):void 0,d=f?void 0:r.filter(p=>p.type==="event");return B(e,ua,"getLogs")({address:n,args:o,blockHash:s,event:f,events:d,fromBlock:i,toBlock:c,strict:u})}Qr();Xe();So();async function fe(e,t){let{abi:r,address:n,args:o,functionName:s,...a}=t,i=ie({abi:r,args:o,functionName:s});try{let{data:c}=await B(e,jt,"call")({...a,data:i,to:n});return ot({abi:r,args:o,functionName:s,data:c||"0x"})}catch(c){throw St(c,{abi:r,address:n,args:o,docsPath:"/docs/contract/readContract",functionName:s})}}be();Qr();Xe();So();async function ba(e,t){let{abi:r,address:n,args:o,functionName:s,dataSuffix:a=typeof e.dataSuffix=="string"?e.dataSuffix:e.dataSuffix?.value,...i}=t,c=i.account?K(i.account):e.account,u=ie({abi:r,args:o,functionName:s});try{let{data:f}=await B(e,jt,"call")({batch:!1,data:`${u}${a?a.replace("0x",""):""}`,to:n,...i,account:c}),d=ot({abi:r,args:o,functionName:s,data:f||"0x"}),p=r.filter(m=>"name"in m&&m.name===t.functionName);return{result:d,request:{abi:p,address:n,args:o,dataSuffix:a,functionName:s,...i,account:c}}}catch(f){throw St(f,{abi:r,address:n,args:o,docsPath:"/docs/contract/simulateContract",functionName:s,sender:c?.address})}}Ae();Fs();var mf=new Map,sm=new Map,h5=0;function st(e,t,r){let n=++h5,o=()=>mf.get(e)||[],s=()=>{let d=o().filter(p=>p.id!==n);if(d.length===0){mf.delete(e),sm.delete(e);return}mf.set(e,d)},a=()=>{let f=o();if(!f.some(p=>p.id===n))return;let d=sm.get(e);if(f.length===1&&d){let p=d();p instanceof Promise&&p.catch(()=>{})}s()},i=o();if(mf.set(e,[...i,{id:n,fns:t}]),i&&i.length>0)return a;let c={};for(let f in t)c[f]=((...d)=>{let p=o();if(p.length!==0)for(let m of p)m.fns[f]?.(...d)});let u=r(c);return typeof u=="function"&&sm.set(e,u),a}rr();async function Ui(e,{signal:t}={}){return new Promise((r,n)=>{if(t?.aborted){n(Ge(t));return}let o=()=>t?.removeEventListener("abort",a),s=setTimeout(()=>{o(),r()},e),a=()=>{clearTimeout(s),o(),n(Ge(t))};t?.addEventListener("abort",a,{once:!0})})}function Vt(e,{emitOnBegin:t,initialWaitTime:r,interval:n}){let o=!0,s=()=>o=!1;return(async()=>{let i;t&&(i=await e({unpoll:s}));let c=await r?.(i)??n;await Ui(c);let u=async()=>{o&&(await e({unpoll:s}),await Ui(n),u())};u()})(),s}Qe();var y5=new Map,x5=new Map;function Fy(e){let t=(o,s)=>({clear:()=>s.delete(o),get:()=>s.get(o),set:a=>s.set(o,a)}),r=t(e,y5),n=t(e,x5);return{clear:()=>{r.clear(),n.clear()},promise:r,response:n}}async function Oy(e,{cacheKey:t,cacheTime:r=Number.POSITIVE_INFINITY}){let n=Fy(t),o=n.response.get();if(o&&r>0&&Date.now()-o.created.getTime()`blockNumber.${e}`;async function Pr(e,{cacheTime:t=e.cacheTime}={}){let r=await Oy(()=>e.request({method:"eth_blockNumber"}),{cacheKey:b5(e.uid),cacheTime:t});return BigInt(r)}async function Hn(e,{filter:t}){let r="strict"in t&&t.strict,n=await t.request({method:"eth_getFilterChanges",params:[t.id]});if(typeof n[0]=="string")return n;let o=n.map(s=>je(s));return!("abi"in t)||!t.abi?o:Er({abi:t.abi,logs:o,strict:r})}async function Dn(e,{filter:t}){return t.request({method:"eth_uninstallFilter",params:[t.id]})}function $y(e,t){let{abi:r,address:n,args:o,batch:s=!0,eventName:a,fromBlock:i,onError:c,onLogs:u,poll:f,pollingInterval:d=e.pollingInterval,strict:p}=t;return(typeof f<"u"?f:typeof i=="bigint"?!0:!(e.transport.type==="webSocket"||e.transport.type==="ipc"||e.transport.type==="fallback"&&(e.transport.transports[0].config.type==="webSocket"||e.transport.transports[0].config.type==="ipc")))?(()=>{let x=p??!1,b=ne(["watchContractEvent",n,o,s,e.uid,a,d,x,i]);return st(b,{onLogs:u,onError:c},A=>{let v;i!==void 0&&(v=i-1n);let y,w=!1,E=Vt(async()=>{if(!w){try{y=await B(e,nu,"createContractEventFilter")({abi:r,address:n,args:o,eventName:a,strict:x,fromBlock:i})}catch{}w=!0;return}try{let T;if(y)T=await B(e,Hn,"getFilterChanges")({filter:y});else{let S=await B(e,Pr,"getBlockNumber")({});v&&v{y&&await B(e,Dn,"uninstallFilter")({filter:y}),E()}})})():(()=>{let x=p??!1,b=ne(["watchContractEvent",n,o,s,e.uid,a,d,x]),A=!0,v=()=>A=!1;return st(b,{onLogs:u,onError:c},y=>((async()=>{try{let w=(()=>{if(e.transport.type==="fallback"){let S=e.transport.transports.find(R=>R.config.type==="webSocket"||R.config.type==="ipc");return S?S.value:e.transport}return e.transport})(),E=a?lr({abi:r,eventName:a,args:o}):[],{unsubscribe:T}=await w.subscribe({params:["logs",{address:n,topics:E}],onData(S){if(!A)return;let R=S.result;try{let{eventName:P,args:z}=vo({abi:r,data:R.data,topics:R.topics,strict:p}),H=je(R,{args:z,eventName:P});y.onLogs([H])}catch(P){let z,H;if(P instanceof Mr||P instanceof mn){if(p)return;z=P.abiItem.name,H=P.abiItem.inputs?.some(q=>!("name"in q&&q.name))}let O=je(R,{args:H?[]:{},eventName:z});y.onLogs([O])}},onError(S){y.onError?.(S)}});v=T,A||v()}catch(w){c?.(w)}})(),()=>v()))})()}be();J();var Ie=class extends g{constructor({docsPath:t}={}){super(["Could not find an Account to execute with this Action.","Please provide an Account with the `account` argument on the Action, or by supplying an `account` to the Client."].join(`
+`),{docsPath:t,docsSlug:"account",name:"AccountNotFoundError"})}},qt=class extends g{constructor({docsPath:t,metaMessages:r,type:n}){super(`Account type "${n}" is not supported.`,{docsPath:t,metaMessages:r,name:"AccountTypeNotSupportedError"})}};Xe();be();J();Hi();function ga({chain:e,currentChainId:t}){if(!e)throw new of;if(t!==e.id)throw new nf({chain:e,currentChainId:t})}qe();bo();Yr();no();vr();async function va(e,{serializedTransaction:t}){return e.request({method:"eth_sendRawTransaction",params:[t]},{retryCount:0})}var am=new lt(128);async function Un(e,t){let{account:r=e.account,assertChainId:n=!0,chain:o=e.chain,accessList:s,authorizationList:a,blobs:i,data:c,dataSuffix:u=typeof e.dataSuffix=="string"?e.dataSuffix:e.dataSuffix?.value,gas:f,gasPrice:d,maxFeePerBlobGas:p,maxFeePerGas:m,maxPriorityFeePerGas:l,nonce:h,type:x,value:b,...A}=t;if(typeof r>"u")throw new Ie({docsPath:"/docs/actions/wallet/sendTransaction"});let v=r?K(r):null,y;try{He(t);let w=await(async()=>{if(t.to)return t.to;if(t.to!==null&&a&&a.length>0)return await Cn({authorization:a[0]}).catch(()=>{throw new g("`to` is required. Could not infer from `authorizationList`.")})})();if(v?.type==="json-rpc"||v===null){let E;o!==null&&(E=await B(e,Ue,"getChainId")({}),n&&ga({currentChainId:E,chain:o}));let T=e.chain?.formatters?.transactionRequest?.format,R=(T||nt)({...Dt(A,{format:T}),accessList:s,account:v,authorizationList:a,blobs:i,chainId:E,data:u?Oe([c??"0x",u]):c,gas:f,gasPrice:d,maxFeePerBlobGas:p,maxFeePerGas:m,maxPriorityFeePerGas:l,nonce:h,to:w,type:x,value:b},"sendTransaction"),P=am.get(e.uid),z=P?"wallet_sendTransaction":"eth_sendTransaction";try{return await e.request({method:z,params:[R]},{retryCount:0})}catch(H){if(P===!1)throw H;let O=H;if(O.name==="InvalidInputRpcError"||O.name==="InvalidParamsRpcError"||O.name==="MethodNotFoundRpcError"||O.name==="MethodNotSupportedRpcError")return await e.request({method:"wallet_sendTransaction",params:[R]},{retryCount:0}).then(q=>(am.set(e.uid,!0),q)).catch(q=>{let k=q;throw k.name==="MethodNotFoundRpcError"||k.name==="MethodNotSupportedRpcError"?(am.set(e.uid,!1),O):k});throw O}}if(v?.type==="local"){if(v.nonceManager&&typeof h>"u"){let R=A.chainId,P=await(async()=>typeof R=="number"?R:o?o.id:B(e,Ue,"getChainId")({}))();y={address:v.address,chainId:P}}let E=await B(e,wr,"prepareTransactionRequest")({account:v,accessList:s,authorizationList:a,blobs:i,chain:o,data:u?Oe([c??"0x",u]):c,gas:f,gasPrice:d,maxFeePerBlobGas:p,maxFeePerGas:m,maxPriorityFeePerGas:l,nonce:h,nonceManager:v.nonceManager,parameters:[...ki,"sidecars"],type:x,value:b,...A,to:w}),T=o?.serializers?.transaction,S=await v.signTransaction(E,{serializer:T});return await B(e,va,"sendRawTransaction")({serializedTransaction:S})}throw v?.type==="smart"?new qt({metaMessages:["Consider using the `sendUserOperation` Action instead."],docsPath:"/docs/actions/bundler/sendUserOperation",type:"smart"}):new qt({docsPath:"/docs/actions/wallet/sendTransaction",type:v?.type})}catch(w){throw w instanceof qt?w:(y&&v?.nonceManager?.reset(y),Fn(w,{...t,account:v,chain:t.chain||void 0}))}}async function ar(e,t){return ar.internal(e,Un,"sendTransaction",t)}(function(e){async function t(r,n,o,s){let{abi:a,account:i=r.account,address:c,args:u,functionName:f,...d}=s;if(typeof i>"u")throw new Ie({docsPath:"/docs/contract/writeContract"});let p=i?K(i):null,m=ie({abi:a,args:u,functionName:f});try{return await B(r,n,o)({data:m,to:c,account:p,...d})}catch(l){throw St(l,{abi:a,address:c,args:u,docsPath:"/docs/contract/writeContract",functionName:f,sender:p?.address})}}e.internal=t})(ar||(ar={}));J();J();var lf=class extends g{constructor(t){super(`Call bundle failed with status: ${t.statusCode}`,{name:"BundleFailedError"}),Object.defineProperty(this,"result",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.result=t}};cf();rr();function _o(e,{delay:t=100,retryCount:r=2,shouldRetry:n=()=>!0,signal:o}={}){return new Promise((s,a)=>{let i=async({count:c=0}={})=>{if(o?.aborted){a(Ge(o));return}let u=async({error:f})=>{let d=typeof t=="function"?t({count:c,error:f}):t;if(d)try{await Ui(d,{signal:o})}catch(p){a(p);return}i({count:c+1})};try{let f=await e();s(f)}catch(f){if(o?.aborted){a(Ge(o));return}if(bt(f)){a(f);return}if(cje(n)):null,to:e.to?e.to:null,transactionIndex:e.transactionIndex?ye(e.transactionIndex):null,status:e.status?im[e.status]:null,type:e.type?zp[e.type]||e.type:null};return e.blobGasPrice&&(r.blobGasPrice=BigInt(e.blobGasPrice)),e.blobGasUsed&&(r.blobGasUsed=BigInt(e.blobGasUsed)),r}var Hy=Xs("transactionReceipt",Io);be();J();Fs();Xe();qe();ze();Z();var cm="0x5792579257925792579257925792579257925792579257925792579257925792",um=I(0,{size:32});async function hf(e,t){let{account:r=e.account,chain:n=e.chain,experimental_fallback:o,experimental_fallbackDelay:s=32,forceAtomic:a=!1,id:i,version:c="2.0.0"}=t,u=r?K(r):null,f=t.capabilities;e.dataSuffix&&!t.capabilities?.dataSuffix&&(typeof e.dataSuffix=="string"?f={...t.capabilities,dataSuffix:{value:e.dataSuffix,optional:!0}}:f={...t.capabilities,dataSuffix:{value:e.dataSuffix.value,...e.dataSuffix.required?{}:{optional:!0}}});let d=t.calls.map(p=>{let m=p,l=m.abi?ie({abi:m.abi,functionName:m.functionName,args:m.args}):m.data;return{data:m.dataSuffix&&l?Oe([l,m.dataSuffix]):l,to:m.to,value:m.value?I(m.value):void 0}});try{let p=await e.request({method:"wallet_sendCalls",params:[{atomicRequired:a,calls:d,capabilities:f,chainId:I(n.id),from:u?.address,id:i,version:c}]},{retryCount:0});return typeof p=="string"?{id:p}:p}catch(p){let m=p;if(o&&(m.name==="MethodNotFoundRpcError"||m.name==="MethodNotSupportedRpcError"||m.name==="UnknownRpcError"||m.details.toLowerCase().includes("does not exist / is not available")||m.details.toLowerCase().includes("missing or invalid. request()")||m.details.toLowerCase().includes("did not match any variant of untagged enum")||m.details.toLowerCase().includes("account upgraded to unsupported contract")||m.details.toLowerCase().includes("eip-7702 not supported")||m.details.toLowerCase().includes("unsupported wc_ method")||m.details.toLowerCase().includes("feature toggled misconfigured")||m.details.toLowerCase().includes("jsonrpcengine: response has no error or result for request"))){if(f&&Object.values(f).some(b=>!b.optional)){let b="non-optional `capabilities` are not supported on fallback to `eth_sendTransaction`.";throw new Pn(new g(b,{details:b}))}if(a&&d.length>1){let x="`forceAtomic` is not supported on fallback to `eth_sendTransaction`.";throw new Sn(new g(x,{details:x}))}let l=[];for(let x of d){try{let b=await Un(e,{account:u,chain:n,data:x.data,to:x.to,value:x.value?pe(x.value):void 0});l.push({status:"fulfilled",value:b})}catch(b){l.push({reason:b,status:"rejected"})}s>0&&await new Promise(b=>setTimeout(b,s))}if(l.every(x=>x.status==="rejected"))throw l[0].reason;let h=l.map(x=>x.status==="fulfilled"?x.value:um);return{id:Oe([...h,I(n.id,{size:32}),cm])}}throw Fn(p,{...t,account:u,chain:t.chain})}}async function yf(e,t){async function r(f){if(f.endsWith(cm.slice(2))){let p=Me(tu(f,-64,-32)),m=tu(f,0,-64).slice(2).match(/.{1,64}/g),l=await Promise.all(m.map(x=>um.slice(2)!==x?e.request({method:"eth_getTransactionReceipt",params:[`0x${x}`]},{dedupe:!0}):void 0)),h=l.some(x=>x===null)?100:l.every(x=>x?.status==="0x1")?200:l.every(x=>x?.status==="0x0")?500:600;return{atomic:!1,chainId:ye(p),receipts:l.filter(Boolean),status:h,version:"2.0.0"}}return e.request({method:"wallet_getCallsStatus",params:[f]})}let{atomic:n=!1,chainId:o,receipts:s,version:a="2.0.0",...i}=await r(t.id),[c,u]=(()=>{let f=i.status;return f>=100&&f<200?["pending",f]:f>=200&&f<300?["success",f]:f>=300&&f<700?["failure",f]:f==="CONFIRMED"?["success",200]:f==="PENDING"?["pending",100]:[void 0,f]})();return{...i,atomic:n,chainId:o?ye(o):void 0,receipts:s?.map(f=>({...f,blockNumber:pe(f.blockNumber),gasUsed:pe(f.gasUsed),status:im[f.status]}))??[],statusCode:u,status:c,version:a}}async function xf(e,t){let{id:r,pollingInterval:n=e.pollingInterval,status:o=({statusCode:h})=>h===200||h>=300,retryCount:s=4,retryDelay:a=({count:h})=>~~(1<{let x=Vt(async()=>{let b=A=>{clearTimeout(m),x(),A(),l()};try{let A=await _o(async()=>{let v=await B(e,yf,"getCallsStatus")({id:r});if(c&&v.status==="failure")throw new lf(v);return v},{retryCount:s,delay:a});if(!o(A))return;b(()=>h.resolve(A))}catch(A){b(()=>h.reject(A))}},{interval:n,emitOnBegin:!0});return x});return m=i?setTimeout(()=>{l(),clearTimeout(m),p(new fm({id:r}))},i):void 0,await f}var fm=class extends g{constructor({id:t}){super(`Timed out while waiting for call bundle with id "${t}" to be confirmed.`,{name:"WaitForCallsStatusTimeoutError"})}};be();var bf=256,gf;function vf(e=11){if(!gf||bf+e>256*2){gf="",bf=0;for(let t=0;t<256;t++)gf+=(256+Math.random()*256|0).toString(16).substring(1)}return gf.substring(bf,bf+++e)}function wf(e){let{batch:t,chain:r,ccipRead:n,dataSuffix:o,key:s="base",name:a="Base Client",tokens:i,type:c="base"}=e,u=e.experimental_blockTag??(typeof r?.experimental_preconfirmationTime=="number"?"pending":void 0),f=r?.blockTime??12e3,d=Math.min(Math.max(Math.floor(f/2),500),4e3),p=e.pollingInterval??d,m=e.cacheTime??p,l=e.account?K(e.account):void 0,{config:h,request:x,value:b}=e.transport({account:l,chain:r,pollingInterval:p}),A={...h,...b},v={account:l,batch:t,cacheTime:m,ccipRead:n,chain:r,dataSuffix:o,key:s,name:a,pollingInterval:p,request:x,tokens:i,transport:A,type:c,uid:vf(),...u?{experimental_blockTag:u}:{}};function y(w){return E=>{let T=E(w);for(let R in v)delete T[R];let S={...w,...T};for(let R in T){let P=w[R],z=T[R];Dy(P)&&Dy(z)&&(S[R]={...P,...z})}return Object.assign(S,{extend:y(S)})}}return Object.assign(v,{extend:y(v)})}function Dy(e){if(typeof e!="object"||e===null)return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function Sr(e,t){let r=(n={})=>t(e,n);for(let n of["call","calls","callWithPeriod","estimateGas","prepare","simulate"])if(Object.hasOwn(t,n)){let o=t[n];r[n]=(s={})=>o.length===1?o(s):o(e,s)}for(let n of["extractEvent","extractEvents"])Object.hasOwn(t,n)&&(r[n]=t[n]);return r}Ze();Qr();Xe();tr();Po();pt();Qn();Z();J();En();function wa(e){if(!(e instanceof g))return!1;let t=e.walk(r=>r instanceof co);return t instanceof co?t.data?.errorName==="HttpError"||t.data?.errorName==="ResolverError"||t.data?.errorName==="ResolverNotContract"||t.data?.errorName==="ResolverNotFound"||t.data?.errorName==="ReverseAddressMismatch"||t.data?.errorName==="UnsupportedResolverProfile":!1}Di();qe();Fe();Z();At();Rt();function Ef(e){if(e.length!==66||e.indexOf("[")!==0||e.indexOf("]")!==65)return null;let t=`0x${e.slice(1,65)}`;return we(t)?t:null}function Li(e){let t=new Uint8Array(32).fill(0);if(!e)return ae(t);let r=e.split(".");for(let n=r.length-1;n>=0;n-=1){let o=Ef(r[n]),s=o?it(o):oe(Mt(r[n]),"bytes");t=oe(Oe([t,s]),"bytes")}return ae(t)}Fe();function Uy(e){return`[${e.slice(2)}]`}Fe();Z();At();function Ly(e){let t=new Uint8Array(32).fill(0);return e?Ef(e)||oe(Mt(e)):ae(t)}function Ea(e){let t=e.replace(/^\.|\.$/gm,"");if(t.length===0)return new Uint8Array(1);let r=new Uint8Array(Mt(t).byteLength+2),n=0,o=t.split(".");for(let s=0;s255&&(a=Mt(Uy(Ly(o[s])))),r[n]=a.length,r.set(a,n+1),n+=a.length+1}return r.byteLength!==n+1?r.slice(0,n+1):r}async function jy(e,t){let{blockNumber:r,blockTag:n,coinType:o,name:s,gatewayUrls:a,strict:i}=t,{chain:c}=e,u=(()=>{if(t.universalResolverAddress)return t.universalResolverAddress;if(!c)throw new Error("client chain not configured. universalResolverAddress is required.");return Lt({blockNumber:r,chain:c,contract:"ensUniversalResolver"})})(),f=c?.ensTlds;if(f&&!f.some(p=>s.endsWith(p)))return null;let d=o!=null?[Li(s),BigInt(o)]:[Li(s)];try{let p=ie({abi:Kp,functionName:"addr",args:d}),m={address:u,abi:tf,functionName:"resolveWithGateways",args:[ce(Ea(s)),p,a??[rn]],blockNumber:r,blockTag:n},h=await B(e,fe,"readContract")(m);if(h[0]==="0x")return null;let x=g5({coinType:o,data:h[0],args:d});return x==="0x"||Me(x)==="0x00"?null:x}catch(p){if(i)throw p;if(wa(p))return null;throw p}}function g5({coinType:e,data:t,args:r}){try{return ot({abi:Kp,args:r,functionName:"addr",data:t})}catch(n){if(e==null)throw n;let o=Me(t);if(ee(o)===20)return de(o);throw n}}J();var Tf=class extends g{constructor({data:t}){super("Unable to extract image from metadata. The metadata may be malformed or invalid.",{metaMessages:["- Metadata must be a JSON object with at least an `image`, `image_url` or `image_data` property.","",`Provided data: ${JSON.stringify(t)}`],name:"EnsAvatarInvalidMetadataError"})}},Ln=class extends g{constructor({reason:t}){super(`ENS NFT avatar URI is invalid. ${t}`,{name:"EnsAvatarInvalidNftUriError"})}},Ta=class extends g{constructor({uri:t}){super(`Unable to resolve ENS avatar URI "${t}". The URI may be malformed, invalid, or does not respond with a valid image.`,{name:"EnsAvatarUriResolutionError"})}},Af=class extends g{constructor({namespace:t}){super(`ENS NFT avatar namespace "${t}" is not supported. Must be "erc721" or "erc1155".`,{name:"EnsAvatarUnsupportedNamespaceError"})}};var v5=/(?https?:\/\/[^/]*|ipfs:\/|ipns:\/|ar:\/)?(?\/)?(?ipfs\/|ipns\/)?(?[\w\-.]+)(?\/.*)?/,w5=/^(Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[A-Za-z2-7]{58,}|B[A-Z2-7]{58,}|z[1-9A-HJ-NP-Za-km-z]{48,}|F[0-9A-F]{50,})(\/(?[\w\-.]+))?(?\/.*)?$/,E5=/^data:([a-zA-Z\-/+]*);base64,([^"].*)/,T5=/^data:([a-zA-Z\-/+]*)?(;[a-zA-Z0-9].*?)?(,)/;async function A5(e){try{let t=await fetch(e,{method:"HEAD"});return t.status===200?t.headers.get("content-type")?.startsWith("image/"):!1}catch(t){return typeof t=="object"&&typeof t.response<"u"||!Object.hasOwn(globalThis,"Image")?!1:new Promise(r=>{let n=new Image;n.onload=()=>{r(!0)},n.onerror=()=>{r(!1)},n.src=e})}}function Vy(e,t){return e?e.endsWith("/")?e.slice(0,-1):e:t}function dm({uri:e,gatewayUrls:t}){let r=E5.test(e);if(r)return{uri:e,isOnChain:!0,isEncoded:r};let n=Vy(t?.ipfs,"https://ipfs.io"),o=Vy(t?.arweave,"https://arweave.net"),s=e.match(v5),{protocol:a,subpath:i,target:c,subtarget:u=""}=s?.groups||{},f=a==="ipns:/"||i==="ipns/",d=a==="ipfs:/"||i==="ipfs/"||w5.test(e);if(e.startsWith("http")&&!f&&!d){let m=e;return t?.arweave&&(m=e.replace(/https:\/\/arweave.net/g,t?.arweave)),{uri:m,isOnChain:!1,isEncoded:!1}}if((f||d)&&c)return{uri:`${n}/${f?"ipns":"ipfs"}/${c}${u}`,isOnChain:!1,isEncoded:!1};if(a==="ar:/"&&c)return{uri:`${o}/${c}${u||""}`,isOnChain:!1,isEncoded:!1};let p=e.replace(T5,"");if(p.startsWith("o.json());return await Pf({gatewayUrls:e,uri:pm(r)})}catch{throw new Ta({uri:t})}}async function Pf({gatewayUrls:e,uri:t}){let{uri:r,isOnChain:n}=dm({uri:t,gatewayUrls:e});if(n||await A5(r))return r;throw new Ta({uri:t})}function Gy(e){let t=e;t.startsWith("did:nft:")&&(t=t.replace("did:nft:","").replace(/_/g,"/"));let[r,n,o]=t.split("/"),[s,a]=r.split(":"),[i,c]=n.split(":");if(!s||s.toLowerCase()!=="eip155")throw new Ln({reason:"Only EIP-155 supported"});if(!a)throw new Ln({reason:"Chain ID not found"});if(!c)throw new Ln({reason:"Contract address not found"});if(!o)throw new Ln({reason:"Token ID not found"});if(!i)throw new Ln({reason:"ERC namespace not found"});return{chainID:Number.parseInt(a,10),namespace:i.toLowerCase(),contractAddress:c,tokenID:o}}async function Wy(e,{nft:t}){if(t.namespace==="erc721")return fe(e,{address:t.contractAddress,abi:[{name:"tokenURI",type:"function",stateMutability:"view",inputs:[{name:"tokenId",type:"uint256"}],outputs:[{name:"",type:"string"}]}],functionName:"tokenURI",args:[BigInt(t.tokenID)]});if(t.namespace==="erc1155")return fe(e,{address:t.contractAddress,abi:[{name:"uri",type:"function",stateMutability:"view",inputs:[{name:"_id",type:"uint256"}],outputs:[{name:"",type:"string"}]}],functionName:"uri",args:[BigInt(t.tokenID)]});throw new Af({namespace:t.namespace})}async function Zy(e,{gatewayUrls:t,record:r}){return/eip155:/i.test(r)?P5(e,{gatewayUrls:t,record:r}):Pf({uri:r,gatewayUrls:t})}async function P5(e,{gatewayUrls:t,record:r}){let n=Gy(r),o=await Wy(e,{nft:n}),{uri:s,isOnChain:a,isEncoded:i}=dm({uri:o,gatewayUrls:t});if(a&&(s.includes("data:application/json;base64,")||s.startsWith("{"))){let u=i?atob(s.replace("data:application/json;base64,","")):s,f=JSON.parse(u);return Pf({uri:pm(f),gatewayUrls:t})}let c=n.tokenID;return n.namespace==="erc1155"&&(c=c.replace("0x","").padStart(64,"0")),qy({gatewayUrls:t,uri:s.replace(/(?:0x)?{id}/,c)})}Ze();Qr();Xe();Po();Z();Di();async function Sf(e,t){let{blockNumber:r,blockTag:n,key:o,name:s,gatewayUrls:a,strict:i}=t,{chain:c}=e,u=(()=>{if(t.universalResolverAddress)return t.universalResolverAddress;if(!c)throw new Error("client chain not configured. universalResolverAddress is required.");return Lt({blockNumber:r,chain:c,contract:"ensUniversalResolver"})})(),f=c?.ensTlds;if(f&&!f.some(d=>s.endsWith(d)))return null;try{let d={address:u,abi:tf,args:[ce(Ea(s)),ie({abi:Zp,functionName:"text",args:[Li(s),o]}),a??[rn]],functionName:"resolveWithGateways",blockNumber:r,blockTag:n},m=await B(e,fe,"readContract")(d);if(m[0]==="0x")return null;let l=ot({abi:Zp,functionName:"text",data:m[0]});return l===""?null:l}catch(d){if(i)throw d;if(wa(d))return null;throw d}}async function Ky(e,{blockNumber:t,blockTag:r,assetGatewayUrls:n,name:o,gatewayUrls:s,strict:a,universalResolverAddress:i}){let c=await B(e,Sf,"getEnsText")({blockNumber:t,blockTag:r,key:"avatar",name:o,universalResolverAddress:i,gatewayUrls:s,strict:a});if(!c)return null;try{return await Zy(e,{record:c,gatewayUrls:n})}catch{return null}}Ze();Po();Di();async function Yy(e,t){let{address:r,blockNumber:n,blockTag:o,coinType:s=60n,gatewayUrls:a,strict:i}=t,{chain:c}=e,u=(()=>{if(t.universalResolverAddress)return t.universalResolverAddress;if(!c)throw new Error("client chain not configured. universalResolverAddress is required.");return Lt({blockNumber:n,chain:c,contract:"ensUniversalResolver"})})();try{let f={address:u,abi:by,args:[r,s,a??[rn]],functionName:"reverseWithGateways",blockNumber:n,blockTag:o},d=B(e,fe,"readContract"),[p]=await d(f);return p||null}catch(f){if(i)throw f;if(wa(f))return null;throw f}}Po();Z();async function Jy(e,t){let{blockNumber:r,blockTag:n,name:o}=t,{chain:s}=e,a=(()=>{if(t.universalResolverAddress)return t.universalResolverAddress;if(!s)throw new Error("client chain not configured. universalResolverAddress is required.");return Lt({blockNumber:r,chain:s,contract:"ensUniversalResolver"})})(),i=s?.ensTlds;if(i&&!i.some(u=>o.endsWith(u)))throw new Error(`${o} is not a valid ENS TLD (${i?.join(", ")}) for chain "${s.name}" (id: ${s.id}).`);let[c]=await B(e,fe,"readContract")({address:a,abi:[{inputs:[{type:"bytes"}],name:"findResolver",outputs:[{type:"address"},{type:"bytes32"},{type:"uint256"}],stateMutability:"view",type:"function"}],functionName:"findResolver",args:[ce(Ea(o))],blockNumber:r,blockTag:n});return c}So();be();J();Z();Xp();bo();Yr();vr();async function _f(e,t){let{account:r=e.account,blockNumber:n,blockTag:o="latest",blobs:s,data:a,gas:i,gasPrice:c,maxFeePerBlobGas:u,maxFeePerGas:f,maxPriorityFeePerGas:d,to:p,value:m,...l}=t,h=r?K(r):void 0;try{He(t);let b=(typeof n=="bigint"?I(n):void 0)||o,A=e.chain?.formatters?.transactionRequest?.format,y=(A||nt)({...Dt(l,{format:A}),account:h,blobs:s,data:a,gas:i,gasPrice:c,maxFeePerBlobGas:u,maxFeePerGas:f,maxPriorityFeePerGas:d,to:p,value:m},"createAccessList"),w=await e.request({method:"eth_createAccessList",params:[y,b]});if(w.error)throw new g(w.error,{details:w.error});return{accessList:w.accessList,gasUsed:BigInt(w.gasUsed)}}catch(x){throw af(x,{...t,account:h,chain:e.chain})}}async function Xy(e){let t=vn(e,{method:"eth_newBlockFilter"}),r=await e.request({method:"eth_newBlockFilter"});return{id:r,request:t(r),type:"block"}}Z();async function If(e,{address:t,args:r,event:n,events:o,fromBlock:s,strict:a,toBlock:i}={}){let c=o??(n?[n]:void 0),u=vn(e,{method:"eth_newFilter"}),f=[];c&&(f=[c.flatMap(m=>lr({abi:[m],eventName:m.name,args:r}))],n&&(f=f[0]));let d=await e.request({method:"eth_newFilter",params:[{address:t,fromBlock:typeof s=="bigint"?I(s):s,toBlock:typeof i=="bigint"?I(i):i,...f.length?{topics:f}:{}}]});return{abi:c,args:r,eventName:n?n.name:void 0,fromBlock:s,id:d,request:u(d),strict:!!a,toBlock:i,type:"event"}}async function Bf(e){let t=vn(e,{method:"eth_newPendingTransactionFilter"}),r=await e.request({method:"eth_newPendingTransactionFilter"});return{id:r,request:t(r),type:"transaction"}}Ze();Qr();Xe();go();So();async function Qy(e,{address:t,blockHash:r,blockNumber:n,blockTag:o=e.experimental_blockTag??"latest",requireCanonical:s}){let a=Bt({blockHash:r,blockNumber:n,blockTag:o,requireCanonical:s});if(e.batch?.multicall&&e.chain?.contracts?.multicall3){let c=e.chain.contracts.multicall3.address,u=ie({abi:sr,functionName:"getEthBalance",args:[t]}),{data:f}=await B(e,jt,"call")({to:c,data:u,blockHash:r,blockNumber:n,blockTag:o,requireCanonical:s});return ot({abi:sr,functionName:"getEthBalance",args:[t],data:f||"0x"})}let i=await e.request({method:"eth_getBalance",params:[t,a]});return BigInt(i)}async function ex(e){let t=await e.request({method:"eth_blobBaseFee"});return BigInt(t)}Z();async function tx(e,{blockHash:t,blockNumber:r,blockTag:n=e.experimental_blockTag??"latest"}={}){let o=r!==void 0?I(r):void 0,s=await e.request({method:"eth_getBlockReceipts",params:[t||o||n]},{dedupe:!!(t||o)});if(!s)throw new zn({blockHash:t,blockNumber:r});let a=e.chain?.formatters?.transactionReceipt?.format||Io;return s.map(i=>a(i,"getBlockReceipts"))}ze();Z();async function rx(e,{blockHash:t,blockNumber:r,blockTag:n="latest"}={}){let o=r!==void 0?I(r):void 0,s;return t?s=await e.request({method:"eth_getBlockTransactionCountByHash",params:[t]},{dedupe:!0}):s=await e.request({method:"eth_getBlockTransactionCountByNumber",params:[o||n]},{dedupe:!!o}),ye(s)}go();async function Bo(e,{address:t,blockHash:r,blockNumber:n,blockTag:o="latest",requireCanonical:s}){let a=Bt({blockHash:r,blockNumber:n,blockTag:o,requireCanonical:s}),i=await e.request({method:"eth_getCode",params:[t,a]},{dedupe:typeof n=="bigint"||r!==void 0});if(i!=="0x")return i}tr();pt();Hr();async function nx(e,{address:t,blockNumber:r,blockTag:n="latest"}){let o=await Bo(e,{address:t,...r!==void 0?{blockNumber:r}:{blockTag:n}});if(o&&ee(o)===23&&o.startsWith("0xef0100"))return de(yt(o,3,23))}J();var kf=class extends g{constructor({address:t}){super(`No EIP-712 domain found on contract "${t}".`,{metaMessages:["Ensure that:",`- The contract is deployed at the address "${t}".`,"- `eip712Domain()` function exists on the contract.","- `eip712Domain()` function matches signature to ERC-5267 specification."],name:"Eip712DomainNotFoundError"})}};async function ox(e,t){let{address:r,factory:n,factoryData:o}=t;try{let[s,a,i,c,u,f,d]=await B(e,fe,"readContract")({abi:S5,address:r,functionName:"eip712Domain",factory:n,factoryData:o});return{domain:{name:a,version:i,chainId:Number(c),verifyingContract:u,salt:f},extensions:d,fields:s}}catch(s){let a=s;throw a.name==="ContractFunctionExecutionError"&&a.cause.name==="ContractFunctionZeroDataError"?new kf({address:r}):a}}var S5=[{inputs:[],name:"eip712Domain",outputs:[{name:"fields",type:"bytes1"},{name:"name",type:"string"},{name:"version",type:"string"},{name:"chainId",type:"uint256"},{name:"verifyingContract",type:"address"},{name:"salt",type:"bytes32"},{name:"extensions",type:"uint256[]"}],stateMutability:"view",type:"function"}];Z();function sx(e){return{baseFeePerGas:e.baseFeePerGas.map(t=>BigInt(t)),gasUsedRatio:e.gasUsedRatio,oldestBlock:BigInt(e.oldestBlock),reward:e.reward?.map(t=>t.map(r=>BigInt(r)))}}async function ax(e,{blockCount:t,blockNumber:r,blockTag:n="latest",rewardPercentiles:o}){let s=typeof r=="bigint"?I(r):void 0,a=await e.request({method:"eth_feeHistory",params:[I(t),s||n,o]},{dedupe:!!s});return sx(a)}async function ix(e,{filter:t}){let r=t.strict??!1,o=(await t.request({method:"eth_getFilterLogs",params:[t.id]})).map(s=>je(s));return t.abi?Er({abi:t.abi,logs:o,strict:r}):o}go();Xe();Z();Pt();qe();Qn();Z();Mu();er();J();Hi();Rn();ht();pt();Hr();ze();function cx(e){let{authorizationList:t}=e;if(t)for(let r of t){let{chainId:n}=r,o=r.address;if(!re(o))throw new me({address:o});if(n<0)throw new To({chainId:n})}Cf(e)}function ux(e){let{blobVersionedHashes:t}=e;if(t){if(t.length===0)throw new sa;for(let r of t){let n=ee(r),o=ye(yt(r,0,1));if(n!==32)throw new Hu({hash:r,size:n});if(o!==1)throw new Du({hash:r,version:o})}}Cf(e)}function Cf(e){let{chainId:t,maxPriorityFeePerGas:r,maxFeePerGas:n,to:o}=e;if(t<=0)throw new To({chainId:t});if(o&&!re(o))throw new me({address:o});if(n&&n>gr)throw new $t({maxFeePerGas:n});if(r&&n&&r>n)throw new br({maxFeePerGas:n,maxPriorityFeePerGas:r})}function fx(e){let{chainId:t,maxPriorityFeePerGas:r,gasPrice:n,maxFeePerGas:o,to:s}=e;if(t<=0)throw new To({chainId:t});if(s&&!re(s))throw new me({address:s});if(r||o)throw new g("`maxFeePerGas`/`maxPriorityFeePerGas` is not a valid EIP-2930 Transaction attribute.");if(n&&n>gr)throw new $t({maxFeePerGas:n})}function dx(e){let{chainId:t,maxPriorityFeePerGas:r,gasPrice:n,maxFeePerGas:o,to:s}=e;if(s&&!re(s))throw new me({address:s});if(typeof t<"u"&&t<=0)throw new To({chainId:t});if(r||o)throw new g("`maxFeePerGas`/`maxPriorityFeePerGas` is not a valid Legacy Transaction attribute.");if(n&&n>gr)throw new $t({maxFeePerGas:n})}er();Pt();ht();function ji(e){if(!e||e.length===0)return[];let t=[];for(let r=0;r"u"||typeof m>"u")){let v=typeof e.blobs[0]=="string"?e.blobs:e.blobs.map(E=>ae(E)),y=e.kzg,w=na({blobs:v,kzg:y});if(typeof p>"u"&&(p=Ou({commitments:w})),typeof m>"u"){let E=oa({blobs:v,commitments:w,kzg:y});m=Uu({blobs:v,commitments:w,proofs:E})}}let l=ji(f),h=[I(r),o?I(o):"0x",u?I(u):"0x",c?I(c):"0x",n?I(n):"0x",s??"0x",a?I(a):"0x",d??"0x",l,i?I(i):"0x",p??[],...Aa(e,t)],x=[],b=[],A=[];if(m)for(let v=0;v{if(t.v>=35n)return(t.v-35n)/2n>0?t.v:27n+(t.v===35n?0n:1n);if(r>0)return BigInt(r*2)+BigInt(35n+t.v-27n);let m=27n+(t.v===27n?0n:1n);if(t.v!==m)throw new pu({v:t.v});return m})(),d=Me(t.r),p=Me(t.s);u=[...u,I(f),d==="0x00"?"0x":d,p==="0x00"?"0x":p]}else r>0&&(u=[...u,I(r),"0x","0x"]);return or(u)}function Aa(e,t){let r=t??e,{v:n,yParity:o}=r;if(typeof r.r>"u")return[];if(typeof r.s>"u")return[];if(typeof n>"u"&&typeof o>"u")return[];let s=Me(r.r),a=Me(r.s);return[typeof o=="number"?o?I(1):"0x":n===0n?"0x":n===1n?I(1):n===27n?"0x":I(1),s==="0x00"?"0x":s,a==="0x00"?"0x":a]}function px(e){if(!e||e.length===0)return[];let t=[];for(let r of e){let{chainId:n,nonce:o,...s}=r,a=r.address;t.push([n?ce(n):"0x",a,o?ce(o):"0x",...Aa({},s)])}return t}tr();Xr();async function mx({address:e,authorization:t,signature:r}){return Le(de(e),await Cn({authorization:t,signature:r}))}J();fo();Fs();rr();no();var Nf=new lt(8192);function lx(e,{enabled:t=!0,id:r}){if(!t||!r)return e();if(Nf.get(r))return Nf.get(r);let n=e().finally(()=>Nf.delete(r));return Nf.set(r,n),n}Qe();function hx(e,t={}){return async(r,n={})=>{let{dedupe:o=!1,methods:s,retryDelay:a=150,retryCount:i=3,signal:c,uid:u}={...t,...n},{method:f}=r;if(s?.exclude?.includes(f))throw new An(new Error("method not supported"),{method:f});if(s?.include&&!s.include.includes(f))throw new An(new Error("method not supported"),{method:f});if(c?.aborted)throw Ge(c);let d=o?N5(`${u}.${ne(r)}`):void 0;return lx(()=>_o(async()=>{try{return await e(r,c?{signal:c}:void 0)}catch(p){if(c?.aborted)throw Ge(c);if(bt(p))throw p;let m=p;switch(m.code){case xs.code:throw new xs(m);case bs.code:throw new bs(m);case gs.code:throw new gs(m,{method:r.method});case vs.code:throw new vs(m);case Lr.code:throw new Lr(m);case Ot.code:throw new Ot(m);case ws.code:throw new ws(m);case Es.code:throw new Es(m);case Ts.code:throw new Ts(m);case An.code:throw new An(m,{method:r.method});case po.code:throw new po(m);case As.code:throw new As(m);case mo.code:throw new mo(m);case Ps.code:throw new Ps(m);case Ss.code:throw new Ss(m);case _s.code:throw new _s(m);case Is.code:throw new Is(m);case Bs.code:throw new Bs(m);case Pn.code:throw new Pn(m);case ks.code:throw new ks(m);case Cs.code:throw new Cs(m);case Rs.code:throw new Rs(m);case Ns.code:throw new Ns(m);case Ms.code:throw new Ms(m);case Sn.code:throw new Sn(m);case 5e3:throw new mo(m);case zs.code:throw new zs(m);default:throw p instanceof g?p:new gu(m)}}},{delay:({count:p,error:m})=>{if(m&&m instanceof Ft){let l=m?.headers?.get("Retry-After");if(l?.match(/\d/))return Number.parseInt(l,10)*1e3}return~~(1<R5(p)}),{enabled:o,id:d})}}function R5(e){return bt(e)?!1:"code"in e&&typeof e.code=="number"?e.code===-1||e.code===po.code||e.code===Lr.code||e.code===429:e instanceof Ft&&e.status?e.status===403||e.status===408||e.status===413||e.status===429||e.status===500||e.status===502||e.status===503||e.status===504:!0}function N5(e,t=0){let r=3735928559^t,n=1103547991^t;for(let o=0;o>>16,2246822507),r^=Math.imul(n^n>>>16,3266489909),n=Math.imul(n^n>>>16,2246822507),n^=Math.imul(r^r>>>16,3266489909),(4294967296*(2097151&n)+(r>>>0)).toString(36)}function Pa(e){let t={formatters:void 0,fees:void 0,serializers:void 0,...e};function r(n){return o=>{let s=typeof o=="function"?o(n):o,a={...n,...s};return Object.assign(a,{extend:r(a)})}}return Object.assign(t,{extend:r(t)})}ze();fo();rr();rr();function yx(e,{errorInstance:t=new Error("timed out"),timeout:r,signal:n}){return new Promise((o,s)=>{(async()=>{let a,i=new AbortController;try{r>0&&(a=setTimeout(()=>{n?i.abort():s(t)},r)),o(await e({signal:i?.signal||null}))}catch(c){if(i?.signal.aborted&&bt(c)){s(t);return}s(c)}finally{clearTimeout(a)}})()})}Qe();function M5(){return{current:0,take(){return this.current++},reset(){this.current=0}}}var mm=M5();var z5=10485760;function xx(e,t={}){let{url:r,headers:n}=O5(e);return{async request(o){let{body:s,fetchFn:a=t.fetchFn??fetch,maxResponseBodySize:i=t.maxResponseBodySize??z5,onRequest:c=t.onRequest,onResponse:u=t.onResponse,timeout:f=t.timeout??1e4}=o,d={...t.fetchOptions??{},...o.fetchOptions??{}},{headers:p,method:m,signal:l}=d;try{let h=await yx(async({signal:A})=>{let v={...d,body:Array.isArray(s)?ne(s.map(T=>({jsonrpc:"2.0",id:T.id??mm.take(),...T}))):ne({jsonrpc:"2.0",id:s.id??mm.take(),...s}),headers:{...n,"Content-Type":"application/json",...p},method:m||"POST",signal:l||(f>0?A:null)},y=new Request(r,v),w=await c?.(y,v)??{...v,url:r};return await a(w.url??r,w)},{errorInstance:new xi({body:s,url:r}),timeout:f,signal:!0});u&&await u(h);let x,b=await F5(h,{maxResponseBodySize:i});if(h.headers.get("Content-Type")?.startsWith("application/json"))x=JSON.parse(b);else{x=b;try{x=JSON.parse(x||"{}")}catch(A){if(h.ok)throw A;x={error:x}}}if(!h.ok){if(typeof x.error?.code=="number"&&typeof x.error?.message=="string")return x;throw new Ft({body:s,details:ne(x.error)||h.statusText,headers:h.headers,status:h.status,url:r})}return x}catch(h){throw l?.aborted?Ge(l):bt(h)||h instanceof Ft||h instanceof uo||h instanceof xi?h:new Ft({body:s,cause:h,url:r})}}}}async function F5(e,{maxResponseBodySize:t}){if(t===!1)return e.text();let r=e.headers.get("Content-Length");if(r){let i=Number(r);if(i>t)throw new uo({maxSize:t,size:i})}if(!e.body){let i=await e.text(),c=new TextEncoder().encode(i).length;if(c>t)throw new uo({maxSize:t,size:c});return i}let n=e.body.getReader(),o=new TextDecoder,s="",a=0;try{for(;;){let{done:i,value:c}=await n.read();if(i)break;if(a+=c.byteLength,a>t)throw await n.cancel(),new uo({maxSize:t,size:a});s+=o.decode(c,{stream:!0})}return s+=o.decode(),s}finally{n.releaseLock()}}function O5(e){try{let t=new URL(e),r=(()=>{if(t.username){let n=`${decodeURIComponent(t.username)}:${decodeURIComponent(t.password)}`;return t.username="",t.password="",{url:t.toString(),headers:{Authorization:`Basic ${btoa(n)}`}}}})();return{url:t.toString(),...r}}catch{return{url:e}}}At();var bx=`Ethereum Signed Message:
+`;qe();pt();Z();function gx(e){let t=typeof e=="string"?Fr(e):typeof e.raw=="string"?e.raw:ae(e.raw),r=Fr(`${bx}${ee(t)}`);return Oe([r,t])}function Sa(e,t){return oe(gx(e),t)}Dr();qe();Z();At();Ae();er();Qe();J();var Mf=class extends g{constructor({domain:t}){super(`Invalid domain "${ne(t)}".`,{metaMessages:["Must be a valid EIP-712 domain."]})}},zf=class extends g{constructor({primaryType:t,types:r}){super(`Invalid primary type \`${t}\` must be one of \`${JSON.stringify(Object.keys(r))}\`.`,{docsPath:"/api/glossary/Errors#typeddatainvalidprimarytypeerror",metaMessages:["Check that the primary type is a key in `types`."]})}},Ff=class extends g{constructor({type:t}){super(`Struct type "${t}" is invalid.`,{metaMessages:["Struct type must not be a Solidity type."],name:"InvalidStructTypeError"})}};ht();pt();Z();Yd();Qe();function vx(e){let{domain:t,message:r,primaryType:n,types:o}=e,s=(c,u)=>{let f={...u};for(let d of c){let{name:p,type:m}=d;m==="address"&&(f[p]=f[p].toLowerCase())}return f},a=o.EIP712Domain?t?s(o.EIP712Domain,t):{}:{},i=(()=>{if(n!=="EIP712Domain")return s(o[n],r)})();return ne({domain:a,message:i,primaryType:n,types:o})}function Of(e){let{domain:t,message:r,primaryType:n,types:o}=e,s=(a,i)=>{for(let c of a){let{name:u,type:f}=c,d=i[u],p=f.match(ru);if(p&&(typeof d=="number"||typeof d=="bigint")){let[h,x,b]=p;I(d,{signed:x==="int",size:Number.parseInt(b,10)/8})}if(f==="address"&&typeof d=="string"&&!re(d))throw new me({address:d});let m=f.match(S0);if(m){let[h,x]=m;if(x&&ee(d)!==Number.parseInt(x,10))throw new $c({expectedSize:Number.parseInt(x,10),givenSize:ee(d)})}let l=o[f];l&&($5(f),s(l,d))}};if(o.EIP712Domain&&t){if(typeof t!="object")throw new Mf({domain:t});s(o.EIP712Domain,t)}if(n!=="EIP712Domain")if(o[n])s(o[n],r);else throw new zf({primaryType:n,types:o})}function $f({domain:e}){return[typeof e?.name=="string"&&{name:"name",type:"string"},e?.version&&{name:"version",type:"string"},(typeof e?.chainId=="number"||typeof e?.chainId=="bigint")&&{name:"chainId",type:"uint256"},e?.verifyingContract&&{name:"verifyingContract",type:"address"},e?.salt&&{name:"salt",type:"bytes32"}].filter(Boolean)}function $5(e){if(e==="address"||e==="bool"||e==="string"||e.startsWith("bytes")||e.startsWith("uint")||e.startsWith("int"))throw new Ff({type:e})}function Hf(e){let{domain:t={},message:r,primaryType:n}=e,o={EIP712Domain:$f({domain:t}),...e.types};Of({domain:t,message:r,primaryType:n,types:o});let s=["0x1901"];return t&&s.push(H5({domain:t,types:o})),n!=="EIP712Domain"&&s.push(wx({data:r,primaryType:n,types:o})),oe(Oe(s))}function H5({domain:e,types:t}){return wx({data:e,primaryType:"EIP712Domain",types:t})}function wx({data:e,primaryType:t,types:r}){let n=Ex({data:e,primaryType:t,types:r});return oe(n)}function Ex({data:e,primaryType:t,types:r}){let n=[{type:"bytes32"}],o=[D5({primaryType:t,types:r})];for(let s of r[t]){let[a,i]=Ax({types:r,name:s.name,type:s.type,value:e[s.name]});n.push(a),o.push(i)}return Je(n,o)}function D5({primaryType:e,types:t}){let r=ce(U5({primaryType:e,types:t}));return oe(r)}function U5({primaryType:e,types:t}){let r="",n=Tx({primaryType:e,types:t});n.delete(e);let o=[e,...Array.from(n).sort()];for(let s of o)r+=`${s}(${t[s].map(({name:a,type:i})=>`${i} ${a}`).join(",")})`;return r}function Tx({primaryType:e,types:t},r=new Set){let o=e.match(/^\w*/u)?.[0];if(r.has(o)||t[o]===void 0)return r;r.add(o);for(let s of t[o])Tx({primaryType:s.type,types:t},r);return r}function Ax({types:e,name:t,type:r,value:n}){if(e[r]!==void 0)return[{type:"bytes32"},oe(Ex({data:n,primaryType:r,types:e}))];if(r==="bytes")return[{type:"bytes32"},oe(n)];if(r==="string")return[{type:"bytes32"},oe(ce(n))];if(r.lastIndexOf("]")===r.length-1){let o=r.slice(0,r.lastIndexOf("[")),s=n.map(a=>Ax({name:t,type:o,types:e,value:a}));return[{type:"bytes32"},oe(Je(s.map(([a])=>a),s.map(([,a])=>a)))]}return[{type:r},n]}var Yi={};Qa(Yi,{InvalidWrappedSignatureError:()=>ed,assert:()=>td,from:()=>Ev,magicBytes:()=>Hm,suffixParameters:()=>Dm,unwrap:()=>qx,validate:()=>Av,wrap:()=>Tv});ri();On();var Df=class extends Map{constructor(t){super(),Object.defineProperty(this,"maxSize",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.maxSize=t}get(t){let r=super.get(t);return super.has(t)&&r!==void 0&&(this.delete(t),super.set(t,r)),r}set(t,r){if(super.set(t,r),this.maxSize&&this.size>this.maxSize){let n=this.keys().next().value;n&&this.delete(n)}return this}};var L5={checksum:new Df(8192)},Uf=L5.checksum;vt();qd();On();Ve();function ko(e,t={}){let{as:r=typeof e=="string"?"Hex":"Bytes"}=t,n=Qc(iy(e));return r==="Bytes"?n:Se(n)}On();vt();Ve();Ni();function Px(e,t={}){let{compressed:r}=t,{prefix:n,x:o,y:s}=e;if(r===!1||typeof o=="bigint"&&typeof s=="bigint"){if(n!==4)throw new Lf({prefix:n,cause:new xm});return}if(r===!0||typeof o=="bigint"&&typeof s>"u"){if(n!==3&&n!==2)throw new Lf({prefix:n,cause:new ym});return}throw new hm({publicKey:e})}function Sx(e){let t=(()=>{if(zi(e))return _x(e);if(my(e))return V5(e);let{prefix:r,x:n,y:o}=e;return typeof n=="bigint"&&typeof o=="bigint"?{prefix:r??4,x:n,y:o}:{prefix:r,x:n}})();return Px(t),t}function V5(e){return _x(Se(e))}function _x(e){if(e.length!==132&&e.length!==130&&e.length!==68)throw new bm({publicKey:e});if(e.length===130){let n=BigInt(ve(e,0,32)),o=BigInt(ve(e,32,64));return{prefix:4,x:n,y:o}}if(e.length===132){let n=Number(ve(e,0,1)),o=BigInt(ve(e,1,33)),s=BigInt(ve(e,33,65));return{prefix:n,x:o,y:s}}let t=Number(ve(e,0,1)),r=BigInt(ve(e,1,33));return{prefix:t,x:r}}function gm(e,t={}){Px(e);let{prefix:r,x:n,y:o}=e,{includePrefix:s=!0}=t;return Ee(s?ue(r,{size:1}):"0x",ue(n,{size:32}),typeof o=="bigint"?ue(o,{size:32}):"0x")}var hm=class extends j{constructor({publicKey:t}){super(`Value \`${$n(t)}\` is not a valid public key.`,{metaMessages:["Public key must contain:","- an `x` and `prefix` value (compressed)","- an `x`, `y`, and `prefix` value (uncompressed)"]}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"PublicKey.InvalidError"})}},Lf=class extends j{constructor({prefix:t,cause:r}){super(`Prefix "${t}" is invalid.`,{cause:r}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"PublicKey.InvalidPrefixError"})}},ym=class extends j{constructor(){super("Prefix must be 2 or 3 for compressed public keys."),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"PublicKey.InvalidCompressedPrefixError"})}},xm=class extends j{constructor(){super("Prefix must be 4 for uncompressed public keys."),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"PublicKey.InvalidUncompressedPrefixError"})}},bm=class extends j{constructor({publicKey:t}){super(`Value \`${t}\` is an invalid public key size.`,{metaMessages:["Expected: 33 bytes (compressed + prefix), 64 bytes (uncompressed) or 65 bytes (uncompressed + prefix).",`Received ${ge(la(t))} bytes.`]}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"PublicKey.InvalidSerializedSizeError"})}};var q5=/^0x[a-fA-F0-9]{40}$/;function Co(e,t={}){let{strict:r=!0}=t;if(!q5.test(e))throw new jf({address:e,cause:new vm});if(r){if(e.toLowerCase()===e)return;if(Vf(e)!==e)throw new jf({address:e,cause:new wm})}}function Vf(e){if(Uf.has(e))return Uf.get(e);Co(e,{strict:!1});let t=e.substring(2).toLowerCase(),r=ko(cy(t),{as:"Bytes"}),n=t.split("");for(let s=0;s<40;s+=2)r[s>>1]>>4>=8&&n[s]&&(n[s]=n[s].toUpperCase()),(r[s>>1]&15)>=8&&n[s+1]&&(n[s+1]=n[s+1].toUpperCase());let o=`0x${n.join("")}`;return Uf.set(e,o),o}function G5(e,t={}){let{checksum:r=!1}=t;return Co(e),r?Vf(e):e}function Bx(e,t={}){let r=ko(`0x${gm(e).slice(4)}`).substring(26);return G5(`0x${r}`,t)}function qf(e,t={}){let{strict:r=!0}=t??{};try{return Co(e,{strict:r}),!0}catch{return!1}}var jf=class extends j{constructor({address:t,cause:r}){super(`Address "${t}" is invalid.`,{cause:r}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Address.InvalidAddressError"})}},vm=class extends j{constructor(){super("Address is not a 20 byte (40 hexadecimal character) value."),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Address.InvalidInputError"})}},wm=class extends j{constructor(){super("Address does not match its checksum counterpart."),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Address.InvalidChecksumError"})}};On();vt();Ve();On();vt();Ve();var kx=/^(.*)\[([0-9]*)\]$/,Cx=/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/,Wf=/^(u?int)(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/,RM=2n**(8n-1n)-1n,NM=2n**(16n-1n)-1n,MM=2n**(24n-1n)-1n,zM=2n**(32n-1n)-1n,FM=2n**(40n-1n)-1n,OM=2n**(48n-1n)-1n,$M=2n**(56n-1n)-1n,HM=2n**(64n-1n)-1n,DM=2n**(72n-1n)-1n,UM=2n**(80n-1n)-1n,LM=2n**(88n-1n)-1n,jM=2n**(96n-1n)-1n,VM=2n**(104n-1n)-1n,qM=2n**(112n-1n)-1n,GM=2n**(120n-1n)-1n,WM=2n**(128n-1n)-1n,ZM=2n**(136n-1n)-1n,KM=2n**(144n-1n)-1n,YM=2n**(152n-1n)-1n,JM=2n**(160n-1n)-1n,XM=2n**(168n-1n)-1n,QM=2n**(176n-1n)-1n,ez=2n**(184n-1n)-1n,tz=2n**(192n-1n)-1n,rz=2n**(200n-1n)-1n,nz=2n**(208n-1n)-1n,oz=2n**(216n-1n)-1n,sz=2n**(224n-1n)-1n,az=2n**(232n-1n)-1n,iz=2n**(240n-1n)-1n,cz=2n**(248n-1n)-1n,uz=2n**(256n-1n)-1n,fz=-(2n**(8n-1n)),dz=-(2n**(16n-1n)),pz=-(2n**(24n-1n)),mz=-(2n**(32n-1n)),lz=-(2n**(40n-1n)),hz=-(2n**(48n-1n)),yz=-(2n**(56n-1n)),xz=-(2n**(64n-1n)),bz=-(2n**(72n-1n)),gz=-(2n**(80n-1n)),vz=-(2n**(88n-1n)),wz=-(2n**(96n-1n)),Ez=-(2n**(104n-1n)),Tz=-(2n**(112n-1n)),Az=-(2n**(120n-1n)),Pz=-(2n**(128n-1n)),Sz=-(2n**(136n-1n)),_z=-(2n**(144n-1n)),Iz=-(2n**(152n-1n)),Bz=-(2n**(160n-1n)),kz=-(2n**(168n-1n)),Cz=-(2n**(176n-1n)),Rz=-(2n**(184n-1n)),Nz=-(2n**(192n-1n)),Mz=-(2n**(200n-1n)),zz=-(2n**(208n-1n)),Fz=-(2n**(216n-1n)),Oz=-(2n**(224n-1n)),$z=-(2n**(232n-1n)),Hz=-(2n**(240n-1n)),Dz=-(2n**(248n-1n)),Uz=-(2n**(256n-1n)),Lz=2n**8n-1n,jz=2n**16n-1n,Vz=2n**24n-1n,qz=2n**32n-1n,Gz=2n**40n-1n,Wz=2n**48n-1n,Zz=2n**56n-1n,Kz=2n**64n-1n,Yz=2n**72n-1n,Jz=2n**80n-1n,Xz=2n**88n-1n,Qz=2n**96n-1n,eF=2n**104n-1n,tF=2n**112n-1n,rF=2n**120n-1n,nF=2n**128n-1n,oF=2n**136n-1n,sF=2n**144n-1n,aF=2n**152n-1n,iF=2n**160n-1n,cF=2n**168n-1n,uF=2n**176n-1n,fF=2n**184n-1n,dF=2n**192n-1n,pF=2n**200n-1n,mF=2n**208n-1n,lF=2n**216n-1n,hF=2n**224n-1n,yF=2n**232n-1n,xF=2n**240n-1n,bF=2n**248n-1n,Em=2n**256n-1n;function Ro(e,t,r){let{checksumAddress:n,staticPosition:o}=r,s=Pm(t.type);if(s){let[a,i]=s;return Z5(e,{...t,type:i},{checksumAddress:n,length:a,staticPosition:o})}if(t.type==="tuple")return X5(e,t,{checksumAddress:n,staticPosition:o});if(t.type==="address")return W5(e,{checksum:n});if(t.type==="bool")return K5(e);if(t.type.startsWith("bytes"))return Y5(e,t,{staticPosition:o});if(t.type.startsWith("uint")||t.type.startsWith("int"))return J5(e,t);if(t.type==="string")return Q5(e,{staticPosition:o});throw new _a(t.type)}var Nx=32,Tm=32;function W5(e,t={}){let{checksum:r=!1}=t,n=e.readBytes(32);return[(s=>r?Vf(s):s)(Se(uy(n,-20))),32]}function Z5(e,t,r){let{checksumAddress:n,length:o,staticPosition:s}=r;if(!o){let c=Tr(e.readBytes(Tm)),u=s+c,f=u+Nx;e.setPosition(u);let d=Tr(e.readBytes(Nx)),p=Vi(t),m=0,l=[];for(let h=0;h48?fy(o,{signed:r}):Tr(o,{signed:r}),32]}function X5(e,t,r){let{checksumAddress:n,staticPosition:o}=r,s=t.components.length===0||t.components.some(({name:c})=>!c),a=s?[]:{},i=0;if(Vi(t)){let c=Tr(e.readBytes(Tm)),u=o+c;for(let f=0;f0?Ee(u,c):u}}if(a)return{dynamic:!0,encoded:c}}return{dynamic:!1,encoded:Ee(...i.map(({encoded:c})=>c))}}function rv(e,{type:t}){let[,r]=t.split("bytes"),n=ge(e);if(!r){let o=e;return n%32!==0&&(o=Ar(o,Math.ceil((e.length-2)/2/32)*32)),{dynamic:!0,encoded:Ee(tn(ue(n,{size:32})),o)}}if(n!==Number.parseInt(r,10))throw new qi({expectedSize:Number.parseInt(r,10),value:e});return{dynamic:!1,encoded:Ar(e)}}function nv(e){if(typeof e!="boolean")throw new j(`Invalid boolean value: "${e}" (type: ${typeof e}). Expected: \`true\` or \`false\`.`);return{dynamic:!1,encoded:tn(Xu(e))}}function ov(e,{signed:t,size:r}){if(typeof r=="number"){let n=2n**(BigInt(r)-(t?1n:0n))-1n,o=t?-n-1n:0n;if(e>n||ea))}}function Pm(e){let t=e.match(/^(.*)\[(\d+)?\]$/);return t?[t[2]?Number(t[2]):null,t[1]]:void 0}function Vi(e){let{type:t}=e;if(t==="string"||t==="bytes"||t.endsWith("[]"))return!0;if(t==="tuple")return e.components?.some(Vi);let r=Pm(e.type);return!!(r&&Vi({...e,type:r[1]}))}vt();var cv={bytes:new Uint8Array,dataView:new DataView(new ArrayBuffer(0)),position:0,positionReadCount:new Map,recursiveReadCount:0,recursiveReadLimit:Number.POSITIVE_INFINITY,assertReadLimit(){if(this.recursiveReadCount>=this.recursiveReadLimit)throw new _m({count:this.recursiveReadCount+1,limit:this.recursiveReadLimit})},assertPosition(e){if(e<0||e>this.bytes.length-1)throw new Sm({length:this.bytes.length,position:e})},decrementPosition(e){if(e<0)throw new Jf({offset:e});let t=this.position-e;this.assertPosition(t),this.position=t},getReadCount(e){return this.positionReadCount.get(e||this.position)||0},incrementPosition(e){if(e<0)throw new Jf({offset:e});let t=this.position+e;this.assertPosition(t),this.position=t},inspectByte(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectBytes(e,t){let r=t??this.position;return this.assertPosition(r+e-1),this.bytes.subarray(r,r+e)},inspectUint8(e){let t=e??this.position;return this.assertPosition(t),this.bytes[t]},inspectUint16(e){let t=e??this.position;return this.assertPosition(t+1),this.dataView.getUint16(t)},inspectUint24(e){let t=e??this.position;return this.assertPosition(t+2),(this.dataView.getUint16(t)<<8)+this.dataView.getUint8(t+2)},inspectUint32(e){let t=e??this.position;return this.assertPosition(t+3),this.dataView.getUint32(t)},pushByte(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushBytes(e){this.assertPosition(this.position+e.length-1),this.bytes.set(e,this.position),this.position+=e.length},pushUint8(e){this.assertPosition(this.position),this.bytes[this.position]=e,this.position++},pushUint16(e){this.assertPosition(this.position+1),this.dataView.setUint16(this.position,e),this.position+=2},pushUint24(e){this.assertPosition(this.position+2),this.dataView.setUint16(this.position,e>>8),this.dataView.setUint8(this.position+2,e&255),this.position+=3},pushUint32(e){this.assertPosition(this.position+3),this.dataView.setUint32(this.position,e),this.position+=4},readByte(){this.assertReadLimit(),this._touch();let e=this.inspectByte();return this.position++,e},readBytes(e,t){this.assertReadLimit(),this._touch();let r=this.inspectBytes(e);return this.position+=t??e,r},readUint8(){this.assertReadLimit(),this._touch();let e=this.inspectUint8();return this.position+=1,e},readUint16(){this.assertReadLimit(),this._touch();let e=this.inspectUint16();return this.position+=2,e},readUint24(){this.assertReadLimit(),this._touch();let e=this.inspectUint24();return this.position+=3,e},readUint32(){this.assertReadLimit(),this._touch();let e=this.inspectUint32();return this.position+=4,e},get remaining(){return this.bytes.length-this.position},setPosition(e){let t=this.position;return this.assertPosition(e),this.position=e,()=>this.position=t},_touch(){if(this.recursiveReadLimit===Number.POSITIVE_INFINITY)return;let e=this.getReadCount();this.positionReadCount.set(this.position,e+1),e>0&&this.recursiveReadCount++}};function Xf(e,{recursiveReadLimit:t=8192}={}){let r=Object.create(cv);return r.bytes=e,r.dataView=new DataView(e.buffer,e.byteOffset,e.byteLength),r.positionReadCount=new Map,r.recursiveReadLimit=t,r}var Jf=class extends j{constructor({offset:t}){super(`Offset \`${t}\` cannot be negative.`),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Cursor.NegativeOffsetError"})}},Sm=class extends j{constructor({length:t,position:r}){super(`Position \`${r}\` is out of bounds (\`0 < position < ${t}\`).`),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Cursor.PositionOutOfBoundsError"})}},_m=class extends j{constructor({count:t,limit:r}){super(`Recursive read limit of \`${r}\` exceeded (recursive read count: \`${t}\`).`),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Cursor.RecursiveReadLimitExceededError"})}};function Ia(e,t,r={}){let{as:n="Array",checksumAddress:o=!1}=r,s=typeof t=="string"?ma(t):t,a=Xf(s);if(Ut(s)===0&&e.length>0)throw new km;if(Ut(s)&&Ut(s)<32)throw new Bm({data:typeof t=="string"?t:Se(t),parameters:e,size:Ut(s)});let i=0,c=n==="Array"?[]:{};for(let u=0;uOx(t))):dv(e)}function fv(e){let t=e.reduce((o,s)=>o+s.length,0),r=$x(t);return{length:t<=55?1+t:1+r+t,encode(o){t<=55?o.pushByte(192+t):(o.pushByte(247+r),r===1?o.pushUint8(t):r===2?o.pushUint16(t):r===3?o.pushUint24(t):o.pushUint32(t));for(let{encode:s}of e)s(o)}}}function dv(e){let t=typeof e=="string"?ma(e):e,r=$x(t.length);return{length:t.length===1&&t[0]<128?1:t.length<=55?1+t.length:1+r+t.length,encode(o){t.length===1&&t[0]<128?o.pushBytes(t):t.length<=55?(o.pushByte(128+t.length),o.pushBytes(t)):(o.pushByte(183+r),r===1?o.pushUint8(t.length):r===2?o.pushUint16(t.length):r===3?o.pushUint24(t.length):o.pushUint32(t.length),o.pushBytes(t))}}}function $x(e){if(e<=255)return 1;if(e<=65535)return 2;if(e<=16777215)return 3;if(e<=4294967295)return 4;throw new j("Length is too large.")}vt();Ve();Ni();function zm(e,t={}){let{recovered:r}=t;if(typeof e.r>"u")throw new Zi({signature:e});if(typeof e.s>"u")throw new Zi({signature:e});if(r&&typeof e.yParity>"u")throw new Zi({signature:e});if(e.r<0n||e.r>Em)throw new Rm({value:e.r});if(e.s<0n||e.s>Em)throw new Nm({value:e.s});if(typeof e.yParity=="number"&&e.yParity!==0&&e.yParity!==1)throw new Ki({value:e.yParity})}function mv(e){return Hx(Se(e))}function Hx(e){if(e.length!==130&&e.length!==132)throw new Cm({signature:e});let t=BigInt(ve(e,0,32)),r=BigInt(ve(e,32,64)),n=(()=>{let o=+`0x${e.slice(130)}`;if(!Number.isNaN(o))try{return $m(o)}catch{throw new Ki({value:o})}})();return typeof n>"u"?{r:t,s:r}:{r:t,s:r,yParity:n}}function Fm(e){if(!(typeof e.r>"u")&&!(typeof e.s>"u"))return Om(e)}function Om(e){let t=typeof e=="string"?Hx(e):e instanceof Uint8Array?mv(e):typeof e.r=="string"?hv(e):e.v?lv(e):{r:e.r,s:e.s,...typeof e.yParity<"u"?{yParity:e.yParity}:{}};return zm(t),t}function lv(e){return{r:e.r,s:e.s,yParity:$m(e.v)}}function hv(e){let t=(()=>{let r=e.v?Number(e.v):void 0,n=e.yParity?Number(e.yParity):void 0;if(typeof r=="number"&&typeof n!="number"&&(n=$m(r)),typeof n!="number")throw new Ki({value:e.yParity});return n})();return{r:BigInt(e.r),s:BigInt(e.s),yParity:t}}function Dx(e){let{r:t,s:r,yParity:n}=e;return[n?"0x01":"0x",t===0n?"0x":Gp(ue(t)),r===0n?"0x":Gp(ue(r))]}function $m(e){if(e===0||e===27)return 0;if(e===1||e===28)return 1;if(e>=35)return e%2===0?1:0;throw new Mm({value:e})}var Cm=class extends j{constructor({signature:t}){super(`Value \`${t}\` is an invalid signature size.`,{metaMessages:["Expected: 64 bytes or 65 bytes.",`Received ${ge(la(t))} bytes.`]}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Signature.InvalidSerializedSizeError"})}},Zi=class extends j{constructor({signature:t}){super(`Signature \`${$n(t)}\` is missing either an \`r\`, \`s\`, or \`yParity\` property.`),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Signature.MissingPropertiesError"})}},Rm=class extends j{constructor({value:t}){super(`Value \`${t}\` is an invalid r value. r must be a positive integer less than 2^256.`),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Signature.InvalidRError"})}},Nm=class extends j{constructor({value:t}){super(`Value \`${t}\` is an invalid s value. s must be a positive integer less than 2^256.`),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Signature.InvalidSError"})}},Ki=class extends j{constructor({value:t}){super(`Value \`${t}\` is an invalid y-parity value. Y-parity must be 0 or 1.`),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Signature.InvalidYParityError"})}},Mm=class extends j{constructor({value:t}){super(`Value \`${t}\` is an invalid v value. v must be 27, 28 or >=35.`),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"Signature.InvalidVError"})}};function Lx(e,t={}){return typeof e.chainId=="string"?yv(e):{...e,...t.signature}}function yv(e){let{address:t,chainId:r,nonce:n}=e,o=Fm(e);return{address:t,chainId:Number(r),nonce:BigInt(n),...o}}function jx(e){return xv(e,{presign:!0})}function xv(e,t={}){let{presign:r}=t;return ko(Ee("0x05",Fx(bv(r?{address:e.address,chainId:e.chainId,nonce:e.nonce}:e))))}function bv(e){let{address:t,chainId:r,nonce:n}=e,o=Fm(e);return[r?ue(r):"0x",t,n?ue(n):"0x",...o?Dx(o):[]]}vt();Ve();js();Ve();function Vx(e){return Bx(vv(e))}function vv(e){let{payload:t,signature:r}=e,{r:n,s:o,yParity:s}=r,i=new It.Signature(BigInt(n),BigInt(o)).addRecoveryBit(s).recoverPublicKey(la(t).substring(2));return Sx(i)}var Hm="0x8010801080108010801080108010801080108010801080108010801080108010",Dm=Wi("(uint256 chainId, address delegation, uint256 nonce, uint8 yParity, uint256 r, uint256 s), address to, bytes data");function td(e){if(typeof e=="string"){if(ve(e,-32)!==Hm)throw new ed(e)}else zm(e.authorization)}function Ev(e){return typeof e=="string"?qx(e):e}function qx(e){td(e);let t=Ku(ve(e,-64,-32)),r=ve(e,-t-64,-64),n=ve(e,0,-t-64),[o,s,a]=Ia(Dm,r);return{authorization:Lx({address:o.delegation,chainId:Number(o.chainId),nonce:o.nonce,yParity:o.yParity,r:o.r,s:o.s}),signature:n,...a&&a!=="0x"?{data:a,to:s}:{}}}function Tv(e){let{data:t,signature:r}=e;td(e);let n=Vx({payload:jx(e.authorization),signature:Om(e.authorization)}),o=jn(Dm,[{...e.authorization,delegation:e.authorization.address,chainId:BigInt(e.authorization.chainId)},e.to??n,t??"0x"]),s=ue(ge(o),{size:32});return Ee(r,o,s,Hm)}function Av(e){try{return td(e),!0}catch{return!1}}var ed=class extends j{constructor(t){super(`Value \`${t}\` is an invalid ERC-8010 wrapped signature.`),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"SignatureErc8010.InvalidWrappedSignatureError"})}};J();var rd=class extends g{constructor({value:t}){super(`Number \`${t}\` is not a valid decimal number.`,{name:"InvalidDecimalNumberError"})}};function nd(e,t){if(!/^(-?)([0-9]*)\.?([0-9]*)$/.test(e))throw new rd({value:e});let[r,n="0"]=e.split("."),o=r.startsWith("-");if(o&&(r=r.slice(1)),n=n.replace(/(0+)$/,""),t===0)Math.round(+`.${n}`)===1&&(r=`${BigInt(r)+1n}`),n="";else if(n.length>t){let[s,a,i]=[n.slice(0,t-1),n.slice(t-1,t),n.slice(t)],c=Math.round(+`${a}.${i}`);c>9?n=`${BigInt(s)+BigInt(1)}0`.padStart(s.length+1,"0"):n=`${s}${c}`,n.length>t&&(n=n.slice(1),r=`${BigInt(r)+1n}`),n=n.slice(0,t)}else n=n.padEnd(t,"0");return BigInt(`${o?"-":""}${r}${n}`)}function Pv(e){return e.map(t=>({...t,value:BigInt(t.value)}))}function Gx(e){return{...e,balance:e.balance?BigInt(e.balance):void 0,nonce:e.nonce?ye(e.nonce):void 0,storageProof:e.storageProof?Pv(e.storageProof):void 0}}async function Wx(e,{address:t,blockHash:r,blockNumber:n,blockTag:o="latest",requireCanonical:s,storageKeys:a}){let i=Bt({blockHash:r,blockNumber:n,blockTag:o,requireCanonical:s}),c=await e.request({method:"eth_getProof",params:[t,a,i]});return Gx(c)}Pt();async function Zx(e,{hash:t}){let r=await e.request({method:"eth_getRawTransactionByHash",params:[t]},{dedupe:!0});if(!r)throw new wn({hash:t});return r}go();async function Kx(e,{address:t,blockHash:r,blockNumber:n,blockTag:o="latest",requireCanonical:s,slot:a}){let i=Bt({blockHash:r,blockNumber:n,blockTag:o,requireCanonical:s});return await e.request({method:"eth_getStorageAt",params:[t,a,i]})}Pt();Z();async function Ba(e,{blockHash:t,blockNumber:r,blockTag:n,hash:o,index:s,sender:a,nonce:i}){let c=n||"latest",u=r!==void 0?I(r):void 0,f=null;if(o?f=await e.request({method:"eth_getTransactionByHash",params:[o]},{dedupe:!0}):t?f=await e.request({method:"eth_getTransactionByBlockHashAndIndex",params:[t,I(s)]},{dedupe:!0}):(u||c)&&typeof s=="number"?f=await e.request({method:"eth_getTransactionByBlockNumberAndIndex",params:[u||c,I(s)]},{dedupe:!!u}):a&&typeof i=="number"&&(f=await e.request({method:"eth_getTransactionBySenderAndNonce",params:[a,I(i)]},{dedupe:!0})),!f)throw new wn({blockHash:t,blockNumber:r,blockTag:c,hash:o,index:s});return(e.chain?.formatters?.transaction?.format||Jr)(f,"getTransaction")}async function Yx(e,{hash:t,transactionReceipt:r}){let[n,o]=await Promise.all([B(e,Pr,"getBlockNumber")({}),t?B(e,Ba,"getTransaction")({hash:t}):void 0]),s=r?.blockNumber||o?.blockNumber;return s?n-s+1n:0n}Pt();async function ka(e,{hash:t}){let r=await e.request({method:"eth_getTransactionReceipt",params:[t]},{dedupe:!0});if(!r)throw new ls({hash:t});return(e.chain?.formatters?.transactionReceipt?.format||Io)(r,"getTransactionReceipt")}Ze();Oi();Ae();J();En();Qr();Xe();Po();async function Jx(e,t){let{account:r,authorizationList:n,allowFailure:o=!0,blockHash:s,blockNumber:a,blockOverrides:i,blockTag:c,requireCanonical:u,stateOverride:f}=t,d=t.contracts,p=typeof e.batch?.multicall=="object"?e.batch.multicall:{},m=t.batchSize??p.batchSize??1024,l=t.deployless??p.deployless??!1,h=(()=>{if(t.multicallAddress)return t.multicallAddress;if(l)return null;if(e.chain)return Lt({blockNumber:a,chain:e.chain,contract:"multicall3"});throw new Error("client chain not configured. multicallAddress is required.")})(),x=[[]],b=0,A=0;for(let w=0;w0&&A>m&&x[b].length>0&&(b++,A=(P.length-2)/2,x[b]=[]),x[b]=[...x[b],{allowFailure:!0,callData:P,target:T}]}catch(P){let z=St(P,{abi:E,address:T,args:S,docsPath:"/docs/contract/multicall",functionName:R,sender:r});if(!o)throw z;x[b]=[...x[b],{allowFailure:!0,callData:"0x",target:T}]}}let v=await Promise.allSettled(x.map(w=>B(e,fe,"readContract")({...h===null?{code:ya}:{address:h},abi:sr,account:r,args:[w],authorizationList:n,blockHash:s,blockNumber:a,blockOverrides:i,blockTag:c,functionName:"aggregate3",requireCanonical:u,stateOverride:f}))),y=[];for(let w=0;w{let b=x,A=b.account?K(b.account):void 0,v=b.abi?ie(b):b.data,y={...b,account:A,data:b.dataSuffix?Oe([v||"0x",b.dataSuffix]):v,from:b.from??A?.address};return He(y),nt(y)}),h=p.stateOverrides?Qs(p.stateOverrides):void 0;c.push({blockOverrides:m,calls:l,stateOverrides:h})}let f=(typeof r=="bigint"?I(r):void 0)||n;return(await e.request({method:"eth_simulateV1",params:[{blockStateCalls:c,returnFullTransactions:s,traceTransfers:a,validation:i},f]})).map((p,m)=>({...Bi(p),calls:p.calls.map((l,h)=>{let{abi:x,args:b,functionName:A,to:v}=o[m].calls[h],y=l.error?.data??l.returnData,w=BigInt(l.gasUsed),E=l.logs?.map(P=>je(P)),T=l.status==="0x1"?"success":"failure",S=x&&T==="success"&&y!=="0x"?ot({abi:x,data:y,functionName:A}):null,R=(()=>{if(T==="success")return;let P;if(y==="0x"?P=new Nt:y&&(P=new yr({data:y})),!!P)return St(P,{abi:x??[],address:v??"0x",args:b,functionName:A??""})})();return{data:y,gasUsed:w,logs:E,status:T,...T==="success"?{result:S}:{error:R}}})}))}catch(c){let u=c,f=Nn(u,{});throw f instanceof Ht?u:f}}ri();vt();Ve();vt();function sd(e){let t=!0,r="",n=0,o="",s=!1;for(let a=0;aod(Object.values(e)[s],o)):/^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/.test(n)?r==="number"||r==="bigint":/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/.test(n)?r==="string"||e instanceof Uint8Array:/[a-z]+[1-9]{0,3}(\[[0-9]{0,}\])+$/.test(n)?Array.isArray(e)&&e.every(o=>od(o,{...t,type:n.replace(/(\[[0-9]{0,}\])$/,"")})):!1}}function Um(e,t,r){for(let n in e){let o=e[n],s=t[n];if(o.type==="tuple"&&s.type==="tuple"&&"components"in o&&"components"in s)return Um(o.components,s.components,r[n]);let a=[o.type,s.type];if(a.includes("address")&&a.includes("bytes20")?!0:a.includes("address")&&a.includes("string")?qf(r[n],{strict:!1}):a.includes("address")&&a.includes("bytes")?qf(r[n],{strict:!1}):!1)return a}}function ad(e,t={}){let{prepare:r=!0}=t,n=Array.isArray(e)?Sc(e):typeof e=="string"?Sc(e):e;return{...n,...r?{hash:Ca(n)}:{}}}function Xi(e,t,r){let{args:n=[],prepare:o=!0}=r??{},s=zi(t,{strict:!1}),a=e.filter(u=>s?u.type==="function"||u.type==="error"?jm(u)===ve(t,0,4):u.type==="event"?Ca(u)===t:!1:"name"in u&&u.name===t);if(a.length===0)throw new Vn({name:t});if(a.length===1)return{...a[0],...o?{hash:Ca(a[0])}:{}};let i;for(let u of a){if(!("inputs"in u))continue;if(!n||n.length===0){if(!u.inputs||u.inputs.length===0)return{...u,...o?{hash:Ca(u)}:{}};continue}if(!u.inputs||u.inputs.length===0||u.inputs.length!==n.length)continue;if(n.every((d,p)=>{let m="inputs"in u&&u.inputs[p];return m?od(d,m):!1})){if(i&&"inputs"in i&&i.inputs){let d=Um(u.inputs,i.inputs,n);if(d)throw new Lm({abiItem:u,type:d[0]},{abiItem:i,type:d[1]})}i=u}}let c=(()=>{if(i)return i;let[u,...f]=a;return{...u,overloads:f}})();if(!c)throw new Vn({name:t});return{...c,...o?{hash:Ca(c)}:{}}}function jm(...e){let t=(()=>{if(Array.isArray(e[0])){let[r,n]=e;return Xi(r,n)}return e[0]})();return ve(Ca(t),0,4)}function _v(...e){let t=(()=>{if(Array.isArray(e[0])){let[n,o]=e;return Xi(n,o)}return e[0]})(),r=typeof t=="string"?t:Xn(t);return sd(r)}function Ca(...e){let t=(()=>{if(Array.isArray(e[0])){let[r,n]=e;return Xi(r,n)}return e[0]})();return typeof t!="string"&&"hash"in t&&t.hash?t.hash:ko(ha(_v(t)))}var Lm=class extends j{constructor(t,r){super("Found ambiguous types in overloaded ABI Items.",{metaMessages:[`\`${t.type}\` in \`${sd(Xn(t.abiItem))}\`, and`,`\`${r.type}\` in \`${sd(Xn(r.abiItem))}\``,"","These types encode differently and cannot be distinguished at runtime.","Remove one of the ambiguous items in the ABI."]}),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"AbiItem.AmbiguityError"})}},Vn=class extends j{constructor({name:t,data:r,type:n="item"}){let o=t?` with name "${t}"`:r?` with data "${r}"`:"";super(`ABI ${n}${o} not found.`),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"AbiItem.NotFoundError"})}};Ve();function Qx(...e){let[t,r]=(()=>{if(Array.isArray(e[0])){let[s,a]=e;return[Iv(s),a]}return e})(),{bytecode:n,args:o}=r;return Ee(n,t.inputs?.length&&o?.length?jn(t.inputs,o):"0x")}function eb(e){return ad(e)}function Iv(e){let t=e.find(r=>r.type==="constructor");if(!t)throw new Vn({name:"constructor"});return t}Ve();function rb(...e){let[t,r=[]]=(()=>{if(Array.isArray(e[0])){let[u,f,d]=e;return[tb(u,f,{args:d}),d]}let[i,c]=e;return[i,c]})(),{overloads:n}=t,o=n?tb([t,...n],t.name,{args:r}):t,s=kv(o),a=r.length>0?jn(o.inputs,r):void 0;return a?Ee(s,a):s}function No(e,t={}){return ad(e,t)}function tb(e,t,r){let n=Xi(e,t,r);if(n.type!=="function")throw new Vn({name:t,type:"function"});return n}function kv(e){return jm(e)}be();var nb="0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",Ct="0x0000000000000000000000000000000000000000";Oi();J();Xe();var Rv="0x6080604052348015600e575f80fd5b5061016d8061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c8063f8b2cb4f1461002d575b5f80fd5b610047600480360381019061004291906100db565b61005d565b604051610054919061011e565b60405180910390f35b5f8173ffffffffffffffffffffffffffffffffffffffff16319050919050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100aa82610081565b9050919050565b6100ba816100a0565b81146100c4575f80fd5b50565b5f813590506100d5816100b1565b92915050565b5f602082840312156100f0576100ef61007d565b5b5f6100fd848285016100c7565b91505092915050565b5f819050919050565b61011881610106565b82525050565b5f6020820190506101315f83018461010f565b9291505056fea26469706673582212203b9fe929fe995c7cf9887f0bdba8a36dd78e8b73f149b17d2d9ad7cd09d2dc6264736f6c634300081a0033";async function ob(e,t){let{blockNumber:r,blockTag:n,calls:o,stateOverrides:s,traceAssetChanges:a,traceTransfers:i,validation:c}=t,u=t.account?K(t.account):void 0;if(a&&!u)throw new g("`account` is required when `traceAssetChanges` is true");let f=u?Qx(eb("constructor(bytes, bytes)"),{bytecode:rf,args:[Rv,rb(No("function getBalance(address)"),[u.address])]}):void 0,d=a?await Promise.all(t.calls.map(async M=>{if(!M.data&&!M.abi)return;let{accessList:L}=await _f(e,{account:u.address,...M,data:M.abi?ie(M):M.data});return L.map(({address:W,storageKeys:se})=>se.length>0?W:null)})).then(M=>M.flat().filter(Boolean)):[],p=await Ji(e,{blockNumber:r,blockTag:n,blocks:[...a?[{calls:[{data:f}],stateOverrides:s},{calls:d.map((M,L)=>({abi:[No("function balanceOf(address) returns (uint256)")],functionName:"balanceOf",args:[u.address],to:M,from:Ct,nonce:L})),stateOverrides:[{address:Ct,nonce:0}]}]:[],{calls:[...o,{to:Ct}].map(M=>({...M,from:u?.address})),stateOverrides:s},...a?[{calls:[{data:f}]},{calls:d.map((M,L)=>({abi:[No("function balanceOf(address) returns (uint256)")],functionName:"balanceOf",args:[u.address],to:M,from:Ct,nonce:L})),stateOverrides:[{address:Ct,nonce:0}]},{calls:d.map((M,L)=>({to:M,abi:[No("function decimals() returns (uint256)")],functionName:"decimals",from:Ct,nonce:L})),stateOverrides:[{address:Ct,nonce:0}]},{calls:d.map((M,L)=>({to:M,abi:[No("function tokenURI(uint256) returns (string)")],functionName:"tokenURI",args:[0n],from:Ct,nonce:L})),stateOverrides:[{address:Ct,nonce:0}]},{calls:d.map((M,L)=>({to:M,abi:[No("function symbol() returns (string)")],functionName:"symbol",from:Ct,nonce:L})),stateOverrides:[{address:Ct,nonce:0}]}]:[]],traceTransfers:i,validation:c}),m=a?p[2]:p[0],[l,h,,x,b,A,v,y]=a?p:[],{calls:w,...E}=m,T=w.slice(0,-1)??[],S=l?.calls??[],R=h?.calls??[],P=[...S,...R].map(M=>M.status==="success"?pe(M.data):null),z=x?.calls??[],H=b?.calls??[],O=[...z,...H].map(M=>M.status==="success"?pe(M.data):null),q=(A?.calls??[]).map(M=>M.status==="success"?M.result:null),k=(y?.calls??[]).map(M=>M.status==="success"?M.result:null),C=(v?.calls??[]).map(M=>M.status==="success"?M.result:null),D=[];for(let[M,L]of O.entries()){let W=P[M];if(typeof L!="bigint"||typeof W!="bigint")continue;let se=q[M-1],Re=k[M-1],ft=C[M-1],he=M===0?{address:nb,decimals:18,symbol:"ETH"}:{address:d[M-1],decimals:ft||se?Number(se??1):void 0,symbol:Re??void 0};D.some(Be=>Be.token.address===he.address)||D.push({token:he,value:{pre:W,post:L,diff:L-W}})}return{assetChanges:D,block:E,results:T}}var Qi={};Qa(Qi,{InvalidWrappedSignatureError:()=>id,assert:()=>qm,from:()=>zv,magicBytes:()=>Vm,universalSignatureValidatorAbi:()=>Mv,universalSignatureValidatorBytecode:()=>Nv,unwrap:()=>sb,validate:()=>Ov,wrap:()=>Fv});vt();Ve();var Vm="0x6492649264926492649264926492649264926492649264926492649264926492",Nv="0x608060405234801561001057600080fd5b5060405161069438038061069483398101604081905261002f9161051e565b600061003c848484610048565b9050806000526001601ff35b60007f64926492649264926492649264926492649264926492649264926492649264926100748361040c565b036101e7576000606080848060200190518101906100929190610577565b60405192955090935091506000906001600160a01b038516906100b69085906105dd565b6000604051808303816000865af19150503d80600081146100f3576040519150601f19603f3d011682016040523d82523d6000602084013e6100f8565b606091505b50509050876001600160a01b03163b60000361016057806101605760405162461bcd60e51b815260206004820152601e60248201527f5369676e617475726556616c696461746f723a206465706c6f796d656e74000060448201526064015b60405180910390fd5b604051630b135d3f60e11b808252906001600160a01b038a1690631626ba7e90610190908b9087906004016105f9565b602060405180830381865afa1580156101ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d19190610633565b6001600160e01b03191614945050505050610405565b6001600160a01b0384163b1561027a57604051630b135d3f60e11b808252906001600160a01b03861690631626ba7e9061022790879087906004016105f9565b602060405180830381865afa158015610244573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102689190610633565b6001600160e01b031916149050610405565b81516041146102df5760405162461bcd60e51b815260206004820152603a602482015260008051602061067483398151915260448201527f3a20696e76616c6964207369676e6174757265206c656e6774680000000000006064820152608401610157565b6102e7610425565b5060208201516040808401518451859392600091859190811061030c5761030c61065d565b016020015160f81c9050601b811480159061032b57508060ff16601c14155b1561038c5760405162461bcd60e51b815260206004820152603b602482015260008051602061067483398151915260448201527f3a20696e76616c6964207369676e617475726520762076616c756500000000006064820152608401610157565b60408051600081526020810180835289905260ff83169181019190915260608101849052608081018390526001600160a01b0389169060019060a0016020604051602081039080840390855afa1580156103ea573d6000803e3d6000fd5b505050602060405103516001600160a01b0316149450505050505b9392505050565b600060208251101561041d57600080fd5b508051015190565b60405180606001604052806003906020820280368337509192915050565b6001600160a01b038116811461045857600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561048c578181015183820152602001610474565b50506000910152565b600082601f8301126104a657600080fd5b81516001600160401b038111156104bf576104bf61045b565b604051601f8201601f19908116603f011681016001600160401b03811182821017156104ed576104ed61045b565b60405281815283820160200185101561050557600080fd5b610516826020830160208701610471565b949350505050565b60008060006060848603121561053357600080fd5b835161053e81610443565b6020850151604086015191945092506001600160401b0381111561056157600080fd5b61056d86828701610495565b9150509250925092565b60008060006060848603121561058c57600080fd5b835161059781610443565b60208501519093506001600160401b038111156105b357600080fd5b6105bf86828701610495565b604086015190935090506001600160401b0381111561056157600080fd5b600082516105ef818460208701610471565b9190910192915050565b828152604060208201526000825180604084015261061e816060850160208701610471565b601f01601f1916919091016060019392505050565b60006020828403121561064557600080fd5b81516001600160e01b03198116811461040557600080fd5b634e487b7160e01b600052603260045260246000fdfe5369676e617475726556616c696461746f72237265636f7665725369676e6572",Mv=[{inputs:[{name:"_signer",type:"address"},{name:"_hash",type:"bytes32"},{name:"_signature",type:"bytes"}],stateMutability:"nonpayable",type:"constructor"},{inputs:[{name:"_signer",type:"address"},{name:"_hash",type:"bytes32"},{name:"_signature",type:"bytes"}],outputs:[{type:"bool"}],stateMutability:"nonpayable",type:"function",name:"isValidSig"}];function qm(e){if(ve(e,-32)!==Vm)throw new id(e)}function zv(e){return typeof e=="string"?sb(e):e}function sb(e){qm(e);let[t,r,n]=Ia(Wi("address, bytes, bytes"),e);return{data:r,signature:n,to:t}}function Fv(e){let{data:t,signature:r,to:n}=e;return Ee(jn(Wi("address, bytes, bytes"),[n,t,r]),Vm)}function Ov(e){try{return qm(e),!0}catch{return!1}}var id=class extends j{constructor(t){super(`Value \`${t}\` is an invalid ERC-6492 wrapped signature.`),Object.defineProperty(this,"name",{enumerable:!0,configurable:!0,writable:!0,value:"SignatureErc6492.InvalidWrappedSignatureError"})}};Ze();Oi();En();sf();Xe();tr();Xr();qe();Rt();ze();Z();js();ze();Fe();function cd({r:e,s:t,to:r="hex",v:n,yParity:o}){let s=(()=>{if(o===0||o===1)return o;if(n&&(n===27n||n===28n||n>=35n))return n%2n===0n?1:0;throw new Error("Invalid `v` or `yParity` value")})(),a=`0x${new It.Signature(pe(e),pe(t)).toCompactHex()}${s===0?"1b":"1c"}`;return r==="hex"?a:ke(a)}So();async function qn(e,t){let{address:r,chain:n=e.chain,hash:o,erc6492VerifierAddress:s=t.universalSignatureVerifierAddress??n?.contracts?.erc6492Verifier?.address,multicallAddress:a=t.multicallAddress??n?.contracts?.multicall3?.address,mode:i="auto"}=t;if(n?.verifyHash)return await n.verifyHash(e,t);let c=(()=>{let u=t.signature;return we(u)?u:typeof u=="object"&&"r"in u&&"s"in u?cd(u):ae(u)})();try{if(i==="eoa")try{if(Le(de(r),await _i({hash:o,signature:c})))return!0}catch{}return Yi.validate(c)?await $v(e,{...t,multicallAddress:a,signature:c}):await Hv(e,{...t,verifierAddress:s,signature:c})}catch(u){if(i!=="eoa")try{if(Le(de(r),await _i({hash:o,signature:c})))return!0}catch{}if(u instanceof nn)return!1;throw u}}async function $v(e,t){let{address:r,blockHash:n,blockNumber:o,blockTag:s,hash:a,multicallAddress:i,requireCanonical:c}=t,{authorization:u,data:f,signature:d,to:p}=Yi.unwrap(t.signature);if(await Bo(e,{address:r,blockHash:n,blockNumber:o,blockTag:s,requireCanonical:c})===xe(["0xef0100",u.address]))return await Dv(e,{...t,signature:d});let l={address:u.address,chainId:Number(u.chainId),nonce:Number(u.nonce),r:I(u.r,{size:32}),s:I(u.s,{size:32}),yParity:u.yParity};if(!await mx({address:r,authorization:l}))throw new nn;let x=await B(e,fe,"readContract")({...i?{address:i}:{code:ya},authorizationList:[l],abi:sr,blockHash:n,blockNumber:o,blockTag:"pending",functionName:"aggregate3",requireCanonical:c,args:[[...f?[{allowFailure:!0,target:p??r,callData:f}]:[],{allowFailure:!0,target:r,callData:ie({abi:Yp,functionName:"isValidSignature",args:[a,d]})}]]});if(x[x.length-1]?.returnData?.startsWith("0x1626ba7e"))return!0;throw new nn}async function Hv(e,t){let{address:r,factory:n,factoryData:o,hash:s,signature:a,verifierAddress:i,...c}=t,u=await(async()=>!n&&!o||Qi.validate(a)?a:Qi.wrap({data:o,signature:a,to:n}))(),f=i?{to:i,data:ie({abi:Fi,functionName:"isValidSig",args:[r,s,u]}),...c}:{data:Ao({abi:Fi,args:[r,s,u],bytecode:Ey}),...c},{data:d}=await B(e,jt,"call")(f).catch(p=>{throw p instanceof hs?new nn:p});if(Ld(d??"0x0"))return!0;throw new nn}async function Dv(e,t){let{address:r,blockHash:n,blockNumber:o,blockTag:s,hash:a,requireCanonical:i,signature:c}=t;if((await B(e,fe,"readContract")({address:r,abi:Yp,args:[a,c],blockHash:n,blockNumber:o,blockTag:s,functionName:"isValidSignature",requireCanonical:i}).catch(f=>{throw f instanceof ys?new nn:f})).startsWith("0x1626ba7e"))return!0;throw new nn}var nn=class extends Error{};async function ab(e,{address:t,message:r,factory:n,factoryData:o,signature:s,...a}){let i=Sa(r);return B(e,qn,"verifyHash")({address:t,factory:n,factoryData:o,hash:i,signature:s,...a})}async function ib(e,t){let{address:r,factory:n,factoryData:o,signature:s,message:a,primaryType:i,types:c,domain:u,...f}=t,d=Hf({message:a,primaryType:i,types:c,domain:u});return B(e,qn,"verifyHash")({address:r,factory:n,factoryData:o,hash:d,signature:s,...f})}Pt();cf();Qe();ze();Qe();function ud(e,{emitOnBegin:t=!1,emitMissed:r=!1,onBlockNumber:n,onError:o,poll:s,pollingInterval:a=e.pollingInterval}){let i=typeof s<"u"?s:!(e.transport.type==="webSocket"||e.transport.type==="ipc"||e.transport.type==="fallback"&&(e.transport.transports[0].config.type==="webSocket"||e.transport.transports[0].config.type==="ipc")),c;return i?(()=>{let d=ne(["watchBlockNumber",e.uid,t,r,a]);return st(d,{onBlockNumber:n,onError:o},p=>Vt(async()=>{try{let m=await B(e,Pr,"getBlockNumber")({cacheTime:0});if(c!==void 0){if(m===c)return;if(m-c>1&&r)for(let l=c+1n;lc)&&(p.onBlockNumber(m,c),c=m)}catch(m){p.onError?.(m)}},{emitOnBegin:t,interval:a}))})():(()=>{let d=ne(["watchBlockNumber",e.uid,t,r]);return st(d,{onBlockNumber:n,onError:o},p=>{let m=!0,l=()=>m=!1;return(async()=>{try{let h=(()=>{if(e.transport.type==="fallback"){let b=e.transport.transports.find(A=>A.config.type==="webSocket"||A.config.type==="ipc");return b?b.value:e.transport}return e.transport})(),{unsubscribe:x}=await h.subscribe({params:["newHeads"],onData(b){if(!m)return;let A=pe(b.result?.number);p.onBlockNumber(A,c),c=A},onError(b){p.onError?.(b)}});l=x,m||l()}catch(h){o?.(h)}})(),()=>l()})})()}async function fd(e,t){let{checkReplacement:r=e.chain?.supportsTransactionReplacementDetection??!0,confirmations:n=1,hash:o,onReplaced:s,retryCount:a=6,retryDelay:i=({count:w})=>~~(1<{x?.(),h?.(),v(new yu({hash:o}))},c):void 0;return h=st(u,{onReplaced:s,resolve:A,reject:v},async w=>{if(m=await B(e,ka,"getTransactionReceipt")({hash:o}).catch(()=>{}),m&&n<=1){clearTimeout(y),w.resolve(m),h?.();return}x=B(e,ud,"watchBlockNumber")({emitMissed:!0,emitOnBegin:!0,poll:!0,pollingInterval:f,async onBlockNumber(E){let T=R=>{clearTimeout(y),x?.(),R(),h?.()},S=E;if(!l)try{if(m){if(n>1&&(!m.blockNumber||S-m.blockNumber+1nw.resolve(m));return}if(r&&!d&&(l=!0,await _o(async()=>{d=await B(e,Ba,"getTransaction")({hash:o}),d.blockNumber&&(S=d.blockNumber)},{delay:i,retryCount:a}),l=!1),m=await B(e,ka,"getTransactionReceipt")({hash:o}),n>1&&(!m.blockNumber||S-m.blockNumber+1nw.resolve(m))}catch(R){if(R instanceof wn||R instanceof ls){if(!d){l=!1;return}try{p=d,l=!0;let P=await _o(()=>B(e,De,"getBlock")({blockNumber:S,includeTransactions:!0}),{delay:i,retryCount:a,shouldRetry:({error:O})=>O instanceof zn});l=!1;let z=P.transactions.find(({from:O,nonce:q})=>O===p.from&&q===p.nonce);if(!z||(m=await B(e,ka,"getTransactionReceipt")({hash:z.hash}),n>1&&(!m.blockNumber||S-m.blockNumber+1n{w.onReplaced?.({reason:H,replacedTransaction:p,transaction:z,transactionReceipt:m}),w.resolve(m)})}catch(P){T(()=>w.reject(P))}}else T(()=>w.reject(R))}}})}),b}Qe();function cb(e,{blockTag:t=e.experimental_blockTag??"latest",emitMissed:r=!1,emitOnBegin:n=!1,onBlock:o,onError:s,includeTransactions:a,poll:i,pollingInterval:c=e.pollingInterval}){let u=typeof i<"u"?i:!(e.transport.type==="webSocket"||e.transport.type==="ipc"||e.transport.type==="fallback"&&(e.transport.transports[0].config.type==="webSocket"||e.transport.transports[0].config.type==="ipc")),f=a??!1,d;return u?(()=>{let l=ne(["watchBlocks",e.uid,t,r,n,f,c]);return st(l,{onBlock:o,onError:s},h=>Vt(async()=>{try{let x=await B(e,De,"getBlock")({blockTag:t,includeTransactions:f});if(x.number!==null&&d?.number!=null){if(x.number===d.number)return;if(x.number-d.number>1&&r)for(let b=d?.number+1n;bd.number)&&(h.onBlock(x,d),d=x)}catch(x){h.onError?.(x)}},{emitOnBegin:n,interval:c}))})():(()=>{let l=!0,h=!0,x=()=>l=!1;return(async()=>{try{n&&B(e,De,"getBlock")({blockTag:t,includeTransactions:f}).then(v=>{l&&h&&(o(v,void 0),h=!1)}).catch(s);let b=(()=>{if(e.transport.type==="fallback"){let v=e.transport.transports.find(y=>y.config.type==="webSocket"||y.config.type==="ipc");return v?v.value:e.transport}return e.transport})(),{unsubscribe:A}=await b.subscribe({params:["newHeads"],async onData(v){if(!l)return;let y=await B(e,De,"getBlock")({blockNumber:v.result?.number,includeTransactions:f}).catch(()=>{});l&&(o(y,d),h=!1,d=y)},onError(v){s?.(v)}});x=A,l||x()}catch(b){s?.(b)}})(),()=>x()})()}Ae();Fs();Qe();function ub(e,{address:t,args:r,batch:n=!0,event:o,events:s,fromBlock:a,onError:i,onLogs:c,poll:u,pollingInterval:f=e.pollingInterval,strict:d}){let p=typeof u<"u"?u:typeof a=="bigint"?!0:!(e.transport.type==="webSocket"||e.transport.type==="ipc"||e.transport.type==="fallback"&&(e.transport.transports[0].config.type==="webSocket"||e.transport.transports[0].config.type==="ipc")),m=d??!1;return p?(()=>{let x=ne(["watchEvent",t,r,n,e.uid,o,f,a]);return st(x,{onLogs:c,onError:i},b=>{let A;a!==void 0&&(A=a-1n);let v,y=!1,w=Vt(async()=>{if(!y){try{v=await B(e,If,"createEventFilter")({address:t,args:r,event:o,events:s,strict:m,fromBlock:a})}catch{}y=!0;return}try{let E;if(v)E=await B(e,Hn,"getFilterChanges")({filter:v});else{let T=await B(e,Pr,"getBlockNumber")({});A&&A!==T?E=await B(e,ua,"getLogs")({address:t,args:r,event:o,events:s,fromBlock:A+1n,toBlock:T}):E=[],A=T}if(E.length===0)return;if(n)b.onLogs(E);else for(let T of E)b.onLogs([T])}catch(E){v&&E instanceof Ot&&(y=!1),b.onError?.(E)}},{emitOnBegin:!0,interval:f});return async()=>{v&&await B(e,Dn,"uninstallFilter")({filter:v}),w()}})})():(()=>{let x=!0,b=()=>x=!1;return(async()=>{try{let A=(()=>{if(e.transport.type==="fallback"){let E=e.transport.transports.find(T=>T.config.type==="webSocket"||T.config.type==="ipc");return E?E.value:e.transport}return e.transport})(),v=s??(o?[o]:void 0),y=[];v&&(y=[v.flatMap(T=>lr({abi:[T],eventName:T.name,args:r}))],o&&(y=y[0]));let{unsubscribe:w}=await A.subscribe({params:["logs",{address:t,topics:y}],onData(E){if(!x)return;let T=E.result;try{let{eventName:S,args:R}=vo({abi:v??[],data:T.data,topics:T.topics,strict:m}),P=je(T,{args:R,eventName:S});c([P])}catch(S){let R,P;if(S instanceof Mr||S instanceof mn){if(d)return;R=S.abiItem.name,P=S.abiItem.inputs?.some(H=>!("name"in H&&H.name))}let z=je(T,{args:P?[]:{},eventName:R});c([z])}},onError(E){i?.(E)}});b=w,x||b()}catch(A){i?.(A)}})(),()=>b()})()}Qe();function fb(e,{batch:t=!0,onError:r,onTransactions:n,poll:o,pollingInterval:s=e.pollingInterval}){return(typeof o<"u"?o:e.transport.type!=="webSocket"&&e.transport.type!=="ipc")?(()=>{let u=ne(["watchPendingTransactions",e.uid,t,s]);return st(u,{onTransactions:n,onError:r},f=>{let d,p=Vt(async()=>{try{if(!d)try{d=await B(e,Bf,"createPendingTransactionFilter")({});return}catch(l){throw p(),l}let m=await B(e,Hn,"getFilterChanges")({filter:d});if(m.length===0)return;if(t)f.onTransactions(m);else for(let l of m)f.onTransactions([l])}catch(m){f.onError?.(m)}},{emitOnBegin:!0,interval:s});return async()=>{d&&await B(e,Dn,"uninstallFilter")({filter:d}),p()}})})():(()=>{let u=!0,f=()=>u=!1;return(async()=>{try{let{unsubscribe:d}=await e.transport.subscribe({params:["newPendingTransactions"],onData(p){if(!u)return;let m=p.result;n([m])},onError(p){r?.(p)}});f=d,u||f()}catch(d){r?.(d)}})(),()=>f()})()}function db(e){let{scheme:t,statement:r,...n}=e.match(Uv)?.groups??{},{chainId:o,expirationTime:s,issuedAt:a,notBefore:i,requestId:c,...u}=e.match(Lv)?.groups??{},f=e.split("Resources:")[1]?.split(`
+- `).slice(1);return{...n,...u,...o?{chainId:Number(o)}:{},...s?{expirationTime:new Date(s)}:{},...a?{issuedAt:new Date(a)}:{},...i?{notBefore:new Date(i)}:{},...c?{requestId:c}:{},...f?{resources:f}:{},...t?{scheme:t}:{},...r?{statement:r}:{}}}var Uv=/^(?:(?[a-zA-Z][a-zA-Z0-9+-.]*):\/\/)?(?[a-zA-Z0-9+-.]*(?::[0-9]{1,5})?) (?:wants you to sign in with your Ethereum account:\n)(?
0x[a-fA-F0-9]{40})\n\n(?:(?.*)\n\n)?/,Lv=/(?:URI: (?.+))\n(?:Version: (?.+))\n(?:Chain ID: (?\d+))\n(?:Nonce: (?[a-zA-Z0-9]+))\n(?:Issued At: (?.+))(?:\nExpiration Time: (?.+))?(?:\nNot Before: (?.+))?(?:\nRequest ID: (?.+))?/;ht();Xr();function pb(e){let{address:t,domain:r,message:n,nonce:o,scheme:s,time:a=new Date}=e;if(r&&n.domain!==r||o&&n.nonce!==o||s&&n.scheme!==s||n.expirationTime&&a>=n.expirationTime||n.notBefore&&a"u")throw new Ie({docsPath:"/docs/actions/wallet/sendTransactionSync"});let E=r?K(r):null,T;try{He(t);let S=await(async()=>{if(t.to)return t.to;if(t.to!==null&&a&&a.length>0)return await Cn({authorization:a[0]}).catch(()=>{throw new g("`to` is required. Could not infer from `authorizationList`.")})})();if(E?.type==="json-rpc"||E===null){let R;o!==null&&(R=await B(e,Ue,"getChainId")({}),n&&ga({currentChainId:R,chain:o}));let P=e.chain?.formatters?.transactionRequest?.format,H=(P||nt)({...Dt(y,{format:P}),accessList:s,account:E,authorizationList:a,blobs:i,chainId:R,data:u?Oe([c??"0x",u]):c,gas:f,gasPrice:d,maxFeePerBlobGas:p,maxFeePerGas:m,maxPriorityFeePerGas:l,nonce:h,to:S,type:A,value:v},"sendTransaction"),O=Zm.get(e.uid),q=O?"wallet_sendTransaction":"eth_sendTransaction",k=await(async()=>{try{return await e.request({method:q,params:[H]},{retryCount:0})}catch(D){if(O===!1)throw D;let M=D;if(M.name==="InvalidInputRpcError"||M.name==="InvalidParamsRpcError"||M.name==="MethodNotFoundRpcError"||M.name==="MethodNotSupportedRpcError")return await e.request({method:"wallet_sendTransaction",params:[H]},{retryCount:0}).then(L=>(Zm.set(e.uid,!0),L)).catch(L=>{let W=L;throw W.name==="MethodNotFoundRpcError"||W.name==="MethodNotSupportedRpcError"?(Zm.set(e.uid,!1),M):W});throw M}})(),C=await B(e,fd,"waitForTransactionReceipt")({checkReplacement:!1,hash:k,pollingInterval:x,timeout:w});if(b&&C.status==="reverted")throw new so({receipt:C});return C}if(E?.type==="local"){if(E.nonceManager&&typeof h>"u"){let H=y.chainId,O=await(async()=>typeof H=="number"?H:o?o.id:B(e,Ue,"getChainId")({}))();T={address:E.address,chainId:O}}let R=await B(e,wr,"prepareTransactionRequest")({account:E,accessList:s,authorizationList:a,blobs:i,chain:o,data:u?Oe([c??"0x",u]):c,gas:f,gasPrice:d,maxFeePerBlobGas:p,maxFeePerGas:m,maxPriorityFeePerGas:l,nonce:h,nonceManager:E.nonceManager,parameters:[...ki,"sidecars"],type:A,value:v,...y,to:S}),P=o?.serializers?.transaction,z=await E.signTransaction(R,{serializer:P});return await B(e,za,"sendRawTransactionSync")({serializedTransaction:z,throwOnReceiptRevert:b,timeout:t.timeout})}throw E?.type==="smart"?new qt({metaMessages:["Consider using the `sendUserOperation` Action instead."],docsPath:"/docs/actions/bundler/sendUserOperation",type:"smart"}):new qt({docsPath:"/docs/actions/wallet/sendTransactionSync",type:E?.type})}catch(S){throw S instanceof qt?S:(T&&!(S instanceof so)&&E?.nonceManager?.reset(T),Fn(S,{...t,account:E,chain:t.chain||void 0}))}}async function Fa(e,t){return ar.internal(e,md,"sendTransactionSync",t)}async function Km(e,t){let{amount:r,token:n,throwOnReceiptRevert:o=!0}=t,{decimals:s}=Ke(e,{token:n}),a=pd(r,s),i=await on.inner(Fa,e,{...t,throwOnReceiptRevert:o}),{args:c}=on.extractEvent(i.logs);return{...c,...a===void 0?{}:{decimals:a,formatted:zt(c.value,a)},receipt:i}}Ze();async function Oa(e,t){let{account:r,decimals:n,spender:o,token:s,...a}=t,[i,{decimals:c}]=await Promise.all([fe(e,{...a,...Oa.call(e,{account:r,spender:o,token:s})}),Na(e,{decimals:n,token:s})]);return Ra(i,c)}(function(e){function t(r,n){return _r({address:Ke(r,n).address,abi:_e,functionName:"allowance",args:[n.account,n.spender]})}e.call=t})(Oa||(Oa={}));be();Ze();async function $a(e,t){let{account:r=e.account,decimals:n,token:o,...s}=t;if(!r)throw new Ie;let a=K(r).address,[i,{decimals:c}]=await Promise.all([fe(e,{...s,...$a.call(e,{account:a,token:o})}),Na(e,{decimals:n,token:o})]);return Ra(i,c)}(function(e){function t(r,n){let o=n.account??r.account;if(!o)throw new Ie;let s=K(o).address;return _r({address:Ke(r,n).address,abi:_e,functionName:"balanceOf",args:[s]})}e.call=t})($a||($a={}));Ze();async function Ym(e,t){let{token:r,...n}=t,{address:o}=Ke(e,{token:r}),s=Wm(e,r),[a,i,c]=await Promise.all([s?.decimals??fe(e,{...n,abi:_e,address:o,functionName:"decimals"}),s?.name??fe(e,{...n,abi:_e,address:o,functionName:"name"}),s?.symbol??fe(e,{...n,abi:_e,address:o,functionName:"symbol"})]);return{decimals:a,name:i,symbol:c}}Ze();async function Ha(e,t){let{decimals:r,token:n,...o}=t,[s,{decimals:a}]=await Promise.all([fe(e,{...o,...Ha.call(e,{token:n})}),Na(e,{decimals:r,token:n})]);return Ra(s,a)}(function(e){function t(r,n){return _r({address:Ke(r,n).address,abi:_e,args:[],functionName:"totalSupply"})}e.call=t})(Ha||(Ha={}));Ze();async function sn(e,t){return sn.inner(ar,e,t)}(function(e){async function t(a,i,c){return await a(i,{...c,...e.call(i,c)})}e.inner=t;function r(a,i){return _r(Wv(a,i))}e.call=r;async function n(a,i){return ca(a,{...Ma(i),...e.call(a,i)})}e.estimateGas=n;async function o(a,i){return ba(a,{...Ma(i),...e.call(a,i)})}e.simulate=o;function s(a){let[i]=Er({abi:_e,logs:a,eventName:"Transfer",strict:!0});if(!i)throw new Error("`Transfer` event not found.");return i}e.extractEvent=s})(sn||(sn={}));function Wv(e,t){let{amount:r,from:n,to:o,token:s}=t,{address:a,decimals:i}=Ke(e,{token:s}),c=dd(r,i);return n?{abi:_e,address:a,args:[n,o,c],functionName:"transferFrom"}:{abi:_e,address:a,args:[o,c],functionName:"transfer"}}oo();async function Jm(e,t){let{amount:r,token:n,throwOnReceiptRevert:o=!0}=t,{decimals:s}=Ke(e,{token:n}),a=pd(r,s),i=await sn.inner(Fa,e,{...t,throwOnReceiptRevert:o}),{args:c}=sn.extractEvent(i.logs);return{...c,...a===void 0?{}:{decimals:a,formatted:zt(c.value,a)},receipt:i}}function lb(e){return{call:t=>jt(e,t),createAccessList:t=>_f(e,t),createBlockFilter:()=>Xy(e),createContractEventFilter:t=>nu(e,t),createEventFilter:t=>If(e,t),createPendingTransactionFilter:()=>Bf(e),estimateContractGas:t=>ca(e,t),estimateGas:t=>ia(e,t),getBalance:t=>Qy(e,t),getBlobBaseFee:()=>ex(e),getBlock:t=>De(e,t),getBlockNumber:t=>Pr(e,t),getBlockReceipts:t=>tx(e,t),getBlockTransactionCount:t=>rx(e,t),getBytecode:t=>Bo(e,t),getChainId:()=>Ue(e),getCode:t=>Bo(e,t),getContractEvents:t=>ju(e,t),getDelegation:t=>nx(e,t),getEip712Domain:t=>ox(e,t),getEnsAddress:t=>jy(e,t),getEnsAvatar:t=>Ky(e,t),getEnsName:t=>Yy(e,t),getEnsResolver:t=>Jy(e,t),getEnsText:t=>Sf(e,t),getFeeHistory:t=>ax(e,t),estimateFeesPerGas:t=>Uh(e,t),getFilterChanges:t=>Hn(e,t),getFilterLogs:t=>ix(e,t),getGasPrice:()=>ta(e),getLogs:t=>ua(e,t),getProof:t=>Wx(e,t),estimateMaxPriorityFeePerGas:t=>Dh(e,t),fillTransaction:t=>aa(e,t),getRawTransaction:t=>Zx(e,t),getStorageAt:t=>Kx(e,t),getTransaction:t=>Ba(e,t),getTransactionConfirmations:t=>Yx(e,t),getTransactionCount:t=>ra(e,t),getTransactionReceipt:t=>ka(e,t),multicall:t=>Jx(e,t),prepareTransactionRequest:t=>wr(e,t),readContract:t=>fe(e,t),sendRawTransaction:t=>va(e,t),sendRawTransactionSync:t=>za(e,t),simulate:t=>Ji(e,t),simulateBlocks:t=>Ji(e,t),simulateCalls:t=>ob(e,t),simulateContract:t=>ba(e,t),verifyHash:t=>qn(e,t),verifyMessage:t=>ab(e,t),verifySiweMessage:t=>mb(e,t),verifyTypedData:t=>ib(e,t),uninstallFilter:t=>Dn(e,t),waitForTransactionReceipt:t=>fd(e,t),watchBlocks:t=>cb(e,t),watchBlockNumber:t=>ud(e,t),watchContractEvent:t=>$y(e,t),watchEvent:t=>ub(e,t),watchPendingTransactions:t=>fb(e,t),token:Zv(e)}}function Zv(e){return{getAllowance:Sr(e,Oa),getBalance:Sr(e,$a),getMetadata:Sr(e,Ym),getTotalSupply:Sr(e,Ha)}}function ld(e){let{key:t="public",name:r="Public Client"}=e;return wf({...e,key:t,name:r,type:"publicClient"}).extend(lb)}Z();async function hb(e,{chain:t}){let{id:r,name:n,nativeCurrency:o,rpcUrls:s,blockExplorers:a}=t;await e.request({method:"wallet_addEthereumChain",params:[{chainId:I(r),chainName:n,nativeCurrency:o,rpcUrls:s.default.http,blockExplorerUrls:a?Object.values(a).map(({url:i})=>i):void 0}]},{dedupe:!0,retryCount:0})}sf();function yb(e,t){let{abi:r,args:n,bytecode:o,...s}=t,a=Ao({abi:r,args:n,bytecode:o});return Un(e,{...s,...s.authorizationList?{to:null}:{},data:a})}tr();async function xb(e){return e.account?.type==="local"?[e.account.address]:(await e.request({method:"eth_accounts"},{dedupe:!0})).map(r=>pr(r))}be();Z();async function bb(e,t={}){let{account:r=e.account,chainId:n}=t,o=r?K(r):void 0,s=n?[o?.address,[I(n)]]:[o?.address],a=await e.request({method:"wallet_getCapabilities",params:s}),i={};for(let[c,u]of Object.entries(a)){i[Number(c)]={};for(let[f,d]of Object.entries(u))f==="addSubAccount"&&(f="unstable_addSubAccount"),i[Number(c)][f]=d}return typeof n=="number"?i[n]:i}async function gb(e){return await e.request({method:"wallet_getPermissions"},{dedupe:!0})}be();Xr();async function hd(e,t){let{account:r=e.account,chainId:n,nonce:o}=t;if(!r)throw new Ie({docsPath:"/docs/eip7702/prepareAuthorization"});let s=K(r),a=(()=>{if(t.executor)return t.executor==="self"?t.executor:K(t.executor)})(),i={address:t.contractAddress??t.address,chainId:n,nonce:o};return typeof i.chainId>"u"&&(i.chainId=e.chain?.id??await B(e,Ue,"getChainId")({})),typeof i.nonce>"u"&&(i.nonce=await B(e,ra,"getTransactionCount")({address:s.address,blockTag:"pending"}),(a==="self"||a?.address&&Le(a.address,s.address))&&(i.nonce+=1)),i}tr();async function vb(e){return(await e.request({method:"eth_requestAccounts"},{dedupe:!0,retryCount:0})).map(r=>de(r))}async function wb(e,t){return e.request({method:"wallet_requestPermissions",params:[t]},{retryCount:0})}async function Eb(e,t){let{chain:r=e.chain}=t,n=t.timeout??Math.max((r?.blockTime??0)*3,5e3),o=await B(e,hf,"sendCalls")(t);return await B(e,xf,"waitForCallsStatus")({...t,id:o.id,timeout:n})}async function Tb(e,t){let{id:r}=t;await e.request({method:"wallet_showCallsStatus",params:[r]})}be();async function Ab(e,t){let{account:r=e.account}=t;if(!r)throw new Ie({docsPath:"/docs/eip7702/signAuthorization"});let n=K(r);if(!n.signAuthorization)throw new qt({docsPath:"/docs/eip7702/signAuthorization",metaMessages:["The `signAuthorization` Action does not support JSON-RPC Accounts."],type:n.type});let o=await hd(e,t);return n.signAuthorization(o)}be();Z();async function Pb(e,{account:t=e.account,message:r}){if(!t)throw new Ie({docsPath:"/docs/actions/wallet/signMessage"});let n=K(t);if(n.signMessage)return n.signMessage({message:r});let o=typeof r=="string"?Fr(r):r.raw instanceof Uint8Array?ce(r.raw):r.raw;return e.request({method:"personal_sign",params:[o,n.address]},{retryCount:0})}be();Z();Yr();vr();async function Sb(e,t){let{account:r=e.account,chain:n=e.chain,...o}=t;if(!r)throw new Ie({docsPath:"/docs/actions/wallet/signTransaction"});let s=K(r);He({account:s,...t});let a=await B(e,Ue,"getChainId")({});n!==null&&ga({currentChainId:a,chain:n});let c=(n?.formatters||e.chain?.formatters)?.transactionRequest?.format||nt;return s.signTransaction?s.signTransaction({...o,account:s,chainId:a},{serializer:e.chain?.serializers?.transaction}):await e.request({method:"eth_signTransaction",params:[{...c({...o,account:s},"signTransaction"),chainId:I(a),from:s.address}]},{retryCount:0})}be();async function _b(e,t){let{account:r=e.account,domain:n,message:o,primaryType:s}=t;if(!r)throw new Ie({docsPath:"/docs/actions/wallet/signTypedData"});let a=K(r),i={EIP712Domain:$f({domain:n}),...t.types};if(Of({domain:n,message:o,primaryType:s,types:i}),a.signTypedData)return a.signTypedData({domain:n,message:o,primaryType:s,types:i});let c=vx({domain:n,message:o,primaryType:s,types:i});return e.request({method:"eth_signTypedData_v4",params:[a.address,c]},{retryCount:0})}Z();async function Ib(e,{id:t}){await e.request({method:"wallet_switchEthereumChain",params:[{chainId:I(t)}]},{retryCount:0})}async function Bb(e,t){return await e.request({method:"wallet_watchAsset",params:t},{retryCount:0})}function kb(e){return{addChain:t=>hb(e,t),deployContract:t=>yb(e,t),fillTransaction:t=>aa(e,t),getAddresses:()=>xb(e),getCallsStatus:t=>yf(e,t),getCapabilities:t=>bb(e,t),getChainId:()=>Ue(e),getPermissions:()=>gb(e),prepareAuthorization:t=>hd(e,t),prepareTransactionRequest:t=>wr(e,t),requestAddresses:()=>vb(e),requestPermissions:t=>wb(e,t),sendCalls:t=>hf(e,t),sendCallsSync:t=>Eb(e,t),sendRawTransaction:t=>va(e,t),sendRawTransactionSync:t=>za(e,t),sendTransaction:t=>Un(e,t),sendTransactionSync:t=>md(e,t),showCallsStatus:t=>Tb(e,t),signAuthorization:t=>Ab(e,t),signMessage:t=>Pb(e,t),signTransaction:t=>Sb(e,t),signTypedData:t=>_b(e,t),switchChain:t=>Ib(e,t),waitForCallsStatus:t=>xf(e,t),watchAsset:t=>Bb(e,t),writeContract:t=>ar(e,t),writeContractSync:t=>Fa(e,t),token:{approve:Sr(e,on),approveSync:Sr(e,Km),transfer:Sr(e,sn),transferSync:Sr(e,Jm)}}}function Cb(e){let{key:t="wallet",name:r="Wallet Client",transport:n}=e;return wf({...e,key:t,name:r,transport:n,type:"walletClient"}).extend(kb)}function yd({key:e,methods:t,name:r,request:n,retryCount:o=3,retryDelay:s=150,timeout:a,type:i},c){let u=vf();return{config:{key:e,methods:t,name:r,request:n,retryCount:o,retryDelay:s,timeout:a,type:i},request:hx(n,{methods:t,retryCount:o,retryDelay:s,uid:u}),value:c}}function Rb(e,t={}){let{key:r="custom",methods:n,name:o="Custom Provider",retryDelay:s}=t;return({retryCount:a})=>yd({key:r,methods:n,name:o,request:e.request.bind(e),retryCount:t.retryCount??a,retryDelay:s,type:"custom"})}fo();J();var xd=class extends g{constructor(){super("No URL was provided to the Transport. Please provide a valid RPC URL to the Transport.",{docsPath:"/docs/clients/intro",name:"UrlRequiredError"})}};em();var Kv=0,Nb=new WeakMap;function Yv(e){if(!e)return"default";let t=Nb.get(e);if(t!==void 0)return t;let r=Kv++;return Nb.set(e,r),r}function bd(e,t={}){let{batch:r,fetchFn:n,fetchOptions:o,key:s="http",maxResponseBodySize:a,methods:i,name:c="HTTP JSON-RPC",onFetchRequest:u,onFetchResponse:f,retryDelay:d,raw:p}=t;return({chain:m,retryCount:l,timeout:h})=>{let{batchSize:x=1e3,wait:b=0}=typeof r=="object"?r:{},A=t.retryCount??l,v=h??t.timeout??1e4,y=e||m?.rpcUrls.default.http[0];if(!y)throw new xd;let w=xx(y,{fetchFn:n,fetchOptions:o,maxResponseBodySize:a,onRequest:u,onResponse:f,timeout:v});return yd({key:s,methods:i,name:c,async request({method:E,params:T},S){let R={method:E,params:T},P=S?.signal?{signal:S.signal}:void 0,{schedule:z}=uf({id:`${y}.${Yv(S?.signal)}`,wait:b,shouldSplitBatch(k){return k.length>x},fn:k=>w.request({body:k,fetchOptions:P}),sort:(k,C)=>k.id-C.id}),H=async k=>r?z(k):[await w.request({body:k,fetchOptions:P})],[{error:O,result:q}]=await H(R);if(p)return{error:O,result:q};if(O)throw new Tn({body:R,error:O,url:y});return q},retryCount:A,retryDelay:d,timeout:v,type:"http"},{fetchOptions:o,url:y})}}Ze();Mu();Xe();tr();Fe();Z();At();oo();js();Z();er();ht();function Mb(e){if(typeof e=="string"){if(!re(e,{strict:!1}))throw new me({address:e});return{address:e,type:"json-rpc"}}if(!re(e.address,{strict:!1}))throw new me({address:e.address});return{address:e.address,nonceManager:e.nonceManager,sign:e.sign,signAuthorization:e.signAuthorization,signMessage:e.signMessage,signTransaction:e.signTransaction,signTypedData:e.signTypedData,source:"custom",type:"local"}}js();Rt();Fe();Z();var Xm=!1;async function Ir({hash:e,privateKey:t,to:r="object"}){let{r:n,s:o,recovery:s}=It.sign(e.slice(2),t.slice(2),{lowS:!0,extraEntropy:we(Xm,{strict:!1})?ke(Xm):Xm}),a={r:I(n,{size:32}),s:I(o,{size:32}),v:s?28n:27n,yParity:s};return r==="bytes"||r==="hex"?cd({...a,to:r}):a}async function zb(e){let{chainId:t,nonce:r,privateKey:n,to:o="object"}=e,s=e.contractAddress??e.address,a=await Ir({hash:ku({address:s,chainId:t,nonce:r}),privateKey:n,to:o});return o==="object"?{address:s,chainId:t,nonce:r,...a}:a}async function Fb({message:e,privateKey:t}){return await Ir({hash:Sa(e),privateKey:t,to:"hex"})}At();async function Ob(e){let{privateKey:t,transaction:r,serializer:n=Rf}=e,o=r.type==="eip4844"?{...r,sidecars:!1}:r,s=await Ir({hash:oe(await n(o)),privateKey:t});return await n(r,s)}async function $b(e){let{privateKey:t,...r}=e;return await Ir({hash:Hf(r),privateKey:t,to:"hex"})}function Hb(e,t={}){let{nonceManager:r}=t,n=ce(It.getPublicKey(e.slice(2),!1)),o=vu(n);return{...Mb({address:o,nonceManager:r,async sign({hash:a}){return Ir({hash:a,privateKey:e,to:"hex"})},async signAuthorization(a){return zb({...a,privateKey:e})},async signMessage({message:a}){return Fb({message:a,privateKey:e})},async signTransaction(a,{serializer:i}={}){return Ob({privateKey:e,transaction:a,serializer:i})},async signTypedData(a){return $b({...a,privateKey:e})}}),publicKey:n,source:"privateKey"}}var Db={gasPriceOracle:{address:"0x420000000000000000000000000000000000000F"},l1Block:{address:"0x4200000000000000000000000000000000000015"},l2CrossDomainMessenger:{address:"0x4200000000000000000000000000000000000007"},l2Erc721Bridge:{address:"0x4200000000000000000000000000000000000014"},l2StandardBridge:{address:"0x4200000000000000000000000000000000000010"},l2ToL1MessagePasser:{address:"0x4200000000000000000000000000000000000016"}};ze();var Ub={block:Hh({format(e){return{transactions:e.transactions?.map(r=>{if(typeof r=="string")return r;let n=Jr(r);return n.typeHex==="0x7e"&&(n.isSystemTx=r.isSystemTx,n.mint=r.mint?pe(r.mint):void 0,n.sourceHash=r.sourceHash,n.type="deposit"),n}),stateRoot:e.stateRoot}}}),transaction:$h({format(e){let t={};return e.type==="0x7e"&&(t.isSystemTx=e.isSystemTx,t.mint=e.mint?pe(e.mint):void 0,t.sourceHash=e.sourceHash,t.type="deposit"),t}}),transactionReceipt:Hy({format(e){return{...e.depositNonce?{depositNonce:pe(e.depositNonce)}:{},...e.depositReceiptVersion?{depositReceiptVersion:ye(e.depositReceiptVersion)}:{},l1GasPrice:e.l1GasPrice?pe(e.l1GasPrice):null,l1GasUsed:e.l1GasUsed?pe(e.l1GasUsed):null,l1Fee:e.l1Fee?pe(e.l1Fee):null,l1FeeScalar:e.l1FeeScalar?Number(e.l1FeeScalar):null}}})};er();ht();qe();Z();function Jv(e,t){return Qv(e)?Xv(e):Rf(e,t)}var Lb={transaction:Jv};function Xv(e){ew(e);let{sourceHash:t,data:r,from:n,gas:o,isSystemTx:s,mint:a,to:i,value:c}=e,u=[t,n,i??"0x",a?ce(a):"0x",c?ce(c):"0x",o?ce(o):"0x",s?"0x1":"0x",r??"0x"];return xe(["0x7e",or(u)])}function Qv(e){return e.type==="deposit"||typeof e.sourceHash<"u"}function ew(e){let{from:t,to:r}=e;if(t&&!re(t))throw new me({address:t});if(r&&!re(r))throw new me({address:r})}var Da={blockTime:2e3,contracts:Db,formatters:Ub,serializers:Lb};var ec=1,Qm=Pa({...Da,id:8453,name:"Base",nativeCurrency:{name:"Ether",symbol:"ETH",decimals:18},rpcUrls:{default:{http:["https://mainnet.base.org"]}},blockExplorers:{default:{name:"Basescan",url:"https://basescan.org",apiUrl:"https://api.basescan.org/api"}},contracts:{...Da.contracts,disputeGameFactory:{[ec]:{address:"0x43edB88C4B80fDD2AdFF2412A7BebF9dF42cB40e"}},l2OutputOracle:{[ec]:{address:"0x56315b90c40730925ec5485cf004d835058518A0"}},multicall3:{address:"0xca11bde05977b3631167028862be2a173976ca11",blockCreated:5022},portal:{[ec]:{address:"0x49048044D57e1C92A77f79988d21Fa8fAF74E97e",blockCreated:17482143}},l1StandardBridge:{[ec]:{address:"0x3154Cf16ccdb4C6d922629664174b904d80F2C35",blockCreated:17482143}}},sourceId:ec}),tw=Pa({...Qm,experimental_preconfirmationTime:200,rpcUrls:{default:{http:["https://mainnet-preconf.base.org"]}}});var tc=11155111,el=Pa({...Da,id:84532,network:"base-sepolia",name:"Base Sepolia",nativeCurrency:{name:"Sepolia Ether",symbol:"ETH",decimals:18},rpcUrls:{default:{http:["https://sepolia.base.org"]}},blockExplorers:{default:{name:"Basescan",url:"https://sepolia.basescan.org",apiUrl:"https://api-sepolia.basescan.org/api"}},contracts:{...Da.contracts,disputeGameFactory:{[tc]:{address:"0xd6E6dBf4F7EA0ac412fD8b65ED297e64BB7a06E1"}},l2OutputOracle:{[tc]:{address:"0x84457ca9D0163FbC4bbfe4Dfbb20ba46e48DF254"}},portal:{[tc]:{address:"0x49f53e41452c74589e85ca1677426ba426459e85",blockCreated:4446677}},l1StandardBridge:{[tc]:{address:"0xfd0Bf71F60660E2f608ed56e1659C450eB113120",blockCreated:4446677}},multicall3:{address:"0xca11bde05977b3631167028862be2a173976ca11",blockCreated:1059647}},testnet:!0,sourceId:tc}),rw=Pa({...el,experimental_preconfirmationTime:200,rpcUrls:{default:{http:["https://sepolia-preconf.base.org"]}}});var F={};Qa(F,{BRAND:()=>Iw,DIRTY:()=>Mo,EMPTY_PATH:()=>aw,INVALID:()=>G,NEVER:()=>pE,OK:()=>at,ParseStatus:()=>Ye,Schema:()=>Q,ZodAny:()=>Zn,ZodArray:()=>fn,ZodBigInt:()=>Fo,ZodBoolean:()=>Oo,ZodBranded:()=>nc,ZodCatch:()=>Zo,ZodDate:()=>$o,ZodDefault:()=>Wo,ZodDiscriminatedUnion:()=>wd,ZodEffects:()=>Zt,ZodEnum:()=>qo,ZodError:()=>wt,ZodFirstPartyTypeKind:()=>Y,ZodFunction:()=>Td,ZodIntersection:()=>Lo,ZodIssueCode:()=>N,ZodLazy:()=>jo,ZodLiteral:()=>Vo,ZodMap:()=>Ga,ZodNaN:()=>Za,ZodNativeEnum:()=>Go,ZodNever:()=>ir,ZodNull:()=>Do,ZodNullable:()=>Cr,ZodNumber:()=>zo,ZodObject:()=>Et,ZodOptional:()=>Gt,ZodParsedType:()=>U,ZodPipeline:()=>oc,ZodPromise:()=>Kn,ZodReadonly:()=>Ko,ZodRecord:()=>Ed,ZodSchema:()=>Q,ZodSet:()=>Wa,ZodString:()=>Wn,ZodSymbol:()=>Va,ZodTransformer:()=>Zt,ZodTuple:()=>kr,ZodType:()=>Q,ZodUndefined:()=>Ho,ZodUnion:()=>Uo,ZodUnknown:()=>un,ZodVoid:()=>qa,addIssueToContext:()=>$,any:()=>Ow,array:()=>Uw,bigint:()=>Rw,boolean:()=>Qb,coerce:()=>dE,custom:()=>Yb,date:()=>Nw,datetimeRegex:()=>Zb,defaultErrorMap:()=>an,discriminatedUnion:()=>qw,effect:()=>nE,enum:()=>eE,function:()=>Jw,getErrorMap:()=>Ua,getParsedType:()=>Br,instanceof:()=>kw,intersection:()=>Gw,isAborted:()=>gd,isAsync:()=>La,isDirty:()=>vd,isValid:()=>Gn,late:()=>Bw,lazy:()=>Xw,literal:()=>Qw,makeIssue:()=>rc,map:()=>Kw,nan:()=>Cw,nativeEnum:()=>tE,never:()=>Hw,null:()=>Fw,nullable:()=>sE,number:()=>Xb,object:()=>Lw,objectUtil:()=>tl,oboolean:()=>fE,onumber:()=>uE,optional:()=>oE,ostring:()=>cE,pipeline:()=>iE,preprocess:()=>aE,promise:()=>rE,quotelessJson:()=>nw,record:()=>Zw,set:()=>Yw,setErrorMap:()=>sw,strictObject:()=>jw,string:()=>Jb,symbol:()=>Mw,transformer:()=>nE,tuple:()=>Ww,undefined:()=>zw,union:()=>Vw,unknown:()=>$w,util:()=>te,void:()=>Dw});var te;(function(e){e.assertEqual=o=>{};function t(o){}e.assertIs=t;function r(o){throw new Error}e.assertNever=r,e.arrayToEnum=o=>{let s={};for(let a of o)s[a]=a;return s},e.getValidEnumValues=o=>{let s=e.objectKeys(o).filter(i=>typeof o[o[i]]!="number"),a={};for(let i of s)a[i]=o[i];return e.objectValues(a)},e.objectValues=o=>e.objectKeys(o).map(function(s){return o[s]}),e.objectKeys=typeof Object.keys=="function"?o=>Object.keys(o):o=>{let s=[];for(let a in o)Object.prototype.hasOwnProperty.call(o,a)&&s.push(a);return s},e.find=(o,s)=>{for(let a of o)if(s(a))return a},e.isInteger=typeof Number.isInteger=="function"?o=>Number.isInteger(o):o=>typeof o=="number"&&Number.isFinite(o)&&Math.floor(o)===o;function n(o,s=" | "){return o.map(a=>typeof a=="string"?`'${a}'`:a).join(s)}e.joinValues=n,e.jsonStringifyReplacer=(o,s)=>typeof s=="bigint"?s.toString():s})(te||(te={}));var tl;(function(e){e.mergeShapes=(t,r)=>({...t,...r})})(tl||(tl={}));var U=te.arrayToEnum(["string","nan","number","integer","float","boolean","date","bigint","symbol","function","undefined","null","array","object","unknown","promise","void","never","map","set"]),Br=e=>{switch(typeof e){case"undefined":return U.undefined;case"string":return U.string;case"number":return Number.isNaN(e)?U.nan:U.number;case"boolean":return U.boolean;case"function":return U.function;case"bigint":return U.bigint;case"symbol":return U.symbol;case"object":return Array.isArray(e)?U.array:e===null?U.null:e.then&&typeof e.then=="function"&&e.catch&&typeof e.catch=="function"?U.promise:typeof Map<"u"&&e instanceof Map?U.map:typeof Set<"u"&&e instanceof Set?U.set:typeof Date<"u"&&e instanceof Date?U.date:U.object;default:return U.unknown}};var N=te.arrayToEnum(["invalid_type","invalid_literal","custom","invalid_union","invalid_union_discriminator","invalid_enum_value","unrecognized_keys","invalid_arguments","invalid_return_type","invalid_date","invalid_string","too_small","too_big","invalid_intersection_types","not_multiple_of","not_finite"]),nw=e=>JSON.stringify(e,null,2).replace(/"([^"]+)":/g,"$1:"),wt=class e extends Error{get errors(){return this.issues}constructor(t){super(),this.issues=[],this.addIssue=n=>{this.issues=[...this.issues,n]},this.addIssues=(n=[])=>{this.issues=[...this.issues,...n]};let r=new.target.prototype;Object.setPrototypeOf?Object.setPrototypeOf(this,r):this.__proto__=r,this.name="ZodError",this.issues=t}format(t){let r=t||function(s){return s.message},n={_errors:[]},o=s=>{for(let a of s.issues)if(a.code==="invalid_union")a.unionErrors.map(o);else if(a.code==="invalid_return_type")o(a.returnTypeError);else if(a.code==="invalid_arguments")o(a.argumentsError);else if(a.path.length===0)n._errors.push(r(a));else{let i=n,c=0;for(;cr.message){let r={},n=[];for(let o of this.issues)if(o.path.length>0){let s=o.path[0];r[s]=r[s]||[],r[s].push(t(o))}else n.push(t(o));return{formErrors:n,fieldErrors:r}}get formErrors(){return this.flatten()}};wt.create=e=>new wt(e);var ow=(e,t)=>{let r;switch(e.code){case N.invalid_type:e.received===U.undefined?r="Required":r=`Expected ${e.expected}, received ${e.received}`;break;case N.invalid_literal:r=`Invalid literal value, expected ${JSON.stringify(e.expected,te.jsonStringifyReplacer)}`;break;case N.unrecognized_keys:r=`Unrecognized key(s) in object: ${te.joinValues(e.keys,", ")}`;break;case N.invalid_union:r="Invalid input";break;case N.invalid_union_discriminator:r=`Invalid discriminator value. Expected ${te.joinValues(e.options)}`;break;case N.invalid_enum_value:r=`Invalid enum value. Expected ${te.joinValues(e.options)}, received '${e.received}'`;break;case N.invalid_arguments:r="Invalid function arguments";break;case N.invalid_return_type:r="Invalid function return type";break;case N.invalid_date:r="Invalid date";break;case N.invalid_string:typeof e.validation=="object"?"includes"in e.validation?(r=`Invalid input: must include "${e.validation.includes}"`,typeof e.validation.position=="number"&&(r=`${r} at one or more positions greater than or equal to ${e.validation.position}`)):"startsWith"in e.validation?r=`Invalid input: must start with "${e.validation.startsWith}"`:"endsWith"in e.validation?r=`Invalid input: must end with "${e.validation.endsWith}"`:te.assertNever(e.validation):e.validation!=="regex"?r=`Invalid ${e.validation}`:r="Invalid";break;case N.too_small:e.type==="array"?r=`Array must contain ${e.exact?"exactly":e.inclusive?"at least":"more than"} ${e.minimum} element(s)`:e.type==="string"?r=`String must contain ${e.exact?"exactly":e.inclusive?"at least":"over"} ${e.minimum} character(s)`:e.type==="number"?r=`Number must be ${e.exact?"exactly equal to ":e.inclusive?"greater than or equal to ":"greater than "}${e.minimum}`:e.type==="bigint"?r=`Number must be ${e.exact?"exactly equal to ":e.inclusive?"greater than or equal to ":"greater than "}${e.minimum}`:e.type==="date"?r=`Date must be ${e.exact?"exactly equal to ":e.inclusive?"greater than or equal to ":"greater than "}${new Date(Number(e.minimum))}`:r="Invalid input";break;case N.too_big:e.type==="array"?r=`Array must contain ${e.exact?"exactly":e.inclusive?"at most":"less than"} ${e.maximum} element(s)`:e.type==="string"?r=`String must contain ${e.exact?"exactly":e.inclusive?"at most":"under"} ${e.maximum} character(s)`:e.type==="number"?r=`Number must be ${e.exact?"exactly":e.inclusive?"less than or equal to":"less than"} ${e.maximum}`:e.type==="bigint"?r=`BigInt must be ${e.exact?"exactly":e.inclusive?"less than or equal to":"less than"} ${e.maximum}`:e.type==="date"?r=`Date must be ${e.exact?"exactly":e.inclusive?"smaller than or equal to":"smaller than"} ${new Date(Number(e.maximum))}`:r="Invalid input";break;case N.custom:r="Invalid input";break;case N.invalid_intersection_types:r="Intersection results could not be merged";break;case N.not_multiple_of:r=`Number must be a multiple of ${e.multipleOf}`;break;case N.not_finite:r="Number must be finite";break;default:r=t.defaultError,te.assertNever(e)}return{message:r}},an=ow;var jb=an;function sw(e){jb=e}function Ua(){return jb}var rc=e=>{let{data:t,path:r,errorMaps:n,issueData:o}=e,s=[...r,...o.path||[]],a={...o,path:s};if(o.message!==void 0)return{...o,path:s,message:o.message};let i="",c=n.filter(u=>!!u).slice().reverse();for(let u of c)i=u(a,{data:t,defaultError:i}).message;return{...o,path:s,message:i}},aw=[];function $(e,t){let r=Ua(),n=rc({issueData:t,data:e.data,path:e.path,errorMaps:[e.common.contextualErrorMap,e.schemaErrorMap,r,r===an?void 0:an].filter(o=>!!o)});e.common.issues.push(n)}var Ye=class e{constructor(){this.value="valid"}dirty(){this.value==="valid"&&(this.value="dirty")}abort(){this.value!=="aborted"&&(this.value="aborted")}static mergeArray(t,r){let n=[];for(let o of r){if(o.status==="aborted")return G;o.status==="dirty"&&t.dirty(),n.push(o.value)}return{status:t.value,value:n}}static async mergeObjectAsync(t,r){let n=[];for(let o of r){let s=await o.key,a=await o.value;n.push({key:s,value:a})}return e.mergeObjectSync(t,n)}static mergeObjectSync(t,r){let n={};for(let o of r){let{key:s,value:a}=o;if(s.status==="aborted"||a.status==="aborted")return G;s.status==="dirty"&&t.dirty(),a.status==="dirty"&&t.dirty(),s.value!=="__proto__"&&(typeof a.value<"u"||o.alwaysSet)&&(n[s.value]=a.value)}return{status:t.value,value:n}}},G=Object.freeze({status:"aborted"}),Mo=e=>({status:"dirty",value:e}),at=e=>({status:"valid",value:e}),gd=e=>e.status==="aborted",vd=e=>e.status==="dirty",Gn=e=>e.status==="valid",La=e=>typeof Promise<"u"&&e instanceof Promise;var V;(function(e){e.errToObj=t=>typeof t=="string"?{message:t}:t||{},e.toString=t=>typeof t=="string"?t:t?.message})(V||(V={}));var Wt=class{constructor(t,r,n,o){this._cachedPath=[],this.parent=t,this.data=r,this._path=n,this._key=o}get path(){return this._cachedPath.length||(Array.isArray(this._key)?this._cachedPath.push(...this._path,...this._key):this._cachedPath.push(...this._path,this._key)),this._cachedPath}},Vb=(e,t)=>{if(Gn(t))return{success:!0,data:t.value};if(!e.common.issues.length)throw new Error("Validation failed but no issues detected.");return{success:!1,get error(){if(this._error)return this._error;let r=new wt(e.common.issues);return this._error=r,this._error}}};function X(e){if(!e)return{};let{errorMap:t,invalid_type_error:r,required_error:n,description:o}=e;if(t&&(r||n))throw new Error(`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`);return t?{errorMap:t,description:o}:{errorMap:(a,i)=>{let{message:c}=e;return a.code==="invalid_enum_value"?{message:c??i.defaultError}:typeof i.data>"u"?{message:c??n??i.defaultError}:a.code!=="invalid_type"?{message:i.defaultError}:{message:c??r??i.defaultError}},description:o}}var Q=class{get description(){return this._def.description}_getType(t){return Br(t.data)}_getOrReturnCtx(t,r){return r||{common:t.parent.common,data:t.data,parsedType:Br(t.data),schemaErrorMap:this._def.errorMap,path:t.path,parent:t.parent}}_processInputParams(t){return{status:new Ye,ctx:{common:t.parent.common,data:t.data,parsedType:Br(t.data),schemaErrorMap:this._def.errorMap,path:t.path,parent:t.parent}}}_parseSync(t){let r=this._parse(t);if(La(r))throw new Error("Synchronous parse encountered promise.");return r}_parseAsync(t){let r=this._parse(t);return Promise.resolve(r)}parse(t,r){let n=this.safeParse(t,r);if(n.success)return n.data;throw n.error}safeParse(t,r){let n={common:{issues:[],async:r?.async??!1,contextualErrorMap:r?.errorMap},path:r?.path||[],schemaErrorMap:this._def.errorMap,parent:null,data:t,parsedType:Br(t)},o=this._parseSync({data:t,path:n.path,parent:n});return Vb(n,o)}"~validate"(t){let r={common:{issues:[],async:!!this["~standard"].async},path:[],schemaErrorMap:this._def.errorMap,parent:null,data:t,parsedType:Br(t)};if(!this["~standard"].async)try{let n=this._parseSync({data:t,path:[],parent:r});return Gn(n)?{value:n.value}:{issues:r.common.issues}}catch(n){n?.message?.toLowerCase()?.includes("encountered")&&(this["~standard"].async=!0),r.common={issues:[],async:!0}}return this._parseAsync({data:t,path:[],parent:r}).then(n=>Gn(n)?{value:n.value}:{issues:r.common.issues})}async parseAsync(t,r){let n=await this.safeParseAsync(t,r);if(n.success)return n.data;throw n.error}async safeParseAsync(t,r){let n={common:{issues:[],contextualErrorMap:r?.errorMap,async:!0},path:r?.path||[],schemaErrorMap:this._def.errorMap,parent:null,data:t,parsedType:Br(t)},o=this._parse({data:t,path:n.path,parent:n}),s=await(La(o)?o:Promise.resolve(o));return Vb(n,s)}refine(t,r){let n=o=>typeof r=="string"||typeof r>"u"?{message:r}:typeof r=="function"?r(o):r;return this._refinement((o,s)=>{let a=t(o),i=()=>s.addIssue({code:N.custom,...n(o)});return typeof Promise<"u"&&a instanceof Promise?a.then(c=>c?!0:(i(),!1)):a?!0:(i(),!1)})}refinement(t,r){return this._refinement((n,o)=>t(n)?!0:(o.addIssue(typeof r=="function"?r(n,o):r),!1))}_refinement(t){return new Zt({schema:this,typeName:Y.ZodEffects,effect:{type:"refinement",refinement:t}})}superRefine(t){return this._refinement(t)}constructor(t){this.spa=this.safeParseAsync,this._def=t,this.parse=this.parse.bind(this),this.safeParse=this.safeParse.bind(this),this.parseAsync=this.parseAsync.bind(this),this.safeParseAsync=this.safeParseAsync.bind(this),this.spa=this.spa.bind(this),this.refine=this.refine.bind(this),this.refinement=this.refinement.bind(this),this.superRefine=this.superRefine.bind(this),this.optional=this.optional.bind(this),this.nullable=this.nullable.bind(this),this.nullish=this.nullish.bind(this),this.array=this.array.bind(this),this.promise=this.promise.bind(this),this.or=this.or.bind(this),this.and=this.and.bind(this),this.transform=this.transform.bind(this),this.brand=this.brand.bind(this),this.default=this.default.bind(this),this.catch=this.catch.bind(this),this.describe=this.describe.bind(this),this.pipe=this.pipe.bind(this),this.readonly=this.readonly.bind(this),this.isNullable=this.isNullable.bind(this),this.isOptional=this.isOptional.bind(this),this["~standard"]={version:1,vendor:"zod",validate:r=>this["~validate"](r)}}optional(){return Gt.create(this,this._def)}nullable(){return Cr.create(this,this._def)}nullish(){return this.nullable().optional()}array(){return fn.create(this)}promise(){return Kn.create(this,this._def)}or(t){return Uo.create([this,t],this._def)}and(t){return Lo.create(this,t,this._def)}transform(t){return new Zt({...X(this._def),schema:this,typeName:Y.ZodEffects,effect:{type:"transform",transform:t}})}default(t){let r=typeof t=="function"?t:()=>t;return new Wo({...X(this._def),innerType:this,defaultValue:r,typeName:Y.ZodDefault})}brand(){return new nc({typeName:Y.ZodBranded,type:this,...X(this._def)})}catch(t){let r=typeof t=="function"?t:()=>t;return new Zo({...X(this._def),innerType:this,catchValue:r,typeName:Y.ZodCatch})}describe(t){let r=this.constructor;return new r({...this._def,description:t})}pipe(t){return oc.create(this,t)}readonly(){return Ko.create(this)}isOptional(){return this.safeParse(void 0).success}isNullable(){return this.safeParse(null).success}},iw=/^c[^\s-]{8,}$/i,cw=/^[0-9a-z]+$/,uw=/^[0-9A-HJKMNP-TV-Z]{26}$/i,fw=/^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/i,dw=/^[a-z0-9_-]{21}$/i,pw=/^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/,mw=/^[-+]?P(?!$)(?:(?:[-+]?\d+Y)|(?:[-+]?\d+[.,]\d+Y$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:(?:[-+]?\d+W)|(?:[-+]?\d+[.,]\d+W$))?(?:(?:[-+]?\d+D)|(?:[-+]?\d+[.,]\d+D$))?(?:T(?=[\d+-])(?:(?:[-+]?\d+H)|(?:[-+]?\d+[.,]\d+H$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:[-+]?\d+(?:[.,]\d+)?S)?)??$/,lw=/^(?!\.)(?!.*\.\.)([A-Z0-9_'+\-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i,hw="^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$",rl,yw=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,xw=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/(3[0-2]|[12]?[0-9])$/,bw=/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/,gw=/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,vw=/^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/,ww=/^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/,Gb="((\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-((0[13578]|1[02])-(0[1-9]|[12]\\d|3[01])|(0[469]|11)-(0[1-9]|[12]\\d|30)|(02)-(0[1-9]|1\\d|2[0-8])))",Ew=new RegExp(`^${Gb}$`);function Wb(e){let t="[0-5]\\d";e.precision?t=`${t}\\.\\d{${e.precision}}`:e.precision==null&&(t=`${t}(\\.\\d+)?`);let r=e.precision?"+":"?";return`([01]\\d|2[0-3]):[0-5]\\d(:${t})${r}`}function Tw(e){return new RegExp(`^${Wb(e)}$`)}function Zb(e){let t=`${Gb}T${Wb(e)}`,r=[];return r.push(e.local?"Z?":"Z"),e.offset&&r.push("([+-]\\d{2}:?\\d{2})"),t=`${t}(${r.join("|")})`,new RegExp(`^${t}$`)}function Aw(e,t){return!!((t==="v4"||!t)&&yw.test(e)||(t==="v6"||!t)&&bw.test(e))}function Pw(e,t){if(!pw.test(e))return!1;try{let[r]=e.split(".");if(!r)return!1;let n=r.replace(/-/g,"+").replace(/_/g,"/").padEnd(r.length+(4-r.length%4)%4,"="),o=JSON.parse(atob(n));return!(typeof o!="object"||o===null||"typ"in o&&o?.typ!=="JWT"||!o.alg||t&&o.alg!==t)}catch{return!1}}function Sw(e,t){return!!((t==="v4"||!t)&&xw.test(e)||(t==="v6"||!t)&&gw.test(e))}var Wn=class e extends Q{_parse(t){if(this._def.coerce&&(t.data=String(t.data)),this._getType(t)!==U.string){let s=this._getOrReturnCtx(t);return $(s,{code:N.invalid_type,expected:U.string,received:s.parsedType}),G}let n=new Ye,o;for(let s of this._def.checks)if(s.kind==="min")t.data.lengths.value&&(o=this._getOrReturnCtx(t,o),$(o,{code:N.too_big,maximum:s.value,type:"string",inclusive:!0,exact:!1,message:s.message}),n.dirty());else if(s.kind==="length"){let a=t.data.length>s.value,i=t.data.lengtht.test(o),{validation:r,code:N.invalid_string,...V.errToObj(n)})}_addCheck(t){return new e({...this._def,checks:[...this._def.checks,t]})}email(t){return this._addCheck({kind:"email",...V.errToObj(t)})}url(t){return this._addCheck({kind:"url",...V.errToObj(t)})}emoji(t){return this._addCheck({kind:"emoji",...V.errToObj(t)})}uuid(t){return this._addCheck({kind:"uuid",...V.errToObj(t)})}nanoid(t){return this._addCheck({kind:"nanoid",...V.errToObj(t)})}cuid(t){return this._addCheck({kind:"cuid",...V.errToObj(t)})}cuid2(t){return this._addCheck({kind:"cuid2",...V.errToObj(t)})}ulid(t){return this._addCheck({kind:"ulid",...V.errToObj(t)})}base64(t){return this._addCheck({kind:"base64",...V.errToObj(t)})}base64url(t){return this._addCheck({kind:"base64url",...V.errToObj(t)})}jwt(t){return this._addCheck({kind:"jwt",...V.errToObj(t)})}ip(t){return this._addCheck({kind:"ip",...V.errToObj(t)})}cidr(t){return this._addCheck({kind:"cidr",...V.errToObj(t)})}datetime(t){return typeof t=="string"?this._addCheck({kind:"datetime",precision:null,offset:!1,local:!1,message:t}):this._addCheck({kind:"datetime",precision:typeof t?.precision>"u"?null:t?.precision,offset:t?.offset??!1,local:t?.local??!1,...V.errToObj(t?.message)})}date(t){return this._addCheck({kind:"date",message:t})}time(t){return typeof t=="string"?this._addCheck({kind:"time",precision:null,message:t}):this._addCheck({kind:"time",precision:typeof t?.precision>"u"?null:t?.precision,...V.errToObj(t?.message)})}duration(t){return this._addCheck({kind:"duration",...V.errToObj(t)})}regex(t,r){return this._addCheck({kind:"regex",regex:t,...V.errToObj(r)})}includes(t,r){return this._addCheck({kind:"includes",value:t,position:r?.position,...V.errToObj(r?.message)})}startsWith(t,r){return this._addCheck({kind:"startsWith",value:t,...V.errToObj(r)})}endsWith(t,r){return this._addCheck({kind:"endsWith",value:t,...V.errToObj(r)})}min(t,r){return this._addCheck({kind:"min",value:t,...V.errToObj(r)})}max(t,r){return this._addCheck({kind:"max",value:t,...V.errToObj(r)})}length(t,r){return this._addCheck({kind:"length",value:t,...V.errToObj(r)})}nonempty(t){return this.min(1,V.errToObj(t))}trim(){return new e({...this._def,checks:[...this._def.checks,{kind:"trim"}]})}toLowerCase(){return new e({...this._def,checks:[...this._def.checks,{kind:"toLowerCase"}]})}toUpperCase(){return new e({...this._def,checks:[...this._def.checks,{kind:"toUpperCase"}]})}get isDatetime(){return!!this._def.checks.find(t=>t.kind==="datetime")}get isDate(){return!!this._def.checks.find(t=>t.kind==="date")}get isTime(){return!!this._def.checks.find(t=>t.kind==="time")}get isDuration(){return!!this._def.checks.find(t=>t.kind==="duration")}get isEmail(){return!!this._def.checks.find(t=>t.kind==="email")}get isURL(){return!!this._def.checks.find(t=>t.kind==="url")}get isEmoji(){return!!this._def.checks.find(t=>t.kind==="emoji")}get isUUID(){return!!this._def.checks.find(t=>t.kind==="uuid")}get isNANOID(){return!!this._def.checks.find(t=>t.kind==="nanoid")}get isCUID(){return!!this._def.checks.find(t=>t.kind==="cuid")}get isCUID2(){return!!this._def.checks.find(t=>t.kind==="cuid2")}get isULID(){return!!this._def.checks.find(t=>t.kind==="ulid")}get isIP(){return!!this._def.checks.find(t=>t.kind==="ip")}get isCIDR(){return!!this._def.checks.find(t=>t.kind==="cidr")}get isBase64(){return!!this._def.checks.find(t=>t.kind==="base64")}get isBase64url(){return!!this._def.checks.find(t=>t.kind==="base64url")}get minLength(){let t=null;for(let r of this._def.checks)r.kind==="min"&&(t===null||r.value>t)&&(t=r.value);return t}get maxLength(){let t=null;for(let r of this._def.checks)r.kind==="max"&&(t===null||r.valuenew Wn({checks:[],typeName:Y.ZodString,coerce:e?.coerce??!1,...X(e)});function _w(e,t){let r=(e.toString().split(".")[1]||"").length,n=(t.toString().split(".")[1]||"").length,o=r>n?r:n,s=Number.parseInt(e.toFixed(o).replace(".","")),a=Number.parseInt(t.toFixed(o).replace(".",""));return s%a/10**o}var zo=class e extends Q{constructor(){super(...arguments),this.min=this.gte,this.max=this.lte,this.step=this.multipleOf}_parse(t){if(this._def.coerce&&(t.data=Number(t.data)),this._getType(t)!==U.number){let s=this._getOrReturnCtx(t);return $(s,{code:N.invalid_type,expected:U.number,received:s.parsedType}),G}let n,o=new Ye;for(let s of this._def.checks)s.kind==="int"?te.isInteger(t.data)||(n=this._getOrReturnCtx(t,n),$(n,{code:N.invalid_type,expected:"integer",received:"float",message:s.message}),o.dirty()):s.kind==="min"?(s.inclusive?t.datas.value:t.data>=s.value)&&(n=this._getOrReturnCtx(t,n),$(n,{code:N.too_big,maximum:s.value,type:"number",inclusive:s.inclusive,exact:!1,message:s.message}),o.dirty()):s.kind==="multipleOf"?_w(t.data,s.value)!==0&&(n=this._getOrReturnCtx(t,n),$(n,{code:N.not_multiple_of,multipleOf:s.value,message:s.message}),o.dirty()):s.kind==="finite"?Number.isFinite(t.data)||(n=this._getOrReturnCtx(t,n),$(n,{code:N.not_finite,message:s.message}),o.dirty()):te.assertNever(s);return{status:o.value,value:t.data}}gte(t,r){return this.setLimit("min",t,!0,V.toString(r))}gt(t,r){return this.setLimit("min",t,!1,V.toString(r))}lte(t,r){return this.setLimit("max",t,!0,V.toString(r))}lt(t,r){return this.setLimit("max",t,!1,V.toString(r))}setLimit(t,r,n,o){return new e({...this._def,checks:[...this._def.checks,{kind:t,value:r,inclusive:n,message:V.toString(o)}]})}_addCheck(t){return new e({...this._def,checks:[...this._def.checks,t]})}int(t){return this._addCheck({kind:"int",message:V.toString(t)})}positive(t){return this._addCheck({kind:"min",value:0,inclusive:!1,message:V.toString(t)})}negative(t){return this._addCheck({kind:"max",value:0,inclusive:!1,message:V.toString(t)})}nonpositive(t){return this._addCheck({kind:"max",value:0,inclusive:!0,message:V.toString(t)})}nonnegative(t){return this._addCheck({kind:"min",value:0,inclusive:!0,message:V.toString(t)})}multipleOf(t,r){return this._addCheck({kind:"multipleOf",value:t,message:V.toString(r)})}finite(t){return this._addCheck({kind:"finite",message:V.toString(t)})}safe(t){return this._addCheck({kind:"min",inclusive:!0,value:Number.MIN_SAFE_INTEGER,message:V.toString(t)})._addCheck({kind:"max",inclusive:!0,value:Number.MAX_SAFE_INTEGER,message:V.toString(t)})}get minValue(){let t=null;for(let r of this._def.checks)r.kind==="min"&&(t===null||r.value>t)&&(t=r.value);return t}get maxValue(){let t=null;for(let r of this._def.checks)r.kind==="max"&&(t===null||r.valuet.kind==="int"||t.kind==="multipleOf"&&te.isInteger(t.value))}get isFinite(){let t=null,r=null;for(let n of this._def.checks){if(n.kind==="finite"||n.kind==="int"||n.kind==="multipleOf")return!0;n.kind==="min"?(r===null||n.value>r)&&(r=n.value):n.kind==="max"&&(t===null||n.valuenew zo({checks:[],typeName:Y.ZodNumber,coerce:e?.coerce||!1,...X(e)});var Fo=class e extends Q{constructor(){super(...arguments),this.min=this.gte,this.max=this.lte}_parse(t){if(this._def.coerce)try{t.data=BigInt(t.data)}catch{return this._getInvalidInput(t)}if(this._getType(t)!==U.bigint)return this._getInvalidInput(t);let n,o=new Ye;for(let s of this._def.checks)s.kind==="min"?(s.inclusive?t.datas.value:t.data>=s.value)&&(n=this._getOrReturnCtx(t,n),$(n,{code:N.too_big,type:"bigint",maximum:s.value,inclusive:s.inclusive,message:s.message}),o.dirty()):s.kind==="multipleOf"?t.data%s.value!==BigInt(0)&&(n=this._getOrReturnCtx(t,n),$(n,{code:N.not_multiple_of,multipleOf:s.value,message:s.message}),o.dirty()):te.assertNever(s);return{status:o.value,value:t.data}}_getInvalidInput(t){let r=this._getOrReturnCtx(t);return $(r,{code:N.invalid_type,expected:U.bigint,received:r.parsedType}),G}gte(t,r){return this.setLimit("min",t,!0,V.toString(r))}gt(t,r){return this.setLimit("min",t,!1,V.toString(r))}lte(t,r){return this.setLimit("max",t,!0,V.toString(r))}lt(t,r){return this.setLimit("max",t,!1,V.toString(r))}setLimit(t,r,n,o){return new e({...this._def,checks:[...this._def.checks,{kind:t,value:r,inclusive:n,message:V.toString(o)}]})}_addCheck(t){return new e({...this._def,checks:[...this._def.checks,t]})}positive(t){return this._addCheck({kind:"min",value:BigInt(0),inclusive:!1,message:V.toString(t)})}negative(t){return this._addCheck({kind:"max",value:BigInt(0),inclusive:!1,message:V.toString(t)})}nonpositive(t){return this._addCheck({kind:"max",value:BigInt(0),inclusive:!0,message:V.toString(t)})}nonnegative(t){return this._addCheck({kind:"min",value:BigInt(0),inclusive:!0,message:V.toString(t)})}multipleOf(t,r){return this._addCheck({kind:"multipleOf",value:t,message:V.toString(r)})}get minValue(){let t=null;for(let r of this._def.checks)r.kind==="min"&&(t===null||r.value>t)&&(t=r.value);return t}get maxValue(){let t=null;for(let r of this._def.checks)r.kind==="max"&&(t===null||r.valuenew Fo({checks:[],typeName:Y.ZodBigInt,coerce:e?.coerce??!1,...X(e)});var Oo=class extends Q{_parse(t){if(this._def.coerce&&(t.data=!!t.data),this._getType(t)!==U.boolean){let n=this._getOrReturnCtx(t);return $(n,{code:N.invalid_type,expected:U.boolean,received:n.parsedType}),G}return at(t.data)}};Oo.create=e=>new Oo({typeName:Y.ZodBoolean,coerce:e?.coerce||!1,...X(e)});var $o=class e extends Q{_parse(t){if(this._def.coerce&&(t.data=new Date(t.data)),this._getType(t)!==U.date){let s=this._getOrReturnCtx(t);return $(s,{code:N.invalid_type,expected:U.date,received:s.parsedType}),G}if(Number.isNaN(t.data.getTime())){let s=this._getOrReturnCtx(t);return $(s,{code:N.invalid_date}),G}let n=new Ye,o;for(let s of this._def.checks)s.kind==="min"?t.data.getTime()s.value&&(o=this._getOrReturnCtx(t,o),$(o,{code:N.too_big,message:s.message,inclusive:!0,exact:!1,maximum:s.value,type:"date"}),n.dirty()):te.assertNever(s);return{status:n.value,value:new Date(t.data.getTime())}}_addCheck(t){return new e({...this._def,checks:[...this._def.checks,t]})}min(t,r){return this._addCheck({kind:"min",value:t.getTime(),message:V.toString(r)})}max(t,r){return this._addCheck({kind:"max",value:t.getTime(),message:V.toString(r)})}get minDate(){let t=null;for(let r of this._def.checks)r.kind==="min"&&(t===null||r.value>t)&&(t=r.value);return t!=null?new Date(t):null}get maxDate(){let t=null;for(let r of this._def.checks)r.kind==="max"&&(t===null||r.valuenew $o({checks:[],coerce:e?.coerce||!1,typeName:Y.ZodDate,...X(e)});var Va=class extends Q{_parse(t){if(this._getType(t)!==U.symbol){let n=this._getOrReturnCtx(t);return $(n,{code:N.invalid_type,expected:U.symbol,received:n.parsedType}),G}return at(t.data)}};Va.create=e=>new Va({typeName:Y.ZodSymbol,...X(e)});var Ho=class extends Q{_parse(t){if(this._getType(t)!==U.undefined){let n=this._getOrReturnCtx(t);return $(n,{code:N.invalid_type,expected:U.undefined,received:n.parsedType}),G}return at(t.data)}};Ho.create=e=>new Ho({typeName:Y.ZodUndefined,...X(e)});var Do=class extends Q{_parse(t){if(this._getType(t)!==U.null){let n=this._getOrReturnCtx(t);return $(n,{code:N.invalid_type,expected:U.null,received:n.parsedType}),G}return at(t.data)}};Do.create=e=>new Do({typeName:Y.ZodNull,...X(e)});var Zn=class extends Q{constructor(){super(...arguments),this._any=!0}_parse(t){return at(t.data)}};Zn.create=e=>new Zn({typeName:Y.ZodAny,...X(e)});var un=class extends Q{constructor(){super(...arguments),this._unknown=!0}_parse(t){return at(t.data)}};un.create=e=>new un({typeName:Y.ZodUnknown,...X(e)});var ir=class extends Q{_parse(t){let r=this._getOrReturnCtx(t);return $(r,{code:N.invalid_type,expected:U.never,received:r.parsedType}),G}};ir.create=e=>new ir({typeName:Y.ZodNever,...X(e)});var qa=class extends Q{_parse(t){if(this._getType(t)!==U.undefined){let n=this._getOrReturnCtx(t);return $(n,{code:N.invalid_type,expected:U.void,received:n.parsedType}),G}return at(t.data)}};qa.create=e=>new qa({typeName:Y.ZodVoid,...X(e)});var fn=class e extends Q{_parse(t){let{ctx:r,status:n}=this._processInputParams(t),o=this._def;if(r.parsedType!==U.array)return $(r,{code:N.invalid_type,expected:U.array,received:r.parsedType}),G;if(o.exactLength!==null){let a=r.data.length>o.exactLength.value,i=r.data.lengtho.maxLength.value&&($(r,{code:N.too_big,maximum:o.maxLength.value,type:"array",inclusive:!0,exact:!1,message:o.maxLength.message}),n.dirty()),r.common.async)return Promise.all([...r.data].map((a,i)=>o.type._parseAsync(new Wt(r,a,r.path,i)))).then(a=>Ye.mergeArray(n,a));let s=[...r.data].map((a,i)=>o.type._parseSync(new Wt(r,a,r.path,i)));return Ye.mergeArray(n,s)}get element(){return this._def.type}min(t,r){return new e({...this._def,minLength:{value:t,message:V.toString(r)}})}max(t,r){return new e({...this._def,maxLength:{value:t,message:V.toString(r)}})}length(t,r){return new e({...this._def,exactLength:{value:t,message:V.toString(r)}})}nonempty(t){return this.min(1,t)}};fn.create=(e,t)=>new fn({type:e,minLength:null,maxLength:null,exactLength:null,typeName:Y.ZodArray,...X(t)});function ja(e){if(e instanceof Et){let t={};for(let r in e.shape){let n=e.shape[r];t[r]=Gt.create(ja(n))}return new Et({...e._def,shape:()=>t})}else return e instanceof fn?new fn({...e._def,type:ja(e.element)}):e instanceof Gt?Gt.create(ja(e.unwrap())):e instanceof Cr?Cr.create(ja(e.unwrap())):e instanceof kr?kr.create(e.items.map(t=>ja(t))):e}var Et=class e extends Q{constructor(){super(...arguments),this._cached=null,this.nonstrict=this.passthrough,this.augment=this.extend}_getCached(){if(this._cached!==null)return this._cached;let t=this._def.shape(),r=te.objectKeys(t);return this._cached={shape:t,keys:r},this._cached}_parse(t){if(this._getType(t)!==U.object){let u=this._getOrReturnCtx(t);return $(u,{code:N.invalid_type,expected:U.object,received:u.parsedType}),G}let{status:n,ctx:o}=this._processInputParams(t),{shape:s,keys:a}=this._getCached(),i=[];if(!(this._def.catchall instanceof ir&&this._def.unknownKeys==="strip"))for(let u in o.data)a.includes(u)||i.push(u);let c=[];for(let u of a){let f=s[u],d=o.data[u];c.push({key:{status:"valid",value:u},value:f._parse(new Wt(o,d,o.path,u)),alwaysSet:u in o.data})}if(this._def.catchall instanceof ir){let u=this._def.unknownKeys;if(u==="passthrough")for(let f of i)c.push({key:{status:"valid",value:f},value:{status:"valid",value:o.data[f]}});else if(u==="strict")i.length>0&&($(o,{code:N.unrecognized_keys,keys:i}),n.dirty());else if(u!=="strip")throw new Error("Internal ZodObject error: invalid unknownKeys value.")}else{let u=this._def.catchall;for(let f of i){let d=o.data[f];c.push({key:{status:"valid",value:f},value:u._parse(new Wt(o,d,o.path,f)),alwaysSet:f in o.data})}}return o.common.async?Promise.resolve().then(async()=>{let u=[];for(let f of c){let d=await f.key,p=await f.value;u.push({key:d,value:p,alwaysSet:f.alwaysSet})}return u}).then(u=>Ye.mergeObjectSync(n,u)):Ye.mergeObjectSync(n,c)}get shape(){return this._def.shape()}strict(t){return V.errToObj,new e({...this._def,unknownKeys:"strict",...t!==void 0?{errorMap:(r,n)=>{let o=this._def.errorMap?.(r,n).message??n.defaultError;return r.code==="unrecognized_keys"?{message:V.errToObj(t).message??o}:{message:o}}}:{}})}strip(){return new e({...this._def,unknownKeys:"strip"})}passthrough(){return new e({...this._def,unknownKeys:"passthrough"})}extend(t){return new e({...this._def,shape:()=>({...this._def.shape(),...t})})}merge(t){return new e({unknownKeys:t._def.unknownKeys,catchall:t._def.catchall,shape:()=>({...this._def.shape(),...t._def.shape()}),typeName:Y.ZodObject})}setKey(t,r){return this.augment({[t]:r})}catchall(t){return new e({...this._def,catchall:t})}pick(t){let r={};for(let n of te.objectKeys(t))t[n]&&this.shape[n]&&(r[n]=this.shape[n]);return new e({...this._def,shape:()=>r})}omit(t){let r={};for(let n of te.objectKeys(this.shape))t[n]||(r[n]=this.shape[n]);return new e({...this._def,shape:()=>r})}deepPartial(){return ja(this)}partial(t){let r={};for(let n of te.objectKeys(this.shape)){let o=this.shape[n];t&&!t[n]?r[n]=o:r[n]=o.optional()}return new e({...this._def,shape:()=>r})}required(t){let r={};for(let n of te.objectKeys(this.shape))if(t&&!t[n])r[n]=this.shape[n];else{let s=this.shape[n];for(;s instanceof Gt;)s=s._def.innerType;r[n]=s}return new e({...this._def,shape:()=>r})}keyof(){return Kb(te.objectKeys(this.shape))}};Et.create=(e,t)=>new Et({shape:()=>e,unknownKeys:"strip",catchall:ir.create(),typeName:Y.ZodObject,...X(t)});Et.strictCreate=(e,t)=>new Et({shape:()=>e,unknownKeys:"strict",catchall:ir.create(),typeName:Y.ZodObject,...X(t)});Et.lazycreate=(e,t)=>new Et({shape:e,unknownKeys:"strip",catchall:ir.create(),typeName:Y.ZodObject,...X(t)});var Uo=class extends Q{_parse(t){let{ctx:r}=this._processInputParams(t),n=this._def.options;function o(s){for(let i of s)if(i.result.status==="valid")return i.result;for(let i of s)if(i.result.status==="dirty")return r.common.issues.push(...i.ctx.common.issues),i.result;let a=s.map(i=>new wt(i.ctx.common.issues));return $(r,{code:N.invalid_union,unionErrors:a}),G}if(r.common.async)return Promise.all(n.map(async s=>{let a={...r,common:{...r.common,issues:[]},parent:null};return{result:await s._parseAsync({data:r.data,path:r.path,parent:a}),ctx:a}})).then(o);{let s,a=[];for(let c of n){let u={...r,common:{...r.common,issues:[]},parent:null},f=c._parseSync({data:r.data,path:r.path,parent:u});if(f.status==="valid")return f;f.status==="dirty"&&!s&&(s={result:f,ctx:u}),u.common.issues.length&&a.push(u.common.issues)}if(s)return r.common.issues.push(...s.ctx.common.issues),s.result;let i=a.map(c=>new wt(c));return $(r,{code:N.invalid_union,unionErrors:i}),G}}get options(){return this._def.options}};Uo.create=(e,t)=>new Uo({options:e,typeName:Y.ZodUnion,...X(t)});var cn=e=>e instanceof jo?cn(e.schema):e instanceof Zt?cn(e.innerType()):e instanceof Vo?[e.value]:e instanceof qo?e.options:e instanceof Go?te.objectValues(e.enum):e instanceof Wo?cn(e._def.innerType):e instanceof Ho?[void 0]:e instanceof Do?[null]:e instanceof Gt?[void 0,...cn(e.unwrap())]:e instanceof Cr?[null,...cn(e.unwrap())]:e instanceof nc||e instanceof Ko?cn(e.unwrap()):e instanceof Zo?cn(e._def.innerType):[],wd=class e extends Q{_parse(t){let{ctx:r}=this._processInputParams(t);if(r.parsedType!==U.object)return $(r,{code:N.invalid_type,expected:U.object,received:r.parsedType}),G;let n=this.discriminator,o=r.data[n],s=this.optionsMap.get(o);return s?r.common.async?s._parseAsync({data:r.data,path:r.path,parent:r}):s._parseSync({data:r.data,path:r.path,parent:r}):($(r,{code:N.invalid_union_discriminator,options:Array.from(this.optionsMap.keys()),path:[n]}),G)}get discriminator(){return this._def.discriminator}get options(){return this._def.options}get optionsMap(){return this._def.optionsMap}static create(t,r,n){let o=new Map;for(let s of r){let a=cn(s.shape[t]);if(!a.length)throw new Error(`A discriminator value for key \`${t}\` could not be extracted from all schema options`);for(let i of a){if(o.has(i))throw new Error(`Discriminator property ${String(t)} has duplicate value ${String(i)}`);o.set(i,s)}}return new e({typeName:Y.ZodDiscriminatedUnion,discriminator:t,options:r,optionsMap:o,...X(n)})}};function nl(e,t){let r=Br(e),n=Br(t);if(e===t)return{valid:!0,data:e};if(r===U.object&&n===U.object){let o=te.objectKeys(t),s=te.objectKeys(e).filter(i=>o.indexOf(i)!==-1),a={...e,...t};for(let i of s){let c=nl(e[i],t[i]);if(!c.valid)return{valid:!1};a[i]=c.data}return{valid:!0,data:a}}else if(r===U.array&&n===U.array){if(e.length!==t.length)return{valid:!1};let o=[];for(let s=0;s{if(gd(s)||gd(a))return G;let i=nl(s.value,a.value);return i.valid?((vd(s)||vd(a))&&r.dirty(),{status:r.value,value:i.data}):($(n,{code:N.invalid_intersection_types}),G)};return n.common.async?Promise.all([this._def.left._parseAsync({data:n.data,path:n.path,parent:n}),this._def.right._parseAsync({data:n.data,path:n.path,parent:n})]).then(([s,a])=>o(s,a)):o(this._def.left._parseSync({data:n.data,path:n.path,parent:n}),this._def.right._parseSync({data:n.data,path:n.path,parent:n}))}};Lo.create=(e,t,r)=>new Lo({left:e,right:t,typeName:Y.ZodIntersection,...X(r)});var kr=class e extends Q{_parse(t){let{status:r,ctx:n}=this._processInputParams(t);if(n.parsedType!==U.array)return $(n,{code:N.invalid_type,expected:U.array,received:n.parsedType}),G;if(n.data.lengththis._def.items.length&&($(n,{code:N.too_big,maximum:this._def.items.length,inclusive:!0,exact:!1,type:"array"}),r.dirty());let s=[...n.data].map((a,i)=>{let c=this._def.items[i]||this._def.rest;return c?c._parse(new Wt(n,a,n.path,i)):null}).filter(a=>!!a);return n.common.async?Promise.all(s).then(a=>Ye.mergeArray(r,a)):Ye.mergeArray(r,s)}get items(){return this._def.items}rest(t){return new e({...this._def,rest:t})}};kr.create=(e,t)=>{if(!Array.isArray(e))throw new Error("You must pass an array of schemas to z.tuple([ ... ])");return new kr({items:e,typeName:Y.ZodTuple,rest:null,...X(t)})};var Ed=class e extends Q{get keySchema(){return this._def.keyType}get valueSchema(){return this._def.valueType}_parse(t){let{status:r,ctx:n}=this._processInputParams(t);if(n.parsedType!==U.object)return $(n,{code:N.invalid_type,expected:U.object,received:n.parsedType}),G;let o=[],s=this._def.keyType,a=this._def.valueType;for(let i in n.data)o.push({key:s._parse(new Wt(n,i,n.path,i)),value:a._parse(new Wt(n,n.data[i],n.path,i)),alwaysSet:i in n.data});return n.common.async?Ye.mergeObjectAsync(r,o):Ye.mergeObjectSync(r,o)}get element(){return this._def.valueType}static create(t,r,n){return r instanceof Q?new e({keyType:t,valueType:r,typeName:Y.ZodRecord,...X(n)}):new e({keyType:Wn.create(),valueType:t,typeName:Y.ZodRecord,...X(r)})}},Ga=class extends Q{get keySchema(){return this._def.keyType}get valueSchema(){return this._def.valueType}_parse(t){let{status:r,ctx:n}=this._processInputParams(t);if(n.parsedType!==U.map)return $(n,{code:N.invalid_type,expected:U.map,received:n.parsedType}),G;let o=this._def.keyType,s=this._def.valueType,a=[...n.data.entries()].map(([i,c],u)=>({key:o._parse(new Wt(n,i,n.path,[u,"key"])),value:s._parse(new Wt(n,c,n.path,[u,"value"]))}));if(n.common.async){let i=new Map;return Promise.resolve().then(async()=>{for(let c of a){let u=await c.key,f=await c.value;if(u.status==="aborted"||f.status==="aborted")return G;(u.status==="dirty"||f.status==="dirty")&&r.dirty(),i.set(u.value,f.value)}return{status:r.value,value:i}})}else{let i=new Map;for(let c of a){let u=c.key,f=c.value;if(u.status==="aborted"||f.status==="aborted")return G;(u.status==="dirty"||f.status==="dirty")&&r.dirty(),i.set(u.value,f.value)}return{status:r.value,value:i}}}};Ga.create=(e,t,r)=>new Ga({valueType:t,keyType:e,typeName:Y.ZodMap,...X(r)});var Wa=class e extends Q{_parse(t){let{status:r,ctx:n}=this._processInputParams(t);if(n.parsedType!==U.set)return $(n,{code:N.invalid_type,expected:U.set,received:n.parsedType}),G;let o=this._def;o.minSize!==null&&n.data.sizeo.maxSize.value&&($(n,{code:N.too_big,maximum:o.maxSize.value,type:"set",inclusive:!0,exact:!1,message:o.maxSize.message}),r.dirty());let s=this._def.valueType;function a(c){let u=new Set;for(let f of c){if(f.status==="aborted")return G;f.status==="dirty"&&r.dirty(),u.add(f.value)}return{status:r.value,value:u}}let i=[...n.data.values()].map((c,u)=>s._parse(new Wt(n,c,n.path,u)));return n.common.async?Promise.all(i).then(c=>a(c)):a(i)}min(t,r){return new e({...this._def,minSize:{value:t,message:V.toString(r)}})}max(t,r){return new e({...this._def,maxSize:{value:t,message:V.toString(r)}})}size(t,r){return this.min(t,r).max(t,r)}nonempty(t){return this.min(1,t)}};Wa.create=(e,t)=>new Wa({valueType:e,minSize:null,maxSize:null,typeName:Y.ZodSet,...X(t)});var Td=class e extends Q{constructor(){super(...arguments),this.validate=this.implement}_parse(t){let{ctx:r}=this._processInputParams(t);if(r.parsedType!==U.function)return $(r,{code:N.invalid_type,expected:U.function,received:r.parsedType}),G;function n(i,c){return rc({data:i,path:r.path,errorMaps:[r.common.contextualErrorMap,r.schemaErrorMap,Ua(),an].filter(u=>!!u),issueData:{code:N.invalid_arguments,argumentsError:c}})}function o(i,c){return rc({data:i,path:r.path,errorMaps:[r.common.contextualErrorMap,r.schemaErrorMap,Ua(),an].filter(u=>!!u),issueData:{code:N.invalid_return_type,returnTypeError:c}})}let s={errorMap:r.common.contextualErrorMap},a=r.data;if(this._def.returns instanceof Kn){let i=this;return at(async function(...c){let u=new wt([]),f=await i._def.args.parseAsync(c,s).catch(m=>{throw u.addIssue(n(c,m)),u}),d=await Reflect.apply(a,this,f);return await i._def.returns._def.type.parseAsync(d,s).catch(m=>{throw u.addIssue(o(d,m)),u})})}else{let i=this;return at(function(...c){let u=i._def.args.safeParse(c,s);if(!u.success)throw new wt([n(c,u.error)]);let f=Reflect.apply(a,this,u.data),d=i._def.returns.safeParse(f,s);if(!d.success)throw new wt([o(f,d.error)]);return d.data})}}parameters(){return this._def.args}returnType(){return this._def.returns}args(...t){return new e({...this._def,args:kr.create(t).rest(un.create())})}returns(t){return new e({...this._def,returns:t})}implement(t){return this.parse(t)}strictImplement(t){return this.parse(t)}static create(t,r,n){return new e({args:t||kr.create([]).rest(un.create()),returns:r||un.create(),typeName:Y.ZodFunction,...X(n)})}},jo=class extends Q{get schema(){return this._def.getter()}_parse(t){let{ctx:r}=this._processInputParams(t);return this._def.getter()._parse({data:r.data,path:r.path,parent:r})}};jo.create=(e,t)=>new jo({getter:e,typeName:Y.ZodLazy,...X(t)});var Vo=class extends Q{_parse(t){if(t.data!==this._def.value){let r=this._getOrReturnCtx(t);return $(r,{received:r.data,code:N.invalid_literal,expected:this._def.value}),G}return{status:"valid",value:t.data}}get value(){return this._def.value}};Vo.create=(e,t)=>new Vo({value:e,typeName:Y.ZodLiteral,...X(t)});function Kb(e,t){return new qo({values:e,typeName:Y.ZodEnum,...X(t)})}var qo=class e extends Q{_parse(t){if(typeof t.data!="string"){let r=this._getOrReturnCtx(t),n=this._def.values;return $(r,{expected:te.joinValues(n),received:r.parsedType,code:N.invalid_type}),G}if(this._cache||(this._cache=new Set(this._def.values)),!this._cache.has(t.data)){let r=this._getOrReturnCtx(t),n=this._def.values;return $(r,{received:r.data,code:N.invalid_enum_value,options:n}),G}return at(t.data)}get options(){return this._def.values}get enum(){let t={};for(let r of this._def.values)t[r]=r;return t}get Values(){let t={};for(let r of this._def.values)t[r]=r;return t}get Enum(){let t={};for(let r of this._def.values)t[r]=r;return t}extract(t,r=this._def){return e.create(t,{...this._def,...r})}exclude(t,r=this._def){return e.create(this.options.filter(n=>!t.includes(n)),{...this._def,...r})}};qo.create=Kb;var Go=class extends Q{_parse(t){let r=te.getValidEnumValues(this._def.values),n=this._getOrReturnCtx(t);if(n.parsedType!==U.string&&n.parsedType!==U.number){let o=te.objectValues(r);return $(n,{expected:te.joinValues(o),received:n.parsedType,code:N.invalid_type}),G}if(this._cache||(this._cache=new Set(te.getValidEnumValues(this._def.values))),!this._cache.has(t.data)){let o=te.objectValues(r);return $(n,{received:n.data,code:N.invalid_enum_value,options:o}),G}return at(t.data)}get enum(){return this._def.values}};Go.create=(e,t)=>new Go({values:e,typeName:Y.ZodNativeEnum,...X(t)});var Kn=class extends Q{unwrap(){return this._def.type}_parse(t){let{ctx:r}=this._processInputParams(t);if(r.parsedType!==U.promise&&r.common.async===!1)return $(r,{code:N.invalid_type,expected:U.promise,received:r.parsedType}),G;let n=r.parsedType===U.promise?r.data:Promise.resolve(r.data);return at(n.then(o=>this._def.type.parseAsync(o,{path:r.path,errorMap:r.common.contextualErrorMap})))}};Kn.create=(e,t)=>new Kn({type:e,typeName:Y.ZodPromise,...X(t)});var Zt=class extends Q{innerType(){return this._def.schema}sourceType(){return this._def.schema._def.typeName===Y.ZodEffects?this._def.schema.sourceType():this._def.schema}_parse(t){let{status:r,ctx:n}=this._processInputParams(t),o=this._def.effect||null,s={addIssue:a=>{$(n,a),a.fatal?r.abort():r.dirty()},get path(){return n.path}};if(s.addIssue=s.addIssue.bind(s),o.type==="preprocess"){let a=o.transform(n.data,s);if(n.common.async)return Promise.resolve(a).then(async i=>{if(r.value==="aborted")return G;let c=await this._def.schema._parseAsync({data:i,path:n.path,parent:n});return c.status==="aborted"?G:c.status==="dirty"?Mo(c.value):r.value==="dirty"?Mo(c.value):c});{if(r.value==="aborted")return G;let i=this._def.schema._parseSync({data:a,path:n.path,parent:n});return i.status==="aborted"?G:i.status==="dirty"?Mo(i.value):r.value==="dirty"?Mo(i.value):i}}if(o.type==="refinement"){let a=i=>{let c=o.refinement(i,s);if(n.common.async)return Promise.resolve(c);if(c instanceof Promise)throw new Error("Async refinement encountered during synchronous parse operation. Use .parseAsync instead.");return i};if(n.common.async===!1){let i=this._def.schema._parseSync({data:n.data,path:n.path,parent:n});return i.status==="aborted"?G:(i.status==="dirty"&&r.dirty(),a(i.value),{status:r.value,value:i.value})}else return this._def.schema._parseAsync({data:n.data,path:n.path,parent:n}).then(i=>i.status==="aborted"?G:(i.status==="dirty"&&r.dirty(),a(i.value).then(()=>({status:r.value,value:i.value}))))}if(o.type==="transform")if(n.common.async===!1){let a=this._def.schema._parseSync({data:n.data,path:n.path,parent:n});if(!Gn(a))return G;let i=o.transform(a.value,s);if(i instanceof Promise)throw new Error("Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.");return{status:r.value,value:i}}else return this._def.schema._parseAsync({data:n.data,path:n.path,parent:n}).then(a=>Gn(a)?Promise.resolve(o.transform(a.value,s)).then(i=>({status:r.value,value:i})):G);te.assertNever(o)}};Zt.create=(e,t,r)=>new Zt({schema:e,typeName:Y.ZodEffects,effect:t,...X(r)});Zt.createWithPreprocess=(e,t,r)=>new Zt({schema:t,effect:{type:"preprocess",transform:e},typeName:Y.ZodEffects,...X(r)});var Gt=class extends Q{_parse(t){return this._getType(t)===U.undefined?at(void 0):this._def.innerType._parse(t)}unwrap(){return this._def.innerType}};Gt.create=(e,t)=>new Gt({innerType:e,typeName:Y.ZodOptional,...X(t)});var Cr=class extends Q{_parse(t){return this._getType(t)===U.null?at(null):this._def.innerType._parse(t)}unwrap(){return this._def.innerType}};Cr.create=(e,t)=>new Cr({innerType:e,typeName:Y.ZodNullable,...X(t)});var Wo=class extends Q{_parse(t){let{ctx:r}=this._processInputParams(t),n=r.data;return r.parsedType===U.undefined&&(n=this._def.defaultValue()),this._def.innerType._parse({data:n,path:r.path,parent:r})}removeDefault(){return this._def.innerType}};Wo.create=(e,t)=>new Wo({innerType:e,typeName:Y.ZodDefault,defaultValue:typeof t.default=="function"?t.default:()=>t.default,...X(t)});var Zo=class extends Q{_parse(t){let{ctx:r}=this._processInputParams(t),n={...r,common:{...r.common,issues:[]}},o=this._def.innerType._parse({data:n.data,path:n.path,parent:{...n}});return La(o)?o.then(s=>({status:"valid",value:s.status==="valid"?s.value:this._def.catchValue({get error(){return new wt(n.common.issues)},input:n.data})})):{status:"valid",value:o.status==="valid"?o.value:this._def.catchValue({get error(){return new wt(n.common.issues)},input:n.data})}}removeCatch(){return this._def.innerType}};Zo.create=(e,t)=>new Zo({innerType:e,typeName:Y.ZodCatch,catchValue:typeof t.catch=="function"?t.catch:()=>t.catch,...X(t)});var Za=class extends Q{_parse(t){if(this._getType(t)!==U.nan){let n=this._getOrReturnCtx(t);return $(n,{code:N.invalid_type,expected:U.nan,received:n.parsedType}),G}return{status:"valid",value:t.data}}};Za.create=e=>new Za({typeName:Y.ZodNaN,...X(e)});var Iw=Symbol("zod_brand"),nc=class extends Q{_parse(t){let{ctx:r}=this._processInputParams(t),n=r.data;return this._def.type._parse({data:n,path:r.path,parent:r})}unwrap(){return this._def.type}},oc=class e extends Q{_parse(t){let{status:r,ctx:n}=this._processInputParams(t);if(n.common.async)return(async()=>{let s=await this._def.in._parseAsync({data:n.data,path:n.path,parent:n});return s.status==="aborted"?G:s.status==="dirty"?(r.dirty(),Mo(s.value)):this._def.out._parseAsync({data:s.value,path:n.path,parent:n})})();{let o=this._def.in._parseSync({data:n.data,path:n.path,parent:n});return o.status==="aborted"?G:o.status==="dirty"?(r.dirty(),{status:"dirty",value:o.value}):this._def.out._parseSync({data:o.value,path:n.path,parent:n})}}static create(t,r){return new e({in:t,out:r,typeName:Y.ZodPipeline})}},Ko=class extends Q{_parse(t){let r=this._def.innerType._parse(t),n=o=>(Gn(o)&&(o.value=Object.freeze(o.value)),o);return La(r)?r.then(o=>n(o)):n(r)}unwrap(){return this._def.innerType}};Ko.create=(e,t)=>new Ko({innerType:e,typeName:Y.ZodReadonly,...X(t)});function qb(e,t){let r=typeof e=="function"?e(t):typeof e=="string"?{message:e}:e;return typeof r=="string"?{message:r}:r}function Yb(e,t={},r){return e?Zn.create().superRefine((n,o)=>{let s=e(n);if(s instanceof Promise)return s.then(a=>{if(!a){let i=qb(t,n),c=i.fatal??r??!0;o.addIssue({code:"custom",...i,fatal:c})}});if(!s){let a=qb(t,n),i=a.fatal??r??!0;o.addIssue({code:"custom",...a,fatal:i})}}):Zn.create()}var Bw={object:Et.lazycreate},Y;(function(e){e.ZodString="ZodString",e.ZodNumber="ZodNumber",e.ZodNaN="ZodNaN",e.ZodBigInt="ZodBigInt",e.ZodBoolean="ZodBoolean",e.ZodDate="ZodDate",e.ZodSymbol="ZodSymbol",e.ZodUndefined="ZodUndefined",e.ZodNull="ZodNull",e.ZodAny="ZodAny",e.ZodUnknown="ZodUnknown",e.ZodNever="ZodNever",e.ZodVoid="ZodVoid",e.ZodArray="ZodArray",e.ZodObject="ZodObject",e.ZodUnion="ZodUnion",e.ZodDiscriminatedUnion="ZodDiscriminatedUnion",e.ZodIntersection="ZodIntersection",e.ZodTuple="ZodTuple",e.ZodRecord="ZodRecord",e.ZodMap="ZodMap",e.ZodSet="ZodSet",e.ZodFunction="ZodFunction",e.ZodLazy="ZodLazy",e.ZodLiteral="ZodLiteral",e.ZodEnum="ZodEnum",e.ZodEffects="ZodEffects",e.ZodNativeEnum="ZodNativeEnum",e.ZodOptional="ZodOptional",e.ZodNullable="ZodNullable",e.ZodDefault="ZodDefault",e.ZodCatch="ZodCatch",e.ZodPromise="ZodPromise",e.ZodBranded="ZodBranded",e.ZodPipeline="ZodPipeline",e.ZodReadonly="ZodReadonly"})(Y||(Y={}));var kw=(e,t={message:`Input not instance of ${e.name}`})=>Yb(r=>r instanceof e,t),Jb=Wn.create,Xb=zo.create,Cw=Za.create,Rw=Fo.create,Qb=Oo.create,Nw=$o.create,Mw=Va.create,zw=Ho.create,Fw=Do.create,Ow=Zn.create,$w=un.create,Hw=ir.create,Dw=qa.create,Uw=fn.create,Lw=Et.create,jw=Et.strictCreate,Vw=Uo.create,qw=wd.create,Gw=Lo.create,Ww=kr.create,Zw=Ed.create,Kw=Ga.create,Yw=Wa.create,Jw=Td.create,Xw=jo.create,Qw=Vo.create,eE=qo.create,tE=Go.create,rE=Kn.create,nE=Zt.create,oE=Gt.create,sE=Cr.create,aE=Zt.createWithPreprocess,iE=oc.create,cE=()=>Jb().optional(),uE=()=>Xb().optional(),fE=()=>Qb().optional(),dE={string:(e=>Wn.create({...e,coerce:!0})),number:(e=>zo.create({...e,coerce:!0})),boolean:(e=>Oo.create({...e,coerce:!0})),bigint:(e=>Fo.create({...e,coerce:!0})),date:(e=>$o.create({...e,coerce:!0}))};var pE=G;var Kt=F.string().min(1),ol=F.record(F.unknown()),Ad=F.record(F.unknown()).optional().nullable(),sl=Kt,t1=F.string().min(3).refine(e=>e.includes(":"),{message:"Network must be in CAIP-2 format (e.g., 'eip155:84532')"}),Yq=F.union([sl,t1]),e1=/^[\x20-\x7e]+$/,r1=F.object({url:Kt,description:F.string().nullish().transform(e=>e??void 0),mimeType:F.string().nullish().transform(e=>e??void 0),serviceName:F.string().min(1).max(32).regex(e1).nullish().transform(e=>e??void 0),tags:F.array(F.string().min(1).max(32).regex(e1)).max(5).nullish().transform(e=>e??void 0),iconUrl:F.string().max(2048).nullish().transform(e=>e??void 0)}),n1=F.object({scheme:Kt,network:sl,maxAmountRequired:Kt,resource:Kt,description:F.string(),mimeType:F.string().optional(),outputSchema:ol.optional().nullable(),payTo:Kt,maxTimeoutSeconds:F.number().positive(),asset:Kt,extra:Ad}),mE=F.object({x402Version:F.literal(1),error:F.string().optional(),accepts:F.array(n1).min(1)}),lE=F.object({x402Version:F.literal(1),scheme:Kt,network:sl,payload:ol}),al=F.object({scheme:Kt,network:t1,amount:Kt,asset:Kt,payTo:Kt,maxTimeoutSeconds:F.number().positive(),extra:Ad}),hE=F.object({x402Version:F.literal(2),error:F.string().nullish().transform(e=>e??void 0),resource:r1,accepts:F.array(al).min(1),extensions:Ad}),yE=F.object({x402Version:F.literal(2),resource:r1.nullish().transform(e=>e??void 0),accepted:al,payload:ol,extensions:Ad}),Jq=F.union([n1,al]),Xq=F.discriminatedUnion("x402Version",[mE,hE]),Qq=F.discriminatedUnion("x402Version",[lE,yE]);var il=2;var xE=e=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),bE=e=>{let t=xE(e).replace(/\\\*/g,".*");return new RegExp(`^${t}$`)},gE=(e,t)=>bE(e).test(t),cl=(e,t)=>{let r=e.get(t);if(!r){for(let[n,o]of e.entries())if(gE(n,t)){r=o;break}}return r},ul=(e,t,r)=>cl(e,r)?.get(t);var fl=/^[A-Za-z0-9+/]*={0,2}$/;function o1(e){if(typeof globalThis<"u"&&typeof globalThis.btoa=="function"){let t=new TextEncoder().encode(e),r=Array.from(t,n=>String.fromCharCode(n)).join("");return globalThis.btoa(r)}return Buffer.from(e,"utf8").toString("base64")}function dl(e){if(typeof globalThis<"u"&&typeof globalThis.atob=="function"){let t=globalThis.atob(e),r=new Uint8Array(t.length);for(let o=0;oe??void 0),invalidMessage:F.string().nullish().transform(e=>e??void 0),payer:F.string().nullish().transform(e=>e??void 0),extensions:F.record(F.string(),F.unknown()).nullish().transform(e=>e??void 0),extra:F.record(F.string(),F.unknown()).nullish().transform(e=>e??void 0)}),yG=F.object({success:F.boolean(),errorReason:F.string().nullish().transform(e=>e??void 0),errorMessage:F.string().nullish().transform(e=>e??void 0),payer:F.string().nullish().transform(e=>e??void 0),transaction:F.string(),network:F.custom(e=>typeof e=="string"),amount:F.string().nullish().transform(e=>e??void 0),extensions:F.record(F.string(),F.unknown()).nullish().transform(e=>e??void 0),extra:F.record(F.string(),F.unknown()).nullish().transform(e=>e??void 0)}),wE=F.object({x402Version:F.number(),scheme:F.string(),network:F.custom(e=>typeof e=="string"),extra:F.record(F.string(),F.unknown()).nullish().transform(e=>e??void 0)}),xG=F.object({kinds:F.array(wE),extensions:F.array(F.string()).default([]),signers:F.record(F.string(),F.array(F.string())).default({})});var Yo=class{constructor(e){this.client=e,this.paymentRequiredHooks=[]}onPaymentRequired(e){return this.paymentRequiredHooks.push(e),this}async handlePaymentRequired(e){for(let t of this.getPaymentRequiredHooks(e)){let r=await t({paymentRequired:e});if(r?.headers)return r.headers}return null}encodePaymentSignatureHeader(e){switch(e.x402Version){case 2:return{"PAYMENT-SIGNATURE":pl(e)};case 1:return{"X-PAYMENT":pl(e)};default:throw new Error(`Unsupported x402 version: ${e.x402Version}`)}}getPaymentRequiredResponse(e,t){let r=e("PAYMENT-REQUIRED");if(r)return ml(r);if(t&&t instanceof Object&&"x402Version"in t&&t.x402Version===1)return t;throw new Error("Invalid payment required response")}getPaymentSettleResponse(e){let t=e("PAYMENT-RESPONSE");if(t)return Ka(t);let r=e("X-PAYMENT-RESPONSE");if(r)return Ka(r);throw new Error("Payment response header not found")}async createPaymentPayload(e){return this.client.createPaymentPayload(e)}async processPaymentResult(e,t,r){let n;try{n=this.getPaymentSettleResponse(t)}catch{}if(e.x402Version===1)return{recovered:!1,settleResponse:n};let o;if(!n&&r===402)try{o=this.getPaymentRequiredResponse(t)}catch{}let s=e.accepted;if(!s)throw new Error("Invalid x402 v2 payment payload: missing `accepted`");let a={paymentPayload:e,requirements:s,...n?{settleResponse:n}:{},...o?{paymentRequired:o}:{}};return{recovered:(await this.client.handlePaymentResponse(a))?.recovered===!0,settleResponse:n}}parsePaymentResult(e){let{status:t,getHeader:r,body:n}=e,o;try{o=this.getPaymentSettleResponse(r)}catch{if(t===402)try{o=this.getPaymentRequiredResponse(r,n)}catch{}}let s="none";return o&&!("success"in o)&&(s="payment_required"),o&&"success"in o&&(s=o.success?"settled":"settle_failed"),{status:t,paymentStatus:s,body:n,header:o}}async processResponse(e){let t=o=>e.headers.get(o),n=(e.headers.get("content-type")??"").includes("application/json")?await e.json():await e.text();return this.parsePaymentResult({status:e.status,getHeader:t,body:n})}getPaymentRequiredHooks(e){let t=[...this.paymentRequiredHooks],r=e.extensions;if(!r)return t;for(let n of this.client.getExtensions()){let s=n.transportHooks?.http?.onPaymentRequired;!s||!(n.key in r)||t.push(a=>s(r[n.key],a))}return t}};function pl(e){return o1(JSON.stringify(e))}function ml(e){if(!fl.test(e))throw new Error("Invalid payment required header");return JSON.parse(dl(e))}function Ka(e){if(!fl.test(e))throw new Error("Invalid payment response header");return JSON.parse(dl(e))}var Pd=class s1{constructor(t){this.registeredClientSchemes=new Map,this.schemeClientHookAdapters=new Map,this.policies=[],this.registeredExtensions=new Map,this.beforePaymentCreationHooks=[],this.afterPaymentCreationHooks=[],this.onPaymentCreationFailureHooks=[],this.paymentResponseHooks=[],this.paymentRequirementsSelector=t||((r,n)=>n[0])}static fromConfig(t){let r=new s1(t.paymentRequirementsSelector);return t.schemes.forEach(n=>{n.x402Version===1?r.registerV1(n.network,n.client):r.register(n.network,n.client)}),t.policies?.forEach(n=>{r.registerPolicy(n)}),r}register(t,r){return this._registerScheme(il,t,r)}registerV1(t,r){return this._registerScheme(1,t,r)}registerPolicy(t){return this.policies.push(t),this}registerExtension(t){return this.registeredExtensions.set(t.key,t),this}getExtensions(){return Array.from(this.registeredExtensions.values())}onBeforePaymentCreation(t){return this.beforePaymentCreationHooks.push(t),this}onAfterPaymentCreation(t){return this.afterPaymentCreationHooks.push(t),this}onPaymentCreationFailure(t){return this.onPaymentCreationFailureHooks.push(t),this}onPaymentResponse(t){return this.paymentResponseHooks.push(t),this}async handlePaymentResponse(t){for(let r of this.getLabeledHooks("onPaymentResponse",t.paymentPayload.x402Version,t.requirements,t.paymentRequired?.extensions??t.paymentPayload.extensions)){let n=await r(t);if(n&&"recovered"in n&&n.recovered)return{recovered:!0}}}async createPaymentPayload(t){let r=this.registeredClientSchemes.get(t.x402Version);if(!r)throw new Error(`No client registered for x402 version: ${t.x402Version}`);let n=this.selectPaymentRequirements(t.x402Version,t.accepts),o={paymentRequired:t,selectedRequirements:n};for(let s of this.getLabeledHooks("beforePaymentCreation",t.x402Version,n,t.extensions)){let a=await s(o);if(a&&"abort"in a&&a.abort)throw new Error(`Payment creation aborted: ${a.reason}`)}try{let s=ul(r,n.scheme,n.network);if(!s)throw new Error(`No client registered for scheme: ${n.scheme} and network: ${n.network}`);let a=await s.createPaymentPayload(t.x402Version,n,{extensions:t.extensions}),i;if(a.x402Version==1)i=a;else{let u=this.mergeExtensions(t.extensions,a.extensions);i={x402Version:a.x402Version,payload:a.payload,extensions:u,resource:t.resource,accepted:n}}i=await this.enrichPaymentPayloadWithExtensions(i,t);let c={...o,paymentPayload:i};for(let u of this.getLabeledHooks("afterPaymentCreation",t.x402Version,n,t.extensions))await u(c);return i}catch(s){let a={...o,error:s};for(let i of this.getLabeledHooks("onPaymentCreationFailure",t.x402Version,n,t.extensions)){let c=await i(a);if(c&&"recovered"in c&&c.recovered)return c.payload}throw s}}mergeExtensions(t,r){if(!r)return t;if(!t)return r;let n={...t};for(let[o,s]of Object.entries(r)){let a=n[o];if(a===null||typeof a!="object"||Array.isArray(a)||s===null||typeof s!="object"||Array.isArray(s)){n[o]=s;continue}let i=a,c=s,u={...i},f=[{target:u,source:c}];for(let d of f)for(let[p,m]of Object.entries(d.source)){let l=d.target[p];if(l!==null&&typeof l=="object"&&!Array.isArray(l)&&m!==null&&typeof m=="object"&&!Array.isArray(m)){let h={...l};d.target[p]=h,f.push({target:h,source:m});continue}Object.prototype.hasOwnProperty.call(d.target,p)||(d.target[p]=m)}n[o]=u}return n}async enrichPaymentPayloadWithExtensions(t,r){if(!r.extensions||this.registeredExtensions.size===0)return t;let n=t;for(let[o,s]of this.registeredExtensions)o in r.extensions&&s.enrichPaymentPayload&&(n=await s.enrichPaymentPayload(n,r));return{...n,extensions:this.mergeExtensions(r.extensions,n.extensions)}}selectPaymentRequirements(t,r){let n=this.registeredClientSchemes.get(t);if(!n)throw new Error(`No client registered for x402 version: ${t}`);let o=r.filter(a=>{let i=cl(n,a.network);return i?i.has(a.scheme):!1});if(o.length===0)throw new Error(`No network/scheme registered for x402 version: ${t} which comply with the payment requirements. ${JSON.stringify({x402Version:t,paymentRequirements:r,x402Versions:Array.from(this.registeredClientSchemes.keys()),networks:Array.from(n.keys()),schemes:Array.from(n.values()).map(a=>Array.from(a.keys())).flat()})}`);let s=o;for(let a of this.policies)if(s=a(t,s),s.length===0)throw new Error(`All payment requirements were filtered out by policies for x402 version: ${t}`);return this.paymentRequirementsSelector(t,s)}_registerScheme(t,r,n){this.registeredClientSchemes.has(t)||this.registeredClientSchemes.set(t,new Map);let o=this.registeredClientSchemes.get(t);o.has(r)||o.set(r,new Map),o.get(r).set(n.scheme,n),this.schemeClientHookAdapters.has(t)||this.schemeClientHookAdapters.set(t,new Map);let a=this.schemeClientHookAdapters.get(t);a.has(r)||a.set(r,new Map);let i=a.get(r),c=n.schemeHooks;if(!c)return i.delete(n.scheme),this;let u={};return c.onBeforePaymentCreation&&(u.beforePaymentCreation=c.onBeforePaymentCreation),c.onAfterPaymentCreation&&(u.afterPaymentCreation=c.onAfterPaymentCreation),c.onPaymentCreationFailure&&(u.onPaymentCreationFailure=c.onPaymentCreationFailure),c.onPaymentResponse&&(u.onPaymentResponse=c.onPaymentResponse),Object.keys(u).length>0?i.set(n.scheme,u):i.delete(n.scheme),this}getLabeledHooks(t,r,n,o){let s;switch(t){case"beforePaymentCreation":s=this.beforePaymentCreationHooks;break;case"afterPaymentCreation":s=this.afterPaymentCreationHooks;break;case"onPaymentCreationFailure":s=this.onPaymentCreationFailureHooks;break;case"onPaymentResponse":s=this.paymentResponseHooks;break}let a=[...s],i=this.schemeClientHookAdapters.get(r),u=(i?ul(i,n.scheme,n.network):void 0)?.[t];if(u!==void 0&&a.push(u),!o)return a;let f=this.getClientExtensionHookKey(t);for(let[d,p]of this.registeredExtensions){if(!(d in o))continue;let m=p.hooks?.[f];m&&a.push((async l=>m(o[d],l)))}return a}getClientExtensionHookKey(t){switch(t){case"beforePaymentCreation":return"onBeforePaymentCreation";case"afterPaymentCreation":return"onAfterPaymentCreation";case"onPaymentCreationFailure":return"onPaymentCreationFailure";case"onPaymentResponse":return"onPaymentResponse"}}};function EE(e,t){let r=t instanceof Yo?t:new Yo(t);return async(n,o)=>{let s=new Request(n,o),a=s.clone(),i=await e(s);if(i.status!==402)return i;let c;try{let l=x=>i.headers.get(x),h;try{let x=await i.text();x&&(h=JSON.parse(x))}catch{}c=r.getPaymentRequiredResponse(l,h)}catch(l){throw new Error(`Failed to parse payment requirements: ${l instanceof Error?l.message:"Unknown error"}`)}let u=await r.handlePaymentRequired(c);if(u){let l=a.clone();for(let[x,b]of Object.entries(u))l.headers.set(x,b);let h=await e(l);if(h.status!==402)return h}let f;try{f=await t.createPaymentPayload(c)}catch(l){throw new Error(`Failed to create payment payload: ${l instanceof Error?l.message:"Unknown error"}`)}let d=r.encodePaymentSignatureHeader(f);if(a.headers.has("PAYMENT-SIGNATURE")||a.headers.has("X-PAYMENT"))throw new Error("Payment already attempted");for(let[l,h]of Object.entries(d))a.headers.set(l,h);a.headers.set("Access-Control-Expose-Headers","PAYMENT-RESPONSE,X-PAYMENT-RESPONSE");let p=await e(a);if((await r.processPaymentResult(f,l=>p.headers.get(l),p.status)).recovered){let l=await t.createPaymentPayload(c),h=r.encodePaymentSignatureHeader(l),x=new Request(n,o);for(let[A,v]of Object.entries(h))x.headers.set(A,v);x.headers.set("Access-Control-Expose-Headers","PAYMENT-RESPONSE,X-PAYMENT-RESPONSE");let b=await e(x);return await r.processPaymentResult(l,A=>b.headers.get(A),b.status),b}return p}}var Sd={TransferWithAuthorization:[{name:"from",type:"address"},{name:"to",type:"address"},{name:"value",type:"uint256"},{name:"validAfter",type:"uint256"},{name:"validBefore",type:"uint256"},{name:"nonce",type:"bytes32"}]},ll={PermitWitnessTransferFrom:[{name:"permitted",type:"TokenPermissions"},{name:"spender",type:"address"},{name:"nonce",type:"uint256"},{name:"deadline",type:"uint256"},{name:"witness",type:"Witness"}],TokenPermissions:[{name:"token",type:"address"},{name:"amount",type:"uint256"}],Witness:[{name:"to",type:"address"},{name:"validAfter",type:"uint256"}]};var a1={Permit:[{name:"owner",type:"address"},{name:"spender",type:"address"},{name:"value",type:"uint256"},{name:"nonce",type:"uint256"},{name:"deadline",type:"uint256"}]},i1=[{type:"function",name:"nonces",inputs:[{name:"owner",type:"address"}],outputs:[{type:"uint256"}],stateMutability:"view"}],_d=[{type:"function",name:"approve",inputs:[{name:"spender",type:"address"},{name:"amount",type:"uint256"}],outputs:[{type:"bool"}],stateMutability:"nonpayable"}],Ya=[{type:"function",name:"allowance",inputs:[{name:"owner",type:"address"},{name:"spender",type:"address"}],outputs:[{type:"uint256"}],stateMutability:"view"}],c1=70000n,u1=1000000000n,f1=100000000n,Yt="0x000000000022D473030F116dDEE9F6B43aC78BA3",hl="0x402085c248EeA27D92E8b30b2C58ed07f9E20001";var ZG=oe(new TextEncoder().encode("PaymentInfo(address operator,address payer,address receiver,address token,uint120 maxAmount,uint48 preApprovalExpiry,uint48 authorizationExpiry,uint48 refundExpiry,uint16 minFeeBps,uint16 maxFeeBps,address feeReceiver,uint256 salt)"));function cr(e){if(e.startsWith("eip155:")){let t=e.split(":")[1],r=parseInt(t,10);if(isNaN(r))throw new Error(`Invalid CAIP-2 chain ID: ${e}`);return r}throw new Error(`Unsupported network format: ${e} (expected eip155:CHAIN_ID)`)}function d1(){let e=globalThis.crypto;if(!e)throw new Error("Crypto API not available");return e}function Id(){return ce(d1().getRandomValues(new Uint8Array(32)))}function Bd(){let e=d1().getRandomValues(new Uint8Array(32));return BigInt(ce(e)).toString()}var tW=oe(it("ChannelConfig(address payer,address payerAuthorizer,address receiver,address receiverAuthorizer,address token,uint40 withdrawDelay,bytes32 salt)"));var p1="eip2612GasSponsoring",m1="erc20ApprovalGasSponsoring",TE="1";async function AE(e,t,r,n,o,s,a){let i=e.address,c=de(Yt),u=await e.readContract({address:t,abi:i1,functionName:"nonces",args:[i]}),f={name:r,version:n,chainId:o,verifyingContract:t},d=BigInt(a),p={owner:i,spender:c,value:d,nonce:u,deadline:BigInt(s)},m=await e.signTypedData({domain:f,types:a1,primaryType:"Permit",message:p});return{from:i,asset:t,spender:c,amount:d.toString(),nonce:u.toString(),deadline:s,signature:m,version:"1"}}async function PE(e,t,r){let n=e.address,o=de(Yt),s=ie({abi:_d,functionName:"approve",args:[o,gr]}),a=await e.getTransactionCount({address:n}),i,c;try{let f=await e.estimateFeesPerGas?.();if(!f)throw new Error("no fee estimates available");i=f.maxFeePerGas,c=f.maxPriorityFeePerGas}catch{i=u1,c=f1}let u=await e.signTransaction({to:t,data:s,nonce:a,gas:c1,maxFeePerGas:i,maxPriorityFeePerGas:c,chainId:r});return{from:n,asset:t,spender:o,amount:gr.toString(),signedTransaction:u,version:TE}}var l1=new Map;function SE(e){let t=Object.keys(e);return t.length>0&&t.every(r=>/^\d+$/.test(r))}function _E(e){let t=l1.get(e);if(t)return t;let r=ld({transport:bd(e)});return l1.set(e,r),r}function IE(e,t){if(t){if(SE(t)){let r=cr(e);return t[r]?.rpcUrl}return t.rpcUrl}}function h1(e,t,r){let n={signTransaction:t.signTransaction,readContract:t.readContract,getTransactionCount:t.getTransactionCount,estimateFeesPerGas:t.estimateFeesPerGas};if(!(!n.readContract||!n.getTransactionCount||!n.estimateFeesPerGas))return n;let s=IE(e,r);if(!s)return n;let a=_E(s);return n.readContract||(n.readContract=i=>a.readContract(i)),n.getTransactionCount||(n.getTransactionCount=async i=>a.getTransactionCount({address:i.address})),n.estimateFeesPerGas||(n.estimateFeesPerGas=async()=>a.estimateFeesPerGas()),n}async function kd(e,t,r,n,o,s){let a=h1(r.network,e,t);if(!a.readContract||!o?.extensions?.[p1])return;let i=r.extra?.name,c=r.extra?.version;if(!i||!c)return;let u=cr(r.network),f=de(r.asset),d=s??r.amount;try{if(await a.readContract({address:f,abi:Ya,functionName:"allowance",args:[e.address,Yt]})>=BigInt(d))return}catch{}let m=n.payload?.permit2Authorization?.deadline??Math.floor(Date.now()/1e3+r.maxTimeoutSeconds).toString(),l=await AE({address:e.address,signTypedData:h=>e.signTypedData(h),readContract:a.readContract},f,i,c,u,m,d);return{[p1]:{info:l}}}async function Cd(e,t,r,n,o){let s=h1(r.network,e,t);if(!s.readContract||!n?.extensions?.[m1]||!s.signTransaction||!s.getTransactionCount)return;let a=cr(r.network),i=de(r.asset),c=o??r.amount;try{if(await s.readContract({address:i,abi:Ya,functionName:"allowance",args:[e.address,Yt]})>=BigInt(c))return}catch{}let u=await PE({address:e.address,signTransaction:s.signTransaction,getTransactionCount:s.getTransactionCount,estimateFeesPerGas:s.estimateFeesPerGas},i,a);return{[m1]:{info:u}}}var HE={ethereum:1,sepolia:11155111,abstract:2741,"abstract-testnet":11124,"base-sepolia":84532,base:8453,"avalanche-fuji":43113,avalanche:43114,iotex:4689,sei:1329,"sei-testnet":1328,polygon:137,"polygon-amoy":80002,peaq:3338,story:1514,educhain:41923,"skale-base-sepolia":324705682,megaeth:4326,monad:143,stable:988,"stable-testnet":2201},DE=Object.keys(HE);async function x1(e,t,r,n){let o=Math.floor(Date.now()/1e3),s=Bd(),a="0",i=(o+n.maxTimeoutSeconds).toString(),c={from:t.address,permitted:{token:de(n.asset),amount:n.amount},spender:e,nonce:s,deadline:i,witness:{to:de(n.payTo),validAfter:a}},u=await UE(t,c,n);return{x402Version:r,payload:{signature:u,permit2Authorization:c}}}async function UE(e,t,r){let n=cr(r.network);return await e.signTypedData({domain:{name:"Permit2",chainId:n,verifyingContract:Yt},types:ll,primaryType:"PermitWitnessTransferFrom",message:{permitted:{token:de(t.permitted.token),amount:BigInt(t.permitted.amount)},spender:de(t.spender),nonce:BigInt(t.nonce),deadline:BigInt(t.deadline),witness:{to:de(t.witness.to),validAfter:BigInt(t.witness.validAfter)}}})}var GZ=BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");async function b1(e,t,r){return x1(hl,e,t,r)}async function LE(e,t,r){let n=Id(),o=Math.floor(Date.now()/1e3),s={from:e.address,to:de(r.payTo),value:r.amount,validAfter:"0",validBefore:(o+r.maxTimeoutSeconds).toString(),nonce:n},a=await jE(e,s,r);return{x402Version:t,payload:{authorization:s,signature:a}}}async function jE(e,t,r){let n=cr(r.network);if(!r.extra?.name||!r.extra?.version)throw new Error(`EIP-712 domain parameters (name, version) are required in payment requirements for asset ${r.asset}`);let{name:o,version:s}=r.extra,a={name:o,version:s,chainId:n,verifyingContract:de(r.asset)},i={from:de(t.from),to:de(t.to),value:BigInt(t.value),validAfter:BigInt(t.validAfter),validBefore:BigInt(t.validBefore),nonce:t.nonce};return await e.signTypedData({domain:a,types:Sd,primaryType:"TransferWithAuthorization",message:i})}var g1=class{constructor(e,t){this.signer=e,this.options=t,this.scheme="exact"}async createPaymentPayload(e,t,r){if((t.extra?.assetTransferMethod??"eip3009")==="permit2"){let o=await b1(this.signer,e,t),s=await kd(this.signer,this.options,t,o,r);if(s)return{...o,extensions:s};let a=await Cd(this.signer,this.options,t,r);return a?{...o,extensions:a}:o}return LE(this.signer,e,t)}};function GE(e,t){let r=e.readContract??t?.readContract.bind(t),n={address:e.address,signTypedData:i=>e.signTypedData(i)};r&&(n.readContract=r);let o=e.signTransaction;o&&(n.signTransaction=i=>o(i));let s=e.getTransactionCount??t?.getTransactionCount?.bind(t);s&&(n.getTransactionCount=i=>s(i));let a=e.estimateFeesPerGas??t?.estimateFeesPerGas?.bind(t);return a&&(n.estimateFeesPerGas=()=>a()),n}export{g1 as ExactEvmScheme,Qm as base,el as baseSepolia,ld as createPublicClient,Cb as createWalletClient,Rb as custom,_e as erc20Abi,zt as formatUnits,bd as http,oe as keccak256,nd as parseUnits,Hb as privateKeyToAccount,GE as toClientEvmSigner,EE as wrapFetchWithPayment,Pd as x402Client}; +/*! Bundled license information: + +@noble/hashes/esm/utils.js: + (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *) + +@noble/curves/esm/abstract/utils.js: +@noble/curves/esm/abstract/modular.js: +@noble/curves/esm/abstract/curve.js: +@noble/curves/esm/abstract/weierstrass.js: +@noble/curves/esm/_shortw_utils.js: +@noble/curves/esm/secp256k1.js: + (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *) +*/ diff --git a/internal/serviceoffercontroller/assets/chat.html b/internal/serviceoffercontroller/assets/chat.html new file mode 100644 index 00000000..63f8284c --- /dev/null +++ b/internal/serviceoffercontroller/assets/chat.html @@ -0,0 +1,444 @@ + + + + + +{{.Title}} — chat + + + + +
+

{{.Title}}

+ /message · +
+
+
Pay-per-message chat — connect a wallet, fund a small session once, then every message is paid automatically. No account needed.
+
+
+ session + + $0.00 + + + +
+
+
+ + + diff --git a/internal/serviceoffercontroller/catalog.go b/internal/serviceoffercontroller/catalog.go index 97327377..0d920180 100644 --- a/internal/serviceoffercontroller/catalog.go +++ b/internal/serviceoffercontroller/catalog.go @@ -51,7 +51,8 @@ func staticSiteContentMatches(cm *unstructured.Unstructured, content, servicesJS if data["skill.md"] != content || data["services.json"] != servicesJSON || data["openapi.json"] != openAPIJSON || - data["api.html"] != apiDocsHTML { + data["api.html"] != apiDocsHTML || + data["chat-vendor.js"] != chatWidgetVendorJS { return false } // Per-offer bundles: every expected file present + identical, and no @@ -87,7 +88,11 @@ func (c *Controller) staticSiteContentUnchanged(ctx context.Context, content, se } func computeStaticSiteContentHash(content, servicesJSON, openAPIJSON, apiDocsHTML string, bundles []offerBundleFile) string { - return fmt.Sprintf("%x", md5Sum(content+servicesJSON+openAPIJSON+apiDocsHTML+bundleDigestInput(bundles)))[:8] + // The embedded vendor bundle is part of the served content: fold it in + // so a controller upgrade that changes it re-applies the ConfigMap and + // rolls the httpd (otherwise the skip-when-unchanged fast path pins the + // old asset forever). The per-offer chat pages flow through bundles. + return fmt.Sprintf("%x", md5Sum(content+servicesJSON+openAPIJSON+apiDocsHTML+chatWidgetVendorJS+bundleDigestInput(bundles)))[:8] } func staticSiteDeployedContentHash(deployment *unstructured.Unstructured) string { diff --git a/internal/serviceoffercontroller/catalog_test.go b/internal/serviceoffercontroller/catalog_test.go index 184a5939..309a2378 100644 --- a/internal/serviceoffercontroller/catalog_test.go +++ b/internal/serviceoffercontroller/catalog_test.go @@ -49,3 +49,21 @@ func TestStaticSiteDeployedContentHash(t *testing.T) { t.Fatalf("missing annotation hash = %q, want empty", got) } } + +// TestStaticSiteStaleChatWidgetTriggersUpdate pins the upgrade path: a +// deployed ConfigMap whose chat widget differs from the binary's embedded +// copy must NOT match, otherwise the skip-when-unchanged fast path pins the +// old asset across controller upgrades forever. (Per-offer chat pages flow +// through the offer bundles, which the match already covers.) +func TestStaticSiteStaleChatWidgetTriggersUpdate(t *testing.T) { + cm := buildStaticSiteConfigMap("# cat", `{}`, `{}`, "", nil) + if !staticSiteContentMatches(cm, "# cat", `{}`, `{}`, "", nil) { + t.Fatalf("fresh ConfigMap should match its own inputs") + } + if err := unstructured.SetNestedField(cm.Object, "stale vendor", "data", "chat-vendor.js"); err != nil { + t.Fatal(err) + } + if staticSiteContentMatches(cm, "# cat", `{}`, `{}`, "", nil) { + t.Fatalf("stale chat-vendor.js must trigger a ConfigMap update") + } +} diff --git a/internal/serviceoffercontroller/chatwidget.go b/internal/serviceoffercontroller/chatwidget.go new file mode 100644 index 00000000..d9e3b11e --- /dev/null +++ b/internal/serviceoffercontroller/chatwidget.go @@ -0,0 +1,63 @@ +package serviceoffercontroller + +import ( + _ "embed" + "html/template" + "strings" + + "github.com/ObolNetwork/obol-stack/internal/monetizeapi" + "github.com/ObolNetwork/obol-stack/internal/schemas" + "github.com/ObolNetwork/obol-stack/internal/storefront" +) + +// The agent chat widget: a self-contained browser chat client served free on +// every agent-type offer's dedicated origin at /chat (and embedded on the +// offer's landing page). The page discovers pricing at runtime from its own +// origin — price, model, payment network and asset come from the 402 +// challenge on POST /v1/chat/completions — while identity and theme are +// rendered per offer: the template receives the offer's display name and the +// same resolved storefront theme tokens as its landing page, so default and +// branded designs flow through identically. +// +// Payment is fully client-side: the visitor connects an injected wallet, +// signs one fixed message ("sign in with Ethereum") whose keccak256 becomes +// a deterministic local session key, funds that session address with a small +// USDC transfer, and every chat turn is then paid silently via x402 +// (EIP-3009 transferWithAuthorization signed by the session key — gasless +// for the payer). The session key never leaves the page and is re-derived by +// re-signing the same message, so nothing is persisted. +// +//go:embed assets/chat.html +var chatWidgetTmplSrc string + +var chatWidgetTmpl = template.Must(template.New("chat_widget").Parse(chatWidgetTmplSrc)) + +// chatWidgetVendorJS is the widget's only dependency: viem 2.21.25 + +// @x402/fetch 2.18.0 + @x402/evm 2.18.0 bundled into one ESM file so the +// page loads with zero external requests (no CDN, works on air-gapped +// stacks). Served once at the catalog httpd root — per-offer /chat pages +// import it behind a content-hash ?v= cache-buster. Rebuild: see +// assets/README.md. +// +//go:embed assets/chat-vendor.js +var chatWidgetVendorJS string + +// buildOfferChatHTML renders the offer's /chat page with the same title and +// resolved theme as its landing page. +func buildOfferChatHTML(offer *monetizeapi.ServiceOffer, profile schemas.StorefrontProfile) string { + title := strings.TrimSpace(offer.Spec.Registration.Name) + if title == "" { + title = offer.Name + } + theme := storefront.ResolveTheme(profile.Theme, profile.AccentColor) + var out strings.Builder + err := chatWidgetTmpl.Execute(&out, map[string]any{ + "Title": title, + "OfferName": offer.Name, + "ThemeCSS": template.CSS(theme.CSSVars()), + }) + if err != nil { + return "" + template.HTMLEscapeString(title) + "" + } + return out.String() +} diff --git a/internal/serviceoffercontroller/hostoffer_test.go b/internal/serviceoffercontroller/hostoffer_test.go index 1f47ff85..bcdffd11 100644 --- a/internal/serviceoffercontroller/hostoffer_test.go +++ b/internal/serviceoffercontroller/hostoffer_test.go @@ -1,7 +1,9 @@ package serviceoffercontroller import ( + "crypto/sha256" "encoding/json" + "fmt" "strings" "testing" @@ -46,9 +48,9 @@ func TestBuildHostHTTPRoute(t *testing.T) { // Rule shapes: first three Exact → catalog httpd with full-path // rewrite; last PathPrefix / → verifier with prefix rewrite + headers. wantExact := map[string]string{ - "/": "/offers/sec/audit/index.html", - "/openapi.json": "/offers/sec/audit/openapi.json", - "/.well-known/x402": "/offers/sec/audit/x402.json", + "/": "/offers/sec/audit/index.html", + "/openapi.json": "/offers/sec/audit/openapi.json", + "/.well-known/x402": "/offers/sec/audit/x402.json", "/.well-known/agent-registration.json": "/offers/sec/audit/agent-registration.json", } for i := 0; i < 4; i++ { @@ -273,6 +275,164 @@ func TestCatalogAdvertisesDedicatedOrigin(t *testing.T) { } } +// TestBuildHostHTTPRoute_AgentChatWidget pins the agent-only chat rules: +// Exact /chat and /chat-vendor.js rewrite to the SHARED widget files at the +// catalog httpd root (not the offer bundle dir), sit between the discovery +// rules and the catch-all so they win over the payment gate, and do not +// exist at all for non-agent offers (covered by TestBuildHostHTTPRoute's +// rule-count pin). +func TestBuildHostHTTPRoute_AgentChatWidget(t *testing.T) { + offer := hostnameOffer() + offer.Spec.Type = "agent" + route := buildHostHTTPRoute(offer) + + rules, _, _ := unstructured.NestedSlice(route.Object, "spec", "rules") + if len(rules) != 7 { + t.Fatalf("rules = %d, want 7 (4 discovery + /chat + /chat-vendor.js + catch-all)", len(rules)) + } + + wantShared := map[string]string{ + "/chat": "/offers/sec/audit/chat.html", + "/chat-vendor.js": "/chat-vendor.js", + } + for i := 4; i <= 5; i++ { + rule := rules[i].(map[string]any) + match := rule["matches"].([]any)[0].(map[string]any)["path"].(map[string]any) + if match["type"] != "Exact" { + t.Errorf("rule %d match type = %v, want Exact", i, match["type"]) + } + public := match["value"].(string) + want, ok := wantShared[public] + if !ok { + t.Fatalf("rule %d matches unexpected path %q", i, public) + } + filter := rule["filters"].([]any)[0].(map[string]any) + rewrite := filter["urlRewrite"].(map[string]any)["path"].(map[string]any) + if got := rewrite["replaceFullPath"]; got != want { + t.Errorf("rule %d (%s) rewrites to %v, want %s", i, public, got, want) + } + backend := rule["backendRefs"].([]any)[0].(map[string]any) + if backend["name"] != staticSiteConfigMapName || backend["namespace"] != staticSiteNamespace { + t.Errorf("rule %d backend = %v", i, backend) + } + } + + // /chat holds a hot session key and signs USDC transfers — it must carry + // frame-ancestors 'self' so it can't be clickjacked into a cross-origin + // iframe (the offer's own landing page still embeds it same-origin). + chatRule := rules[4].(map[string]any) + var sawCSP bool + for _, rawFilter := range chatRule["filters"].([]any) { + filter := rawFilter.(map[string]any) + if filter["type"] != "ResponseHeaderModifier" { + continue + } + for _, s := range filter["responseHeaderModifier"].(map[string]any)["set"].([]any) { + h := s.(map[string]any) + if h["name"] == "Content-Security-Policy" && h["value"] == "frame-ancestors 'self'" { + sawCSP = true + } + } + } + if !sawCSP { + t.Errorf("/chat rule missing Content-Security-Policy: frame-ancestors 'self'") + } + + // Catch-all must still be last. + last := rules[6].(map[string]any) + match := last["matches"].([]any)[0].(map[string]any)["path"].(map[string]any) + if match["type"] != "PathPrefix" { + t.Fatalf("last rule = %v, want the PathPrefix catch-all", match) + } +} + +// TestOfferLandingChatEmbed pins the landing-page widget embed: agent offers +// get the chat card iframing /chat; everything else keeps the plain landing. +func TestOfferLandingChatEmbed(t *testing.T) { + profile := schemas.StorefrontProfile{DisplayName: "Acme", ContactEmail: "ops@acme.example"} + + plain := buildOfferLandingHTML(hostnameOffer(), profile) + if strings.Contains(plain, `data-obol="chat"`) { + t.Fatalf("non-agent landing embeds the chat widget") + } + + agent := hostnameOffer() + agent.Spec.Type = "agent" + withChat := buildOfferLandingHTML(agent, profile) + if !strings.Contains(withChat, `data-obol="chat"`) || !strings.Contains(withChat, `src="/chat"`) { + t.Fatalf("agent landing missing chat embed:\n%s", withChat) + } +} + +// TestStaticSiteServesChatWidget pins the widget delivery: the shared +// vendor bundle sits in the catalog ConfigMap (served with a JavaScript +// MIME type — module imports hard-fail otherwise), while the chat page is +// rendered per agent offer into its bundle with the offer's title and the +// same resolved theme tokens as its landing page. +func TestStaticSiteServesChatWidget(t *testing.T) { + cm := buildStaticSiteConfigMap("# cat", `{"services":[]}`, `{}`, "", nil) + data, _, _ := unstructured.NestedStringMap(cm.Object, "data") + if data["chat-vendor.js"] == "" { + t.Fatalf("catalog ConfigMap missing the shared chat vendor bundle") + } + if !strings.Contains(data["httpd.conf"], ".js:text/javascript") { + t.Fatalf("httpd.conf missing .js MIME mapping: %q", data["httpd.conf"]) + } + var sawJS bool + for _, raw := range staticSiteVolumeItems(nil) { + item := raw.(map[string]any) + if item["key"] == "chat-vendor.js" && item["path"] == "chat-vendor.js" { + sawJS = true + } + } + if !sawJS { + t.Fatalf("volume items missing the chat vendor projection") + } + + // Per-offer page: agent offers gain a chat.html bundle file carrying + // the landing page's theme tokens and title; non-agent offers do not. + profile := schemas.StorefrontProfile{DisplayName: "Acme"} + plain := buildOfferBundles([]*monetizeapi.ServiceOffer{hostnameOffer()}, profile, noUpstreamOpenAPI) + for _, f := range plain { + if strings.HasSuffix(f.Path, "chat.html") { + t.Fatalf("non-agent offer rendered a chat page: %s", f.Path) + } + } + agent := hostnameOffer() + agent.Spec.Type = "agent" + bundles := buildOfferBundles([]*monetizeapi.ServiceOffer{agent}, profile, noUpstreamOpenAPI) + var chat string + for _, f := range bundles { + if f.Path == "offers/sec/audit/chat.html" { + chat = f.Content + } + } + if chat == "" { + t.Fatalf("agent offer bundle missing chat.html (got %d files)", len(bundles)) + } + theme := storefront.ResolveTheme(profile.Theme, profile.AccentColor) + if !strings.Contains(chat, "--bg01:"+theme.Vars["bg01"]) { + t.Errorf("chat page missing resolved theme tokens") + } + if !strings.Contains(chat, "chat-vendor.js?v=") { + t.Errorf("chat page missing cache-busted vendor import") + } +} + +// TestChatVendorVersionMatchesBundle guards the ?v= cache-buster on +// chat.html's chat-vendor.js import against a forgotten bump: the bundle is +// served 1-year immutable (buildHostHTTPRoute's exactToShared), so a rebuild +// that forgets to bump ?v= would silently serve returning visitors the OLD +// payment-signing bundle for up to a year. This must fail CI whenever +// assets/chat-vendor.js and assets/chat.html's ?v= go out of sync. +func TestChatVendorVersionMatchesBundle(t *testing.T) { + sum := sha256.Sum256([]byte(chatWidgetVendorJS)) + want := "chat-vendor.js?v=" + fmt.Sprintf("%x", sum)[:8] + if !strings.Contains(chatWidgetTmplSrc, want) { + t.Fatalf("chat.html's chat-vendor.js ?v= does not match sha256(assets/chat-vendor.js); want %q (see assets/README.md rebuild steps)", want) + } +} + // TestHostRouteDiscoveryRulesAreGETOnly pins the method scoping: a // root-priced offer advertises POST / as its paid resource, so the // Exact "/" discovery rule must only capture GETs — POSTs fall through to @@ -308,7 +468,7 @@ func TestBuildOfferBundles_UpstreamOpenAPI(t *testing.T) { "get": map[string]any{ "summary": "Leaderboard", "security": []any{map[string]any{"x402": []any{}}}, "x-payment-info": map[string]any{"price": map[string]any{"amount": "0.001"}}, - "responses": map[string]any{"200": map[string]any{}, "402": map[string]any{}}, + "responses": map[string]any{"200": map[string]any{}, "402": map[string]any{}}, }, }, "/v1/markets/overview": map[string]any{ diff --git a/internal/serviceoffercontroller/offerbundle.go b/internal/serviceoffercontroller/offerbundle.go index ba9c34ba..3d60127a 100644 --- a/internal/serviceoffercontroller/offerbundle.go +++ b/internal/serviceoffercontroller/offerbundle.go @@ -20,7 +20,7 @@ import ( // resources per origin. An offer with spec.hostname therefore gets its own // discovery documents — an openapi.json scoped to just that offer with // paths rooted at "/", a /.well-known/x402 resource list, and a minimal -// landing page — served by the same static-site httpd via per-offer ConfigMap +// landing page — served by the same catalog httpd via per-offer ConfigMap // keys and Exact-match rewrite routes on the offer's hostname. // offerBundleFile is one generated file: Key is the ConfigMap data key, @@ -95,6 +95,16 @@ func buildOfferBundles(offers []*monetizeapi.ServiceOffer, profile schemas.Store Content: buildOfferLandingHTML(offer, originProfile), }, ) + if offer.IsAgent() { + // The chat widget page is themed and titled per offer (same + // resolved profile as the landing page); the heavy vendor + // bundle stays shared at the httpd root. + bundles = append(bundles, offerBundleFile{ + Key: offerBundleKey(offer, "chat.html"), + Path: offerBundleDir(offer) + "/chat.html", + Content: buildOfferChatHTML(offer, originProfile), + }) + } } sort.Slice(bundles, func(i, j int) bool { return bundles[i].Key < bundles[j].Key }) return bundles @@ -331,6 +341,9 @@ func buildOfferLandingHTML(offer *monetizeapi.ServiceOffer, profile schemas.Stor var out strings.Builder err := offerLandingTmpl.Execute(&out, map[string]any{ "Title": title, + // Agent-type offers get the embedded chat widget: the /chat and + // /chat-vendor.js Exact routes exist on the hostname iff IsAgent. + "ChatEnabled": offer.IsAgent(), // Meta/OG tags keep the plain text; the body renders the markdown. "Description": desc, "DescriptionHTML": storefront.RenderRichText(desc), diff --git a/internal/serviceoffercontroller/render.go b/internal/serviceoffercontroller/render.go index f6829e88..cd05d294 100644 --- a/internal/serviceoffercontroller/render.go +++ b/internal/serviceoffercontroller/render.go @@ -270,7 +270,13 @@ func buildStaticSiteConfigMap(content, servicesJSON, openAPIJSON, apiDocsHTML st "services.json": servicesJSON, "openapi.json": openAPIJSON, "api.html": apiDocsHTML, - "httpd.conf": ".md:text/markdown\n.json:application/json\n.html:text/html\n.js:text/javascript\n", + // .js must map to a JavaScript MIME type: the chat widget loads + // chat-vendor.js as an ES module and browsers hard-fail module + // imports served with a non-script Content-Type. + "httpd.conf": ".md:text/markdown\n.json:application/json\n.html:text/html\n.js:text/javascript\n", + // The chat widget's shared vendor bundle (per-offer /chat pages are + // rendered into the offer bundles; this one heavy file is common). + "chat-vendor.js": chatWidgetVendorJS, } for _, f := range bundles { data[f.Key] = f.Content @@ -293,8 +299,8 @@ func buildStaticSiteConfigMap(content, servicesJSON, openAPIJSON, apiDocsHTML st } // staticSiteVolumeItems projects the ConfigMap keys into the httpd's /www -// tree: the aggregate documents plus one file per hostname-offer bundle -// entry (offers///…). +// tree: the aggregate documents, the shared agent chat widget, plus one +// file per hostname-offer bundle entry (offers///…). func staticSiteVolumeItems(bundles []offerBundleFile) []any { items := []any{ map[string]any{"key": "skill.md", "path": "skill.md"}, @@ -305,6 +311,9 @@ func staticSiteVolumeItems(bundles []offerBundleFile) []any { // HTTPRoute also matches the trailing-slash variant so the // resolver kicks in either way. map[string]any{"key": "api.html", "path": "api/index.html"}, + // The chat widget's shared vendor bundle at the /www root; + // agent-offer hostname routes rewrite /chat-vendor.js here. + map[string]any{"key": "chat-vendor.js", "path": "chat-vendor.js"}, } for _, f := range bundles { items = append(items, map[string]any{"key": f.Key, "path": f.Path}) @@ -919,8 +928,10 @@ func buildHostHTTPRoute(offer *monetizeapi.ServiceOffer) *unstructured.Unstructu } // hostRouteRules assembles the dedicated-origin rule list: the four -// discovery rules (landing, openapi, x402, agent-registration) and the -// PathPrefix / payment gate last. +// discovery rules (landing, openapi, x402, agent-registration), the free +// chat widget for agent-type offers (Exact rules, so they win over the +// verifier catch-all like the discovery paths do), and the PathPrefix / +// payment gate last. func hostRouteRules(offer *monetizeapi.ServiceOffer, exactTo, exactToShared func(string, string) map[string]any, catchallFilters []any) []any { rules := []any{ exactTo("/", "index.html"), @@ -928,6 +939,19 @@ func hostRouteRules(offer *monetizeapi.ServiceOffer, exactTo, exactToShared func exactTo("/.well-known/x402", "x402.json"), exactTo("/.well-known/agent-registration.json", "agent-registration.json"), } + if offer.IsAgent() { + // The chat page holds a hot session key and signs USDC transfers — + // frame-ancestors 'self' stops it being clickjacked into a + // cross-origin iframe (Connect/Fund/Withdraw/Send). The offer's own + // landing page embeds it same-origin (TestOfferLandingChatEmbed), so + // that legitimate embed still works. + chatRule := exactTo("/chat", "chat.html") + addResponseHeader(chatRule, "Content-Security-Policy", "frame-ancestors 'self'") + rules = append(rules, + chatRule, + exactToShared("/chat-vendor.js", "chat-vendor.js"), + ) + } return append(rules, map[string]any{ "matches": []any{ map[string]any{"path": map[string]any{"type": "PathPrefix", "value": "/"}}, @@ -939,6 +963,23 @@ func hostRouteRules(offer *monetizeapi.ServiceOffer, exactTo, exactToShared func }) } +// addResponseHeader appends a Set entry to a rule's existing +// ResponseHeaderModifier filter (every exactTo rule has exactly one, from +// cacheFilter) — Gateway API's core filters are unspecified/implementation +// -defined if repeated within one rule, so extra headers merge into the +// existing filter instead of adding a second one. +func addResponseHeader(rule map[string]any, name, value string) { + for _, f := range rule["filters"].([]any) { + fm := f.(map[string]any) + if fm["type"] != "ResponseHeaderModifier" { + continue + } + mod := fm["responseHeaderModifier"].(map[string]any) + mod["set"] = append(mod["set"].([]any), map[string]any{"name": name, "value": value}) + return + } +} + // sharedOriginRule is the /services/ PathPrefix rule → verifier, // with the protection middleware attached when spec.limits is set. func sharedOriginRule(offer *monetizeapi.ServiceOffer) map[string]any { diff --git a/internal/serviceoffercontroller/templates/offer_landing.html b/internal/serviceoffercontroller/templates/offer_landing.html index 25d5968e..ddd9f942 100644 --- a/internal/serviceoffercontroller/templates/offer_landing.html +++ b/internal/serviceoffercontroller/templates/offer_landing.html @@ -35,6 +35,11 @@ code, .mono { font-family:var(--mono); font-size:13px; color:var(--light); } a { color:var(--green); } .fineprint { color:var(--muted); font-size:13px; margin-top:32px; } + /* The chat card is nothing but the widget: no heading, no padding — + the page around it already carries the title and price. */ + .chatcard { padding:0; overflow:hidden; } + .chatframe { display:block; width:100%; height:480px; border:0; background:var(--bg01); } + .chatlink { text-align:right; font-size:12px; margin:6px 2px 0; } {{if .CustomCSS}}{{end}} @@ -46,6 +51,12 @@

{{.Title}}

{{.DescriptionHTML}}
+ {{if .ChatEnabled}} +
+ +
+ + {{end}}

For agents & developers

/openapi.json — request shapes + per-route pricing

diff --git a/web/public-storefront/src/components/ServiceCard.tsx b/web/public-storefront/src/components/ServiceCard.tsx index 63f8a772..a5504f77 100644 --- a/web/public-storefront/src/components/ServiceCard.tsx +++ b/web/public-storefront/src/components/ServiceCard.tsx @@ -249,7 +249,7 @@ export function ServiceCard({ service }: { service: Service }) {
{endpointOrigin(service.endpoint) ? (
- API docs + API docs Date: Mon, 20 Jul 2026 20:24:51 +0400 Subject: [PATCH 21/31] fix(x402): surface on-chain settle tx hash on unlock settle failure handlePaidUnlock discarded the settle response when facilitatorSettle errored, losing the tx hash the facilitator returns when it submits the settle tx on-chain and then fails on the receipt path. A charged buyer got a bare settle_failed with no cookie, no answer, and no way to reconcile the on-chain debit. Mirror the per-request paid path (HandleProxy): surface settleResp.Transaction via X-PAYMENT-RESPONSE + a 'you may pay twice' hint, and log it, on both the transport-error and !Success branches. A failed settle still mints no session. Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v --- internal/x402/authcapture_test.go | 80 +++++++++++++++++++++++++++++++ internal/x402/unlockgate.go | 38 +++++++++++++-- 2 files changed, 113 insertions(+), 5 deletions(-) diff --git a/internal/x402/authcapture_test.go b/internal/x402/authcapture_test.go index bcef5a31..78f7e4b8 100644 --- a/internal/x402/authcapture_test.go +++ b/internal/x402/authcapture_test.go @@ -316,6 +316,86 @@ func TestPaidUnlock_InlineFirstMessage(t *testing.T) { } } +// TestPaidUnlock_SettleErrorSurfacesTxHash locks in the forensic contract: when +// the facilitator submits the settle tx on-chain and THEN fails on the receipt +// path (non-200 with a tx hash in the body), the buyer must get the tx hash back +// (X-PAYMENT-RESPONSE + a "you may pay twice" hint) rather than a bare error — +// otherwise an on-chain debit goes silent with no way to reconcile. +func TestPaidUnlock_SettleErrorSurfacesTxHash(t *testing.T) { + const settledTx = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + mux := http.NewServeMux() + mux.HandleFunc("/verify", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + fmt.Fprintf(w, `{"isValid":true,"payer":%q}`, testUnlockPayer) + }) + mux.HandleFunc("/settle", func(w http.ResponseWriter, r *http.Request) { + // tx landed, receipt path 5xx'd: return the tx hash with a non-200. + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, `{"success":false,"transaction":%q,"network":"eip155:84532","payer":%q,"errorReason":"receipt_timeout"}`, settledTx, testUnlockPayer) + }) + facilitator := httptest.NewServer(mux) + t.Cleanup(facilitator.Close) + + unlockConfig := validAuthCaptureConfig() + v, err := NewVerifier(&PricingConfig{ + Wallet: "0x3333333333333333333333333333333333333333", + Chain: "base-sepolia", + FacilitatorURL: facilitator.URL, + Routes: []RouteRule{{ + Pattern: "/services/agent/*", + Gate: "auth", + StripPrefix: unlockConfig.OfferPrefix, + UpstreamURL: "http://upstream.invalid", + OfferNamespace: "test", + OfferName: "agent", + }}, + AuthCaptureUnlock: &unlockConfig, + }) + if err != nil { + t.Fatalf("NewVerifier: %v", err) + } + + path := unlockConfig.OfferPrefix + "/chat" + challengeReq := httptest.NewRequest(http.MethodGet, path, nil) + cw := httptest.NewRecorder() + v.HandleProxy(cw, challengeReq) + var challenge struct { + Accepts []x402types.PaymentRequirements `json:"accepts"` + } + if err := json.Unmarshal(cw.Body.Bytes(), &challenge); err != nil { + t.Fatalf("decode challenge: %v", err) + } + wireJSON, _ := json.Marshal(map[string]any{ + "x402Version": 2, "payload": map[string]any{"stub": true}, "accepted": challenge.Accepts[0], + }) + req := httptest.NewRequest(http.MethodGet, path, nil) + req.Header.Set("X-PAYMENT", base64.StdEncoding.EncodeToString(wireJSON)) + w := httptest.NewRecorder() + v.HandleProxy(w, req) + + if w.Code != http.StatusBadGateway { + t.Fatalf("status = %d, want 502; body: %s", w.Code, w.Body.String()) + } + if got := w.Header().Get("X-PAYMENT-RESPONSE"); got == "" { + t.Error("X-PAYMENT-RESPONSE header not set — the on-chain tx hash is lost") + } else { + decoded, derr := base64.StdEncoding.DecodeString(got) + if derr != nil || !strings.Contains(string(decoded), settledTx) { + t.Errorf("X-PAYMENT-RESPONSE does not carry the settle tx: %s", string(decoded)) + } + } + if !strings.Contains(w.Body.String(), "pay twice") { + t.Errorf("error body missing the reconcile hint: %s", w.Body.String()) + } + // A failed settle must NOT mint a session. + for _, c := range w.Result().Cookies() { + if c.Name == SIWXSessionCookie { + t.Error("session cookie minted despite settle failure") + } + } +} + // TestValidateSignedUnlockRequirement guards the money path: the verifier // settles the requirement the CLIENT signed (payload.accepted), so a client // must not be able to redirect the fee, underpay, or swap the authorizer. Each diff --git a/internal/x402/unlockgate.go b/internal/x402/unlockgate.go index bce2ae63..22e337eb 100644 --- a/internal/x402/unlockgate.go +++ b/internal/x402/unlockgate.go @@ -135,14 +135,24 @@ func (v *Verifier) handlePaidUnlock(w http.ResponseWriter, r *http.Request, rule settleResp, err := facilitatorSettle(r.Context(), &http.Client{Timeout: facilitatorSettleTimeout}, cfg.FacilitatorURL, payloadBytes, signedReq) if err != nil { - writeUnlockJSON(w, http.StatusBadGateway, map[string]any{"error": "settle_failed", "detail": err.Error()}) + // The facilitator can submit the settle tx on-chain and THEN fail on + // the receipt path (facilitatorSettle returns the tx-bearing response + // alongside the error). Surface the tx hash so a charged-but-unanswered + // buyer can reconcile against the chain — same forensic contract as the + // per-request paid path in HandleProxy. Without this the debit is silent. + body := map[string]any{"error": "settle_failed", "detail": err.Error()} + if v.surfaceOnChainSettle(w, settleResp) { + body["hint"] = "the settle tx in X-PAYMENT-RESPONSE may have landed on-chain — verify against the chain before retrying, or you may pay twice" + } + writeUnlockJSON(w, http.StatusBadGateway, body) return } if !settleResp.Success { - writeUnlockJSON(w, http.StatusBadGateway, map[string]any{ - "error": "settle_failed", - "detail": facilitatorDetail(settleResp.ErrorReason, settleResp.ErrorMessage), - }) + body := map[string]any{"error": "settle_failed", "detail": facilitatorDetail(settleResp.ErrorReason, settleResp.ErrorMessage)} + if v.surfaceOnChainSettle(w, settleResp) { + body["hint"] = "the settle tx in X-PAYMENT-RESPONSE may have landed on-chain — verify against the chain before retrying, or you may pay twice" + } + writeUnlockJSON(w, http.StatusBadGateway, body) return } @@ -182,6 +192,24 @@ func (v *Verifier) handlePaidUnlock(w http.ResponseWriter, r *http.Request, rule proxy.ServeHTTP(&statusRecorder{ResponseWriter: w, status: http.StatusOK}, r) } +// surfaceOnChainSettle sets X-PAYMENT-RESPONSE from a settle response that +// carries an on-chain tx hash (the facilitator submitted the tx then errored), +// so a charged buyer can reconcile. Must run before writeUnlockJSON commits the +// status. Returns true when a tx was surfaced. Mirrors the HandleProxy path. +func (v *Verifier) surfaceOnChainSettle(w http.ResponseWriter, settleResp *facilitatorSettleResponse) bool { + if settleResp == nil || settleResp.Transaction == "" { + return false + } + if encoded, err := json.Marshal(settleResp); err == nil { + b64 := base64.StdEncoding.EncodeToString(encoded) + w.Header().Set("X-PAYMENT-RESPONSE", b64) + w.Header().Set("PAYMENT-RESPONSE", b64) + } + log.Printf("x402-verifier: auth-capture unlock settle returned tx %s with an error — verify on-chain (network=%s payer=%s)", + settleResp.Transaction, settleResp.Network, settleResp.Payer) + return true +} + func bigIntToFloat(x *big.Int) float64 { f, _ := new(big.Float).SetInt(x).Float64() return f From 6cef65fe7cd2973eef11b6638f870af435f2665f Mon Sep 17 00:00:00 2001 From: bussyjd Date: Mon, 20 Jul 2026 20:25:09 +0400 Subject: [PATCH 22/31] fix(chat-widget): cap per-turn spend at displayed price; document MPC-EOA gap Two review fixes on the session-wallet widget: - Per-turn payment policy capped at the price shown at page load (bounded by the session balance), not the whole balance. A seller that drifts the price up mid-session can no longer silently drain a funded session in one message; an over-price turn is refused with an honest error, and a missing/zero amount is dropped rather than treated as free. A legit price change is picked up on reload. - Document the MPC/threshold-ECDSA residual: such wallets are code-less EOAs that pass the contract-wallet guard but sign non-deterministically, stranding session funds. No on-chain detection without a second signature popup this flow avoids; noted at the guard and in assets/README.md limitations. Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v --- .../serviceoffercontroller/assets/README.md | 19 +++++++++++++++ .../serviceoffercontroller/assets/chat.html | 24 +++++++++++++++---- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/internal/serviceoffercontroller/assets/README.md b/internal/serviceoffercontroller/assets/README.md index 0c071647..32174061 100644 --- a/internal/serviceoffercontroller/assets/README.md +++ b/internal/serviceoffercontroller/assets/README.md @@ -7,6 +7,25 @@ the live 402 challenge, so the same file works for every agent offer on any stack and network (Base mainnet `eip155:8453` and Base Sepolia `eip155:84532` are supported). +## Known limitations + +- **Session key is derived from a wallet signature** (`keccak256(personal_sign())`), + so the wallet must produce a *deterministic* signature for the same message. + Standard RFC-6979 EOAs (MetaMask, Rabby, Ledger, Trezor) do; the `getCode` + guard rejects contract wallets (non-reproducible ERC-1271) while allowing + EIP-7702-delegated EOAs. **Residual gap:** MPC / threshold-ECDSA wallets are + code-less EOAs that pass the guard but sign non-deterministically — funding a + session from one strands the balance on the next visit. There is no on-chain + signal to detect this without a second signature popup, which this + one-signature flow deliberately avoids. Use a standard EOA. +- **Per-turn spend is capped at the price shown when the page loaded** (and the + session balance). A turn whose 402 amount exceeds the displayed price is + refused; a legitimate price change is picked up on reload. Max loss per + session is bounded by what you fund into the session wallet. +- **The signature itself is key material.** Anything that can read it (a + malicious extension, a hooked `window.ethereum`) controls the session funds — + keep session balances small. + `chat-vendor.js` — generated single-file ESM bundle of the widget's dependencies. Do not edit by hand. Rebuild: diff --git a/internal/serviceoffercontroller/assets/chat.html b/internal/serviceoffercontroller/assets/chat.html index 63f8284c..56ef0eec 100644 --- a/internal/serviceoffercontroller/assets/chat.html +++ b/internal/serviceoffercontroller/assets/chat.html @@ -203,6 +203,12 @@

{{.Title}}

[mainAddr] = await window.ethereum.request({ method: "eth_requestAccounts" }); // Non-deterministic ERC-1271 signatures would strand session funds: the // session key is derived from a signature a contract wallet cannot reproduce. + // KNOWN RESIDUAL: MPC / threshold-ECDSA wallets (e.g. Zengo, some embedded + // signers) are code-less EOAs, so they pass this guard, yet their signatures + // are non-deterministic — funding one strands the balance on the next visit. + // There is no on-chain signal to detect this; the only programmatic defense + // is a sign-twice-and-compare probe (a second popup this one-signature flow + // deliberately avoids). Documented in assets/README.md. const code = await pub.getCode({ address: mainAddr }); if (code && code !== "0x" && !code.startsWith("0xef0100")) { say("sys err", "Smart-contract wallets aren't supported: their signatures aren't deterministic, so the session wallet this page derives could not be recovered later. Connect a regular (EOA) wallet."); @@ -255,12 +261,20 @@

{{.Title}}

status("waiting for signature…", true); const sig = await wallet.signMessage({ account: mainAddr, message: signinMessage() }); burner = privateKeyToAccount(keccak256(sig)); - // Per-payment ceiling = the session balance: whatever the seller prices - // a turn at, the widget never signs an authorization for more than the - // user chose to park in the session wallet. No arbitrary price cap. + // Per-turn ceiling = the price this page displayed at load (and never more + // than the session balance). A turn whose 402 amount exceeds the shown + // price is refused rather than silently paid up to the whole balance — so a + // seller can't drift the price up mid-session and drain a funded wallet in + // one message. The "above this page's safety cap" error copy below depends + // on this being the real cap. (A legit price change is picked up on reload.) + // A missing/zero amount is dropped, not treated as free. const x = new x402Client() .register(net, new ExactEvmScheme(toClientEvmSigner(burner, pub))) - .registerPolicy((_v, reqs) => reqs.filter((q) => BigInt(q.amount ?? q.maxAmountRequired ?? "0") <= balance)); + .registerPolicy((_v, reqs) => reqs.filter((q) => { + const amt = BigInt(q.amount ?? q.maxAmountRequired ?? "0"); + const cap = (price !== null && price > 0n) ? price : balance; + return amt > 0n && amt <= cap && amt <= balance; + })); payFetch = wrapFetchWithPayment(fetch, x); $("sessaddr").textContent = short(burner.address); if (explorer) { $("explorer").href = explorer + "/address/" + burner.address; $("explorer").style.display = ""; } @@ -428,7 +442,7 @@

{{.Title}}

say("sys err", /insufficient|funds|balance/i.test(msg) ? "Payment failed — session balance too low. Top it up with + fund above." : /filtered out by policies/i.test(msg) ? - "Price changed above this page's safety cap — payment refused." : + "This turn's price is higher than the " + priceStr() + " shown when the page loaded — payment refused. Reload to accept the new price." : "Error: " + (e.shortMessage || e.message).slice(0, 200)); status(""); } From 8e42338cf25e9dadc17178c734a8de8f836a4efb Mon Sep 17 00:00:00 2001 From: HananINouman Date: Wed, 29 Jul 2026 16:13:26 +0300 Subject: [PATCH 23/31] feat(storefront): support secure operator previews Render session-only branding drafts through the real public storefront so operators can preview safely without duplicating buyer UI. Co-authored-by: Cursor --- web/public-storefront/src/app/page.tsx | 53 +---- .../src/components/Header.tsx | 23 +- .../src/components/StorefrontPreview.tsx | 207 ++++++++++++++++++ 3 files changed, 225 insertions(+), 58 deletions(-) create mode 100644 web/public-storefront/src/components/StorefrontPreview.tsx diff --git a/web/public-storefront/src/app/page.tsx b/web/public-storefront/src/app/page.tsx index a1f5a654..4c2c78b3 100644 --- a/web/public-storefront/src/app/page.tsx +++ b/web/public-storefront/src/app/page.tsx @@ -1,56 +1,9 @@ -import { DEFAULT_HERO_TITLE, fetchCatalogDocument } from "@/lib/catalog"; -import { Header } from "@/components/Header"; -import { RichText } from "@/components/RichText"; -import { ServicesList } from "@/components/ServicesList"; -import { PaymentFlow } from "@/components/PaymentFlow"; +import { StorefrontPreview } from "@/components/StorefrontPreview"; +import { fetchCatalogDocument } from "@/lib/catalog"; export const dynamic = "force-dynamic"; export const revalidate = 0; export default async function Home() { const catalog = await fetchCatalogDocument(); - - return ( - <> -
-
-
-

- {DEFAULT_HERO_TITLE} -

-

- {catalog.tagline} -

- {catalog.description ? ( - - ) : null} -
- - - -
-
- - ); + return ; } diff --git a/web/public-storefront/src/components/Header.tsx b/web/public-storefront/src/components/Header.tsx index d28cab16..04459fdf 100644 --- a/web/public-storefront/src/components/Header.tsx +++ b/web/public-storefront/src/components/Header.tsx @@ -13,14 +13,21 @@ export function Header({ storefront }: { storefront: StorefrontProfile }) { data-obol="brand" > {isDefaultLogo && dark ? ( - {storefront.displayName} + <> + Obol Stack + {storefront.displayName !== "Obol Stack" ? ( +
+ {storefront.displayName} +
+ ) : null} + ) : isDefaultLogo ? ( // The default wordmark is light-on-dark and invisible on the // light theme — use the dark square mark plus the name instead. diff --git a/web/public-storefront/src/components/StorefrontPreview.tsx b/web/public-storefront/src/components/StorefrontPreview.tsx new file mode 100644 index 00000000..ea8f385e --- /dev/null +++ b/web/public-storefront/src/components/StorefrontPreview.tsx @@ -0,0 +1,207 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Header } from "@/components/Header"; +import { PaymentFlow } from "@/components/PaymentFlow"; +import { RichText } from "@/components/RichText"; +import { ServicesList } from "@/components/ServicesList"; +import type { ServiceCatalogDocument } from "@/lib/catalog"; +import { isDarkTheme, themeStyle } from "@/lib/theme"; + +const DEFAULT_HERO_TITLE = "Agent services"; +const DEFAULT_LOGO_PATH = "/obol-stack-logo.png"; +const PREVIEW_MESSAGE = "obol.storefront.preview"; +const PREVIEW_READY_MESSAGE = "obol.storefront.preview.ready"; +const HEX_RE = /^#[0-9a-fA-F]{3,8}$/; +const MAX_DATA_URL_LENGTH = 360_000; +const ALLOWED_OPERATOR_HOSTS = new Set(["obol.stack", "localhost", "127.0.0.1"]); + +interface PreviewBranding { + displayName: string; + tagline: string; + theme: "light" | "dark" | "obol"; + themeVars: Record; + logoUrl: string | null; +} + +function operatorOrigin(): string | null { + if (!document.referrer) return null; + try { + const url = new URL(document.referrer); + return ALLOWED_OPERATOR_HOSTS.has(url.hostname) ? url.origin : null; + } catch { + return null; + } +} + +function optionalImage(value: unknown): string | null | undefined { + if (value === null) return null; + if (typeof value !== "string" || value.length > MAX_DATA_URL_LENGTH) { + return undefined; + } + if ( + /^data:image\/[^;,]+;base64,[a-zA-Z0-9+/=\s]+$/.test(value) || + value.startsWith("https://") || + value.startsWith("http://") || + value.startsWith("/") + ) { + return value; + } + return undefined; +} + +function parsePreviewMessage(data: unknown): PreviewBranding | null { + if (!data || typeof data !== "object") return null; + const message = data as Record; + if (message.type !== PREVIEW_MESSAGE || message.version !== 1) return null; + if (!message.branding || typeof message.branding !== "object") return null; + + const branding = message.branding as Record; + const theme = branding.theme; + if (theme !== "light" && theme !== "dark" && theme !== "obol") return null; + if ( + typeof branding.displayName !== "string" || + branding.displayName.length > 120 || + typeof branding.tagline !== "string" || + branding.tagline.length > 500 || + !branding.themeVars || + typeof branding.themeVars !== "object" + ) { + return null; + } + + const themeVars: Record = {}; + for (const [key, value] of Object.entries( + branding.themeVars as Record, + )) { + if (typeof value === "string" && HEX_RE.test(value)) { + themeVars[key] = value; + } + } + const logoUrl = optionalImage(branding.logoUrl); + if (logoUrl === undefined) { + return null; + } + + return { + displayName: branding.displayName, + tagline: branding.tagline, + theme, + themeVars, + logoUrl, + }; +} + +export function StorefrontPreview({ + initial, +}: { + initial: ServiceCatalogDocument; +}) { + const [storefront, setStorefront] = useState(initial); + + useEffect(() => { + const origin = operatorOrigin(); + if (!origin || window.parent === window) return; + + const handleMessage = (event: MessageEvent) => { + if (event.source !== window.parent || event.origin !== origin) return; + const branding = parsePreviewMessage(event.data); + if (!branding) return; + setStorefront((published) => ({ + ...published, + displayName: branding.displayName, + tagline: branding.tagline, + theme: branding.theme, + themeVars: branding.themeVars, + logoUrl: branding.logoUrl ?? DEFAULT_LOGO_PATH, + })); + }; + + window.addEventListener("message", handleMessage); + const publishedTheme = + initial.theme === "dark" || initial.theme === "obol" + ? initial.theme + : "light"; + window.parent.postMessage( + { + type: PREVIEW_READY_MESSAGE, + version: 1, + published: { + displayName: initial.displayName, + tagline: initial.tagline, + theme: publishedTheme, + accent: initial.themeVars?.green ?? "#0b9b71", + logoUrl: initial.logoUrl, + }, + }, + origin, + ); + return () => window.removeEventListener("message", handleMessage); + }, [initial]); + + useEffect(() => { + const root = document.documentElement; + const previewStyle = themeStyle(storefront.themeVars); + const previous = new Map(); + for (const [name, value] of Object.entries(previewStyle)) { + previous.set(name, root.style.getPropertyValue(name)); + root.style.setProperty(name, value); + } + const previousScheme = root.style.colorScheme; + root.style.colorScheme = isDarkTheme(storefront.theme) ? "dark" : "light"; + return () => { + for (const [name, value] of previous) { + if (value) root.style.setProperty(name, value); + else root.style.removeProperty(name); + } + root.style.colorScheme = previousScheme; + }; + }, [storefront.theme, storefront.themeVars]); + + return ( + <> +
+
+
+

+ {DEFAULT_HERO_TITLE} +

+

+ {storefront.tagline} +

+ {storefront.description ? ( + + ) : null} +
+ + + + +
+ + ); +} From aa9ebf7f8c3a31c7c3402a5a7ee9060bf69b3857 Mon Sep 17 00:00:00 2001 From: HananINouman Date: Wed, 29 Jul 2026 22:59:37 +0300 Subject: [PATCH 24/31] feat(storefront): publish local-only preview and SSA branding applies Keep operator live previews on storefront-preview.obol.stack (never the public tunnel), retain that renderer when the public catch-all is torn down, and use server-side apply so large inline logos survive kubectl's 256 KiB last-applied annotation limit. --- cmd/obol/sell_info.go | 17 ++- cmd/obol/sell_info_test.go | 37 +++++ internal/stack/stack.go | 9 +- internal/storefront/record.go | 12 +- internal/tunnel/tunnel.go | 136 +++++++++++++++--- internal/tunnel/tunnel_test.go | 109 ++++++++++++-- web/public-storefront/src/app/globals.css | 14 ++ .../src/components/StorefrontPreview.tsx | 34 ++++- 8 files changed, 332 insertions(+), 36 deletions(-) diff --git a/cmd/obol/sell_info.go b/cmd/obol/sell_info.go index cca58c66..ddffe220 100644 --- a/cmd/obol/sell_info.go +++ b/cmd/obol/sell_info.go @@ -841,7 +841,22 @@ func applySellerProfile(cfg *config.Config, profile schemas.StorefrontProfile) e if err != nil { return err } - if err := kubectlApply(cfg, manifest); err != nil { + raw, err := json.Marshal(manifest) + if err != nil { + return fmt.Errorf("marshal storefront profile: %w", err) + } + bin, kubeconfig := kubectl.Paths(cfg) + // Inline data:image values can make kubectl's client-side + // last-applied-configuration annotation exceed its 256 KiB limit even + // though the ConfigMap itself remains below Kubernetes' 1 MiB limit. + // The host-side profile record is authoritative, so use server-side apply + // for both initial writes and updates. + if err := kubectl.ApplyServerSideForceConflicts( + bin, + kubeconfig, + raw, + "obol-storefront-profile", + ); err != nil { return fmt.Errorf("apply storefront profile: %w", err) } return nil diff --git a/cmd/obol/sell_info_test.go b/cmd/obol/sell_info_test.go index d3c0ebbc..2a73f689 100644 --- a/cmd/obol/sell_info_test.go +++ b/cmd/obol/sell_info_test.go @@ -1,12 +1,49 @@ package main import ( + "fmt" + "os" + "path/filepath" "strings" "testing" + "github.com/ObolNetwork/obol-stack/internal/config" "github.com/ObolNetwork/obol-stack/internal/schemas" ) +func TestApplySellerProfileUsesServerSideApplyForInlineImages(t *testing.T) { + cfg := &config.Config{ConfigDir: t.TempDir(), BinDir: t.TempDir()} + argsPath := filepath.Join(t.TempDir(), "kubectl-args") + script := fmt.Sprintf("#!/bin/sh\nprintf '%%s\\n' \"$@\" > %q\ncat >/dev/null\n", argsPath) + if err := os.WriteFile(filepath.Join(cfg.BinDir, "kubectl"), []byte(script), 0o755); err != nil { + t.Fatal(err) + } + + profile := schemas.StorefrontProfile{ + LogoURL: "data:image/png;base64," + strings.Repeat("a", 270_000), + } + if err := applySellerProfile(cfg, profile); err != nil { + t.Fatalf("applySellerProfile: %v", err) + } + + args, err := os.ReadFile(argsPath) + if err != nil { + t.Fatal(err) + } + got := string(args) + for _, want := range []string{ + "apply\n", + "--server-side\n", + "--force-conflicts\n", + "--field-manager=obol-storefront-profile\n", + "-f\n-\n", + } { + if !strings.Contains(got, want) { + t.Fatalf("kubectl args missing %q:\n%s", want, got) + } + } +} + func TestClearProfileFields(t *testing.T) { base := schemas.StorefrontProfile{ DisplayName: "Acme", diff --git a/internal/stack/stack.go b/internal/stack/stack.go index 6c1b5464..d1bd0b3d 100644 --- a/internal/stack/stack.go +++ b/internal/stack/stack.go @@ -249,7 +249,8 @@ func Up(cfg *config.Config, u *ui.UI, wildcardDNS bool) error { // Ensure the base host before syncing defaults. Include existing agent // hostnames so stack up never shrinks the managed /etc/hosts block to only // obol.stack when default setup is skipped. - if err := dns.EnsureHostsEntries(agentruntime.CollectHostnames(cfg)); err != nil { + hostnames := append(agentruntime.CollectHostnames(cfg), tunnel.StorefrontPreviewHostname) + if err := dns.EnsureHostsEntries(hostnames); err != nil { u.Warnf("Could not update /etc/hosts for obol.stack: %v", err) } @@ -588,6 +589,12 @@ func syncDefaults(cfg *config.Config, u *ui.UI, kubeconfigPath string, dataDir s u.Dim(" You can manually apply later with: obol agent init") } + // Local-only storefront preview must exist even when the public tunnel is + // dormant — the operator branding editor iframes storefront-preview.obol.stack. + if err := tunnel.EnsureLocalStorefrontPreview(cfg); err != nil { + u.Warnf("Could not publish local storefront preview: %v", err) + } + // Start the Cloudflare tunnel only if a persistent DNS tunnel is provisioned. // Quick tunnels are dormant by default and activate on first `obol sell`. u.Blank() diff --git a/internal/storefront/record.go b/internal/storefront/record.go index 26d4d7ce..896e3803 100644 --- a/internal/storefront/record.go +++ b/internal/storefront/record.go @@ -49,7 +49,17 @@ func ReconcileRecorded(cfg *config.Config, u *ui.UI) { } bin, kubeconfig := kubectl.Paths(cfg) - if err := kubectl.Apply(bin, kubeconfig, payload); err != nil { + // Profiles may contain inline data:image URIs. Client-side apply mirrors + // the complete manifest into last-applied-configuration, and that annotation + // has a 256 KiB hard limit even though the ConfigMap itself allows 1 MiB. + // The host record is authoritative, so server-side ownership is appropriate + // and keeps large valid profiles replayable after stack recreation. + if err := kubectl.ApplyServerSideForceConflicts( + bin, + kubeconfig, + payload, + "obol-storefront-profile", + ); err != nil { u.Warnf("Could not reconcile recorded storefront profile: %v", err) return } diff --git a/internal/tunnel/tunnel.go b/internal/tunnel/tunnel.go index d20f6553..4cec0392 100644 --- a/internal/tunnel/tunnel.go +++ b/internal/tunnel/tunnel.go @@ -18,6 +18,7 @@ import ( "github.com/ObolNetwork/obol-stack/internal/agentruntime" "github.com/ObolNetwork/obol-stack/internal/config" + stackdefaults "github.com/ObolNetwork/obol-stack/internal/defaults" "github.com/ObolNetwork/obol-stack/internal/images" "github.com/ObolNetwork/obol-stack/internal/ui" ) @@ -1229,8 +1230,16 @@ func deleteLocalCloudflareTunnel(u *ui.UI, tunnelName, tunnelID string) error { return nil } -// storefrontNamespace is where the storefront landing page resources live. -const storefrontNamespace = "traefik" +// Storefront resources live beside the Gateway. The preview hostname is +// deliberately local-only: unlike tunnel hostnames it is never written into +// cloudflared ingress configuration, so the operator dashboard can frame the +// real renderer without bringing the public tunnel into its trust boundary. +const ( + storefrontNamespace = "traefik" + // StorefrontPreviewHostname is the local-only operator preview origin. + // It is never written into cloudflared ingress configuration. + StorefrontPreviewHostname = "storefront-preview.obol.stack" +) // storefrontHostnames returns the hostnames the public storefront should be // published on: the full tracked set for a persistent tunnel, else the host @@ -1276,6 +1285,63 @@ func offerBoundHostnames(kubectlPath, kubeconfigPath string) (map[string]bool, e return bound, nil } +func buildStorefrontPreviewHTTPRoute() map[string]any { + return map[string]any{ + "apiVersion": "gateway.networking.k8s.io/v1", + "kind": "HTTPRoute", + "metadata": map[string]any{ + "name": "storefront-preview", + "namespace": storefrontNamespace, + }, + "spec": map[string]any{ + "hostnames": []string{StorefrontPreviewHostname}, + "parentRefs": []map[string]any{ + { + "name": "traefik-gateway", + "namespace": "traefik", + "sectionName": "web", + }, + }, + "rules": []map[string]any{ + { + "matches": []map[string]any{ + {"path": map[string]string{"type": "PathPrefix", "value": "/"}}, + }, + "filters": []map[string]any{ + { + "type": "ResponseHeaderModifier", + "responseHeaderModifier": map[string]any{ + "set": []map[string]string{ + { + "name": "Content-Security-Policy", + "value": "frame-ancestors 'self' http://obol.stack:* https://obol.stack:*", + }, + { + "name": "Permissions-Policy", + "value": "camera=(), microphone=(), geolocation=(), payment=()", + }, + {"name": "X-Content-Type-Options", "value": "nosniff"}, + }, + }, + }, + }, + "backendRefs": []map[string]any{ + {"name": "tunnel-storefront", "port": 3000}, + }, + }, + }, + }, + } +} + +func storefrontImage(cfg *config.Config) string { + const repo = "ghcr.io/obolnetwork/obol-stack-public-storefront" + if strings.EqualFold(strings.TrimSpace(os.Getenv("OBOL_DEVELOPMENT")), "true") { + return images.ResolveDev(repo, stackdefaults.ReadDevImageTag(cfg)) + } + return images.Resolve(repo) +} + // CreateStorefront creates (or updates) the public storefront landing page and // publishes it at the root path of EVERY supplied hostname. Each argument may be // a bare hostname or a full URL (scheme/path stripped); empty or duplicate @@ -1315,22 +1381,41 @@ func CreateStorefront(cfg *config.Config, hostnames ...string) error { } hosts = kept if len(hosts) == 0 { - // Every tracked hostname is offer-bound: the storefront has - // nothing left to serve at any hostname. Tear it down instead - // of leaving the previously-applied HTTPRoute (with the now- - // stale wider host list) on the cluster — Gateway API breaks - // the resulting PathPrefix-/ tie by route age, so that stale - // route would otherwise keep shadowing the offer's own - // dedicated-origin route. - return DeleteStorefront(cfg) + // Every tracked public hostname is offer-bound: remove only the + // public catch-all HTTPRoute so it cannot shadow dedicated-origin + // offer routes. Keep the renderer + local preview hostname so the + // operator dashboard can still frame a non-public copy. + cmd := exec.Command(kubectlPath, + "--kubeconfig", kubeconfigPath, + "delete", "httproute/tunnel-storefront", + "-n", storefrontNamespace, + "--ignore-not-found", + ) + _ = cmd.Run() + return ensureStorefrontRenderer(cfg, nil) } } + return ensureStorefrontRenderer(cfg, hosts) +} + +// EnsureLocalStorefrontPreview publishes the local-only storefront renderer +// (Deployment + Service + storefront-preview.obol.stack HTTPRoute) without +// requiring a public tunnel hostname. Safe to call on every stack up so the +// operator branding editor can iframe a non-public copy before tunnel setup. +func EnsureLocalStorefrontPreview(cfg *config.Config) error { + return ensureStorefrontRenderer(cfg, nil) +} + +// ensureStorefrontRenderer applies the Next.js storefront Deployment + Service +// and the local-only preview HTTPRoute. When publicHosts is non-empty it also +// publishes the tunnel catch-all HTTPRoute for those hostnames. +func ensureStorefrontRenderer(cfg *config.Config, publicHosts []string) error { + kubectlPath := filepath.Join(cfg.BinDir, "kubectl") + kubeconfigPath := filepath.Join(cfg.ConfigDir, "kubeconfig.yaml") labels := map[string]string{"app": "tunnel-storefront"} - // Build the resources for the public storefront. resources := []map[string]any{ - // Deployment: Next.js public storefront image. { "apiVersion": "apps/v1", "kind": "Deployment", @@ -1351,7 +1436,7 @@ func CreateStorefront(cfg *config.Config, hostnames ...string) error { "containers": []map[string]any{ { "name": "storefront", - "image": images.Resolve("ghcr.io/obolnetwork/obol-stack-public-storefront"), + "image": storefrontImage(cfg), "imagePullPolicy": "IfNotPresent", "ports": []map[string]any{ {"containerPort": 3000, "name": "http"}, @@ -1397,7 +1482,6 @@ func CreateStorefront(cfg *config.Config, hostnames ...string) error { }, }, }, - // Service { "apiVersion": "v1", "kind": "Service", @@ -1412,8 +1496,14 @@ func CreateStorefront(cfg *config.Config, hostnames ...string) error { }, }, }, - // HTTPRoute: tunnel hostname → storefront (more specific than frontend catch-all). - { + // Local-only preview route. Hostname scoping is the security boundary: + // cloudflared never advertises *.obol.stack, while the CSP limits who + // may frame this renderer even on the local machine. + buildStorefrontPreviewHTTPRoute(), + } + + if len(publicHosts) > 0 { + resources = append(resources, map[string]any{ "apiVersion": "gateway.networking.k8s.io/v1", "kind": "HTTPRoute", "metadata": map[string]any{ @@ -1421,7 +1511,7 @@ func CreateStorefront(cfg *config.Config, hostnames ...string) error { "namespace": storefrontNamespace, }, "spec": map[string]any{ - "hostnames": hosts, + "hostnames": publicHosts, "parentRefs": []map[string]any{ { "name": "traefik-gateway", @@ -1443,10 +1533,9 @@ func CreateStorefront(cfg *config.Config, hostnames ...string) error { }, }, }, - }, + }) } - // Apply each resource via kubectl apply. for _, res := range resources { data, err := json.Marshal(res) if err != nil { @@ -1468,7 +1557,10 @@ func CreateStorefront(cfg *config.Config, hostnames ...string) error { return nil } -// DeleteStorefront removes the storefront landing page resources. +// DeleteStorefront removes the public storefront catch-all HTTPRoute while +// keeping the local-only preview renderer (Deployment/Service + +// storefront-preview.obol.stack). The operator branding editor must keep +// working when the tunnel is dormant or the last offer is deleted. func DeleteStorefront(cfg *config.Config) error { kubectlPath := filepath.Join(cfg.BinDir, "kubectl") @@ -1479,8 +1571,6 @@ func DeleteStorefront(cfg *config.Config) error { for _, resource := range []string{ "httproute/tunnel-storefront", - "service/tunnel-storefront", - "deployment/tunnel-storefront", "configmap/tunnel-storefront", } { cmd := exec.Command(kubectlPath, @@ -1492,7 +1582,7 @@ func DeleteStorefront(cfg *config.Config) error { _ = cmd.Run() // best-effort cleanup } - return nil + return EnsureLocalStorefrontPreview(cfg) } func parseQuickTunnelURL(logs string) (string, bool) { diff --git a/internal/tunnel/tunnel_test.go b/internal/tunnel/tunnel_test.go index 2a116dbc..ea5015a5 100644 --- a/internal/tunnel/tunnel_test.go +++ b/internal/tunnel/tunnel_test.go @@ -1,6 +1,7 @@ package tunnel import ( + "encoding/json" "fmt" "os" "path/filepath" @@ -11,6 +12,49 @@ import ( "github.com/ObolNetwork/obol-stack/internal/config" ) +func TestBuildStorefrontPreviewHTTPRouteSecurity(t *testing.T) { + route := buildStorefrontPreviewHTTPRoute() + data, err := json.Marshal(route) + if err != nil { + t.Fatal(err) + } + rendered := string(data) + + for _, want := range []string{ + `"hostnames":["storefront-preview.obol.stack"]`, + `"name":"Content-Security-Policy"`, + `frame-ancestors 'self' http://obol.stack:* https://obol.stack:*`, + `"name":"Permissions-Policy"`, + `camera=(), microphone=(), geolocation=(), payment=()`, + `"name":"X-Content-Type-Options","value":"nosniff"`, + `"name":"tunnel-storefront","port":3000`, + } { + if !strings.Contains(rendered, want) { + t.Fatalf("preview route missing %q:\n%s", want, rendered) + } + } + if strings.Contains(rendered, `"hostnames":[]`) { + t.Fatalf("preview route must never become hostless:\n%s", rendered) + } +} + +func TestStorefrontImageUsesPersistedDevTag(t *testing.T) { + t.Setenv("OBOL_DEVELOPMENT", "true") + cfg := &config.Config{ConfigDir: t.TempDir()} + if err := os.WriteFile( + filepath.Join(cfg.ConfigDir, ".dev-image-tag"), + []byte("dev-abc1234"), + 0o600, + ); err != nil { + t.Fatal(err) + } + + const want = "ghcr.io/obolnetwork/obol-stack-public-storefront:dev-abc1234" + if got := storefrontImage(cfg); got != want { + t.Fatalf("storefrontImage() = %q, want %q", got, want) + } +} + func TestWaitReadyTimeout(t *testing.T) { t.Setenv("FLOW_TUNNEL_TIMEOUT", "") if got := waitReadyTimeout(); got != defaultWaitReadyTimeout { @@ -162,6 +206,7 @@ func TestBuildLocalManagedConfigYAMLDeduplicates(t *testing.T) { // every invocation (argv) to logPath and, for a `get serviceoffers.obol.org` // query, prints boundHostname as the sole offer-bound hostname -- just enough // to drive offerBoundHostnames()/DeleteStorefront() without a real cluster. +// Apply stdin is appended so callers can assert which resources were published. func writeFakeKubectl(t *testing.T, cfg *config.Config, logPath, boundHostname string) { t.Helper() if err := os.MkdirAll(cfg.BinDir, 0o755); err != nil { @@ -171,9 +216,14 @@ func writeFakeKubectl(t *testing.T, cfg *config.Config, logPath, boundHostname s echo "$@" >> %q case "$*" in *"get serviceoffers.obol.org"*) echo %q ;; + *apply*) + echo "---APPLY---" >> %q + cat >> %q + echo >> %q + ;; esac exit 0 -`, logPath, boundHostname) +`, logPath, boundHostname, logPath, logPath, logPath) if err := os.WriteFile(filepath.Join(cfg.BinDir, "kubectl"), []byte(script), 0o755); err != nil { t.Fatalf("write fake kubectl: %v", err) } @@ -181,11 +231,10 @@ exit 0 // TestCreateStorefront_TearsDownWhenAllHostsOfferBound guards the Canary402 // fix: when every hostname CreateStorefront was asked to serve turns out to -// be offer-bound, it must tear down the previously-applied tunnel-storefront -// HTTPRoute (via DeleteStorefront) instead of a no-op `return nil` that would -// leave a stale, wider-hostname route on the cluster shadowing the offer's -// own dedicated-origin route (equal PathPrefix-/ specificity, older route -// wins the Gateway API tie). +// be offer-bound, it must tear down the previously-applied public +// tunnel-storefront HTTPRoute instead of leaving a stale wider-hostname +// route shadowing the offer's dedicated-origin route. The local preview +// renderer (Deployment/Service + storefront-preview route) is retained. func TestCreateStorefront_TearsDownWhenAllHostsOfferBound(t *testing.T) { cfg := newHostnameTestConfig(t) writeFakeKubeconfig(t, cfg) @@ -206,8 +255,52 @@ func TestCreateStorefront_TearsDownWhenAllHostsOfferBound(t *testing.T) { if !strings.Contains(log, "delete httproute/tunnel-storefront") { t.Fatalf("CreateStorefront must tear down the stale tunnel-storefront HTTPRoute when every requested hostname is offer-bound; kubectl invocations:\n%s", log) } - if strings.Contains(log, "apply") { - t.Fatalf("CreateStorefront must not re-apply the storefront HTTPRoute when every requested hostname is offer-bound; kubectl invocations:\n%s", log) + if !strings.Contains(log, "apply") { + t.Fatalf("CreateStorefront must retain the local storefront renderer when every requested hostname is offer-bound; kubectl invocations:\n%s", log) + } + if !strings.Contains(log, `"hostnames":["storefront-preview.obol.stack"]`) { + t.Fatalf("CreateStorefront must keep the local-only preview HTTPRoute; kubectl invocations:\n%s", log) + } + if strings.Contains(log, `"hostnames":["example.com"]`) { + t.Fatalf("CreateStorefront must not re-publish the public catch-all for offer-bound hostnames; kubectl invocations:\n%s", log) + } + if strings.Contains(log, "delete httproute/storefront-preview") || + strings.Contains(log, "delete deployment/tunnel-storefront") || + strings.Contains(log, "delete service/tunnel-storefront") { + t.Fatalf("CreateStorefront must not delete the local preview renderer when only public hosts are offer-bound; kubectl invocations:\n%s", log) + } +} + +// TestDeleteStorefront_KeepsLocalPreview ensures tearing down the public +// catch-all (tunnel delete / last quick-tunnel offer) still leaves the +// operator branding editor a local renderer to iframe. +func TestDeleteStorefront_KeepsLocalPreview(t *testing.T) { + cfg := newHostnameTestConfig(t) + writeFakeKubeconfig(t, cfg) + + logPath := filepath.Join(cfg.ConfigDir, "kubectl.log") + writeFakeKubectl(t, cfg, logPath, "") + + if err := DeleteStorefront(cfg); err != nil { + t.Fatalf("DeleteStorefront: %v", err) + } + + logBytes, err := os.ReadFile(logPath) + if err != nil { + t.Fatalf("read kubectl log: %v", err) + } + log := string(logBytes) + + if !strings.Contains(log, "delete httproute/tunnel-storefront") { + t.Fatalf("DeleteStorefront must remove the public catch-all; kubectl invocations:\n%s", log) + } + if strings.Contains(log, "delete httproute/storefront-preview") || + strings.Contains(log, "delete deployment/tunnel-storefront") || + strings.Contains(log, "delete service/tunnel-storefront") { + t.Fatalf("DeleteStorefront must not tear down the local preview renderer; kubectl invocations:\n%s", log) + } + if !strings.Contains(log, `"hostnames":["storefront-preview.obol.stack"]`) { + t.Fatalf("DeleteStorefront must re-ensure the local preview route; kubectl invocations:\n%s", log) } } diff --git a/web/public-storefront/src/app/globals.css b/web/public-storefront/src/app/globals.css index 5024e5f4..0a9027e9 100644 --- a/web/public-storefront/src/app/globals.css +++ b/web/public-storefront/src/app/globals.css @@ -39,6 +39,20 @@ body { color: var(--color-text-light); } +/* Operator iframe preview: disable pay/wallet controls but keep page scroll. */ +html[data-obol-preview] a, +html[data-obol-preview] button, +html[data-obol-preview] input, +html[data-obol-preview] textarea, +html[data-obol-preview] select, +html[data-obol-preview] summary, +html[data-obol-preview] [role="button"] { + pointer-events: none; +} +html[data-obol-preview] { + cursor: default; +} + /* Scrollbars follow the theme */ * { scrollbar-color: var(--color-bg04) var(--color-bg01); diff --git a/web/public-storefront/src/components/StorefrontPreview.tsx b/web/public-storefront/src/components/StorefrontPreview.tsx index ea8f385e..aaf428a1 100644 --- a/web/public-storefront/src/components/StorefrontPreview.tsx +++ b/web/public-storefront/src/components/StorefrontPreview.tsx @@ -15,6 +15,7 @@ const PREVIEW_READY_MESSAGE = "obol.storefront.preview.ready"; const HEX_RE = /^#[0-9a-fA-F]{3,8}$/; const MAX_DATA_URL_LENGTH = 360_000; const ALLOWED_OPERATOR_HOSTS = new Set(["obol.stack", "localhost", "127.0.0.1"]); +const PREVIEW_HOST = "storefront-preview.obol.stack"; interface PreviewBranding { displayName: string; @@ -34,6 +35,12 @@ function operatorOrigin(): string | null { } } +function isOperatorPreviewFrame(): boolean { + if (typeof window === "undefined") return false; + if (window.parent !== window) return true; + return window.location.hostname === PREVIEW_HOST; +} + function optionalImage(value: unknown): string | null | undefined { if (value === null) return null; if (typeof value !== "string" || value.length > MAX_DATA_URL_LENGTH) { @@ -98,10 +105,20 @@ export function StorefrontPreview({ initial: ServiceCatalogDocument; }) { const [storefront, setStorefront] = useState(initial); + const [previewMode, setPreviewMode] = useState(false); useEffect(() => { + const preview = isOperatorPreviewFrame(); + setPreviewMode(preview); + const root = document.documentElement; + if (preview) root.dataset.obolPreview = "1"; + else delete root.dataset.obolPreview; const origin = operatorOrigin(); - if (!origin || window.parent === window) return; + if (!origin || window.parent === window) { + return () => { + delete root.dataset.obolPreview; + }; + } const handleMessage = (event: MessageEvent) => { if (event.source !== window.parent || event.origin !== origin) return; @@ -136,7 +153,10 @@ export function StorefrontPreview({ }, origin, ); - return () => window.removeEventListener("message", handleMessage); + return () => { + window.removeEventListener("message", handleMessage); + delete root.dataset.obolPreview; + }; }, [initial]); useEffect(() => { @@ -162,6 +182,14 @@ export function StorefrontPreview({ <>
+ {previewMode ? ( +

+ Operator preview — payment and wallet actions are disabled here. +

+ ) : null}

/skill.md /.well-known/agent-registration.json From eac5514db7d7444bffa84c93bcc2c4b9f11b7776 Mon Sep 17 00:00:00 2001 From: HananINouman Date: Sun, 2 Aug 2026 20:03:38 +0300 Subject: [PATCH 25/31] feat: add aggregate /.well-known/x402 and external buyer-tool compat Publish AgentCash/x402scan discovery fallback on the shared storefront, surface AgentCash/Bankr buy prompts, and gate the path with flow-22 (generic x402 SDK + agent chat-completions) in release-smoke. Co-authored-by: Cursor --- CLAUDE.md | 2 + flows/clients/x402-generic-buyer.go | 141 ++++++ flows/flow-22-external-buyer-compat.sh | 461 ++++++++++++++++++ flows/release-smoke.sh | 7 + internal/buyprompts/buyprompts.go | 62 +++ internal/buyprompts/buyprompts_test.go | 4 +- internal/embed/skills/buy-x402/SKILL.md | 7 + internal/serviceoffercontroller/catalog.go | 11 +- .../serviceoffercontroller/catalog_test.go | 14 +- internal/serviceoffercontroller/controller.go | 10 +- .../serviceoffercontroller/offerbundle.go | 32 +- internal/serviceoffercontroller/render.go | 132 ++++- .../render_builders_test.go | 6 +- .../wellknown_aggregate_test.go | 124 +++++ .../src/components/ServiceCard.tsx | 118 ++++- 15 files changed, 1078 insertions(+), 53 deletions(-) create mode 100644 flows/clients/x402-generic-buyer.go create mode 100755 flows/flow-22-external-buyer-compat.sh create mode 100644 internal/serviceoffercontroller/wellknown_aggregate_test.go diff --git a/CLAUDE.md b/CLAUDE.md index d88e4bf6..b4ab4d5c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -62,6 +62,7 @@ Integration tests use `//go:build integration`; skip when prerequisites missing. | Public (tunnel) | none | `/.well-known/agent-registration.json` | ERC-8004 httpd | | Public (tunnel) | none | `/skill.md` | service catalog | | Public (tunnel) | none | `/api/services.json` | service catalog JSON feed (`displayName`, `tagline`, `logoUrl`, `theme`, `themeVars`, `faviconUrl`, `ogImageUrl`, `description(+Html)`, `customCss`, `services[]`) | +| Public (tunnel) | none | `/.well-known/x402` | aggregate x402 discovery fallback (AgentCash/x402scan-style crawlers that don't parse `/openapi.json`'s `x-payment-info`) | | Public (tunnel) | tunnel hostname only | `/` | storefront landing page (Next.js) | **NEVER remove hostname restrictions from frontend or eRPC HTTPRoutes** — exposing the frontend/RPC to the public internet is a critical security flaw. @@ -478,6 +479,7 @@ The Cloudflare tunnel exposes the cluster to the public internet. Only x402-gate - `/.well-known/agent-registration.json` — ERC-8004 discovery - `/skill.md` — machine-readable service catalog - `/api/services.json` — service catalog envelope (`displayName`, `tagline`, `logoUrl`, theme/branding fields, `services[]`) +- `/.well-known/x402` — aggregate x402 discovery fallback (AgentCash/x402scan-style discovery convention; no secret material) - `/` on tunnel hostname — public storefront landing page (Next.js) ## Dependencies diff --git a/flows/clients/x402-generic-buyer.go b/flows/clients/x402-generic-buyer.go new file mode 100644 index 00000000..03242bb7 --- /dev/null +++ b/flows/clients/x402-generic-buyer.go @@ -0,0 +1,141 @@ +//go:build ignore + +// Generic x402 HTTP buyer client for flow-22-external-buyer-compat. +// +// Built entirely from the public x402-foundation/x402/go/v2 SDK's +// documented client pattern (see CLIENT.md "Basic HTTP Client" in the SDK +// module): wrap a plain *http.Client with x402 payment handling and call it +// like any other HTTP client. No Obol CLI, no buy.py, no PurchaseRequest CR +// — this is what a third-party buyer tool's own wallet/agent does under the +// hood (AgentCash, Bankr, or any other x402-compliant client), so a +// successful "paid" run here is evidence the seller works with any +// standards-compliant buyer, not just Obol's own tooling. +// +// Run from the repo root (module context): +// +// go run flows/clients/x402-generic-buyer.go \ +// -mode -url \ +// [-method GET|POST] [-body ''] +// +// Modes: +// +// unpaid call the endpoint with no signer registered — expect a 402 +// paid call the endpoint with a signer from X402_CLIENT_KEY — the +// wrapped client detects the 402, signs, retries, and returns +// whatever the seller sends back (expect 200 on success) +// +// -method/-body cover both the plain HTTP demo (GET, empty body) and the +// OpenAI-compatible chat-completions shape (POST + JSON body) used by +// inference/agent offers. +// +// X402_CLIENT_KEY carries the buyer's 0x-prefixed private key via env so it +// never appears on argv. Output is ONE JSON object on stdout; the flow +// asserts on its fields. +package main + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "io" + "net/http" + "os" + "strings" + "time" + + x402 "github.com/x402-foundation/x402/go/v2" + x402http "github.com/x402-foundation/x402/go/v2/http" + exactclient "github.com/x402-foundation/x402/go/v2/mechanisms/evm/exact/client" + evmsigner "github.com/x402-foundation/x402/go/v2/signers/evm" +) + +func main() { + url := flag.String("url", "", "paid resource URL") + mode := flag.String("mode", "paid", "unpaid|paid") + network := flag.String("network", "eip155:84532", "CAIP-2 network to register the signer for") + method := flag.String("method", "GET", "HTTP method") + body := flag.String("body", "", "optional request body (e.g. chat-completions JSON)") + timeout := flag.Duration("timeout", 120*time.Second, "per-request timeout") + flag.Parse() + + out := map[string]any{"mode": *mode, "method": strings.ToUpper(*method)} + if *url == "" { + out["error"] = "-url is required" + emit(out) + os.Exit(1) + } + if err := run(*url, *mode, *network, strings.ToUpper(*method), *body, *timeout, out); err != nil { + out["error"] = err.Error() + emit(out) + os.Exit(1) + } + emit(out) +} + +func emit(m map[string]any) { + b, _ := json.Marshal(m) + fmt.Println(string(b)) +} + +func run(url, mode, network, method, body string, timeout time.Duration, out map[string]any) error { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + // mode=unpaid deliberately skips the x402 wrapper entirely — a real + // unpaid buyer is just a plain HTTP client with no x402 awareness, and + // the wrapper's RoundTrip returns an error (not a 402 response) when no + // scheme is registered for the seller's network, since it can't build a + // payment payload. Bypassing it here is what lets this mode observe the + // seller's raw 402 challenge, exactly like a naive caller would. + httpClient := http.DefaultClient + if mode == "paid" { + key := os.Getenv("X402_CLIENT_KEY") + if key == "" { + return fmt.Errorf("X402_CLIENT_KEY is required for -mode paid") + } + signer, err := evmsigner.NewClientSignerFromPrivateKey(key) + if err != nil { + return fmt.Errorf("signer: %w", err) + } + out["buyerAddress"] = signer.Address() + + // Following CLIENT.md's quick-start verbatim: create the core + // client, register the scheme, wrap a plain http.Client. + client := x402.Newx402Client(). + Register(x402.Network(network), exactclient.NewExactEvmScheme(signer, nil)) + httpClient = x402http.WrapHTTPClientWithPayment(http.DefaultClient, x402http.Newx402HTTPClient(client)) + } + + var bodyReader io.Reader + if body != "" { + bodyReader = strings.NewReader(body) + } + req, err := http.NewRequestWithContext(ctx, method, url, bodyReader) + if err != nil { + return fmt.Errorf("build request: %w", err) + } + if body != "" { + req.Header.Set("Content-Type", "application/json") + } + resp, err := httpClient.Do(req) + if err != nil { + return fmt.Errorf("do: %w", err) + } + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("read body: %w", err) + } + + out["status"] = resp.StatusCode + out["body"] = string(respBody) + if v := resp.Header.Get("X-PAYMENT-RESPONSE"); v != "" { + out["paymentResponseHeader"] = v + } + if v := resp.Header.Get("PAYMENT-RESPONSE"); v != "" { + out["paymentResponseHeaderV2"] = v + } + return nil +} diff --git a/flows/flow-22-external-buyer-compat.sh b/flows/flow-22-external-buyer-compat.sh new file mode 100755 index 00000000..17ac0c69 --- /dev/null +++ b/flows/flow-22-external-buyer-compat.sh @@ -0,0 +1,461 @@ +#!/bin/bash +# Flow 22: external buyer-tool compatibility — AgentCash, Bankr, x402scan/Poncho. +# +# The CTO asked us to "reflect" AgentCash and Bankr in the buy flow and check +# what else x402scan exposes (e.g. Poncho). Research established: +# +# - AgentCash, x402scan, and Poncho are all Merit Systems products sharing +# one discovery convention: OpenAPI `x-payment-info` + a `/.well-known/x402` +# fallback. Obol Stack already emits the former; this PR adds the latter +# at the aggregate storefront level (internal/serviceoffercontroller). +# - Bankr publishes no discovery format of its own; it's a plain x402 +# wallet, compatible via the core 402/X-PAYMENT handshake only. +# +# Since none of these third-party tools can be pointed at our own test seller +# in CI (AgentCash's discovery CLI can, Bankr's only public CLI cannot — see +# below), this flow proves compatibility two ways: +# +# 1. The REAL AgentCash discovery CLI (`@agentcash/discovery`, Merit +# Systems), run unmodified against a live offer. It is a pure, +# unauthenticated, read-only HTTP prober (confirmed by reading its +# published bundle) — no wallet, no login, genuinely CI-safe. +# 2. A generic x402 buyer built from nothing but the public +# x402-foundation/x402/go/v2 SDK's documented client pattern +# (flows/clients/x402-generic-buyer.go) — no Obol CLI, no buy.py, no +# PurchaseRequest CR. This is what AgentCash's and Bankr's own +# wallets/agents do under the hood, so a successful paid call here is +# protocol-level evidence that covers Bankr too. +# +# Coverage shape: +# A. Plain HTTP demo (GET) — payment handshake only. +# B. Agent offer (type=agent) — POST /v1/chat/completions via the same +# generic x402 SDK buyer. This is the path AgentCash/Bankr use on +# agent sellers (OpenAI-compatible chat-completions). Skips cleanly +# when no usable model is configured for `obol agent new`. +# +# Bankr's own public CLI example (BankrBot/x402-cli-example) is NOT +# exercised here: it's interactive-only and hardcoded to call Bankr's own +# hosted agent endpoint via the closed-source @bankr/sdk — there is no +# documented way to point it at an arbitrary third-party seller, so it +# cannot be automated against our test offer. Do not extend this flow to +# fake that coverage; (1) and (2) above are the real, honest automated +# checks available today. +# +# Requires flow-10 (anvil fork of Base Sepolia + local facilitator) and a +# running public tunnel (`obol tunnel status`) — quick tunnel +# (*.trycloudflare.com) or permanent hostname both work. Override with +# TUNNEL_URL=https://… if needed. External buyer tools reach the seller +# over the public internet, not obol.stack. +# +# Requires a freshly built + rolled serviceoffer-controller (CLAUDE.md +# pitfall #19): /.well-known/x402 and /openapi.json's x-payment-info are +# only published by a controller image built from this branch. A warm +# `obol stack up` cache does NOT pick this up — force it: +# OBOL_FORCE_REBUILD_LOCAL_DEV_IMAGES=serviceoffer-controller OBOL_DEVELOPMENT=true obol stack up +# The discovery-document checks below fail with an explicit hint if the +# route 404s or the fields are missing, rather than a bare grep mismatch. +source "$(dirname "$0")/lib.sh" + +FLOW_STATE_DIR="$OBOL_ROOT/.workspace/state/flows" +mkdir -p "$FLOW_STATE_DIR" + +ANVIL_RPC="${ANVIL_RPC:-http://localhost:8545}" +FACILITATOR_PORT="${FACILITATOR_PORT:-4040}" +FACILITATOR_URL="http://localhost:$FACILITATOR_PORT" +OFFER_NAME="flow22-buyer-compat" +AGENT_NAME="${FLOW22_AGENT_NAME:-flow22buyer}" +AGENT_NS="agent-${AGENT_NAME}" +OFFER_NETWORK="base-sepolia" +OFFER_CAIP2="eip155:84532" +OFFER_PRICE="0.001" + +# Fresh seller EOA — same rationale as flow-17/flow-08: a well-known test +# account may carry a real-chain balance or EIP-7702 delegation code that +# makes balance-delta checks flaky; a fresh address starts clean. +SELLER_NEW=$(cast wallet new --json) +SELLER_ADDR=$(printf '%s' "$SELLER_NEW" | python3 -c "import json,sys;print(json.load(sys.stdin)[0]['address'])") + +# Fresh buyer EOA. Standard anvil/hardhat accounts #1-#9 carry EIP-7702 +# delegation code on a Base-Sepolia fork (real-chain 7702 experiments); +# FiatTokenV2_2 routes any code-bearing `from` to EIP-1271 and rejects an +# otherwise-valid ECDSA signature. A freshly generated key is clean. +BUYER_NEW=$(cast wallet new --json) +BUYER_KEY=$(printf '%s' "$BUYER_NEW" | python3 -c "import json,sys;print(json.load(sys.stdin)[0]['private_key'])") +BUYER_ADDR=$(printf '%s' "$BUYER_NEW" | python3 -c "import json,sys;print(json.load(sys.stdin)[0]['address'])") + +client() { # [method] [body] [timeout] — generic x402 SDK buyer + local mode="$1"; local url="$2"; local method="${3:-GET}"; local body="${4:-}" + local timeout="${5:-120s}" + local args=(-mode "$mode" -url "$url" -network "$OFFER_CAIP2" -method "$method" -timeout "$timeout") + if [ -n "$body" ]; then + args+=(-body "$body") + fi + (cd "$OBOL_ROOT" && go run flows/clients/x402-generic-buyer.go "${args[@]}" 2>&1) +} + +# §1: prerequisites — same as flow-17 (this offer routes through the +# cluster's x402-verifier, wired to the local anvil-fork facilitator by +# flow-10). +step "Local facilitator reachable (flow-10)" +if curl -sf "$FACILITATOR_URL/supported" >/dev/null 2>&1; then + pass "Facilitator at $FACILITATOR_URL" +else + fail "Facilitator not reachable at $FACILITATOR_URL — run flow-10 first" + emit_metrics + exit 0 +fi + +step "Anvil fork reachable" +if curl -sf "$ANVIL_RPC" -X POST -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}' | grep -q '0x14a34'; then + pass "Anvil fork of Base Sepolia at $ANVIL_RPC" +else + fail "Anvil not reachable / wrong chain at $ANVIL_RPC — run flow-10 first" + emit_metrics + exit 0 +fi + +# External buyer tools reach the seller over the public internet — this +# flow requires a live tunnel rather than the local-only obol.stack alias +# (which no real AgentCash/Bankr instance could ever resolve). Accepts a +# permanent hostname (e.g. https://seller.example.com) or a quick tunnel +# (*.trycloudflare.com). Prefer an explicit TUNNEL_URL if already set. +step "Public tunnel reachable" +if [ -z "${TUNNEL_URL:-}" ]; then + TUNNEL_OUTPUT=$("$OBOL" tunnel status 2>&1) || true + # Prefer an explicit "URL: https://…" line from tunnel status; fall + # back to the first public https origin in the output (skip localhost). + TUNNEL_URL=$(echo "$TUNNEL_OUTPUT" | grep -oE 'URL:[[:space:]]*https://[^[:space:]]+' | head -1 | awk '{print $2}' || true) + if [ -z "$TUNNEL_URL" ]; then + TUNNEL_URL=$(echo "$TUNNEL_OUTPUT" | grep -oE 'https://[^[:space:]]+' | grep -vE 'https://(127\.0\.0\.1|localhost)(:|/|$)' | head -1 || true) + fi + TUNNEL_URL="${TUNNEL_URL%/}" +fi +if [ -z "${TUNNEL_URL:-}" ]; then + fail "No public tunnel URL found — run \`obol tunnel restart\` / \`obol tunnel setup\`, or set TUNNEL_URL=https://…" + emit_metrics + exit 0 +fi +pass "Tunnel at $TUNNEL_URL" + +# §2: the seller — a demo HTTP offer, gated at OFFER_PRICE USDC/request. +step "Deploy demo offer ($OFFER_NAME)" +deploy_out=$("$OBOL" sell demo hello \ + --name "$OFFER_NAME" \ + --pay-to "$SELLER_ADDR" \ + --network "$OFFER_NETWORK" \ + --token USDC \ + --price "$OFFER_PRICE" 2>&1) || true +if echo "$deploy_out" | grep -qi "$OFFER_NAME"; then + pass "obol sell demo hello $OFFER_NAME" +else + fail "sell demo failed — ${deploy_out:0:300}" + emit_metrics + exit 0 +fi + +# "demo" matches demoNamespace in cmd/obol/sell.go — obol sell demo always +# creates its ServiceOffer there. +poll_step_grep "ServiceOffer $OFFER_NAME Ready (waiting for controller)" \ + "$OFFER_NAME.*True" 48 5 \ + "$OBOL" sell list --namespace demo + +OFFER_URL="$TUNNEL_URL/services/$OFFER_NAME" + +# §3: unpaid call is rejected with a real 402 + accepts[]. +step "Unpaid call returns 402 with accepts[]" +unpaid_body=$(curl -s "$OFFER_URL" 2>&1) || true +unpaid_status=$(curl -s -o /dev/null -w '%{http_code}' "$OFFER_URL" 2>&1) || true +if [ "$unpaid_status" = "402" ] && echo "$unpaid_body" | grep -q '"accepts"'; then + pass "402 with accepts[] — ${unpaid_body:0:120}" +else + fail "expected 402+accepts[], got status=$unpaid_status body=${unpaid_body:0:200}" +fi + +# §4: discovery documents carry this offer (Part A of this change). +# ServiceOffer Ready does NOT mean the static-site catalog has republished +# yet — reconcileStaticSite runs after Ready and can lag a few seconds +# (ConfigMap + httpd roll). Poll rather than one-shot curl, otherwise a +# just-created offer fails here while older offers still appear in the +# docs (misleading "stale controller" hint). A true 404 on well-known, or +# a doc that never gains this offer, still surfaces the rebuild hint. +STALE_CONTROLLER_HINT="If this stays missing after retries, rebuild + roll the controller: OBOL_FORCE_REBUILD_LOCAL_DEV_IMAGES=serviceoffer-controller OBOL_DEVELOPMENT=true obol stack up" + +step "Aggregate /openapi.json advertises this offer with x-payment-info" +openapi_ok=0 +openapi_doc="" +for _ in $(seq 1 24); do + openapi_doc=$(curl -s "$TUNNEL_URL/openapi.json" 2>&1) || true + if echo "$openapi_doc" | grep -q "/services/$OFFER_NAME" && echo "$openapi_doc" | grep -q "x-payment-info"; then + openapi_ok=1 + break + fi + sleep 5 +done +if [ "$openapi_ok" = "1" ]; then + pass "/openapi.json lists /services/$OFFER_NAME with x-payment-info" +else + fail "/openapi.json missing offer or x-payment-info after retries — ${openapi_doc:0:200} — $STALE_CONTROLLER_HINT" +fi + +step "Aggregate /.well-known/x402 fallback advertises this offer" +wellknown_ok=0 +wellknown_doc="" +wellknown_status="" +for _ in $(seq 1 24); do + wellknown_status=$(curl -s -o /dev/null -w '%{http_code}' "$TUNNEL_URL/.well-known/x402" 2>&1) || true + wellknown_doc=$(curl -s "$TUNNEL_URL/.well-known/x402" 2>&1) || true + if [ "$wellknown_status" = "404" ]; then + break + fi + if echo "$wellknown_doc" | grep -q "/services/$OFFER_NAME" && echo "$wellknown_doc" | grep -q '"accepts"'; then + wellknown_ok=1 + break + fi + sleep 5 +done +if [ "$wellknown_status" = "404" ]; then + fail "/.well-known/x402 returned 404 — this controller build doesn't publish it yet. $STALE_CONTROLLER_HINT" +elif [ "$wellknown_ok" = "1" ]; then + pass "/.well-known/x402 lists /services/$OFFER_NAME" +else + fail "/.well-known/x402 missing offer after retries — ${wellknown_doc:0:200} — $STALE_CONTROLLER_HINT" +fi + +# §5: the real AgentCash discovery CLI (Merit Systems), unmodified. +step "AgentCash discovery CLI accepts this offer as a real x402 resource" +if ! command -v npx >/dev/null 2>&1; then + skip "npx not available in this environment — cannot run the real AgentCash CLI" +else + agentcash_out=$(npx -y @agentcash/discovery@latest check "$OFFER_URL" --json 2>&1) + agentcash_status=$? + if [ "$agentcash_status" -eq 0 ]; then + pass "agentcash discovery check: $(echo "$agentcash_out" | tr -d '\n' | head -c 160)" + else + fail "agentcash discovery check failed (exit $agentcash_status) — ${agentcash_out:0:300}" + fi +fi + +# §6: fund the buyer and sync the fork clock (flow-17/flow-08 pattern). +step "Buyer is a clean EOA (no EIP-7702 / contract code on the fork)" +buyer_code=$(cast code "$BUYER_ADDR" --rpc-url "$ANVIL_RPC" 2>/dev/null || echo "0x") +if [ "$buyer_code" = "0x" ] || [ -z "$buyer_code" ]; then + pass "Buyer $BUYER_ADDR has no code — EIP-3009 ECDSA path will be used" +else + fail "Buyer $BUYER_ADDR carries code ($buyer_code) — FiatTokenV2_2 routes to EIP-1271 and will reject the signature" +fi + +step "Fund buyer wallet with USDC on the fork" +BUYER_SLOT=$(cast index address "$BUYER_ADDR" 9 2>&1) || true +if [[ "$BUYER_SLOT" =~ ^0x[0-9a-fA-F]+$ ]] && \ + cast rpc anvil_setStorageAt "$USDC_ADDRESS" "$BUYER_SLOT" \ + "0x000000000000000000000000000000000000000000000000000000003B9ACA00" \ + --rpc-url "$ANVIL_RPC" >/dev/null 2>&1; then + pass "Buyer $BUYER_ADDR funded with 1000 USDC" +else + fail "Could not fund buyer — ${BUYER_SLOT:0:120}" +fi + +step "Sync fork clock to real time" +if cast rpc evm_setNextBlockTimestamp "$(( $(date +%s) + 30 ))" --rpc-url "$ANVIL_RPC" >/dev/null 2>&1 \ + && cast rpc evm_mine --rpc-url "$ANVIL_RPC" >/dev/null 2>&1; then + pass "Fork block.timestamp advanced to ~now (+30s buffer for the signing window)" +else + fail "Could not advance fork clock" +fi + +# §7: the generic SDK buyer proves an unpaid call is rejected... +step "Generic x402 buyer (no signer) is rejected without payment" +out=$(client unpaid "$OFFER_URL") || true +if echo "$out" | grep -q '"status":402'; then + pass "Generic buyer without a signer got 402: $(echo "$out" | head -c 160)" +else + fail "Expected 402 for unsigned generic buyer call — ${out:0:300}" +fi + +# ...then completes a REAL paid call with only the public x402 SDK. +step "Generic x402 buyer completes a real paid call (no Obol tooling)" +out=$(X402_CLIENT_KEY="$BUYER_KEY" client paid "$OFFER_URL") || true +if echo "$out" | grep -q '"status":200' && echo "$out" | grep -qi "successfully paid"; then + pass "Generic buyer paid successfully: $(echo "$out" | head -c 200)" +else + fail "Generic buyer paid call failed — ${out:0:400}" +fi + +# ═════════════════════════════════════════════════════════════════ +# §8: Agent offer — OpenAI-compatible POST /v1/chat/completions. +# Same generic SDK buyer against a real type=agent ServiceOffer (Hermes +# upstream). This is the buyer-facing shape AgentCash/Bankr use on agent +# sellers. Skips when no model is available for `obol agent new`. +# ═════════════════════════════════════════════════════════════════ +step "Resolve a model for the external-buyer agent" +AGENT_MODEL="${FLOW22_AGENT_MODEL:-${FLOW_AGENT_MODEL:-${OBOL_LLM_MODEL:-}}}" +if [ -z "$AGENT_MODEL" ]; then + llm_config=$("$OBOL" kubectl get cm litellm-config -n llm \ + -o jsonpath='{.data.config\.yaml}' 2>&1) || true + AGENT_MODEL=$(printf '%s\n' "$llm_config" | awk ' + /model_name:/ { + gsub(/.*model_name:[[:space:]]*/, "") + gsub(/["'\'']/, "") + gsub(/[[:space:]]+$/, "") + if ($0 != "" && tolower($0) !~ /embed/) { print; exit } + }') +fi +if [ -n "$AGENT_MODEL" ]; then + pass "Using model $AGENT_MODEL for agent $AGENT_NAME" +else + skip "No model configured (set OBOL_LLM_MODEL / FLOW22_AGENT_MODEL or run obol model setup) — skipping agent external-buyer coverage" + emit_metrics + exit 0 +fi + +step "obol agent new $AGENT_NAME" +# Idempotent enough for re-runs: delete prior offer/agent only when asked. +if [ "${FLOW_CLEANUP:-0}" = "1" ]; then + "$OBOL" sell delete "$AGENT_NAME" -n "$AGENT_NS" --force >/dev/null 2>&1 || true + "$OBOL" agent delete --force "$AGENT_NAME" >/dev/null 2>&1 || true + sleep 2 +fi +new_out=$("$OBOL" agent new "$AGENT_NAME" \ + --model "$AGENT_MODEL" \ + --create-wallet \ + --objective "You are a smoke-test agent for external x402 buyers. Reply with exactly: ok" \ + 2>&1) || true +if echo "$new_out" | grep -qiE "Agent .*$AGENT_NAME (created|updated)|already exists|ready"; then + pass "Agent $AGENT_NAME declared" +else + # agent new may print success without those exact words — accept a live CR. + if "$OBOL" kubectl get agent "$AGENT_NAME" -n "$AGENT_NS" >/dev/null 2>&1; then + pass "Agent $AGENT_NAME present" + else + fail "obol agent new failed — ${new_out:0:300}" + emit_metrics + exit 0 + fi +fi + +step "Hermes pod ready for $AGENT_NAME" +pod_ready="" +for _ in $(seq 1 36); do + pod_ready=$("$OBOL" kubectl get pods -n "$AGENT_NS" -l app.kubernetes.io/name=hermes \ + -o jsonpath='{.items[0].status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || true) + [ "$pod_ready" = "True" ] && break + sleep 5 +done +if [ "$pod_ready" = "True" ]; then + pass "Hermes pod Ready in $AGENT_NS" +else + fail "Hermes pod not Ready within 180s (ready=$pod_ready)" + emit_metrics + exit 0 +fi + +step "obol sell agent $AGENT_NAME" +sell_out=$("$OBOL" sell agent "$AGENT_NAME" \ + --pay-to "$SELLER_ADDR" \ + --price "$OFFER_PRICE" \ + --token USDC \ + --chain "$OFFER_NETWORK" \ + --no-register \ + 2>&1) || true +if echo "$sell_out" | grep -qiE "ServiceOffer .*$AGENT_NAME (created|updated)|$AGENT_NAME"; then + pass "ServiceOffer $AGENT_NAME published (type=agent)" +else + fail "obol sell agent failed — ${sell_out:0:300}" + emit_metrics + exit 0 +fi + +# With --no-register the aggregate Ready may stay False +# (AwaitingExternalRegistration). Gate on the serving conditions instead +# (same pattern as flow-16). +step "Agent offer reaches serving state (UpstreamHealthy+PaymentGateReady+RoutePublished)" +serving="" +conds="" +for _ in $(seq 1 60); do + conds=$("$OBOL" kubectl get serviceoffer "$AGENT_NAME" -n "$AGENT_NS" \ + -o jsonpath='{range .status.conditions[*]}{.type}={.status} {end}' 2>/dev/null || true) + if echo "$conds" | grep -qE '(^| )UpstreamHealthy=True' \ + && echo "$conds" | grep -qE '(^| )PaymentGateReady=True' \ + && echo "$conds" | grep -qE '(^| )RoutePublished=True'; then + serving="yes" + break + fi + sleep 5 +done +if [ -n "$serving" ]; then + pass "Agent offer serving (conditions: $conds)" +else + fail "Agent offer not serving within 300s — conditions: ${conds:-unreadable}" + emit_metrics + exit 0 +fi + +AGENT_URL="$TUNNEL_URL/services/$AGENT_NAME/v1/chat/completions" +# stream:true is the recommended agent path (tunnel idle windows). Agents +# ignore the request model field; agent/ is the conventional placeholder. +AGENT_BODY=$(python3 -c "import json; print(json.dumps({ + 'model': 'agent/$AGENT_NAME', + 'messages': [{'role': 'user', 'content': 'Reply with exactly: ok'}], + 'stream': True, +}))") + +step "Discovery lists agent chat-completions resource" +agent_disc_ok=0 +for _ in $(seq 1 24); do + wk=$(curl -s "$TUNNEL_URL/.well-known/x402" 2>&1) || true + oa=$(curl -s "$TUNNEL_URL/openapi.json" 2>&1) || true + if echo "$wk" | grep -q "/services/$AGENT_NAME/v1/chat/completions" \ + && echo "$oa" | grep -q "/services/$AGENT_NAME/v1/chat/completions" \ + && echo "$oa" | grep -q "x-payment-info"; then + agent_disc_ok=1 + break + fi + sleep 5 +done +if [ "$agent_disc_ok" = "1" ]; then + pass "Discovery advertises $AGENT_URL" +else + fail "Discovery missing agent chat-completions resource — $STALE_CONTROLLER_HINT" +fi + +step "AgentCash discovery CLI accepts agent chat-completions resource" +if ! command -v npx >/dev/null 2>&1; then + skip "npx not available — cannot run AgentCash CLI against agent URL" +else + agentcash_agent=$(npx -y @agentcash/discovery@latest check "$AGENT_URL" --json 2>&1) + agentcash_agent_status=$? + if [ "$agentcash_agent_status" -eq 0 ]; then + pass "agentcash discovery check (agent): $(echo "$agentcash_agent" | tr -d '\n' | head -c 160)" + else + fail "agentcash discovery check (agent) failed (exit $agentcash_agent_status) — ${agentcash_agent:0:300}" + fi +fi + +step "Sync fork clock before agent paid call" +cast rpc evm_setNextBlockTimestamp "$(( $(date +%s) + 30 ))" --rpc-url "$ANVIL_RPC" >/dev/null 2>&1 || true +cast rpc evm_mine --rpc-url "$ANVIL_RPC" >/dev/null 2>&1 || true +pass "Fork clock advanced for agent buy" + +step "Generic x402 buyer unpaid POST agent chat-completions returns 402" +out=$(client unpaid "$AGENT_URL" POST "$AGENT_BODY" 60s) || true +if echo "$out" | grep -q '"status":402'; then + pass "Unpaid agent chat-completions POST got 402: $(echo "$out" | head -c 160)" +else + fail "Expected 402 for unpaid agent POST — ${out:0:300}" +fi + +step "Generic x402 buyer paid POST agent chat-completions returns 200" +out=$(X402_CLIENT_KEY="$BUYER_KEY" client paid "$AGENT_URL" POST "$AGENT_BODY" 180s) || true +if echo "$out" | grep -q '"status":200'; then + pass "Paid agent chat-completions succeeded: $(echo "$out" | head -c 220)" +else + fail "Paid agent chat-completions failed — ${out:0:500}" +fi + +if [ "${FLOW_CLEANUP:-0}" = "1" ]; then + "$OBOL" sell delete "$AGENT_NAME" -n "$AGENT_NS" --force >/dev/null 2>&1 || true + "$OBOL" agent delete --force "$AGENT_NAME" >/dev/null 2>&1 || true +fi + +emit_metrics diff --git a/flows/release-smoke.sh b/flows/release-smoke.sh index 177c404c..06cead69 100755 --- a/flows/release-smoke.sh +++ b/flows/release-smoke.sh @@ -270,6 +270,13 @@ main() { # hostname binding (P1b), on a self-contained throwaway offer. gate # enforcement needs no facilitator, so it also sits in this group. "$SCRIPT_DIR/flow-21-route-surface.sh" + # flow-22 is the only coverage of external buyer-tool compatibility + # (AgentCash/x402scan/Poncho discovery + a generic non-Obol x402 SDK + # buyer). Reuses flow-10's anvil fork + facilitator (still up here) + # and the tunnel `obol stack up` already started. The AgentCash CLI + # sub-check (`npx @agentcash/discovery`) skips gracefully rather than + # failing when Node isn't available in the runner. + "$SCRIPT_DIR/flow-22-external-buyer-compat.sh" ) for flow in "${flows[@]}"; do diff --git a/internal/buyprompts/buyprompts.go b/internal/buyprompts/buyprompts.go index dbd498d6..91cd92d4 100644 --- a/internal/buyprompts/buyprompts.go +++ b/internal/buyprompts/buyprompts.go @@ -33,6 +33,15 @@ const ( PromptGenericLLM = "generic-llm" // PromptCLI is the shell command a human runs from an obol-stack host. PromptCLI = "cli" + // PromptAgentCash is pasted into an AgentCash-connected agent/wallet. + // AgentCash (Merit Systems) shares its discovery convention — OpenAPI + // `x-payment-info` plus a `/.well-known/x402` fallback — with x402scan + // and Poncho, so this same prompt covers all three. + PromptAgentCash = "agentcash" + // PromptBankr is pasted into a Bankr-connected agent/wallet. Bankr + // publishes no seller-side discovery format of its own, so the copy + // describes the plain x402 handshake rather than anything bespoke. + PromptBankr = "bankr" ) // Input describes one purchasable service. All fields are display-ready @@ -186,6 +195,25 @@ func agentBlock(in Input) Block { "python3 ${OBOL_SKILLS_DIR:-/data/.openclaw/skills}/buy-x402/scripts/buy.py go %s --message %q", in.URL, task(in), ), + PromptAgentCash: fmt.Sprintf( + "Help me call the Obol Agent at %s — it's an autonomous agent (tools + skills + "+ + "memory), not a raw LLM. It's gated by %s, and already publishes AgentCash's "+ + "discovery convention (OpenAPI `x-payment-info`, `/.well-known/x402` fallback) "+ + "so your agent can auto-discover the price. POST OpenAI-style chat-completions "+ + "JSON to %s with this user message in `messages`: "+ + "{\"role\":\"user\",\"content\":%q}. Attach a signed EIP-3009/Permit2 authorization "+ + "as `X-PAYMENT`, use `\"stream\": true`, and report what the agent does.", + in.URL, GuideRef(in.SiteURL), ChatCompletionsURL(in.URL), task(in), + ), + PromptBankr: fmt.Sprintf( + "Pay for the Obol Agent at %s with your Bankr wallet — it's an autonomous agent "+ + "(tools + skills + memory), not a raw LLM. It's gated by %s; there's no "+ + "Bankr-specific format here, just standard x402. POST OpenAI-style "+ + "chat-completions JSON to %s with this user message in `messages`: "+ + "{\"role\":\"user\",\"content\":%q}. Attach a signed EIP-3009/Permit2 authorization "+ + "as `X-PAYMENT`, use `\"stream\": true`, and report what the agent does.", + in.URL, GuideRef(in.SiteURL), ChatCompletionsURL(in.URL), task(in), + ), }, Example: ChatExample(in.URL, in.Model, in.TaskExample), } @@ -216,6 +244,23 @@ func inferenceBlock(in Input) Block { in.URL, model, ChatCompletionsURL(in.URL), GuideRef(in.SiteURL), ), PromptCLI: fmt.Sprintf("obol buy inference %s", in.URL), + PromptAgentCash: fmt.Sprintf( + "I want to use the remote LLM at %s (model %s) as a paid OpenAI-compatible "+ + "chat-completions endpoint at %s. This origin already publishes AgentCash's "+ + "discovery convention (OpenAPI `x-payment-info`, `/.well-known/x402` "+ + "fallback), paid with %s — pre-sign a budget of EIP-3009/Permit2 "+ + "authorizations and POST chat-completions bodies with the X-PAYMENT header "+ + "attached.", + in.URL, model, ChatCompletionsURL(in.URL), GuideRef(in.SiteURL), + ), + PromptBankr: fmt.Sprintf( + "Pay for the remote LLM at %s (model %s) with your Bankr wallet, as a paid "+ + "OpenAI-compatible chat-completions endpoint at %s, paid with %s. There's no "+ + "Bankr-specific format here — pre-sign a budget of EIP-3009/Permit2 "+ + "authorizations and POST chat-completions bodies with the X-PAYMENT header "+ + "attached.", + in.URL, model, ChatCompletionsURL(in.URL), GuideRef(in.SiteURL), + ), }, // The model field is required by chat-completions upstreams for // inference offers, so the example keeps a placeholder when the @@ -255,6 +300,23 @@ func httpBlock(in Input) Block { "python3 ${OBOL_SKILLS_DIR:-/data/.openclaw/skills}/buy-x402/scripts/buy.py go %s", in.URL, ), + PromptAgentCash: fmt.Sprintf( + "Call the paid HTTP endpoint at %s once. It's gated by %s, and this origin "+ + "already speaks AgentCash's own discovery convention (OpenAPI "+ + "`x-payment-info` plus a `/.well-known/x402` fallback) — your "+ + "AgentCash-connected agent can auto-discover the price instead of parsing "+ + "the 402 by hand.%s%s Sign a matching EIP-3009/Permit2 authorization and "+ + "retry with the payload in the `X-PAYMENT` header.", + in.URL, GuideRef(in.SiteURL), priceClause, netClause, + ), + PromptBankr: fmt.Sprintf( + "Call the paid HTTP endpoint at %s once with your Bankr wallet.%s%s There's no "+ + "Bankr-specific format here — it's a standard x402 endpoint: fetch it with "+ + "no payment to read the 402 `accepts[]` pricing, sign a matching "+ + "EIP-3009/Permit2 authorization, and retry with the payload in the "+ + "`X-PAYMENT` header.", + in.URL, priceClause, netClause, + ), }, } } diff --git a/internal/buyprompts/buyprompts_test.go b/internal/buyprompts/buyprompts_test.go index 0f79ceec..0783e3b7 100644 --- a/internal/buyprompts/buyprompts_test.go +++ b/internal/buyprompts/buyprompts_test.go @@ -37,11 +37,11 @@ func TestBuild_ChatOffersTeachCanonicalPath(t *testing.T) { } // TestBuild_AllTypesCarryEveryPromptKey ensures every surface can rely on -// the three standard prompt keys existing for every offer type. +// the five standard prompt keys existing for every offer type. func TestBuild_AllTypesCarryEveryPromptKey(t *testing.T) { for _, typ := range []string{"agent", "inference", "http", "fine-tuning", "", "bogus"} { block := Build(Input{Type: typ, URL: "https://s.example/services/x"}) - for _, key := range []string{PromptObolAgent, PromptGenericLLM, PromptCLI} { + for _, key := range []string{PromptObolAgent, PromptGenericLLM, PromptCLI, PromptAgentCash, PromptBankr} { if strings.TrimSpace(block.Prompts[key]) == "" { t.Errorf("type %q: missing prompt %q", typ, key) } diff --git a/internal/embed/skills/buy-x402/SKILL.md b/internal/embed/skills/buy-x402/SKILL.md index ecaf385b..67dab244 100644 --- a/internal/embed/skills/buy-x402/SKILL.md +++ b/internal/embed/skills/buy-x402/SKILL.md @@ -107,6 +107,13 @@ Permit2 approval above). storefront publishes machine-readable metadata at `/api/services.json` with full asset, EIP-712 signing domain, transfer method, and atomic-unit price for every offered service. +- **Third-party sellers may not publish `/api/services.json`.** When + buying from a non-Obol x402 seller, also check `/openapi.json` + (paid operations carry an `x-payment-info` extension — the AgentCash/ + x402scan/Poncho discovery convention) and, failing that, the flatter + `/.well-known/x402` fallback (`{"x402Version":2,"resources":[...]}`, + one entry per paid resource with its `accepts[]` requirements). Obol + Stack sellers publish all three, plus `/skill.md`. - **`pay` timeout defaults: ~100 s for http, 600 s for inference.** The 100 s http default matches the Cloudflare free-tier tunnel cap — longer requests get killed by the edge before our client ever sees a response. diff --git a/internal/serviceoffercontroller/catalog.go b/internal/serviceoffercontroller/catalog.go index 97327377..c34bcdb6 100644 --- a/internal/serviceoffercontroller/catalog.go +++ b/internal/serviceoffercontroller/catalog.go @@ -40,7 +40,7 @@ func (c *Controller) listServiceOffersForCatalog(ctx context.Context, override * return offers, nil } -func staticSiteContentMatches(cm *unstructured.Unstructured, content, servicesJSON, openAPIJSON, apiDocsHTML string, bundles []offerBundleFile) bool { +func staticSiteContentMatches(cm *unstructured.Unstructured, content, servicesJSON, openAPIJSON, apiDocsHTML, wellKnownX402JSON string, bundles []offerBundleFile) bool { if cm == nil { return false } @@ -51,6 +51,7 @@ func staticSiteContentMatches(cm *unstructured.Unstructured, content, servicesJS if data["skill.md"] != content || data["services.json"] != servicesJSON || data["openapi.json"] != openAPIJSON || + data["x402.json"] != wellKnownX402JSON || data["api.html"] != apiDocsHTML { return false } @@ -75,7 +76,7 @@ func staticSiteContentMatches(cm *unstructured.Unstructured, content, servicesJS return true } -func (c *Controller) staticSiteContentUnchanged(ctx context.Context, content, servicesJSON, openAPIJSON, apiDocsHTML string, bundles []offerBundleFile) (bool, error) { +func (c *Controller) staticSiteContentUnchanged(ctx context.Context, content, servicesJSON, openAPIJSON, apiDocsHTML, wellKnownX402JSON string, bundles []offerBundleFile) (bool, error) { cm, err := c.configMaps.Namespace(staticSiteNamespace).Get(ctx, staticSiteConfigMapName, metav1.GetOptions{}) if apierrors.IsNotFound(err) { return false, nil @@ -83,11 +84,11 @@ func (c *Controller) staticSiteContentUnchanged(ctx context.Context, content, se if err != nil { return false, err } - return staticSiteContentMatches(cm, content, servicesJSON, openAPIJSON, apiDocsHTML, bundles), nil + return staticSiteContentMatches(cm, content, servicesJSON, openAPIJSON, apiDocsHTML, wellKnownX402JSON, bundles), nil } -func computeStaticSiteContentHash(content, servicesJSON, openAPIJSON, apiDocsHTML string, bundles []offerBundleFile) string { - return fmt.Sprintf("%x", md5Sum(content+servicesJSON+openAPIJSON+apiDocsHTML+bundleDigestInput(bundles)))[:8] +func computeStaticSiteContentHash(content, servicesJSON, openAPIJSON, apiDocsHTML, wellKnownX402JSON string, bundles []offerBundleFile) string { + return fmt.Sprintf("%x", md5Sum(content+servicesJSON+openAPIJSON+apiDocsHTML+wellKnownX402JSON+bundleDigestInput(bundles)))[:8] } func staticSiteDeployedContentHash(deployment *unstructured.Unstructured) string { diff --git a/internal/serviceoffercontroller/catalog_test.go b/internal/serviceoffercontroller/catalog_test.go index 184a5939..bb95ee5c 100644 --- a/internal/serviceoffercontroller/catalog_test.go +++ b/internal/serviceoffercontroller/catalog_test.go @@ -7,8 +7,8 @@ import ( ) func TestComputeStaticSiteContentHashDeterministic(t *testing.T) { - a := computeStaticSiteContentHash("# cat", `{"services":[]}`, `{"openapi":"3.1.0"}`, "", nil) - b := computeStaticSiteContentHash("# cat", `{"services":[]}`, `{"openapi":"3.1.0"}`, "", nil) + a := computeStaticSiteContentHash("# cat", `{"services":[]}`, `{"openapi":"3.1.0"}`, "", `{"resources":[]}`, nil) + b := computeStaticSiteContentHash("# cat", `{"services":[]}`, `{"openapi":"3.1.0"}`, "", `{"resources":[]}`, nil) if a != b { t.Fatalf("hash not deterministic: %q vs %q", a, b) } @@ -16,21 +16,21 @@ func TestComputeStaticSiteContentHashDeterministic(t *testing.T) { t.Fatalf("hash length = %d, want 8", len(a)) } - changed := computeStaticSiteContentHash("# cat", `{"services":[{"name":"a"}]}`, `{"openapi":"3.1.0"}`, "", nil) + changed := computeStaticSiteContentHash("# cat", `{"services":[{"name":"a"}]}`, `{"openapi":"3.1.0"}`, "", `{"resources":[]}`, nil) if changed == a { t.Fatal("expected different hash when catalog content changes") } } func TestStaticSiteContentMatches(t *testing.T) { - cm := buildStaticSiteConfigMap("# cat", `{"services":[]}`, `{"openapi":"3.1.0"}`, "", nil) - if !staticSiteContentMatches(cm, "# cat", `{"services":[]}`, `{"openapi":"3.1.0"}`, "", nil) { + cm := buildStaticSiteConfigMap("# cat", `{"services":[]}`, `{"openapi":"3.1.0"}`, "", `{"resources":[]}`, nil) + if !staticSiteContentMatches(cm, "# cat", `{"services":[]}`, `{"openapi":"3.1.0"}`, "", `{"resources":[]}`, nil) { t.Fatal("expected matching catalog content") } - if staticSiteContentMatches(cm, "# changed", `{"services":[]}`, `{"openapi":"3.1.0"}`, "", nil) { + if staticSiteContentMatches(cm, "# changed", `{"services":[]}`, `{"openapi":"3.1.0"}`, "", `{"resources":[]}`, nil) { t.Fatal("expected different skill.md to not match") } - if staticSiteContentMatches(nil, "# cat", `{}`, `{}`, "", nil) { + if staticSiteContentMatches(nil, "# cat", `{}`, `{}`, "", "", nil) { t.Fatal("nil configmap must not match") } } diff --git a/internal/serviceoffercontroller/controller.go b/internal/serviceoffercontroller/controller.go index 1c3ebb8e..05371a5d 100644 --- a/internal/serviceoffercontroller/controller.go +++ b/internal/serviceoffercontroller/controller.go @@ -1352,11 +1352,12 @@ func (c *Controller) reconcileStaticSite(ctx context.Context, override *monetize // on tunnel restarts (the configMap informer re-enqueues every offer // when tunnelURL changes — see enqueueDiscoveryRefresh). openAPIJSON := buildOpenAPIDocument(offers, baseURL, resolvedProfile) + wellKnownX402JSON := buildAggregateWellKnownX402(offers, baseURL) apiDocsHTML := scalarHTML(resolvedProfile) bundles := buildOfferBundles(offers, resolvedProfile, c.upstreamOpenAPICache.get) - contentHash := computeStaticSiteContentHash(content, servicesJSON, openAPIJSON, apiDocsHTML, bundles) + contentHash := computeStaticSiteContentHash(content, servicesJSON, openAPIJSON, apiDocsHTML, wellKnownX402JSON, bundles) - unchanged, err := c.staticSiteContentUnchanged(ctx, content, servicesJSON, openAPIJSON, apiDocsHTML, bundles) + unchanged, err := c.staticSiteContentUnchanged(ctx, content, servicesJSON, openAPIJSON, apiDocsHTML, wellKnownX402JSON, bundles) if err != nil { return err } @@ -1366,7 +1367,7 @@ func (c *Controller) reconcileStaticSite(ctx context.Context, override *monetize return nil } - if err := c.applyObject(ctx, c.configMaps.Namespace(staticSiteNamespace), buildStaticSiteConfigMap(content, servicesJSON, openAPIJSON, apiDocsHTML, bundles)); err != nil { + if err := c.applyObject(ctx, c.configMaps.Namespace(staticSiteNamespace), buildStaticSiteConfigMap(content, servicesJSON, openAPIJSON, apiDocsHTML, wellKnownX402JSON, bundles)); err != nil { return err } if err := c.applyObject(ctx, c.deployments.Namespace(staticSiteNamespace), buildStaticSiteDeployment(contentHash, bundles)); err != nil { @@ -1389,6 +1390,9 @@ func (c *Controller) reconcileStaticSite(ctx context.Context, override *monetize if err := c.applyObject(ctx, c.httpRoutes.Namespace(staticSiteNamespace), buildOpenAPIHTTPRoute()); err != nil { return err } + if err := c.applyObject(ctx, c.httpRoutes.Namespace(staticSiteNamespace), buildWellKnownX402HTTPRoute()); err != nil { + return err + } if err := c.applyObject(ctx, c.httpRoutes.Namespace(staticSiteNamespace), buildAPIDocsHTTPRoute()); err != nil { return err } diff --git a/internal/serviceoffercontroller/offerbundle.go b/internal/serviceoffercontroller/offerbundle.go index 84786e41..b64718c3 100644 --- a/internal/serviceoffercontroller/offerbundle.go +++ b/internal/serviceoffercontroller/offerbundle.go @@ -217,7 +217,25 @@ func openAPIRelPathForOfferRoute(offer *monetizeapi.ServiceOffer, routePath stri // offer without probing). Tracks the x402 discovery-list shape used by // facilitator /discovery/resources feeds. func buildOfferWellKnownX402(offer *monetizeapi.ServiceOffer) string { - origin := offer.EffectiveOrigin() + doc := map[string]any{ + "x402Version": 2, + "resources": wellKnownResourcesForOffer(offer, offer.EffectiveOrigin(), "/"), + } + encoded, err := json.MarshalIndent(doc, "", " ") + if err != nil { + return `{"x402Version":2,"resources":[]}` + } + return string(encoded) +} + +// wellKnownResourcesForOffer builds one /.well-known/x402 resource entry per +// paid route on offer, rooted at origin+pathPrefix. Shared by the per-offer +// hostname-bound bundle (origin=offer.EffectiveOrigin(), pathPrefix="/") and +// the aggregate storefront catalog's fallback document +// (buildAggregateWellKnownX402 in render.go; origin=baseURL, +// pathPrefix=offer.EffectivePath()) so both surfaces describe the same paid +// operations identically. +func wellKnownResourcesForOffer(offer *monetizeapi.ServiceOffer, origin, pathPrefix string) []any { var resources []any for _, rt := range offer.EffectiveRoutes() { if rt.EffectiveGate() != monetizeapi.GatePaid { @@ -242,7 +260,7 @@ func buildOfferWellKnownX402(offer *monetizeapi.ServiceOffer) string { desc = offerDescription(offer, "x402 payment-gated service.") } resources = append(resources, map[string]any{ - "resource": origin + joinOpenAPIPath("/", openAPIRelPathForOfferRoute(offer, rt.Path)), + "resource": origin + joinOpenAPIPath(pathPrefix, openAPIRelPathForOfferRoute(offer, rt.Path)), "type": "http", "method": method, "description": desc, @@ -250,15 +268,7 @@ func buildOfferWellKnownX402(offer *monetizeapi.ServiceOffer) string { "accepts": accepts, }) } - doc := map[string]any{ - "x402Version": 2, - "resources": resources, - } - encoded, err := json.MarshalIndent(doc, "", " ") - if err != nil { - return `{"x402Version":2,"resources":[]}` - } - return string(encoded) + return resources } // wellKnownAccept renders one payment option in the 402-requirement shape diff --git a/internal/serviceoffercontroller/render.go b/internal/serviceoffercontroller/render.go index f6829e88..330ac487 100644 --- a/internal/serviceoffercontroller/render.go +++ b/internal/serviceoffercontroller/render.go @@ -37,6 +37,7 @@ const ( servicesJSONRouteName = "obol-services-json-route" openAPIRouteName = "obol-openapi-route" apiDocsRouteName = "obol-api-docs-route" + wellKnownX402RouteName = "obol-wellknown-x402-route" // catalogHeadersMiddlewareName is the Traefik headers Middleware attached // to the public catalog HTTPRoutes (/skill.md, /openapi.json, /api, @@ -264,11 +265,12 @@ func agentIdentityLabels(identity *monetizeapi.AgentIdentity, appName string) ma } } -func buildStaticSiteConfigMap(content, servicesJSON, openAPIJSON, apiDocsHTML string, bundles []offerBundleFile) *unstructured.Unstructured { +func buildStaticSiteConfigMap(content, servicesJSON, openAPIJSON, apiDocsHTML, wellKnownX402JSON string, bundles []offerBundleFile) *unstructured.Unstructured { data := map[string]any{ "skill.md": content, "services.json": servicesJSON, "openapi.json": openAPIJSON, + "x402.json": wellKnownX402JSON, "api.html": apiDocsHTML, "httpd.conf": ".md:text/markdown\n.json:application/json\n.html:text/html\n.js:text/javascript\n", } @@ -300,6 +302,12 @@ func staticSiteVolumeItems(bundles []offerBundleFile) []any { map[string]any{"key": "skill.md", "path": "skill.md"}, map[string]any{"key": "services.json", "path": "api/services.json"}, map[string]any{"key": "openapi.json", "path": "openapi.json"}, + // Mounted with a .json name (not the public ".well-known/x402" path) + // so busybox's extension-based httpd.conf serves it as + // application/json — the HTTPRoute below rewrites the public path + // to this file, the same pattern buildHostHTTPRoute uses for the + // per-offer copy. + map[string]any{"key": "x402.json", "path": "wellknown-x402.json"}, // busybox httpd resolves /api/ → /api/index.html, so the // Scalar shell sits at api/index.html. The /api Exact // HTTPRoute also matches the trailing-slash variant so the @@ -558,6 +566,73 @@ func buildOpenAPIHTTPRoute() *unstructured.Unstructured { } } +// buildWellKnownX402HTTPRoute exposes the aggregate /.well-known/x402 +// discovery-list fallback for every offer sharing the storefront's origin +// (hostname-bound offers publish their own copy at their dedicated origin +// via buildHostHTTPRoute, but still appear here too under their shared-origin +// alias — see buildAggregateWellKnownX402). AgentCash/x402scan-style +// crawlers that don't parse OpenAPI's x-payment-info extension fall back to +// this document. Same public posture as /openapi.json: no hostnames filter, +// no secret material. +func buildWellKnownX402HTTPRoute() *unstructured.Unstructured { + return &unstructured.Unstructured{ + Object: map[string]any{ + "apiVersion": "gateway.networking.k8s.io/v1", + "kind": "HTTPRoute", + "metadata": map[string]any{ + "name": wellKnownX402RouteName, + "namespace": staticSiteNamespace, + "labels": map[string]any{ + "obol.org/managed-by": "serviceoffer-controller", + }, + }, + "spec": map[string]any{ + "parentRefs": []any{ + map[string]any{ + "name": "traefik-gateway", + "namespace": "traefik", + "sectionName": "web", + }, + }, + "rules": []any{ + map[string]any{ + "matches": []any{ + map[string]any{ + "path": map[string]any{ + "type": "Exact", + "value": "/.well-known/x402", + }, + }, + }, + // Rewrite the extensionless public path to the .json- + // suffixed file mounted in the ConfigMap volume, so + // busybox's extension-based httpd.conf serves + // application/json instead of a default octet-stream. + "filters": append([]any{ + map[string]any{ + "type": "URLRewrite", + "urlRewrite": map[string]any{ + "path": map[string]any{ + "type": "ReplaceFullPath", + "replaceFullPath": "/wellknown-x402.json", + }, + }, + }, + }, catalogHeadersFilters()...), + "backendRefs": []any{ + map[string]any{ + "name": staticSiteConfigMapName, + "namespace": staticSiteNamespace, + "port": int64(8080), + }, + }, + }, + }, + }, + }, + } +} + // buildAPIDocsHTTPRoute exposes the Scalar UI shell at /api and /api/. // Two Exact rules are needed because Gateway API's Exact matcher does not // normalize trailing slashes; busybox httpd resolves /api/ to @@ -1319,6 +1394,7 @@ func buildSkillMarkdown(offers []*monetizeapi.ServiceOffer, baseURL string, expl "> **Machine-readable:** " + fmt.Sprintf("OpenAPI 3.1 (Swagger) at [`%s/openapi.json`](%s/openapi.json) · ", baseURL, baseURL) + fmt.Sprintf("catalog feed at [`%s/api/services.json`](%s/api/services.json) · ", baseURL, baseURL) + + fmt.Sprintf("x402 discovery fallback at [`%s/.well-known/x402`](%s/.well-known/x402) · ", baseURL, baseURL) + fmt.Sprintf("agent identity at [`%s/.well-known/agent-registration.json`](%s/.well-known/agent-registration.json).", baseURL, baseURL), "", } @@ -1441,6 +1517,13 @@ func skillMarkdownHowToPay(baseURL string) []string { "describes every operation's path, method, request/response body, and per-operation pricing "+ "(`x-payment-info`). Load it into any OpenAPI-aware client to generate a typed caller.", baseURL, baseURL), "", + fmt.Sprintf("**Discovery fallback:** a flat resource list is also published at "+ + "[`%s/.well-known/x402`](%s/.well-known/x402) for crawlers that don't parse OpenAPI. This "+ + "catalog is compatible with AgentCash- and x402scan-style discovery (Merit Systems' shared "+ + "convention: OpenAPI `x-payment-info` plus the `/.well-known/x402` fallback — also covers "+ + "Poncho) as well as any standard x402 buyer, e.g. Bankr, via the core 402/`X-PAYMENT` "+ + "handshake above.", baseURL, baseURL), + "", "**Already on Obol Stack?** The `buy-x402` skill automates the whole loop: " + "`buy.py pay ` for one-shot calls (add `--token ` / `--network ` to " + "choose among multi-currency options), or `buy.py buy --endpoint --model ` to " + @@ -1590,6 +1673,53 @@ func offerAwaitingRegistration(offer *monetizeapi.ServiceOffer) bool { return false } +// buildAggregateWellKnownX402 renders the /.well-known/x402 discovery +// fallback for the shared storefront catalog: one resource entry per paid +// route across every operationally-ready offer, rooted at baseURL + +// offer.EffectivePath() — the same shared-origin alias buildOpenAPIDocument +// and buildServiceCatalogJSON already publish every offer under (hostname- +// bound offers included; they additionally get their own root-rooted copy +// via buildOfferWellKnownX402/buildHostHTTPRoute). AgentCash/x402scan-style +// crawlers fall back to this document when they don't parse OpenAPI's +// x-payment-info extension. +func buildAggregateWellKnownX402(offers []*monetizeapi.ServiceOffer, baseURL string) string { + baseURL = strings.TrimRight(baseURL, "/") + + now := time.Now() + var ready []*monetizeapi.ServiceOffer + for _, offer := range offers { + if offer == nil || offer.DeletionTimestamp != nil { + continue + } + if offer.DrainExpired(now) { + continue + } + if offerOperationallyReady(offer) { + ready = append(ready, offer) + } + } + sort.Slice(ready, func(i, j int) bool { + if ready[i].Namespace == ready[j].Namespace { + return ready[i].Name < ready[j].Name + } + return ready[i].Namespace < ready[j].Namespace + }) + + var resources []any + for _, offer := range ready { + resources = append(resources, wellKnownResourcesForOffer(offer, baseURL, offer.EffectivePath())...) + } + doc := map[string]any{ + "x402Version": 2, + "resources": resources, + } + encoded, err := json.MarshalIndent(doc, "", " ") + if err != nil { + return `{"x402Version":2,"resources":[]}` + } + return string(encoded) +} + // buildServiceCatalogJSON returns the public /api/services.json envelope: // seller branding plus operationally-ready ServiceOffers. // diff --git a/internal/serviceoffercontroller/render_builders_test.go b/internal/serviceoffercontroller/render_builders_test.go index a650ffc5..c867f59a 100644 --- a/internal/serviceoffercontroller/render_builders_test.go +++ b/internal/serviceoffercontroller/render_builders_test.go @@ -103,7 +103,7 @@ func TestBuildAgentIdentityRegistrationDeployment_RestrictedPSS(t *testing.T) { // TestBuildStaticSiteConfigMap: exposes skill.md + services.json + openapi.json // + api docs HTML + httpd conf. func TestBuildStaticSiteConfigMap(t *testing.T) { - cm := buildStaticSiteConfigMap("# Catalog", `{"displayName":"Acme","tagline":"t","logoUrl":"https://x/logo.png","services":[{"name":"a"}]}`, `{"openapi":"3.1.0"}`, "shell", nil) + cm := buildStaticSiteConfigMap("# Catalog", `{"displayName":"Acme","tagline":"t","logoUrl":"https://x/logo.png","services":[{"name":"a"}]}`, `{"openapi":"3.1.0"}`, "shell", `{"x402Version":2,"resources":[]}`, nil) if cm.GetName() != staticSiteConfigMapName { t.Errorf("name = %q, want %q", cm.GetName(), staticSiteConfigMapName) @@ -121,6 +121,9 @@ func TestBuildStaticSiteConfigMap(t *testing.T) { if data["openapi.json"] != `{"openapi":"3.1.0"}` { t.Errorf("openapi.json payload mismatch, got %v", data["openapi.json"]) } + if data["x402.json"] != `{"x402Version":2,"resources":[]}` { + t.Errorf("x402.json payload mismatch, got %v", data["x402.json"]) + } if data["api.html"] != "shell" { t.Errorf("api.html payload mismatch, got %v", data["api.html"]) } @@ -164,6 +167,7 @@ func TestBuildStaticSiteDeployment(t *testing.T) { expectedPaths := map[string]string{ "services.json": "api/services.json", "openapi.json": "openapi.json", + "x402.json": "wellknown-x402.json", "api.html": "api/index.html", } foundPaths := map[string]string{} diff --git a/internal/serviceoffercontroller/wellknown_aggregate_test.go b/internal/serviceoffercontroller/wellknown_aggregate_test.go new file mode 100644 index 00000000..e06c4209 --- /dev/null +++ b/internal/serviceoffercontroller/wellknown_aggregate_test.go @@ -0,0 +1,124 @@ +package serviceoffercontroller + +import ( + "testing" + + "github.com/ObolNetwork/obol-stack/internal/monetizeapi" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// TestBuildAggregateWellKnownX402_BasicOffer confirms the aggregate document +// carries one resource entry per ready offer, rooted at baseURL + +// EffectivePath() — the same shared-origin alias buildOpenAPIDocument and +// buildServiceCatalogJSON already publish every offer under. +func TestBuildAggregateWellKnownX402_BasicOffer(t *testing.T) { + offer := readyOfferWithSpec("echo", "demo", monetizeapi.ServiceOfferSpec{ + Type: "http", + Path: "/services/echo", + Payment: monetizeapi.ServiceOfferPayment{ + Network: "base", + PayTo: "0x3333333333333333333333333333333333333333", + Price: monetizeapi.ServiceOfferPriceTable{PerRequest: "0.00001"}, + }, + }) + + doc := parseOpenAPI(t, buildAggregateWellKnownX402([]*monetizeapi.ServiceOffer{offer}, "https://tunnel.example")) + resources, _ := doc["resources"].([]any) + if len(resources) != 1 { + t.Fatalf("resources = %d entries, want 1: %v", len(resources), resources) + } + res := resources[0].(map[string]any) + if res["resource"] != "https://tunnel.example/services/echo" { + t.Errorf("resource = %v, want https://tunnel.example/services/echo", res["resource"]) + } + if res["method"] != "POST" { + t.Errorf("method = %v, want POST", res["method"]) + } + accepts, _ := res["accepts"].([]any) + if len(accepts) != 1 { + t.Fatalf("accepts = %d entries, want 1: %v", len(accepts), accepts) + } +} + +// TestBuildAggregateWellKnownX402_ExcludesNotReadyAndDrained mirrors +// buildOpenAPIDocument's readiness filter (TestBuildOpenAPIDocument_ExcludesNotReadyAndDrained). +func TestBuildAggregateWellKnownX402_ExcludesNotReadyAndDrained(t *testing.T) { + ready := readyOfferWithSpec("alpha", "demo", monetizeapi.ServiceOfferSpec{ + Type: "inference", + Payment: monetizeapi.ServiceOfferPayment{Network: "base", PayTo: "0xaa", Price: monetizeapi.ServiceOfferPriceTable{PerRequest: "0.001"}}, + }) + notReady := &monetizeapi.ServiceOffer{ + ObjectMeta: metav1.ObjectMeta{Name: "beta", Namespace: "demo"}, + Status: monetizeapi.ServiceOfferStatus{Conditions: []monetizeapi.Condition{{Type: "Ready", Status: "False"}}}, + } + + doc := parseOpenAPI(t, buildAggregateWellKnownX402([]*monetizeapi.ServiceOffer{ready, notReady}, "")) + resources, _ := doc["resources"].([]any) + if len(resources) != 1 { + t.Fatalf("resources = %d entries, want only the ready offer: %v", len(resources), resources) + } + res := resources[0].(map[string]any) + if res["resource"] != "/services/alpha/v1/chat/completions" { + t.Errorf("resource = %v, want /services/alpha/v1/chat/completions", res["resource"]) + } +} + +// TestBuildAggregateWellKnownX402_HostnameBoundOfferStillIncluded confirms a +// hostname-bound offer still appears in the aggregate document under its +// shared-origin alias (baseURL + EffectivePath()) — it additionally gets its +// own root-rooted copy at its dedicated origin via buildOfferWellKnownX402, +// which this document does not replace. +func TestBuildAggregateWellKnownX402_HostnameBoundOfferStillIncluded(t *testing.T) { + offer := readyOfferWithSpec("audit", "demo", monetizeapi.ServiceOfferSpec{ + Type: "http", + Path: "/services/audit", + Hostname: "audit.acme.example", + Payment: monetizeapi.ServiceOfferPayment{Network: "base", PayTo: "0xaa", Price: monetizeapi.ServiceOfferPriceTable{PerRequest: "0.001"}}, + }) + + doc := parseOpenAPI(t, buildAggregateWellKnownX402([]*monetizeapi.ServiceOffer{offer}, "https://tunnel.example")) + resources, _ := doc["resources"].([]any) + if len(resources) != 1 { + t.Fatalf("resources = %d entries, want the hostname-bound offer's shared-origin alias: %v", len(resources), resources) + } + res := resources[0].(map[string]any) + if res["resource"] != "https://tunnel.example/services/audit" { + t.Errorf("resource = %v, want shared-origin alias, not the dedicated hostname", res["resource"]) + } +} + +func TestBuildWellKnownX402HTTPRoute(t *testing.T) { + route := buildWellKnownX402HTTPRoute() + if route.GetName() != wellKnownX402RouteName { + t.Fatalf("name = %q, want %q", route.GetName(), wellKnownX402RouteName) + } + spec, _ := route.Object["spec"].(map[string]any) + if _, hasHostnames := spec["hostnames"]; hasHostnames { + t.Error("well-known x402 route must not have hostnames filter (tunnel-reachable by design)") + } + rules, _ := spec["rules"].([]any) + rule := rules[0].(map[string]any) + matches, _ := rule["matches"].([]any) + if got := matches[0].(map[string]any)["path"].(map[string]any)["value"]; got != "/.well-known/x402" { + t.Errorf("match path = %v, want /.well-known/x402", got) + } + + // The public path has no extension, but the mounted file must be + // .json-suffixed for busybox's httpd.conf to serve application/json — + // the route must rewrite to it, mirroring buildHostHTTPRoute's exactTo. + filters, _ := rule["filters"].([]any) + var rewrote bool + for _, f := range filters { + fm, _ := f.(map[string]any) + if fm["type"] != "URLRewrite" { + continue + } + path, _ := fm["urlRewrite"].(map[string]any)["path"].(map[string]any) + if path["type"] == "ReplaceFullPath" && path["replaceFullPath"] == "/wellknown-x402.json" { + rewrote = true + } + } + if !rewrote { + t.Errorf("expected a URLRewrite to /wellknown-x402.json, got filters: %v", filters) + } +} diff --git a/web/public-storefront/src/components/ServiceCard.tsx b/web/public-storefront/src/components/ServiceCard.tsx index 63f8a772..0dbfabd2 100644 --- a/web/public-storefront/src/components/ServiceCard.tsx +++ b/web/public-storefront/src/components/ServiceCard.tsx @@ -1,6 +1,7 @@ "use client"; import { useState } from "react"; +import type { ReactNode } from "react"; import type { Service, ServicePayment } from "@/types"; import { RichText } from "@/components/RichText"; @@ -61,7 +62,7 @@ function normalizeOfferType(t: string): "inference" | "agent" | "http" { return "http"; } -type Tab = "agent" | "other-ai" | "code"; +type Tab = "agent" | "other-ai" | "agentcash" | "bankr" | "code"; const AGENT_TASK_PLACEHOLDER = "Summarise the README and list the top 3 risks."; function resolvedAgentTask(task: string): string { @@ -77,7 +78,7 @@ function resolvedAgentTask(task: string): string { // instructions. function buyPrompt( service: Service, - key: "obol-agent" | "generic-llm" | "cli", + key: "obol-agent" | "generic-llm" | "cli" | "agentcash" | "bankr", agentTask?: string, ): string | null { const raw = service.buy?.prompts?.[key]; @@ -341,13 +342,85 @@ export function ServiceCard({ service }: { service: Service }) { /> )} {tab === "other-ai" && ( - + Paste this into Claude, ChatGPT, Gemini, or any AI agent + with internet access. The buy-x402 skill from{" "} + + ObolNetwork/skills + {" "} + bootstraps the stack and asks for your permission before + spending. + + } + /> + )} + {tab === "agentcash" && ( + + Paste this into your{" "} + + AgentCash + + -connected agent. This origin already publishes AgentCash's + discovery convention — OpenAPI x-payment-info{" "} + plus a /.well-known/x402{" "} + fallback — so it can price this call automatically. + + } + /> + )} + {tab === "bankr" && ( + + Paste this into your{" "} + + Bankr + + -connected agent or wallet. There's no Bankr-specific + format here — this is a standard x402 endpoint (402 + challenge, EIP-3009/Permit2 signature, X-PAYMENT retry). + + } /> )} {tab === "code" && ( @@ -370,10 +443,12 @@ function TabBar({ tab, onChange }: { tab: Tab; onChange: (t: Tab) => void }) { const tabs: { id: Tab; label: string }[] = [ { id: "agent", label: "Ask your Obol agent" }, { id: "other-ai", label: "Ask another AI agent" }, + { id: "agentcash", label: "Pay with AgentCash" }, + { id: "bankr", label: "Pay with Bankr" }, { id: "code", label: "Buy with code" }, ]; return ( -
+
{tabs.map((t) => { const active = t.id === tab; return ( @@ -474,13 +549,20 @@ function BuyViaObolAgent({ ); } -function BuyViaOtherAgent({ +// BuyViaExternalTool renders the "paste this into a buyer tool with no Obol +// tooling" tabs: the generic "another AI agent" tab plus the AgentCash and +// Bankr tabs. All three read the same shape of prompt (promptKey selects +// which one) — the underlying x402 mechanics are identical, only the intro +// copy and published prompt differ per tool. +function BuyViaExternalTool({ service, opt, kind, agentTask, taskReady, requireTask, + promptKey, + intro, }: { service: Service; opt: ServicePayment; @@ -488,44 +570,34 @@ function BuyViaOtherAgent({ agentTask: string; taskReady: boolean; requireTask: boolean; + promptKey: "generic-llm" | "agentcash" | "bankr"; + intro: ReactNode; }) { - // Canonical prompts from the catalog buy block; inline strings are only - // fallbacks for pre-buy-block catalogs. + // fallbacks for pre-buy-block catalogs (these predate the agentcash/bankr + // keys too, so all three tools share the same generic fallback copy). let prompt: string; if (kind === "inference") { const model = service.model || "the advertised model"; prompt = - buyPrompt(service, "generic-llm") ?? + buyPrompt(service, promptKey) ?? `${docsRef(service.endpoint)} I want to use the remote LLM at ${service.endpoint} (model ${model}) as a paid OpenAI-compatible chat-completions endpoint. Pre-sign a budget of EIP-3009 or Permit2 authorisations and POST chat-completions bodies with the X-PAYMENT header attached.`; } else if (kind === "agent") { // An agent runs its own pinned model server-side and ignores the request's // model field, so we don't tell the buyer which model it uses — the request // shape is what matters. prompt = - buyPrompt(service, "generic-llm", agentTask) ?? + buyPrompt(service, promptKey, agentTask) ?? `${docsRef(service.endpoint)} Help me call the Obol Agent at ${service.endpoint} — it's an autonomous agent (tools + skills + memory), not a raw LLM. POST OpenAI-style chat-completions JSON to ${service.endpoint}/v1/chat/completions with this user message in \`messages\`: {"role":"user","content":${quoteAgentTask(agentTask)}}. Attach a signed EIP-3009 or Permit2 authorisation as \`X-PAYMENT\`, and report what the agent does.`; } else { prompt = - buyPrompt(service, "generic-llm") ?? + buyPrompt(service, promptKey) ?? `I want to purchase a service offered by an Obol Agent at ${service.endpoint} for ${opt.price} on ${opt.network}. Please install the run-obol-stack skill from https://github.com/ObolNetwork/skills, ask me for permission to set up the obol stack, and use the buy-x402 skill to make the purchase on my behalf.`; } return (
-

- Paste this into Claude, ChatGPT, Gemini, or any AI agent with - internet access. The buy-x402 skill from{" "} - - ObolNetwork/skills - {" "} - bootstraps the stack and asks for your permission before spending. -

+

{intro}

Date: Wed, 5 Aug 2026 20:25:14 +0400 Subject: [PATCH 26/31] deps: bundle pending renovate updates for v0.14.0-rc2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applied directly rather than merging the PR branches: all four are cut from main while this branch carries newer intermediate versions from rc1's own renovate merges, so every one of them conflicts on every file it touches. Hand-resolving those conflicts is how a dependency silently gets downgraded, so the target versions are applied in one commit instead — the same shape renovate would emit if it rebased onto rc2. obolup.sh (#797): - kubectl 1.36.2 -> 1.36.3, helmfile 1.7.1 -> 1.7.2, helm-diff 3.15.10 -> 3.15.11, ollama 0.32.1 -> 0.32.5 ethereum clients (#796): - geth v1.17.4 -> v1.17.5, nethermind 1.39.1 -> 1.39.2, besu 26.7.0 -> 26.7.1, erigon v3.5.2 -> v3.5.4, lighthouse v8.2.0 -> v8.2.1, prysm v7.1.7 -> v7.1.8, lodestar v1.44.0 -> v1.45.0 (reth and teku were already at target; nimbus tracks a different renovate datasource and had no PR, so it is left alone) scalar (#795): - @scalar/api-reference 1.62.9 -> 1.64.0 with its matching SRI hash, which must move in lockstep or the browser blocks the script cloudflared (#794): - 2026.7.2 -> 2026.7.3 with its matching image digest --- .../embed/infrastructure/cloudflared/values.yaml | 2 +- .../embed/networks/ethereum/helmfile.yaml.gotmpl | 14 +++++++------- internal/serviceoffercontroller/scalar_html.go | 4 ++-- obolup.sh | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/internal/embed/infrastructure/cloudflared/values.yaml b/internal/embed/infrastructure/cloudflared/values.yaml index fbe89d48..d872a5f8 100644 --- a/internal/embed/infrastructure/cloudflared/values.yaml +++ b/internal/embed/infrastructure/cloudflared/values.yaml @@ -5,7 +5,7 @@ transport: image: repository: cloudflare/cloudflared - tag: "2026.7.2@sha256:4f6655284ab3d252b7f28fedb19fe6c8fc82ee5b1295c20ac74d475e5398a52d" + tag: "2026.7.3@sha256:e39ee8da81ad5e05d77f38d2f51c60ca51bf2a8450ac3abab50c17fdb91d91bf" metrics: address: "0.0.0.0:2000" diff --git a/internal/embed/networks/ethereum/helmfile.yaml.gotmpl b/internal/embed/networks/ethereum/helmfile.yaml.gotmpl index 41c93b7f..6c054074 100644 --- a/internal/embed/networks/ethereum/helmfile.yaml.gotmpl +++ b/internal/embed/networks/ethereum/helmfile.yaml.gotmpl @@ -94,16 +94,16 @@ releases: tag: v2.4.1 {{- else if eq .Values.executionClient "geth" }} # renovate: datasource=github-releases depName=ethereum/go-ethereum - tag: v1.17.4 + tag: v1.17.5 {{- else if eq .Values.executionClient "nethermind" }} # renovate: datasource=github-releases depName=NethermindEth/nethermind - tag: "1.39.1" + tag: "1.39.2" {{- else if eq .Values.executionClient "besu" }} # renovate: datasource=github-releases depName=hyperledger/besu - tag: "26.7.0" + tag: "26.7.1" {{- else if eq .Values.executionClient "erigon" }} # renovate: datasource=github-releases depName=erigontech/erigon - tag: v3.5.2 + tag: v3.5.4 {{- end }} {{- if eq .Values.executionClient "reth" }} # reth keeps p2p (30303) closed while running post-crash consistency @@ -131,10 +131,10 @@ releases: image: {{- if eq .Values.consensusClient "lighthouse" }} # renovate: datasource=github-releases depName=sigp/lighthouse - tag: v8.2.0 + tag: v8.2.1 {{- else if eq .Values.consensusClient "prysm" }} # renovate: datasource=github-releases depName=OffchainLabs/prysm - tag: v7.1.7 + tag: v7.1.8 {{- else if eq .Values.consensusClient "teku" }} # renovate: datasource=github-releases depName=Consensys/teku tag: "26.7.1" @@ -143,7 +143,7 @@ releases: tag: multiarch-v26.5.0 {{- else if eq .Values.consensusClient "lodestar" }} # renovate: datasource=github-releases depName=ChainSafe/lodestar - tag: v1.44.0 + tag: v1.45.0 {{- end }} persistence: enabled: true diff --git a/internal/serviceoffercontroller/scalar_html.go b/internal/serviceoffercontroller/scalar_html.go index 0270fd67..c77f21d6 100644 --- a/internal/serviceoffercontroller/scalar_html.go +++ b/internal/serviceoffercontroller/scalar_html.go @@ -14,7 +14,7 @@ import ( // payload never drifts silently. After a bump, refresh the SRI hash below // with scripts/update-scalar-sri.sh (the renovate PR body links it too). // renovate: datasource=npm depName=@scalar/api-reference -const scalarBundleVersion = "1.62.9" +const scalarBundleVersion = "1.64.0" // scalarBundleSRI is the Subresource Integrity hash for the pinned bundle. // The /api page is served over the public tunnel, so the third-party Scalar @@ -24,7 +24,7 @@ const scalarBundleVersion = "1.62.9" // bundle and rewrites this constant). The hash is taken over the exact // (jsdelivr-minified) bytes that the pinned URL serves; it must be refreshed // in lockstep with scalarBundleVersion or the browser will block the script. -const scalarBundleSRI = "sha384-oBiNNPts22DP4xagXD0sZE4A/PyTMc+sYcxwx+v692dheBqr5zHQTp58ufiOqJH2" +const scalarBundleSRI = "sha384-MjGH/UsAZcRWbWSD70Yp9VOYGaCELxb+2a6meIvJsl5NkpqfdvkepJM6+ExL2Vmv" // scalarHTML returns the static HTML shell served at /api. It loads the // pinned @scalar/api-reference bundle from jsdelivr, points it at the diff --git a/obolup.sh b/obolup.sh index a2efc550..ae05227b 100755 --- a/obolup.sh +++ b/obolup.sh @@ -55,19 +55,19 @@ fi # Pinned dependency versions # Update these versions to upgrade dependencies across all installations # renovate: datasource=github-releases depName=kubernetes/kubernetes -readonly KUBECTL_VERSION="1.36.2" +readonly KUBECTL_VERSION="1.36.3" # renovate: datasource=github-releases depName=helm/helm readonly HELM_VERSION="3.21.3" # renovate: datasource=github-releases depName=k3d-io/k3d readonly K3D_VERSION="5.9.0" # renovate: datasource=github-releases depName=helmfile/helmfile -readonly HELMFILE_VERSION="1.7.1" +readonly HELMFILE_VERSION="1.7.2" # renovate: datasource=github-releases depName=derailed/k9s readonly K9S_VERSION="0.51.0" # renovate: datasource=github-releases depName=databus23/helm-diff -readonly HELM_DIFF_VERSION="3.15.10" +readonly HELM_DIFF_VERSION="3.15.11" # renovate: datasource=github-releases depName=ollama/ollama -readonly OLLAMA_VERSION="0.32.1" +readonly OLLAMA_VERSION="0.32.5" # Must match internal/openclaw/OPENCLAW_VERSION (without "v" prefix). # Tested by TestOpenClawVersionConsistency. readonly OPENCLAW_VERSION="2026.4.21" From a5bd91ec8dfcfb1411816ab0c4c38d44c79e8e44 Mon Sep 17 00:00:00 2001 From: bussyjd Date: Wed, 5 Aug 2026 20:25:57 +0400 Subject: [PATCH 27/31] chore(frontend): pin obol-frontend to v0.1.28-rc5 Multi-arch index digest (linux/amd64 + linux/arm64) for the front-end v0.1.28-rc5 release, which carries the /storefront branding editor that this RC's live-preview renderer serves. rc5 rather than rc4 because a second dependency bundle landed on the front-end main after rc4 was tagged; Docker Hub enforces tag immutability on that repo, so republishing rc4 was not an option. --- .../embed/infrastructure/values/obol-frontend.yaml.gotmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/embed/infrastructure/values/obol-frontend.yaml.gotmpl b/internal/embed/infrastructure/values/obol-frontend.yaml.gotmpl index b9951be7..6b0d7e9f 100644 --- a/internal/embed/infrastructure/values/obol-frontend.yaml.gotmpl +++ b/internal/embed/infrastructure/values/obol-frontend.yaml.gotmpl @@ -52,8 +52,8 @@ image: pullPolicy: IfNotPresent # Digest-pinned: tag is informational, sha256 is authoritative. Eliminates # the mutable-tag attack surface called out by the v0.10.0-rc2 supply-chain - # review. Multi-arch index digest for v0.1.28-rc3 (linux/amd64 + linux/arm64). - tag: "v0.1.28-rc3@sha256:eaf7afc6242b480a873df8bd55d0076641ce5407278813eb9856c19274a15f17" + # review. Multi-arch index digest for v0.1.28-rc5 (linux/amd64 + linux/arm64). + tag: "v0.1.28-rc5@sha256:e44d9614c7c3ddc657273386abd1e980e47efa6ffa839c57ab5b7840de2198b0" service: type: ClusterIP From cd406af389830f252595d8727f2e40725f038c29 Mon Sep 17 00:00:00 2001 From: HananINouman Date: Wed, 5 Aug 2026 20:59:20 +0300 Subject: [PATCH 28/31] fix(x402): harden Bankr external-buyer path and type-specific prompts Skip settle on client disconnect/write errors, classify facilitator rejections, and teach HTTP buyers Bankr chat auto-pay while agent/inference use wallet-sign plus long curl after live timeout and voucher failures. Co-authored-by: Cursor --- CLAUDE.md | 2 +- docs/observability.md | 52 ++ internal/buyprompts/buyprompts.go | 105 ++- internal/buyprompts/buyprompts_test.go | 68 ++ .../serviceoffercontroller/offerbundle.go | 2 +- internal/serviceoffercontroller/openapi.go | 30 +- .../serviceoffercontroller/openapi_test.go | 16 +- internal/serviceoffercontroller/render.go | 2 +- .../routesurface_golden_test.go | 2 +- .../wellknown_aggregate_test.go | 4 +- internal/x402/forwardauth.go | 672 ++++++++++++++++-- internal/x402/forwardauth_test.go | 444 ++++++++++++ internal/x402/metrics.go | 4 +- internal/x402/paymentrequired.go | 2 +- internal/x402/verifier.go | 44 +- internal/x402/verifier_test.go | 141 +++- .../src/components/ServiceCard.tsx | 229 +++--- 17 files changed, 1618 insertions(+), 201 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b4ab4d5c..e7ddc746 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -458,7 +458,7 @@ A registry digest pin instead of `:latest` on the verifier means your dev rewrit 20. **402 page silently falls back to JSON when the template errors** — `sendPaymentRequiredHTML` swallows template-exec errors and re-sends the JSON body, so referencing a field in `payment_required.html` that isn't in the render's data struct doesn't crash anything: browsers just start getting JSON. Symptom: `Content-Type: application/json` on an `Accept: text/html` request. Any template-field addition needs the struct field added in `paymentrequired.go` AND a test asserting the HTML branch still renders (the existing branding tests check Content-Type/markup). 21. **`html/template` rejects `data:` URIs in URL contexts (`#ZgotmplZ`)** — inline logos/favicons from `sell info set --logo-file` are `data:image/...;base64` URIs; interpolating them into `src=`/`href=` via a plain string yields the literal `#ZgotmplZ` (broken image). Branding asset URLs must go through `storefront.SafeAssetURL` (validates http(s)/`data:image` then returns `template.URL`); regression test `TestPaymentRequiredHTML_InlineDataURILogo`. 22. **Poisoned LiteLLM model group — intermittent 404 `{'detail': 'Not Found'}` on ~50% of requests** — two `model_list` deployments share a `model_name` but disagree on `api_base` (one with `/v1`, one without); LiteLLM shuffles between them and does not retry a 404. Historic cause: auto-discovery registering the same host endpoint that `obol model setup custom` later added correctly (#745). Diagnosis: compare the router's live view (`GET /model/info`) against the `litellm-config` CM — the drift checker compares model NAMES only and cannot see divergent `api_base` (#746). The vLLM access log is decisive: alternating `POST /chat/completions 404` / `POST /v1/chat/completions 200`. -23. **Buyer disconnect ≠ no charge (zombie settlement)** — a client abort routinely does NOT propagate past cloudflared; the upstream agent finishes the turn, the handler returns 2xx, and settlement fires → buyer debited for a response nobody received (proven on-chain 2026-07-14, the Bankr incident). The verifier skips settlement when the request context is already canceled (#743), but that only covers propagated cancels — the real protection is keeping paid agent runs SHORT (`Agent.spec.maxTurns`; runs must finish inside the ~100s tunnel window and typical client timeouts). Concurrency: Hermes caps simultaneous runs in-process (`gateway.api_server.max_concurrent_runs`, internal default 10, 429 "Too many concurrent runs"; exposed as `Agent.spec.maxConcurrentRuns`, 0 = disabled) — keep it coherent with the edge `spec.limits.maxInFlight` Traefik gate; 4xx responses are never settled, so both gates are financially safe. +23. **Buyer disconnect ≠ no charge (zombie settlement)** — a client abort routinely does NOT propagate past cloudflared; the upstream agent can finish 2xx and older seller builds still called `/settle` → buyer debited while the UI showed failure (Bankr ~30s retries ×3 on-chain, 2026-08-05). This deferred-to-`finalize()` protection applies ONLY to `text/event-stream` (SSE) responses — `settlementInterceptor.WriteHeader` still settles eagerly, before committing the status, for every non-streaming response (plain `sell http`, non-streaming `sell agent`), exactly as before this fix. For SSE, `finalize()` skips `/settle` on canceled context, **client Write errors** (broken pipe), or zero body bytes — see `docs/observability.md` ("External buyers (Bankr)"). Separately, Bankr auto-pay often fails verify with `facilitator_error` because it signs EIP-3009 `validAfter=now` (use past buffer + ≥180s timeout). Still keep paid agent runs SHORT (`Agent.spec.maxTurns`) inside the tunnel window. Concurrency: Hermes `max_concurrent_runs` / `Agent.spec.maxConcurrentRuns` must stay coherent with edge `spec.limits.maxInFlight`; 4xx is never settled. For a fuller debug catalog with symptom->fix mapping, see `.agents/skills/obol-stack-dev/references/release-smoke-debugging.md`. diff --git a/docs/observability.md b/docs/observability.md index e606a941..ea4b7d51 100644 --- a/docs/observability.md +++ b/docs/observability.md @@ -252,6 +252,58 @@ A `Transfer` to the expected recipient that exists while the buyer reports --- +## External buyers (Bankr): two failure modes, one canonical ledger + +Bankr chat / `bankr x402 call` against Obol agent offers confused operators +because the UI error and the on-chain outcome often disagreed. + +**Root cause (2026-08-05 investigation): Bankr chat/CLI auto-pay is not +built to pay arbitrary third-party x402 endpoints at all.** Per Bankr's own +docs, auto-pay is scoped to endpoints deployed through `bankr x402 deploy` +and then approved into Bankr's own discovery index (criteria undocumented) — +[docs.bankr.bot/x402-cloud/quick-start](https://docs.bankr.bot/x402-cloud/quick-start/). +Their Apps SDK (`bankr.x402.fetch`) separately requires a pre-declared +`allowedHosts` allowlist — +[docs.bankr.bot/apps/overview](https://docs.bankr.bot/apps/overview/). Every +documented example targets `"network":"base"` (mainnet); no Base Sepolia +support was found anywhere in their docs or the `BankrBot/skills` repo. An +arbitrary seller like ours is out of scope for chat auto-pay independent of +the two wire-level failure modes below — `bankr wallet sign` +([docs.bankr.bot/cli](https://docs.bankr.bot/cli/)) is Bankr's own +officially documented manual-signing primitive, not a workaround hack, and +is the only proven path today. + +When Bankr auto-pay *does* attempt a call anyway, there are **two separate +wire-level failure modes**: + +| Mode | What the buyer sees | What happened | Charge? | +|---|---|---|---| +| **A — Voucher** | JSON `503` with `reason:facilitator_error`, `detail:unexpected_error` | Bankr auto-pay signed EIP-3009 with `validAfter=wall-clock now`. Base USDC requires `block.timestamp > validAfter`, so facilitator `/verify` rejects. | Usually **no** (verify never succeeded). | +| **B — Timeout / zombie** | Timeout, 504, or a generic “payment failed” after ~30s | Verify **succeeded**, agent was still running (often 30–120s to first SSE byte). Bankr’s short client timeout aborted the UI. Cloudflare often does **not** cancel the seller’s request context, so older seller builds still called `/settle` after upstream finished → BaseScan shows 0.001 USDC Transfers in a triple-retry burst. | **Yes** on older builds. | + +**Seller hardenings** (HandleProxy settlement interceptor): + +- Settle SSE responses only in `finalize()` after the stream completes — + never on the first `WriteHeader(200)`. +- Skip `/settle` when `r.Context()` is canceled, when a body `Write` to the + client fails (broken pipe — the reliable signal when cancel does not + propagate), or when zero body bytes were written. +- Upstream proxy errors after verify return structured JSON with + `paymentVerified:true`, `paymentSettled:false`, `retriable:false` so buyers + do not auto-retry storms. + +**Buyer guidance** (storefront Bankr prompts are type-specific): +- **http** — prefer Bankr chat auto-pay (fast enough for the ~30s window). + Do not ask chat to run `bankr wallet sign` (it cannot). +- **agent / inference** — do not use Bankr chat/Apps auto-pay (`rpc timeout`); + use `bankr wallet sign` with a past `validAfter` buffer and HTTP timeout ≥180s. + After any timeout, check BaseScan before retrying. + +**Still true:** chain Transfers are canonical. Seller `paymentSettled:false` +and Bankr UI copy are best-effort signals. + +--- + ## Recording rule conventions Naming follows the standard Prometheus pattern: diff --git a/internal/buyprompts/buyprompts.go b/internal/buyprompts/buyprompts.go index 91cd92d4..3246769b 100644 --- a/internal/buyprompts/buyprompts.go +++ b/internal/buyprompts/buyprompts.go @@ -38,9 +38,17 @@ const ( // `x-payment-info` plus a `/.well-known/x402` fallback — with x402scan // and Poncho, so this same prompt covers all three. PromptAgentCash = "agentcash" - // PromptBankr is pasted into a Bankr-connected agent/wallet. Bankr - // publishes no seller-side discovery format of its own, so the copy - // describes the plain x402 handshake rather than anything bespoke. + // PromptBankr is pasted into Bankr chat or a Bankr-CLI agent. Guidance is + // type-specific (observed 2026-08-05 against third-party Obol sellers): + // - http: prefer Bankr chat auto-pay — fast endpoints usually finish + // inside Bankr's ~30s client window; do NOT ask chat to run + // `bankr wallet sign` (chat cannot). + // - agent/inference: forbid chat/Apps auto-pay (`rpc timeout` / voucher + // quirks); teach `bankr wallet sign` + long curl in a Bankr-CLI agent + // (Cursor). Bankr's own docs also scope chat/CLI auto-pay to endpoints + // deployed via `bankr x402 deploy` + their discovery index, and Apps + // need an `allowedHosts` allowlist — still teach the proven CLI path + // for slow offers. PromptBankr = "bankr" ) @@ -196,23 +204,53 @@ func agentBlock(in Input) Block { in.URL, task(in), ), PromptAgentCash: fmt.Sprintf( - "Help me call the Obol Agent at %s — it's an autonomous agent (tools + skills + "+ - "memory), not a raw LLM. It's gated by %s, and already publishes AgentCash's "+ - "discovery convention (OpenAPI `x-payment-info`, `/.well-known/x402` fallback) "+ - "so your agent can auto-discover the price. POST OpenAI-style chat-completions "+ - "JSON to %s with this user message in `messages`: "+ - "{\"role\":\"user\",\"content\":%q}. Attach a signed EIP-3009/Permit2 authorization "+ - "as `X-PAYMENT`, use `\"stream\": true`, and report what the agent does.", - in.URL, GuideRef(in.SiteURL), ChatCompletionsURL(in.URL), task(in), + "Call the Obol Agent at %s EXACTLY as specified — do not drop fields. "+ + "It's an autonomous agent (tools + skills + memory), gated by %s "+ + "(OpenAPI `x-payment-info` + `/.well-known/x402`). "+ + "REQUIRED: POST %s with Content-Type application/json and this exact body "+ + "(keep stream true; client HTTP timeout must be ≥180s — agents often take "+ + "30–120s before the first SSE byte; shorter timeouts cause false 'payment failed' retries):\n"+ + "```json\n"+ + "{\"model\":%q,\"stream\":true,\"messages\":[{\"role\":\"user\",\"content\":%q}]}\n"+ + "```\n"+ + "Attach a signed EIP-3009/Permit2 authorization as `X-PAYMENT`. "+ + "If the response has `paymentSettled:false`, you were NOT charged — do not spam retries.", + in.URL, GuideRef(in.SiteURL), ChatCompletionsURL(in.URL), modelOr(in, "hermes-agent"), task(in), ), PromptBankr: fmt.Sprintf( - "Pay for the Obol Agent at %s with your Bankr wallet — it's an autonomous agent "+ - "(tools + skills + memory), not a raw LLM. It's gated by %s; there's no "+ - "Bankr-specific format here, just standard x402. POST OpenAI-style "+ - "chat-completions JSON to %s with this user message in `messages`: "+ - "{\"role\":\"user\",\"content\":%q}. Attach a signed EIP-3009/Permit2 authorization "+ - "as `X-PAYMENT`, use `\"stream\": true`, and report what the agent does.", - in.URL, GuideRef(in.SiteURL), ChatCompletionsURL(in.URL), task(in), + "You have my Bankr wallet via the Bankr CLI (`bankr`). Buy one turn from the Obol Agent at %s "+ + "using MANUAL `bankr wallet sign` + a long curl (timeout ≥180s, prefer 300s). "+ + "Paste this into Cursor (or any Bankr-CLI agent) — not Bankr chat, not a Bankr App.\n\n"+ + "Do NOT use Bankr chat auto-pay, `bankr x402 call`, `/wallet/x402-pay`, or a Bankr App "+ + "`bankr.x402.fetch`. Against this agent offer those paths fail for three reasons:\n"+ + "(A) Scope: per Bankr's own docs, chat/CLI auto-pay is scoped to endpoints deployed via "+ + "`bankr x402 deploy` and approved into Bankr's own discovery index — an arbitrary "+ + "third-party URL like this one is out of scope by design.\n"+ + "(B) Voucher: when they do attempt a call, they often sign EIP-3009 with `validAfter=now` → "+ + "seller returns JSON `reason:facilitator_error` / `detail:unexpected_error` (verify "+ + "rejected; usually no charge).\n"+ + "(C) Timeout: Bankr chat/Apps abort at ~30s with `rpc timeout` while this agent is still "+ + "running (often 30–120s to first SSE byte) → UI failure; never auto-retry; treat BaseScan "+ + "as canonical before any retry.\n\n"+ + "Steps (exact):\n"+ + "1. Probe once without payment: POST %s with the JSON body below → read 402 `accepts[]`. "+ + "Copy network/amount/asset/payTo VERBATIM (CAIP-2 `eip155:8453`, not the alias `base`).\n"+ + "2. Sign EIP-3009 TransferWithAuthorization with `bankr wallet sign` (typed data). "+ + "REQUIRED: set `validAfter` to `0` or `now-600` (past buffer). NEVER `validAfter=now`. "+ + "Set `validBefore` to `now+900`. Match `from`=my Bankr EVM address, `to`/`value`/`asset` from accepts[].\n"+ + "3. Build the x402 payment payload (x402Version 2, accepted = that accepts[] entry, "+ + "payload.authorization + payload.signature), base64-encode it, and curl POST the paid request with:\n"+ + " - Header `X-PAYMENT: ` (also ok: `PAYMENT-SIGNATURE`)\n"+ + " - Header `Content-Type: application/json`\n"+ + " - `curl --max-time 300` (or equivalent) — agents often take 30–120s before first SSE byte\n"+ + "4. Exact body (keep stream true; do not omit fields):\n"+ + "```json\n"+ + "{\"model\":%q,\"stream\":true,\"messages\":[{\"role\":\"user\",\"content\":%q}]}\n"+ + "```\n"+ + "5. Stream the SSE to me. On `facilitator_error`, re-sign once with a past `validAfter` (do not spam). "+ + "On timeout/504/`rpc timeout`, do NOT retry until you check BaseScan for a Transfer to payTo. "+ + "Seller docs: %s.", + in.URL, ChatCompletionsURL(in.URL), modelOr(in, "hermes-agent"), task(in), GuideRef(in.SiteURL), ), }, Example: ChatExample(in.URL, in.Model, in.TaskExample), @@ -254,11 +292,15 @@ func inferenceBlock(in Input) Block { in.URL, model, ChatCompletionsURL(in.URL), GuideRef(in.SiteURL), ), PromptBankr: fmt.Sprintf( - "Pay for the remote LLM at %s (model %s) with your Bankr wallet, as a paid "+ - "OpenAI-compatible chat-completions endpoint at %s, paid with %s. There's no "+ - "Bankr-specific format here — pre-sign a budget of EIP-3009/Permit2 "+ - "authorizations and POST chat-completions bodies with the X-PAYMENT header "+ - "attached.", + "You have my Bankr wallet via the Bankr CLI (`bankr`). Buy the remote LLM at %s "+ + "(model %s) at %s using MANUAL `bankr wallet sign` + curl — do NOT use Bankr chat "+ + "auto-pay or `bankr x402 call`. Bankr's own docs scope chat/CLI auto-pay to endpoints "+ + "deployed via `bankr x402 deploy` and approved into their own discovery index — an "+ + "arbitrary third-party URL like this one is out of scope by design. Even where it does "+ + "attempt a call: (A) `validAfter=now` → `facilitator_error`/`unexpected_error`; "+ + "(B) short client timeout while upstream still runs → UI failure (confirm BaseScan "+ + "before retry). Sign EIP-3009 with `validAfter=0` or `now-600`, match accepts[] verbatim "+ + "(CAIP-2), attach `X-PAYMENT`, timeout ≥180s, `stream:true`. Paid with %s.", in.URL, model, ChatCompletionsURL(in.URL), GuideRef(in.SiteURL), ), }, @@ -310,12 +352,17 @@ func httpBlock(in Input) Block { in.URL, GuideRef(in.SiteURL), priceClause, netClause, ), PromptBankr: fmt.Sprintf( - "Call the paid HTTP endpoint at %s once with your Bankr wallet.%s%s There's no "+ - "Bankr-specific format here — it's a standard x402 endpoint: fetch it with "+ - "no payment to read the 402 `accepts[]` pricing, sign a matching "+ - "EIP-3009/Permit2 authorization, and retry with the payload in the "+ - "`X-PAYMENT` header.", - in.URL, priceClause, netClause, + "Paste this into Bankr chat (or Max Mode). Buy the HTTP endpoint at %s "+ + "using Bankr chat auto-pay — call the URL and let Bankr attach payment.%s%s\n\n"+ + "Prefer chat auto-pay for this fast HTTP offer. Do NOT ask Bankr chat to run "+ + "`bankr wallet sign` or build a manual X-PAYMENT header — chat cannot do that, "+ + "and it will fall back to auto-pay anyway.\n\n"+ + "If chat auto-pay returns `facilitator_error` / `unexpected_error`, that is usually "+ + "a `validAfter=now` voucher quirk (auth not consumed) — retry once; do not spam. "+ + "If it keeps failing, switch to a Bankr-CLI agent (Cursor) and use "+ + "`bankr wallet sign` with `validAfter=0` or `now-600`, then curl with `X-PAYMENT`.\n"+ + "Seller docs: %s.", + in.URL, priceClause, netClause, GuideRef(in.SiteURL), ), }, } diff --git a/internal/buyprompts/buyprompts_test.go b/internal/buyprompts/buyprompts_test.go index 0783e3b7..7e75434f 100644 --- a/internal/buyprompts/buyprompts_test.go +++ b/internal/buyprompts/buyprompts_test.go @@ -62,6 +62,74 @@ func TestBuild_UnknownTypeGetsHTTPSemantics(t *testing.T) { } } +// TestBuild_BankrPromptTeachesManualWalletSign pins type-specific Bankr copy: +// agent/inference forbid chat/Apps auto-pay and teach `bankr wallet sign` + +// long curl; http prefers Bankr chat auto-pay (and forbids asking chat to +// manual-sign). +func TestBuild_BankrPromptTeachesManualWalletSign(t *testing.T) { + for _, typ := range []string{"agent", "inference"} { + block := Build(Input{ + Type: typ, + URL: "https://seller.example.com/services/demo", + Model: "claude-sonnet-4-6", + }) + p := block.Prompts[PromptBankr] + for _, want := range []string{ + "bankr wallet sign", + "validAfter", + "facilitator_error", + "NOT use", + "x402 call", + "BaseScan", + } { + if !strings.Contains(p, want) { + t.Errorf("%s bankr prompt missing %q:\n%s", typ, want, p) + } + } + } + agent := Build(Input{ + Type: "agent", + URL: "https://seller.example.com/services/demo", + Model: "claude-sonnet-4-6", + }).Prompts[PromptBankr] + for _, want := range []string{ + "≥180s", + "stream\":true", + "now-600", + "validAfter=now", + "bankr.x402.fetch", + "rpc timeout", + "curl --max-time 300", + "Cursor", + } { + if !strings.Contains(agent, want) { + t.Errorf("agent bankr prompt missing %q:\n%s", want, agent) + } + } + + httpPrompt := Build(Input{ + Type: "http", + URL: "https://seller.example.com/services/demo", + PriceDisplay: "0.001 USDC per request", + }).Prompts[PromptBankr] + for _, want := range []string{ + "Bankr chat", + "auto-pay", + "Do NOT ask Bankr chat to run", + "bankr wallet sign", + "facilitator_error", + } { + if !strings.Contains(httpPrompt, want) { + t.Errorf("http bankr prompt missing %q:\n%s", want, httpPrompt) + } + } + for _, forbid := range []string{"MANUAL `bankr wallet sign` + curl", "Do NOT use Bankr chat auto-pay"} { + if strings.Contains(httpPrompt, forbid) { + t.Errorf("http bankr prompt must prefer chat auto-pay; still contains %q:\n%s", forbid, httpPrompt) + } + } +} + // TestGuideRef_PointsAtSellerDocs pins that generic-LLM buyers are pointed // at the seller's own machine-readable docs, not a generic external page. func TestGuideRef_PointsAtSellerDocs(t *testing.T) { diff --git a/internal/serviceoffercontroller/offerbundle.go b/internal/serviceoffercontroller/offerbundle.go index b64718c3..e99f3574 100644 --- a/internal/serviceoffercontroller/offerbundle.go +++ b/internal/serviceoffercontroller/offerbundle.go @@ -241,7 +241,7 @@ func wellKnownResourcesForOffer(offer *monetizeapi.ServiceOffer, origin, pathPre if rt.EffectiveGate() != monetizeapi.GatePaid { continue } - method := "POST" + method := defaultPaidMethod(offer) if len(rt.Methods) > 0 { method = strings.ToUpper(rt.Methods[0]) } diff --git a/internal/serviceoffercontroller/openapi.go b/internal/serviceoffercontroller/openapi.go index f3ad02ef..692323ea 100644 --- a/internal/serviceoffercontroller/openapi.go +++ b/internal/serviceoffercontroller/openapi.go @@ -255,14 +255,26 @@ func primaryPaidRoute(offer *monetizeapi.ServiceOffer) (monetizeapi.ServiceOffer return monetizeapi.ServiceOfferRoute{}, false } +// defaultPaidMethod is the method advertised when a paid route does not +// declare Methods. Inference/agent/fine-tuning are write-shaped (POST); +// plain http defaults to GET — that matches demo/hello and most "pay to +// fetch" endpoints, and stops OpenAPI/AgentCash clients POSTing into a +// GET-only upstream (405). +func defaultPaidMethod(offer *monetizeapi.ServiceOffer) string { + if offer != nil && (offer.IsInference() || offer.IsAgent() || strings.EqualFold(offer.Spec.Type, "fine-tuning")) { + return "POST" + } + return "GET" +} + // primaryPaidMethod is the HTTP method advertised for the offer's primary // paid operation: the first declared method on the primary paid route, or -// POST (the phase-1 default emission everywhere else). +// defaultPaidMethod when Methods is empty. func primaryPaidMethod(offer *monetizeapi.ServiceOffer) string { if rt, ok := primaryPaidRoute(offer); ok && len(rt.Methods) > 0 { return strings.ToUpper(rt.Methods[0]) } - return "POST" + return defaultPaidMethod(offer) } // openAPIDocsAnchorForOffer returns the site-relative Scalar deep link for @@ -286,7 +298,7 @@ func openAPIDocsAnchorForOffer(offer *monetizeapi.ServiceOffer) string { // User feedback: agent uses the same OpenAI chat completions wire // format as inference, so they share an emission path. // - fine-tuning → POST with multipart upload, generic 200. -// - http (default) → POST with application/json body, generic 200. +// - http (default) → GET , generic 200 (no request body). // // Every operation references the shared `PaymentRequired` 402 response and // carries an `x-payment-info` extension marking it as x402-paid for @@ -346,13 +358,9 @@ func openAPIPathsForOffer(offer *monetizeapi.ServiceOffer) map[string]map[string default: return annotateAsyncPaths(offer, map[string]map[string]any{ "": { - "post": openAPIOperation(offer, openAPIOperationOptions{ - summary: "Invoke " + offer.Name, - description: offerDescription(offer, "x402 payment-gated HTTP service."), - requestBody: openAPIJSONRequestBody( - "", - "Operator-defined JSON payload. Shape is not specified in phase 1.", - ), + "get": openAPIOperation(offer, openAPIOperationOptions{ + summary: "Invoke " + offer.Name, + description: offerDescription(offer, "x402 payment-gated HTTP service."), successResponse: openAPIGenericSuccessResponse("Upstream response (shape is operator-defined)."), }), }, @@ -469,7 +477,7 @@ func openAPIPathsForRouteTable(offer *monetizeapi.ServiceOffer) map[string]map[s methods := rt.Methods if len(methods) == 0 { if gate == monetizeapi.GatePaid { - methods = []string{"POST"} + methods = []string{defaultPaidMethod(offer)} } else { methods = []string{"GET"} } diff --git a/internal/serviceoffercontroller/openapi_test.go b/internal/serviceoffercontroller/openapi_test.go index c41c018f..bb7511ca 100644 --- a/internal/serviceoffercontroller/openapi_test.go +++ b/internal/serviceoffercontroller/openapi_test.go @@ -355,18 +355,16 @@ func TestBuildOpenAPIDocument_HTTPOffer(t *testing.T) { doc := parseOpenAPI(t, buildOpenAPIDocument([]*monetizeapi.ServiceOffer{offer}, "", schemas.StorefrontProfile{})) - op := dig(t, doc, "paths", "/services/echo", "post") + op := dig(t, doc, "paths", "/services/echo", "get") if op == nil { - t.Fatalf("http offer missing POST /services/echo, paths = %v", doc["paths"]) + t.Fatalf("http offer missing GET /services/echo, paths = %v", doc["paths"]) } - // HTTP offers get a generic JSON body, no $ref into OpenAI components. - schema := dig(t, op.(map[string]any), "requestBody", "content", "application/json", "schema") - schemaMap, _ := schema.(map[string]any) - if _, hasRef := schemaMap["$ref"]; hasRef { - t.Errorf("http offer request body should be generic, got $ref: %v", schema) + // Default http emission is GET with no request body (demo/hello-shaped). + if body := dig(t, op.(map[string]any), "requestBody"); body != nil { + t.Errorf("http GET offer must not advertise a requestBody, got %v", body) } - if schemaMap["type"] != "object" { - t.Errorf("http offer schema type = %v, want object", schemaMap["type"]) + if post := dig(t, doc, "paths", "/services/echo", "post"); post != nil { + t.Errorf("default http offer must not advertise POST when Methods is empty") } } diff --git a/internal/serviceoffercontroller/render.go b/internal/serviceoffercontroller/render.go index 330ac487..13cff68f 100644 --- a/internal/serviceoffercontroller/render.go +++ b/internal/serviceoffercontroller/render.go @@ -2159,7 +2159,7 @@ func skillMarkdownRouteLines(offer *monetizeapi.ServiceOffer, endpoint string) [ methods := strings.Join(rt.Methods, "|") if methods == "" { if gate == monetizeapi.GatePaid { - methods = "POST" + methods = defaultPaidMethod(offer) } else { methods = "GET" } diff --git a/internal/serviceoffercontroller/routesurface_golden_test.go b/internal/serviceoffercontroller/routesurface_golden_test.go index 601cabe1..392c0a9e 100644 --- a/internal/serviceoffercontroller/routesurface_golden_test.go +++ b/internal/serviceoffercontroller/routesurface_golden_test.go @@ -143,7 +143,7 @@ func TestRouteSurface_Golden_SkillMDListsEveryRoute(t *testing.T) { "`POST https://example.com/services/audit/submit` — 0.5 USDC/request — Submit source for audit", "`GET https://example.com/services/audit/jobs` — free (covers sub-paths)", "`GET https://example.com/services/audit/reports` — free, wallet sign-in required (SIWX/EIP-4361 — see the offer's `/auth` page) (covers sub-paths)", - "`POST https://example.com/services/audit` — 0.1 USDC/request (covers sub-paths)", + "`GET https://example.com/services/audit` — 0.1 USDC/request (covers sub-paths)", // Buy prompts + try-it must target the primary paid route, not the root. "curl -i https://example.com/services/audit/submit", } { diff --git a/internal/serviceoffercontroller/wellknown_aggregate_test.go b/internal/serviceoffercontroller/wellknown_aggregate_test.go index e06c4209..05862ce8 100644 --- a/internal/serviceoffercontroller/wellknown_aggregate_test.go +++ b/internal/serviceoffercontroller/wellknown_aggregate_test.go @@ -31,8 +31,8 @@ func TestBuildAggregateWellKnownX402_BasicOffer(t *testing.T) { if res["resource"] != "https://tunnel.example/services/echo" { t.Errorf("resource = %v, want https://tunnel.example/services/echo", res["resource"]) } - if res["method"] != "POST" { - t.Errorf("method = %v, want POST", res["method"]) + if res["method"] != "GET" { + t.Errorf("method = %v, want GET", res["method"]) } accepts, _ := res["accepts"].([]any) if len(accepts) != 1 { diff --git a/internal/x402/forwardauth.go b/internal/x402/forwardauth.go index d1d25b82..91fae55f 100644 --- a/internal/x402/forwardauth.go +++ b/internal/x402/forwardauth.go @@ -5,12 +5,16 @@ import ( "bytes" "context" "encoding/base64" + "encoding/hex" "encoding/json" + "errors" "fmt" "io" "log" "net" "net/http" + "net/url" + "sort" "strings" "time" @@ -93,10 +97,11 @@ type facilitatorVerifyRequest struct { // facilitatorVerifyResponse is the JSON response from POST /verify. type facilitatorVerifyResponse struct { - IsValid bool `json:"isValid"` - InvalidReason string `json:"invalidReason,omitempty"` - InvalidMessage string `json:"invalidMessage,omitempty"` - Payer string `json:"payer,omitempty"` + IsValid bool `json:"isValid"` + InvalidReason string `json:"invalidReason,omitempty"` + InvalidMessage string `json:"invalidMessage,omitempty"` + InvalidReasonDetails string `json:"invalidReasonDetails,omitempty"` + Payer string `json:"payer,omitempty"` } // facilitatorSettleResponse is the JSON response from POST /settle. @@ -114,6 +119,110 @@ var ( facilitatorSettleTimeout = 60 * time.Second ) +// facilitatorOpError classifies failures talking to the facilitator so buyers +// see facilitator_unreachable only for real transport problems. A reachable +// facilitator that returns HTTP 500 unexpected_error (common when a client +// builds a bad voucher) used to be mislabeled "unreachable", which burned +// buyer LLM credits on useless identical retries. +type facilitatorOpError struct { + Op string // "verify" or "settle" + Kind string // "unreachable" | "rejected" | "bad_response" + StatusCode int + Reason string // facilitator's invalidReason / errorReason when known + err error +} + +func (e *facilitatorOpError) Error() string { + if e == nil { + return "facilitator error" + } + if e.err != nil { + return e.err.Error() + } + if e.Reason != "" { + return fmt.Sprintf("facilitator %s failed (%d): %s", e.Op, e.StatusCode, e.Reason) + } + return fmt.Sprintf("facilitator %s failed (%d)", e.Op, e.StatusCode) +} + +func (e *facilitatorOpError) Unwrap() error { return e.err } + +func isTransportFacilitatorErr(err error) bool { + if err == nil { + return false + } + var ne net.Error + if errors.As(err, &ne) { + return true + } + msg := err.Error() + return strings.Contains(msg, "connection refused") || + strings.Contains(msg, "no such host") || + strings.Contains(msg, "i/o timeout") || + strings.Contains(msg, "TLS handshake") || + strings.Contains(msg, "context deadline exceeded") || + strings.Contains(msg, "EOF") +} + +// paymentErrorFromVerifyErr maps a facilitator verify failure onto the +// structured buyer-facing body. Transport → facilitator_unreachable +// (retriable); facilitator HTTP/body rejection → facilitator_error with the +// facilitator's reason in detail (usually not worth identical retries). +func paymentErrorFromVerifyErr(err error) paymentErrorBody { + body := paymentErrorBody{ + Error: "Payment verification failed", + Reason: "facilitator_unreachable", + Hint: "transient facilitator error — retry the identical request in a few seconds; the payment authorization was not consumed", + Retriable: true, + } + var fe *facilitatorOpError + if !errors.As(err, &fe) || fe == nil { + if isTransportFacilitatorErr(err) { + return body + } + // Non-typed unexpected errors (marshal, empty payload) — still not a + // "down" facilitator, but keep the legacy reason for blind callers. + body.Detail = strings.TrimSpace(err.Error()) + return body + } + switch fe.Kind { + case "unreachable": + return body + case "rejected", "bad_response": + detail := strings.TrimSpace(fe.Reason) + if detail == "" && fe.StatusCode != 0 { + detail = fmt.Sprintf("HTTP %d", fe.StatusCode) + } + hint := "seller facilitator reachable but rejected verification — payment authorization was not consumed" + retriable := fe.StatusCode == http.StatusBadGateway || + fe.StatusCode == http.StatusServiceUnavailable || + fe.StatusCode == http.StatusGatewayTimeout + if strings.Contains(fe.Reason, "unexpected_error") || fe.StatusCode == http.StatusInternalServerError { + // Observed with Bankr /wallet/x402-pay against CAIP-2 Obol offers: + // facilitator is up; voucher construction makes it 500. Identical + // retries will not help and burn buyer LLM credits. + retriable = false + hint = "seller facilitator rejected this payment voucher (auth not consumed) — do not spam identical retries; rebuild the voucher from the 402 accepts[] entry verbatim (network/amount/asset/payTo) and re-sign" + lower := strings.ToLower(detail) + if strings.Contains(lower, "not yet valid") || strings.Contains(lower, "validafter") { + hint = "authorization validAfter is too close to wall-clock now (auth not consumed) — sign with a past buffer (e.g. validAfter=0 or now-600s), as AgentCash does; Bankr auto-pay often sets validAfter=now" + } + if strings.Contains(lower, "invalid signature 'v'") || strings.Contains(lower, `invalid signature "v"`) { + hint = "signature recovery id (v) rejected on-chain (auth not consumed) — v must be 27 or 28, not 0/1" + } + } + return paymentErrorBody{ + Error: "Payment verification failed", + Reason: "facilitator_error", + Detail: detail, + Hint: hint, + Retriable: retriable, + } + default: + return body + } +} + // paymentErrorBody is the structured JSON body written on terminal // payment-flow failures (malformed header, facilitator unreachable, // settlement error). Buying agents retry blind when a failure is an opaque @@ -167,6 +276,300 @@ func paymentFailureFrom(r *http.Request) (paymentFailure, bool) { return f, ok } +type facilitatorURLCtxKey struct{} + +func withFacilitatorURL(r *http.Request, facilitatorURL string) *http.Request { + return r.WithContext(context.WithValue(r.Context(), facilitatorURLCtxKey{}, facilitatorURL)) +} + +func facilitatorURLFrom(r *http.Request) string { + url, _ := r.Context().Value(facilitatorURLCtxKey{}).(string) + return url +} + +// legacyCompatRequirements augments the canonical v2 accepts[] entries with a +// second copy using the chain's legacy alias ("base", "base-sepolia", ...) +// when the canonical network is CAIP-2. Bankr's docs and some hosted examples +// still teach the legacy names, so advertising both gives buyers a compatible +// option without dropping the standard CAIP-2 form other clients already use. +func legacyCompatRequirements(requirements []x402types.PaymentRequirements) []x402types.PaymentRequirements { + if len(requirements) == 0 { + return nil + } + out := make([]x402types.PaymentRequirements, 0, len(requirements)*2) + for _, req := range requirements { + out = append(out, req) + chain, err := ResolveChainInfo(req.Network) + if err != nil || chain.Name == "" || chain.Name == req.Network { + continue + } + alias := req + alias.Network = chain.Name + out = append(out, alias) + } + return out +} + +func paymentRequiredBody(r *http.Request, requirements []x402types.PaymentRequirements, extensions map[string]any) map[string]any { + resp := buildPaymentRequired(r, legacyCompatRequirements(requirements), extensions) + body := map[string]any{ + "x402Version": resp.X402Version, + "error": resp.Error, + "resource": resp.Resource, + "accepts": resp.Accepts, + } + if len(resp.Extensions) > 0 { + body["extensions"] = resp.Extensions + } + if facilitator := facilitatorURLFrom(r); facilitator != "" && facilitatorURLSafeToDisclose(facilitator) { + // Legacy compatibility for Bankr Cloud-style readers; harmless for + // standards-compliant v2 clients, which ignore unknown top-level fields. + body["facilitator"] = facilitator + } + return body +} + +// facilitatorURLSafeToDisclose reports whether a facilitator URL is safe to +// echo into a PUBLIC, unauthenticated 402 response for legacy Bankr-Cloud- +// style readers. The production default (x402.gcp.obol.tech) is already +// documented in CLAUDE.md, so disclosing it costs nothing — but an operator +// running a private/internal facilitator (VPN-only address, RFC1918 IP, +// .internal/.local hostname) should not have that topology handed to every +// anonymous internet caller who hits an unpaid /services/* route. +func facilitatorURLSafeToDisclose(rawURL string) bool { + u, err := url.Parse(rawURL) + if err != nil || u.Hostname() == "" { + return false + } + host := u.Hostname() + if host == "localhost" || strings.HasSuffix(host, ".local") || strings.HasSuffix(host, ".internal") { + return false + } + if ip := net.ParseIP(host); ip != nil { + if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsUnspecified() { + return false + } + } + return true +} + +func parsePaymentPayloadCompat(data []byte) (*x402types.PaymentPayload, error) { + payload, err := x402types.ToPaymentPayload(data) + if err == nil { + return payload, nil + } + + var raw struct { + X402Version int `json:"x402Version"` + Payload map[string]interface{} `json:"payload"` + Accepted x402types.PaymentRequirements `json:"accepted"` + Resource json.RawMessage `json:"resource,omitempty"` + Extensions map[string]interface{} `json:"extensions,omitempty"` + } + if jsonErr := json.Unmarshal(data, &raw); jsonErr != nil || raw.X402Version != 2 || len(raw.Resource) == 0 { + return nil, err + } + if raw.Resource[0] != '"' { + return nil, err + } + var resourceURL string + if jsonErr := json.Unmarshal(raw.Resource, &resourceURL); jsonErr != nil || resourceURL == "" { + return nil, err + } + log.Printf("x402: accepted compat v2 payment payload with string resource url=%q", resourceURL) + return &x402types.PaymentPayload{ + X402Version: raw.X402Version, + Payload: raw.Payload, + Accepted: raw.Accepted, + Resource: &x402types.ResourceInfo{URL: resourceURL}, + Extensions: raw.Extensions, + }, nil +} + +func mapKeysSorted(m map[string]interface{}) []string { + if len(m) == 0 { + return nil + } + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +func valueShape(v any) string { + switch t := v.(type) { + case string: + return fmt.Sprintf("string(len=%d)", len(t)) + case float64: + return "number" + case bool: + return "bool" + case nil: + return "null" + case []any: + return fmt.Sprintf("array(len=%d)", len(t)) + case map[string]any: + return fmt.Sprintf("object(keys=%v)", mapKeysSorted(t)) + default: + return fmt.Sprintf("%T", v) + } +} + +func paymentPayloadSummary(payload x402types.PaymentPayload) string { + // Compact, signature-free diagnostic for failure paths only (no-match / + // verify reject). Happy-path paid requests stay quiet so steady-state + // logs are not flooded with buyer addresses and nonces. + summary := map[string]any{ + "x402Version": payload.X402Version, + "accepted": map[string]any{ + "scheme": payload.Accepted.Scheme, + "network": payload.Accepted.Network, + "amount": payload.Accepted.Amount, + "asset": payload.Accepted.Asset, + "payTo": payload.Accepted.PayTo, + }, + "payloadKeys": mapKeysSorted(payload.Payload), + "extensionsKeys": mapKeysSorted(payload.Extensions), + } + if payload.Resource != nil { + summary["resource"] = map[string]any{ + "url": payload.Resource.URL, + "serviceName": payload.Resource.ServiceName, + "mimeType": payload.Resource.MimeType, + } + } + if auth, ok := payload.Payload["authorization"].(map[string]any); ok { + shapes := make(map[string]any, len(auth)) + for k, v := range auth { + shapes[k] = valueShape(v) + } + summary["authorizationShape"] = shapes + // Safe non-secret fields — needed to diagnose Bankr validAfter=now + // vs AgentCash validAfter=0 without dumping the signature. + safeAuth := map[string]any{} + for _, k := range []string{"from", "to", "value", "validAfter", "validBefore", "nonce"} { + if v, ok := auth[k]; ok { + safeAuth[k] = v + } + } + summary["authorization"] = safeAuth + } + if len(payload.Accepted.Extra) > 0 { + summary["acceptedExtraKeys"] = mapKeysSorted(payload.Accepted.Extra) + if name, ok := payload.Accepted.Extra["name"]; ok { + summary["acceptedExtraName"] = name + } + if version, ok := payload.Accepted.Extra["version"]; ok { + summary["acceptedExtraVersion"] = version + } + if method, ok := payload.Accepted.Extra["assetTransferMethod"]; ok { + summary["acceptedExtraMethod"] = method + } + } + if sig, ok := payload.Payload["signature"].(string); ok { + summary["signature"] = map[string]any{ + "has0xPrefix": strings.HasPrefix(sig, "0x"), + "len": len(sig), + "vByte": signatureVByte(sig), + } + } + b, err := json.Marshal(summary) + if err != nil { + return fmt.Sprintf("marshal_error=%v", err) + } + return string(b) +} + +func samePaymentNetwork(a, b string) bool { + if a == b { + return true + } + chainA, errA := ResolveChainInfo(a) + chainB, errB := ResolveChainInfo(b) + if errA != nil || errB != nil { + return false + } + return chainA.CAIP2Network == chainB.CAIP2Network +} + +func signatureVByte(sig string) any { + s := strings.TrimPrefix(strings.TrimSpace(sig), "0x") + if len(s) < 2 || len(s)%2 != 0 { + return nil + } + b, err := hex.DecodeString(s[len(s)-2:]) + if err != nil || len(b) != 1 { + return nil + } + return int(b[0]) +} + +// normalizePaymentPayloadForVerify rewrites known buyer-compat quirks that do +// not change the signed authorization message: ECDSA recovery-id v=0/1 → +// v=27/28. Bankr auto-pay has been observed to produce the former; on-chain +// FiatToken ECRecover expects 27/28 and otherwise returns unexpected_error +// ("invalid signature 'v' value"). Returns the original bytes when no change. +func normalizePaymentPayloadForVerify(paymentPayloadJSON []byte) ([]byte, string) { + var raw map[string]json.RawMessage + if err := json.Unmarshal(paymentPayloadJSON, &raw); err != nil { + return paymentPayloadJSON, "" + } + payloadRaw, ok := raw["payload"] + if !ok { + return paymentPayloadJSON, "" + } + var payload map[string]any + if err := json.Unmarshal(payloadRaw, &payload); err != nil { + return paymentPayloadJSON, "" + } + sig, _ := payload["signature"].(string) + v := signatureVByte(sig) + vi, ok := v.(int) + if !ok || (vi != 0 && vi != 1) { + return paymentPayloadJSON, "" + } + s := strings.TrimPrefix(strings.TrimSpace(sig), "0x") + prefix := "" + if strings.HasPrefix(strings.TrimSpace(sig), "0x") { + prefix = "0x" + } + payload["signature"] = prefix + s[:len(s)-2] + fmt.Sprintf("%02x", vi+27) + newPayload, err := json.Marshal(payload) + if err != nil { + return paymentPayloadJSON, "" + } + raw["payload"] = newPayload + out, err := json.Marshal(raw) + if err != nil { + return paymentPayloadJSON, "" + } + return out, fmt.Sprintf("signature_v_%d_to_%d", vi, vi+27) +} + +func facilitatorRejectDetail(resp *facilitatorVerifyResponse) string { + if resp == nil { + return "" + } + parts := []string{resp.InvalidReason, resp.InvalidMessage, resp.InvalidReasonDetails} + var out []string + for _, p := range parts { + p = strings.TrimSpace(p) + if p != "" { + out = append(out, p) + } + } + return strings.Join(out, " — ") +} + +func truncateForLog(s string, max int) string { + if max <= 0 || len(s) <= max { + return s + } + return s[:max] + "…" +} + // signatureFailureHint returns a targeted hint when the facilitator rejection // looks like a signature problem. The #1 silent killer for external buyers is // signing the wrong EIP-712 domain for the asset; the seller is the only @@ -220,6 +623,7 @@ func NewForwardAuthMiddleware(cfg ForwardAuthConfig, requirements []x402types.Pa return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + r = withFacilitatorURL(r, cfg.FacilitatorURL) // x402 v1 clients send the payment under X-PAYMENT; x402 v2 clients // (agentcash, poncho, coinbase SDK >= v2) send it under PAYMENT-SIGNATURE. // Our 402 challenge advertises x402Version 2, so spec-compliant v2 buyers @@ -250,7 +654,7 @@ func NewForwardAuthMiddleware(cfg ForwardAuthConfig, requirements []x402types.Pa // Decode via the canonical x402 types helper (handles both wire // versions) rather than a local json.Unmarshal, so the payload // envelope stays in lockstep with the SDK. - payloadPtr, err := x402types.ToPaymentPayload(payloadBytes) + payloadPtr, err := parsePaymentPayloadCompat(payloadBytes) if err != nil { log.Printf("x402: invalid payment payload: %v", err) reportFailure("invalid_payment_header") @@ -265,6 +669,7 @@ func NewForwardAuthMiddleware(cfg ForwardAuthConfig, requirements []x402types.Pa matchedReq, found := findMatchingRequirementV1(payload, requirements) if !found { + log.Printf("x402: no matching requirement; payment payload summary %s", paymentPayloadSummary(payload)) reportFailure("no_matching_requirement") send(w, withPaymentFailure(r, paymentFailure{ Reason: "no_matching_requirement", @@ -277,23 +682,26 @@ func NewForwardAuthMiddleware(cfg ForwardAuthConfig, requirements []x402types.Pa cfg.OnPaymentMatched(matchedReq) } + verifyPayload := payloadBytes + if normalized, note := normalizePaymentPayloadForVerify(payloadBytes); note != "" { + verifyPayload = normalized + log.Printf("x402: normalized payment payload before verify (%s)", note) + } + // Verify with facilitator. - verifyResp, err := facilitatorVerify(r.Context(), verifyClient, cfg.FacilitatorURL, payloadBytes, matchedReq) + verifyResp, err := facilitatorVerify(r.Context(), verifyClient, cfg.FacilitatorURL, verifyPayload, matchedReq) if err != nil { - log.Printf("x402: facilitator verify error: %v", err) - reportFailure("facilitator_unreachable") - writePaymentError(w, http.StatusServiceUnavailable, paymentErrorBody{ - Error: "Payment verification failed", - Reason: "facilitator_unreachable", - Hint: "transient facilitator error — retry the identical request in a few seconds; the payment authorization was not consumed", - Retriable: true, - }) + log.Printf("x402: facilitator verify error: %v (payment x402Version=%d scheme=%q network=%q amount=%q); payload summary %s", + err, payload.X402Version, payload.Accepted.Scheme, payload.Accepted.Network, payload.Accepted.Amount, paymentPayloadSummary(payload)) + errBody := paymentErrorFromVerifyErr(err) + reportFailure(errBody.Reason) + writePaymentError(w, http.StatusServiceUnavailable, errBody) return } if !verifyResp.IsValid { - log.Printf("x402: payment invalid: %s", verifyResp.InvalidReason) - detail := strings.TrimSpace(strings.TrimSpace(verifyResp.InvalidReason) + " " + strings.TrimSpace(verifyResp.InvalidMessage)) + detail := facilitatorRejectDetail(verifyResp) + log.Printf("x402: payment invalid: %s; payload summary %s", detail, paymentPayloadSummary(payload)) hint := signatureFailureHint(detail, matchedReq) reportFailure("payment_invalid") send(w, withPaymentFailure(r, paymentFailure{ @@ -309,8 +717,14 @@ func NewForwardAuthMiddleware(cfg ForwardAuthConfig, requirements []x402types.Pa } // Payment verified — wrap with settlement interceptor. - interceptor := &settlementInterceptor{ - w: w, + // SSE settles in finalize() only after the stream completes with + // evidence the client received bytes (Write errors / empty body / + // canceled context → skip). Non-SSE still settles on WriteHeader + // so settle failures can return 503 before the body. Verify ≠ charge. + var interceptor *settlementInterceptor + interceptor = &settlementInterceptor{ + w: w, + deferSettle: !cfg.VerifyOnly, settleFunc: func() bool { if cfg.VerifyOnly { return true @@ -324,7 +738,13 @@ func NewForwardAuthMiddleware(cfg ForwardAuthConfig, requirements []x402types.Pa return false } - settleResp, err := facilitatorSettle(r.Context(), settleClient, cfg.FacilitatorURL, payloadBytes, matchedReq) + // Settle must see the same (possibly recovery-id-normalized) + // bytes that verify saw. Sending the original v=0/1 signature + // here after verify accepted the v=27/28-normalized copy meant + // the exact buyer population this normalization targets + // (Bankr auto-pay) always failed settlement on-chain — the + // seller served the request for free every time. + settleResp, err := facilitatorSettle(r.Context(), settleClient, cfg.FacilitatorURL, verifyPayload, matchedReq) if err != nil { log.Printf("x402: settlement failed: %v", err) // Even on facilitator error, the on-chain submission @@ -335,7 +755,8 @@ func NewForwardAuthMiddleware(cfg ForwardAuthConfig, requirements []x402types.Pa // carries a tx hash, surface it via X-PAYMENT-RESPONSE // before erroring so the buyer (or operator) can // reconcile against the chain. The header has to land - // before http.Error commits the status code. + // before http.Error commits the status code — only + // possible when the upstream body has not started. settledOnChain := false if settleResp != nil && settleResp.Transaction != "" { settledOnChain = true @@ -347,6 +768,12 @@ func NewForwardAuthMiddleware(cfg ForwardAuthConfig, requirements []x402types.Pa settleResp.Transaction, settleResp.Network, settleResp.Payer) } reportFailure("settlement_failed") + if interceptor != nil && interceptor.wroteStatus { + // Body/status already flushed (SSE deferred path) — + // cannot flip the status to 503. Receipt trailers + // (if any) are the only signal left. + return false + } hint := "transient facilitator error — retry the same request in a few seconds" if settledOnChain { hint = "the settle tx in X-PAYMENT-RESPONSE may have landed on-chain — verify against the chain before retrying, or you may pay twice" @@ -363,6 +790,9 @@ func NewForwardAuthMiddleware(cfg ForwardAuthConfig, requirements []x402types.Pa if !settleResp.Success { log.Printf("x402: settlement unsuccessful: %s", settleResp.ErrorReason) reportFailure("settlement_rejected") + if interceptor != nil && interceptor.wroteStatus { + return false + } detail := strings.TrimSpace(strings.TrimSpace(settleResp.ErrorReason) + " " + strings.TrimSpace(settleResp.ErrorMessage)) send(w, withPaymentFailure(r, paymentFailure{ Reason: "settlement_rejected", @@ -375,6 +805,8 @@ func NewForwardAuthMiddleware(cfg ForwardAuthConfig, requirements []x402types.Pa // Encode the settlement receipt. v1 clients read it from // X-PAYMENT-RESPONSE; x402 v2 clients read PAYMENT-RESPONSE. // Emit both so either wire version can confirm the settle. + // When deferSettle already committed the upstream status, + // these land as HTTP trailers (Trailer: declared below). settleJSON, _ := json.Marshal(settleResp) encodedSettle := base64.StdEncoding.EncodeToString(settleJSON) w.Header().Set("X-PAYMENT-RESPONSE", encodedSettle) @@ -387,6 +819,7 @@ func NewForwardAuthMiddleware(cfg ForwardAuthConfig, requirements []x402types.Pa } next.ServeHTTP(interceptor, r) + interceptor.finalize(r.Context()) }) } } @@ -397,9 +830,9 @@ func NewForwardAuthMiddleware(cfg ForwardAuthConfig, requirements []x402types.Pa // is unset and the fallback when the renderer has nothing else to do. func sendPaymentRequiredJSON(w http.ResponseWriter, r *http.Request, requirements []x402types.PaymentRequirements, extensions map[string]any) { setCatalogLinkHeader(w) - resp := buildPaymentRequired(r, requirements, extensions) + bodyMap := paymentRequiredBody(r, requirements, extensions) - body, err := json.Marshal(resp) + body, err := json.Marshal(bodyMap) if err != nil { http.Error(w, "Payment required", http.StatusPaymentRequired) return @@ -481,11 +914,18 @@ func setPaymentRequiredHeader(w http.ResponseWriter, paymentRequiredJSON []byte) } // findMatchingRequirementV1 finds the first requirement matching the payment's -// scheme and network. +// scheme and network. samePaymentNetwork lets a buyer echo back a legacy +// chain alias (e.g. "base-sepolia") instead of our canonical CAIP-2 form — +// the matched requirement forwarded to the facilitator always keeps OUR +// canonical network, never the buyer's alias. Facilitator calls are tagged +// x402Version:2 throughout (see facilitatorVerifyRequest); echoing a v1-style +// alias into that request risked an unresolvable/misresolved network on a +// strict facilitator, for no benefit — the facilitator only needs to know +// which of our own accepted requirements this payment satisfies. func findMatchingRequirementV1(payment x402types.PaymentPayload, requirements []x402types.PaymentRequirements) (x402types.PaymentRequirements, bool) { for _, req := range requirements { if req.Scheme == payment.Accepted.Scheme && - req.Network == payment.Accepted.Network && + samePaymentNetwork(req.Network, payment.Accepted.Network) && req.Amount == payment.Accepted.Amount && req.Asset == payment.Accepted.Asset && req.PayTo == payment.Accepted.PayTo { @@ -500,7 +940,12 @@ func findMatchingRequirementV1(payment x402types.PaymentPayload, requirements [] // base64 X-PAYMENT wrapper. func facilitatorVerify(ctx context.Context, client *http.Client, facilitatorURL string, paymentPayloadJSON []byte, requirement x402types.PaymentRequirements) (*facilitatorVerifyResponse, error) { if len(paymentPayloadJSON) == 0 || !json.Valid(paymentPayloadJSON) { - return nil, fmt.Errorf("payment payload is empty or not valid JSON") + return nil, &facilitatorOpError{ + Op: "verify", + Kind: "bad_response", + Reason: "empty_or_invalid_payment_payload", + err: fmt.Errorf("payment payload is empty or not valid JSON"), + } } body := facilitatorVerifyRequest{ @@ -522,22 +967,49 @@ func facilitatorVerify(ctx context.Context, client *http.Client, facilitatorURL resp, err := client.Do(req) if err != nil { - return nil, fmt.Errorf("facilitator verify: %w", err) + return nil, &facilitatorOpError{ + Op: "verify", + Kind: "unreachable", + err: fmt.Errorf("facilitator verify: %w", err), + } } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("read verify response: %w", err) + return nil, &facilitatorOpError{ + Op: "verify", + Kind: "unreachable", + StatusCode: resp.StatusCode, + err: fmt.Errorf("read verify response: %w", err), + } } var verifyResp facilitatorVerifyResponse if err := json.Unmarshal(respBody, &verifyResp); err != nil { - return nil, fmt.Errorf("facilitator verify (%d): %s", resp.StatusCode, string(respBody)) + snippet := string(respBody) + if len(snippet) > 200 { + snippet = snippet[:200] + } + return nil, &facilitatorOpError{ + Op: "verify", + Kind: "bad_response", + StatusCode: resp.StatusCode, + Reason: "non_json_response", + err: fmt.Errorf("facilitator verify (%d): %s", resp.StatusCode, snippet), + } } if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("facilitator verify failed (%d): %s", resp.StatusCode, verifyResp.InvalidReason) + detail := facilitatorRejectDetail(&verifyResp) + log.Printf("x402: facilitator /verify rejected status=%d body=%s", resp.StatusCode, truncateForLog(string(respBody), 500)) + return nil, &facilitatorOpError{ + Op: "verify", + Kind: "rejected", + StatusCode: resp.StatusCode, + Reason: detail, + err: fmt.Errorf("facilitator verify failed (%d): %s", resp.StatusCode, detail), + } } return &verifyResp, nil @@ -548,7 +1020,12 @@ func facilitatorVerify(ctx context.Context, client *http.Client, facilitatorURL // base64 X-PAYMENT wrapper. func facilitatorSettle(ctx context.Context, client *http.Client, facilitatorURL string, paymentPayloadJSON []byte, requirement x402types.PaymentRequirements) (*facilitatorSettleResponse, error) { if len(paymentPayloadJSON) == 0 || !json.Valid(paymentPayloadJSON) { - return nil, fmt.Errorf("payment payload is empty or not valid JSON") + return nil, &facilitatorOpError{ + Op: "settle", + Kind: "bad_response", + Reason: "empty_or_invalid_payment_payload", + err: fmt.Errorf("payment payload is empty or not valid JSON"), + } } body := facilitatorVerifyRequest{ @@ -570,18 +1047,37 @@ func facilitatorSettle(ctx context.Context, client *http.Client, facilitatorURL resp, err := client.Do(req) if err != nil { - return nil, fmt.Errorf("facilitator settle: %w", err) + return nil, &facilitatorOpError{ + Op: "settle", + Kind: "unreachable", + err: fmt.Errorf("facilitator settle: %w", err), + } } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("read settle response: %w", err) + return nil, &facilitatorOpError{ + Op: "settle", + Kind: "unreachable", + StatusCode: resp.StatusCode, + err: fmt.Errorf("read settle response: %w", err), + } } var settleResp facilitatorSettleResponse if err := json.Unmarshal(respBody, &settleResp); err != nil { - return nil, fmt.Errorf("facilitator settle (%d): %s", resp.StatusCode, string(respBody)) + snippet := string(respBody) + if len(snippet) > 200 { + snippet = snippet[:200] + } + return nil, &facilitatorOpError{ + Op: "settle", + Kind: "bad_response", + StatusCode: resp.StatusCode, + Reason: "non_json_response", + err: fmt.Errorf("facilitator settle (%d): %s", resp.StatusCode, snippet), + } } if resp.StatusCode != http.StatusOK { @@ -591,7 +1087,13 @@ func facilitatorSettle(ctx context.Context, client *http.Client, facilitatorURL // settleResp.Transaction (the tx hash) to the buyer. Without this the // chain debit goes unnoticed — see docs/observability.md // ("Verify settlement against the chain, never the sidecar snapshot"). - return &settleResp, fmt.Errorf("facilitator settle failed (%d): %s", resp.StatusCode, settleResp.ErrorReason) + return &settleResp, &facilitatorOpError{ + Op: "settle", + Kind: "rejected", + StatusCode: resp.StatusCode, + Reason: settleResp.ErrorReason, + err: fmt.Errorf("facilitator settle failed (%d): %s", resp.StatusCode, settleResp.ErrorReason), + } } return &settleResp, nil @@ -665,14 +1167,31 @@ func parsePathOnly(uri string) (string, error) { } // settlementInterceptor wraps a ResponseWriter to intercept the status code. -// Settlement runs only when the inner handler succeeds (status < 400). -// Faithfully ported from mark3labs/x402-go/http/middleware.go. +// +// Settlement policy (deferSettle=true on the seller HandleProxy path): +// - text/event-stream (SSE): settle in finalize() ONLY after the stream +// completes. Skip /settle when the client context is canceled, a body +// Write fails (broken pipe — Cloudflare/Bankr timeouts often do NOT +// cancel r.Context), or zero body bytes were written. Receipt rides +// HTTP trailers. +// - other responses: settle eagerly on WriteHeader(<400) so a settle +// failure can still flip the status to 503 before the body starts +// (in-process sell-inference path). +// +// deferSettle=false keeps the legacy eager path for all content types. type settlementInterceptor struct { - w http.ResponseWriter - settleFunc func() bool - onFailure func(statusCode int) - committed bool - hijacked bool + w http.ResponseWriter + settleFunc func() bool + onFailure func(statusCode int) + deferSettle bool + streamDefer bool // SSE path: wait for finalize() + status int + committed bool // WriteHeader entered (guards re-entry) + wroteStatus bool // status already sent to the client + settled bool + hijacked bool + bytesWritten int + clientWriteErr error } func (i *settlementInterceptor) Header() http.Header { @@ -688,7 +1207,12 @@ func (i *settlementInterceptor) Write(b []byte) (int, error) { return len(b), nil } - return i.w.Write(b) + n, err := i.w.Write(b) + i.bytesWritten += n + if err != nil && i.clientWriteErr == nil { + i.clientWriteErr = err + } + return n, err } func (i *settlementInterceptor) WriteHeader(statusCode int) { @@ -696,25 +1220,85 @@ func (i *settlementInterceptor) WriteHeader(statusCode int) { return } i.committed = true + i.status = statusCode // Handler error — pass through, no settlement. if statusCode >= 400 { if i.onFailure != nil { i.onFailure(statusCode) } + i.wroteStatus = true i.w.WriteHeader(statusCode) return } - // Handler success — settle before writing status. + // SSE: defer settlement until finalize so a Bankr/client timeout cannot + // debit the buyer after upstream finishes behind a dead connection. + if i.deferSettle { + ct := i.w.Header().Get("Content-Type") + if strings.HasPrefix(strings.ToLower(ct), "text/event-stream") { + i.streamDefer = true + i.w.Header().Set("Trailer", "X-PAYMENT-RESPONSE, PAYMENT-RESPONSE") + i.wroteStatus = true + i.w.WriteHeader(statusCode) + return + } + } + + // Non-SSE (and legacy eager path): settle BEFORE committing status to + // the client so settle failures can still return 503 with + // X-PAYMENT-RESPONSE. wroteStatus stays false during settleFunc. if !i.settleFunc() { i.hijacked = true return } - + i.settled = true + i.wroteStatus = true i.w.WriteHeader(statusCode) } +// finalize runs after the upstream handler returns. For SSE (streamDefer) +// this is the only settlement point: success + evidence the client received +// bytes → settle; cancel / write error / empty body → skip. +func (i *settlementInterceptor) finalize(ctx context.Context) { + if i.settled || i.hijacked || !i.streamDefer { + return + } + if i.status == 0 || i.status >= 400 { + return + } + if ctx != nil && ctx.Err() != nil { + log.Printf("x402: buyer disconnected before settlement, skipping settlement: %v", ctx.Err()) + if i.onFailure != nil { + i.onFailure(i.status) + } + return + } + // Cloudflare / Bankr timeouts often do NOT cancel r.Context. A failed + // Write to the client is the signal that nobody received the body — + // settling here is the "zombie settlement" bug (charged, UI shows failure). + if i.clientWriteErr != nil { + log.Printf("x402: client write failed before settlement, skipping settlement: %v (bytesWritten=%d)", i.clientWriteErr, i.bytesWritten) + if i.onFailure != nil { + i.onFailure(i.status) + } + return + } + if i.bytesWritten == 0 { + log.Printf("x402: no response body reached the client before settlement, skipping settlement") + if i.onFailure != nil { + i.onFailure(i.status) + } + return + } + if i.settleFunc == nil { + return + } + if i.settleFunc() { + i.settled = true + } +} + func (i *settlementInterceptor) Flush() { if flusher, ok := i.w.(http.Flusher); ok { flusher.Flush() diff --git a/internal/x402/forwardauth_test.go b/internal/x402/forwardauth_test.go index 23a786d5..00bbaa08 100644 --- a/internal/x402/forwardauth_test.go +++ b/internal/x402/forwardauth_test.go @@ -5,6 +5,7 @@ import ( "context" "encoding/base64" "encoding/json" + "errors" "io" "log" "net/http" @@ -127,6 +128,98 @@ func TestForwardAuth_NoPayment_Returns402_AdvertisesExtensions(t *testing.T) { } } +func TestForwardAuth_NoPayment_AdvertisesLegacyFacilitatorAndNetworkAlias(t *testing.T) { + // A public-looking facilitator URL: facilitatorURLSafeToDisclose + // deliberately filters loopback/private addresses out of the disclosed + // field, and httptest.NewServer always binds to 127.0.0.1 — see + // TestFacilitatorURLSafeToDisclose for that filtering behavior in + // isolation. This is a no-payment request, so no real facilitator call + // happens regardless of whether this URL is reachable. + const facilitatorURL = "https://facilitator.example.com" + + mw := NewForwardAuthMiddleware(ForwardAuthConfig{ + FacilitatorURL: facilitatorURL, + VerifyOnly: true, + }, testRequirements()) + + req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil) + rec := httptest.NewRecorder() + mw(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + t.Fatal("inner handler should not be called") + })).ServeHTTP(rec, req) + + var body map[string]any + if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil { + t.Fatalf("decode 402 body: %v", err) + } + if got := body["facilitator"]; got != facilitatorURL { + t.Fatalf("facilitator = %v, want %q", got, facilitatorURL) + } + accepts, ok := body["accepts"].([]any) + if !ok || len(accepts) < 2 { + t.Fatalf("accepts = %#v, want canonical + legacy alias entries", body["accepts"]) + } + first, _ := accepts[0].(map[string]any) + second, _ := accepts[1].(map[string]any) + if got := first["network"]; got != ChainBaseSepolia.CAIP2Network { + t.Fatalf("accepts[0].network = %v, want %q", got, ChainBaseSepolia.CAIP2Network) + } + if got := second["network"]; got != ChainBaseSepolia.NetworkID { + t.Fatalf("accepts[1].network = %v, want %q", got, ChainBaseSepolia.NetworkID) + } +} + +// TestForwardAuth_NoPayment_LoopbackFacilitatorNotDisclosed pins the other +// side of facilitatorURLSafeToDisclose through the full middleware: a +// loopback facilitator (the address every local/dev setup actually uses) +// must NOT appear in the public 402 body. +func TestForwardAuth_NoPayment_LoopbackFacilitatorNotDisclosed(t *testing.T) { + var verifyCalled, settleCalled atomic.Int32 + fac := mockFacilitatorV1(true, true, &verifyCalled, &settleCalled) + defer fac.Close() + + mw := NewForwardAuthMiddleware(ForwardAuthConfig{ + FacilitatorURL: fac.URL, // http://127.0.0.1: + VerifyOnly: true, + }, testRequirements()) + + req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil) + rec := httptest.NewRecorder() + mw(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + t.Fatal("inner handler should not be called") + })).ServeHTTP(rec, req) + + var body map[string]any + if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil { + t.Fatalf("decode 402 body: %v", err) + } + if _, ok := body["facilitator"]; ok { + t.Fatalf("facilitator field present for a loopback URL: %v", body["facilitator"]) + } +} + +func TestFacilitatorURLSafeToDisclose(t *testing.T) { + cases := []struct { + url string + want bool + }{ + {"https://x402.gcp.obol.tech", true}, + {"https://facilitator.example.com", true}, + {"http://127.0.0.1:4040", false}, + {"http://localhost:4040", false}, + {"http://10.0.0.5:4040", false}, + {"http://192.168.1.5:4040", false}, + {"http://facilitator.internal", false}, + {"http://facilitator.local", false}, + {"not a url", false}, + } + for _, c := range cases { + if got := facilitatorURLSafeToDisclose(c.url); got != c.want { + t.Errorf("facilitatorURLSafeToDisclose(%q) = %v, want %v", c.url, got, c.want) + } + } +} + func TestForwardAuth_ValidPayment_VerifyOnly(t *testing.T) { var verifyCalled, settleCalled atomic.Int32 fac := mockFacilitatorV1(true, true, &verifyCalled, &settleCalled) @@ -449,6 +542,60 @@ func TestForwardAuth_FacilitatorDown_StructuredRetriable503(t *testing.T) { } } +// TestForwardAuth_FacilitatorRejected_NotMislabeledUnreachable pins the Bankr +// incident class: facilitator is reachable but returns HTTP 500 +// unexpected_error. Buyers must see facilitator_error (with detail) and +// retriable=false so they stop burning LLM credits on identical retries. +func TestForwardAuth_FacilitatorRejected_NotMislabeledUnreachable(t *testing.T) { + fac := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/verify" { + http.NotFound(w, r) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + _ = json.NewEncoder(w).Encode(facilitatorVerifyResponse{ + IsValid: false, + InvalidReason: "unexpected_error", + }) + })) + defer fac.Close() + + mw := NewForwardAuthMiddleware(ForwardAuthConfig{ + FacilitatorURL: fac.URL, + VerifyOnly: true, + }, testRequirements()) + + inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Fatal("inner handler should not be called") + }) + + req := httptest.NewRequest("POST", "/v1/chat/completions", nil) + req.Header.Set("X-PAYMENT", validPaymentHeader()) + rec := httptest.NewRecorder() + mw(inner).ServeHTTP(rec, req) + + if rec.Code != http.StatusServiceUnavailable { + t.Fatalf("status = %d, want 503", rec.Code) + } + var body paymentErrorBody + if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil { + t.Fatalf("body is not JSON: %v (body %q)", err, rec.Body.String()) + } + if body.Reason != "facilitator_error" { + t.Errorf("reason = %q, want facilitator_error (not facilitator_unreachable)", body.Reason) + } + if body.Detail != "unexpected_error" { + t.Errorf("detail = %q, want unexpected_error", body.Detail) + } + if body.Retriable { + t.Error("unexpected_error must NOT be marked retriable — identical retries will not help") + } + if !strings.Contains(body.Hint, "auth not consumed") { + t.Errorf("hint = %q, must say auth was not consumed", body.Hint) + } +} + func TestForwardAuth_SettleOnSuccess(t *testing.T) { var verifyCalled, settleCalled atomic.Int32 fac := mockFacilitatorV1(true, true, &verifyCalled, &settleCalled) @@ -727,6 +874,222 @@ func TestForwardAuth_FacilitatorVerifyBodyUsesJSONObjectPaymentPayload(t *testin } } +func TestForwardAuth_AcceptsStringResourceCompatPayload(t *testing.T) { + var verifyCalled, settleCalled atomic.Int32 + fac := mockFacilitatorV1(true, true, &verifyCalled, &settleCalled) + defer fac.Close() + + mw := NewForwardAuthMiddleware(ForwardAuthConfig{ + FacilitatorURL: fac.URL, + VerifyOnly: true, + }, testRequirements()) + + payload := map[string]any{ + "x402Version": 2, + "accepted": map[string]any{ + "scheme": "exact", + "network": "eip155:84532", + "amount": "1000", + "asset": ChainBaseSepolia.USDCAddress, + "payTo": "0xWallet", + }, + "payload": map[string]any{ + "signature": "0xSig", + "authorization": map[string]any{ + "from": "0xFrom", "to": "0xTo", "value": "1000", + "validAfter": "0", "validBefore": "9999999999", "nonce": "0xNonce", + }, + }, + "resource": "https://seller.example.com/services/demo", + } + raw, _ := json.Marshal(payload) + + req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil) + req.Header.Set("PAYMENT-SIGNATURE", base64.StdEncoding.EncodeToString(raw)) + rec := httptest.NewRecorder() + mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })).ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want 200 (resource string should be tolerated)", rec.Code) + } + if verifyCalled.Load() != 1 { + t.Fatalf("verify called %d times, want 1", verifyCalled.Load()) + } + if settleCalled.Load() != 0 { + t.Fatalf("settle called %d times, want 0", settleCalled.Load()) + } +} + +func TestForwardAuth_AcceptsLegacyNetworkAliasPayment(t *testing.T) { + var verifyCalled, settleCalled atomic.Int32 + fac := mockFacilitatorV1(true, true, &verifyCalled, &settleCalled) + defer fac.Close() + + req, err := BuildV2Requirement(ChainBaseMainnet, "0.001", "0xWallet", 0) + if err != nil { + t.Fatalf("BuildV2Requirement: %v", err) + } + mw := NewForwardAuthMiddleware(ForwardAuthConfig{ + FacilitatorURL: fac.URL, + VerifyOnly: true, + }, []x402types.PaymentRequirements{req}) + + payload := x402types.PaymentPayload{ + X402Version: 2, + Accepted: x402types.PaymentRequirements{ + Scheme: "exact", + Network: "base", + Amount: "1000", + Asset: ChainBaseMainnet.USDCAddress, + PayTo: "0xWallet", + }, + Payload: map[string]interface{}{ + "signature": "0xSig", + "authorization": map[string]interface{}{ + "from": "0xFrom", "to": "0xTo", "value": "1000", + "validAfter": "0", "validBefore": "9999999999", "nonce": "0xNonce", + }, + }, + } + raw, _ := json.Marshal(payload) + + reqHTTP := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil) + reqHTTP.Header.Set("PAYMENT-SIGNATURE", base64.StdEncoding.EncodeToString(raw)) + rec := httptest.NewRecorder() + mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })).ServeHTTP(rec, reqHTTP) + + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want 200", rec.Code) + } + if verifyCalled.Load() != 1 { + t.Fatalf("verify called %d times, want 1", verifyCalled.Load()) + } + if settleCalled.Load() != 0 { + t.Fatalf("settle called %d times, want 0", settleCalled.Load()) + } +} + +func TestNormalizePaymentPayloadForVerify_RewritesRecoveryID(t *testing.T) { + sig := "0x" + strings.Repeat("ab", 64) + "00" // v=0 + raw, _ := json.Marshal(map[string]any{ + "x402Version": 2, + "payload": map[string]any{ + "signature": sig, + "authorization": map[string]any{ + "from": "0xFrom", + }, + }, + }) + out, note := normalizePaymentPayloadForVerify(raw) + if note != "signature_v_0_to_27" { + t.Fatalf("note = %q, want signature_v_0_to_27", note) + } + var parsed map[string]any + if err := json.Unmarshal(out, &parsed); err != nil { + t.Fatalf("unmarshal: %v", err) + } + payload := parsed["payload"].(map[string]any) + got := payload["signature"].(string) + if !strings.HasSuffix(got, "1b") { // 27 == 0x1b + t.Fatalf("signature = %q, want v rewritten to 27 (1b)", got) + } +} + +// TestForwardAuth_SettleReceivesNormalizedSignature_V0 is the end-to-end +// regression for the bug where verify used the recovery-id-normalized +// payload (v=0/1 -> 27/28, the Bankr auto-pay quirk) but settle sent the +// original, unnormalized bytes. On a real facilitator that means verify +// succeeds — the seller serves the request for free — and settle then +// fails on-chain ("invalid signature 'v' value"), so the buyer never +// actually gets charged. Both /verify and /settle must see v=27 (0x1b). +func TestForwardAuth_SettleReceivesNormalizedSignature_V0(t *testing.T) { + sig := "0x" + strings.Repeat("ab", 64) + "00" // v=0 + + var verifySig, settleSig string + fac := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var envelope struct { + PaymentPayload struct { + Payload struct { + Signature string `json:"signature"` + } `json:"payload"` + } `json:"paymentPayload"` + } + if err := json.NewDecoder(r.Body).Decode(&envelope); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + switch r.URL.Path { + case "/verify": + verifySig = envelope.PaymentPayload.Payload.Signature + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"isValid":true,"payer":"0xPayer"}`)) + case "/settle": + settleSig = envelope.PaymentPayload.Payload.Signature + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"success":true,"transaction":"0xTxHash","network":"eip155:84532","payer":"0xPayer"}`)) + default: + http.NotFound(w, r) + } + })) + defer fac.Close() + + mw := NewForwardAuthMiddleware(ForwardAuthConfig{ + FacilitatorURL: fac.URL, + VerifyOnly: false, + }, testRequirements()) + + payload := x402types.PaymentPayload{ + X402Version: 2, + Accepted: x402types.PaymentRequirements{ + Scheme: "exact", + Network: "eip155:84532", + Amount: "1000", + Asset: ChainBaseSepolia.USDCAddress, + PayTo: "0xWallet", + }, + Payload: map[string]interface{}{ + "signature": sig, + "authorization": map[string]interface{}{ + "from": "0xFrom", "to": "0xTo", "value": "1000", + "validAfter": "0", "validBefore": "9999999999", "nonce": "0xNonce", + }, + }, + } + raw, _ := json.Marshal(payload) + + req := httptest.NewRequest("POST", "/v1/chat/completions", nil) + req.Header.Set("PAYMENT-SIGNATURE", base64.StdEncoding.EncodeToString(raw)) + rec := httptest.NewRecorder() + mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"result":"ok"}`)) + })).ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("status = %d, want 200", rec.Code) + } + if !strings.HasSuffix(verifySig, "1b") { + t.Fatalf("/verify saw signature %q, want v normalized to 27 (1b)", verifySig) + } + if !strings.HasSuffix(settleSig, "1b") { + t.Fatalf("/settle saw signature %q, want v normalized to 27 (1b) — settle must match verify", settleSig) + } +} + +func TestFacilitatorRejectDetail_IncludesInvalidReasonDetails(t *testing.T) { + got := facilitatorRejectDetail(&facilitatorVerifyResponse{ + InvalidReason: "unexpected_error", + InvalidReasonDetails: "ECRecover: invalid signature 'v' value", + }) + if !strings.Contains(got, "unexpected_error") || !strings.Contains(got, "invalid signature 'v'") { + t.Fatalf("detail = %q, want reason + details", got) + } +} + // TestForwardAuth_VerifyOnlyFalse_EmitsStartupWarning asserts that constructing // the middleware with VerifyOnly=false logs a loud warning. An operator who // flips verifyOnly=false in x402-pricing.yaml may believe that enables real @@ -951,3 +1314,84 @@ func TestForwardAuth_402CarriesCatalogLinkHeader(t *testing.T) { } }) } + +// brokenPipeWriter simulates a Cloudflare/Bankr client that is already gone: +// WriteHeader succeeds (headers buffered) but body Write fails. Request +// context often stays live in that case — write errors must skip settle. +type brokenPipeWriter struct { + header http.Header + code int +} + +func (w *brokenPipeWriter) Header() http.Header { + if w.header == nil { + w.header = make(http.Header) + } + return w.header +} +func (w *brokenPipeWriter) WriteHeader(code int) { w.code = code } +func (w *brokenPipeWriter) Write([]byte) (int, error) { + return 0, errors.New("write: broken pipe") +} + +func TestSettlementInterceptor_ClientWriteErrorSkipsSettle(t *testing.T) { + settleCalls := 0 + i := &settlementInterceptor{ + w: &brokenPipeWriter{}, + deferSettle: true, + settleFunc: func() bool { + settleCalls++ + return true + }, + } + i.Header().Set("Content-Type", "text/event-stream") + i.WriteHeader(http.StatusOK) + _, _ = i.Write([]byte("data: hi\n\n")) + i.finalize(context.Background()) // live context — cancel did NOT propagate + if settleCalls != 0 { + t.Fatalf("settleCalls = %d, want 0 (broken pipe must not charge)", settleCalls) + } + if i.clientWriteErr == nil { + t.Fatal("expected clientWriteErr to be recorded") + } +} + +func TestSettlementInterceptor_EmptyBodySkipsSettle(t *testing.T) { + settleCalls := 0 + rec := httptest.NewRecorder() + i := &settlementInterceptor{ + w: rec, + deferSettle: true, + settleFunc: func() bool { + settleCalls++ + return true + }, + } + i.Header().Set("Content-Type", "text/event-stream") + i.WriteHeader(http.StatusOK) + // Headers only — no body bytes (client timed out before first token). + i.finalize(context.Background()) + if settleCalls != 0 { + t.Fatalf("settleCalls = %d, want 0 (empty body must not charge)", settleCalls) + } +} + +func TestSettlementInterceptor_DeferredNonSSEStillEager(t *testing.T) { + // Non-SSE must keep eager settle-on-WriteHeader so settle failures can + // still become 503 before the body (sell-inference / ForwardAuth tests). + settleCalls := 0 + rec := httptest.NewRecorder() + i := &settlementInterceptor{ + w: rec, + deferSettle: true, + settleFunc: func() bool { + settleCalls++ + return true + }, + } + i.Header().Set("Content-Type", "application/json") + i.WriteHeader(http.StatusOK) + if settleCalls != 1 { + t.Fatalf("settleCalls = %d, want 1 on WriteHeader for non-SSE", settleCalls) + } +} diff --git a/internal/x402/metrics.go b/internal/x402/metrics.go index 69c70c9f..74b74431 100644 --- a/internal/x402/metrics.go +++ b/internal/x402/metrics.go @@ -18,7 +18,7 @@ type verifierMetrics struct { lastPaymentSuccess *prometheus.GaugeVec // paymentFailureReasons splits paymentFailed by WHY (payment_invalid, - // facilitator_unreachable, settlement_failed, ...). paymentFailed alone + // facilitator_unreachable, facilitator_error, settlement_failed, ...). paymentFailed alone // says the funnel leaks; the reason label says where to fix it — the // difference between "first-try success is 20%" and knowing which stage // eats the other 80%. @@ -79,7 +79,7 @@ func newVerifierMetrics() *verifierMetrics { paymentFailureReasons: prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "obol_x402_verifier_payment_failure_reasons_total", - Help: "Payment-flow failures split by machine-readable reason (payment_invalid, facilitator_unreachable, settlement_failed, ...).", + Help: "Payment-flow failures split by machine-readable reason (payment_invalid, facilitator_unreachable, facilitator_error, settlement_failed, ...).", }, []string{"offer_namespace", "offer_name", "chain", "asset_symbol", "reason"}, ), diff --git a/internal/x402/paymentrequired.go b/internal/x402/paymentrequired.go index 2461a8c7..39309767 100644 --- a/internal/x402/paymentrequired.go +++ b/internal/x402/paymentrequired.go @@ -220,7 +220,7 @@ func sendPaymentRequiredHTML(w http.ResponseWriter, r *http.Request, requirement // Catalog discovery link rides on the HTML branch too (and survives the // JSON fallbacks below — Header().Set is idempotent). setCatalogLinkHeader(w) - jsonBody := buildPaymentRequired(r, requirements, extensions) + jsonBody := paymentRequiredBody(r, requirements, extensions) indented, err := json.MarshalIndent(jsonBody, "", " ") if err != nil { // Should not happen — fall back to JSON path. diff --git a/internal/x402/verifier.go b/internal/x402/verifier.go index 7873837e..a8046ebd 100644 --- a/internal/x402/verifier.go +++ b/internal/x402/verifier.go @@ -2,6 +2,9 @@ package x402 import ( "bufio" + "context" + "encoding/json" + "errors" "fmt" "log" "net" @@ -857,13 +860,52 @@ func buildUpstreamProxy(rule *RouteRule) (http.Handler, error) { } }, ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { + // ReverseProxy only reaches ErrorHandler AFTER ForwardAuth + // middleware has already verified payment (settlement still + // pending on the first successful upstream WriteHeader). A + // bare "upstream unavailable" 502 here is what Bankr/AgentCash + // mislabel as "x402 payment failed" — especially on agent + // offers where the buyer times out before Hermes emits a first + // SSE byte and retries 2–3×. Surface a structured body so + // buyers can tell verify≠settle≠payment-invalid apart. log.Printf("x402-verifier: upstream proxy error for %s/%s: %v", rule.OfferNamespace, rule.OfferName, err) - http.Error(w, "upstream unavailable", http.StatusBadGateway) + writeUpstreamProxyError(w, err) }, } return proxy, nil } +// writeUpstreamProxyError writes a structured JSON error for ReverseProxy +// failures after payment verify. Status is 504 when the failure is a +// canceled/deadline context (buyer disconnect or client timeout — the +// common Bankr ~30s retry loop), otherwise 502. +func writeUpstreamProxyError(w http.ResponseWriter, err error) { + status := http.StatusBadGateway + reason := "upstream_unavailable" + hint := "the upstream service failed before producing a response; payment was verified and seller-side settlement was skipped — confirm on BaseScan before retrying" + if err != nil && (errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) || + strings.Contains(err.Error(), "context canceled") || strings.Contains(err.Error(), "context deadline")) { + status = http.StatusGatewayTimeout + reason = "client_timeout_or_disconnect" + hint = "the client disconnected or timed out before the agent finished; seller skips settlement when the client write fails or the context cancels — still confirm on BaseScan before retrying. Raise client timeout to ≥180s, keep stream:true, do not auto-retry" + } + + w.Header().Set("Content-Type", "application/json") + w.Header().Set("X-Payment-Settled", "false") + w.Header().Set("X-Payment-Verified", "true") + w.WriteHeader(status) + _ = json.NewEncoder(w).Encode(map[string]any{ + "error": http.StatusText(status), + "reason": reason, + "paymentVerified": true, + "paymentSettled": false, + // Do NOT encourage auto-retry storms (Bankr fired 3 paid verifies + // in ~30s). Buyer must raise timeout / fix stream before retrying. + "retriable": false, + "hint": hint, + }) +} + // chatCompletionsPath is the OpenAI-compatible path served by inference and // agent upstreams (LiteLLM, Hermes). const chatCompletionsPath = "/v1/chat/completions" diff --git a/internal/x402/verifier_test.go b/internal/x402/verifier_test.go index 4984fbaf..761d8b57 100644 --- a/internal/x402/verifier_test.go +++ b/internal/x402/verifier_test.go @@ -2,6 +2,7 @@ package x402 import ( "bufio" + "context" "encoding/base64" "encoding/json" "fmt" @@ -264,10 +265,15 @@ func TestVerifier_MultiPayment_AdvertisesAllAndSettlesChosen(t *testing.T) { if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil { t.Fatalf("parse 402 body: %v (body=%s)", err, w.Body.String()) } - if len(got.Accepts) != 2 { - t.Fatalf("accepts length = %d, want 2 (multi-currency offer)", len(got.Accepts)) + if len(got.Accepts) != 4 { + // 2 payment options × (CAIP-2 + legacy network alias) for Bankr/legacy + // buyers — see legacyCompatRequirements. + t.Fatalf("accepts length = %d, want 4 (2 options × CAIP-2+legacy)", len(got.Accepts)) + } + amounts := map[string]string{} + for _, a := range got.Accepts { + amounts[a.PayTo] = a.Amount } - amounts := map[string]string{got.Accepts[0].PayTo: got.Accepts[0].Amount, got.Accepts[1].PayTo: got.Accepts[1].Amount} if amounts[payToPrimary] != "1000" || amounts[payToSecond] != "2000" { t.Fatalf("accepts amounts = %v, want primary→1000 secondary→2000", amounts) } @@ -627,6 +633,54 @@ func TestVerifier_HandleProxy_UpstreamFailure_DoesNotSettle(t *testing.T) { } } +// TestWriteUpstreamProxyError_ContextCanceled pins the Bankr/AgentCash +// mislabel fix: client timeout after verify must NOT look like a payment +// failure — structured JSON + X-Payment-Settled:false + 504. +func TestWriteUpstreamProxyError_ContextCanceled(t *testing.T) { + w := httptest.NewRecorder() + writeUpstreamProxyError(w, context.Canceled) + if w.Code != http.StatusGatewayTimeout { + t.Fatalf("status = %d, want 504", w.Code) + } + if w.Header().Get("X-Payment-Settled") != "false" { + t.Fatalf("X-Payment-Settled = %q, want false", w.Header().Get("X-Payment-Settled")) + } + if w.Header().Get("X-Payment-Verified") != "true" { + t.Fatalf("X-Payment-Verified = %q, want true", w.Header().Get("X-Payment-Verified")) + } + var body map[string]any + if err := json.NewDecoder(w.Body).Decode(&body); err != nil { + t.Fatalf("decode: %v", err) + } + if body["reason"] != "client_timeout_or_disconnect" { + t.Fatalf("reason = %v", body["reason"]) + } + if body["paymentSettled"] != false { + t.Fatalf("paymentSettled = %v, want false", body["paymentSettled"]) + } + if body["paymentVerified"] != true { + t.Fatalf("paymentVerified = %v, want true", body["paymentVerified"]) + } + if body["retriable"] != false { + t.Fatalf("retriable = %v, want false (no auto-retry storms)", body["retriable"]) + } +} + +func TestWriteUpstreamProxyError_Generic(t *testing.T) { + w := httptest.NewRecorder() + writeUpstreamProxyError(w, fmt.Errorf("connection reset")) + if w.Code != http.StatusBadGateway { + t.Fatalf("status = %d, want 502", w.Code) + } + var body map[string]any + if err := json.NewDecoder(w.Body).Decode(&body); err != nil { + t.Fatalf("decode: %v", err) + } + if body["reason"] != "upstream_unavailable" { + t.Fatalf("reason = %v", body["reason"]) + } +} + // TestVerifier_HandleProxy_StreamsSSEChunks proves the seller-gateway path // (Traefik → x402-verifier → upstream) preserves Server-Sent Events streaming // end-to-end. This is what makes `obol sell agent` usable as an OpenAI- @@ -709,9 +763,6 @@ func TestVerifier_HandleProxy_StreamsSSEChunks(t *testing.T) { if ct := resp.Header.Get("Content-Type"); !strings.HasPrefix(ct, "text/event-stream") { t.Fatalf("Content-Type = %q, want text/event-stream*", ct) } - if resp.Header.Get("X-PAYMENT-RESPONSE") == "" { - t.Fatal("expected X-PAYMENT-RESPONSE on a streaming success") - } // Read each SSE event ("data: ...\n\n") and capture the elapsed time // since the request started. If anything in the chain buffers the @@ -762,11 +813,87 @@ func TestVerifier_HandleProxy_StreamsSSEChunks(t *testing.T) { arrivals[0], arrivals[3], spread, 2*chunkGap, arrivals) } + // Deferred settlement puts the receipt in HTTP trailers after the SSE + // body completes (so a mid-stream disconnect cannot have already charged). + _, _ = io.Copy(io.Discard, resp.Body) + receipt := resp.Header.Get("X-PAYMENT-RESPONSE") + if receipt == "" { + receipt = resp.Trailer.Get("X-PAYMENT-RESPONSE") + } + if receipt == "" { + t.Fatal("expected X-PAYMENT-RESPONSE header or trailer on a streaming success") + } + if fac.verifyCalls.Load() != 1 { t.Fatalf("verify calls = %d, want 1", fac.verifyCalls.Load()) } if fac.settleCalls.Load() != 1 { - t.Fatalf("settle calls = %d, want 1 (settle is one-shot per paid request, not per chunk)", fac.settleCalls.Load()) + t.Fatalf("settle calls = %d, want 1 (settle is one-shot per paid request, after the stream completes)", fac.settleCalls.Load()) + } +} + +// TestVerifier_HandleProxy_NoSettleWhenClientCancelsMidStream proves the +// deferred-settlement invariant: verify may succeed and upstream may even +// start streaming, but a canceled client must not trigger /settle. +func TestVerifier_HandleProxy_NoSettleWhenClientCancelsMidStream(t *testing.T) { + fac := newMockFacilitator(t, mockFacilitatorOpts{}) + + started := make(chan struct{}) + upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/event-stream") + w.WriteHeader(http.StatusOK) + flusher, _ := w.(http.Flusher) + _, _ = w.Write([]byte("data: {\"choices\":[{\"delta\":{\"content\":\"hi\"}}]}\n\n")) + if flusher != nil { + flusher.Flush() + } + close(started) + <-r.Context().Done() + })) + defer upstream.Close() + + v := newTestVerifier(t, fac.URL, []RouteRule{{ + Pattern: "/services/demo/*", + Price: "0.0001", + UpstreamURL: upstream.URL, + StripPrefix: "/services/demo", + OfferType: "agent", + }}) + + srv := httptest.NewServer(http.HandlerFunc(v.HandleProxy)) + defer srv.Close() + + ctx, cancel := context.WithCancel(context.Background()) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, srv.URL+"/services/demo/v1/chat/completions", strings.NewReader(`{"stream":true}`)) + if err != nil { + t.Fatalf("new request: %v", err) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-PAYMENT", testPaymentHeader(t)) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatalf("client.Do: %v", err) + } + // Wait until upstream has flushed the first SSE byte, then cancel — + // the old settle-on-WriteHeader path would already have charged here. + select { + case <-started: + case <-time.After(2 * time.Second): + t.Fatal("upstream never started streaming") + } + cancel() + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + + // Give finalize a moment to observe the canceled context. + time.Sleep(50 * time.Millisecond) + + if fac.verifyCalls.Load() != 1 { + t.Fatalf("verify calls = %d, want 1", fac.verifyCalls.Load()) + } + if fac.settleCalls.Load() != 0 { + t.Fatalf("settle calls = %d, want 0 (mid-stream cancel must not charge)", fac.settleCalls.Load()) } } diff --git a/web/public-storefront/src/components/ServiceCard.tsx b/web/public-storefront/src/components/ServiceCard.tsx index 0dbfabd2..1d48cccc 100644 --- a/web/public-storefront/src/components/ServiceCard.tsx +++ b/web/public-storefront/src/components/ServiceCard.tsx @@ -62,9 +62,16 @@ function normalizeOfferType(t: string): "inference" | "agent" | "http" { return "http"; } -type Tab = "agent" | "other-ai" | "agentcash" | "bankr" | "code"; +type Tab = "agent" | "external" | "code"; +type ExternalBuyerTool = "agentcash" | "bankr" | "generic-llm"; const AGENT_TASK_PLACEHOLDER = "Summarise the README and list the top 3 risks."; +const EXTERNAL_BUYER_TOOLS: { id: ExternalBuyerTool; label: string }[] = [ + { id: "agentcash", label: "AgentCash" }, + { id: "bankr", label: "Bankr" }, + { id: "generic-llm", label: "Another AI" }, +]; + function resolvedAgentTask(task: string): string { return task.trim() || AGENT_TASK_PLACEHOLDER; } @@ -117,6 +124,8 @@ ${buildAgentPayAgentCommand(endpoint, model, agentTask)}`; export function ServiceCard({ service }: { service: Service }) { const [open, setOpen] = useState(false); const [tab, setTab] = useState("agent"); + const [externalTool, setExternalTool] = + useState("agentcash"); const [copied, setCopied] = useState(false); const [agentTask, setAgentTask] = useState(""); @@ -341,62 +350,7 @@ export function ServiceCard({ service }: { service: Service }) { requireTask={needsAgentTask} /> )} - {tab === "other-ai" && ( - - Paste this into Claude, ChatGPT, Gemini, or any AI agent - with internet access. The buy-x402 skill from{" "} - - ObolNetwork/skills - {" "} - bootstraps the stack and asks for your permission before - spending. - - } - /> - )} - {tab === "agentcash" && ( - - Paste this into your{" "} - - AgentCash - - -connected agent. This origin already publishes AgentCash's - discovery convention — OpenAPI x-payment-info{" "} - plus a /.well-known/x402{" "} - fallback — so it can price this call automatically. - - } - /> - )} - {tab === "bankr" && ( + {tab === "external" && ( - Paste this into your{" "} - - Bankr - - -connected agent or wallet. There's no Bankr-specific - format here — this is a standard x402 endpoint (402 - challenge, EIP-3009/Permit2 signature, X-PAYMENT retry). - - } + tool={externalTool} + onToolChange={setExternalTool} /> )} {tab === "code" && ( @@ -442,9 +381,7 @@ export function ServiceCard({ service }: { service: Service }) { function TabBar({ tab, onChange }: { tab: Tab; onChange: (t: Tab) => void }) { const tabs: { id: Tab; label: string }[] = [ { id: "agent", label: "Ask your Obol agent" }, - { id: "other-ai", label: "Ask another AI agent" }, - { id: "agentcash", label: "Pay with AgentCash" }, - { id: "bankr", label: "Pay with Bankr" }, + { id: "external", label: "Pay with a buyer tool" }, { id: "code", label: "Buy with code" }, ]; return ( @@ -549,11 +486,9 @@ function BuyViaObolAgent({ ); } -// BuyViaExternalTool renders the "paste this into a buyer tool with no Obol -// tooling" tabs: the generic "another AI agent" tab plus the AgentCash and -// Bankr tabs. All three read the same shape of prompt (promptKey selects -// which one) — the underlying x402 mechanics are identical, only the intro -// copy and published prompt differ per tool. +// BuyViaExternalTool is the single "Pay with a buyer tool" tab: a pill +// selector (AgentCash / Bankr / Another AI, same pattern as x402scan's +// AgentCash↔Poncho switch) picks which published buy prompt + intro to show. function BuyViaExternalTool({ service, opt, @@ -561,8 +496,8 @@ function BuyViaExternalTool({ agentTask, taskReady, requireTask, - promptKey, - intro, + tool, + onToolChange, }: { service: Service; opt: ServicePayment; @@ -570,8 +505,8 @@ function BuyViaExternalTool({ agentTask: string; taskReady: boolean; requireTask: boolean; - promptKey: "generic-llm" | "agentcash" | "bankr"; - intro: ReactNode; + tool: ExternalBuyerTool; + onToolChange: (t: ExternalBuyerTool) => void; }) { // Canonical prompts from the catalog buy block; inline strings are only // fallbacks for pre-buy-block catalogs (these predate the agentcash/bankr @@ -580,24 +515,51 @@ function BuyViaExternalTool({ if (kind === "inference") { const model = service.model || "the advertised model"; prompt = - buyPrompt(service, promptKey) ?? + buyPrompt(service, tool) ?? `${docsRef(service.endpoint)} I want to use the remote LLM at ${service.endpoint} (model ${model}) as a paid OpenAI-compatible chat-completions endpoint. Pre-sign a budget of EIP-3009 or Permit2 authorisations and POST chat-completions bodies with the X-PAYMENT header attached.`; } else if (kind === "agent") { // An agent runs its own pinned model server-side and ignores the request's // model field, so we don't tell the buyer which model it uses — the request // shape is what matters. prompt = - buyPrompt(service, promptKey, agentTask) ?? + buyPrompt(service, tool, agentTask) ?? `${docsRef(service.endpoint)} Help me call the Obol Agent at ${service.endpoint} — it's an autonomous agent (tools + skills + memory), not a raw LLM. POST OpenAI-style chat-completions JSON to ${service.endpoint}/v1/chat/completions with this user message in \`messages\`: {"role":"user","content":${quoteAgentTask(agentTask)}}. Attach a signed EIP-3009 or Permit2 authorisation as \`X-PAYMENT\`, and report what the agent does.`; } else { prompt = - buyPrompt(service, promptKey) ?? + buyPrompt(service, tool) ?? `I want to purchase a service offered by an Obol Agent at ${service.endpoint} for ${opt.price} on ${opt.network}. Please install the run-obol-stack skill from https://github.com/ObolNetwork/skills, ask me for permission to set up the obol stack, and use the buy-x402 skill to make the purchase on my behalf.`; } return ( -
-

{intro}

+
+
+ {EXTERNAL_BUYER_TOOLS.map((t) => { + const active = tool === t.id; + return ( + + ); + })} +
+

+ {externalBuyerIntro(tool, kind)} +

+ Paste this into your{" "} + + AgentCash + + -connected agent. This origin already publishes AgentCash's + discovery convention — OpenAPI{" "} + x-payment-info{" "} + plus a{" "} + /.well-known/x402{" "} + fallback — so it can price this call automatically. + + ); + case "bankr": + // HTTP: paste into Bankr chat — auto-pay works for fast endpoints. + // Agent/inference: Cursor + `bankr wallet sign` + long curl (chat/Apps + // hit ~30s `rpc timeout`). + if (kind === "http") { + return ( + <> + Paste this into{" "} + + Bankr + {" "} + chat (or Max Mode) — prefer chat auto-pay for this fast HTTP + offer. Do not ask chat to run{" "} + bankr wallet sign + ; if auto-pay keeps failing, use a Bankr-CLI agent instead. + + ); + } + return ( + <> + Paste this into a{" "} + + Bankr + + -CLI agent (e.g. Cursor) — not Bankr chat auto-pay and not a Bankr + App. This prompt uses{" "} + bankr wallet sign + {" + ≥180s curl (Bankr chat/Apps hit `rpc timeout` at ~30s on agent offers)"} + ; check BaseScan before any retry. + + ); + case "generic-llm": + return ( + <> + Paste this into Claude, ChatGPT, Gemini, or any AI agent with + internet access. The buy-x402 skill from{" "} + + ObolNetwork/skills + {" "} + bootstraps the stack and asks for your permission before spending. + + ); + } +} + function BuyWithCode({ service, opt, From 1bc489b8ed97601d094f333a4bfa0ab9a3608d67 Mon Sep 17 00:00:00 2001 From: HananINouman Date: Wed, 5 Aug 2026 21:26:36 +0300 Subject: [PATCH 29/31] feat(storefront): add Poncho as Merit-family external buyer tab Publish type-specific Poncho chat prompts alongside AgentCash/Bankr and surface them in the storefront pill selector after a successful live pay test. Co-authored-by: Cursor --- internal/buyprompts/buyprompts.go | 45 +++++++++++++++++-- internal/buyprompts/buyprompts_test.go | 27 ++++++++++- .../src/components/ServiceCard.tsx | 34 ++++++++++++-- 3 files changed, 97 insertions(+), 9 deletions(-) diff --git a/internal/buyprompts/buyprompts.go b/internal/buyprompts/buyprompts.go index 3246769b..a556e7c0 100644 --- a/internal/buyprompts/buyprompts.go +++ b/internal/buyprompts/buyprompts.go @@ -33,11 +33,16 @@ const ( PromptGenericLLM = "generic-llm" // PromptCLI is the shell command a human runs from an obol-stack host. PromptCLI = "cli" - // PromptAgentCash is pasted into an AgentCash-connected agent/wallet. - // AgentCash (Merit Systems) shares its discovery convention — OpenAPI - // `x-payment-info` plus a `/.well-known/x402` fallback — with x402scan - // and Poncho, so this same prompt covers all three. + // PromptAgentCash is pasted into an AgentCash-connected agent/wallet + // (Merit Systems MCP/CLI). Shares discovery with x402scan + Poncho: + // OpenAPI `x-payment-info` + `/.well-known/x402`. PromptAgentCash = "agentcash" + // PromptPoncho is pasted into Poncho chat (https://tryponcho.com) — Merit + // Systems' consumer agent. Same discovery + wallet-pay path as AgentCash + // (Poncho runs on the AgentCash micropayment layer), NOT Bankr's scoped + // chat auto-pay. Expect AgentCash-like success on third-party Obol URLs; + // still require long client timeouts for agent/inference offers. + PromptPoncho = "poncho" // PromptBankr is pasted into Bankr chat or a Bankr-CLI agent. Guidance is // type-specific (observed 2026-08-05 against third-party Obol sellers): // - http: prefer Bankr chat auto-pay — fast endpoints usually finish @@ -217,6 +222,22 @@ func agentBlock(in Input) Block { "If the response has `paymentSettled:false`, you were NOT charged — do not spam retries.", in.URL, GuideRef(in.SiteURL), ChatCompletionsURL(in.URL), modelOr(in, "hermes-agent"), task(in), ), + PromptPoncho: fmt.Sprintf( + "Paste this into Poncho chat (https://tryponcho.com). Call the Obol Agent at %s "+ + "EXACTLY as specified — do not drop fields. Poncho runs on AgentCash micropayments "+ + "and speaks the same discovery convention (OpenAPI x-payment-info + /.well-known/x402) "+ + "as AgentCash — enable/fund the USDC wallet if Poncho asks. "+ + "REQUIRED: POST %s with Content-Type application/json and this exact body "+ + "(keep stream true; client HTTP timeout must be ≥180s — agents often take "+ + "30–120s before the first SSE byte):\n"+ + "```json\n"+ + "{\"model\":%q,\"stream\":true,\"messages\":[{\"role\":\"user\",\"content\":%q}]}\n"+ + "```\n"+ + "Confirm the quoted USDC payment when Poncho prompts. "+ + "If the response has `paymentSettled:false`, you were NOT charged — do not spam retries. "+ + "Seller docs: %s.", + in.URL, ChatCompletionsURL(in.URL), modelOr(in, "hermes-agent"), task(in), GuideRef(in.SiteURL), + ), PromptBankr: fmt.Sprintf( "You have my Bankr wallet via the Bankr CLI (`bankr`). Buy one turn from the Obol Agent at %s "+ "using MANUAL `bankr wallet sign` + a long curl (timeout ≥180s, prefer 300s). "+ @@ -291,6 +312,14 @@ func inferenceBlock(in Input) Block { "attached.", in.URL, model, ChatCompletionsURL(in.URL), GuideRef(in.SiteURL), ), + PromptPoncho: fmt.Sprintf( + "Paste this into Poncho chat (https://tryponcho.com). Buy the remote LLM at %s "+ + "(model %s) via POST %s. Poncho uses the AgentCash micropayment layer and the "+ + "same discovery convention (OpenAPI `x-payment-info` + `/.well-known/x402`) — "+ + "enable/fund the USDC wallet if asked. Use `stream:true` and a client timeout "+ + "≥180s. Confirm the quoted payment when prompted. Paid with %s.", + in.URL, model, ChatCompletionsURL(in.URL), GuideRef(in.SiteURL), + ), PromptBankr: fmt.Sprintf( "You have my Bankr wallet via the Bankr CLI (`bankr`). Buy the remote LLM at %s "+ "(model %s) at %s using MANUAL `bankr wallet sign` + curl — do NOT use Bankr chat "+ @@ -351,6 +380,14 @@ func httpBlock(in Input) Block { "retry with the payload in the `X-PAYMENT` header.", in.URL, GuideRef(in.SiteURL), priceClause, netClause, ), + PromptPoncho: fmt.Sprintf( + "Paste this into Poncho chat (https://tryponcho.com). Call the paid HTTP endpoint "+ + "at %s once.%s%s This origin publishes the Merit/AgentCash discovery convention "+ + "(OpenAPI `x-payment-info` + `/.well-known/x402`) that Poncho uses — enable/fund "+ + "the USDC wallet if asked, confirm the quoted payment, and report the response. "+ + "Seller docs: %s.", + in.URL, priceClause, netClause, GuideRef(in.SiteURL), + ), PromptBankr: fmt.Sprintf( "Paste this into Bankr chat (or Max Mode). Buy the HTTP endpoint at %s "+ "using Bankr chat auto-pay — call the URL and let Bankr attach payment.%s%s\n\n"+ diff --git a/internal/buyprompts/buyprompts_test.go b/internal/buyprompts/buyprompts_test.go index 7e75434f..c5d1c174 100644 --- a/internal/buyprompts/buyprompts_test.go +++ b/internal/buyprompts/buyprompts_test.go @@ -41,7 +41,7 @@ func TestBuild_ChatOffersTeachCanonicalPath(t *testing.T) { func TestBuild_AllTypesCarryEveryPromptKey(t *testing.T) { for _, typ := range []string{"agent", "inference", "http", "fine-tuning", "", "bogus"} { block := Build(Input{Type: typ, URL: "https://s.example/services/x"}) - for _, key := range []string{PromptObolAgent, PromptGenericLLM, PromptCLI, PromptAgentCash, PromptBankr} { + for _, key := range []string{PromptObolAgent, PromptGenericLLM, PromptCLI, PromptAgentCash, PromptPoncho, PromptBankr} { if strings.TrimSpace(block.Prompts[key]) == "" { t.Errorf("type %q: missing prompt %q", typ, key) } @@ -130,6 +130,31 @@ func TestBuild_BankrPromptTeachesManualWalletSign(t *testing.T) { } } +// TestBuild_PonchoPromptMirrorsMeritDiscovery pins Poncho as an AgentCash- +// family buyer (tryponcho.com chat + shared x-payment-info discovery), not +// Bankr-style scoped auto-pay. +func TestBuild_PonchoPromptMirrorsMeritDiscovery(t *testing.T) { + httpPrompt := Build(Input{ + Type: "http", + URL: "https://seller.example.com/services/demo", + }).Prompts[PromptPoncho] + for _, want := range []string{"Poncho", "tryponcho.com", "x-payment-info", "/.well-known/x402"} { + if !strings.Contains(httpPrompt, want) { + t.Errorf("http poncho prompt missing %q:\n%s", want, httpPrompt) + } + } + agent := Build(Input{ + Type: "agent", + URL: "https://seller.example.com/services/demo", + Model: "claude-sonnet-4-6", + }).Prompts[PromptPoncho] + for _, want := range []string{"Poncho", "tryponcho.com", "≥180s", "/v1/chat/completions", "stream\":true"} { + if !strings.Contains(agent, want) { + t.Errorf("agent poncho prompt missing %q:\n%s", want, agent) + } + } +} + // TestGuideRef_PointsAtSellerDocs pins that generic-LLM buyers are pointed // at the seller's own machine-readable docs, not a generic external page. func TestGuideRef_PointsAtSellerDocs(t *testing.T) { diff --git a/web/public-storefront/src/components/ServiceCard.tsx b/web/public-storefront/src/components/ServiceCard.tsx index 1d48cccc..8f4beacd 100644 --- a/web/public-storefront/src/components/ServiceCard.tsx +++ b/web/public-storefront/src/components/ServiceCard.tsx @@ -63,11 +63,12 @@ function normalizeOfferType(t: string): "inference" | "agent" | "http" { } type Tab = "agent" | "external" | "code"; -type ExternalBuyerTool = "agentcash" | "bankr" | "generic-llm"; +type ExternalBuyerTool = "agentcash" | "poncho" | "bankr" | "generic-llm"; const AGENT_TASK_PLACEHOLDER = "Summarise the README and list the top 3 risks."; const EXTERNAL_BUYER_TOOLS: { id: ExternalBuyerTool; label: string }[] = [ { id: "agentcash", label: "AgentCash" }, + { id: "poncho", label: "Poncho" }, { id: "bankr", label: "Bankr" }, { id: "generic-llm", label: "Another AI" }, ]; @@ -85,7 +86,7 @@ function resolvedAgentTask(task: string): string { // instructions. function buyPrompt( service: Service, - key: "obol-agent" | "generic-llm" | "cli" | "agentcash" | "bankr", + key: "obol-agent" | "generic-llm" | "cli" | "agentcash" | "poncho" | "bankr", agentTask?: string, ): string | null { const raw = service.buy?.prompts?.[key]; @@ -487,8 +488,8 @@ function BuyViaObolAgent({ } // BuyViaExternalTool is the single "Pay with a buyer tool" tab: a pill -// selector (AgentCash / Bankr / Another AI, same pattern as x402scan's -// AgentCash↔Poncho switch) picks which published buy prompt + intro to show. +// selector (AgentCash / Poncho / Bankr / Another AI) picks which published +// buy prompt + intro to show. function BuyViaExternalTool({ service, opt, @@ -594,6 +595,31 @@ function externalBuyerIntro( fallback — so it can price this call automatically. ); + case "poncho": + // Merit consumer chat on the AgentCash micropayment layer — same + // discovery as AgentCash, not Bankr's scoped auto-pay. Agent offers + // still need a long client timeout. + return ( + <> + Paste this into{" "} + + Poncho + {" "} + chat. Poncho uses AgentCash micropayments and the same OpenAPI{" "} + x-payment-info /{" "} + /.well-known/x402{" "} + discovery — enable the USDC wallet if asked + {kind === "http" + ? "" + : ", and keep the client timeout ≥180s for agent/LLM calls"} + . + + ); case "bankr": // HTTP: paste into Bankr chat — auto-pay works for fast endpoints. // Agent/inference: Cursor + `bankr wallet sign` + long curl (chat/Apps From 3f3c745e4d74e7f421f12bd9d5d5302462b22213 Mon Sep 17 00:00:00 2001 From: HananINouman Date: Wed, 5 Aug 2026 21:51:24 +0300 Subject: [PATCH 30/31] docs: correct Bankr Apps allowlist finding for external buyers Record that a Bankr App with pay:x402 + allowedHosts still hit rpc timeout on Base mainnet agent offers; drop the Sepolia digression and clarify HandleProxy settle-skip vs ForwardAuth verify-only. Co-authored-by: Cursor --- docs/observability.md | 64 +++++++++++++++++-------------- internal/buyprompts/buyprompts.go | 12 +++--- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/docs/observability.md b/docs/observability.md index ea4b7d51..5c6df8a5 100644 --- a/docs/observability.md +++ b/docs/observability.md @@ -254,37 +254,42 @@ A `Transfer` to the expected recipient that exists while the buyer reports ## External buyers (Bankr): two failure modes, one canonical ledger -Bankr chat / `bankr x402 call` against Obol agent offers confused operators -because the UI error and the on-chain outcome often disagreed. - -**Root cause (2026-08-05 investigation): Bankr chat/CLI auto-pay is not -built to pay arbitrary third-party x402 endpoints at all.** Per Bankr's own -docs, auto-pay is scoped to endpoints deployed through `bankr x402 deploy` -and then approved into Bankr's own discovery index (criteria undocumented) — -[docs.bankr.bot/x402-cloud/quick-start](https://docs.bankr.bot/x402-cloud/quick-start/). -Their Apps SDK (`bankr.x402.fetch`) separately requires a pre-declared -`allowedHosts` allowlist — -[docs.bankr.bot/apps/overview](https://docs.bankr.bot/apps/overview/). Every -documented example targets `"network":"base"` (mainnet); no Base Sepolia -support was found anywhere in their docs or the `BankrBot/skills` repo. An -arbitrary seller like ours is out of scope for chat auto-pay independent of -the two wire-level failure modes below — `bankr wallet sign` -([docs.bankr.bot/cli](https://docs.bankr.bot/cli/)) is Bankr's own -officially documented manual-signing primitive, not a workaround hack, and -is the only proven path today. - -When Bankr auto-pay *does* attempt a call anyway, there are **two separate -wire-level failure modes**: +Bankr chat / `bankr x402 call` / Apps against Obol agent offers confused +operators because the UI error and the on-chain outcome often disagreed. +Live Base mainnet testing (2026-08-05) against a public Obol tunnel clarified +what is seller-fixable vs buyer-client limits. + +**Context — Bankr surfaces are not one path.** Chat auto-pay, CLI +`bankr x402 call`, and Apps (`bankr.x402.fetch`) are different clients. +Bankr docs also describe chat/CLI auto-pay as oriented around endpoints +deployed via `bankr x402 deploy` / their discovery index +([docs.bankr.bot/x402-cloud/quick-start](https://docs.bankr.bot/x402-cloud/quick-start/)), +and Apps require a manifest `allowedHosts` allowlist +([docs.bankr.bot/apps/sdk](https://docs.bankr.bot/apps/sdk)). + +**Allowlisting is not enough for agent offers.** We built a Bankr App with +`pay:x402`, `allowedHosts: [""]`, and +`bankr.x402.fetch` against bounty-radar. Payment **often verified** on Base +mainnet; the App still failed with **`rpc timeout` ~30s** while the agent was +still running. So the Apps failure we hit was not “missing allowlist” or +“wrong network” — it was Bankr’s short client timeout on slow agent SSE. +HTTP offers through the same App/chat path usually succeed because they +finish in ~1s. + +When Bankr *does* attempt a paid call, there are **two separate wire-level +failure modes**: | Mode | What the buyer sees | What happened | Charge? | |---|---|---|---| -| **A — Voucher** | JSON `503` with `reason:facilitator_error`, `detail:unexpected_error` | Bankr auto-pay signed EIP-3009 with `validAfter=wall-clock now`. Base USDC requires `block.timestamp > validAfter`, so facilitator `/verify` rejects. | Usually **no** (verify never succeeded). | -| **B — Timeout / zombie** | Timeout, 504, or a generic “payment failed” after ~30s | Verify **succeeded**, agent was still running (often 30–120s to first SSE byte). Bankr’s short client timeout aborted the UI. Cloudflare often does **not** cancel the seller’s request context, so older seller builds still called `/settle` after upstream finished → BaseScan shows 0.001 USDC Transfers in a triple-retry burst. | **Yes** on older builds. | +| **A — Voucher** | JSON `503` with `reason:facilitator_error`, `detail:unexpected_error` | Buyer signed EIP-3009 with `validAfter=wall-clock now` (or a bad typed-data hash). Base USDC rejects (`not yet valid` / `invalid signature`). | Usually **no** (verify never succeeded). | +| **B — Timeout / zombie** | `rpc timeout`, 504, or generic failure after ~30s | Verify **succeeded**, agent still running (often 30–120s to first SSE byte). Bankr client aborted. Cloudflare often does **not** cancel seller context, so older seller builds still called `/settle` after upstream finished → BaseScan shows 0.001 USDC Transfers. | **Yes** on older builds. | -**Seller hardenings** (HandleProxy settlement interceptor): +**Seller hardenings** (in-process HandleProxy settlement for agent/http +gateways that settle after upstream — not the Traefik ForwardAuth +verify-only path): -- Settle SSE responses only in `finalize()` after the stream completes — - never on the first `WriteHeader(200)`. +- Settle SSE only in `finalize()` after the stream completes — never on the + first `WriteHeader(200)`. - Skip `/settle` when `r.Context()` is canceled, when a body `Write` to the client fails (broken pipe — the reliable signal when cancel does not propagate), or when zero body bytes were written. @@ -295,9 +300,10 @@ wire-level failure modes**: **Buyer guidance** (storefront Bankr prompts are type-specific): - **http** — prefer Bankr chat auto-pay (fast enough for the ~30s window). Do not ask chat to run `bankr wallet sign` (it cannot). -- **agent / inference** — do not use Bankr chat/Apps auto-pay (`rpc timeout`); - use `bankr wallet sign` with a past `validAfter` buffer and HTTP timeout ≥180s. - After any timeout, check BaseScan before retrying. +- **agent / inference** — do not use Bankr chat/Apps auto-pay (`rpc timeout` + even with a correct Apps `allowedHosts`); use `bankr wallet sign` with a + past `validAfter` buffer and HTTP timeout ≥180s. After any timeout, check + BaseScan before retrying. **Still true:** chain Transfers are canonical. Seller `paymentSettled:false` and Bankr UI copy are best-effort signals. diff --git a/internal/buyprompts/buyprompts.go b/internal/buyprompts/buyprompts.go index a556e7c0..52b57530 100644 --- a/internal/buyprompts/buyprompts.go +++ b/internal/buyprompts/buyprompts.go @@ -48,12 +48,12 @@ const ( // - http: prefer Bankr chat auto-pay — fast endpoints usually finish // inside Bankr's ~30s client window; do NOT ask chat to run // `bankr wallet sign` (chat cannot). - // - agent/inference: forbid chat/Apps auto-pay (`rpc timeout` / voucher - // quirks); teach `bankr wallet sign` + long curl in a Bankr-CLI agent - // (Cursor). Bankr's own docs also scope chat/CLI auto-pay to endpoints - // deployed via `bankr x402 deploy` + their discovery index, and Apps - // need an `allowedHosts` allowlist — still teach the proven CLI path - // for slow offers. + // - agent/inference: forbid chat/Apps auto-pay (`rpc timeout` even when + // an Apps manifest allowlists our host — observed on Base mainnet); + // teach `bankr wallet sign` + long curl in a Bankr-CLI agent (Cursor). + // Bankr docs also describe chat/CLI auto-pay as oriented around + // `bankr x402 deploy` / their discovery index — still teach the proven + // CLI path for slow offers. PromptBankr = "bankr" ) From a5d6972c9943d62813e52305982a240a67da2712 Mon Sep 17 00:00:00 2001 From: bussyjd Date: Thu, 6 Aug 2026 15:17:29 +0400 Subject: [PATCH 31/31] fix(x402): sanitize untrusted strings before logging (CodeQL go/log-injection) CodeQL flagged four new go/log-injection alerts introduced by this PR in internal/x402/forwardauth.go: the buyer's payment payload and the facilitator's reject reason reach log.Printf unescaped, so a crafted value containing CRLF can forge additional lines in the operator's log (e.g. a fake "payment settled successfully" entry). Fix at the producers rather than at each log call, so future call sites inherit it: paymentPayloadSummary, facilitatorRejectDetail and the normalize note now return CR/LF-stripped strings, and truncateForLog sanitizes too (it carries the raw facilitator /verify body). Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v --- internal/x402/forwardauth.go | 18 ++++++++++++++---- internal/x402/forwardauth_test.go | 21 +++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/internal/x402/forwardauth.go b/internal/x402/forwardauth.go index 91fae55f..3c0b2097 100644 --- a/internal/x402/forwardauth.go +++ b/internal/x402/forwardauth.go @@ -477,9 +477,9 @@ func paymentPayloadSummary(payload x402types.PaymentPayload) string { } b, err := json.Marshal(summary) if err != nil { - return fmt.Sprintf("marshal_error=%v", err) + return sanitizeForLog(fmt.Sprintf("marshal_error=%v", err)) } - return string(b) + return sanitizeForLog(string(b)) } func samePaymentNetwork(a, b string) bool { @@ -545,7 +545,7 @@ func normalizePaymentPayloadForVerify(paymentPayloadJSON []byte) ([]byte, string if err != nil { return paymentPayloadJSON, "" } - return out, fmt.Sprintf("signature_v_%d_to_%d", vi, vi+27) + return out, sanitizeForLog(fmt.Sprintf("signature_v_%d_to_%d", vi, vi+27)) } func facilitatorRejectDetail(resp *facilitatorVerifyResponse) string { @@ -560,10 +560,20 @@ func facilitatorRejectDetail(resp *facilitatorVerifyResponse) string { out = append(out, p) } } - return strings.Join(out, " — ") + return sanitizeForLog(strings.Join(out, " — ")) +} + +// sanitizeForLog strips CR/LF from strings that originate outside the process +// — the buyer's payment payload or the facilitator's response — so a crafted +// value cannot forge extra lines in the operator's log. Applied at the +// producers rather than at each log call so future call sites inherit it. +func sanitizeForLog(s string) string { + s = strings.ReplaceAll(s, "\n", "") + return strings.ReplaceAll(s, "\r", "") } func truncateForLog(s string, max int) string { + s = sanitizeForLog(s) if max <= 0 || len(s) <= max { return s } diff --git a/internal/x402/forwardauth_test.go b/internal/x402/forwardauth_test.go index 00bbaa08..1de61f3e 100644 --- a/internal/x402/forwardauth_test.go +++ b/internal/x402/forwardauth_test.go @@ -1395,3 +1395,24 @@ func TestSettlementInterceptor_DeferredNonSSEStillEager(t *testing.T) { t.Fatalf("settleCalls = %d, want 1 on WriteHeader for non-SSE", settleCalls) } } + +// TestLogFieldsAreSingleLine guards the go/log-injection fix: strings that +// reach log.Printf from outside the process — the facilitator's reject reason +// and the buyer's payment payload — must not be able to forge a new log line. +func TestLogFieldsAreSingleLine(t *testing.T) { + forged := "bad\r\nx402: payment settled successfully" + + if got := facilitatorRejectDetail(&facilitatorVerifyResponse{InvalidReason: forged}); strings.ContainsAny(got, "\r\n") { + t.Errorf("facilitatorRejectDetail leaked a newline: %q", got) + } + if got := truncateForLog(forged, 500); strings.ContainsAny(got, "\r\n") { + t.Errorf("truncateForLog leaked a newline: %q", got) + } + summary := paymentPayloadSummary(x402types.PaymentPayload{ + Accepted: x402types.PaymentRequirements{Scheme: forged, Network: forged}, + Payload: map[string]any{"signature": forged}, + }) + if strings.ContainsAny(summary, "\r\n") { + t.Errorf("paymentPayloadSummary leaked a newline: %q", summary) + } +}