buy

package
v0.10.0-rc17 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 12, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package buy contains host-side helpers for `obol buy` commands.

Today this is primarily pre-flight logic for `obol buy inference`:

  • probe the seller's 402 pricing response
  • verify the seller's ERC-8004 registration document against the priced chain
  • reject budgets that would exceed the advertised one-request price floor

Signing, PurchaseRequest creation, facilitator interaction, and refill bookkeeping all live inside the agent pod's buy.py — see internal/embed/skills/buy-x402/scripts/buy.py.

Index

Constants

This section is empty.

Variables

View Source
var ErrNoAgent = errors.New("no agent instance available to query purchases")

ErrNoAgent is returned when no agent target is available to query the purchase list (e.g. `obol stack up` not yet run, or no agents created).

Functions

func BuyPyCommand

func BuyPyCommand(runtime agentruntime.Runtime, args ...string) []string

BuyPyCommand returns the in-pod argv prefix for the buy-x402 helper in the selected runtime. Hermes carries its own venv; OpenClaw exposes python3 on PATH and mounts skills under /data/.openclaw/skills.

func FetchSellerRegistration

func FetchSellerRegistration(ctx context.Context, sellerURL string) (*erc8004.AgentRegistration, error)

FetchSellerRegistration fetches and parses the seller's ERC-8004 registration document. sellerURL may be either the seller's base URL (e.g. "https://demo-seller.obol.tech") or a service URL (e.g. ".../services/foo"); the path component is replaced with the well-known path so callers can pass either shape.

func ValidateBudgetAgainstPricing

func ValidateBudgetAgainstPricing(budgetBase string, pricing *PricingResponse) error

ValidateBudgetAgainstPricing rejects budgets smaller than one request price. buy.py currently rounds `budget // price` up to at least one auth; the host CLI advertises --budget as a spending cap, so we fail early instead of silently overspending the requested cap.

Call ValidateTokenAgainstPricing first; this function assumes the token has already been validated against the seller's priced asset.

func ValidateTokenAgainstPricing

func ValidateTokenAgainstPricing(token string, pricing *PricingResponse) error

ValidateTokenAgainstPricing checks that the requested --token matches the seller's priced asset. When there is a mismatch it returns a structured error that names both the requested token and the seller's required asset, and suggests the correct --token value when the asset address is known.

Call this before ValidateBudgetAgainstPricing so the caller sees a clear token-mismatch error instead of a confusing budget-validation failure.

func VerifyAgentID

func VerifyAgentID(reg *erc8004.AgentRegistration, expected int64) error

VerifyAgentID returns nil iff at least one of reg.Registrations matches the expected ERC-8004 tokenId. The seller may publish multiple registrations (one per chain); a match on any of them is sufficient.

func VerifyAgentIDForPricing

func VerifyAgentIDForPricing(reg *erc8004.AgentRegistration, expected int64, pricing *PricingResponse) error

VerifyAgentIDForPricing pins the ERC-8004 identity check to the seller's priced network so a matching tokenId on an unrelated registry does not pass.

func VerifyAgentIDOnRegistry

func VerifyAgentIDOnRegistry(reg *erc8004.AgentRegistration, expected int64, expectedRegistry string) error

VerifyAgentIDOnRegistry requires the expected agentId to appear on the specific CAIP-10 registry identifier resolved from the seller's priced payment network.

func VerifySellerEndpoint

func VerifySellerEndpoint(reg *erc8004.AgentRegistration, sellerURL string) error

VerifySellerEndpoint ensures the requested seller URL is one of the service endpoints advertised in the seller's ERC-8004 registration document.

Types

type CatalogEntry

type CatalogEntry = schemas.ServiceCatalogEntry

CatalogEntry re-exports the wire schema so callers don't have to import internal/schemas just to read a /api/services.json response.

func FetchServiceCatalog

func FetchServiceCatalog(ctx context.Context, sellerURL string) ([]CatalogEntry, error)

FetchServiceCatalog fetches the seller's /api/services.json feed and returns the parsed catalog. sellerURL may be either the seller's base URL (storefront hostname) or any /services/<name>/... URL — the path component is replaced with /api/services.json.

func PickCatalogEntry

func PickCatalogEntry(entries []CatalogEntry, sellerURL string) (*CatalogEntry, error)

PickCatalogEntry picks the entry whose endpoint matches sellerURL. When sellerURL is a bare storefront base (no /services/<name>) and exactly one inference entry is advertised, that entry is returned. Returns a human-readable error listing the inference offers otherwise.

type PaymentOption

type PaymentOption struct {
	PayTo             string `json:"payTo"`
	Network           string `json:"network"`
	Asset             string `json:"asset"`
	Amount            string `json:"amount"`
	MaxAmountRequired string `json:"maxAmountRequired"`
}

PaymentOption is one entry from the seller's x402 accepts array.

type PricingResponse

type PricingResponse struct {
	X402Version int             `json:"x402Version"`
	Accepts     []PaymentOption `json:"accepts"`
}

PricingResponse is the subset of the seller's x402 402 body that the host pre-flight needs before dispatching into buy.py.

func FetchSellerPricing

func FetchSellerPricing(ctx context.Context, sellerURL, model string) (*PricingResponse, error)

FetchSellerPricing probes the seller's chat-completions endpoint and returns the parsed x402 402 body. sellerURL may be either the service root (`.../services/foo`) or the final chat-completions URL.

type PurchaseSummary

type PurchaseSummary struct {
	Name          string `json:"name"`
	Alias         string `json:"alias"`
	Model         string `json:"model"`
	Remaining     int    `json:"remaining"`
	Spent         int    `json:"spent"`
	TotalSigned   int    `json:"totalSigned"`
	Expired       int    `json:"expired"`
	Price         string `json:"price"` // per-request atomic amount
	Chain         string `json:"chain"`
	Endpoint      string `json:"endpoint"`
	AutoRefill    bool   `json:"autoRefill"`
	AssetSymbol   string `json:"assetSymbol"`
	AssetDecimals int    `json:"assetDecimals"`
}

PurchaseSummary is the host-side view of one paid-inference subscription the agent is funding. The fields mirror buy.py's `list --json` payload — rename in lockstep with internal/embed/skills/buy-x402/scripts/buy.py (cmd_list `as_json=True`) when changing either side.

func ListPurchases

func ListPurchases(cfg *config.Config, runtime agentruntime.Runtime, id string) ([]PurchaseSummary, error)

ListPurchases shells `buy.py list --json` inside the named agent pod and returns the parsed payload. Used by `obol model status` to surface pre-authorized budgets alongside the provider table.

Returns an empty slice + nil error when the agent has no purchases, so callers can render a "no paid models" message without distinguishing from the "buy.py failed" case.

type WalletInfo

type WalletInfo struct {
	Address     string   // 0x-prefixed signer address from buy.py.
	Token       string   // Upper-case symbol the caller asked for (USDC, OBOL).
	Chain       string   // Canonical chain name the balance was read on.
	AtomicUnits *big.Int // Raw on-chain balance, scaled by the token's decimals.
	Decimals    int      // Decimals for the token, for display formatting.
}

WalletInfo is the host-side view of an agent's remote-signer wallet on a given chain, including the atomic balance for one token.

func FetchWalletInfo

func FetchWalletInfo(cfg *config.Config, runtime agentruntime.Runtime, id, token, chain string) (*WalletInfo, error)

FetchWalletInfo execs `buy.py balance --chain <chain>` inside the named agent pod and parses the output to recover the signer address and the balance for `token` (e.g. "USDC" or "OBOL").

We shell out instead of re-implementing the signer + eRPC RPC walk in Go because the agent pod already has SA credentials, the signer URL, and the eRPC alias resolution wired up via the buy-x402 skill. Re-deriving all three from the host would be a meaningful surface area expansion for what is a single confirmation-line preflight.

func (WalletInfo) HumanBalance

func (w WalletInfo) HumanBalance() string

HumanBalance returns the balance with the minimum fractional digits needed (whole numbers render as "12", dust as "0.001"). Matches the host-side summary formatter (cmd/obol/buy.go::formatTokenAmount) so balance lines and budget lines look consistent.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL