You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implement a cross-contract lifecycle guard that binds policy evaluation, postage escrow, and delivery/read receipts into one verifiable protocol path.
Right now the contracts are strong in their own lanes:
contracts/soroban/policies decides whether a sender can mail a mailbox and what postage/receipt requirements apply.
contracts/soroban/postage escrows, settles, refunds, disputes, expires, and reclaims postage.
contracts/soroban/receipts records delivery/read receipt state for encrypted payloads.
The hard missing piece is an explicit invariant layer that proves the lifecycle stayed consistent across all three contracts. A message should not be able to settle escrow or finalize receipt state unless the policy decision, postage record, and receipt record all agree on the same message identity, sender, recipient, amount, policy version, and terminal state.
Why this matters
This is protocol-critical work. Stealth Mail's core promise is that users decide who can mail them, while postage and receipts make that decision enforceable. If policy decisions, escrow records, and receipt state drift apart, the app can show a trustworthy UI while the underlying contract state is incomplete or inconsistent.
This issue should make that impossible by adding contract-level lifecycle checks and test vectors for the full path:
sender asks whether they can mail a recipient,
sender escrows the required postage,
recipient receives or rejects the message,
receipt state is recorded,
escrow is settled, refunded, disputed, expired, or reclaimed,
terminal states cannot be replayed or crossed with another message.
Relevant paths
contracts/soroban/policies/src/lib.rs
contracts/soroban/postage/src/lib.rs
contracts/soroban/receipts/src/lib.rs
contracts/soroban/**/README.md
contracts/soroban/**/spec.json
src/services/stellar/contracts/*.ts
src/server/api/postage-service.ts
src/server/api/receipt-service.ts
src/server/api/policy-service.ts
relevant unit/snapshot tests under contracts/soroban/**
Implementation direction
Design and implement a shared lifecycle identity/invariant model for contract calls.
At minimum, the implementation should consider:
a stable message_id binding that includes sender, recipient, payload hash, policy version, and required postage;
a way for postage settlement/refund paths to validate that the escrow belongs to the same lifecycle decision that receipts reference;
prevention of settlement when receipt requirements were enabled but no compatible delivery/read receipt exists;
prevention of receipt finalization when policy/postage state is missing, expired, reclaimed, refunded, or from a different sender/recipient pair;
explicit handling for trusted senders with zero postage requirements;
clear terminal-state behavior for settled, refunded, expired, disputed, and reclaimed records;
deterministic events that off-chain indexers can use to reconstruct the full lifecycle without trusting UI state.
The exact API shape is open, but the result should be contract-first and testable without relying on frontend assumptions.
Acceptance criteria
Add or update contract data structures so policy, postage, and receipt records can be linked by a stable lifecycle identity.
Add contract guards that reject mismatched sender, recipient, message id, payload hash, policy version, postage amount, or receipt requirement.
Add tests proving escrow cannot settle when a required receipt is missing or mismatched.
Add tests proving receipt state cannot be finalized for refunded, reclaimed, expired, disputed, or wrong-recipient postage records.
Add tests for trusted/allowed senders where policy permits zero postage without weakening receipt requirements.
Add tests for replay attempts using the same message id across different sender/recipient pairs.
Emit deterministic events for lifecycle transitions so off-chain clients can audit the path.
Regenerate/update contract spec.json files and TypeScript bindings if public contract APIs change.
Update contract README docs with the lifecycle invariant and threat model notes.
cargo test --workspace passes under contracts/soroban.
Existing API service tests are updated if server-side assumptions change.
Out of scope
Do not add a new UI module for this issue.
Do not add real user data, private keys, live network calls, or production secrets.
Do not bypass existing policy, postage, or receipt contracts with an off-chain-only check.
Suggested PR notes
The PR should explain:
the invariant being enforced,
the new or changed public contract methods,
how terminal states are protected,
what event stream off-chain clients should consume,
what migration or compatibility risk exists for existing demo fixtures/bindings.
Summary
Implement a cross-contract lifecycle guard that binds policy evaluation, postage escrow, and delivery/read receipts into one verifiable protocol path.
Right now the contracts are strong in their own lanes:
contracts/soroban/policiesdecides whether a sender can mail a mailbox and what postage/receipt requirements apply.contracts/soroban/postageescrows, settles, refunds, disputes, expires, and reclaims postage.contracts/soroban/receiptsrecords delivery/read receipt state for encrypted payloads.The hard missing piece is an explicit invariant layer that proves the lifecycle stayed consistent across all three contracts. A message should not be able to settle escrow or finalize receipt state unless the policy decision, postage record, and receipt record all agree on the same message identity, sender, recipient, amount, policy version, and terminal state.
Why this matters
This is protocol-critical work. Stealth Mail's core promise is that users decide who can mail them, while postage and receipts make that decision enforceable. If policy decisions, escrow records, and receipt state drift apart, the app can show a trustworthy UI while the underlying contract state is incomplete or inconsistent.
This issue should make that impossible by adding contract-level lifecycle checks and test vectors for the full path:
Relevant paths
contracts/soroban/policies/src/lib.rscontracts/soroban/postage/src/lib.rscontracts/soroban/receipts/src/lib.rscontracts/soroban/**/README.mdcontracts/soroban/**/spec.jsonsrc/services/stellar/contracts/*.tssrc/server/api/postage-service.tssrc/server/api/receipt-service.tssrc/server/api/policy-service.tscontracts/soroban/**Implementation direction
Design and implement a shared lifecycle identity/invariant model for contract calls.
At minimum, the implementation should consider:
message_idbinding that includes sender, recipient, payload hash, policy version, and required postage;settled,refunded,expired,disputed, andreclaimedrecords;The exact API shape is open, but the result should be contract-first and testable without relying on frontend assumptions.
Acceptance criteria
spec.jsonfiles and TypeScript bindings if public contract APIs change.cargo test --workspacepasses undercontracts/soroban.Out of scope
Suggested PR notes
The PR should explain: