Source of truth for contract follow-ups. Update before opening a PR.
Legend: [ ] open, [~] in flight, [x] done.
- Third-party security audit (events + profile). Cost ~$30-80k for a Soroban-experienced firm. Schedule blocker for the mainnet date.
- Non-USDC token end-to-end smoke. 2026-06-05: registered the testnet native XLM SAC (
CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC) on the new events contract; ranscripts/smoke/contract-non-usdc-token.ts. Bounty published in XLM, alice applied + submitted + won + got paid; payout txa6c8daad…. Smoke updated to tolerate native-asset tx fees (~143k stroops / 0.014 XLM for 3 alice-signed txs); for non-native tokens the assertion still demands exact-budget delta. - Mainnet admin multi-sig provisioned per
docs/admin-custody-policy.md(3 signers, 2-of-3). 2026-06-05: launch baseline updated to software multi-sig (Freighter on 3 isolated machines) with a hardware-upgrade trigger codified inadmin-custody-policy.md§10 (TVL $250k OR daily volume $50k/day for 7 days OR first signer-machine incident → mandatory Yubikey upgrade). Pre-flight gate codified for both paths — seedocs/multisig-preflight.md(Section 1.A hardware, Section 1.B Freighter; everything else identical) andscripts/admin/verify-multisig.sh(chain-state gate, hardware-agnostic). The verify script was sanity-tested against a single-sig account and correctly fails with the per-check breakdown. - Mainnet deploy executed per
docs/mainnet-deploy-runbook.md. Result committed todeployments/mainnet.json.
See docs/audit-2026-06-stellar-skill.md for full findings.
- H1 + H2 — Admin/config moved to
instance()storage; persistent reads of event-scoped data bumpextend_ttl. Both contracts. (2026-06-04) - H3 + H4 — Per-event
Vec<Address>andMap<Address, Submission>storage replaced with per-element keys (*Count+*At(idx)+*Slot(addr)for O(1) membership). Submissions keyed per-(event, applicant). Caps lifted to 5,000 each after paged cancel landed. New errorsTooManyApplicants,TooManyContributors. Paged read APIsget_*_count+get_*_atalongside the capped snapshot reads. (2026-06-04) - Paged cancel_event — Replaces single-tx cancel with
start_cancel+process_cancel_batch+finalize_cancel.start_cancelsnapshots the refund math and flipsEventStatus::Cancelling; OwnerOnly branch settles inline. Batches refund up toMAX_REFUNDS_PER_BATCH = 25partners per tx. Finalize pays owner residual on the FullPartnerThenResidual branch and flips Cancelled. Lifts the contributor cap to 5,000. (2026-06-04) - H5 — Two-step rotation for
set_events_contracton profile contract. First-set stays single-step (deploy bootstrap); rotation ispropose_events_contract+accept_events_contractwith a ~1-day timelock and a 7-day expiry.cancel_pending_events_contractto prune. (2026-06-04) - H6 — Timelocked upgrade flow (
propose_upgrade+apply_upgrade+cancel_pending_upgradewith ~1-day timelock, 30-day expiry); on-chainversion()view (initial0.2.0);migrate()admin one-shot per version guarded byMigratedToVersion. Both contracts. (2026-06-04) - Storage layout change shipped post-testnet-deploy. 2026-06-05: testnet redeployed fresh at 0.2.0; the 0.1.x rows held nothing precious. Mainnet has not deployed yet, so the 0.1.0→0.2.0 path will never run in production.
migrate()exists per H6 with a documented per-(from, to)dispatch pattern incontracts/events/src/admin.rs+ the profile mirror, ready for the first real storage-layout upgrade after mainnet goes live. - Domain subscribers ignore the new cancel kinds. 2026-06-05: all four subscribers (
BountyEscrowSubscriber,HackathonEscrowSubscriber,GrantEscrowSubscriber,CrowdfundingEscrowSubscriber) now handleFINALIZE_CANCEL(mark domain row CANCELLED) and theSTART_CANCELOwnerOnly inline path (read on-chain status; if already Cancelled, mirror). Tests green (events + queues + escrow contract suites = 133/133). - Crowdfunding
claim_milestoneorchestrator path needs admin co-sign. 2026-06-05: newAdminSorobanAuthSignerServicefinds the SorobanAuthorizationEntry whose address matches the configured admin and signs it viaauthorizeEntry()from@stellar/stellar-base. Smoke helper exposesadminPreSign: trueondriveToCompletion; the crowdfunding claim path passes that flag. Verified end-to-end on testnet — all three milestone claims confirmed withsigned 1 admin auth entryin the log and the builder receiving the full 900 TUSD across three claim txs.
-
select_winnersre-run semantics: today rejected; revisit if a real customer wants "append-only" behavior. Open question, not blocking. - Grant committee multi-sig primitive (dedicated signer set + quorum at the contract level vs the current address-level multi-sig).
- Bounty Showdown participation badge on the boundless-profile contract for non-winning finalists.
- Multi-token support audit: verify the whitelist mechanism handles tokens with non-Stellar 7-decimal scales (currently assumed uniform).
- M1 —
select_winnerspercent math now usesremaining_escrowsnapshotted at select time so partner top-ups viaadd_fundsflow to winners instead of getting trapped until cancel. (2026-06-04) - M2 — Documented operational policy: contract-layer trustline checks aren't reliably possible in Soroban today (SAC
balance()collapses "no trustline" and "0 balance"). Admin verifies off-chain beforeset_fee_account/register_supported_token;FeeAccountUpdated+TokenRegisteredevents are the off-chain monitoring hooks. (2026-06-04) - M4 — Dropped
wins_count,submissions_count,applications_count,milestones_completedfromProfile. Off-chain indexer derives counters from emitted events. (2026-06-04) - M5 — Crowdfunding
claim_milestonerequiresadmin.require_auth()in addition toevent.owner.require_auth(). Grants stay single-auth (event owner is the grant org). (2026-06-04) - L1 — Audit recommendation targeted the pre-H1 persistent layout. After H1 the Pending* keys live in instance storage which auto-extends and is the right home for them; the temporary-storage swap is no longer a net improvement. Decision recorded inline in storage.rs.
- L2 —
Error::NotInitializedadded to both error enums. (Shipped with H1+H2.) - L3 —
panic_with_error!(NotInitialized)replaces.expect("admin not configured"). (Shipped with H1+H2.) - L4 —
MAX_FEE_BPStightened from 5000 (50%) to 1000 (10%). Per-event overrides still respect the cap. (2026-06-04) - L5 —
EventCreatedevent carriestitle. Indexers can populate listings without a follow-upget_eventread. (2026-06-04) - L6 —
__link_keepremoved;evt::EVENTS_LINK_KEEPconstant gone. The events module is widely consumed (37+evt::references across operation modules), so the keep-alive trick is no longer needed. (2026-06-04) - L7 — Crowdfunding
validate_createrejects nonzeroapplication_credit_cost(reusesError::InvalidPillarto stay inside the contracterror 50-variant cap). (2026-06-04)
- EVM adapter (per B14 phase 3).
- Solana adapter (per B14 phase 4).
- Richer
boundless-profileread API for the cross-pillar builder profile UI.
- 2026-06-03 —
fee_bps_overrideper-event field +effective_fee_bpsresolver. - 2026-06-03 —
WinnersAlreadySelectedreplay lock onselect_winners. - 2026-06-03 — Grant last-milestone sweep (G4).
- 2026-06-03 — Contract upgrade primitive verified end to end on testnet (upgrade tx 50a6ab65).
- 2026-06-04 — Stellar-skill audit landed (
docs/audit-2026-06-stellar-skill.md). - 2026-06-04 — H1+H2 storage migration: admin/config →
instance(), persistent reads bump TTL, both contracts. Tests green (events 64/64, profile 2/2). AddsError::NotInitializedto both error enums; replaces.expect()withpanic_with_error!on the admin getter (L2/L3). - 2026-06-04 — H5 two-step events-contract rotation on the profile contract:
propose+acceptwith a ~1-day timelock and a 7-day expiry;cancel_pending_events_contractprunes expired or unwanted proposals. First-set unchanged. Tests green (profile 9/9). - 2026-06-04 — H3+H4 per-element storage layout for applicants, contributors, winners; per-(event, addr) submission entries. Soft caps at 100 per list to keep cancel_event single-tx. New paged read APIs (
get_*_count+get_*_at). Tests green (events 66/66, profile 9/9). - 2026-06-04 — Paged cancel:
start_cancel+process_cancel_batch+finalize_cancel.EventStatus::Cancellingintermediate state.CancellationStatesnapshots refund math (non_owner_total, remaining_at_start, count_at_start, next_idx, branch). OwnerOnly branch settles inline. Lifts contributor + applicant caps to 5,000. Tests green (events 67/67, profile 9/9). - 2026-06-04 — H6 timelocked upgrade + on-chain version + migrate.
propose_upgrade(wasm_hash, new_version)+apply_upgrade()(after ~1 day, before 30 days) +cancel_pending_upgrade()+migrate()(one-shot per version).version()view returns the on-chain semver string (initial0.2.0). Both contracts. Tests green (events 74/74, profile 13/13). Closes audit recommendation; the prior immediate-upgrade path is gone, callers must rotate through the timelock. - 2026-06-04 — M1 + M2 + M4 + M5.
select_winnersSingle math switched toremaining_escrow(partner top-ups flow to winners). Operational trustline policy documented intoken_whitelist.rs+admin.rs(M2).Profilecounter fields dropped (M4). Crowdfundingclaim_milestonerequires admin auth on top of builder auth (M5). Tests green (events 76/76, profile 13/13). - 2026-06-04 — L4 + L5 + L6 + L7.
MAX_FEE_BPStightened to 10% (L4).EventCreatedevent carriestitlefor indexer convenience (L5).__link_keep+EVENTS_LINK_KEEPremoved (L6). Crowdfunding rejects nonzeroapplication_credit_cost(L7). L1 skipped per inline note: post-H1 the Pending* keys are already in instance storage, so the temporary swap is no longer a net improvement. Tests green (events 76/76, profile 13/13). - 2026-06-04 — Off-chain catch-up.
deploy_and_upgrade.shrewritten for the timelocked upgrade flow (propose-upgrade+apply-upgrade+cancel-pending-upgrade+migrate+status); the oldupgradeaction errors out with migration instructions. boundless-nestjs:EscrowContractClientexposesbuildStartCancel/buildProcessCancelBatch/buildFinalizeCancel+ the H6 upgrade builders; orchestrator gainsbeginStartCancel+beginProcessCancelBatch+beginFinalizeCancel+beginProposeUpgrade+beginApplyUpgrade+beginCancelPendingUpgrade+beginMigrate. Paged read APIs (get_applicant_count/get_applicant_at/get_winner_count/get_winner_at/get_contributor_count/get_contributor_at). Smoke scripts updated. NewEscrowOpKindvalues (START_CANCEL, PROCESS_CANCEL_BATCH, FINALIZE_CANCEL, PROPOSE_UPGRADE, APPLY_UPGRADE, CANCEL_PENDING_UPGRADE, MIGRATE) shipped in Prisma migration20260604100000_paged_cancel_upgrade_op_kinds. Profile.ts loses the four counter fields (M4). EventStatus gainsCancelling.tsc --noEmitclean; escrow-contract jest 125/125 green. - 2026-06-05 — Orchestrator-path testnet smokes against the new wasm (
escrow-cancel.ts,escrow-multi-contributor-cancel.ts,escrow-select-winners.ts,grant-publish-and-claim.ts,hackathon-publish.ts). All confirm the new contract surface flows cleanly throughEscrowContractClient→ orchestrator → reconcile → subscriber. Two smokes surfaced real off-chain integration gaps (recorded above as audit follow-ups): domain subscribers don't yet recognize the new cancel kinds, and the crowdfundingclaim_milestoneorchestrator path needs admin co-signing. Both are downstream of the audit, not in the audit itself. - 2026-06-05 — H5 admin rotation flow on testnet.
propose_events_contract→accept_events_contractreturning contract error 17 (PendingEventsContractTimelock) before the ~1-day timelock elapses →cancel_pending_events_contract(emitsEventsRotationCancelled) →get_pending_events_contractreturns null. Verified the events binding stays unchanged when the rotation is aborted. - 2026-06-05 — Testnet redeploy + smoke battery against the new wasm.
- profile:
CDSURDCUHB5GU64METASSZW6SZ7IIKA7RABBYPZTNM45GANGN72K22MA(wasm hashbf9fd1a7…) - events:
CDKZ5N2D4HYU7BAGNUJAB7WT5EYWVCRNUBJA54G6AVXXRLMAQFUEVMXH(wasm hashd9b0b9a1…) - both
version()return"0.2.0". - TUSD registered (
CC2OZCF4HDJJEOTQJ6QMRXR5MZRALRTSVJHGTUFBBGDMHKHQJWCR7O7N). - Surface verification: legacy
upgrade+cancel_eventconfirmed absent from the contract spec (CLI parser rejects them); all 13 new methods listed by--help(propose_upgrade, apply_upgrade, cancel_pending_upgrade, migrate, start_cancel, process_cancel_batch, finalize_cancel, get_applicant_count, get_winner_count, get_contributor_count, version, get_pending_upgrade, get_migrated_to_version). - OwnerOnly cancel smoke (event 12552982520397825, hackathon, 100 TUSD budget, 0 contributors):
start_cancelinline-flipped toCancelled, full 100 TUSD refunded to admin. Tx8c4390de5b….EventCreated.titlefield present in the event payload (L5 live). - Paged cancel smoke (event 12552982520397826, hackathon, 50 TUSD budget + Alice 20 TUSD):
start_cancelflipped toCancelling(tx7b3df18945…);process_cancel_batch(max_refunds=25)refunded Alice 20 TUSD and returned remaining=0 (tx288aeaa94d…);finalize_cancelrefunded owner 50 TUSD residual and flipped toCancelled(txe6c1049fc7…). Final state:status: "Cancelled",remaining_escrow: "0". - H6 migrate smoke: first
migrate()emittedMigrated(from "0.0.0", to "0.2.0"); replay returned contract error 69 =MigrationAlreadyApplied;get_migrated_to_versionreturns"0.2.0". - All txes recorded in
deployments/testnet.json.
- profile:
Same convention as boundless-nestjs/BACKLOG.md. External coordination (audit firm, formal verification, Stellar SDF) goes in GitHub Issues.