Kalman
Engine
Documentation menu

Wallet & custody

Two wallets, one of which we hold. This page describes how that key is protected, what the system is structurally incapable of doing with it, and what you are still trusting us on.

Two wallets

Your login wallet is the one in your browser extension. Kalman never sees its private key and never asks for it. What we see is its public address and the signatures it produces when you choose to sign something. It is used for exactly three things: proving who you are at sign-in, proving you are present when you export a key, and receiving withdrawals.

Your trading wallet is a separate Solana keypair that Kalman generates for you the first time you sign in. Its private key is held on our infrastructure, encrypted. One trading wallet per login wallet, permanently paired.

That second wallet is custodial. Not "non-custodial with extra steps" — custodial. A key we hold is a key you are trusting us with, and no amount of encryption architecture changes that fact. What the design can do, and what the rest of this page is about, is narrow the number of things that have to go right and give you a way out. The way out is key export.

How the key is stored

Envelope encryption, two layers. Each user gets their own randomly generated data key, created at the moment their wallet is provisioned and never shared with anyone else. That data key encrypts the private key with AES-256-GCM — authenticated encryption, so a tampered ciphertext fails to decrypt rather than decrypting to something wrong. The data key is itself encrypted, under a single master key, with the same algorithm.

The master key exists only in the server environment of the signing process. It is not in the repository, not in the database, and not readable by the web API or by any other service. A copy of the database, on its own, decrypts nothing.

The signer

Exactly one process ever decrypts a private key. It runs as its own operating-system account, separate from the API, and it is not reachable over the network — the API talks to it through a local channel on the same machine, and the signer checks the identity of whatever connects to it using credentials the kernel supplies rather than anything the caller can assert. An unrecognised caller is refused. If the check itself cannot be performed, the connection is refused rather than allowed — it fails closed.

The signer accepts four requests and nothing else:

  • Create a wallet for a user and return its public address.
  • Read back an existing wallet's public address.
  • Sign a withdrawal.
  • Export a private key.

There is no "sign this arbitrary transaction" request. For a withdrawal, the signer builds the transaction itself from the asset, the amount and the destination — it never accepts pre-built transaction bytes from a caller, so a compromised caller cannot smuggle in a transaction that does something else. A decrypted key exists only for the duration of the operation that needs it, is never cached, and is never written to a log.

Withdrawals are pinned

The destination of a withdrawal is your login wallet — the address you first signed in with — and there is no way to change it. This is enforced in three places rather than one:

  • There is no destination field in a withdrawal request. A client that invents one is rejected outright rather than having it quietly ignored.
  • The destination is recorded when your account is created, at first sign-in, and there is no address-change flow anywhere in the product.
  • The signer independently looks up your login wallet from its own store and refuses to sign if the destination it was handed does not match. Comparing a caller-supplied destination against a caller-supplied expectation would prove nothing, so it does not do that.

Beyond the destination, withdrawals carry a per-asset cap over a rolling 24-hour window — a rolling window rather than a calendar day, because a calendar reset is a known moment an attacker can simply wait for. The cap counts every withdrawal that has not failed, including ones still in flight. Its purpose is specific: it bounds what a single compromised session could move before you notice. Every withdrawal is also simulated against the chain before it is broadcast, and a failed simulation means nothing is sent.

Key export

You can take the key and leave. Export requires two things at once, not one.

  • A valid session — you are signed in.
  • A fresh signature from the login wallet, made at the moment you ask. The message you sign carries a deliberately different prefix from the sign-in message, so a signature captured from a login can never be replayed as an export, and vice versa. The challenge is single-use.

Exports are rate-limited, and the limit is enforced on the server's own clock rather than any timestamp a caller supplies — a limit measured by a clock the caller controls is not a limit. Every attempt is recorded before the signer is touched, and the outcome is recorded before a response is returned; if the audit write fails, the export does not proceed. An attempt with no matching outcome is treated as a signal to investigate rather than as noise. Failed signature checks are logged too, so a stolen session hammering forged signatures is visible rather than silent.

Once the key is on your screen it is yours and it is out of our hands. Anyone holding it controls that wallet completely. Kalman cannot recover it, cannot revoke it, and cannot reverse a transfer signed with it. The modal clears the clipboard sixty seconds after a copy, but that is best effort — a browser can refuse a clipboard write once the page loses focus, and clipboard history features on some systems keep a copy anyway. Treat an exported key the way you would treat a seed phrase.

What cannot happen

  • Keys are never logged, printed, or transmitted. The only path by which a private key leaves the system is the export flow, to you, after you have signed for it.
  • Funds cannot be sent to an arbitrary address. There is no code path, and no support or operator action, that sends funds anywhere but your login wallet — the only outbound transaction the signer knows how to build is that one. This is a statement about the signer's operation surface; the residual-risk section below is where its limits are.
  • The database alone is not enough. Encrypted keys without the master key are inert.
  • Your login wallet is never at risk from us. We hold no key for it and can produce no signature on its behalf. Signing in grants no token approval and no spending authority over anything in it.

What you are still trusting

Listing the guarantees without listing their limits would be marketing. These are the residual risks, as we understand them.

It is custody

A sufficiently deep compromise of the machine that runs the signer — one that reaches both the encrypted material and the master key in that process's environment — exposes keys. The architecture raises the bar and narrows the blast radius; it does not remove the category. Exporting your key and moving the funds to a wallet you control is the only action that removes the trust entirely, and it is deliberately available to you at any time.

Sessions last 24 hours

Long enough that you are not re-signing constantly, which would train you to click through signature prompts without reading them — a worse outcome than the one it prevents. The cost is honest: a stolen session cookie stays valid until it expires. Signing out invalidates every session for your account at once, not just the one in that browser, and the withdrawal cap is what bounds the damage in the meantime.

The signer's guarantee is scoped

What the signer enforces is process isolation, a fixed set of operations, and the withdrawal destination. Authenticating you — session validity, the export step-up signature, rate limits on the request path — is the API's job, and those are separate defences that have to hold on their own merits.

We do not sponsor gas

Your trading wallet pays for its own transactions, including your withdrawals. Keep at least 0.02 SOL in it or those actions cannot be sent. This is deliberate: sponsored gas under an open sign-up is an unlimited faucet.