Autonomous, non-custodial treasury & payroll for emerging-market businesses, built on Stellar / Soroban. This document describes the live architecture, the Stellar building blocks we integrate, and the concrete plan to harden the parts of it that still only run on testnet before they reach mainnet — mainnet itself is already live, deliberately scoped to read-only + self-custody-only actions (see §6 "Current mainnet status").
- Live app: https://www.contextio.xyz (network toggle in the navbar switches Testnet/Mainnet)
- API + 24/7 agent (testnet): https://contextio-api.fly.dev
- API (mainnet, read-only + self-custody, invitation-only): https://contextio-api-mainnet.fly.dev
- Self-hosted Anchor Platform (SEP-1/10/31/38, testnet-only by design): https://contextio-anchor-platform.fly.dev/.well-known/stellar.toml
- Repo: https://github.com/contextio/Contextio
- Demo: https://youtu.be/JI7KpNQMo0A
- Competitive positioning: docs/PRIOR-ART.md — named funded Stellar neighbors in payroll and treasury automation, verified live, with an honest account of which claimed differences hold up and which are shared with at least one other project
Contextio is an AI agent that runs treasury and payroll on Stellar. It keeps enough USDC liquid for payday, puts idle cash to work earning real yield (Blend, DeFindex), prices assets with an on-chain oracle (Reflector), and pays teams across borders — non-custodial, with every action bound to owner-signed rules and verifiable on-chain (our Legal Context Protocol). The on-chain decision is deterministic and auditable; an LLM only writes the human-readable rationale.
┌──────────── User (company owner) ────────────┐
│ Stellar wallet (Freighter via Wallets Kit) │
└───────────────┬───────────────────────────────┘
Sign In With Stellar (SEP-53) · self-custody signing
▼
┌──────────────────── API (Fly.io / Express) ────────────────────┐
│ Auth (SEP-10/53) · RBAC · Legal Context Protocol (LCP) gate │
│ Treasury · Payroll · Agent · Integrations │
└───┬───────────┬───────────┬───────────┬───────────┬────────────┘
▼ ▼ ▼ ▼ ▼
Soroban Blend DeFindex Reflector SEP-24 anchor
contracts (lending) (vaults) (oracle) (off-ramp)
(treasury, PIX/Bre-B (prod)
payroll)
│
▼
Supabase (Postgres) — accounts/payroll/audit/LCP refs · RLS · Realtime
▲
24/7 Agent (Fly.io) — calls the API with an internal secret; it has
**no privileged on-chain path of its own**, so LCP + RBAC + audit apply
identically to agent and human actors.
Read path: the dashboard reads live on-chain state — wallet balances (Horizon), Blend/DeFindex positions, and Reflector prices via read-only Soroban simulation — aggregated into a USD snapshot (12s TTL cache). Real on both networks.
Write path (testnet): treasury moves are built as unsigned XDR, signed by the user in Freighter, and submitted via Soroban RPC; agent/payroll actions are signed by a delegated operational key, always bound to a published LCP document.
Write path (mainnet): the delegated-key path above does not exist there at all — assertMainnetHasNoHotKey refuses to boot a mainnet process that has one configured. Every mainnet write is the Freighter-signed, unsigned-XDR path only; there's no 24/7 Agent box on the mainnet side of this diagram, and no Soroban treasury/payroll contract to route through (self-custody there calls Blend's own audited pool directly, or settles via a classic Stellar payment operation).
| Building block | How Contextio uses it | Status |
|---|---|---|
| Soroban smart contracts | Treasury + payroll contracts; idempotent, event-driven; every event carries the LCP SHA-256 hash. | ✅ Deployed |
Blend (PoolContractV2) |
Real USDC lending of idle treasury cash; positions read live. | ✅ Live |
| DeFindex | Real XLM yield vaults; users deploy their own via the factory, Freighter-signed (self-custody). | ✅ Live |
| Reflector (SEP-40 oracle) | Real on-chain XLM/USD priced via read-only Soroban simulation → drives the treasury's USD valuation (replaces a hardcoded rate). Proof: GET /api/v1/public/oracle. |
✅ Live |
| SEP-10 / SEP-53 | SEP-53 for wallet sign-in and agent-authorization consent; SEP-10 challenge/verify for the anchor. | ✅ Live |
| SEP-24 | Interactive anchor off-ramp (USDC/XLM) against a testnet anchor. | ✅ Live |
| Anchor Platform (self-hosted) | Contextio's own deployment of SDF's stellar/anchor-platform (v4.6.2) — platform (SEP+Platform+event-processor+observer, one JVM process) + a custom Node/TS business/callback server (apps/anchor-business) implementing the real callback contract (GET/PUT/DELETE /customer, GET /rate) + a single-broker Kafka (KRaft) for the internal event queue + a dedicated Postgres. All 4 pieces deployed and wired on Fly.io (infra/anchor-platform/). Not the SDF reference anchor — our own infrastructure, our own SEP-1 identity. |
✅ Live |
| SEP-38 | Real indicative + firm FX quotes (GET /public/anchor/sep38) from Contextio's own anchor's ANCHOR_QUOTE_SERVER — XLM priced against iso4217:USD/BRL/ARS/COP, all four verified live end-to-end (/sep38/prices, /sep38/price). No USDC pair configured yet. BRL/ARS/COP rates are a documented demo table (no confirmed live LatAm-fiat oracle on testnet — same limitation Reflector already has), USD uses the real Reflector oracle price. |
✅ Live (4 currencies) |
| SEP-31 | Real institutional-send discovery (GET /public/anchor/sep31) against Contextio's own DIRECT_PAYMENT_SERVER. Unlike the SDF reference anchor (whose receive map is empty), ours has real receive assets configured for native/BRL/ARS/COP with real funding methods (PIX, Transferencias 3.0, Bre-B) and limits — verified live. Protocol + infrastructure are both real; actually settling a payment still needs a licensed local off-ramp partner (see Milestone 2). |
✅ Live (discovery + real receive config) |
| Horizon | Batch payments — real USDC payroll to employee wallets in one tx. | ✅ Live |
| Stellar Wallets Kit | Freighter/xBull/Albedo/Lobstr connection + self-custody signing. | ✅ Live |
| OpenZeppelin Channels (hosted Relayer) | Optional fee-sponsored submission for self-custody Payouts (apps/api/src/integrations/relayer.ts) — the fund/fee-sponsor key lives on OpenZeppelin's infrastructure, never ours, so it's safe to enable on mainnet without touching the boot guard. Falls back to direct submission when unset. |
✅ Live (optional) |
| USDC | Circle testnet USDC for payroll; positions read on-chain. | ✅ Live |
Verifiable example: USDC payroll settled to 3 employees — tx 4bd1b927df7ab404dcd56abe649dcd47f56aa174b8116c747ec4f1aabc12cf78.
EMEA extension (added 2026-08-06, real pieces only — no fake coverage): Contextio was built LatAm-first (BR/AR/CO), but the Stellar/Soroban core is region-agnostic, and three genuinely real EMEA-capable pieces already exist:
- A real Swiss jurisdiction + dispute channel —
jurisdictionsnow includesCH, with its own arbitration channel at/legal/disputes/chnaming the real institution (Swiss Arbitration Centre, formerly SCAI, renamed 1 June 2021 — verified, not assumed) and the real governing framework (Swiss PILA Chapter 12 for international matters). This isn't a random EU pick — it's the same jurisdiction CV Labs (the Stellar accelerator partner) itself operates from. - Real EURC pricing — Reflector's external oracle has no direct fiat EUR/GBP/CHF feed, but it does have a real, verified price for EURC (Circle's actual EUR-pegged Stellar asset, issuer
GDHU6WRG…Y4ITNPP2mainnet) — confirmed live against the real oracle, not assumed.treasuryService.onchainPositionsnow values a real EURC balance correctly instead of miscounting it as $1 like USDC. Honest caveat: no tenant has ever actually held EURC in the treasury wallet — the capability is real and tested against the real oracle, the on-chain usage isn't (yet). - BlindPay's existing SEPA rail (see Milestone 2) already accepts Stellar-USDC for SEPA (Europe) payouts as of June 2026 per their own changelog — the same off-ramp integration scaffolded for LatAm reaches EMEA's biggest single corridor (the Eurozone) without a second partner relationship.
- Not done, and not faked: no EMEA customer discovery, no real EURC funds ever moved, no confirmation from CV Labs that a LatAm-founded team building for LatAm-plus-EMEA actually satisfies their "based in or serving EMEA" eligibility language — that's a real open question, not a solved one.
Africa / Middle East reviewed and deliberately NOT extended (2026-08-06) — the "software only, not a regulated fintech" line matters more than geographic breadth. Before adding any jurisdiction, checked whether the same non-custodial thesis that keeps Contextio outside Mexico's LFPIORPI ("actividad vulnerable") actually holds elsewhere. The underlying test is the FATF functional-control standard most VASP-style regimes are built on: does the business exercise operational control over a client's funds/transfers, as a business, on the client's behalf? Contextio's architecture (client always signs their own XDR, Contextio never holds keys) is designed to sit outside that test everywhere by construction — but how favorably each regulator interprets it is a real, jurisdiction-specific legal question, not a code question. Researched two real candidates instead of assuming either was safe:
- South Africa — the strongest candidate (CV Labs has a real physical office in Cape Town, not just its EMEA Demo Day event there; real Stellar-network activity already exists regionally via Nigeria's Cowrie/Oradian and Kenya's Kotani Pay). But the FSCA's own CASP guidance states plainly that "wallet providers" must be licensed under the FAIS Act — no confirmed carve-out found for a purely non-custodial, client-signs-everything model, unlike Mexico's more specific favorable language. Decision: not added. Kept at BR/AR/CO/CH pending an actual legal opinion, same discipline already applied to the LCP arbitration clause itself (§ mainnet plan item 7).
- UAE / Dubai — weaker candidate on both counts: no CV Labs office there (their EMEA footprint is Zug, Vaduz, Cape Town, Berlin, Lisbon), and VARA's own published guidance states outright that "No virtual asset activity is 'exempt' from regulatory supervision," requiring self-assessment against 8 broad licensed-activity categories with no stated technology-provider carve-out. Materially more aggressive posture than Mexico or Switzerland. Decision: not pursued.
- Checked and confirmed empirically (Reflector, both testnet and mainnet oracles, live query): no price feed exists for ZAR, NGN, KES, AED, or EGP — unlike EURC, there's no real pricing capability to extend to these regions even before the legal question. Only Switzerland's arbitration-seat role (see above) and the EURC/SEPA pieces are real EMEA capability today; Africa/Middle East remain LCP-jurisdictions BR/AR/CO/CH only, by deliberate choice, not oversight.
- Non-custodial: keys stay with the company. The user signs treasury moves in Freighter; the agent uses a delegated operational key bounded by signed rules.
- "LLM proposes, the contract decides": the deterministic risk engine chooses the action + amount; the LLM only explains it. Funds are never moved by the model.
- Legal Context Protocol (LCP): every agentic action is bound to a hashed, multi-jurisdiction terms document at
/.well-known/contextio-legal-context.json; the canonical SHA-256 is written into every treasury flow, payroll run, and audit record — independently verifiable (re-derivable viacontextio-sdk). State-changing endpoints return HTTP 412 without a valid LCP binding.- Verify the hash yourself, three commands, no trust required — and the exact same document/hash on both networks:
As of this writing:
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"]))' curl -s "https://contextio-api.fly.dev/.well-known/contextio-terms.md" -o terms.md sha256sum terms.md # must equal the atrHash above, without the 0x prefix
atrHash 0x579eeabf5f640d9cbb274824287fa7d1ef540ae9cc773e79f6d2cec913b4237e,version 8. Swap the host forcontextio-api-mainnet.fly.devand it returns the identicalatrHash/version— confirmed live — because it's one canonical document per tenant domain in Supabase, read by both API deployments, not a separate copy per network. - Now a genuine, verified implementation of the real open standard (2026-08-06), not just a collision-avoided name. A real "Legal Context Protocol" open standard was launched June 2026 by the American Arbitration Association + Integra Ledger, with SDF as a founding contributor (alongside Google, IBM, Circle) — legalcontextprotocol.org. First found (2026-08-05) that Contextio's own, differently-named-by-coincidence mechanism collided with the standard's reserved discovery path (
/.well-known/legal-context.json) while using an incompatible schema — fixed by moving to/.well-known/contextio-legal-context.json. Went further (2026-08-06): migrated the schema itself to be field-by-field conformant with the standard's ownspec/legal-context.schema.json(fetched and checked directly, not assumed) —termsas a plain HTTPS URL,atrHash(0x+64 lowercase hex SHA-256),acceptanceRequired, a spec-shapeddisputeResolutionobject, andapi, reaching the standard's highest disclosed tier, Level 4 ("Integrated"). The schema's ownadditionalProperties: truemeant every one of Contextio's existing extensions (per-jurisdictiondisputeChannels,consentRequirements,settlement, ...) stayed intact alongside the new spec fields — nothing was removed, only added. Also fixed a real correctness gap found along the way: the terms document previously hashed was a rendered React page reachable via a redirect (the spec explicitly disallows this — "MUST be... not... dynamically rendered content") whose declared hash didn't even match its own fetched bytes; now served as genuine static markdown at/.well-known/contextio-terms.md, withatrHashcomputed from those exact bytes. Independently verified, not just asserted: fetched the live discovery document, fetchedtermsseparately, recomputed SHA-256 from those exact bytes, and confirmed it matches the declaredatrHash— the same check a real third-party verifier performs per the standard's own verification procedure.contextio-sdkbumped to0.3.0for the schema change (0.2.0 already covered the path move) and published for real on npm 2026-08-06 — the version bump landed inpackage.jsona day earlier (2026-08-05) but the actualnpm publishdidn't happen until caught vianpm view contextio-sdk versionsshowing only0.2.0live while writing the README; confirmed independently after (dist.shasumon the registry matches what the publish command itself printed). Thelegal-context-protocolnpm keyword stays removed.
- Verify the hash yourself, three commands, no trust required — and the exact same document/hash on both networks:
- RBAC + zod validation on every state-changing endpoint; RLS on all tenant data; helmet, CORS allowlist, rate limits; secrets redacted in logs.
- Mainnet is receive-only by construction: a mainnet-configured process refuses to boot if any signer secret (
STELLAR_SERVICE_SECRET,BLEND_SIGNER_SECRET) is present (packages/config/src/env.ts) — not a policy, an enforced startup check. Money-moving actions on mainnet exist only as unsigned-XDR "prepare" endpoints (/treasury/prepare,/payroll/runs/prepare) that the caller's own wallet signs and submits; the platform never holds a key capable of broadcasting them. The legacy agent/service-signed paths (/treasury/rebalance,POST /payroll/runs) require a signer secret and therefore cannot run against mainnet at all. - Payroll payouts are contractor-only. Every self-custody payout run requires an explicit
contractorAttestation(the recipients are independent contractors under a commercial contract, never subordinate employees) — Mexican labor law (LFT Art. 101) requires salary to be paid in legal-tender currency, so paying a real "empleado" in USDC would be illegal for the client. The payroll contract'sexecute_runalready supported an allowlistedoperatorcalling it directly (contracts/payroll/src/lib.rs); the self-custody flow uses that existing path rather than the platform's admin key. - Mainnet is invitation-only via an explicit tenant allowlist (
MAINNET_ALLOWLIST_TENANT_IDS) enforced on every self-custody money-moving endpoint — a stand-in for the full KYB/tier gate on the Milestone 2 roadmap below. This wasn't fully true until 2026-08-06:contextio-api-mainnetstill hadAUTH_DEMO_TENANT_ID/AUTH_DEMO_ROLE="owner"set (copy-pasted from testnet's config, where auto-joining any connecting wallet is the intended zero-friction demo behavior) — meaning any wallet that completed SEP-53 sign-in was automatically granted owner membership on the one allowlisted tenant, no vetting at all. The allowlist restricted which tenant could reach self-custody actions, but nothing restricted who could join that tenant. Found and fixed the same day a real wallet connected and needed no authorization step at all to reach Treasury — split the overloaded env var (PUBLIC_ACTIVITY_TENANT_IDnow covers the harmless "show this tenant's activity on the public home feed" use,AUTH_DEMO_TENANT_IDstays unset on mainnet), verified live with a freshly generated, correctly SEP-53-signed random wallet getting a clean401 "No workspace for this wallet"instead of auto-joining. - Contextio is a software provider, not a money-transmission or exchange entity — the self-hosted Anchor Platform (§3, §6 Milestone 2) does not change that. It exists on testnet, deliberately disconnected from any real bank account or payment processor, to prove Contextio can operate anchor-grade SEP-1/10/31/38 infrastructure — a technical capability, not a production settlement path. Real fiat settlement, when it exists, will run through a licensed third-party anchor (the BlindPay/Koywe conversation referenced throughout this doc): Contextio's software talks to their anchor via SEP-31/38, exactly as it already does with SEP-24 today. Contextio itself is deliberately never the counterparty that converts or transmits fiat on a client's behalf — that's the specific activity that would trigger ITF licensing under Mexico's Ley Fintech/LRITF. The underlying legal thesis (FATF: a software provider with no control over funds is not a VASP) still awaits written confirmation from counsel before mainnet — see the mainnet-launch tracking notes; this boundary is written down here so it stays explicit regardless of how much anchor-side technical capability gets built.
- LFPIORPI fracción XVI's actual text (read directly, DOF 16-07-2025 reform — not a summary) is broader and more extraterritorial than a casual reading suggests, and this changed a real decision. The vulnerable activity is "el ofrecimiento habitual y profesional de intercambio de activos virtuales... que administren u operen, facilitando o realizando operaciones de compra o venta de dichos activos propiedad de sus clientes o bien, provean medios para custodiar, almacenar, o transferir activos virtuales... incluidas las operaciones que se realicen con ciudadanos mexicanos desde otra jurisdicción." Two consequences taken seriously here: (1) "provea medios para... transferir" (provide the means to transfer) is textually broader than the "on behalf of the client" framing that makes Brazil's/Argentina's/Colombia's PSAV-equivalent frameworks feel safer for a pure self-custody tool — it could arguably reach signing-assistance software even without Contextio ever holding a key; (2) the extraterritorial clause targets Mexican citizenship, not physical location — a fact IP-based geo-blocking structurally cannot detect (a Mexican citizen browsing from São Paulo would pass a Brazil-allowed check and still be exactly the counterparty the statute names). Consequence:
apps/web/src/middleware.ts's mainnet geo-gate allows onlyBR/AR/CO— Mexico was briefly included (reasoning: team is Mexico-based, deepest existing compliance research), then removed the same day after reading the primary source instead of relying on the earlier softer paraphrase. This doesn't change the money-custody risk (mainnet stays invitation-only regardless, see above) — it changes what the web app itself visibly solicits.
- Treasury:
CASGAQQVHDF4Q2XTK3QWYHRABYX7JUIO6HCLEOZZR7V3TIMVHMXPTA7I - Payroll:
CDXML4PU5RVXQ7DSM7UO5OURKFUJMPGI57PRZCQ3NZTKFGPOIDIOIRCT - Reflector oracle (external, base USD):
CCYOZJCOPG34LLQQ7N24YXBM7LL62R7ONMZ3G6WZAAYPB5OYKOMJRN63 - Smart-treasury signer v5 (OpenZeppelin Stellar Smart Accounts, Milestone 1 — see §6):
CAMEOVPRT3PISVDQ5R6JY6NFUFQDR25AR6UV4IS5HXPNMHLDFN46DCID(v1-v4 are superseded/abandoned — smart-account admin functions are unreachable post-deploy, see §6, so every design fix means a fresh address, never an in-place upgrade) - Spending-limit policy v3 (
contracts/spending-limit-policy, unmodified OpenZeppelin logic):CDDF3B2SPJFZVSAYWXJ3ROHJDKDW667HIRTP34CROWHZHEUEWK7K45AL(v1/v2 superseded — v2 briefly added custom Blend-parsing that turned out unnecessary, see §6)
Deployed / configured on mainnet: only the Reflector oracle. CAFJZQWSED6YAWZU3GWRTOCNPPCGBN32L7QV43XX5LZLFTK6JLN34DLN — SDF's real mainnet price oracle, read-only, no signing involved. No Treasury, Payroll, or Smart Account contract of Contextio's own exists on mainnet, deliberately (§4, §6) — self-custody there calls Blend's own audited mainnet pool contract directly, or settles via a classic Stellar payment operation, never an unaudited Contextio contract.
Mainnet itself is already live (§ "Current mainnet status" below) — what this roadmap tracks is the remaining path to the full-featured version: audited Soroban contracts, the autonomous agent, and Blend/DeFindex auto-rebalancing all running on mainnet, none of which is possible today by design (§4's boot guard).
The six Integration List building blocks this roadmap funds: Blend v2 · DeFindex · Freighter Connect · Stellar Wallets Kit · Anchor Platform · Stellar Disbursement Platform — full status/funded-work breakdown per block in docs/SCF-INTEGRATION.md. Soroban itself, Reflector (SEP-40), Stellar SDK/RPC, and OpenZeppelin Channels are real and load-bearing in the product but are the platform Contextio's own code runs on, not chosen Integration List items — no budget is attributed to them, same distinction that document makes explicitly after an external review caught this section conflating the two.
- Unify the per-integration service accounts into a single smart-account treasury (OpenZeppelin Stellar Smart Accounts, policy-based spending limits — one of Stellar's three official 2026 priorities alongside AI/agent-readiness and Soroban DX), so liquidity, Blend lending, and DeFindex vault positions are held and controlled by one account with a spending-limit policy instead of a raw secret key. This is also the real fix for autonomous agent rebalancing to ever run on mainnet — replaces the hot key the boot guard currently forbids there.
- The signing mechanism is proven end-to-end with real, confirmed testnet transactions (2026-08-04) — not just simulation, not just unit tests.
StellarClient.invokeViaSmartAccount(packages/shared/src/stellar/client.ts) builds the two-entry authorization OpenZeppelin'sSigner::Delegatedpattern requires (the smart account's own credentials carrying a hand-builtAuthPayload, plus a second entry where the agent's real Ed25519 key signs the nested__check_authcall) and has moved real value through it: a confirmedUSDC.transferauthorized purely by the smart account, no raw hot-key signature on the funds-moving operation itself. - Two non-obvious contract-level findings, both found by testing against the real deployed contract rather than trusting the crate's docs/tests in isolation:
- OpenZeppelin's generic
ExecutionEntryPoint::execute(target, fn, args)helper is a dead end for a policy like ours: its ownrequire_auth()always produces a self-referential context (contract: <the smart account itself>, fn_name: "execute"), which can never match aCallContract(target)-scoped rule or a policy that inspectsfn_name(likespending_limit, which only recognizesfn_name == "transfer"). The fix is calling the target contract directly (usdc.transfer(smartAccount, to, amount), noexecute()wrapper) — that makes the target's own internalrequire_authproduce a context that actually matches. - Smart-account admin functions (
add_context_rule,add_policy,remove_policy, ...) are unreachable after deployment — each is itself gated by the same self-referentialrequire_auth()pattern, and by default no rule exists that authorizes calls on the smart account itself. Every context rule the account will ever need must be baked into the constructor at deploy time; there is no in-place way to add one later. This is why the deployed address has moved twice this session (v1 → v2 → v3) instead of being patched.
- OpenZeppelin's generic
- Rules are scoped per target CONTRACT, not per asset — confirmed on real Blend calls: a direct
blendPool.submit(...)call's outer auth context carries the pool's address, and Blend's own internal call to the underlying token'stransfer()(for aSupplyrequest) surfaces as a second, separate auth context carrying the token's address — both need their own matchingCallContract(...)rule.spending_limit's stock, unmodifiedenforce()(fn_name == "transfer"only) gates a real Blend Supply natively with zero custom parsing, as long as a rule is scoped to the underlying reserve token contract (Blend'sCAQCFVLO…SRCJU, a different contract than the app's everydayUSDC_CONTRACT_ID— testnet has two distinct USDC-pegged SACs in play). An earlier version ofcontracts/spending-limit-policyadded atranslate_blend_submithelper to re-expresssubmit()itself as a synthetic transfer — removed after confirming it double-recorded the same spend against two independent rule buckets (once via the translation on the pool-level rule, once via the real nested context on the token-level rule) with no safety benefit. The policy contract is back to a pure, unmodified pass-through to OpenZeppelin's audited module. - Confirmed on real
Withdrawcalls too: pulling funds back out of Blend does not generate a second nested context requiring the smart account's auth at all (the underlying token leg's "from" is the pool's own reserve, not the smart account) — so aWithdrawnaturally needs only the outer gateway rule, with nothing recorded against the spending cap. No special-casing needed;StellarClient.invokeViaSmartAccountdiscovers however many contexts a call actually produces (by walking the recorded invocation tree from onesimulateTransactionrecording pass) and maps each one to a rule id via a caller-suppliedcontextRuleIdByTarget, rather than assuming a fixed count. - Final v5 deploy (
CAMEOVPRT…HLDFN46DCID, policy v3CDDF3B2S…7K45AL) has three rules, split into two kinds: capped rules (carry the spending-limit policy, real cap) — rule 0CallContract(USDC_CONTRACT_ID)for plain transfers, rule 1CallContract(<Blend reserve token>)for the real Blend Supply/Repay outflow; and one gateway rule (signer-gate only, deliberately no policy) — rule 2CallContract(BLEND_POOL_ID), since attaching a cap there too is exactly the double-counting bug described above. Each capped rule is independently capped at 100 USDC/day (a deliberate choice: per-target caps, not one shared pool; worst-case ceiling is the sum across capped targets). Verified end-to-end with real, confirmed transactions, not just simulation: a real 0.1 USDC Blend Supply (recorded once, correctly, against rule 1 only) and a real 0.05 USDC Blend Withdraw (correctly recorded nothing) both went through, fully authorized by the smart account.contexta-smart-treasurygained tests for the capped/gateway rule split;contexta-spending-limit-policyis back to its original test-free pass-through. All 21 contract tests across the workspace pass. - Done (2026-08-05): the live 24/7 agent now signs Blend through the smart account, not the raw hot key.
BlendClient(apps/api/src/integrations/blend.ts) gained asmartAccountIdconfig field — unset by default (existing direct-signing behavior unchanged) — that, when present, routessupply/withdrawthroughinvokeViaSmartAccountinstead ofsubmitOperationXdr, and switchesgetVaultData's position read (positionHolderAddress) from the signer's own classic address to the smart account's, since that's now who actually holds and controls the position. Reuses Blend SDK's ownPoolContractV2.submit()to build the request (just extracts its args instead of hand-encoding theRequeststruct again), so the on-chain shape is exactly what Blend's SDK already produces. Before flipping the config, migrated the live position for real: withdrew the agent's entire existing ~50 USDC Blend position from its classic address (an over-request the pool correctly capped to the exact available balance), transferred it into the smart account, and re-supplied it there — verified via the actual compiledBlendClientclass (not a throwaway script) reading back the correct position afterward.contextio-api(the Fly app hostingAgentService) redeployed withBLEND_SMART_ACCOUNT_IDset;contextio-agent(the worker) needed no changes at all, since it only ever calls intoapps/api's endpoints, never Blend directly (per the single-entry-point rule). - Not yet done: the equivalent migration for DeFindex (still signs directly) — deliberately scoped to Blend first, since that's the integration the live agent actually uses most; extending
contexta-spending-limit-policywith a DeFindex-shaped rule is real, separate work, not yet started.
- The signing mechanism is proven end-to-end with real, confirmed testnet transactions (2026-08-04) — not just simulation, not just unit tests.
- Expand Reflector to feed FX pairs into the agent's risk/buffer engine (not just XLM/USD valuation); pair with SEP-38 (below) for a firm quote instead of an estimated buffer.
- Done when: the agent's live rebalance calls route through the smart-account signer, not just the standalone contracts existing on-chain. Like all fund-governing contract logic, moving this to mainnet still requires the Milestone-3 audit regardless of the signer model.
- Self-hosted Anchor Platform, standing up our own SEP-31/38 anchor rather than just a client against someone else's — SDF's
stellar/anchor-platform(v4.6.2) deployed for real, not the reference anchor:platform(sep-server + platform-server + event-processor + stellar-observer, one JVM process) + a custom Node/TS callback business server (apps/anchor-business, implementsGET/PUT/DELETE /customerandGET /rateagainst the real callback API contract — verified field-by-field againstapi-schema/.../callback/*.java, not guessed) + a single-broker Kafka (KRaft mode) for the internal event queue + a dedicated Postgres. Own SEP-1 identity, own testnet distribution account (GB7U6HCA7Y…4JOQ2IBGB5EH6).- Live on testnet (2026-08-04), verified end-to-end:
GET /.well-known/stellar.toml(SEP-1),GET /auth(SEP-10 challenge, real signed XDR),GET /sep38/prices+/sep38/price(SEP-38 — XLM priced against USD/BRL/ARS/COP, all four self-consistent to the platform's ownprice*buy_amount+fee≈sell_amountrounding check, computed with exact decimal arithmetic viadecimal.jsrather than floats — floats alone weren't precise enough once buy_amount hit the hundreds of thousands for ARS/COP),GET /sep31/info(SEP-31 — realreceiveconfig for native/BRL/ARS/COP with PIX/Transferencias 3.0/Bre-B funding methods, unlike the SDF reference anchor's emptyreceivemap). Public proof still exposed the same way:GET /api/v1/public/anchor/sep38and/sep31(apps/api/src/integrations/anchor.ts, now pointed atANCHOR_SEP3138_URL= our own anchor, separate fromANCHOR_SEP24_URLwhich still targets the SDF reference anchor since SEP-24 isn't enabled on ours yet). - Not yet done, deliberately deferred (decided 2026-08-05): SEP-24 on our own anchor, and closing the SEP-31 transaction lifecycle (JSON-RPC report-back). Neither advances the actual goal: this self-hosted anchor is never the real settlement counterparty (§4) — BlindPay's own Stellar payout API doesn't even go through a SEP-24 interactive flow, and closing the SEP-31 lifecycle only matters once a real SEP-31 payment exists, which needs the BlindPay relationship first, not more protocol-completeness code. SEP-24 already works today via the SDF reference anchor, so there's no functional gap for users either. Revisit once a real settlement partner is live and an actual gap shows up — building it now would be speculative.
- Still gated on a business step, not code: actually receiving real fiat on the other end needs a licensed local off-ramp partner — that relationship doesn't exist yet, and no amount of additional integration code changes that. Koywe is ruled out — confirmed twice against its own docs (2026-08-05): only Ethereum/Polygon/BSC, Stellar never appears. BlindPay is the real candidate, and a stronger fit than first assumed: Stellar support shipped May 2025 (14+ months mature, not experimental) and is still actively maintained (a wallet-rotation fix May 2026; SEPA payouts extended to accept Stellar-USDC as recently as June 2026, per their public changelog). Its Stellar payout flow is
quote → POST /payouts/stellar/authorize(returns an unsigned XDR) → client signs it →POST /payouts/stellarexecutes — i.e. BlindPay never custodies the crypto leg, matching Contextio's non-custodial model exactly (the treasury/agent wallet would sign directly, no handoff). Confirmed rails: PIX (Brazil), SPEI (Mexico, via Bitso), ACH (Colombia, via Bitso), Transfers (Argentina, via Bitso), SEPA (Europe) — covers the whole BR/AR/CO target plus MX. It absorbs the KYC/compliance layer itself (Contextio would collect the data, BlindPay verifies it). Practical next step is smaller than it looked: a freedevelopmentsandbox instance is ready immediately on sign-up, no paid plan or business verification required first — real integration work (testing the unsigned-XDR Stellar payout flow against sandboxUSDB) can start without waiting on any business conversation. Production instances take "up to 3 business days" to activate (where KYB likely happens) and paid plans start at $399/mo (Basic) or $1,599/mo (Business), custom pricing above that. No dedicated partnership email exists; sign-up is the direct path, demo call if a human conversation is wanted first — both are business steps for a person to take, not something to build. The protocol-level anchor is real and self-operated now, not borrowed; the settlement corridor is not. By design, that stays true even after a partner relationship exists — real settlement routes through the licensed partner's own anchor, not this one (see §4); this instance never gets connected to a real bank account.
- Live on testnet (2026-08-04), verified end-to-end:
- Stellar Disbursement Platform (SDP) for the bulk-payout engine itself — committed in the original CV Labs Integration track application ("Blend v2 + DeFindex + SDP") but not yet built. SDP is a real multi-service deployment (dashboard + core API + transaction-submission service, its own distribution + SEP-10 accounts) built on top of Anchor Platform — now that the Anchor Platform layer underneath it is real and deployed, SDP is the next concrete step rather than a re-sequenced one.
- Multi-entity treasury + KYC/KYB onboarding.
- Done when: a real off-ramp transaction settles via SEP-31 through a licensed anchor; SDP running a real bulk disbursement; ≥1 multi-entity tenant live with KYB.
- Deploy treasury + payroll Soroban contracts to mainnet; onboard the first pilot customer settling real value; professional user testing.
- Done when: contracts live on mainnet; ≥1 pilot moving real USDC payroll + treasury on mainnet.
(Budget excludes marketing and security-audit costs, per SCF rules; audit credits are applied at Tranche #3.)
Audit path: SDF's Soroban Audit Bank (5% co-payment, refundable if Critical/High/Medium findings are remediated within 20 business days) is the intended route for contracts/treasury/contracts/payroll — cheaper and more Stellar-native than sourcing a full-price audit independently — but it's gated on prior SCF funding, which Contextio doesn't have yet (checked its real eligibility rules, 2026-08-05: mandatory requirement is "funded through the Stellar Community Fund," not a standalone application). Contextio would qualify easily once funded — it's squarely a "financial protocol managing on-chain value," one of the program's automatic-eligibility priority categories. Contact for when that's true: sorobanaudits@stellar.org (or communityfund@stellar.org first, to actually get the SCF funding). No money-moving contract or endpoint goes to mainnet before an audit completes, regardless of which signing model it uses — and, in parallel, not before question 5 below is answered either; the two gates are independent, neither substitutes for the other.
Legal review pending: the LCP arbitration clause (BR/AR/CO dispute channels) has not yet been reviewed by a licensed attorney and should not be treated as binding until it is. LFPIORPI fracción XVI (activos virtuales = actividad vulnerable) takes effect 2027-01-17 — well after this mainnet target, but the non-custodial design above (unsigned XDR, client signs, contractor-only payouts) is deliberately built to already sit outside that "transmisión por cuenta de terceros" assumption; get that read confirmed in writing by counsel before Jan 2027. A second, separate Mexican instrument needs the same treatment before mainnet: Acuerdo 115/2026 (SHCP, DOF 07-08-2026, signed by Secretario Édgar Abraham Amador Zamora), which reforms LFPIORPI's Reglas de Carácter General and adds new arts. 24 Bis 2-5 on virtual-asset intermediation, general effective date 30-11-2026. The Acuerdo's existence, date, signatory, and article range are confirmed directly against the DOF (dof.gob.mx/nota_detalle.php?codigo=5795797&fecha=07/08/2026) and cross-checked against independent secondary summaries (Holland & Knight, Basham, piranirisk.com, cumplimientopld.com.mx); the specific question below is not — see question 5.
Questions for counsel, consolidated (2026-08-06, Acuerdo 115/2026 added 2026-08-15) — ask them together:
- Mexico — scope of "provean medios para... transferir." LFPIORPI Art. 17 fracción XVI (reforma DOF 16-07-2025) reaches anyone who "provides the means to" custody/store/transfer virtual assets, not only those who custody on the client's behalf. Does a purely non-custodial tool that only helps a client build and sign their own transaction fall inside or outside that phrase?
- Mexico — extraterritorial reach. The same fracción explicitly covers "operaciones que se realicen con ciudadanos mexicanos desde otra jurisdicción." Does serving a Mexican citizen from outside Mexico trigger the same obligations as operating domestically?
- EU/EEA — MiCA applicability. Does Regulation (EU) 2023/1114 (MiCA)'s crypto-asset-service-provider licensing regime reach a non-custodial tool that never takes control of a client's assets, or does it (like several other jurisdictions reviewed here) hinge on the same custody/control test?
- United States — FinCEN/BSA + state MTLs. FinCEN's 2019 guidance (FIN-2019-G001) draws a custodial/non-custodial line for money-transmitter status federally — does Contextio's pattern fall on the safe side, and does that federal answer generalize across the ~50 state money-transmitter regimes, or does state-by-state exposure remain regardless?
- Mexico — does Acuerdo 115/2026's "intermediación" reach a non-custodial agent. Arts. 24 Bis 2-5 impose identification/monitoring duties tied to custody and intermediation of virtual assets; unconfirmed secondhand reporting (not yet independently verified against the article text itself, which truncates in every automated fetch attempted so far) suggests Art. 24 Bis 4's definition of "facilitación o intermediación" may not require control of the assets. If that reading holds, does it reach Contextio's non-custodial agentic treasury decisions (the agent proposes and can trigger execution, but a client's own signature is still required for anything that moves funds) the same way question 1 asks about fracción XVI itself? Treat as unconfirmed until someone reads the operative text directly, not this summary.
Until these are answered, self-custody treasury/payout actions exclude the EU/EEA, United States, and China by default (apps/web/src/middleware.ts technical block + a Restricted Jurisdictions clause in the Terms + a per-action jurisdiction attestation checkbox, all shipped 2026-08-06) — not because any of the three has been found unsafe, but because none has been reviewed and each carries a well-documented, expensive-to-get-wrong regime. This replaced an earlier country-allowlist approach (BR/AR/CO only) that had the opposite, unscalable default — closed until individually proven safe, which is how a real team member ended up excluded from testing despite no actual finding against their own country.
The jurisdiction-attestation checkbox landed on both self-custody surfaces, but they didn't start from the same place: Payroll already had a contractorAttestation/acknowledgeTerms pair (per-run, not a one-time setting — see §4), so adding a third checkbox there was additive. Treasury's prepareMoveSchema had zero attestation fields of any kind before 2026-08-06 — a real gap found by grep, not assumed — so this shipped Treasury's first attestation UI at all, acknowledgeTerms and jurisdictionAttestation together, not just the new one.
contextio-api-mainnet (https://contextio-api-mainnet.fly.dev/) is live, scoped to read-only endpoints (Reflector oracle — real XLM/USD from mainnet's actual oracle contract, CAFJZQWSED6YAWZU3GWRTOCNPPCGBN32L7QV43XX5LZLFTK6JLN34DLN; audit trail; LCP viewer; anchor status, which correctly reports live:false — no licensed anchor yet) plus self-custody Treasury/Payouts, gated by MAINNET_ALLOWLIST_TENANT_IDS containing one dedicated mainnet tenant (adba87a4-bfdf-4b00-bac4-f2f0f1d6bb72 — a fresh tenant created specifically so mainnet's activity feed never displays testnet's real 24/7 agent history as if it were mainnet's own, see §4). No Treasury/Payroll Soroban contract and no autonomous agent exist on this deployment at all — GET /api/v1/public/activity returns agentAddress: null, contracts: {treasury: null, payroll: null}, verify directly rather than trusting this doc.
Both wallets used for testing this — treasury GBGHQMXAAS5BQELFXAHHARKLOBX77USARAGYJ3ETEWAAEOBJHFW4RGBW and the receiving test wallet GAGBORWREIMHNL7GOOWSXEHXP7UATSCI5YMLKGLKKLWRRS4D4H6LIIAU — are internally controlled (verified via Horizon: single signer each, no unexpected additions). No third party's funds move through this deployment.
This matters because it's the line the whole legal posture rests on: LFPIORPI's "actividad vulnerable" and Ley Fintech's ITF-reserved-activity rules both hinge on offering the service habitually, to clients — a third party distinct from the operator. Founders testing a flow with their own funds, in a closed allowlist nobody else can reach, doesn't cross that line; it's the same as anyone using their own wallet. The audit-Bank / lawyer-review gate above is about protecting someone else's money before it's exposed to unaudited code — it doesn't block the operators from dogfooding their own tool with funds they own and the risk of which they're accepting knowingly.
This note stops being true the moment a second, real tenant is added to MAINNET_ALLOWLIST_TENANT_IDS. At that point the audit + lawyer review stop being "next milestone" and become hard prerequisites, not optional — update or remove this note when that happens.
A real gap in "invitation-only" existed until 2026-08-06, found and fixed the same day a real wallet connected and needed no authorization step to reach Treasury — see §4's AUTH_DEMO_TENANT_ID bullet. Until that fix, any wallet completing SEP-53 sign-in was automatically granted owner membership on this same allowlisted tenant, no vetting at all — the allowlist restricted which tenant could act, nothing restricted who could join it. Verified fixed live: a freshly generated, correctly signed random wallet now gets 401 "No workspace for this wallet".
Geo-restriction added the same day: self-custody Treasury/Payout actions are unavailable from the EU/EEA, United States, and China by default — both a technical check (apps/web/src/middleware.ts) and a real per-action jurisdiction-attestation checkbox (apps/api/src/http/schemas.ts, jurisdictionAttestation). See §6's consolidated counsel-questions list for exactly why these three and not others.
Blend/DeFindex on mainnet — deliberately not configured (decided 2026-08-05). Checked precisely, not just asserted: even with Milestone 1's smart account wired in on testnet, assertMainnetHasNoHotKey (packages/config/src/env.ts) still refuses to boot with STELLAR_NETWORK=mainnet if BLEND_SIGNER_SECRET is present — and the smart-account signing path still needs that raw key present (to sign the delegated __check_auth entry), so the guard would trip exactly the same either way. Loosening that guard to special-case a smart-account-bounded key is a real, security-relevant decision, not a config tweak — and even if made, Milestone 3's audit is still required first, "regardless of which signing model it uses" (this doc says so on purpose). No real client is waiting on this yet either. Revisit only after the audit, not before.
Stellar · Soroban (Rust) · @stellar/stellar-sdk v16 (Protocol 23) · Stellar Wallets Kit · Reflector (SEP-40) · Next.js · TypeScript · Express · Supabase (Postgres/Auth/Realtime) · Fly.io · Vercel. Open SDK: contextio-sdk (npm).
Web https://www.contextio.xyz · API https://contextio-api.fly.dev · Repo https://github.com/contextio/Contextio · Demo https://youtu.be/JI7KpNQMo0A · SDK https://www.npmjs.com/package/contextio-sdk