- Node.js >= 22 (required for
node:sqlitebuilt-in module) - pnpm >= 9 (workspace package manager)
- Rust toolchain (for Soroban contracts, Phase 3+)
pnpm install --frozen-lockfilecp .env.example .env
# Edit .env with your testnet credentialsThe API server loads .env from the repository root automatically
(node --env-file-if-exists) when started via pnpm --filter @4evergent/api start
or pnpm --filter @4evergent/api dev. Real environment variables always take
precedence over .env values. .env is gitignored — never commit it.
Note: the live smoke test and direct node invocations do NOT load .env;
export variables in your shell for those (export STELLAR_TESTNET_SECRET_KEY=...).
# Run live smoke test (no secrets required, only reads Horizon root)
npx tsx packages/stellar/test/live-smoke.tsThis verifies:
- Horizon endpoint is reachable
- Network passphrase matches Testnet
- StellarSimulator can call real Horizon API (simulation gate works)
# Generate a testnet keypair
node -e "const {Keypair}=require('@stellar/stellar-sdk'); const k=Keypair.random(); console.log('Public:', k.publicKey()); console.log('Secret:', k.secret());"
# Fund the account via friendbot
curl "https://friendbot.stellar.org?addr=<YOUR_PUBLIC_KEY>"
# Set environment
export STELLAR_TESTNET_SECRET_KEY=<YOUR_SECRET_KEY>node -e "
const {StellarAdapter} = require('./packages/agent-core/dist/index.js');
const a = new StellarAdapter('https://horizon-testnet.stellar.org');
a.getAccount('<YOUR_PUBLIC_KEY>').then(acc => {
console.log('Address:', acc.address);
console.log('Sequence:', acc.sequence);
console.log('Balances:', acc.balances);
});
"pnpm test # 290 tests PASS# Start API server (without LIVE_SUBMIT)
pnpm --filter @4evergent/api start
# Submit intent for simulation/approval
curl -X POST http://localhost:3000/agents/<agent-id>/intents \
-H "Content-Type: application/json" \
-d '{"type":"payment","asset":"XLM","destination":"G...","amount":"0.001","reason":"test"}'Intent will be simulated against real Testnet Horizon. If policy allows and no approval required, it will be submitted ONLY if LIVE_SUBMIT=1.
# Stop server, restart with LIVE_SUBMIT=1
LIVE_SUBMIT=1 pnpm --filter @4evergent/api startWith LIVE_SUBMIT=1, intents that pass policy + simulation + approval will be signed and submitted to Testnet.
curl http://localhost:3000/agents/<agent-id>/executionsOr check on Stellar Expert with your txHash.
# Without secrets — only verify network + simulation
npx tsx packages/stellar/test/live-smoke.tsFull live E2E validation (signing + submission + on-chain confirmation + reconciliation). Runs against the real Stellar Testnet.
# OPT-IN GATES (both required):
# - STELLAR_TESTNET_SECRET_KEY: funded TESTNET key (never committed)
# - LIVE_SUBMIT=1: explicit live submission enable
# 1. Fund a testnet account (once):
# node -e "const {Keypair}=require('@stellar/stellar-sdk'); const k=Keypair.random(); console.log(k.secret());" > /tmp/stellar-secret
# curl "https://friendbot.stellar.org?addr=<PUBLIC_KEY_FROM_ABOVE>"
# 2. Run live E2E:
STELLAR_TESTNET_SECRET_KEY=$(cat /tmp/stellar-secret) LIVE_SUBMIT=1 \
npx tsx packages/stellar/test/live-e2e.tsExpected output (all 8 steps pass):
network: Test SDF Network ; September 2015
network_guard: testnet=accept, mainnet=reject
policy: allow
simulation: success
outcome: submitted
tx_hash: <64-hex>
submission: submitted
confirmation: confirmed in ledger <N>
reconciliation: submitted → confirmed
result: PASS
Without STELLAR_TESTNET_SECRET_KEY the script runs the network + simulation
preflight only and stops before signing. Without LIVE_SUBMIT=1 it stops
before submission (simulation gate verified, no real transaction sent).
TestnetLocalSigner (packages/stellar/src/testnet-local-signer.ts) is development infrastructure. It:
- Loads a secret key from
STELLAR_TESTNET_SECRET_KEY - Is testnet-only (the pipeline asserts the network passphrase matches)
- Never exposes the secret key through any method
- Never returns the secret key through the
Signerinterface - Is never returned through API responses
- Is never logged
| Field | Value |
|---|---|
| Contract | contracts/agent-registry (crate agent-registry) |
| Network | Stellar Testnet only (Test SDF Network ; September 2015) |
| Contract ID | CDXQRPVGMPJB5UXQHKFAPBLK6G37DSVKDW2BKRAF76RO5ENCCB4W3QX7 |
| Deployed | 2026-09-28 (UTC) |
| Deployer | local CLI identity deployer (testnet key, never committed) |
| Built wasm | contracts/target/wasm32-unknown-unknown/release/agent_registry.wasm, 9,549 bytes optimized |
| Wasm hash | 83656ffddd08a22e1977b6cf129af4d6515be7e8494d843ded1aa1c0ef3f8445 |
| Explorer | https://stellar.expert/explorer/testnet/contract/CDXQRPVGMPJB5UXQHKFAPBLK6G37DSVKDW2BKRAF76RO5ENCCB4W3QX7 |
| Stellar Lab | https://lab.stellar.org/r/testnet/contract/CDXQRPVGMPJB5UXQHKFAPBLK6G37DSVKDW2BKRAF76RO5ENCCB4W3QX7 |
| Upload tx | https://stellar.expert/explorer/testnet/tx/baebfc6bddb46e406fd5cd6d202798983c733498fc9952ba1e14f141071a2d1c |
| Create tx | https://stellar.expert/explorer/testnet/tx/bd88d3b35cbd02eb00056d9169848093e7ade2d6f3f2e4069c00f09d7fa9247f |
contracts/permissions is not deployed on any network: it is excluded from
the contracts workspace, is not compiled or tested, and lacks authorization
checks. See contracts/permissions/README.md.
Pinned toolchain comes from contracts/rust-toolchain.toml (Rust 1.81.0 with the
wasm32-unknown-unknown target). Install the CLI if missing:
cargo install --locked stellar-cliVerified with: rustc --version, cargo --version, stellar --version,
rustup target list --installed.
Always run cargo from contracts/ so the pinned toolchain and Cargo.lock apply:
cd contracts
cargo test --locked
cargo +1.81.0 test --locked # 15 unit tests expected
stellar contract build # wasm artifact + optimized hash printedArtifact: contracts/target/wasm32-unknown-unknown/release/agent_registry.wasm.
Create and fund a throwaway testnet identity. The secret key stays in the local
CLI config (~/.config/stellar/identity/, mode 0600) — never commit it, never
print it, and .gitignore already covers .env*:
stellar keys generate deployer --network testnet --fund
stellar keys address deployer # public key onlyDeploy and give it a local alias:
cd contracts
stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/agent_registry.wasm \
--source deployer --network testnet --alias agent_registry--network testnet is required and there is no mainnet path in this repo. The
command prints the contract ID; it also stores the ID under the alias in the
local CLI config.
# read-only call (query returns null for an id that was never registered)
stellar contract invoke --id --source deployer --network testnet --send=no \
-- query --id not_registered
# write then read back
stellar contract invoke --id --source deployer --network testnet \
-- register --caller $(stellar keys address deployer) --id deploy_smoke \
--display_name "Deploy Smoke" --stellar_address $(stellar keys address deployer) \
--capabilities '["pay"]'
stellar contract invoke --id --source deployer --network testnet --send=no \
-- query --id deploy_smoke
# confirm the on-chain wasm matches the local build (sha256 == wasm hash above)
stellar contract fetch --id --network testnet --out-file /tmp/fetched.wasm
sha256sum /tmp/fetched.wasmVerified on 2026-09-28: query on an unknown id returned null; after
register, query returned the record with "active": true; the fetched wasm
sha256 equalled 83656ffddd08a22e1977b6cf129af4d6515be7e8494d843ded1aa1c0ef3f8445.
- Network validation: Only Testnet URL + passphrase accepted. Mainnet explicitly rejected.
- Live submission gate:
LIVE_SUBMIT=1required. Default = disabled. - Secret isolation: Private key never in database, logs, API responses, error messages.
- Atomic claim:
updateIfStatus(queued→executing)CAS prevents concurrent execution. - Idempotency:
Idempotency-Keyheader prevents duplicate financial execution. - Pre-check: Horizon lookup before blind retry prevents duplicate submission.
- Only Stellar Testnet supported. Mainnet execution not supported.
- Exactly-once not guaranteed (Stellar classic limitation).
- Single-process architecture (no distributed workers).
- Production authentication not implemented.
- No live transaction has been executed by CI. All tests use deterministic mocks.