Working directory: Run deployment commands from the repository root. Each step begins with
cd "$(git rev-parse --show-toplevel)"so it can be run independently from any directory inside the repository. Paths to build artifacts below are relative to the repository root.
| Network | Contract ID |
|---|---|
| Stellar Testnet | CAUAO7CYKULE2K4EJMQ6LLRUHP7Y7JYOH6G2VBXKYG7PTETE3UZ3DU7Q |
- Contract: https://lab.stellar.org/r/testnet/contract/CAUAO7CYKULE2K4EJMQ6LLRUHP7Y7JYOH6G2VBXKYG7PTETE3UZ3DU7Q
- Deploy TX: https://stellar.expert/explorer/testnet/tx/8587efcf3c302c878eccca0d6e00fb06cb8e09d535675a10f35f98541835d63f
| Value | |
|---|---|
| Network | Stellar Testnet |
| RPC URL | https://soroban-testnet.stellar.org |
| Network Passphrase | Test SDF Network ; September 2015 |
| Soroban SDK Version | 22.x (Protocol 26 compatible) |
| Explorer | https://stellar.expert/explorer/testnet |
Each GitHub Release publishes a .sha256 checksum and a .asc GPG detached
signature alongside every WASM/CLI binary artifact.
- Download the artifact, its
.sha256file, and its.ascfile from the Releases page. - Verify the checksum:
sha256sum -c trellis-x86_64-unknown-linux-gnu.sha256
- Verify the GPG signature (import the maintainers' public key first):
gpg --verify trellis-x86_64-unknown-linux-gnu.asc trellis-x86_64-unknown-linux-gnu
- Rust stable toolchain
wasm32-unknown-unknowntarget:rustup target add wasm32-unknown-unknown
- stellar CLI 26.x+: https://developers.stellar.org/docs/tools/cli/install-cli
cd "$(git rev-parse --show-toplevel)"
stellar keys generate trellis-deployer --network testnet
stellar keys fund trellis-deployer --network testnet --rpc-url https://soroban-testnet.stellar.orgcd "$(git rev-parse --show-toplevel)"
cargo rustc --manifest-path=contracts/trellis_core/Cargo.toml --crate-type=cdylib --target=wasm32-unknown-unknown --releaseNote: Use
cargo rustcwith--target=wasm32-unknown-unknownrather thanstellar contract build— the latter targetswasm32v1-nonewhich requires soroban-sdk 22.x+ and a newer toolchain. The manualcargo rustccommand gives you full control over the target.
The compiled WASM will be at:
target/wasm32-unknown-unknown/release/trellis_core.wasm
cd "$(git rev-parse --show-toplevel)"
stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/trellis_core.wasm \
--source trellis-deployer \
--network testnetThis returns a C... contract address. Save it — you'll need it for all CLI commands.
cd "$(git rev-parse --show-toplevel)"
export STELLAR_RPC_URL="https://soroban-testnet.stellar.org"
export STELLAR_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
export TRELLIS_CONTRACT_ID="<your contract ID>"
export TRELLIS_SOURCE_KEY="<your stellar identity name>"If you sign with a raw S… secret seed instead of a named stellar keys
identity, put it in a file instead of exporting it. Environment variables
end up in /proc/<pid>/environ, shell history and .env files:
umask 077
printf '%s\n' 'S...your-secret...' > ~/.trellis-payer.key # mode 0600
export TRELLIS_SOURCE_KEY_FILE=~/.trellis-payer.key
# or per invocation:
trellis --source-key-file ~/.trellis-payer.key status --agreement-id <hex>The source key is resolved in this order (first match wins):
--source-key-file <PATH>TRELLIS_SOURCE_KEY_FILETRELLIS_SOURCE_KEY
A configured key file always wins. If TRELLIS_SOURCE_KEY is also set, the
CLI prints a note saying it was ignored. If a configured key file is missing
or empty, the command fails instead of falling back to TRELLIS_SOURCE_KEY.
On Linux/macOS a warning is printed when the key file is readable by the
group or other users (anything other than 0600/0400). Whitespace and a
trailing newline around the key are trimmed.
Escrow commands have to be signed by different parties: the payer, the payee
or the dispute resolver. You don't need to re-export TRELLIS_SOURCE_KEY
before each one. Put all three identities in .env and pick one per command
with --as:
# .env
TRELLIS_PAYER_KEY=trellis-payer # stellar identity name or S… seed
TRELLIS_PAYEE_KEY=trellis-payee
TRELLIS_RESOLVER_KEY=trellis-resolver
# Or point at key files instead (they take precedence over the *_KEY vars):
# TRELLIS_PAYER_KEY_FILE=~/.trellis-payer.keytrellis --as payer lock-funds --agreement-id <hex> --milestone-id 0--as |
Key file variable (checked first) | Key variable |
|---|---|---|
payer |
TRELLIS_PAYER_KEY_FILE |
TRELLIS_PAYER_KEY |
payee |
TRELLIS_PAYEE_KEY_FILE |
TRELLIS_PAYEE_KEY |
resolver |
TRELLIS_RESOLVER_KEY_FILE |
TRELLIS_RESOLVER_KEY |
If the chosen role's variables aren't set, --as fails. It never falls back
to TRELLIS_SOURCE_KEY, so a missing entry can't make a command sign with
the wrong identity. Without --as, the CLI uses TRELLIS_SOURCE_KEY exactly
as before. --as cannot be combined with --source-key-file.
cd "$(git rev-parse --show-toplevel)"
cargo build --release --manifest-path=cli/trellis_cli/Cargo.tomlBinary will be at target/release/trellis.exe (Windows) or
target/release/trellis (Linux/Mac), relative to the repository root.
trellis --as payer init \
--agreement-id <64-char hex string> \
--payer <payer G... address> \
--payee <payee G... address> \
--token <USDC SAC contract address> \
--resolver <dispute resolver G... address> \
--milestones "1000,2000"The
--milestonesflag accepts comma-separated amounts in stroops. Multiple milestones:--milestones "1000,2000,3000"
trellis status --agreement-id <hex>trellis total-amount --agreement-id <hex>Read-only; no signature or confirmation needed. Returns the sum of all milestone amounts (an
i128in the token's base unit, printed as a quoted string). The frontend's agreement status page shows the same value as Total in the Milestones header.
trellis --as payer lock-funds --agreement-id <hex> --milestone-id 0Must be signed by the payer identity (
--as payer)
trellis batch-lock-funds --agreement-id <hex> --milestone-ids "0,1,2"Must be signed by the payer identity. The batch is atomic: if any listed milestone is out of range or not
Pending, the whole transaction reverts and no tokens move.--milestone-idstakes comma-separated zero-based indices; empty input, empty fields ("0,,1"), non-integers and duplicate IDs are rejected by the CLI before anything is submitted.
trellis --as payee submit-work \
--agreement-id <hex> \
--milestone-id 0 \
--proof-uri "ipfs://your-proof-hash"Must be signed by the payee identity (
--as payee)
--proof-uri is optional — omit it to mark the milestone submitted without a
proof link. Do not pass an empty string: the contract stores proof_uri as an
Option, so "no proof" is the absent flag, not "".
The web UI follows the same rule: leaving the Proof URI field blank submits
None, not an empty string.
trellis --as payer approve-release --agreement-id <hex> --milestone-id 0Must be signed by the payer identity (
--as payer)
trellis --as payer raise-dispute \
--agreement-id <hex> \
--milestone-id 0 \
--caller <payer address>Can be called by either the payer or the payee. Use
--as payeewith the payee's address as--callerto raise it from the payee side.
In the web UI, the Dispute button only appears when the connected wallet is the agreement's payer or payee; any other wallet sees an explanatory note instead.
trellis --as resolver resolve-dispute \
--agreement-id <hex> \
--milestone-id 0 \
--refund-to-payer trueMust be signed by the dispute resolver identity (
--as resolver).--refund-to-payer truereturns funds to payer.--refund-to-payer falsereleases funds to payee.
trellis --as payer cancel-milestone --agreement-id <hex> --milestone-id 0Must be signed by the payer (
--as payer). Only works on milestones still inPendingstatus.
trellis extend-ttl --agreement-id <hex> --caller <your G... address>Any address may call this — no party signature is required — but the source account pays the rent.
--calleris recorded in thettl_extendedevent; pass the source key's public address. Prompts for confirmation unless--yesis given.
trellis health
trellis --json health # {"status":"success","result":{"health":{...},"latest_ledger":{"hash","protocol_version","sequence"}},...}Calls the Soroban
getHealthandgetLatestLedgerJSON-RPC methods directly over HTTP. Thestellarbinary,TRELLIS_CONTRACT_IDandTRELLIS_SOURCE_KEYare all optional here, since only the RPC URL is used. Exits non-zero if the endpoint is unreachable or does not reporthealthy.--dry-runprints the JSON-RPC requests without sending them.
The USDC Stellar Asset Contract address on testnet:
CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA
To derive it yourself:
stellar contract id asset \
--network testnet \
--asset USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5To get testnet USDC, visit the Stellar testnet faucet: https://developers.stellar.org/docs/tools/quickstart#friendbot
A live test agreement was created on testnet during initial deployment verification:
| Field | Value |
|---|---|
| Agreement ID | 0101010101010101010101010101010101010101010101010101010101010101 |
| Payer | GDTFKGGBI4GGHSZ6ILTTJJTXOA4CIKCT6J2DRC3Q3OT3YMWGB7TBLXMK |
| Payee | GCGX32T5547AE57PUIAX5CDQWKHHEQFYOPIGXH7O3CENKDX3FR6U67RY |
| Milestone 0 | 1000 stroops, status: Pending |
Note — testnet state is ephemeral: Stellar resets testnet ledger state periodically (roughly quarterly). When that happens, the contract instance above and this agreement ID are wiped, and
trellis statuswill returnagreement not found. If the command below fails, see Testnet Reset to redeploy and create fresh test data.
Verify it live:
trellis status --agreement-id 0101010101010101010101010101010101010101010101010101010101010101Everything above is testnet-only. A mainnet deployment moves real funds and some decisions cannot be undone, so add these precautions:
- Key management: use a dedicated hardware-backed or offline-generated deployer identity (
stellar keys add trellis-mainnet-deployer --seed-phrase/ ledger). Never reuse testnet keys, never store the secret in.env, CI variables, or shell history. - Review gates: deploy only a WASM built from a tagged, reviewed release (see "Verifying Release Artifacts"); compare its sha256 against the release attestation.
make deploy-mainnetprints the hash and requires typingmainnetto confirm. - Irreversibility:
initparameters and any upgrade/admin decisions are permanent once submitted. Dry-run the full flow on testnet first, and confirm the admin and fee settings with a second reviewer before submitting. - Config: start from
frontend/.env.mainnet.example; the CLI uses--network mainnet.
make build-contract
make deploy-mainnet MAINNET_IDENTITY=trellis-mainnet-deployer.github/workflows/deploy-mainnet.yml is the manual, environment: mainnet-gated deploy workflow.
Any workflow that targets mainnet must declare environment: mainnet. In Settings → Environments → New environment → mainnet, enable Required reviewers (at least two maintainers), restrict deployment to tags/master, and store mainnet secrets only as environment secrets. Jobs then pause until a reviewer manually approves.
Stellar's testnet is periodically reset, wiping all deployed contract instances and stored ledger entries (including every Trellis agreement). When this happens, the contract ID in Live Contract and the agreement ID in Verified Test Agreement both stop resolving.
A scheduled workflow checks this for you. Testnet Health
runs daily (07:00 UTC, or on demand via Actions → Testnet Health → Run
workflow) and calls trellis status against the
Live Contract and Verified Test Agreement:
- Reset detected — the contract or agreement no longer exists on-chain
(e.g.
Error(Contract, #3)/AgreementNotFound, or a missing contract instance). The workflow opens an issue labelledtestnet-reset, or comments on the existing open one, and the run fails. Check for that issue before debugging "agreement not found" yourself. - Inconclusive — the probe failed for another reason (RPC outage, Friendbot down, build failure). The run fails so it is visible, but no issue is filed, so a flaky RPC never raises a false reset alarm.
- Healthy — any open
testnet-resetissue is closed automatically.
Each run's probe output is in its job summary. The same check runs locally:
TRELLIS_CONTRACT_ID=<contract ID> TRELLIS_SOURCE_KEY=<identity> \
TRELLIS_BIN=target/release/trellis ./scripts/check-testnet-health.sh [agreement ID]It exits 0 (healthy), 10 (reset suspected) or 1 (inconclusive).
Signs you can check manually:
trellis status --agreement-id <any known ID>returnsagreement not foundeven though the ID was previously valid.- The contract ID no longer resolves on Stellar Expert.
- Redeploy the contract (Steps 1–3 under Deploying Your Own Instance) to get a new contract ID.
- Update
TRELLIS_CONTRACT_ID(CLI) andVITE_CONTRACT_ID(frontend/.env) to the new contract ID. - Create a fresh test agreement with
trellis init(see CLI Usage) and note the new agreement ID for testing. - Optionally run
scripts/reset-testnet.sh, which automates steps 1–3: it rebuilds and redeploys the contract, builds the CLI, creates a new test agreement, and then writes the new values back into the repo (see below).
./scripts/reset-testnet.shAfter a successful reset the script updates, in place:
DEPLOYMENT.md— the blocks between<!-- reset-testnet:begin … -->and<!-- reset-testnet:end … -->markers in Live Contract and Verified Test Agreement are regenerated with the new contract ID, agreement ID, and payer/payee addresses. Keep those markers intact; content between them is overwritten on every reset. The script fails before writing anything if a marker is missing.frontend/.env—VITE_CONTRACT_IDis set to the new contract ID (other variables are preserved; the file is created fromfrontend/.env.exampleif it does not exist). SetFRONTEND_ENV_FILEto patch a different file instead.frontend/.env.example—VITE_CONTRACT_IDis updated so new checkouts default to the live contract.
Nothing is written if any earlier step (build, deploy, trellis init, or the
verification below) fails.
A reset is only done once the new agreement can actually move funds — an
agreement can be created successfully with a token address that no
lock_funds call can ever transfer from. scripts/reset-testnet.sh checks
this itself before updating any files, and exits non-zero if any check fails:
- Token is a token contract. The test agreement uses the native XLM
Stellar Asset Contract (
stellar contract id asset --asset native), which must resolve to aC…contract ID. AG…account/issuer address is not a token and will make everylock_fundsfail. - Agreement reads back.
trellis statusreturns the new agreement and it references the expected payer, payee, resolver and token. - Funds actually move.
trellis lock-fundsfunds milestone 0 astrellis-payer,trellis milestone-statusreportsFunded, and the token'sbalancefor the new contract equals the milestone amount.
As a result the test agreement in
Verified Test Agreement is left with milestone 0
Funded, ready for submit-work / approve-release testing.
If you reset manually (steps 1–3 above) instead, run the same checks by hand:
TOKEN_ID=$(stellar contract id asset --asset native --network testnet)
# ... trellis init --token "$TOKEN_ID" ... (see CLI Usage)
trellis status --agreement-id <new-id>
TRELLIS_SOURCE_KEY=<payer identity> trellis lock-funds --agreement-id <new-id> --milestone-id 0
trellis milestone-status --agreement-id <new-id> --milestone-id 0 # expect Funded
stellar contract invoke --id "$TOKEN_ID" --network testnet --source <any identity> \
-- balance --id <new contract id> # expect the milestone amountReview and commit the changes with git diff DEPLOYMENT.md frontend/.env.example (frontend/.env is git-ignored).
The script requires the same prerequisites as manual deployment (Rust,
wasm32-unknown-unknown target, stellar CLI) and a funded
trellis-deployer identity — it will create and fund one via Friendbot if it
does not already exist.
Soroban archives persistent entries whose TTL runs out. Every state-mutating
call renews an agreement's TTL to ~30 days, but an agreement that sits idle
longer than that (a long delivery window, a stalled dispute) needs
extend_agreement_ttl called on it. The repo ships a minimal keeper for this:
scripts/ttl-keeper.shrunstrellis extend-ttl --yesfor every agreement listed in.github/keeper-agreements.txt(or inTRELLIS_KEEPER_AGREEMENT_IDS, comma-separated, when set). It attempts every agreement and exits non-zero if any failed..github/workflows/ttl-keeper.ymlruns the script weekly (Wednesdays 05:00 UTC) and on manual dispatch, where anagreement_idsinput can override the file for a one-off run. The run's output is written to the job summary.
Setup (maintainers). The workflow is skipped until these are configured in the repository settings:
| Name | Kind | Value |
|---|---|---|
TRELLIS_KEEPER_SECRET_KEY |
Secret | S… secret key of a funded testnet account that pays the rent |
TRELLIS_KEEPER_ADDRESS |
Variable | The matching G… public address (recorded as caller; the contract requires it to sign, so it must match the secret key) |
TRELLIS_CONTRACT_ID |
Variable (optional) | Contract to target; defaults to the Live Contract |
Use a dedicated low-balance account — never a payer, payee or resolver key.
Running it locally / as a cron job:
export TRELLIS_CONTRACT_ID="<contract ID>"
export TRELLIS_SOURCE_KEY="<funded identity name>"
export TRELLIS_KEEPER_CALLER="$(stellar keys address <funded identity name>)"
TRELLIS_BIN=target/release/trellis ./scripts/ttl-keeper.shAssumptions and limitations. This is a stopgap, not a production keeper network:
- It only renews agreements someone has added to the tracked list — there is no on-chain discovery, so new long-lived agreements must be added by PR.
- It depends on GitHub's scheduler (runs can be delayed, and scheduled workflows are disabled after 60 days without repository activity) and on a single funded key. If that key runs out of XLM, every extension fails.
- A failed run is visible only as a failed workflow run; nothing pages anyone. The weekly cadence leaves roughly three missed runs of headroom before a 30-day TTL lapses.
- After a Testnet Reset the listed IDs no longer exist and
every extension fails with
agreement not founduntil the list (andTRELLIS_CONTRACT_ID) is updated.
| Command | Status | Notes |
|---|---|---|
init |
✅ Verified on testnet | Agreement created on-chain |
status |
✅ Verified on testnet | Returns full agreement state as JSON |
total-amount |
✅ Implemented | Returns the agreement's total value via get_total_amount (Issue #492) |
lock-funds |
✅ Verified on testnet | Funds successfully locked in escrow (Issue #7) |
batch-lock-funds |
✅ Implemented | Atomically funds multiple milestones in one transaction (Issue #493) |
submit-work |
✅ Verified on testnet | Work proof submitted and milestone transitioned to WorkSubmitted (Issue #8) |
approve-release |
✅ Verified on testnet | Funds released to payee, milestone marked Completed (Issue #8) |
raise-dispute |
✅ Verified on testnet | Dispute raised and milestone transitioned to Disputed (Issue #9) |
resolve-dispute |
✅ Verified on testnet (both paths) | Resolves to both Refunded (payer) and Completed (payee) states (Issue #9) |
cancel-milestone |
✅ Implemented | Cancels unfunded milestones |
extend-ttl |
✅ Implemented | Renews an agreement's ledger TTL; used by the TTL keeper (Issue #494) |
Objective: Verify the complete payment flow from initial funding through approval and release.
Steps:
- Create agreement with payer, payee, resolver identities
- Fund milestone 0 (payer calls
lock-funds) - Submit work proof (payee calls
submit-work) - Approve and release (payer calls
approve-release)
Verification:
- ✅ Milestone transitions:
Pending→Funded→WorkSubmitted→Completed - ✅ Token transfer confirmed from payer to contract, then to payee
- ✅ Events emitted at each stage (queryable via event filters)
- ✅
statusreturnsCompletedstate with proof URI recorded
Objective: Verify dispute resolution when resolver rules in payer's favor.
Steps:
- Create and fund agreement as above
- Dispute raised by payee (payer calls
raise-disputewith payee caller) - Resolver ruling: refund to payer (resolver calls
resolve-dispute --refund-to-payer true)
Verification:
- ✅ Milestone transitions:
Funded→Disputed→Refunded - ✅ Funds returned to payer's address
- ✅ Payee does not receive payment
- ✅ Events show dispute resolution with refund outcome
Objective: Verify dispute resolution when resolver rules in payee's favor.
Steps:
- Create and fund agreement
- Work submitted, then dispute raised by payer (payer calls
raise-disputewith caller=payer) - Resolver ruling: release to payee (resolver calls
resolve-dispute --refund-to-payer false)
Verification:
- ✅ Milestone transitions:
WorkSubmitted→Disputed→Completed - ✅ Funds released to payee's address
- ✅ Payer does not recover funds
- ✅ Events show dispute resolution with payee-win outcome
The frontend is a single-page app using client-side routing (React Router). A
direct load or refresh on a nested route (e.g. /agreement/123) must be
served index.html by the web server, not a 404 — the router only resolves
the path once the JS bundle loads.
-
Vite dev server: already configured via
appType: 'spa'infrontend/vite.config.ts; no extra setup needed. -
Netlify / Vercel / other static hosts that honor
_redirects:frontend/public/_redirectsships a catch-all (/* /index.html 200) that is copied intodist/on build. -
nginx: add a fallback to the
location /block serving the builtdist/directory:location / { try_files $uri $uri/ /index.html; }
-
Apache: enable
mod_rewriteand add todist/.htaccess:RewriteEngine On RewriteBase / RewriteRule ^index\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.html [L]
npm run build emits dist/_headers (honoured by Netlify and Cloudflare
Pages) carrying a Content-Security-Policy plus X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin,
X-Frame-Options: DENY and a restrictive Permissions-Policy. The same CSP
(minus frame-ancestors, which browsers ignore in a <meta> tag) is
injected into dist/index.html as a fallback. The policy is generated by
frontend/security-headers.ts:
connect-srcis'self'plus the origins ofVITE_RPC_URLand, when set,VITE_INDEXER_URL— set those before building or RPC calls will be blocked.script-src 'self'with no'unsafe-inline'or'unsafe-eval'; the build (and the frontend CI job) fails if'unsafe-eval'is ever added.- Freighter needs no allowance: it talks to the page via
postMessage.
For nginx or Apache, copy the values from dist/_headers into
add_header … always; / Header always set … directives so
frame-ancestors 'none' is enforced too.
indexer/ is an optional service that sits between the frontend and Soroban
RPC (#495, #496). It:
- polls
getEventsfor the contract once, server-side, and stores every event in a SQLite database; - serves stored history over HTTP (
/events,/stats); - pushes new events to connected browsers over Server-Sent Events
(
/stream/events,/stream/stats).
Without it, each open browser tab polls RPC on its own, and agreement history disappears once events age out of the RPC node's retention window.
Requirements: Node 22.13+ (for the built-in node:sqlite), a persistent
disk for the database file, and one long-running process. There are no npm
dependencies, so no install step is needed.
cd indexer
RPC_URL=https://soroban-testnet.stellar.org \
CONTRACT_ID=CAUAO7CYKULE2K4EJMQ6LLRUHP7Y7JYOH6G2VBXKYG7PTETE3UZ3DU7Q \
DB_PATH=/var/lib/trellis/events.db \
CORS_ORIGIN=https://your-frontend.example \
PORT=8787 \
npm startThen build the frontend with VITE_INDEXER_URL=https://indexer.your-domain.example.
See indexer/README.md for every option and endpoint.
- Run exactly one instance per contract. The relay keeps its subscriber list in memory and the SQLite file is single-writer, so don't scale it horizontally. One instance handles thousands of SSE connections.
- Reverse proxies: SSE needs response buffering off and a long read
timeout. The service sends
X-Accel-Buffering: noand a heartbeat comment every 15 s. For nginx, also setproxy_read_timeout 1h;andproxy_http_version 1.1;on the/stream/location. - Health checks: use
GET /health. It reportsstatus: "degraded"while RPC polls are failing, and comparesrpcLatestLedgerwithlatestIndexedLedgerso you can see lag. - Redeploying the contract: each database is tied to one contract ID, and
the service refuses to start if
CONTRACT_IDdoesn't match the one already stored inDB_PATH. PointDB_PATHat a new file for the new contract. - Fallback: if
VITE_INDEXER_URLis unset, or the stream can't connect within 5 s, the frontend polls RPC directly as before. An indexer outage makes the app slower to update, but it keeps working.
- The RPC node is a rolling window. Public testnet RPC keeps roughly the
last 7 days of events (
getHealth→ledgerRetentionWindow). Anything older is gone from that node. - The indexer keeps events indefinitely. It never prunes. Events are keyed
by their RPC event
id, so re-ingesting an overlapping page is a no-op. - First start: ingestion begins at the node's
oldestLedger, or atSTART_LEDGERif set. Events older than that window can't be recovered from this node. To get full history for an older contract, setSTART_LEDGERand pointRPC_URLat a node or archive that still holds those ledgers. - Downtime: the ingest cursor is stored in the database, so a restart
resumes where it stopped. If the indexer is down longer than the RPC
retention window, the cursor expires. The indexer then resumes from the
node's
oldestLedger, logs a warning, and records the gap underlastGapin/health. Events inside that gap are missing until backfilled from another source. - The database is the only copy of old history. Back up
DB_PATHregularly (e.g.sqlite3 events.db ".backup events.bak"). Losing it means falling back to whatever the RPC node still retains.
-
Auth per command: Each CLI command must be signed by the correct identity. Pass
--as payer|payee|resolverto pick the required signer per operation (payer forinit,lock-funds,approve-release,cancel-milestone; payee forsubmit-work; resolver forresolve-dispute). See One config for every role. -
WASM build target: Use
wasm32-unknown-unknownviacargo rustcrather thanstellar contract builduntil the project upgrades to a toolchain that fully supportswasm32v1-none. -
Windows paths: On Windows Git Bash, use the full path to the binary:
C:\Users\<you>\Documents\Trellis\target\release\trellis.exe -
Native RPC client (#11): The current implementation falls back to the stellar CLI for transaction signing and submission. A full native Rust implementation would require integrating the Stellar SDK for key management, transaction envelope building, and signing. The structure is in place for future native implementation in
cli/trellis_cli/src/rpc.rs.