// TINL_MOBILE_UAT_GUIDE
Mobile UAT — Android in-wallet path
Separate track from the Chrome / portal UAT Guide. Use this when you have the TINL demo wallet on an Android emulator or device. Part 1 explains the path; Part 2 is the test.
This guide is only for the TINL Android demo wallet (in-wallet path). It is not the Chrome extension UAT and not the portal Wallet Demo 25-step guide. For MetaMask / Phantom in the browser, use the portal tab “UAT Guide” (Chrome / portal) instead. This build evaluates via Mode B (local TINL service over REST). On-device Mode A is not wired yet. Use the app’s built-in scenario buttons — live WalletConnect relay pairing is stubbed in v0.
Download PDF saves TINL-Mobile-UAT-Guide-Android.pdf — mobile steps only. Generated from this same tab so the printout cannot disagree with the portal.
Part 1 — How the mobile track is laid out, and why
Sign path: dApp → WalletConnect → demo wallet → TINL evaluateSessionRequest → in-app decision sheet → only then optional native sign. No window.ethereum inject.
Kept as its own tab so Chrome/portal checks and Android checks never share a step list. Mixing them would force MetaMask/Phantom steps on a phone path that does not use those extensions.
Profile is the policy variable (Retail — Strict default). Scenario cards are real session_request fixtures through evaluateSessionRequest — not mocked decisions. UI is Deep Signal (dark navy, teal), not stock Expo.
Accepts a wc: URI and documents the seam, but live relay pairing needs WalletKit + RN crypto polyfills (out of scope for this build). Security property under test does not depend on pairing — scenarios exercise the same evaluate() path.
BLOCK / WARN / NORMALIZE / ALLOW UI lives inside the wallet process. A hostile website cannot skip or forge this sheet via page JavaScript. BLOCK shows Dismiss only — no proceed affordance.
NORMALIZE is auto-applied by @a3e9/tinl-walletconnect (headless package). This build does not yet offer the portal’s interactive amount picker. The sheet shows the rewritten params the app would sign.
Before you begin
- Android emulator on Linux/WSL (or a physical Android device). Android-first; iPhone via Expo EAS is optional later.
- TINL service running for Mode B: cargo run -p a3e9-tinl (default A3E9_TINL_LISTEN=0.0.0.0:8090 — same port as extension Mode B).
- Demo app from a3e9-tinl apps/mobile: ./scripts/android-demo.sh (Linux+KVM) or ./scripts/android-demo-wsl-windows.sh (WSL+Windows AVD). Emulator API base must be http://10.0.2.2:8090/api/v1 (includes /api/v1; port 8090 not 8080).
- Your evaluator token, used only to open this portal guide — the mobile app does not import seed phrases from this website.
- You never enter a private key or seed into any website for this test. If any web screen asks for one, stop and report it.
Part 2 — The test, steps 1–19
Work top to bottom. Steps marked RELEASE BLOCKER stop the mobile demo path if they fail. Optional Solana / outage steps may be marked skipped when the build does not expose them yet.
Confirms you are evaluating the Android in-wallet path, not the browser extension. Wrong track = wrong expectations on WARN/NORMALIZE enforcement.
| # | Do this | Input | Expected output |
|---|---|---|---|
| 1 | Open the TINL evaluator and sign in. | https://a3e9.com/tinl/evaluator — paste the evaluator token you were issued. | Portal loads at /tinl/evaluator/portal. Tabs include Wallet Demo, Price Impact, UAT Guide (Chrome / portal), and Mobile UAT Guide. Open Mobile UAT Guide for this track. |
| 2 | Install or launch the TINL mobile demo on Android. | From monorepo: ./scripts/android-demo.sh (or npm run android with service on :8090). | App opens with Deep Signal styling (dark background, teal accents). Home shows profile selector defaulting to Retail — Strict. Honesty banner states Mode B (REST), not on-device Mode A. No seed phrase request from a3e9.com. |
| 3 | Confirm you are not using the Chrome UAT Guide for this run. | None — scope check. | You are not following MetaMask/Phantom connect steps. Those belong only to the Chrome / portal UAT Guide tab. |
Before testing what TINL stops, confirm ordinary activity still reaches a normal confirm path. A tool that blocks everything is not a control.
| # | Do this | Input | Expected output |
|---|---|---|---|
| 4 | Set profile to Retail — Strict. Tap scenario “Transfer WETH to another account”. | Profile: Retail — Strict. Scenario button on Home (canned session_request). | TINL decision is ALLOW, rule ERC20_TRANSFER (or equivalent transfer rule). Sheet offers Proceed / Cancel. You may dismiss — no need to “sign” for real. |
| 5 | Note where the TINL decision appeared. | None — visual check. | Any TINL UI appeared inside the mobile app, not as a browser extension popup and not as a page overlay on a dApp site. |
Same policy intent as the Chrome guide’s unlimited-approve sequence, but enforcement is stronger on this path: consent and BLOCK live in the wallet process. Step 8 proves the rewritten payload is not unlimited; step 9 proves BLOCK never offers a proceed path.
| # | Do this | Input | Expected output |
|---|---|---|---|
| 6 | Tap scenario “Approve UNLIMITED WETH spend”. | Profile: Retail — Strict. | In-app TINL sheet opens. Decision: NORMALIZE. Rule: ERC20_UNLIMITED_APPROVE. Action from evaluateSessionRequest is proceed-normalized. |
| 7 | Read the NORMALIZE sheet (no amount picker in this build). | None — read-only. | Sheet shows a NORMALIZED PAYLOAD panel (JSON of rewritten params). There is no portal-style “Only this much / Nothing / Unlimited” chooser yet — auto-normalize by the WC package is intentional. Button label: “Sign the normalized request”. |
| 8 | Confirm the normalized payload is not the original unlimited approve. Release blocker | None — inspect the payload panel. | The data field is not the original max-uint256 approve (engine rewrote the amount). Cancel is available. Primary button only signs the normalized params — never the original unlimited request. |
| 9 | Tap scenario “setApprovalForAll on an NFT collection”. Release blocker | Profile: Retail — Strict. | TINL shows BLOCK, rule ERC721_SET_APPROVAL_ALL. Only a Dismiss control — no Proceed. No raw private-key / seed sign sheet opens. |
On Chrome, WARN consent can be advisory against a hostile page (documented extension limit). On this mobile path, WARN is rendered by the app’s own UI; the page cannot postMessage a forge. That honesty difference is why this guide is separate.
| # | Do this | Input | Expected output |
|---|---|---|---|
| 10 | Tap scenario “Call an unrecognized function”. | Profile: Retail — Strict. | In-app WARN sheet. Rule SELECTOR_UNKNOWN. Proceed and Cancel are controlled by the app. |
| 11 | Tap Cancel on the WARN sheet. | None. | Sheet closes. No signature is produced. |
| 12 | Re-read the mobile scope note / in-app honesty banner. | None — visual check. | Claims stay scoped to users of this in-wallet path. It does not claim that installing the Chrome extension alone gives the same guarantees. Mode B is disclosed. |
Proves policy is profile-driven. Solana scenario buttons are not in the EVM-first demo vector set yet — mark skipped.
| # | Do this | Input | Expected output |
|---|---|---|---|
| 13 | Switch profile to Active DeFi User — Balanced and re-run a scenario that was BLOCK or NORMALIZE under Retail — Strict. | Same scenario button, different profile. | Decision can change with profile. Same input, different posture — profile is the policy. |
| 14 | (Optional) Solana unlimited SPL approve. | Not available as a Home scenario in this build. | Record “skipped — EVM-only demo vectors”. Engine and @a3e9/tinl-walletconnect already support Solana when a live payload is supplied. |
| 15 | (Optional) Benign SOL transfer → ALLOW. | Not available as a Home scenario in this build. | Record skipped — same reason as step 14. |
Mode A native binding over core/ is the next milestone. This demo is Mode B only and says so in the UI.
| # | Do this | Input | Expected output |
|---|---|---|---|
| 16 | Look for offline / Mode A / outage simulation controls. | None in current apps/mobile. | Record “N/A — Mode B only”. Stopping the local service should make scenarios fail with a clear evaluation error, not silent ALLOW. |
| 17 | Confirm native mode is not silently selected. | None. | App does not offer a fake on-device mode. nativeEvaluatorUnavailable() throws if forced — no silent REST fallback while claiming Mode A. |
Honesty fence for App Store / Play / NIST-facing notes. Closing Chrome races for users of this wallet does not certify every phone wallet on earth.
| # | Do this | Input | Expected output |
|---|---|---|---|
| 18 | Confirm residual non-claims. | None — read-only. | This run does not prove: Chrome extension MAIN-world consent is fixed; MetaMask Mobile native hooks without WalletConnect; iOS Simulator without a Mac; Play/App Store listing already live; HSM a3e9-attestation running on the phone; on-device offline evaluation. |
| 19 | Record build identity for the report. | git rev-parse --short HEAD from a3e9-tinl (and branch name). | You can name exactly which monorepo SHA was evaluated. A report without a build id is not reproducible. |
Record the result
| Date / evaluator | |
| Android emulator or device + OS version | |
| Mobile app monorepo git SHA / branch | |
| Step 8 — normalized payload not unlimited (RELEASE BLOCKER) | |
| Step 9 — BLOCK never offered proceed (RELEASE BLOCKER) | |
| Steps 14–15 — Solana path (or marked skipped — expected for this build) | |
| Were you ever asked for a private key or seed on a website? | |
| Did you use this guide (Mobile) rather than Chrome UAT by mistake? |
Any failure at step 8 or step 9 is a release blocker for the mobile demo path. Do not mark Chrome extension UAT complete based on this guide, or vice versa — the tracks stay separate. Keep this module aligned with a3e9-tinl/doc/TINL_MOBILE_UAT_CHECKLIST.md and apps/mobile scenario labels; re-issue the PDF from the portal after content changes.