Hire an AI agent, pay it in XLM or in lira. Escrow on Stellar, settled by a Soroban contract; identity, a compliance gate and a TRY rail around it.
Live app · Contract on Stellar Testnet · Deploy runbook · SDK · Agent
AI agents are becoming services you hire for a task: summarise this, translate that, research the other. Paying one today means trusting it up front, or trusting a platform in the middle. Neither side has a guarantee: the client that the work will arrive, the agent that it will be paid.
Square puts the payment in escrow on Stellar and lets the chain settle it. The client funds a job in XLM; the agent delivers; the client has a short window to reject; after that the payout is the agent's, and nobody — not Square, not a platform, not the other party — can hold it back or take it. Everything settles in seconds for a fraction of a cent, which is what makes it work for jobs priced in single XLM.
Who it is for: anyone hiring an agent for a discrete task, and anyone running one. Square ships both sides: an app for the client and an agent runtime that watches the chain, does the work and collects. A client in Türkiye pays in lira: the app puts TRY in through a SEP anchor and the job is funded in USDC, and the agent takes its payout out the same way. An institution puts a private spending mandate in front of every release and the chain checks a zero-knowledge proof against it before the money moves.
Every agent is somebody. Agents carry a did:aip identity, a W3C DID method
registered in the W3C DID Extensions registry and anchored in the 8004 identity registry
on Stellar. A job is opened for a verifiable agent, not a bare address, and its
settlement is written back as reputation: evidence, not a claim.
create_job ──set_budget──▶ Open ──fund──▶ Funded ──submit──▶ Submitted
│ │ │
reject reject reject (inside the window)
▼ ▼ claim_refund ▼ finalize (after it, anyone)
Rejected Rejected / Expired Rejected Completed
- Open. The client opens a job for an agent's address with a description: the work
order (
summarise: the quarterly report). - Price. The agent sets the budget it wants; the client accepts by funding exactly
that amount. A repriced job cannot be funded by surprise (
BudgetMismatch). - Fund. XLM moves from the client into the contract in one signed transaction. No
approvestep exists on Stellar: the client's one authorization covers the call and the token transfer beneath it. - Deliver. The agent runs the job and submits the SHA-256 of its output. The content itself is served by the agent; the hash on chain is what proves it later.
- Window. The client may
rejectforchallenge_windowseconds and gets the whole budget back. Otherwise, once the window has passed, anyone mayfinalize: the agent is credited the budget less the platform fee, the fee goes to the owner. - Withdraw. Credits are pulled, never pushed: the agent (or a refunded client) calls
withdraw_toand the XLM leaves the contract.
A funded job the agent never delivers expires at expired_at, and claim_refund returns
the budget to the client. A delivered job cannot expire: it settles only by reject or
finalize.
flowchart LR
subgraph Client
App["Web app<br/>(Next.js, Stellar Wallets Kit)"]
Wallet["Freighter / any<br/>Stellar wallet"]
end
subgraph Agent["Agent runtime (square-hosted)"]
Loop["Provider loop<br/>watch · work · submit · finalize · withdraw"]
Model["Claude"]
HTTP["HTTP<br/>/jobs/:id/deliverable"]
end
SDK["@squaresdk/core/stellar<br/>simulate → sign → send<br/>anchor client · did:aip"]
RPC["Stellar RPC<br/>(testnet)"]
Kernel["square_job<br/>Soroban contract"]
XLM["XLM / USDC<br/>Stellar Asset Contracts"]
Anchor["TRY anchor<br/>SEP-1 · SEP-10 · SEP-6 · SEP-38"]
Registry["8004 identity registry<br/>did:aip · reputation"]
Prover["Compliance prover<br/>Groth16 on BN254"]
Verifier["groth16_verifier<br/>BN254 host functions"]
App --> Wallet
App --> SDK
Loop --> SDK
Loop --> Model
SDK --> RPC --> Kernel
Kernel -- transfer --> XLM
App -- reads the deliverable --> HTTP
App -- lira in --> Anchor
Loop -- payout to lira --> Anchor
Anchor -- USDC --> XLM
App -- resolves the agent --> Registry
Kernel -- settlement as reputation --> Registry
Prover -- proof --> App
Kernel -- checks the proof --> Verifier
| Component | Where | What it does |
|---|---|---|
square_job |
contracts/contracts/square_job |
The kernel: the state machine above, the escrow and a pull-payment ledger. One Soroban contract, 22.6 KB of Wasm, no upgrade entry point. |
square-common |
contracts/common |
The types, error codes, events, TTL rules and the two-step owner the kernel is built on. |
@squaresdk/core/stellar |
packages/core |
The TypeScript client: every kernel method, the network profile, the deployment record, the signer abstraction, error and event decoding. Arguments and results go through the contract's own generated bindings, so the SDK carries no copy of the interface. |
| Anchor client | packages/core/src/stellar/anchor.ts |
The fiat rail: SEP-1 discovery of an anchor's stellar.toml, SEP-10 sign-in with the wallet's own signer, SEP-38 quotes, SEP-6 deposits and withdrawals followed to their final status. Lira in, USDC out; USDC in, lira out. |
| Identity | packages/did-resolver, packages/did-aip-driver |
did:aip resolution and registration against the 8004 identity registry on Stellar; the agent card the app reads an agent's capabilities and prices from (docs/agent-card). |
| Compliance | circuits/, services/prover, contracts/contracts/groth16_verifier |
The spending-mandate circuit and its prover, and the on-chain check of the 512-byte Groth16 proof through Stellar's BN254 host functions. No proof, no release. |
@squaresdk/agent/stellar |
packages/agent |
The agent for hire: finds the jobs created for its key, works them once funded, submits, finalizes after the window, withdraws, and serves the deliverable behind the hash. Restart-safe: its state lives in a file. |
square-hosted |
packages/hosted |
The agent from a configuration file: each capability's instructions become a Claude run on the job's description. |
| App | app/ |
The client's side: connect a wallet, pick an agent by its did:aip, open, fund in XLM or put lira in through the anchor, read the deliverable, reject or wait, withdraw, take lira out. Reads the job list straight from RPC (getEvents), no indexer. |
| Deploy script | contracts/script/deploy.sh |
Build → upload → deploy at a fixed salt → constructor → read back → write contracts/deployments/<network>.json. |
Soroban authorization instead of msg.sender and approve. Every write takes the
acting address as a parameter and calls require_auth() on it, then compares it with the
job record. fund moves XLM through the native Stellar Asset Contract's SEP-41
transfer inside the client's own authorization tree, so one signature covers the call
and the transfer; the authorization trees were measured on testnet before the contract
was written (auth-and-token-flow.md). The two
cranks, finalize and claim_refund, name nobody: anyone may send them.
Storage and TTL. Jobs and balances are persistent entries, settings are instance
storage. A job's entry is extended to live until expired_at + challenge_window plus one
window of slack; balances are refreshed to the network's minPersistentTtl on every
write. The two network values the rules convert with (ledgerTargetCloseTimeMilliseconds,
minPersistentTtl) are written in by the deploy script and correctable by the owner, so
no ledger count is typed into the contract (fees-and-ttl.md).
Events in the contract spec. The kernel's events are #[contractevent] structs, so
their schema ships inside the Wasm; the SDK and the agent read job_created, funded,
submitted, finalized, rejected, refunded, withdrawn from the spec rather than
from a hand-written table.
Anchors, so the product has a fiat rail. The app talks to a SEP anchor through the
SDK's anchor client: SEP-1 finds the anchor from its home domain, SEP-10 signs the
client in with the same wallet that signs jobs, SEP-38 prices the lira, SEP-6 opens the
deposit and hands back the bank instructions, and the SDK follows the transaction to
completed. The USDC lands in the client's wallet and funds the job; the agent's
withdrawal runs the same path in reverse. On testnet the anchor is
tr-mock-anchor.fly.dev, a sandbox that simulates the bank and pays real testnet USDC.
Identity on the 8004 registries. Agents are registered in the Stellar 8004 identity
registry and resolvable as did:aip, the method this project registered with the W3C
(method-spec-v2.md). The app opens a job for a DID,
resolves it to the address the kernel pays, and reads the agent's card for its
capabilities and prices; a settled job is written back to the reputation registry, so an
agent's history is on chain and nobody's word.
The compliance gate on BN254. An institution's spending mandate stays private: the prover turns a release into a Groth16 proof that the six rules held, and the kernel path checks the 512-byte proof with Stellar's BN254 host functions (CAP-0074, CAP-0080) before the payout is credited. A proof that says "not compliant" is still a valid proof, and it is refused. The trusted setup behind the circuit is a development one and says so (docs/disclosure/).
Ecosystem pieces used. Stellar Wallets Kit
for the app's wallet connection (any SEP-43 wallet is a Signer to the SDK); a SEP
anchor (SEP-1, SEP-10, SEP-6, SEP-38) for the TRY rail; the 8004 identity and reputation
registries; soroban-sdk 27.0.6 and stellar-cli 27.1.0 for the contract, its tests and
its bindings; @stellar/stellar-sdk 16.3.0 for everything in TypeScript; Stellar RPC and
Friendbot on testnet; Stellar Expert for the links.
Stellar Wallets Kit, on the SCF Integration List under Wallet Connection Layers. The test applied is the short one: take it away, and which part of the product stops?
The lifecycle is six steps and five of them are a transaction signed by a party's own
wallet — create_job and fund by the client, set_budget and submit by the agent,
withdraw_to by whoever is owed — set_budget is open to either party, and the agent
is the one that prices in this flow. Only finalize names nobody. In the app the kit is
where every one of those signatures comes from, across Freighter, xBull, Albedo, Lobstr
and Hana, and it hands back exactly the two SEP-43 calls the SDK's Signer is made of,
so nothing is adapted between them: packages/core/src/stellar/signer.ts is already the
shape the kit has. Remove it and there is no client side at all.
What that claim does not cover, because a decision that overstates itself is worse
than none: today's flow signs through signTransaction alone — an account that submits
its own transaction authorizes through the envelope signature, and signAuthEntry is the
path for a signer that is not the submitter. And the agent runtime does not use the kit;
a server process has no browser extension to ask, so it holds its own key through
keypairSigner. The kit is load-bearing for the client side of a two-sided product.
The TRY anchor is the second integration, and the one that makes the product usable by someone who holds lira and not XLM: the wallet that signs jobs also signs the anchor's SEP-10 challenge, and the USDC the anchor pays is what funds the USDC kernel. Take it away and a client in Türkiye is back to buying XLM on an exchange first.
CCTP is the cross-chain route into the USDC kernel — USDC burnt on Ethereum, Base or
Arbitrum and minted on Stellar — documented and read back from the chain rather than
wired into the app: the fiat route is the anchor. Its three Stellar Testnet contracts
answer their specs; MessageTransmitter answers get_local_domain() → 27.
Blend v2 was evaluated and refused, against an earlier steer: it is struck from the
official SCF Integration List, and the only Blend pool on Stellar Testnet prices its
collateral through a contract whose published interface carries set_price — the prices
are written by an administrator. The full reasoning, with everything read from the chain,
is in core-integration.md.
From skills.stellar.org. Each row names the file and the
part of this repository it bears on, so the citation can be checked rather than taken on
trust. Every path is in stellar/stellar-dev-skill unless another repository is given.
| Skill file | Where it bears on this repository |
|---|---|
skills/standards/SKILL.md |
The standards the code implements: SEP-41, the token interface fund calls inside the client's own authorization tree; SEP-43, which is the shape of Signer in packages/core/src/stellar/signer.ts and therefore of every wallet; SEP-53, signed actions in packages/hardening/src/signedMessages.ts; CAP-0073 trust, which is what SquareClient.trustToken sends. |
skills/smart-contracts/SKILL.md, skills/smart-contracts/development.md |
The kernel: authorization taken as a parameter and checked with require_auth rather than read from a caller, instance storage for settings against persistent storage for jobs and balances, and the TTL arithmetic in contracts/common/src/ttl.rs. The reasoning is in auth-and-token-flow.md and fees-and-ttl.md. |
skills/smart-contracts/testing.md |
The kernel's own suite, contracts/contracts/square_job/src/test.rs: moving ledger time, asserting authorization trees, reading #[contractevent] structs back out of the environment. |
skills/assets/SKILL.md |
The Stellar Asset Contract path — packages/core/src/stellar/usdc.ts and the client's trustline, assertReceivable and tokenBalance: why native XLM needs no trustline and an issued asset does, which is why the MVP is paid in XLM. |
skills/data/SKILL.md |
How the chain is read: SquareClient.getEvents over the kernel's own events, and the latestLedgerCloseTime each page carries as the clock the agent runtime's finalize runs on; getHealth for the latest ledger; getLedgerEntries on the account entry for a native balance, the SAC's balance entry for a contract's. The app adds getLedgers for the close time its countdowns run on and Horizon for an account's XLM. |
skills/dapp/SKILL.md |
The app's wallet layer: Stellar Wallets Kit behind the same Signer the SDK takes, so the browser, the anchor sign-in and a script all sign the same way. |
skills/cross-chain/cctp.md |
Where CCTP would sit, written up in cctp-funding.md for the EVM side and re-read for Stellar: domain 27, and the rule that a transfer into Stellar names CctpForwarder as both mintRecipient and destinationCaller. The decision on whether it carries the product is #57. |
SKILL.md (CheesecakeLabs/stellar-anchor-skill) |
The anchor client packages/core/src/stellar/anchor.ts: SEP-1 discovery of a stellar.toml, SEP-10 sign-in with the wallet's own signer, SEP-6 deposit and withdrawal with their status, SEP-38 quotes; and the app's lira-in and lira-out flows built on it. |
skills/zk-proofs/SKILL.md |
The compliance circuit circuits/payment.circom, Poseidon over BN254's scalar field, groth16-on-soroban.md for what checking it on Soroban costs, and contracts/contracts/groth16_verifier, the check itself through the BN254 host functions. |
| Network | Stellar Testnet (Test SDF Network ; September 2015, CAIP-2 stellar:testnet) |
| RPC / Horizon / Friendbot | https://soroban-testnet.stellar.org · https://horizon-testnet.stellar.org · https://friendbot.stellar.org |
| Payment tokens | Native XLM through its Stellar Asset Contract CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC (7 decimals; every account holds it, no trustline); USDC from Circle's testnet issuer GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5 for the anchor rail (a trustline, which trustToken opens) |
| Fees | XLM, paid by whoever submits the transaction |
| TRY anchor | tr-mock-anchor.fly.dev — SEP-1 stellar.toml, SEP-10 at /auth, SEP-6 at /sep6, SEP-12 and SEP-38; a sandbox whose bank leg is simulated and whose USDC is real testnet USDC |
| Identity | The Stellar 8004 identity registry CDE3K4COIAGWNNJQQLL26SYI3KBJF5FUDHXG5FA6GYDJCG7T5V7FIWZH; Square's agents 29 and 30 registered in 2c7248b0… (ledger 4765322), resolvable as did:aip |
| Compliance | Groth16 on BN254: CAQVJ4EX…TULE checked a 512-byte proof from this repository's prover with the CAP-0074/0080 host functions (groth16-on-soroban.md) |
| Contract | Address | Parameters |
|---|---|---|
square_job |
CATY3ZGNSS44HY4GAPBBAWQUW4E7YHNHG7GVFLUWPJPO22WP3O5YZVII |
challenge window 30 s, platform fee 2.5 %, native XLM; Wasm e497c6bb…c2e1, built from this tree |
square_job (USDC) |
the square_job_usdc entry of contracts/deployments/testnet.json, written by deploy-testnet.sh with TOKEN=usdc |
the same Wasm, paid in USDC: the kernel the anchor rail funds |
The 30-second window is the demo's, so a judge can watch a job settle. That address is
the deployment: it is what contracts/deployments/testnet.json names and, copied, what
@squaresdk/core's deploymentFor("stellar:testnet") answers, so the app, the agent
runtime and the SDK all resolve the same kernel. Deployments use a fixed salt, so a
testnet reset reproduces the same address from the same deployer and Wasm
(docs/deploy/stellar-mvp.md). npm --prefix packages/core run check:deployed-wasm asks the chain which Wasm the contract runs and compares it with
the record and the working tree's build; it passes on this deployment, which runs the
same e497c6bb…c2e1 this tree builds.
contracts/script/deploy-testnet.sh deploys with production parameters (120 s, 1 %)
unless CHALLENGE_WINDOW and PLATFORM_FEE_BPS say otherwise. Its defaults were run
end to end on 2026-09-20 at
CBSPPHW2…KFJQ
(the run), which is how
the script itself is known to work; the demo above is the deployment the record names.
What ran against it, all from the SDK: create (0.0135 XLM in fees), price, fund 2.5 XLM
(0.0026 XLM), submit, a finalize inside the window refused in simulation (WindowOpen,
nothing sent), finalize after it (payout 2.4375, fee 0.0625), withdraw; and the agent
runtime, hired by a fresh account, finding the job by itself, working it, submitting,
finalizing and withdrawing. packages/core/test/stellar/live.test.ts and
packages/agent/test/stellar-live.test.ts are those runs, repeatable with
STELLAR_LIVE=1 STELLAR_KERNEL=<contract>.
Job 4 is the same path run by npm --prefix packages/core run lifecycle:stellar, which
writes every transaction hash, ledger and fee it charged to
docs/deploy/lifecycle-testnet.md: 10 XLM funded,
9.75 paid out, 0.25 kept, and 0.0328744 XLM of network fees across the six
transactions.
The app. Live at square.up.railway.app (a static export
served by nginx, built from app/Dockerfile; the same build is published to
GitHub Pages on every push to
main). It also runs from the repository against the same deployed kernels:
(cd packages/core && npm install --install-links && npm run build)
cd app && npm install --install-links && npm run dev # http://localhost:3000Connect Freighter on testnet — Friendbot funds a new testnet account with 10,000 XLM —
then open a job for an agent's did:aip, set a budget, fund it, and watch the window.
To pay in lira instead, sign in to the anchor from the same wallet, take the SEP-38
quote, follow the SEP-6 deposit (the sandbox's bank page settles it), and fund the USDC
job with what arrives.
Run an agent yourself. A configuration names the capabilities and their instructions; the runtime does the rest.
cd packages/hosted && npm install --install-links && npm run build
SQUARE_NETWORK=stellar:testnet \
SQUARE_SECRET_KEY=S… # a Friendbot-funded key: jobs are created for it
SQUARE_DEPLOYMENT_FILE=../../contracts/deployments/testnet.json \
ANTHROPIC_API_KEY=sk-ant-… \
node dist/bin.js atlas.json # see packages/hosted/README.md for atlas.jsonIt logs each job as it is discovered, worked, submitted, finalized and paid, and serves
GET /jobs/<id>/deliverable for the client.
Drive the flow from code.
import { connectSquareClient, deploymentFromJson, keypairSigner, STROOPS_PER_XLM } from "@squaresdk/core/stellar";
const deployment = deploymentFromJson(JSON.parse(readFileSync("contracts/deployments/testnet.json", "utf8")));
const client = await connectSquareClient({ deployment, signer: keypairSigner(Keypair.fromSecret(secret), deployment.networkPassphrase) });
const { result: jobId } = await client.createJob({ provider: agentAddress, expiredAt: now + 3600n, description: "summarise: the quarterly report" });
await client.fund(jobId, (25n * STROOPS_PER_XLM) / 10n); // 2.5 XLM, the price the agent set
const job = await client.getJob(jobId); // status, deliverable hash, finalizeAfterEvery write is simulated first; a refusal comes back as the contract's own error by name
(WindowOpen, NotClient, BudgetMismatch, …) and nothing is signed or sent.
Deploy your own kernel.
stellar keys generate --fund --network testnet square-testnet-deployer
BROADCAST=0 contracts/script/deploy-testnet.sh # review every parameter, send nothing
contracts/script/deploy-testnet.sh # deploy, write contracts/deployments/testnet.json
SQUARE_NETWORK=testnet npm --prefix packages/core run lifecycle:stellar # one job end to end, with every fee recordedNothing below needs a key of ours, an API key, or trust in this README: every step reads the chain, and every claim it makes can be checked against it.
1. The contract is real, and it is the one this tree builds. No toolchain needed:
$ curl -s https://api.stellar.expert/explorer/testnet/contract/CATY3ZGNSS44HY4GAPBBAWQUW4E7YHNHG7GVFLUWPJPO22WP3O5YZVII
{"contract":"CATY3ZGN…","created":1789853902,"creator":"GC6QXBEC…","wasm":"e497c6bbea72b06c9080281b1b08d9ec5ee2b3b01154584eb8332ee22e81c2e1",…}That wasm is the sha256 of square_job.wasm. With the toolchain
(stellar-target.md pins the versions), the same
number comes out of a local build, and one command checks all three at once — the
record, the contract instance on chain, and the bytes in contracts/target:
$ cd contracts && stellar contract build --package square_job
$ shasum -a 256 target/wasm32v1-none/release/square_job.wasm
e497c6bbea72b06c9080281b1b08d9ec5ee2b3b01154584eb8332ee22e81c2e1 target/wasm32v1-none/release/square_job.wasm
$ cd .. && npm --prefix packages/core run check:deployed-wasm -- testnet
square_job CATY3ZGN…ZVII e497c6bbea72… okThe Cargo workspace is contracts/, so the build runs from there; check:deployed-wasm
runs from the repository root.
2. The settlement works, and here is a job that ran. Job 4 went through the whole
path on 2026-09-20; every hash, ledger and fee is in
docs/deploy/lifecycle-testnet.md, and each links to
stellar.expert. 10 XLM funded, 9.75 paid out, 0.25 kept as the platform fee, 0.0328744
XLM of network fees across six transactions. The one that proves the window is real is
the refusal: a finalize sent inside the window came back WindowOpen from the
simulation, so nothing was ever sent.
3. Run one yourself. Two Friendbot accounts, no keys of ours:
$ (cd packages/core && npm install --install-links && npm run build)
$ SQUARE_NETWORK=testnet npm --prefix packages/core run lifecycle:stellarIt creates, prices, funds, submits, waits out the window, finalizes and withdraws, and writes its own dated report next to the one above.
4. Read the contract's own interface, rather than ours.
$ stellar contract info interface --id CATY3ZGNSS44HY4GAPBBAWQUW4E7YHNHG7GVFLUWPJPO22WP3O5YZVII \
--rpc-url https://soroban-testnet.stellar.org --network-passphrase "Test SDF Network ; September 2015"Everything the SDK and the app call is in that list, because both encode through the contract's generated bindings: a method the kernel does not export is a compile error, not a runtime one.
5. The tests. cargo test --locked in contracts/, npm test in packages/core,
packages/agent and packages/hosted. The section below says what each covers.
| Where | What |
|---|---|
contracts/ — cargo test --locked |
The kernel against a real Stellar Asset Contract in the Soroban test host: the optimistic path with every event checked, the reject paths, expiry, every guard, unsigned calls, owner functions under a stranger's authorization, two-step ownership, TTL, fee arithmetic, and a solvency invariant (balance == escrowed + withdrawable) over 400 random actions. |
packages/core — npm test |
The client against real testnet RPC answers captured as fixtures and return values encoded with the contract's spec; the deployment record; events; amounts; signers; the anchor client against an anchor that issues real SEP-10 challenges. |
circuits/, services/prover — npm test |
The mandate circuit's constraints, and the prover producing proofs the on-chain check accepts and refusals it rejects. |
packages/agent, packages/hosted — npm test |
The provider loop against an in-memory kernel (lifecycle, the ledger's clock, capability choice, price, retries, resume from disk, rejection and expiry), the HTTP surface, the model runner with a scripted model. |
Live — STELLAR_LIVE=1 STELLAR_KERNEL=C… npm test |
The same flows against the testnet contract, from Friendbot accounts; and the TRY rail against tr-mock-anchor.fly.dev: sign-in, quote, deposit, the simulated bank transfer, USDC in the wallet. |
Every pull request runs the first three, plus a secret scan and a check that the committed bindings match the contracts.
Each is a record in docs/decisions/, written before the code:
- Testnet, Protocol 27, versions pinned —
soroban-sdk =27.0.6,stellar-cli 27.1.0,@stellar/stellar-sdk 16.3.0, Rust 1.98.1 (stellar-target.md). - The window model — the client's silence is consent; anyone may finalize; rejection is a full refund. No evaluator, no arbitration in the MVP: the simplest settlement that protects both sides.
- Explicit signers, no
approve,i128at the boundary andu64in the record (auth-and-token-flow.md). - TTL by rule, not by number (fees-and-ttl.md).
- No upgrade entry point. The escrow's code cannot change under a user's funds; CI fails any Wasm that imports
update_current_contract_wasm(upgradeability-and-governance.md). - XLM first, USDC through the anchor. Every account holds XLM and needs no trustline, so a client can fund a job the minute Friendbot pays it. The kernel and the SDK are token-generic (
trustline,tokenBalance,trustToken), and a second deployment of the same Wasm is paid in USDC: that is the one the lira arrives in. - A rail, not an exchange. Lira enters through a SEP anchor the wallet itself signs into, never through a custodian of Square's; the USDC is the client's before it is the job's.
- The gate checks a proof, not the mandate. The institution's rules never leave its prover; what the chain sees is a Groth16 proof and a verdict.
Trade-offs taken. The deliverable's content lives with the agent, only its hash on chain: cheap, and verifiable by anyone who has the content. The agent's price is enforced by the agent, not the kernel: a job funded below it is left alone. The window is a deployment parameter, not per job: one rule, one thing to explain.
Challenges. Soroban has no msg.sender and no allowance, so the whole flow had to be
redesigned around authorization trees and measured on chain first. Ledger entries expire
(state archival), so every write had to reason about how long its entries must live. The
testnet is reset a few times a year, so addresses are reproduced by script rather than
kept. The generated bindings put the event schema and the error table in the Wasm, and
the SDK was built to read them from there rather than duplicate them. A SEP-10 challenge
has to be verified before it is signed — the anchor's signing key, the home domain, the
web-auth domain and the sequence number — or the wallet signs whatever a server sends;
the client reads the challenge with the SDK's WebAuth and refuses one that does not
match the stellar.toml. The Groth16 proof had to be re-encoded for the host: snarkjs
and BN254 on Soroban order the G2 coordinates differently, and the swap is measured in
groth16-on-soroban.md.
The next step is an SCF / InstAward application for the parts that come after the testnet product:
- Mainnet: production parameters, the deployment record and the addresses reproduced by script.
- More rails: EUR and USD anchors beside TRY, so a client pays in their own currency and the agent is paid in theirs.
- A marketplace: agents found by capability and 8004 reputation in the app, priced per job, hired in one signature.
- The ceremony: a public phase-2 ceremony for the compliance circuit, so the proving key is nobody's. Until then the setup is a development one and says so (docs/disclosure/).
- Disputes: an optimistic evaluator with a paid finalize, and bonded arbitration.
contracts/ Soroban workspace: square_job (the kernel), common, deploy scripts, tests;
the earlier Arc (EVM) contracts remain for reference
packages/ core (the SDK, @squaresdk/core/stellar, with the anchor client), agent
(@squaresdk/agent/stellar), hosted (square-hosted), hardening (SEP-53, RPC
failover), did-resolver and did-aip-driver (did:aip), policy (the mandate),
and the packages of the earlier version (a2a, mcp, x402, aa, data,
observability, cli)
app/ The client's web app (Next.js static export, Stellar Wallets Kit, the TRY rail)
site/ The website
docs/ Decision records, the deploy runbook, measurements, disclosure, the did:aip spec
circuits/, services/prover The compliance circuit and its prover
services/ indexer, keeper and screener of the earlier version
Square was first built for Arc (EVM) and Solana; this repository is its Stellar version. The design records, the circuit and the prover carry over; the on-chain layer, the SDK and the agent were written for Soroban. Code of the earlier version that the MVP does not use is still in the tree and marked as such in its own READMEs.
Apache-2.0. See LICENSE. NOTICE carries the SIL OFL 1.1 notice of the Open Runde typeface the app and the site are set in.
Stellar is a trademark of the Stellar Development Foundation. Square is built on Stellar and is not affiliated with or endorsed by the Stellar Development Foundation.