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
- Variables
- func Mount(app cloud.Router, deps cloud.Deps) error
- func Shutdown() error
- func TreasuryAnchorSigner(ctx context.Context, org, chain string) (address string, sign func(context.Context, []byte) ([]byte, error), ok bool)
- func WalletForLedgerAccount(ctx context.Context, org, ledgerAccount string) (address string, ok bool)
- type Account
- type Custody
- type Kind
- type PaymentTarget
- type SafeTx
- type SafeTxResult
- type Scope
- type Tier
- type Wallet
Constants ¶
const DefaultTier = TierHot
DefaultTier is the tier a wallet gets when the request omits one.
Variables ¶
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 ¶
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 TreasuryAnchorSigner ¶
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) )
type PaymentTarget ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
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.