Wallet
The wallet subsystem provides signing backends (self-hosted and embedded) with a policy gate that enforces spend caps, contract allow-lists, and chain allow-lists before every signature.
Source files: internal/wallet/wallet.go, internal/wallet/embedded.go
The wallet subsystem provides signing backends with a policy gate. It supports two modes: self_hosted (operator holds the key locally) and embedded (delegates to the Paxeer embedded wallet over the agent-native DID lane). Every signature passes through a Gate that enforces spend caps, contract allow-lists, and chain allow-lists.
Design decisions
Two wallet modes
Self-hosted (self_hosted): Signs locally with an operator-held key. Three signer backends:
| Signer | Config key | Source |
|---|---|---|
raw | wallet.self_hosted.private_key | Raw hex private key (last resort; file must be 0600) |
env | wallet.self_hosted.private_key | Reads from env var reference |
keystore | wallet.self_hosted.keystore_path + keystore_password | Decrypts a geth v3 web3 secret-storage JSON |
Backwards-compatible env shims: TACHYON_DEV_PRIVATE_KEY or TACHYON_ALLOW_DEV_SIGNER=true selects self-hosted mode automatically.
Embedded (embedded): Delegates signing and broadcasting to the Paxeer embedded wallet over the agent-native DID lane. The daemon's ed25519 seed proves a did:matrix:<label>:<keyfp> identity; the EVM key stays server-side and the wallet enforces custody policy (frozen / read_only / spend caps / allow-lists).
Two operating modes within embedded:
- Single-tenant (keyfile set): loads the ed25519 seed, derives the agent DID, and authenticates with it. Used when one
tachyondinstance serves one agent. - Multi-tenant token-only (keyfile empty): holds no seed. Every signing request must carry a forwarded
TxIntent.AuthToken(the per-agent bearer minted upstream). This is the shared-engine deployment: many agents, zero seeds on the box.
Backwards-compatible env shims: PAXEER_WALLET_TOKEN or MATRIX_EXECUTOR_KEY selects embedded mode automatically.
Policy gate (Gate)
The Gate wraps a Signer and a map of named Policy profiles:
type Gate struct {
Signer Signer
Profiles map[string]Policy
}
type Policy struct {
Name string
SpendCapWei *big.Int
AllowedContracts []common.Address
AllowedChains []string
ChainID string
}
Authorize(token, requestCap, chainID) resolves a capability token to an effective policy:
- When profiles are configured, an unknown token is denied
- The request's spend cap can only tighten the profile's cap, never raise it
- Chain and contract allow-lists are enforced
Sign(ctx, client, intent, policy) validates the policy against the intent, then delegates to the signer.
Signer interface
type Signer interface {
Sign(ctx context.Context, client *evm.Client, intent TxIntent) (SignResult, error)
Address(ctx context.Context) (common.Address, error)
}
SignResult
Exactly one of RawTx / TxHash is populated:
RawTx: a locally signed transaction the caller must broadcast (self-hosted mode)TxHash: the signer already broadcast (remote send); caller waits for receipt (embedded mode)
Embedded wallet handshake
The embedded signer authenticates via ed25519 challenge/verify:
POST /v1/agent/auth/challenge {did} -> {message, nonce}
ed25519-sign(message)
POST /v1/agent/auth/verify {did, public_key, nonce, signature} -> {token}
POST /v1/agent/send {tx} (Bearer token) -> {tx_hash, address}
The token is cached and refreshed automatically on 401 responses.
TxIntent
type TxIntent struct {
From string
To string // "" => contract creation
Data []byte
Value *big.Int
Gas uint64 // 0 => estimate
AuthToken string // forwarded embedded-wallet bearer for multi-tenant
}
NewGate construction
func NewGate(cfg config.Config) (*Gate, error)
self_hostedmode: creates aLocalSignerfrom the wallet configembeddedmode: creates anEmbeddedSignerfrom the wallet config- Default (no mode): returns a nil-signer gate (read-only verbs still work; signing verbs return
WALLET_NOT_CONFIGURED)
Policy profiles are built from config.Policies (policy.* sections in tachyon.config.kvx).
Modifying the wallet
| What to change | Where |
|---|---|
| Add signer backend | New type implementing Signer interface |
| Add policy check | internal/wallet/wallet.go - validatePolicy |
| Change embedded handshake | internal/wallet/embedded.go - authenticate |
| Add new wallet mode | internal/config/config.go - WalletMode* constants + NewGate switch |
