mirror of
https://github.com/hanzoai/commerce.git
synced 2026-10-30 15:08:42 +00:00
An agent or a person signs one EIP-3009 authorization against its Circle Gateway balance, made out to the treasury, and is credited the moment Gateway locks it. No human, no subscription, cards untouched. GET /v1/billing/crypto/gateway terms + a nonce issued to the caller POST /v1/billing/crypto/gateway the signed payment GET /v1/billing/crypto/gateway/:id pending, accepted, settled, failed GET /v1/commerce/treasury superadmin: Gateway position payment/providers/circle replaces the Payments API v1 provider with a permissionless Gateway client and the GatewayWalletBatched EIP-712 encoding, proven against eth-account and Circle's testnet verifier. billing/gateway checks a payment before Gateway sees it (treasury, live network, whole cents in $1..$1,000, window, low-s signature from `from`, nonce bound to the caller), settles it through /v1/x402/settle, credits the host ledger keyed on the remittance, and reconciles by polling /v1/x402/transfers: Gateway's webhooks report no batched payment. Armed by CIRCLE_GATEWAY_TREASURY with CIRCLE_ENVIRONMENT; unset is inert.
68 lines
2.1 KiB
Go
68 lines
2.1 KiB
Go
// Copyright (c) 2014-present Hanzo AI, Inc.
|
|
// Licensed under MIT OR Apache-2.0. See LICENSE-MIT and LICENSE-APACHE.
|
|
|
|
package circle
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// The deployment arms the rail with two plain values and no secret: Gateway
|
|
// takes no key for anything the rail calls.
|
|
//
|
|
// CIRCLE_GATEWAY_TREASURY the address payments are made TO — Hanzo's wallet.
|
|
// Unset: the rail is shut and nothing below is read.
|
|
// CIRCLE_ENVIRONMENT "production" (mainnet) or "sandbox" (testnet).
|
|
// Required once the treasury is set: which money is
|
|
// real is never a default.
|
|
const (
|
|
EnvTreasury = "CIRCLE_GATEWAY_TREASURY"
|
|
EnvNetwork = "CIRCLE_ENVIRONMENT"
|
|
)
|
|
|
|
// Config is the rail as a deployment arms it.
|
|
type Config struct {
|
|
Network Network
|
|
Treasury string // 0x + 40 lowercase hex; empty when the rail is not armed
|
|
}
|
|
|
|
// Armed reports whether a treasury is configured. An unarmed rail offers
|
|
// nothing and accepts nothing.
|
|
func (c Config) Armed() bool { return c.Treasury != "" }
|
|
|
|
// FromEnv reads the rail's configuration from environ (as os.Environ returns
|
|
// it). No treasury is the inert state, not an error. A treasury with an
|
|
// unreadable address or no stated environment IS an error: an operator who
|
|
// armed the rail must not get a process that takes payments into an address
|
|
// it misread, or on a network it guessed.
|
|
func FromEnv(environ []string) (Config, error) {
|
|
env := map[string]string{}
|
|
for _, kv := range environ {
|
|
if k, v, ok := strings.Cut(kv, "="); ok {
|
|
env[k] = strings.TrimSpace(v)
|
|
}
|
|
}
|
|
raw := env[EnvTreasury]
|
|
if raw == "" {
|
|
return Config{}, nil
|
|
}
|
|
addr, err := Address(raw)
|
|
if err != nil {
|
|
return Config{}, fmt.Errorf("%s: %w", EnvTreasury, err)
|
|
}
|
|
if addr == ([20]byte{}) {
|
|
return Config{}, fmt.Errorf("%s is the zero address", EnvTreasury)
|
|
}
|
|
var n Network
|
|
switch env[EnvNetwork] {
|
|
case "production":
|
|
n = Mainnet
|
|
case "sandbox":
|
|
n = Testnet
|
|
default:
|
|
return Config{}, fmt.Errorf("%s is set, so %s must be \"production\" or \"sandbox\", not %q", EnvTreasury, EnvNetwork, env[EnvNetwork])
|
|
}
|
|
return Config{Network: n, Treasury: Hex(addr[:])}, nil
|
|
}
|