Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
ae65eda
docs(30): context - decode offline, never guess decimals, never mislabel
braianxde Sep 19, 2026
f338172
docs(30): research calldata decoding, Squid router reality, and the D…
braianxde Sep 19, 2026
3715200
docs(30): Squid's input side decodes, its destination does not
braianxde Sep 19, 2026
20063bf
docs(30): plan calldata decoding as four waves, tracer first
braianxde Sep 19, 2026
eeb8d09
docs(state): phase 30 planned; develop's baseline is 1216, not the st…
braianxde Sep 19, 2026
54c284f
docs(30-01): record the measured branch baseline before decoding work
braianxde Sep 19, 2026
605e06e
test(30-01): pin what the drawer may claim about ERC-20 transfer call…
braianxde Sep 19, 2026
9a4727f
feat(30-01): show the real recipient and token unit for an ERC-20 tra…
braianxde Sep 19, 2026
1d3bb82
test(30-01): prove decoding never writes into the map that gets signed
braianxde Sep 19, 2026
9055beb
docs(30-01): summarise the tracer slice and log the comment-ID cleanu…
braianxde Sep 19, 2026
edccc62
docs(30-01): close the tracer plan and record phase 30 progress
braianxde Sep 19, 2026
a8c8c12
feat(30-02): add the drawer body for calls that are not a plain send
braianxde Sep 19, 2026
cf9d551
feat(30-02): read an approve, and say so when the allowance has no limit
braianxde Sep 19, 2026
97cd343
feat(30-02): admit it when a token cannot be identified
braianxde Sep 19, 2026
5f9f8ef
test(30-02): pin the same drawer line for a decoded token send
braianxde Sep 19, 2026
de3093c
docs(30-02): hold the three-line doc-comment budget on the new files
braianxde Sep 19, 2026
6ce63d6
docs(30-02): close the approve and unverified-token plan
braianxde Sep 19, 2026
427bfee
feat(30-03): say a call is unreadable instead of dumping its params
braianxde Sep 19, 2026
3d36de3
fix(30-03): answer every request, with a code that survives serialisa…
braianxde Sep 19, 2026
0e67490
fix(30-03): file the record under what was actually approved
braianxde Sep 19, 2026
5503c52
docs(30-03): name the ceiling the signature branch sits on
braianxde Sep 19, 2026
09b7638
docs(30-03): close the unknown-call and always-respond plan
braianxde Sep 19, 2026
cabad48
test(30-04): pin a real Squid payload to its input side
braianxde Sep 19, 2026
b56f7c4
feat(30-04): read a router swap on the side the payload proves
braianxde Sep 19, 2026
3c7a799
test(30-04): pin what a swap drawer may and may not say
braianxde Sep 19, 2026
5b9975f
feat(30-04): say what a swap spends, and admit the rest is unreadable
braianxde Sep 19, 2026
23aa009
docs(30-04): stop naming a sibling suite from a comment
braianxde Sep 19, 2026
b920f16
docs(30-04): close the phase on measured numbers, with DAP-02 recorde…
braianxde Sep 19, 2026
504796e
docs(30-04): cut the summary toward its budget
braianxde Sep 19, 2026
ab81eea
fix(30): a send on Polygon says MATIC, not ETH
braianxde Sep 19, 2026
baa2eb0
docs(30): verification - three of four criteria, and why the fourth i…
braianxde Sep 19, 2026
bc27a67
docs(handoff): phase 30 shipped, three PRs open and the order they mo…
braianxde Sep 19, 2026
41bc1dd
docs(state): reconcile with develop after the #233 merge
braianxde Sep 21, 2026
0e313ea
fix(30): keep a resolved token beside its native value, and stop offe…
braianxde Sep 21, 2026
9f8460a
docs(30): defer the token receipt unit split to a model change
braianxde Sep 21, 2026
65e6c4d
fix(30): gas on every drawer, the chain's real coin, scoped tokens, a…
braianxde Sep 21, 2026
c4ca07f
fix(30): a request is answered only once the relay took the answer
braianxde Sep 21, 2026
ad59175
docs(30): comment and summary budgets
braianxde Sep 21, 2026
cc6c83c
docs(state): reconcile with develop after the #234 merge
braianxde Sep 21, 2026
158c3a2
docs(30): verification addendum - the live walk is done, W-1 closed, …
braianxde Sep 21, 2026
fcded22
docs(handoff): #234 merged, #235 merge-ready after three review round…
braianxde Sep 21, 2026
6703584
fix(30): a swap spending the chain's own coin is one native figure
braianxde Sep 21, 2026
87258c9
fix(30): the retry says what was said, and every drawer names the acc…
braianxde Sep 21, 2026
a8b3f9c
fix(30): an approve to an unknown contract is unreadable, and an appr…
braianxde Sep 21, 2026
3fa0270
docs(todo): order-dependent Inter font failure, with the reproducing …
braianxde Sep 21, 2026
c41eb4e
fix(30): a request names its chain, the hash answer is retried, and s…
braianxde Sep 21, 2026
d9c57b0
fix(30): only chains the wallet can sign on are advertised, and a nat…
braianxde Sep 21, 2026
2df6af4
fix(30): a native swap with no value attached is shown as the mismatc…
braianxde Sep 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .planning/REQUIREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,9 @@ was a `TODO` that recorded a fake `completed` transaction with `hash: ""`) and t

### dApp Signing Honesty (DAP)

- [ ] **DAP-01**: Reown approval flow decodes ERC-20 `transfer`/`approve` calldata and shows the decoded action in the drawer
- [ ] **DAP-02**: Known-router swap calls decode to "swapping X → Y" in the approval drawer
- [ ] **DAP-03**: Undecodable calldata is labeled an unknown-contract call with a visible warning, never presented as a plain send
- [x] **DAP-01**: Reown approval flow decodes ERC-20 `transfer`/`approve` calldata and shows the decoded action in the drawer
- [ ] **DAP-02**: Known-router swap calls decode to "swapping X → Y" in the approval drawer — PARTIAL: the input side ("Swapping 1 GNUS via Squid") ships and the drawer states that the destination cannot be read; "→ Y" is not met because Squid's calldata does not carry the destination token or amount at any fixed offset
- [x] **DAP-03**: Undecodable calldata is labeled an unknown-contract call with a visible warning, never presented as a plain send

### Beyond v2.0 (formerly "v2 Requirements", deferred)

Expand Down Expand Up @@ -254,9 +254,9 @@ was a `TODO` that recorded a fake `completed` transaction with `hash: ""`) and t
| SWAP-01 | Phase 26 — one requirement, eight plans (26-01..26-08); the former 27 and 28 were the same work | **Complete** — 8/8 criteria delivered and walked on Base mainnet 2026-09-17; a real swap executed, 26-VERIFICATION.md passed 33/33 |
| FEE-01 | **Deferred to backlog 2026-09-18** — business item; only Squid can enable it | Deferred |
| FEE-02 | Phase 29 — Fee transparency | Complete |
| DAP-01 | Phase 30 — dApp calldata decoding (end blind signing) | Pending |
| DAP-02 | Phase 30 — dApp calldata decoding (end blind signing) | Pending |
| DAP-03 | Phase 30 — dApp calldata decoding (end blind signing) | Pending |
| DAP-01 | Phase 30 — dApp calldata decoding (end blind signing) | Complete |
| DAP-02 | Phase 30 — dApp calldata decoding (end blind signing) | Partial — input side only |
| DAP-03 | Phase 30 — dApp calldata decoding (end blind signing) | Complete |

**Coverage (v2.0):** 6 total; **5/5 active mapped to phases 26-30 ✓** — no orphans, no duplicates.
FEE-01 deferred to the backlog 2026-09-18 (business item, not engineering).
Expand Down
19 changes: 13 additions & 6 deletions .planning/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -1530,9 +1530,9 @@ money (Phase 26) and legs needing real funds or the live catalogue get a debug-b
| SWAP-01 | Phase 26 — one end-to-end requirement; all 8 criteria delivered and walked on Base mainnet 2026-09-17 | Complete |
| FEE-01 | **Deferred to backlog 2026-09-18** — business item; only Squid can enable it, server-side on the integrator ID | Deferred |
| FEE-02 | Phase 29 — Fee transparency | Complete |
| DAP-01 | Phase 30 — dApp calldata decoding (end blind signing) | Pending |
| DAP-02 | Phase 30 — dApp calldata decoding (end blind signing) | Pending |
| DAP-03 | Phase 30 — dApp calldata decoding (end blind signing) | Pending |
| DAP-01 | Phase 30 — dApp calldata decoding (end blind signing) | Complete |
| DAP-02 | Phase 30 — dApp calldata decoding (end blind signing) | Partial — input side only; the destination is not in the transaction |
| DAP-03 | Phase 30 — dApp calldata decoding (end blind signing) | Complete |

**Coverage:** 5/5 active v2.0 requirements mapped — no orphans, no duplicates. FEE-01 was deferred
to the backlog on 2026-09-18 as a business item (see Out of scope). `SWAP-01` is one end-to-end promise owned by Phase 26 alone (the former 27 and 28 were the same work under three numbers); the drafted `SWP-01..08` that split it were retired into its criteria on 2026-09-16.
Expand Down Expand Up @@ -1607,9 +1607,16 @@ a missing field. Criterion 5 is what makes Squid's eventual switch a no-op in th
3. Undecodable calldata renders an explicit unknown-contract-call warning — never presented as a plain send (widget test with a garbage/unknown-selector payload)
4. Decoding is display-only: the transaction payload signed is byte-identical to before — the Phase 21 behavioral-identity contract test for the two signing drawers stays green (decode may not alter what is signed)

**Plans**: TBD
**Plans**: 4/4 plans executed

Plans:
- [x] 30-01-PLAN.md — tracer: one ERC-20 transfer decoded end to end, plus the measured branch baseline
- [x] 30-02-PLAN.md — approve, unlimited allowance, and the token this wallet cannot vouch for
- [x] 30-03-PLAN.md — unknown calls, the two sign methods that currently hang the caller, and an honest receipt
- [x] 30-04-PLAN.md — Squid input-side decode, the router allow-list, and the phase gate

**UI hint**: yes
**Security note**: signing-path UI — plans touching the two approve drawers carry a threat model, per the standing Phase 21 gate.
**Security note**: signing-path UI — plans touching the two approve drawers carry a threat model, per the standing Phase 21 gate. Every plan in this phase carries one.

## Progress (v2.0)

Expand All @@ -1621,4 +1628,4 @@ this milestone.
|-------|----------------|--------|-----------|
| 26. Swap that actually swaps | 8/8 | Complete — every plan walked on Base mainnet; 26-VERIFICATION.md passed 33/33 | 2026-09-17 |
| 29. Integrator fee | 0/TBD | Not started | - |
| 30. dApp calldata decoding (end blind signing) | 0/TBD | Not started | - |
| 30. dApp calldata decoding (end blind signing) | 3/4 | In progress | - |
23 changes: 11 additions & 12 deletions .planning/STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
gsd_state_version: 1.0
milestone: v2.0
milestone_name: Squid Router integration
current_phase: 30
current_phase_name: "dApp calldata decoding (end blind signing) — not started; phase 29 (Fee transparency) COMPLETE 2026-09-18: 4/4 plans, FEE-02 closed"
current_phase: 30
current_phase_name: "dApp calldata decoding (end blind signing) - 4/4 plans executed"
status: in_progress
stopped_at: Completed 29-04-PLAN.md — phase 29 fully executed 4/4
last_updated: "2026-09-18T16:29:13.969Z"
last_activity: 2026-09-18
last_activity_desc: 29-04 executed and verified — rendering coverage, FEE-02 closed
stopped_at: "Phase 30 COMPLETE (4/4) on branch phase-30-calldata-decoding. Plan 30-04 added the Squid router allow-list and the input-side swap decode: kKnownRouters is const and holds ONE chain (Base 8453) and one address, because that pair is the only one a recorded live response evidences -- the chain-1 entry was dropped for lack of proof. tryDecodeSwapInput reuses the ERC-20 selector-then-tuple core, so the same 68-byte guard stops a fixed-offset read on a payload that did not earn it; a mutated selector, a 67-byte payload, an off-Base chain and an unrecognised selector on a listed router all fall to unknownCall (the router still named). The drawer says "Swapping 1 GNUS via Squid" and states plainly that the destination token and amount are not in the transaction and must be checked on the dApp. Nothing scans the blob for a token address. DAP-02 is recorded PARTIAL, not met: Squid's calldata carries fromToken/fromAmount at fixed offsets but toToken only nested at a route-dependent position and toAmount not at all. 1341 pass / 3 skip / 0 fail (30-03 baseline 1302, +39 = exactly the cases this plan added), analyze "No issues found!" exit 0, dart format lib test 0 changed exit 0, brace + raw-colour gates exit 0, drawer census 32/32 green with no new entry needed, git diff develop --stat -- pubspec.yaml pubspec.lock empty. Next: human walk of the two drawers on a real WalletConnect session, then verification."
last_updated: "2026-09-19T22:40:00.000Z"
last_activity: 2026-09-19
last_activity_desc: Phase 30 executed and verified on phase-30-calldata-decoding (PR #235); DAP-02 partial by design
progress:
total_phases: 3
completed_phases: 2
Expand All @@ -24,8 +24,7 @@ progress:
See: .planning/PROJECT.md (updated 2026-07-21)

**Core value:** Users can safely custody their keys and reliably perform core wallet actions.
**Current focus:** Phase 29 — Fee transparency COMPLETE (4/4 plans, 2026-09-18), FEE-02 closed.
Next: Phase 30 — dApp calldata decoding (end blind signing).
**Current focus:** Phase 30 complete (2026-09-19) on `phase-30-calldata-decoding`, draft PR #235. Phases 26 (PR #233) and 29 (PR #234) merged to develop 2026-09-21; #235 is the last v2.0 PR.

## Current Position

Expand All @@ -46,8 +45,8 @@ Last activity: 2026-09-18 — 29-04 executed and verified
| Phase | Name | Status |
|-------|------|--------|
| 26 | Swap that actually swaps (absorbs former 27, 28) | Complete 2026-09-17 |
| 29 | Fee transparency | Complete 2026-09-18 (4/4 plans; FEE-02 closed) |
| 30 | dApp calldata decoding (end blind signing) | Not started |
| 29 | Fee transparency | Complete 2026-09-18 (4/4 plans; FEE-02 closed) |
| 30 | dApp calldata decoding (end blind signing) | Complete 2026-09-19 (4/4; DAP-02 partial by design) |

(v1.0 residue still executes alongside v2.0 — see the v1.0 Progress table in ROADMAP.md: phase 14
has an unexecuted plan 14-08, and the mobile-pass tail plus deferred light-mode walks remain
Expand Down Expand Up @@ -526,8 +525,8 @@ the redesign track added many test files since the original 14-test snapshot). *

## Session Continuity

Last session: 2026-09-18T16:29:13.277Z
Stopped at: Completed 29-04-PLAN.md — phase 29 fully executed 4/4
Last session: 2026-09-19T22:40:00.000Z
Stopped at: Phase 30 COMPLETE (4/4) on branch phase-30-calldata-decoding. Plan 30-04 added the Squid router allow-list and the input-side swap decode: kKnownRouters is const and holds ONE chain (Base 8453) and one address, because that pair is the only one a recorded live response evidences -- the chain-1 entry was dropped for lack of proof. tryDecodeSwapInput reuses the ERC-20 selector-then-tuple core, so the same 68-byte guard stops a fixed-offset read on a payload that did not earn it; a mutated selector, a 67-byte payload, an off-Base chain and an unrecognised selector on a listed router all fall to unknownCall (the router still named). The drawer says "Swapping 1 GNUS via Squid" and states plainly that the destination token and amount are not in the transaction and must be checked on the dApp. Nothing scans the blob for a token address. DAP-02 is recorded PARTIAL, not met: Squid's calldata carries fromToken/fromAmount at fixed offsets but toToken only nested at a route-dependent position and toAmount not at all. 1341 pass / 3 skip / 0 fail (30-03 baseline 1302, +39 = exactly the cases this plan added), analyze "No issues found!" exit 0, dart format lib test 0 changed exit 0, brace + raw-colour gates exit 0, drawer census 32/32 green with no new entry needed, git diff develop --stat -- pubspec.yaml pubspec.lock empty. Next: human walk of the two drawers on a real WalletConnect session, then verification.
UNCOMMITTED). Next in the walk queue: **15-06** (Transactions tab walk), then 16 + 17 walks,
then Phase 13 code (13-03 walk, 13-04, 13-05). App running single clean instance. Light deferred.
Current official-track resume point: **Phase 06 is 5/6 — 06-06 (closeout) is next.** The paragraph
Expand Down
73 changes: 73 additions & 0 deletions .planning/handoffs/HANDOFF-260919-phase-30-and-three-open-prs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Handoff — 2026-09-19 — phase 30 shipped, three PRs open

Executor session. Branch ended on `phase-30-calldata-decoding`, tree clean apart from
the pre-existing untracked `squid.local.json`.

## What happened

- **Phase 29 (fee transparency)** was self-reviewed by four agents, four findings fixed,
rebased onto the phase-26 tip, and opened as draft **PR #234**.
- **Phase 30 (dApp calldata decoding)** ran end to end — context, research, patterns, four
plans, four execution waves, verification — and opened as draft **PR #235**. This closes
the last phase of milestone v2.0.

## The three PRs, and the order they have to move in

| PR | Branch | Base | State |
|---|---|---|---|
| #233 | `phase-26-swap-wiring` | `develop` | Ready. 10/10 checks, 10/10 threads resolved. **Unmerged — this is the bottleneck.** |
| #234 | `phase-29-integrator-fee` | `phase-26-swap-wiring` | Draft. **Gets no CI at all** — `build.yml` only triggers `pull_request` on `develop`/`main`. Retarget to develop once #233 merges. |
| #235 | `phase-30-calldata-decoding` | `develop` | Draft. Cut from develop, independent of the swap stack, so CI runs normally. |

Merging #233 unblocks #234's retarget and its first CI run. Nothing blocks #235.

## Baselines — do not read these against each other

- `phase-26-swap-wiring`: 1366 → after yesterday's fixes, the stack sits higher
- `phase-29-integrator-fee`: **1392** pass / 5 skip / 0 fail
- `phase-30-calldata-decoding`: **1342** pass / 3 skip / 0 fail

30's number is lower because it is cut from develop, which carries neither 26 nor 29.
That is arithmetic, not a regression. This repo has burned a day on exactly this confusion
before.

## Decisions that are settled — do not re-litigate

- **DAP-02 ships PARTIAL and the requirement box stays unticked.** Squid's router is a
generic multicall: verified against our own recorded response, word 0 is exactly
`fromToken.address` and word 1 exactly `fromAmount`, but `toToken` appears only nested at
a route-dependent position and `toAmount` not at all. The drawer says what is being spent
and states the destination is unreadable. Locating it by scanning the blob is a heuristic
a hostile payload can seed, on the last screen before a signature.
- **The router allow-list holds one chain (Base 8453).** Ethereum mainnet was dropped
because only 8453 is evidenced; the chain-1 address came from a docs page that 404'd.
- **Message signing rejects cleanly rather than offering an Approve button** it cannot
honour. A real EIP-712 renderer is deferred.
- **#233's F4 and N4 are deferred by choice**, not forgotten — cross-chain is a product
call, the cubit refactor is a refactor. Both have replies on their threads.

## Traps found this session

- **`gsd-tools query state.*` corrupts this repo's STATE.md** — resets `current_phase`,
invents fields, and rewrites CRLF→LF (1436-line diff for a 12-line edit). Hand-edit the
frontmatter instead. Same family as the known `phase.complete` bugs.
- **`squid.local.json` is not gitignored on develop.** The `*.local.json` rule exists only
on `phase-26-swap-wiring` and arrives with #233. Until then, do not `git add -A` on a
develop-based branch — the file holds the Squid integrator ID.
- `dart format .` over the whole tree is red and always has been (365 generated files under
`squidrouter/` and `banxa/`). CI gates the scoped `dart format lib test`, which is clean.

## Owed

- **The phase 30 live walk.** A debug build on a real WalletConnect session: one ERC-20
transfer and one Squid swap, both appearances. Nothing else confirms the relay path, a
*current* Squid payload, or light-mode legibility on device.
- Six pre-existing decision-ID comments in `lib/reown/` files phase 30 did not touch —
listed in the phase's `deferred-items.md`. They block that phase's own grep gate.
- A token send now shows **no** explorer link where it previously showed a wrong-chain one;
`getExplorerUrl` is keyed on coin symbol, which is now correct rather than always "ETH".

## Next

Merge #233, retarget #234, confirm its CI. Phase 30 needs the device walk before #235
leaves draft. v2.0 has no phases left after that.
57 changes: 57 additions & 0 deletions .planning/handoffs/HANDOFF-260921-two-prs-through-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Handoff — 2026-09-21 — #234 merged, #235 merge-ready, v2.0 has no PRs left

Executor session. Branch ended on `phase-30-calldata-decoding`, tree clean apart from
the pre-existing untracked `squid.local.json`.

## What happened

- **#233 (phase 26) merged into develop** at 13:08Z by henriqueaklein.
- **#234 (phase 29)** rebased onto that develop (clean), retargeted, CI'd for the first
time, taken through two Codex rounds (6 threads, all fixed and resolved), and
**merged by Braian at 17:07Z**.
- **#235 (phase 30)** rebased twice (after #233, then after #234), taken through three
Codex rounds (12 threads: 11 fixed and resolved, one deferred by decision), CI green
8/8 on `cc6c83c5`, out of draft, mergeable. **The live walk is done** — Braian ran
both flows in both appearances on 2026-09-21. Verification addendum in
`30-VERIFICATION.md`. Nothing blocks the merge.

## The one open thread on #235, and why

Codex P1 "build token receipts from the decoded transfer": `Transaction.coinSymbol` is
the model's only unit, and history renders the amount, the Network Fee *and* the
Network row from it. A token transfer is mislabelled whichever coin it is filed under.
Braian chose to leave it as is; it is recorded in the phase's `deferred-items.md` next
to the explorer-link item, which needs the same asset-vs-chain split. That is a Hive
schema change across every transaction display — a phase, not a review fix.

## Traps found this session

- **A CRLF Dart file passes `tool/check_brace_style.sh` locally and fails it on CI.**
Ubuntu's mawk sees `) {\r` and prints every correctly-braced `if` in the file as a
violation, so the log lists good code. Both PRs hit it (`handle_dapp_requests.dart`,
`route_details_card_test.dart`). Check `git ls-files --eol lib test | grep i/crlf`
before pushing; only two pre-existing test files are CRLF on develop.
- **The same flip on `.planning/STATE.md` makes a 9-line change a whole-file rebase
conflict.** Rebuild from develop's LF copy and re-apply the branch's own edits as a
patch (`git diff <old-base> <old-tip> -- file | git apply -3`) — taking `--theirs`
wholesale drops the sibling phase's close-out.
- **`build.yml` fires `pull_request` only on `develop`/`main`**, so a stacked PR gets no
CI and a base retarget alone does not trigger one; close/reopen does.
- **Dependency downloads flake.** `Failed to download SuperGenius/GeniusSDK: "HTTP
response code said error"` and a corrupt Android SDK zip took out different platforms
on consecutive attempts; every job passed on `gh run rerun --failed` with no code
change. Check the control case (develop's own run) before reading it as code.
- In this Git Bash, `git show rev:path` needs `MSYS_NO_PATHCONV=1`, and Python does not
see MSYS `/tmp` — use the scratchpad path.

## Numbers — do not read these against each other

- develop after #234: the phase-30 branch's suite on top of it is **1539 / 5 skip / 0**.
- Codex's three rounds on #235 added 26 tests; the two on #234 added 9.

## Next

Merge #235 (`gh pr merge 235 --rebase --delete-branch` per the merge-pr skill; #233 and
#234 landed as merge commits, so either is defensible). After that v2.0 has no open
phases and no open PRs; the deferred items are the asset-unit model change and the
explorer-by-chainId fix, both in phase 30's `deferred-items.md`.
Loading
Loading