Skip to content

feat(spike): prove Flutter Web Freighter signing and Testnet SAC payments - #70

Merged
TOMOKI977 merged 4 commits into
mainfrom
spike/68-flutter-stellar-wallet
Oct 5, 2026
Merged

TOMOKI977 merged 4 commits into
mainfrom
spike/68-flutter-stellar-wallet

Conversation

@TOMOKI977

Copy link
Copy Markdown
Contributor

Closes #68

Summary

A Flutter Web spike, spikes/flutter-stellar-wallet/, proves the wallet boundary for puls3. Freighter 6.x connects on Testnet and signs server-prepared transactions and CAP-71 ADDRESS_V2 authorization entries. Every request is bound to the connected account and to the Testnet passphrase, and keys never leave the wallet. Payment evidence is decoded from official RPC/XDR responses. docs/spikes/flutter-stellar-wallet.md records the evidence and the concrete changes this implies for #8 and ADR-0003.

Live testing found and fixed several defects:

  • Source-account binding: the adapter now rejects, before prompting, a payload whose source is another account.
  • Freighter auth-entry contract: the adapter now signs the HashIdPreimage Freighter expects, not the whole entry.
  • ADDRESS_V2: support was added. Testnet is on protocol 29 and simulation returns only V2 credentials.
  • RPC harness: it did not compile with dart run because stellar_flutter_sdk pulls in dart:ui. Event lookup was also unpaginated.
  • Payment evidence: parsing rejected the real SAC event shape (4 topics, per-operation events).
  • Malformed envelopes: envelopes with trailing bytes reached the wallet prompt.

Acceptance criteria

  • Flutter Web connects to Freighter and obtains address plus active network without receiving private keys.
  • The spike blocks signing when the wallet is not on Testnet and binds every request to the expected network passphrase.
  • A prepared transaction XDR can be signed and the returned envelope is verified as semantically unchanged except for valid signatures.
  • Soroban authorization-entry signing is demonstrated end to end.
  • A SAC Testnet payment to a muxed destination is submitted and its event is decoded with payer, destination, muxed ID, amount, asset contract, status, and hash.
  • No secret seed or private key is committed, logged, transmitted to Serverpod, or stored by the application.
  • Automated adapter tests cover network mismatch, rejected signature, malformed XDR, modified envelope, and wallet unavailability.
  • Findings produce explicit recommended changes for issue docs: API contract between Flutter app and Serverpod #8 and ADR-0003 without implementing production endpoints.

Verification evidence

The live run used Freighter 6.x on Testnet, payer GCCB4MKFLRRD4HBXNGMXQMIIU5TSYX2TBAQIQRIGE5LSILPW77E44GOZ, on 2026-09-30.

Connect Freighter              -> address + Testnet passphrase shown
Sign 1 XLM payment to muxed M… (id 68), app accepted the signed envelope
  submit -> SUCCESS, ledger 4953089
  tx 820c8a985f219346bd7c5672d33b13b7621d384057098619ef82524501d756b4
  SAC transfer event: topics [transfer, from, to, native], data {amount: 10000000, to_muxed_id: 68}
Switch account without reconnect -> WalletAccountChanged (no prompt)
Sign ADDRESS_V2 auth entry     -> Freighter "Confirm Authorizations" (authorized address GCCB…4GOZ)
  SAC transfer 0.5 XLM authorized by that entry -> SUCCESS, ledger 4953815
  tx 731fdb98f97db64e48200999ba852ec3136dc3848dfa07c95a9958bfde62650e

cd spikes/flutter-stellar-wallet
flutter analyze      -> No issues found
flutter test         -> 42 passed
flutter build web    -> Built build\web
dart run bin/rpc_payment_harness.dart (no args) -> usage, exit 64

Explorer links:

Notes for reviewers

  • The spike uses native XLM. USDC over SAC follows the same event path but was not exercised live.
  • Classic-event emission (EMIT_CLASSIC_EVENTS) is confirmed for the SDF Testnet RPC only. Hosted providers must confirm it.
  • The Freighter API is loaded from a pinned CDN in the spike only. Production must bundle it and enforce CSP.
  • The two non-blocking review warnings, plus rejecting zero-expiration auth entries, are tracked in chore(frontend): enforce pre-prompt checks in the #68 wallet spike adapter #69.
  • The root pubspec.yaml and pubspec.lock change only to register the spike package in the workspace.

…ents

Add the issue #68 spike: a Flutter Web adapter that connects to Freighter 6.x,
binds every signature to the connected Testnet account, verifies signatures
cryptographically, and decodes official RPC/XDR payment evidence.

Live Testnet evidence (2026-09-30):
- 1 XLM classic payment to a muxed address (id 68): SUCCESS, ledger 4953089,
  tx 820c8a985f219346bd7c5672d33b13b7621d384057098619ef82524501d756b4; the SAC
  transfer event carried amount and to_muxed_id.
- Account switch without reconnect rejected with WalletAccountChanged.
- ADDRESS_V2 (CAP-71) auth entry signed by Freighter and executed in a SAC
  transfer: SUCCESS, ledger 4953815,
  tx 731fdb98f97db64e48200999ba852ec3136dc3848dfa07c95a9958bfde62650e.

Fixes found during live testing:
- Reject transaction or operation sources that differ from the session
  before prompting (PayloadAccountMismatch).
- Sign auth entries through the HashIdPreimage contract Freighter expects,
  including ENVELOPE_TYPE_SOROBAN_AUTHORIZATION_WITH_ADDRESS for ADDRESS_V2.
- Make the RPC harness runnable with plain dart run via Flutter-free SDK
  imports, and page getEvents from the transaction ledger.
- Read operation-level SAC events with four topics in PaymentEvidence,
  covered by recorded Testnet fixtures.

Refs #68
… changes

- Re-encode the incoming transaction envelope and reject it as malformed
  before prompting when it is not canonical; the SDK silently accepts
  trailing bytes, so Freighter was prompted before rejection.
- Cover empty, whitespace, non-base64, truncated and trailing-byte
  envelopes with a test that asserts no wallet prompt.
- Add "Recommended changes for #8 and ADR-0003" grounded in the live
  Testnet evidence, and record the live ADDRESS_V2 auth-entry success.

Refs #68
@TOMOKI977 TOMOKI977 added this to the Stellar Elite (Oct 10) milestone Sep 30, 2026
@TOMOKI977 TOMOKI977 added area: frontend Flutter implementation type: spike Time-boxed research with a written deliverable P0 Blocks a deadline deliverable labels Sep 30, 2026
@TOMOKI977 TOMOKI977 self-assigned this Sep 30, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Deploying puls3 with  Cloudflare Pages  Cloudflare Pages

Latest commit: 459c53f
Status: ✅  Deploy successful!
Preview URL: https://13fcdd2f.puls3-4lw.pages.dev
Branch Preview URL: https://spike-68-flutter-stellar-wal.puls3-4lw.pages.dev

View logs

The unbounded `S[A-Z2-7]{55}` rule matched a 56-character run inside
zero-padded base64 XDR in a public Testnet fixture and failed `scan`.
Word boundaries keep detecting seeds that stand alone or follow `=`,
quotes or whitespace. Verified with gitleaks v8: the PR range and the full
43-commit history report no leaks, and three freshly generated seeds in
.env, JSON and bare-line placements are still detected.

Refs #68
@TOMOKI977

Copy link
Copy Markdown
Contributor Author

Heads-up for reviewers: the payment part of this spike follows ADR-0003's direct SAC transfer to the agent, which ADR-0005 (#72) replaces with an ERC-8183 escrow (create_job + fund, then complete/reject).

The spike is still valid for what it proves: Freighter signing from Flutter Web and relaying a signed Soroban transaction on Testnet. That carries over unchanged to the escrow calls. Please review it as a signing/integration proof, not as the final payment flow; the real hire flow is specified in #77 and the contract in #55.

@Pericena Pericena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving as a signing/integration proof (not the final payment flow, per ADR-0005).

Verified locally on 8a07cd7: flutter analyze (0 issues), flutter test (42/42), flutter build web (OK).
Account binding, Testnet binding, signed-envelope integrity and ADDRESS/ADDRESS_V2 preimage signing
are enforced and cryptographically verified; no secrets found.

The fee-bump pre-prompt gap is already tracked in #69 (R3-001). Two items not covered there, for a follow-up:

  1. .gitleaks.toml: \bS[A-Z2-7]{55}\b weakens seed detection repo-wide (e.g. KEY_S... no longer matches).
    Prefer the original regex plus a path allowlist for spikes/**/test/fixtures/*.json.
  2. ci.yml has no spikes/* path filter, so the spike's analyze/tests never run in CI.
    For #27/#37: cap signatureExpirationLedger (not only reject 0) and allowlist rootInvocation contract/function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: frontend Flutter implementation P0 Blocks a deadline deliverable type: spike Time-boxed research with a written deliverable

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spike(frontend): prove Flutter–Freighter signing and SAC payment flow

2 participants