Skip to content

Instawards: pay_batch SAC settlement, Ed25519 oracle hardening, Bulk Pay dashboard - #5

Merged
r-json merged 1 commit into
mainfrom
instawards/pay-batch-sac
Sep 5, 2026
Merged

Instawards: pay_batch SAC settlement, Ed25519 oracle hardening, Bulk Pay dashboard#5
r-json merged 1 commit into
mainfrom
instawards/pay-batch-sac

Conversation

@r-json

@r-json r-json commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Deliverables 1–3 of the 30-day Instawards sprint, validated on Stellar Testnet.

Contract: CCVIQZLSJIPSCFH2QGPKN5IOAA5ZQ4DOD4HLBZFMYKXPRAMCIOZGFJDF

Reviewer evidence

Artifact Location
Testnet validation docs/evidence/TESTNET_VALIDATION.md
50 transaction hashes docs/evidence/all_50_hashes.txt
Developer guide docs/DEVELOPER_GUIDE.md
Demo video checklist docs/evidence/DEMO_VIDEO_CHECKLIST.md

Representative transactions:

D1 — Token transfer integration

PaymentSchedule carries a per-payee token: Address, so one batch settles multiple assets. Custody is funded and refunded per distinct asset (one transfer per token, not per payee). pay_batch replaces finalize_payment, which remains as a delegating alias so the live Mainnet ABI and existing client keep working.

Verified on Testnet: custody funded with two transfers (500000000 USDC + 100000000 native), settled with two transfers, custody drained to 0 in both assets.

D2 — Ed25519 oracle verification

  • Signature is verified before the nonce is consumed. Previously a garbage signature could burn an escrow's nonce sequence.
  • pay_batch returns ProofMissing unless every payment carries a verified attestation. Both approvals could previously settle a batch with zero oracle attestations — the "funds move only against proof of work" property was procedural, not enforced.
  • rotate_oracle_key revokes previously verified proofs, since rotation implies the retired key may be compromised.
  • SignersNotDistinct rejects manager == finance_approver at creation and settlement.
  • scripts/oracle-cli.mjs signs the 32-byte per-payment message, one signature per payee with sequential nonces.

D3 — Bulk Pay dashboard

/bulk-pay: CSV upload, payee preview, role-based dual signing, transaction status.

Approval badges read on-chain state via get_escrow, not React state. manager_approve and finance_approve are separate on-chain transactions; local state would render green with no on-chain approval and settlement would fail with #5.

Build fix (was blocking, pre-existing)

The contract could not be deployed at all before this PR:

HostError: Error(WasmVm, InvalidAction)
"reference-types not enabled: zero byte expected"

rustc 1.85 emits reference-types into wasm32-unknown-unknown. Fixed by targeting wasm32v1-none. rust-toolchain.toml pins 1.85.0 — the only version that works, since soroban-sdk 20.5.0 fails to compile on newer rustc (ethnum E0512) and fails to resolve on older cargo (zeroize edition2024).

CI

Adds a Rust job (cargo test --locked + wasm32v1-none build). No Rust test has ever run in CI. Trigger extended to instawards/**, without which this branch never builds.

Tests

Suite Before After
Rust 29 40
TypeScript 79 79

Includes a cross-language conformance test asserting the CLI's signature bytes are accepted on-chain, so encoding drift fails in CI rather than as an opaque Testnet rejection.

Not yet done

  • The Freighter signing path has never been executed in a browser. Compile-verified only; needs manual validation before the demo video.
  • Demo video not recorded.
  • Negative cases (#5, #15, retired-key rejection) are simulation-rejected and have no on-chain hashes — reviewers reproduce them by re-running the documented commands.
  • ESCROW_ID is hardcoded in /bulk-pay; should become a selector.

🤖 Generated with Claude Code

…, Bulk Pay UI

Deliverables 1-3 of the Instawards sprint, validated on Stellar Testnet.

Contract (D1 - token transfer integration):
- PaymentSchedule gains `token: Address`, so one batch settles multiple assets
  (USDC + native XLM proven on Testnet). CoreFlowEscrow.token removed.
- Custody funds and refunds per distinct asset: one transfer per token rather
  than one per payee.
- `pay_batch` replaces `finalize_payment`, which is retained as a delegating
  alias so the live Mainnet ABI and existing dashboard client keep working.

Contract (D2 - oracle verification):
- `verify_oracle_work` / `track_nonce` extracted. Signature is now verified
  BEFORE the nonce is consumed; previously a garbage signature could burn an
  escrow's nonce sequence.
- `pay_batch` returns ProofMissing unless every payment carries a verified
  oracle attestation. Both approvals could previously settle a batch with zero
  attestations, so the "funds move only against proof of work" property was
  procedural rather than enforced.
- `rotate_oracle_key` revokes previously verified proofs, since a rotation
  implies the retired key may be compromised.
- `SignersNotDistinct` rejects manager == finance_approver at creation and at
  settlement; identical signers made the dual-approval gate vacuous.

Build (blocking, pre-existing):
- rust-toolchain.toml pins 1.85.0. soroban-sdk 20.5.0 fails to compile on newer
  rustc (ethnum E0512) and fails to resolve on older cargo (zeroize edition2024).
- Target moved to wasm32v1-none. wasm32-unknown-unknown under 1.85 emits
  reference-types and the Soroban host rejects the upload with
  Error(WasmVm, InvalidAction). The contract was undeployable before this.

Tooling and dashboard (D3):
- scripts/oracle-cli.mjs signs the 32-byte per-payment message the contract
  reconstructs, one signature per payee with sequential nonces.
- scripts/generate-testnet-batches.mjs drives full lifecycles for bulk
  validation.
- /bulk-pay: CSV upload, payee preview, role-based dual signing. Approval
  badges read on-chain state via get_escrow rather than local React state,
  which would show green without any on-chain approval.
- /api/submit-batch returns oracle signatures from the live nonce watermark.

CI:
- Adds a Rust job (cargo test --locked + wasm32v1-none build). No Rust test has
  ever run in CI. Trigger extended to instawards/** or the branch never builds.

Tests: 40 Rust (was 29), 79 TypeScript. Cross-language conformance test asserts
CLI signature bytes are accepted on-chain.

Testnet evidence: docs/evidence/TESTNET_VALIDATION.md, 50 hashes in
docs/evidence/all_50_hashes.txt.
Contract: CCVIQZLSJIPSCFH2QGPKN5IOAA5ZQ4DOD4HLBZFMYKXPRAMCIOZGFJDF

Not validated: the Freighter signing path has never been executed in a browser.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
coreflow Ready Ready Preview Sep 5, 2026 12:39pm UTC

@r-json
r-json merged commit 2273ea3 into main Sep 5, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant