wallets

package
v1.801.256 Latest Latest
Warning

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

Go to latest
Published: Jul 27, 2026 License: Apache-2.0 Imports: 28 Imported by: 0

Documentation

Overview

Package wallets is the Hanzo Cloud accounts/wallets/custody/keys/sign surface (/v1/wallets/*): one configurable custody seam over three orthogonal signing backends, selected PER WALLET by its Kind.

TOPOLOGY (HIP-0106). Custody composes the two canonical Hanzo key services without fusing either into the hot binary:

  • KMS single-sig (KindKMS) is IN-PROCESS via the embedded luxfi/kms client (deps.KMS, a cloud.KMSClient). This is the fully-exercised spine: a real secp256k1 key is generated, its private bytes sealed under the KMS envelope, and every Sign recovers to the wallet address. No network hop.

  • MPC m-of-n (KindMPC) and treasury named-signer custody (KindTreasury) DELEGATE over HTTP to the DEPLOYED luxfi/mpc cluster (mpcclient.go), a thin typed REST client. cloud is a faithful CLIENT of the real service — it never imports github.com/luxfi/mpc (that drags chi/Postgres/HSM/ webauthn into the binary). Exactly the clients/mpc precedent. When the cluster is not configured these backends fail CLOSED (ErrMPCNotConfigured); a signature is NEVER fabricated.

The seam is the whole point: swapping a wallet's custody is a config value on one row, not a code path. custody.go owns the interface + the three backends; mpcclient.go owns the mpc wire; store.go owns persistence; wallets.go owns HTTP.

wallets.go owns the HTTP surface (/v1/wallets/*), the Mount/config seam that selects the custody set, the process singleton, and the finance seam.

POST /v1/wallets/accounts   {name}                              -> create account
GET  /v1/wallets/accounts                                       -> list MY accounts
POST /v1/wallets            {accountId,name,custody,tier,chain}  -> create wallet (Provision)
GET  /v1/wallets                                                -> list MY wallets
GET  /v1/wallets/:id                                            -> get one (404 if not my org)
POST /v1/wallets/:id/keys                                       -> rotate key material
POST /v1/wallets/:id/sign   {message?|digest?}                  -> sign (digest=hex 32B, else Keccak256(message))

Every handler derives the tenant through principal.Org (the ONE trust signal) and refuses with 403 when absent. Config selects the custody set: KMS is ALWAYS available (deps.KMS); MPC + treasury only when the cluster is wired (CLOUD_WALLETS_MPC_ADDR) and the JWT secret resolves from KMS — else those Kinds fail closed with ErrMPCNotConfigured.

Index

Constants

View Source
const DefaultTier = TierHot

DefaultTier is the tier a wallet gets when the request omits one.

Variables

View Source
var (
	ErrMPCNotConfigured = errors.New("mpc cluster not configured; set CLOUD_WALLETS_MPC_ADDR")
	ErrUnknownCustody   = errors.New("unknown custody kind")
)

Fail-closed sentinels. ErrMPCNotConfigured is returned whenever an MPC/treasury wallet is provisioned or signed without a configured ring + internal API key — the operator must wire CLOUD_WALLETS_MPC_ADDR (and the KMS API-key ref).

Functions

func Mount

func Mount(app cloud.Router, deps cloud.Deps) error

Mount wires the wallets surface onto app per HIP-0106. Complex flavour: it holds a package-global (mounted, the finance seam singleton) so it constructs the Service value directly rather than via cloud.Mount.

func Shutdown

func Shutdown() error

Shutdown closes the store. Idempotent.

func TreasuryAnchorSigner added in v1.786.131

func TreasuryAnchorSigner(ctx context.Context, org, chain string) (address string, sign func(context.Context, []byte) ([]byte, error), ok bool)

TreasuryAnchorSigner resolves-or-provisions the org's stable treasury MPC wallet and returns its EVM address plus a sign closure that produces an EVM-recoverable (r‖s‖v) signature delegating to the ring's threshold custody. ok=false when wallets is unmounted or treasury (ring) custody is not configured — the anchor then keeps its KMS-key signer (fail-safe, never a fabricated signer).

The wallet is a KindTreasury wallet under a reserved account+name, so repeated calls resolve the SAME wallet (idempotent) — its address is stable, so funding it for gas once is durable.

func WalletForLedgerAccount

func WalletForLedgerAccount(ctx context.Context, org, ledgerAccount string) (address string, ok bool)

WalletForLedgerAccount resolves the on-chain wallet bound to a finance ledger account — the seam by which the treasury reserve signer BECOMES an MPC treasury wallet later. Pure lookup; ("",false) when unmounted/unbound. Does NOT modify treasury.

Types

type Account

type Account struct {
	ID        string `json:"id"`
	Org       string `json:"org"`
	Name      string `json:"name"`
	CreatedAt int64  `json:"createdAt"`
}

Account is a named grouping of wallets owned by exactly one org.

type Custody

type Custody interface {
	Kind() Kind
	Provision(ctx context.Context, w *Wallet) (address string, err error)       // create signing material, set w.KeyRef, return address
	Sign(ctx context.Context, w *Wallet, digest []byte) (sig []byte, err error) // sign a 32-byte digest
	Rotate(ctx context.Context, w *Wallet) (address string, err error)          // roll key material, set w.KeyRef, return address
}

Custody is the ONE seam. Each backend creates signing material (Provision), signs a 32-byte digest (Sign), and rolls the material (Rotate). Provision and Rotate return the resulting pubkey ADDRESS and set w.KeyRef to the backend's handle for that material.

type Kind

type Kind string

Kind selects a wallet's custody backend. One interface, three backends.

const (
	KindKMS      Kind = "kms"      // in-process single-sig via the embedded luxfi/kms
	KindMPC      Kind = "mpc"      // m-of-n threshold via the deployed luxfi/mpc cluster (HTTP)
	KindTreasury Kind = "treasury" // named-signer governance via luxfi/mpc's treasury surface (HTTP)
	KindSafe     Kind = "safe"     // Safe (Gnosis-Safe-style) smart wallet on the ring, owned by an MPC EOA (HTTP: :9800 keygen + :8081 deploy/propose)
)

func (Kind) Valid

func (k Kind) Valid() bool

Valid reports whether k is one of the three supported custody kinds.

type PaymentTarget added in v1.800.1

type PaymentTarget struct {
	Address string
	Org     string
	Subject string // ledger subject for the earnings credit (the wallet id)
}

PaymentTarget is a wallet resolved for RECEIVING a payment: its on-chain address (the x402 payee) plus the org + ledger subject whose books the earnings credit.

func ResolvePaymentTarget added in v1.800.1

func ResolvePaymentTarget(ctx context.Context, org, walletID string) (PaymentTarget, bool)

ResolvePaymentTarget resolves a payout wallet {org, walletID} to its address + ledger subject — the seam the x402 settlement uses to route payment to a recipient wallet. The lookup is org-scoped (getWallet), so a resource can only ever name a wallet WITHIN the org it declared: no cross-org payee spoofing. ("", false) when wallets is unmounted or the wallet is not found in that org.

type SafeTx added in v1.786.131

type SafeTx struct {
	To      string `json:"to"`
	Value   string `json:"value"`
	Data    string `json:"data"`
	ChainID int64  `json:"chainId"`
	Nonce   int    `json:"nonce"`
}

SafeTx is the Safe transaction to propose (and MPC-sign the EIP-712 hash of).

type SafeTxResult added in v1.786.131

type SafeTxResult struct {
	SafeTxHash string `json:"safeTxHash"`
	R          string `json:"r"`
	S          string `json:"s"`
}

SafeTxResult is the outcome of a propose: the EIP-712 Safe-tx hash the ring computed and the threshold ECDSA signature (r,s) its MPC produced over it.

type Scope added in v1.800.1

type Scope struct {
	Org       string `json:"org"`
	Project   string `json:"project,omitempty"`
	Agent     string `json:"agent,omitempty"`
	AccountID string `json:"accountId"`
}

Scope is the ownership+addressing scope of custodied key material and the ONE key both the KMS secret ref (keyRef) and the store lookup derive from. Org is the tenant isolation boundary — always required, never crossed. Project, Agent, and AccountID are optional NARROWINGS within the org: a wallet may belong to the whole org (all narrowings empty), to an org project, to a specific agent, or to a named account grouping. One scope type, one derivation/lookup path — so a wallet's key material and its row are addressed identically at every layer.

type Tier

type Tier string

Tier mirrors luxfi/mpc's 9-tier wallet model (pkg/wallet/tier.go) as string constants so cloud never imports the mpc package. The values are the wire contract the cluster keys its TierPolicy on; a local mirror keeps them in ONE place here and refuses an unknown tier at the boundary.

const (
	TierHot           Tier = "hot"
	TierWarm          Tier = "warm"
	TierCold          Tier = "cold"
	TierGas           Tier = "gas"
	TierBridge        Tier = "bridge"
	TierContractAdmin Tier = "contract_admin"
	TierValidator     Tier = "validator"
	TierQuarantine    Tier = "quarantine"
	TierDR            Tier = "disaster_recovery"
)

type Wallet

type Wallet struct {
	ID             string `json:"id"`
	Scope                 // Org, Project, Agent, AccountID — the addressing scope
	Name           string `json:"name"`
	Custody        Kind   `json:"custody"`
	Tier           Tier   `json:"tier"`
	Chain          string `json:"chain"`
	Address        string `json:"address"`
	KeyRef         string `json:"-"` // custody-internal handle; never serialized
	FinanceAccount string `json:"financeAccount,omitempty"`
	CreatedAt      int64  `json:"createdAt"`
}

Wallet is one signing identity. Custody selects the backend; KeyRef is the custody-internal HANDLE to the signing material (a KMS secret ref, or the mpc wallet id) — set by Provision, never a private-key VALUE, and never returned over the API. It embeds Scope, so Org/Project/Agent/AccountID promote (and stay flat in JSON) and the wallet is addressed through the ONE scope type.

Jump to

Keyboard shortcuts

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