|
| 1 | +--- |
| 2 | +name: testing-wallet-adapter-changes |
| 3 | +description: Use when the user lands a change to any wallet adapter package in this monorepo (core, react, react-ui, or any `wallets/*` adapter) or to `packages/aleo-types` that adds or modifies a field consumed by dapps. Walks Claude through writing a **self-contained example component in `examples/react-app`** that exercises the change, and testing it end-to-end against a real wallet extension installed in the browser. |
| 4 | +--- |
| 5 | + |
| 6 | +# Testing a wallet-adapter change end-to-end |
| 7 | + |
| 8 | +## When this applies |
| 9 | + |
| 10 | +Invoke this skill whenever a change lands that alters the dapp-facing surface of the wallet adapter. Concretely: |
| 11 | + |
| 12 | +- A new field on `TransactionOptions` / `AleoDeployment` / any adapter method argument (defined in `packages/aleo-types/src/`). |
| 13 | +- A new method on `BaseAleoWalletAdapter` or any `wallets/<name>/src/<Name>WalletAdapter.ts`. |
| 14 | +- A change to the `useWallet()` context shape in `packages/aleo-wallet-adaptor/react/src/context.ts`. |
| 15 | +- Any wire-protocol change the adapter forwards to the extension (even if the adapter code itself just spreads options — the demo is still needed to prove the value flows). |
| 16 | + |
| 17 | +If the change is purely internal to an adapter (refactor, logging, no visible surface change), this skill does not apply — write a unit test in that package instead. |
| 18 | + |
| 19 | +## The plan in one line |
| 20 | + |
| 21 | +Add a **new sibling component** under `examples/react-app/src/components/functions/` that exercises the new surface, then build and run against a real wallet extension and a testnet program chosen to exercise the specific semantics. |
| 22 | + |
| 23 | +## Guardrails (do NOT violate) |
| 24 | + |
| 25 | +- **Never modify existing demo components** (`ExecuteTransaction.tsx`, `DeployProgram.tsx`, etc.) to add coverage for a new feature. Add a sibling. Existing components are reference material for the docs site. |
| 26 | +- **Never touch wallet-adapter packages or `aleo-types`** from the example to work around missing plumbing. If the adapter doesn't pass your field through, fix the adapter (and write a PR for it), don't hack around it in the demo. |
| 27 | +- **Never hardcode private keys, mnemonics, or funded addresses** in committed code. The demo should default to `useWallet().address` for any "from" field. |
| 28 | +- **Never add a new runtime dependency** to `examples/react-app` solely for this test. If you need a helper (e.g. program-ID → field), ship a tiny lookup or compute it via primitives already in the workspace. |
| 29 | + |
| 30 | +## Step-by-step procedure |
| 31 | + |
| 32 | +### 1. Understand the change |
| 33 | + |
| 34 | +Before writing a line of dapp code, answer: |
| 35 | + |
| 36 | +- **What is the new surface?** Which type gained a field, or which method gained an argument? Locate the type definition in `packages/aleo-types/` or the method in `packages/aleo-wallet-adaptor/wallets/<name>/src/`. |
| 37 | +- **Does the adapter forward the new surface?** Grep the target adapter for the new field/argument. If it's passed through via object spread (`{ ...options }`) or explicit forwarding, you're good. If the adapter drops it, the change is incomplete — file that first. |
| 38 | +- **Does the extension handle the new surface?** For Shield, that's `~/dev/shield-extension`. Look for the matching end-to-end path (typically: messaging validation → service → worker). If the extension doesn't handle it yet, the demo will succeed at the adapter boundary but you won't see the feature land on-chain. |
| 39 | +- **What success looks like** for this specific feature — return value from the extension? A specific shape on the testnet explorer? Different popup UI? Write it down before coding. |
| 40 | + |
| 41 | +### 2. Pick a testnet target that exercises the change |
| 42 | + |
| 43 | +Use an already-deployed program on testnet so nothing needs deploying. Canonical sources of truth: |
| 44 | + |
| 45 | +- `https://api.provable.com/v2/testnet/programs/<name>.aleo` — raw Aleo-instructions source (JSON with a `"program"` field). |
| 46 | +- `https://testnet.explorer.provable.com/program/<name>.aleo` — human-readable browser view. |
| 47 | + |
| 48 | +Prefer a program whose transition: |
| 49 | +- Has only **public** inputs (avoids record-state bootstrapping). |
| 50 | +- Has **low finalize-state dependency** (either no finalize, or finalize state that can be seeded from the dapp in one extra button click). |
| 51 | +- Exercises the specific semantics of the change — e.g. `call.dynamic` programs for `imports`, deployment flow for a deployment-option change, a record-returning transition for a decryption-flow change. |
| 52 | + |
| 53 | +If you can't find one, note it explicitly and degrade to a *plumbing-only* test: prove the wallet accepts the new option and returns a `transactionId`, regardless of finalize. |
| 54 | + |
| 55 | +### 3. Scaffold the new component |
| 56 | + |
| 57 | +Each of these five edit points mirrors how every existing demo in this example is registered. Follow the pattern exactly — do not improvise. |
| 58 | + |
| 59 | +1. `examples/react-app/src/components/functions/<Feature>.tsx` — the component. Use `useWallet()` for `connected`, `address`, `executeTransaction` (or whichever method is under test), `transactionStatus`, and `network`. Use `useWalletModal()` from `@provablehq/aleo-wallet-adaptor-react-ui` for the `openWalletModal` affordance. |
| 60 | +2. `examples/react-app/src/pages/<Feature>Page.tsx` — trivial wrapper: `<div className="max-w-4xl mx-auto"><Feature /></div>`. |
| 61 | +3. `examples/react-app/src/pages/index.ts` — add the export. |
| 62 | +4. `examples/react-app/src/routes.tsx` — add `{ path: '<slug>', element: <FeaturePage /> }`. |
| 63 | +5. `examples/react-app/src/components/layout/Sidebar.tsx` — add entry to the appropriate `navigationGroups` group (Transactions / Signatures / Data / etc.) with a `lucide-react` icon. |
| 64 | +6. `examples/react-app/src/lib/codeExamples.ts` — add a `<feature>` entry and any new `PLACEHOLDERS` keys. Render via `<CodePanel code={codeExamples.<feature>} language="tsx" highlightValues={{ [PLACEHOLDERS.X]: state.x, … }} />`. |
| 65 | + |
| 66 | +UI primitives are in `examples/react-app/src/components/ui/`: `button`, `input`, `label`, `alert`, `separator`, `tabs`, `select`, `checkbox`, `card`, `textarea`, `tooltip`, `badge`. |
| 67 | + |
| 68 | +### 4. Copy the status-polling block verbatim from `ExecuteTransaction.tsx` |
| 69 | + |
| 70 | +Don't abstract. The `pollTransactionStatus` + status-polling `<Alert>` block is duplicated across several demos on purpose (it keeps each demo readable in isolation). Copy it, adapt state names if needed, keep the `TransactionStatus.ACCEPTED / REJECTED / FAILED` branching. |
| 71 | + |
| 72 | +### 5. Make the demo self-sufficient where feasible |
| 73 | + |
| 74 | +If the feature-under-test needs on-chain preconditions (a record, a balance, an approval) that can be satisfied from the dapp, add small "prep" buttons *inside the same component* — do not send users to a different page. Example: a feature that moves tokens through a router should include a "Mint yourself tokens" button so the finalize step can land, not just a note telling the reader to go mint elsewhere. |
| 75 | + |
| 76 | +Rules of thumb for prep buttons: |
| 77 | +- One labeled `<Input>` for the per-action amount (shared across same-kind buttons). |
| 78 | +- A `pendingAction` enum (`'prep-a' | 'prep-b' | 'main' | null`) so only one button runs at a time and the others disable. |
| 79 | +- Clear labels on spinners (`"Minting toka…"`, `"Approving router…"`) — not a generic "Loading". |
| 80 | +- A shared status Alert for the most recent call (no need for per-action status panes). |
| 81 | + |
| 82 | +If a precondition *cannot* be satisfied from the dapp (e.g. it needs a deployer key), say so in the component's explanatory `<Alert>` — don't pretend it can. |
| 83 | + |
| 84 | +### 6. Types must compile without `as any` |
| 85 | + |
| 86 | +The whole point of landing the new surface in `aleo-types` is that `useWallet().executeTransaction(...)` accepts the new field by type. If you find yourself casting `as any` or `@ts-ignore`, either: |
| 87 | +- The `aleo-types` change didn't ship — fix the upstream. |
| 88 | +- Your local `node_modules` is stale — `pnpm install` at the monorepo root. |
| 89 | + |
| 90 | +### 7. Verify the baseline compiles |
| 91 | + |
| 92 | +```bash |
| 93 | +cd <monorepo-root> |
| 94 | +pnpm install |
| 95 | +pnpm -w build |
| 96 | +pnpm --filter react-app-example dev |
| 97 | +``` |
| 98 | + |
| 99 | +`pnpm -w build` does **not** build the example (it's excluded via turbo filter in the root `package.json`). To typecheck the example itself: |
| 100 | + |
| 101 | +```bash |
| 102 | +pnpm --filter react-app-example build |
| 103 | +# or just start the dev server — vite typechecks the module graph on demand |
| 104 | +``` |
| 105 | + |
| 106 | +### 8. Test against a real wallet extension |
| 107 | + |
| 108 | +Preconditions — ask before prescribing: |
| 109 | + |
| 110 | +- **Q1 — Is the matching extension build loaded in Chrome?** For Shield that's `~/dev/shield-extension` on a branch whose commits implement the feature end-to-end. If yes, skip to Q2. If unsure, rebuild: `cd ~/dev/shield-extension && yarn install && yarn build`, then click **↻ Reload** on the Shield card in `chrome://extensions` (or **Load unpacked** on `dist/chrome-mv3` if it wasn't loaded at all). |
| 111 | +- **Q2 — Is there a Testnet account in the wallet?** If yes, skip. If no, create/import from the extension popup, select **Testnet** in the network picker, copy the address. |
| 112 | +- *(Very optional — skip unless the account is genuinely empty)* fund the address from a faucet. |
| 113 | + |
| 114 | +### Authorize every program the demo calls — before connecting |
| 115 | + |
| 116 | +Shield (and similar permissioned wallets) binds an allowed-programs list to the connection at connect time via the adapter's `connect(network, decryptPermission, programs)` argument. Any `executeTransaction` for a program not in that list is rejected at the extension boundary with an error like: |
| 117 | + |
| 118 | +> `<program>.aleo is not in the allowed programs, request it when connect` |
| 119 | +
|
| 120 | +The example app manages this list through the **Programs** button in the header (top-right, with a count badge). It's persisted in the `programsAtom` jotai atom (`examples/react-app/src/lib/store/global.ts`) and read at the `AleoWalletProvider` level in `src/App.tsx`. |
| 121 | + |
| 122 | +Before connecting the wallet on the dev server: |
| 123 | + |
| 124 | +1. Click the **Programs** button in the header. |
| 125 | +2. Add every program your new demo component calls. For the dynamic-dispatch example that's `token_router.aleo`, `toka_token.aleo`, and `tokb_token.aleo` — include both prep-button targets (mint) and the main-action targets (dispatch). Anything you omit will blow up at call time with the error above. |
| 126 | +3. If you're already connected when you add a program, you must **disconnect and reconnect** — the allowed list is negotiated only at connect time, not per call. |
| 127 | + |
| 128 | +As a rule of thumb: if your new component exercises a program outside `credits.aleo` / `hello_world.aleo` (the defaults), you must update this list before a manual test run. Bake the list into your worked-example docs. |
| 129 | + |
| 130 | +Then: |
| 131 | + |
| 132 | +1. Run the dev server. |
| 133 | +2. **Connect Wallet** → pick the wallet under test → approve in the extension popup (the popup will ask you to authorize each program in the list). |
| 134 | +3. Click the prep buttons (if any) and watch the status. These test the *base* adapter path without the new feature — confirming you haven't broken the default. |
| 135 | +4. Click the main action that exercises the new surface. |
| 136 | +5. Read the response in the status Alert. Cross-reference with the extension's popup (did it show the new feature? did the preview match?) and with the testnet explorer page for the returned `transactionId`. |
| 137 | + |
| 138 | +### 9. Success criteria |
| 139 | + |
| 140 | +Plumbing success is the first and sometimes only signal: the wallet returns a `transactionId`. That alone proves the new surface made it across the dapp → adapter → extension → SDK boundary. |
| 141 | + |
| 142 | +Feature-specific success will usually show up in one of: |
| 143 | +- **The extension popup's preview pane** — e.g. a new list of resolved imports, a new permission prompt, a new metadata field. |
| 144 | +- **The explorer transaction page** — e.g. a nested transition from a dynamically-dispatched program, a new transition type, updated mapping values. |
| 145 | +- **The wallet's local UI** — e.g. a balance change, a record appearing in the user's list. |
| 146 | + |
| 147 | +Write down which of these is the real signal for the change you're testing and look at it explicitly; "the tx went through" is not enough if the feature is supposed to change *how* it went through. |
| 148 | + |
| 149 | +### 10. Interpret partial success honestly |
| 150 | + |
| 151 | +A returned `transactionId` with a `Rejected` finalize isn't necessarily a feature bug — it may be the program's finalize asserting something about testnet state. Before filing a bug: |
| 152 | + |
| 153 | +- Reproduce with a *base-case* `executeTransaction` (without the new field) against the same function. Does it fail in the same way? If yes, the issue is state, not the feature. |
| 154 | +- Check the explorer's "finalize error" string. `balance insufficient`, `allowance not found`, `record spent` all point at state. |
| 155 | +- Only file a bug when the new surface is the difference between success and failure. |
| 156 | + |
| 157 | +## Worked example reference |
| 158 | + |
| 159 | +The first feature tested with this skill was the `imports?: string[]` field added to `TransactionOptions` to support `call.dynamic` dapps. The resulting artifacts are: |
| 160 | + |
| 161 | +- `examples/react-app/src/components/functions/DynamicDispatch.tsx` |
| 162 | +- `examples/react-app/src/pages/DynamicDispatchPage.tsx` |
| 163 | +- `examples/react-app/src/lib/programIdField.ts` |
| 164 | +- route `/dynamic-dispatch`, sidebar entry in the Transactions group |
| 165 | +- `codeExamples.dynamicDispatch` snippet |
| 166 | + |
| 167 | +Read those files for a concrete shape: two prep buttons (mint on either of two token programs), a tab selector for the target program, the main dispatch action, a shared status Alert, and a live `CodePanel` mirroring form state. That's the template you're aiming for. |
| 168 | + |
| 169 | +## Common failure modes (look here first) |
| 170 | + |
| 171 | +- **`<program>.aleo is not in the allowed programs, request it when connect`** → the program isn't in the dapp's authorized list. Open the **Programs** dropdown in the header, add it, then disconnect + reconnect (the allowed list is bound at connect time, not per call). |
| 172 | +- **Types reject the new field** → upstream `aleo-types` change isn't on the current branch or isn't rebuilt. `pnpm install` at monorepo root. |
| 173 | +- **Extension popup rejects at messaging-validation** → extension build predates the plumbing commit. `yarn build` and reload. |
| 174 | +- **Extension shows the call but no `transactionId` returns** → offscreen worker error. Extension DevTools → `offscreen.html` console. |
| 175 | +- **`transactionId` returns but explorer shows no trace of the new feature** → wallet-adapter or extension didn't actually forward the field. Grep the adapter for the field name; grep the extension's messaging validator and service layer for the same. |
| 176 | +- **`programIdToField` (or equivalent helper) throws on a placeholder** → someone shipped a literal-lookup helper with a TODO. Compute the value via snarkvm / `snarkos developer` / the extension's offscreen-worker SDK console, paste it in. |
| 177 | + |
| 178 | +## Stopping rule |
| 179 | + |
| 180 | +You're done when: |
| 181 | + |
| 182 | +1. The new component compiles without `as any`. |
| 183 | +2. Clicking the main action against the real extension returns a `transactionId`. |
| 184 | +3. The explorer (or whatever the designated success signal is) shows the feature-specific evidence. |
| 185 | +4. You can describe in one sentence, without waffling, what would be the symptom if the feature regressed. |
| 186 | + |
| 187 | +If you can't do (4), the component doesn't yet exercise the feature tightly enough — go back to step 1. |
0 commit comments