Documentation
deployments

Deployments

Agama runs natively on Stellar. All protocol logic is implemented as Soroban smart contracts written in Rust and compiled to WASM. Deposits and redemptions settle in native Circle USDC, not a wrapped or synthetic asset.

Network

NetworkStellar Testnet
RPChttps://soroban-testnet.stellar.org
Applicationapp.agama.finance/stellar
Sourcegithub.com/agamafinance/agama-soroban (Apache-2.0)

Core contracts

ContractStandardAddress
USDC (Circle)Stellar asset contractCBIELTK6...XQDAMA
VaultCustody, mint, FIFO withdrawal queue, its own reserve floorCANIR7O2...HTG247
agUSDSEP-41, minted only by the VaultCDK6A24G...LSW4MO
sagUSDSEP-41, share-price accounting sharesCD6Q4G7U...PGIOGF
Allocation EngineCaps in bps of net assets, reserve floor in bps of floor_base, per-pool cap and delistingCDYT5BFC...C5TUFL
Oracle AdapterPer-feed staleness, deviation, band, rate limit and quorum thresholdCAUOHPPN...TWIQUS

The USDC issuer on Stellar is GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5.

Pool adapters

Both implement the same interface (allocate, deallocate, write_down, recover_surplus, get_exposure, engine, vault), so the Allocation Engine stays agnostic to pool type. register_pool reads the last two and refuses any adapter that does not name the Engine registering it and that Engine's Vault: an adapter pointed elsewhere would take capital from this Vault and repay a third party while the Engine's book decremented as though the money had come home.

That check runs on every call that moves a pool's capital, not only at registration. Half of the condition is a fact about the Engine, and set_vault can change it, so a pool registered before a Vault repoint goes on naming the Vault the Engine has stopped governing. allocate, deallocate and recover re-read engine() and vault() and refuse with AdapterMismatch until set_counterparties brings the adapter across. write_down does not, deliberately: it moves no capital, and it is the only call that can unwind an adapter whose Vault pointer has already gone stale.

AdapterSettlementAddress
Private creditD+15 to D+90, off-chain originatorCAQQLSSX...T4VEDF
EtherfuseInstant, on-chain redemptionCBLYG64B...6G6GCF

Superseded deployments

Earlier generations stay on the ledger rather than being deleted from the record. They are listed here so nothing published previously points somewhere unexplained.

ContractAddressWhy it was superseded
Vault Contract, eleventh, September 2026CCPBVZ3W...SZNFFDReplaced together by scripts/deploy-vault-generation.sh, so that an admin can no longer hold withdrawals shut indefinitely. In the Vault it replaces, set_paused refuses deposits, new allocations and withdrawal requests alike, with no limit on how long, so a holder of agUSD could be kept from converting a claim they already owned into a queued one for as long as an admin left the switch on. The comment above that switch already argued the principle, that a breaker stopping new obligations is a breaker and one refusing to honour obligations already on the books is a freeze; it stopped one step short of noticing an agUSD holder is owed something too. Requesting a withdrawal also moves no money: it burns the agUSD and writes a claim, and the USDC leaves in claim_withdrawal, which was never pausable in either generation. The replacement takes withdrawal requests out of set_paused entirely and gives that power its own entry point, freeze_exits, bounded at MAX_EXIT_FREEZE_SECS of 72 hours after which it lapses with no admin action, rate limited by EXIT_FREEZE_COOLDOWN_SECS of 7 days measured from when exits actually reopened rather than from when the freeze would have lapsed, with thaw_exits to lift one early and an ExitFreezeSet event so a freeze is countable separately from a routine deposit pause. An invariant fuzzer drives freeze_exits, thaw_exits and the passage of time and asserts that exits_frozen_until is never further out than one freeze, clean at 1500 cases. Neither the bound nor the separation is reachable through a setter, so shipping them takes a new Vault, and the difference is checkable on-chain: the retired one exposes no exits_frozen_until. agUSD moved with it because agusd-core::set_minter refuses once mints > 0, so the minter pointer was frozen at the Vault being retired. Staking followed agUSD for its own frozen setter; the Engine, the oracle and both adapters followed through theirs. The retired Vault keeps the recovered capital it had no agUSD to release against, which is recorded under strandedInRetiredVaults.
agUSD, tenth, September 2026CDBIY2KA...CGEX3ZReplaced together by scripts/deploy-vault-generation.sh, so that an admin can no longer hold withdrawals shut indefinitely. In the Vault it replaces, set_paused refuses deposits, new allocations and withdrawal requests alike, with no limit on how long, so a holder of agUSD could be kept from converting a claim they already owned into a queued one for as long as an admin left the switch on. The comment above that switch already argued the principle, that a breaker stopping new obligations is a breaker and one refusing to honour obligations already on the books is a freeze; it stopped one step short of noticing an agUSD holder is owed something too. Requesting a withdrawal also moves no money: it burns the agUSD and writes a claim, and the USDC leaves in claim_withdrawal, which was never pausable in either generation. The replacement takes withdrawal requests out of set_paused entirely and gives that power its own entry point, freeze_exits, bounded at MAX_EXIT_FREEZE_SECS of 72 hours after which it lapses with no admin action, rate limited by EXIT_FREEZE_COOLDOWN_SECS of 7 days measured from when exits actually reopened rather than from when the freeze would have lapsed, with thaw_exits to lift one early and an ExitFreezeSet event so a freeze is countable separately from a routine deposit pause. An invariant fuzzer drives freeze_exits, thaw_exits and the passage of time and asserts that exits_frozen_until is never further out than one freeze, clean at 1500 cases. Neither the bound nor the separation is reachable through a setter, so shipping them takes a new Vault, and the difference is checkable on-chain: the retired one exposes no exits_frozen_until. agUSD moved with it because agusd-core::set_minter refuses once mints > 0, so the minter pointer was frozen at the Vault being retired. Staking followed agUSD for its own frozen setter; the Engine, the oracle and both adapters followed through theirs. The retired Vault keeps the recovered capital it had no agUSD to release against, which is recorded under strandedInRetiredVaults.
sagUSD staking, twelfth, September 2026CD3VPDGF...PG35Y3Replaced because agUSD moved and set_agusd cannot follow it. The guard reads a cumulative stake counter rather than the current balance, and that is deliberate: its own documentation says unwinding to zero is not the same thing as never having taken custody, and the pending queue can be non-empty while the share supply is nil. So the token pointer freezes at the first stake and never thaws, which is the right property for stakers and means a new agUSD generation costs a staking generation every time. This one had taken 64 stakes. It was emptied first, supply and NAV both zero, so nothing was stranded in it. What was wrong was the migration script, which said staking takes the new token 'which it can only do while empty' and then asserted it; emptiness is not what the guard tests, and the ten generations before this one were all replaced rather than repointed. The script now deploys the replacement instead of trying a setter that cannot succeed.
Oracle Adapter, fourth, September 2026CBH7NW5L...EVFB3NReplaced by scripts/redeploy-oracle-exact-bound.sh, because its deviation bound was not the bound it advertised. The check computed the move as (nav - last).abs() * BPS / last.nav and compared that against the feed's deviation_bps. Integer division truncates toward zero, so the computed figure understates the move and anything between the bound and one basis point above it rounded down onto the bound and was accepted. On the private credit feed that is reachable arithmetic rather than a corner: a NAV of 10000000 against a 500 bps bound allows 500000 stroops of movement, and 500001 through 500999 all computed as 500 and passed, so the bound advertised 5 percent and enforced 5.00999 percent. The replacement decides by multiplying, comparing delta * BPS against deviation_bps * last.nav, which is exact and is the discipline every other limit in this protocol already uses: the reserve floor compares free * BPS against floor_bps * base and never divides. The division survives only for the deviation_bps carried in the rejection event, because a number a human reads may round. Nothing else was redeployed: only the Vault names the oracle, through set_oracle, and the pool adapters carry their feed as a constant Symbol rather than an address. The three feeds were registered with the parameters read off the outgoing contract rather than retyped, since register_feed is write once per feed.
Allocation Engine, fifteenth, September 2026CCMATPP7...TTSZLWReplaced by scripts/rewire-engine-one-base.sh to restore book_recovery, which closes M1 of the third adversarial review. This Engine has no way to book a recovery whose cash is already in the Vault. The adapter's recover_surplus may be taken by the adapter's own admin as well as by the Engine, which is how an adapter stuck to superseded counterparties is unstuck without a working Engine, and taken that way the cash reaches the Vault with no book moving: recover then gets NothingToRecover from the adapter and reverts, record_recovery has no other caller, and the write-down sits on recognised_losses and on the pool's concentration charge with nothing able to correct it. book_recovery is recover with the adapter leg removed, bounded by the Vault, which subtracts the balance it can account for from the balance it holds and refuses anything larger, so a recovery stays evidenced rather than asserted. The entry point existed once and was removed because an invariant fuzzer showed it could lower floor_base in four operations while the base read the raw token balance; that basis is gone, both halves are neutral under the accounted base, and the fuzzer now drives the operation with that invariant applied rather than excluded, clean at 1500 cases wide and 300 cases of up to 89 operations. Nothing else was redeployed: the Vault took the replacement through set_engine and both adapters followed through set_counterparties.
Allocation Engine, fourteenth, September 2026CAJPGOOY...CWSBSGReplaced by scripts/rewire-engine-one-base.sh, so that the Engine and the Vault measure the reserve floor on one base. This Engine builds its base from Vault::free_reserves(), which reads the real token balance, while the Vault had already moved its own floor onto accounted cash. For any USDC that reached the Vault without its books being told, this Engine's base runs higher by that amount and its free-reserves term runs higher too, so asking for floor_bps of the base leaves it more permissive than the Vault by (1 - floor_bps) of that cash, and get_reserve_ratio reports liquidity the Vault would refuse to release. The direction is safe, settle_allocation re-checking in the same transaction and reverting, but a limit enforced in two places against two different numbers is one limit and one decoration. The replacement reads accounted_free_reserves() and floor_base() off the Vault instead of rebuilding them, which also matters for the clamp: the Vault sums the base unclamped and clamps once at the end, and a rebuild that clamps the cash term first drifts the base down a stroop per deallocation. The concentration caps moved with it, because their denominator is total assets and a larger denominator loosens a cap. Nothing else was redeployed: the Vault took the replacement through set_engine and both adapters followed through set_counterparties, empty at the time as that setter requires. It costs one more cross-contract call per allocation, about 180000 instructions, which moves the pool registry's measured worst case from 277 same-bucket pools to 276.
Vault Contract, tenth, September 2026CBNS3LWI...ZAYHLTReplaced together by scripts/deploy-vault-generation.sh, so that the reserve floor stops being a share of the raw idle balance. In the Vault it replaces, floor_base is built on the USDC the contract happens to hold, so cash arriving without the books being told raises the base the moment it lands and can be spent a second time when something finally books it: an invariant fuzzer reproduced that in four operations. The replacement measures the base on the flows the Vault recorded itself and adds accounted_free_reserves, which the liquidity check in settle_allocation now uses. Neither is reachable through a setter, so shipping them takes a new Vault, and the difference is checkable on-chain: the retired one exposes booked_reserves and no accounted_free_reserves. agUSD moved with it because agusd-core::set_minter refuses once mints > 0, so the minter pointer was frozen at the Vault being retired and a replacement Vault could never have minted that token. Both retired at zero, the outstanding supply having been redeemed first. Staking followed agUSD for its own frozen setter; the Engine, the oracle and both adapters followed through theirs.
agUSD (contracts/agusd-core), ninth, September 2026CDXCGLN3...MICZXVReplaced together by scripts/deploy-vault-generation.sh, so that the reserve floor stops being a share of the raw idle balance. In the Vault it replaces, floor_base is built on the USDC the contract happens to hold, so cash arriving without the books being told raises the base the moment it lands and can be spent a second time when something finally books it: an invariant fuzzer reproduced that in four operations. The replacement measures the base on the flows the Vault recorded itself and adds accounted_free_reserves, which the liquidity check in settle_allocation now uses. Neither is reachable through a setter, so shipping them takes a new Vault, and the difference is checkable on-chain: the retired one exposes booked_reserves and no accounted_free_reserves. agUSD moved with it because agusd-core::set_minter refuses once mints > 0, so the minter pointer was frozen at the Vault being retired and a replacement Vault could never have minted that token. Both retired at zero, the outstanding supply having been redeemed first. Staking followed agUSD for its own frozen setter; the Engine, the oracle and both adapters followed through theirs.
sagUSD staking, eleventh, September 2026CCXCJYWY...7TO2GPReplaced by scripts/redeploy-staking-follow-agusd.sh, so that staking accepts the agUSD generation deployed alongside the reserve floor change. Its own set_agusd could not do it: the setter refuses once stakes > 0, and stakes is a cumulative count of every stake ever taken which never decrements, so the pointer freezes permanently at the first one even on a contract that is completely empty. That is conservative rather than over-strict, because a pending unstake survives with supply and NAV both at zero and is denominated in assets, so repointing the token underneath it would pay a claim in a token it was never priced against, and the contract cannot enumerate its own pending records to check. The cost is that every agUSD generation forces a staking redeployment for the life of the protocol. It was empty here, so it stranded nobody, and nothing on-chain names this contract.
Vault Contract, ninth, September 2026CBNAGG47...Y2BUSBReplaced together by scripts/deploy-vault-generation.sh. The Vault it replaces does not interrogate its oracle in set_oracle, exposes neither oracle() nor oracle_feed(), and does not require the agUSD it mints to count stroops the way its USDC does, which is the requirement the one for one peg actually rests on. agUSD moved with it because agusd-core::set_minter refuses once mints > 0, so the minter pointer was frozen at the Vault being retired and a replacement Vault could never have minted that token. Both retired at zero: the outstanding supply was redeemed first, which needed the position lifted over the 1 agUSD anti-dust floor by a deposit, and which is why this generation waited on the operator account being funded. Nothing else was redeployed: the Engine, the oracle, both adapters and staking all followed through their own setters.
agUSD (contracts/agusd-core), eighth, September 2026CCUGWQ5D...JFHA7VReplaced together by scripts/deploy-vault-generation.sh. The Vault it replaces does not interrogate its oracle in set_oracle, exposes neither oracle() nor oracle_feed(), and does not require the agUSD it mints to count stroops the way its USDC does, which is the requirement the one for one peg actually rests on. agUSD moved with it because agusd-core::set_minter refuses once mints > 0, so the minter pointer was frozen at the Vault being retired and a replacement Vault could never have minted that token. Both retired at zero: the outstanding supply was redeemed first, which needed the position lifted over the 1 agUSD anti-dust floor by a deposit, and which is why this generation waited on the operator account being funded. Nothing else was redeployed: the Engine, the oracle, both adapters and staking all followed through their own setters.
agUSD, first generationCCXEP6QA...NQ6H3A self-contained vault rather than a plain token. It mints only inside its own deposit() and exposes no mint entry point, so a separate Vault contract cannot mint against a deposit.
Vault, first deploymentsCAVKHGBQ...F5OFJW3 and CDQP7L5R...B4TR3KS4Each stored a counterparty address at initialize() with no setter, so one could never mint and the other could never deploy capital.
sagUSD, first deploymentCABPYD4U...XTALXAccepts the first generation agUSD and stores it at initialize() with no setter. It also has no re-initialization guard: anyone can call its initialize a second time and take it over, so the agUSD it still custodies should be treated as at risk. The current sagUSD rejects a second initialize with AlreadyInitialized.
sagUSD, second and third deploymentsCDY3ED6T...BTC345 and CBMEW3QA...WFTHZFThe second was replaced within the day after review, because its set_agusd guard keyed off the stake counter alone and delivered yield takes custody without touching it. The third exposed accrue_yield and share_price, the names the contract shipped with, rather than distribute_yield and exchange_rate, the names Agama committed to. Both were superseded holding nothing: NAV and share supply were zero at each handover.
Allocation Engine and pool adapters, first deploymentsCANDJEHB...KSL2SGS, CCDZRKZD...KCXT3VZ, CBS3OGCV...WKFLYKKBound to a superseded Vault at initialize() with no setter. Their replacements carry admin-gated setters, guarded so they are refused once the contract holds state the change would invalidate.
Vault, agUSD, Allocation Engine, Oracle Adapter, both pool adapters and sagUSD, September 2026CCGPF36P...F5KVRR, CCW763RT...U4ALZL, CAFJKWLU...SZ5HUX, CDV5BC4X...XCSV7G, CBAPY7KR...ZGFTOZ, CBA3GQLH...AH7EWI, CCBEDKRQ...L6HFO2Replaced together after an adversarial security review ahead of the OtterSec audit. The Vault delegated the reserve floor entirely to whatever Allocation Engine it pointed at; queued withdrawals were counted as free liquidity by the floor and the caps; one unclaimed withdrawal froze the queue for everyone behind it; a credit loss could not be recognised on-chain at all; the oracle accepted an unbounded first value and had no rate limit; sagUSD carried report_nav, a bare setter on the denominator of its own share price; and no contract had admin rotation. None of these contracts is upgradeable, so every fix is a redeployment.
Vault, agUSD, Allocation Engine, private credit adapter and sagUSD, September 2026, second reviewCCW5EQCV...KDSIWP, CANR4HJC...VJGIYG, CAOGJDWH...KJDONN, CBWFVABY...JT5BBT, CDU7BYCE...GVYE7X, and the intermediate generations recorded in deployments/testnet.jsonReplaced after a second adversarial review, run against the fixes the first one produced. Two Critical findings, both in the new code. The reserve floor was a share of net assets, and a write-down lowers net assets with no cash moving, so recognising a loss handed back releasable headroom worth the floor's own percentage of itself and the reserves could be walked out a slice at a time. And a withdrawal payout the USDC contract refused to deliver trapped the whole call, so any claimant without a trustline, with a frozen one, or with a limit below their claim froze the entire queue permanently. The Oracle Adapter, the Etherfuse adapter and sagUSD did not change and were repointed in place through the setters the first review added, rather than redeployed; the private credit adapter could not be, because it holds USDC from a written-down position and no entry point can move it.
Vault, agUSD, Allocation Engine, Oracle Adapter, both pool adapters and sagUSD, September 2026, Medium findingsCAK7NGMF...AFCSSL, CDO7WPJH...V2L6PL, CBM2RACB...G56HRT, CCIABPQM...N4DWJG, CCDB2I75...CA4PSR, CADNIBDB...KF4ZFV and CALOJ3UH...SRQWKXReplaced together to close the Medium findings of the second review. Every contract in this generation wired itself through an initialize() sent after the deploy, which is a public window in which the same call naming a different admin can land first, and which took its counterparties on trust while the setters that repair those same pointers interrogated them. A __constructor replaces it in all seven, so no deployed bytecode still matched the source and none could be reused. This generation also has no record_recovery and no bump_claim on the Vault, no recover on the Engine and no recover_surplus on either adapter, so capital written off and later repaid had no way back onto the books, and it measures the three concentration caps on live exposure, which a write-down sets to zero while the adapter goes on holding the cash.
Allocation Engine, September 2026, admin alignmentCAX576TM...7L4OCGReplaced on its own. write_down checked the amount and the pool before it checked that this Engine's admin and the Vault's were still the same address, so an operator halfway through a rotation was told the amount was wrong and sent to look at the position rather than at the rotation. The alignment is a condition on the wiring rather than on the call and now runs first. It is a legibility change and not a security one: the same call is refused either way. Nothing else was redeployed. The Vault took the replacement through set_engine and both adapters followed through set_counterparties, which was the first time a change to one contract had been absorbed by the rest without a redeployment.
Allocation Engine, September 2026, token edgeCCSI33BM...FHVJCWReplaced together, for the third edge of the wiring triangle. Registration proved an adapter named this Engine and this Engine's Vault, and the adapter proved from its side that the Engine governs the Vault, and neither was about the asset. An adapter stores a token, it was a constructor argument nothing validated, and it is the address every transfer in the adapter uses. With the right pointers and the wrong token an adapter is a one way door: the Vault sends what the Vault holds, so real USDC arrives, and repaying sends back the token the adapter stores, of which it has none, and traps. Both sides check it now, the adapter at construction and the Engine on every call that moves capital.
Private credit adapter, September 2026, token edgeCA24YZ2N...EJ4MSRReplaced together, for the third edge of the wiring triangle. Registration proved an adapter named this Engine and this Engine's Vault, and the adapter proved from its side that the Engine governs the Vault, and neither was about the asset. An adapter stores a token, it was a constructor argument nothing validated, and it is the address every transfer in the adapter uses. With the right pointers and the wrong token an adapter is a one way door: the Vault sends what the Vault holds, so real USDC arrives, and repaying sends back the token the adapter stores, of which it has none, and traps. Both sides check it now, the adapter at construction and the Engine on every call that moves capital.
Etherfuse adapter, September 2026, token edgeCAYOGJ35...5PV2NFReplaced together, for the third edge of the wiring triangle. Registration proved an adapter named this Engine and this Engine's Vault, and the adapter proved from its side that the Engine governs the Vault, and neither was about the asset. An adapter stores a token, it was a constructor argument nothing validated, and it is the address every transfer in the adapter uses. With the right pointers and the wrong token an adapter is a one way door: the Vault sends what the Vault holds, so real USDC arrives, and repaying sends back the token the adapter stores, of which it has none, and traps. Both sides check it now, the adapter at construction and the Engine on every call that moves capital.
sagUSD staking, seventh, September 2026CDFKRDCD...CHYLEZReplaced by scripts/redeploy-staking-errors.sh. Its four user facing entry points, stake, request_unstake, claim and distribute_yield, trapped with string panics rather than returning a typed error, alone among the contracts here. An integrator could see that a stake had failed and not why, and could not branch on it. The replacement returns codes in the 800 range for all of them. It was empty at the time, with no supply, no NAV and no stakes, so it stranded nobody, and nothing on-chain names this contract, so nothing had to be rewired.
sagUSD staking, eighth, September 2026CCJY5C45...OO7XAMReplaced by scripts/redeploy-staking-ttl.sh. request_unstake wrote the Pending record with set and nothing else, which gave it 4095 ledgers, under six hours. That record is the whole of what says a departed staker is still owed anything, because the shares are burned and the assets are out of nav by then, so its archival leaves the money belonging to nobody until somebody pays for a RestoreFootprint. The Vault gives a withdrawal claim ninety days and exposes bump_claim so that anybody can push it out, both added by the second adversarial review for exactly this failure; this contract had neither. The replacement writes the record through one helper that extends it to the same ninety days and adds bump_pending, permissionless for the reason bump_claim is. It was empty at the time, so it stranded nobody, and nothing on-chain names this contract.
sagUSD staking, ninth, September 2026CAXVZRR6...YAFQO4Replaced by scripts/redeploy-staking-events.sh. Its four asset moving entry points, stake, request_unstake, claim and distribute_yield, emitted nothing, so the only events this contract produced were administrative. Share supply was visible through the SEP-41 mint and burn events the token layer emits, and nav was not visible at all, which means the share price history the grant funds an indexer to expose could not be built from the event stream: it could only be sampled by polling exchange_rate(), which has no past. The replacement emits all four, carrying nav and supply as they stand after the call so that every price in the history is a fact from the ledger. It was empty at the time, so it stranded nobody, and nothing on-chain names this contract.
sagUSD staking, tenth, September 2026CBXEB4DN...VMBMCVReplaced by scripts/redeploy-staking-decimals.sh. Its set_agusd checked that the contract had taken no custody and nothing else, so it would accept an agUSD counting stroops differently from the sagUSD it issues. The first staker gets shares one for one, raw stroop for raw stroop, and every share price after that is measured from there, so a misaligned token makes the exchange rate reported as 1.0 not one to one in value, with nothing downstream able to tell because the internal arithmetic stays consistent in stroops. Both were seven at the time, so this is a guard against a future mis-wiring rather than a live defect. It was empty, so it stranded nobody, and nothing on-chain names this contract.
Allocation Engine, thirteenth, September 2026CCSZ3QEV...CAVIMQReplaced by scripts/rewire-drop-book-recovery.sh. register_pool proved an adapter named this Engine and this Engine's Vault, and set_counterparties proved from the other side that the Engine governs the Vault, and neither was about the asset. An adapter stores a token, it was a constructor argument nothing validated against the Vault, and it is the address every transfer in the adapter uses. An adapter with the right pointers and the wrong token is a one way door: settle_allocation sends what the Vault holds so real USDC arrives, deallocate sends back the token the adapter stores and traps, recover_surplus measures its surplus in that token and reports nothing to recover, and a write-down clears all three books while the money stays. Both sides check it now, the adapter at construction and the Engine on every call that moves capital.
Allocation Engine, September 2026, pool registry leversCBDEWCRB...A27V4SReplaced on its own, for M2 of the third adversarial review. It has no set_pool_cap and no unregister_pool, so a registered pool's own cap could never move and the registry was a map with no way to remove an entry. A pool whose own figure was the binding half of its effective limit was capped at it for the life of the contract, and because the originator and jurisdiction sums are built by walking the registry, a defaulted pool consumed both with no lever over either. The charging rule did not change.
Oracle Adapter, September 2026, quorum thresholdCCTAZFQ3...INL6SXReplaced so that the deployed Oracle Adapter is the one in this repository. It has no per-feed quorum threshold, so the V2 path described in Oracle Design existed only in the documentation. The replacement carries it with every feed defaulting to a threshold of 1, which is V1 exactly: the first vote is quorum and it commits, and the staleness, band, deviation and rate limit guards run unchanged. No live feed was raised above 1, because a real 2-of-3 needs reporter keys that are actually independent. Only the Vault names this contract, so the rewiring was one set_oracle with the three feeds re-registered and their values restored.
Allocation Engine, September 2026, stranded recoveriesCAPMQI4U...TXMQYHReplaced on its own, for M1 of the third adversarial review. A pool adapter's surplus sweep can be taken by the adapter's own admin as well as by the Engine, which is how an adapter stuck to superseded counterparties is unstuck without a working Engine. Taken that way the cash reaches the Vault and no book moves, and in this Engine that is terminal: the surplus is gone, so recover is refused by the adapter on the way in, and the Vault call it was going to make has no other caller. The write-down then sits in the reserve floor's base for the life of the Vault and on the pool's concentration charge for the life of the Engine. book_recovery is that booking on its own, bounded by the balance the Vault holds and cannot account for. Nothing else was redeployed, for the third time.
Allocation Engine, September 2026, adapter counterpartiesCAOUV6AA...VVULBXReplaced on its own, for the one High finding of a third adversarial review. register_pool proves an adapter names this Engine and this Engine's Vault, and nothing re-ran that check afterwards, while set_vault can move the Engine's own end of it. Every pool already registered then names the Vault the Engine has just stopped governing, and the next allocate releases the new Vault's USDC to an adapter that repays the old one, which in this protocol is a superseded Vault where nothing can move USDC at all. The position cannot be unwound either: deallocate sends the cash to the old Vault and asks the new one to confirm it arrived. allocate, deallocate and recover now re-run the check. Nothing else was redeployed, for the second time.

These are testnet contracts and hold no user funds. They are documented rather than removed because a published address that quietly disappears is worse than one explained.

Credit vaults

Six credit vaults are live on testnet, curated by Qiro and Tenka. Each vault is an independent Soroban contract with its own share token.

These six are deployed instances of an earlier build of the same staking contract that issues sagUSD, from before its yield entry point took the distribute_yield name. They answer to accrue_yield and to share_price, not to distribute_yield and exchange_rate. Anything integrating against them directly should read the interface each instance publishes rather than assume the sagUSD one.

VaultCuratorStrategyShare tokenAddress
Payment FinancingQiroShort-term payment receivablesqPAYCAUFXVGK...YQEF4
Private CreditQiroDiversified credit fundqPCVCADVWAZ3...VECN3
Institutional CreditQiroInstitutional lender financingqICVCC3MOBKH...MJBK2
FlagshipTenkaABF seniortFLAGCBOF52TX...ULKKS
High YieldTenkaABF mezzaninetHYCCWXOUPQ...NHOPG
Deal VaultsTenkaDeal-by-dealtDEALCBXKGXB4...2IDO5G

Every contract above is verifiable on Stellar Expert.

Wallets

The application connects through the Stellar Wallets Kit, covering Freighter, xBull, Albedo and Ledger.