LIMEN
TESTNET

Reference

The tables you come back for. Every error code here is read from @limen/chain, and every hash from the deployments file — none of it is transcribed into this page.

TESTNET ONLYCOMPOSITION ONLY

Contract error codes

Decoded from a failed transaction’s own diagnostic events. The boundary column is the distinction the whole product turns on: a boundary refusal is evidence the rule did its job, whereas a missing rule or a malformed call is not. ContextRuleNotFound is deliberately excluded — “the boundary refused you” and “the boundary is gone” are different claims.

CodeNameBoundary
3000ContextRuleNotFound
smart account
no rule
3002UnvalidatedContext
smart account
refusal
3003ExternalVerificationFailed
smart account
—
3004NoSignersAndPolicies
smart account
—
3005PastValidUntil
smart account
—
3006SignerNotFound
smart account
—
3007DuplicateSigner
smart account
—
3008PolicyNotFound
smart account
—
3009DuplicatePolicy
smart account
—
3010TooManySigners
smart account
—
3011TooManyPolicies
smart account
—
3012MathOverflow
smart account
—
3013KeyDataTooLarge
smart account
—
3014ContextRuleIdsLengthMismatch
smart account
refusal
3015NameTooLong
smart account
—
3016UnauthorizedSigner
smart account
refusal
3220SmartAccountNotInstalled
spending limit
—
3221SpendingLimitExceeded
spending limit
refusal
3222InvalidLimitOrPeriod
spending limit
—
3223NotAllowed
spending limit
refusal
3224HistoryCapacityExceeded
spending limit
—
3225AlreadyInstalled
spending limit
—
3226LessThanZero
spending limit
—
3227OnlyCallContractAllowed
spending limit
—

Policy primitives

The closed set of things a derived boundary can be lowered into. Both are configurations of existing audited code — adding a third means deploying a third audited primitive, not generating one.

KindConfiguresRefuses
spending_limit
OpenZeppelin spending-limit policy
asset, limit, windowLedgersan outflow above the cap within the window
function_allowlist
context rule scoping
contractId, functionsany function on that contract outside the list

The six deny axes

Each axis changes exactly one dimension of the permitted flow. These are the attempts made against a real rule on a real account; every one reached a ledger and failed there.

VerdictAxisAttemptSimulated / on ledgerHash
DENYamounttransfer above the cap
SpendingLimitExceeded#3221
SpendingLimitExceeded#3221
ac477549…7a77
DENYfunctionapprove on the permitted token contract
NotAllowed#3223
NotAllowed#3223
45a0eb20…1f58
DENYassettransfer of a different token
UnvalidatedContext#3002
UnvalidatedContext#3002
1312be89…4880
DENYcontractexecute on the smart account itself
UnvalidatedContext#3002
UnvalidatedContext#3002
6b7f4ded…9389
DENYinvocationappended second invocation
ContextRuleIdsLengthMismatch#3014
ContextRuleIdsLengthMismatch#3014
e365e681…7149
DENYexpirysame call after valid_until
UnvalidatedContext#3002
trapped; contract code not recovered from diagnostics
f5ebce51…8405

Both columns are shown because they are different claims. The simulation error is what the RPC predicted; the ledger error is what the network actually returned, decoded from diagnostic events. They agree on five of six — the sixth reached a ledger and failed there, but its contract code was not recovered from that run’s diagnostics, and the recording says so rather than filling it in.

Environment variables

None of these is required to read the site or the documentation, and most have a fallback or a route that declines cleanly in their absence — a missing variable never degrades into a screen that silently shows something wrong. Two are exceptions, and both refuse rather than degrade on the production deployment: the shared store, because the fallback there would be a rate limit counted per instance rather than in total; and the passkey relying party, because the fallback would be trusting the caller’s own headers to say which site an assertion came from. Elsewhere both fall back and say so.

VariableScopePurpose
NEXT_PUBLIC_STELLAR_RPC_URLbrowserSoroban RPC endpoint for reads. Falls back to the public testnet endpoint.
SOROBAN_RPC_URLserverServer-side RPC endpoint, used by the routes that keep the SDK out of the browser bundle.
NEXT_PUBLIC_SMART_ACCOUNT_IDbrowserThe account the app screens open on when no other is chosen.
NEXT_PUBLIC_SITE_URLbrowserAbsolute origin for share-card URLs. Vercel supplies one automatically in production.
VERCEL_PROJECT_PRODUCTION_URLplatformSupplied by Vercel, not set by hand. Used as the share-card origin when NEXT_PUBLIC_SITE_URL is absent.
ANTHROPIC_API_KEYserverEnables the explain endpoint. Absent, that route declines rather than degrading silently.
LIMEN_DEMO_SECRETserverA disposable testnet key used only by the scripted demo transfer. Never a user key.
LIMEN_DEMO_DESTINATIONserverWhere the scripted demo transfer sends its testnet dust.
LIMEN_SIMULATION_SOURCEserverSource account used for read-only simulation, which costs no fee and needs no signature.
WAITLIST_STORE_PATHserverWhere waitlist entries are written. Defaults to a file in the system temp directory.
LIMEN_ERROR_WEBHOOKserverWhere error reports are delivered. Server-side only, because a webhook URL is a credential. Set for preview deployments as well as production; preview reports are prefixed with their environment so they do not read as production incidents. Unset, a report is logged and not sent.
VERCEL_ENVserverWhich deployment this is — production, preview or development. Read for two things: labelling an error report, so a preview experiment does not read as a production incident in the same channel; and deciding whether a missing shared store is a refusal or a fallback, since Vercel sets NODE_ENV=production for previews too and it therefore cannot answer that question. Set by the platform, not by hand; absent, the label is omitted rather than guessed.
UPSTASH_REDIS_REST_URLserverThe shared store behind the rate limits and the transaction cache, over HTTP because a serverless function has no connection to pool. This is the one entry in this table the production deployment will not start without: falling back to per-instance counters there would enforce a limit per instance rather than in total, which is the behaviour V8 M1 set out to retire. On a preview or in development the fallback is allowed and is logged to stderr.
UPSTASH_REDIS_REST_TOKENserverCredential for the above. Both are needed together — either one alone counts as unset, because half a configuration is not a store.
DATABASE_URLserverPostgres, holding users and sessions as of V8 M1. Reached over neon-http from the web app, which sends each query as an HTTP request and therefore has no connection to exhaust across many function instances — and, as the cost of that, cannot run interactive transactions. There is no fallback: a route that needs the database refuses and names this variable, because a session that does not survive the next request is not a session. Migrations use the direct, unpooled endpoint rather than this one.
LIMEN_RUNTIME_URLserverWhere apps/runtime is reachable. The web chat accepts a message, asks the model which tool it wants, and hands that tool to the runtime — which owns execution because a turn takes 15–45 seconds and can move money, and a payment in flight inside a request handler is a payment that disappears when the handler does. There is deliberately no localhost fallback: a default would make a misconfigured deployment fail by quietly trying to reach a runtime that is not there, surfacing as a timeout on the money path rather than as the configuration error it is. Unset is reported as unset, and the chat says so instead of reporting a failed turn.
LIMEN_WEBAUTHN_RP_IDserverThe relying party a passkey is bound to — a registrable domain such as limen.app, never an origin. Required on the production deployment, which refuses to start without it: the alternative is deriving the expected origin from the request, and the Origin and Host headers are supplied by the caller, so that check would accept a replayed assertion from any site while looking exactly like a working login. Outside production it defaults to localhost so the ceremony still runs.
LIMEN_WEBAUTHN_ORIGINSserverComma-separated list of origins an assertion may come from, matched exactly and never by prefix. Checked because the on-chain verifier validates neither origin nor rpIdHash, so a valid assertion proves which credential signed but not which site asked.
VERCEL_URLplatformThis deployment's own hostname, set by the platform and not by hand. Added to the accepted passkey origins so a preview can run the login ceremony without its URL being configured per branch. Safe in a way a request header is not, because it comes from the deployment's environment rather than the caller — though a passkey registered on production still will not work on a preview, since the two are different relying parties.
NODE_ENVplatformSet by the toolchain, never by hand. Read only as the fallback answer to "is this production" where the platform does not say — a self-hosted container, which has no preview concept. On Vercel it is production for preview builds too, which is why VERCEL_ENV is preferred over it.
NEXT_PUBLIC_LIMEN_RELEASEbrowserThe short commit SHA an error report names as its build. Derived from the platform at build time, not set by hand.

No secret key appears in any of these except LIMEN_DEMO_SECRET, which is a disposable testnet key used only by the scripted demo transfer. No user key is ever read from the environment, because no user key ever leaves the browser that generated it.