Software is starting to move company money on its own. Afterward, nobody can prove that a specific payment was authorized under specific terms. Contextio is an AI agent that moves treasury and payroll funds for companies in Brazil, Argentina, and Colombia, and it binds every action it takes to a verifiable Legal Context Protocol (LCP) document, so that proof exists. Treasury settles in digital dollars and XLM on Stellar, earns yield through DeFindex and Blend, and off-ramps through anchors and local rails (PIX, Transferencias 3.0, Bre-B). Users sign in with a Stellar wallet (Sign In With Stellar, SEP-53), no passwords.
Contextio started as a submission for the Stellar PULSO Hackathon. It is now a working project aimed at the SCF Integration Track and real production use. It runs live on both Stellar testnet and mainnet. Testnet runs the full autonomous stack: a 24/7 agent that rebalances treasury and lends idle cash on its own. Mainnet is narrower on purpose. It serves read-only data (real on-chain prices and real activity) and self-custody actions, nothing else. Every mainnet transaction is built as an unsigned XDR and signed by the tenant's own wallet. Contextio holds no key that could move mainnet funds. A boot guard enforces that: the process will not start if a signer secret is present. See Mainnet below for detail. Mainnet access is invitation-only while the Soroban contracts wait for external audit. All code in this repository is original.
Testnet runs full autonomy. Everything below settles for real.
| Surface | URL / ID |
|---|---|
| Web app | https://www.contextio.xyz (Testnet is the default network) |
| API (Fly.io, region GRU) | https://contextio-api.fly.dev |
| Autonomous agent (Fly.io, GRU) | app contextio-agent. Rebalances and lends 24/7, no human in the loop. |
| Treasury contract | CASGAQ...MXPTA7I |
| Payroll contract | CDXML4...OIRCT |
| Smart Account (policy-gated agent signer) | account CAMEOVP...46DCID, spending-limit policy CDDF3B2...7K45AL. The live agent's Blend supply and withdraw are authorized through this, not a raw hot-key signature. |
| DeFindex vault (real yield) | CDR4WR...P4UJGTHS |
| Reflector price oracle (SEP-40) | CCYOZJ...OMJRN63, live at /api/v1/public/oracle |
| Self-hosted Anchor Platform (SEP-1/10/31/38) | app contextio-anchor-platform. Real protocol capability, deliberately never connected to a bank account (see Mainnet for why). |
| Client SDK (npm) | contextio-sdk, npm i contextio-sdk. Version 0.3.0, published 2026-08-06 with the real Level-4 LCP schema. Types match what the API actually returns. |
| Demo video | https://youtu.be/JI7KpNQMo0A |
| Pitch deck | https://www.contextio.xyz/pitch |
Connect Freighter (network toggle defaults to Testnet) at the web app, sign the message, then explore live treasury, agent, and payroll data.
Mainnet is live and real, and deliberately narrow.
| Surface | URL / ID |
|---|---|
| Web app | https://www.contextio.xyz. Switch the network pill in the navbar to Mainnet. |
| API (Fly.io, region GRU) | https://contextio-api-mainnet.fly.dev |
| Reflector price oracle (SEP-40, real XLM/USD) | CAFJZQ...P34DLN, live at /api/v1/public/oracle |
| Legal Context Protocol document | same canonical document as testnet, below. One shared source of truth per tenant domain, not a separate copy. |
| Treasury / Payroll Soroban contracts | not deployed on mainnet. Self-custody there settles through classic Stellar operations, or calls into Blend's own audited contracts, never Contextio's unaudited ones. See Mainnet above. |
| Autonomous agent | not running on mainnet. GET /api/v1/public/activity on the mainnet API returns agentAddress: null. Verify it yourself; no autonomous hot-key process exists there. |
| Access | invitation-only, gated by an explicit tenant allowlist, while the contracts wait for external audit. |
GET https://contextio-api-mainnet.fly.dev/readyz reports live health.
GET .../api/v1/public/activity is the same honest, sanitized activity feed
as testnet's. Check it yourself; don't take the table above on faith.
contextio/
├─ apps/
│ ├─ web/ Next.js frontend: wallet sign-in, live API data, Supabase Realtime
│ ├─ api/ Express/TS backend (Fly.io): wallet auth, treasury, payroll, LCP, integrations
│ └─ worker/ Agent loop, scheduled payroll (Fly.io)
├─ packages/
│ ├─ shared/ domain types, logger, Supabase and Stellar clients, LCP module
│ ├─ config/ type-safe env (zod), RBAC capability matrix, security settings
│ ├─ tests/ fixtures, DeFindex/Blend mocks (built dep used by the API)
│ └─ sdk/ public client SDK published to npm as contextio-sdk
├─ contracts/
│ ├─ treasury/ Soroban (Rust): LCP-bound treasury flows
│ └─ payroll/ Soroban (Rust): idempotent, LCP-bound payroll runs
├─ supabase/ Postgres schema (migrations), RLS, seed, full_setup.sql
└─ .github/ CI (Node, Soroban, E2E)
The worker has no privileged path of its own. It acts through the API with
x-internal-secret auth, so legal-context enforcement, RBAC, and audit
logging apply identically to the agent and to human operators.
Company Treasury (USDC, XLM, RWA)
│ custody stays with the company
▼
Stellar wallet sign-in (SEP-53) ─▶ API (Fly.io) ─▶ Soroban contracts ─▶ DeFindex / Blend / Anchors
AI Agents ──▶ API / Worker ────────────┤ │
├── Supabase (accounts, payroll, audit, LCP refs) + Realtime
└── Legal Context Protocol binding on every agentic tx
See docs/ARCHITECTURE.md and the Overview page diagram.
No email or password. Users connect a Stellar wallet (Freighter, xBull, Albedo, Lobstr, and others, through Stellar Wallets Kit) and sign a server challenge:
POST /api/v1/auth/wallet/challengereturns a stateless, HMAC-signed, time-boxed message.- The wallet signs it (SEP-53 message signing).
POST /api/v1/auth/wallet/verifyverifies the ed25519 signature overSHA256("Stellar Signed Message:\n" + message), maps the address to a user, and mints a session JWT (HS256).
That single JWT authenticates the API and authorizes Supabase Realtime/RLS
(auth.uid() = sub). The auth middleware also accepts asymmetric Supabase
JWTs via JWKS (ES256) for forward compatibility.
Each tenant publishes a machine-readable terms document at
https://{tenant-domain}/.well-known/contextio-legal-context.json (terms,
jurisdiction, consent requirements, dispute channels, authorized settlement
networks/assets). The platform computes a canonical SHA-256 of that document
and binds the hash into every agentic Stellar transaction (treasury flow /
payroll run) and audit record. This makes the legal basis of an action
independently verifiable. Implementation:
packages/shared/src/lcp. The contextio-sdk
lets any client re-derive and verify that hash with byte-for-byte parity.
The document is field-conformant with the independent
Legal Context Protocol standard (AAA and
Integra Ledger, with SDF as a founding contributor). terms is a real,
standalone markdown file, not a rendered page, which the spec disallows.
atrHash is a real SHA-256 hash of that file's actual bytes. disputeResolution
names a real arbitration venue for each jurisdiction: Brazil, Argentina,
Colombia, and Switzerland as a neutral arbitration seat. See
/legal/disputes/{br,ar,co,ch}.
Verify it yourself. No trust required, three commands:
# 1. Fetch the live discovery document and read the declared hash + version
curl -s "https://contextio-api.fly.dev/.well-known/contextio-legal-context.json?domain=contextio.xyz" \
| python3 -c 'import json,sys; d=json.load(sys.stdin); print(d["atrHash"], "v"+str(d["version"]))'
# 2. Fetch the terms document it points to, independently
curl -s "https://contextio-api.fly.dev/.well-known/contextio-terms.md" -o terms.md
# 3. Hash it yourself and compare to the atrHash from step 1
sha256sum terms.mdAs of this writing, that's
atrHash: 0x579eeabf5f640d9cbb274824287fa7d1ef540ae9cc773e79f6d2cec913b4237e,
document version 8. The sha256sum above should produce the same value,
without the 0x prefix. The same document and hash are served from both the
testnet and mainnet APIs. It is one canonical source of truth per tenant
domain, not a separate copy per network.
npm i contextio-sdkimport { ContextioClient, signInWithStellar, hashLegalContext } from "contextio-sdk";
import { StellarWalletsKit, Networks, defaultModules } from "@creit.tech/stellar-wallets-kit";
const client = new ContextioClient({ baseUrl: "https://contextio-api.fly.dev" });
StellarWalletsKit.init({ network: Networks.TESTNET, modules: defaultModules() });
const { address } = await StellarWalletsKit.authModal();
const session = await signInWithStellar({
client, address,
signMessage: async (m) => (await StellarWalletsKit.signMessage(m, { address })).signedMessage,
});
const api = client.withSession(session);
const treasury = await api.treasury();Isomorphic (browser + Node >= 18), ESM, zero server deps. Source:
packages/sdk.
Prerequisites: Node >= 20, pnpm >= 10, Rust (stable + wasm32-unknown-unknown),
and optionally the Supabase CLI and Stellar CLI.
pnpm install
# Build workspace packages first (apps depend on their d.ts):
pnpm --filter @contextio/config build
pnpm --filter @contextio/shared build
pnpm --filter @contextio/tests build
cp .env.example .env.local # fill in values (works in mock mode with blanks)apps/api and apps/worker dev scripts auto-load the repo-root .env.local
(tsx --env-file-if-exists). apps/web (Next.js) loads apps/web/.env.local,
public NEXT_PUBLIC_* variables only.
pnpm dev:web # http://localhost:3000 (connect a wallet, or browse demo data)
pnpm dev:api # http://localhost:8080 (REST API; mock integrations if keys unset)
pnpm dev:worker # agent loop (needs the API running)With no DeFindex/Blend credentials, those integrations run in deterministic mock mode, so the full stack works offline.
- Create a project (or
supabase startlocally). - Put
SUPABASE_URL,SUPABASE_ANON_KEY,SUPABASE_SERVICE_ROLE_KEY,SUPABASE_JWT_SECRETin.env.local. The JWT secret is the project's Legacy JWT Secret, used to verify and mint HS256 session tokens. - Apply schema and seed (migrations
0001to0004, RLS, seed):supabase db reset # local # hosted: run supabase/full_setup.sql against the project (pooler connection)
See supabase/README.md.
cargo test --all # unit tests (fast, native)
cargo build --release --target wasm32-unknown-unknown
# deploy + wire contract ids: see contracts/README.mdAlready deployed on testnet; IDs are in the Live table above. Set
TREASURY_CONTRACT_ID, PAYROLL_CONTRACT_ID, and STELLAR_SERVICE_SECRET
to settle on-chain instead of in simulation.
API on Fly.io (region GRU, single machine):
fly apps create contextio-api
fly secrets set SUPABASE_URL=... SUPABASE_ANON_KEY=... SUPABASE_SERVICE_ROLE_KEY=... \
SUPABASE_JWT_SECRET=... INTERNAL_API_SECRET=... STELLAR_SERVICE_SECRET=... \
TREASURY_CONTRACT_ID=... PAYROLL_CONTRACT_ID=... CORS_ORIGINS=https://<your-web>.vercel.app
fly deploy --config apps/api/fly.toml --dockerfile apps/api/Dockerfile --ha=falseRun fly deploy from the repo root so workspace packages resolve.
Web on Vercel (standalone from apps/web):
vercel deploy --prod --cwd apps/web --yes
# set NEXT_PUBLIC_API_URL / NEXT_PUBLIC_SUPABASE_URL / NEXT_PUBLIC_SUPABASE_ANON_KEY
# / NEXT_PUBLIC_STELLAR_NETWORK on the Vercel projectAfter deploying the web, add its domain to the API's CORS_ORIGINS.
API on Fly.io, mainnet variant. Do not copy the testnet command above. A
mainnet deployment must set STELLAR_NETWORK=mainnet. It must not set
STELLAR_SERVICE_SECRET, BLEND_SIGNER_SECRET, TREASURY_CONTRACT_ID, or
PAYROLL_CONTRACT_ID at all. assertMainnetHasNoHotKey
(packages/config/src/env.ts) refuses to boot otherwise, on purpose. Only
MAINNET_ALLOWLIST_TENANT_IDS and PUBLIC_ACTIVITY_TENANT_ID are
mainnet-specific additions on top of the shared secrets above.
pnpm lint # eslint across all TS packages + next lint
pnpm typecheck # tsc --noEmit across packages
pnpm test # vitest (config, shared, api, worker, sdk): 38 tests
pnpm test:e2e # Playwright E2E (web)
cargo test --all # Soroban contract unit tests: 16What's covered: LCP hashing/binding and money math (shared), env validation (config), HTTP/auth/RBAC, agent planning, and DeFindex/Blend integration (api), scheduler (worker), SDK LCP parity (sdk), key UI flows (web E2E), and contract logic including idempotency, auth, and balance invariants (Soroban). CI mirrors these in .github/workflows/ci.yml.
- Non-custodial. Keys stay with the company. On testnet, the agent invokes authorized contract methods through a policy-gated Smart Account, with spending-limit caps per target contract, verified with real transactions.
- Mainnet cannot hold a signing key.
assertMainnetHasNoHotKeyrefuses to boot any process withSTELLAR_NETWORK=mainnetif a signer secret is present. This is tested, and independently confirmed against the real deployed secrets: none are present. Every mainnet action is self-custody by construction. - Wallet auth. Sign In With Stellar (SEP-53), verified server-side.
Sessions are short-lived JWTs. The API also supports Supabase JWKS (ES256)
verification. On mainnet, new wallets get a clean
401instead of any auto-provisioning, verified live with a freshly generated, correctly signed random wallet. - RBAC. Declarative capability matrix in
@contextio/config, enforced at the API. - Mainnet access control.
MAINNET_ALLOWLIST_TENANT_IDSgates every self-custody endpoint to a real, explicit tenant allowlist, one internal tenant today. This is an account-level control, independent of network location. - Mainnet geo-restriction. Self-custody actions are unavailable from the
EU/EEA, US, and China by default. This is a blocklist of well-documented
regimes, not a closed-until-proven-safe allowlist. It is enforced both
technically, in
apps/web/src/middleware.ts, and contractually, with a real per-action jurisdiction-attestation checkbox inapps/api/src/http/schemas.ts. - Input validation. Zod on every state-changing endpoint.
- Hardening. Helmet and explicit headers, CORS allowlist, per-route rate limits, body-size cap. Secrets are redacted in logs.
- LCP gate. Agentic operations return HTTP 412 unless a legal context is published and the action's required consents are satisfied. See Verify it yourself above for the real hash.
- RLS. Browser and anon Supabase access is tenant-scoped. The service-role key is server-only. Realtime subscriptions are RLS-authorized with the session JWT.
See docs/SCF-INTEGRATION.md and apps/api/README.md for the API surface. See docs/PRIOR-ART.md for how Contextio compares to other funded Stellar projects in payroll and treasury automation: named competitors, verified against their own sites, with an honest account of which differences are architectural and which are just naming.
Every claim below has a command or a link next to it. Check it yourself rather than taking the bullet on faith.
- Autonomous agent, live 24/7.
contextio-agenton Fly rebalances treasury and lends idle cash on its own, within owner-signed rules.GET /api/v1/public/activityreturns its real Stellar address and a rolling window of its real decisions. - Real Blend lending, self-custody by policy. Idle USDC supplied to the
Blend pool (
PoolContractV2) is authorized through the Smart Account (CAMEOVP...46DCIDabove). It discovers however many Soroban auth contexts a call produces and gates each one against a spending-limit policy. Verified with real, confirmed Supply and Withdraw transactions. - Real DeFindex vaults. XLM yield vaults. Users deploy their own through the DeFindex factory, signed in Freighter for self-custody.
- Real on-chain price oracle, Reflector. The treasury values XLM, and
EURC where held, in USD from Reflector's SEP-40 oracle on Stellar, read
through Soroban simulation, not a hardcoded rate. Live proof:
GET /api/v1/public/oracle. - Real USDC payroll. Salaries settle to employee wallets in BR/AR/CO in
a Horizon batch transaction, testnet demo-scaled 1:100. Verified example:
4bd1b927...bc12cf78. - Self-custody, "Move capital." Builds an unsigned transaction. The user signs in Freighter, and it settles through Soroban RPC. Keys never leave the wallet.
- Real, self-hosted SEP-1/10/31/38 anchor,
contextio-anchor-platform. SDF's ownstellar/anchor-platformimage, with a real quoting, discovery, and KYC-stub handshake. There is also a lighter, real SEP-24 off-ramp against SDF's public reference anchor (GET /api/v1/public/anchor). It is deliberately never connected to a real bank account; see Mainnet for why. - Real AI reasoning layer. A pluggable LLM writes each decision's rationale, OpenAI by default. Bring your own key for Claude, Gemini, Grok, and others, through an OpenAI-compatible gateway with no SSRF risk. The on-chain decision stays deterministic and auditable. The LLM only explains it.
- Live dashboards. Every KPI, position, and activity row is read from on-chain state or the API, no mock when signed in, with verifiable explorer links.
Mainnet runs the same codebase. The money-moving surface is structurally cut down until the contracts are audited:
- A hot key cannot exist on the mainnet process.
assertMainnetHasNoHotKey(packages/config/src/env.ts) refuses to boot any process withSTELLAR_NETWORK=mainnetif a signer secret is present. This is tested inapps/api/test/envGuard.test.ts, and independently confirmed against the real deployed secrets oncontextio-api-mainnet: noSTELLAR_SERVICE_SECRETorBLEND_SIGNER_SECRETis present. This is why there is no autonomous agent, no Blend/DeFindex auto-rebalancing, and no Treasury/Payroll Soroban contract on mainnet. None of that is possible without a key, and Contextio has made that key impossible to hold there. - Self-custody Treasury and Payroll actions work the same as testnet. The client builds an unsigned XDR, signs it in their own wallet, and submits it. Payroll settles through a classic Stellar payment operation, with no unaudited contract in the mainnet path. Treasury calls Blend's own audited pool contract directly.
- Invitation-only.
MAINNET_ALLOWLIST_TENANT_IDSgates every self-custody endpoint to a single internal tenant today. This is a real account-level control, not an IP check. Verified live with a freshly generated, correctly SEP-53-signed random wallet: it gets a clean401 "No workspace for this wallet"instead of being auto-provisioned. - Geo-restricted by default, for a documented reason. Self-custody
actions are unavailable from the EU/EEA, the United States, and China
until those specific regimes are reviewed. That's MiCA, FinCEN and BSA
plus roughly 50 state money-transmitter regimes, and China's outright
prohibition. See the Terms' "Restricted Jurisdictions" section.
Everywhere else is open by default. This is backed by both a technical
check, in
apps/web/src/middleware.ts, and a real per-action jurisdiction-attestation checkbox. - Real on-chain price oracle. Same Reflector integration as testnet, reading mainnet's real oracle contract from the table above, not a mirrored value.
Production last mile. Local off-ramp rails, PIX, Transferencias 3.0, and
Bre-B, need a licensed anchor partner in production, and that business
relationship doesn't exist yet. BlindPay is the researched candidate: real
Stellar support, an unsigned-XDR flow that matches this project's model, but
no API key configured anywhere. It's confirmed inert. Soroban Audit Bank is
the intended path to auditing contracts/treasury and contracts/payroll
before they touch mainnet, gated on prior SCF funding.
What's next. External audit of the Soroban contracts, which unblocks a mainnet Smart Account migration mirroring testnet's. A licensed anchor relationship for real PIX and Bre-B settlement. Multi-entity treasury. LCP as an open standard for accountable on-chain agents.
A team building for LATAM, two of them Stellar Ambassadors:
- Giovanny Amador (Gio), Founder and lead architect, Stellar México Ambassador. Built the full technical protocol: Soroban contracts, the autonomous agent, TS/Python SDKs, and the "LLM proposes, the contract decides" security pattern. 3rd place, Fintech World Cup MX 2026. ETH Uruguay and ETH MX hackathon winner.
- Monserrat Mendoza (Monse), Cofounder and COO, Stellar México Ambassador. Leads operations, product, and UX/UI. SCF Kickstart co-applicant. 3rd place, Fintech World Cup MX 2026.
- Gonzalo Chacón, Cofounder and CCO. Program and go-to-market: 14+ years of enterprise delivery, Senior PM at UST, MBA in finance, Scrum Master. Leads commercial development and the in-person pitch across BR/AR/CO.
Gio and Monse co-created Nirium before starting Contextio. Through that project they went through SDF SCALE and Impacta, took part in BBVA Open Deal, and won an SCF Instaward at Alebrije/SCALE.
Built with and for real LATAM founders. We're running recorded customer-discovery interviews validating the pain (idle cash losing value, costly cross-border payroll, manual treasury) and our solution. Interviews: https://drive.google.com/drive/folders/1OU8ShexpLqSakSd3ytPVS9WYWvodprUS?usp=sharing
Contributions, issues, and reviews are welcome, whether you're a judge evaluating the project or a developer extending it.
Getting started. Fork and clone the repo, then follow
Quickstart: pnpm install, build the workspace packages,
then cp .env.example .env.local. Everything runs in deterministic mock
mode with blank credentials, so you can review offline. Create a feature
branch: git checkout -b feat/your-change.
Before opening a PR, keep CI green:
pnpm lint && pnpm typecheck && pnpm test && pnpm build
cargo fmt --all -- --check && cargo clippy --target wasm32-unknown-unknown -- -D warnings && cargo test --allConventions
- TypeScript. Strict types, no
any, preferunknown. Explicit.jsextensions on relative imports in backend ESM packages. Validate every state-changing HTTP body withzod. Log through the structured@contextio/sharedlogger and redact secrets. - Architecture rules. All agentic actions go through
apps/api. The worker never writes on-chain or to the database directly; it calls the API withx-internal-secret. Treasury and payroll state-changers must carry a valid LCP binding, or return HTTP 412. New Postgres tables get RLS policies. - Soroban. Persistent storage with
extend_ttl. Payroll idempotency onrun_id. Emitted events always include the canonical LCP SHA-256 hash. - Commits. Short, imperative messages. Small, focused PRs. Never commit
secrets;
.env*files are gitignored (see .env.example).
Security: please report vulnerabilities privately to the maintainers rather than opening a public issue.
AI assistance. This repository is built with AI assistance. See docs/AI-USAGE.md for what is disclosed, what never gets asserted without a human confirming it directly, and the commit-trailer policy. Engineering rules, including build and test commands, code conventions, and the non-custodial invariants as hard requirements, are in docs/CLAUDE.md.
Licensed under the Apache License 2.0. See LICENSE.
Copyright 2026 Contextio
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0