1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
|
<!--
Author: cirakg & claude ai
Summary: How to install and use OTP Wallet: the Store beta, adding and importing entries, the PIN, daily use, the glance, moving watches, troubleshooting and known limitations.
-->
# OTP Wallet user guide
OTP Wallet keeps one-time passwords (TOTP, HOTP and Steam Guard) and barcodes (loyalty cards, tickets, QR codes) on your Garmin watch, encrypted. Nothing leaves the watch: there is no account, no cloud and no network access. You add entries from the Garmin Connect app on your phone; the watch is the only place they are stored.
## Contents
- [Install](#install)
- [Where the settings are](#where-the-settings-are)
- [Add an authenticator code](#add-an-authenticator-code)
- [Import from Aegis or otpauth links](#import-from-aegis-or-otpauth-links)
- [Add a barcode or QR code](#add-a-barcode-or-qr-code)
- [Change, move or delete an entry](#change-move-or-delete-an-entry)
- [The PIN and locked entries](#the-pin-and-locked-entries)
- [Daily use on the watch](#daily-use-on-the-watch)
- [The glance](#the-glance)
- [Options](#options)
- [Moving to a new watch, and backups](#moving-to-a-new-watch-and-backups)
- [Troubleshooting](#troubleshooting)
- [Known limitations](#known-limitations)
## Install
Garmin's app settings only work for apps installed from the Connect IQ Store, so OTP Wallet is installed as a private beta that only your Garmin account can see:
1. The developer uploads the signed package (`make package` builds `build/package/otp-wallet-<version>.iq`) on Garmin's developer site as a beta app.
2. On your phone, open the Connect IQ Store app, find the beta under your apps, and install it on your watch.
3. Each new version is a new upload; the Store updates the watch, and your entries stay (check this once with the [real-watch checklist](TESTING.md#real-watch-checklist)).
**From the beta to the Store app (1.0.0):** the public Store app has a new application ID, so the watch treats it as a different app from the private beta (0.9.0 and earlier). Nothing carries over: install the Store app, import your entries again from your authenticator, set a PIN, check the codes, then delete the beta from the watch.
A sideloaded build (copied over USB) runs, but gets no phone settings, so you can't add entries to it. Sideloaded debug builds are for testing with fake demo entries only.
## Where the settings are
In the Garmin Connect app: your watch, then **Connect IQ apps**, then **OTP Wallet**, then **Settings**. The settings are a set of forms. Each form has a switch at the bottom ("Add this entry", "Import now", "Apply"); the watch acts on a form only when its switch is on, and only when it receives the settings:
- If OTP Wallet is open on the watch, it applies the forms within seconds of saving.
- Otherwise it applies them the next time you open it.
Each form has a **Result** field the watch writes back, and the watch shows the same lines on its **Settings results** screen (also in its menu). After a form is applied the watch turns its switch off and clears it, the secret field first. After a problem only the secret field is cleared, so you can correct the rest and send it again. Sending exactly the same form twice does nothing the second time.
Close and reopen the settings page on the phone to see the cleared fields and the Result text. Garmin Connect on Android shows them after reopening ([SETTINGS_SPIKE.md](SETTINGS_SPIKE.md)); changes reach an open app in about a second.
## Add an authenticator code
Use the **Add OTP** form with the details the website shows when you set up two-factor authentication (choose "can't scan the code?" or "enter the key manually" to see them):
| Field | What to enter |
| --- | --- |
| Type | TOTP (almost every site), HOTP (counter based) or Steam |
| Issuer (service) and Account | For example "GitHub" and your user name. At least one of them |
| Secret | The key in Base32 (letters A–Z and digits 2–7). Spaces, hyphens and lower case are fine |
| Algorithm, Digits, Period | Leave SHA1, 6 and 30 unless the site says otherwise |
| Counter | HOTP only: the starting counter, usually 0 |
| Group | Optional, shown in the list |
| Require PIN to display | The code is only shown after you enter the app PIN |
Turn on **Add this entry** and save. The result says, for example, "Added K7F2 GitHub (alice)", or exactly what to fix ("Secret: '1' is not a Base32 character (position 5)"). Check that the watch shows the same code as the website or your phone authenticator before you rely on it.
## Import from Aegis or otpauth links
The **Import** form takes the text of an **unencrypted Aegis export** (Aegis: Settings, Import & Export, Export, plain JSON) or any number of `otpauth://` links:
1. Paste the text into **Text, part 1**. If it is longer than one field takes, continue in the next part; the watch joins parts 1 to 8 in order.
2. Choose the mode: **Add to my entries** (default), or **Replace imported entries** (removes entries that came from earlier imports first; entries you added by hand are never touched).
3. Turn on **Require PIN for these entries** if they should all be locked.
4. Turn on **Import now** and save.
The report lists how many entries were imported and every one that wasn't, with the reason: duplicates of entries you already have, unsupported types (mOTP, Yandex), or bad values. Aegis favourites stay favourites (see [the glance](#the-glance)); icons and notes are not imported.
**Long exports.** If the text is still incomplete after joining the fields, the watch keeps what it got (encrypted) and says "Waiting for more: received ... characters". Paste the next part into the fields, turn **Import now** on again, and save. The waiting part expires after 24 hours and is limited to 32 KB; choose the mode **Cancel waiting import** to discard it. A compact export takes about 190 characters per entry; remove icons from the export if it is much larger.
**Afterwards**, delete the exported JSON file from your phone, and keep such files out of cloud-synced folders. The import fields are cleared on the phone by the watch.
**iPhone:** if Garmin Connect refuses the `&` in a link, write `;` instead; the watch accepts both.
Google Authenticator's export links (`otpauth-migration://`) are not supported. Import them into Aegis first and export from there.
## Add a barcode or QR code
Use the **Add code** form:
| Field | What to enter |
| --- | --- |
| Card name | For example "Gym card" |
| Barcode type | QR, Code 128, Code 39, EAN-13, EAN-8, UPC-A, UPC-E, ITF, Codabar, Data Matrix, PDF417 or Aztec |
| Contents | The text or digits the code holds (as printed under the code, or what a scanner app reads from it) |
| Error correction | 2D codes: Medium is right for most uses |
| Check digit | EAN, UPC and ITF: "Add or verify" checks the last digit; "Fix if wrong" corrects it |
| Show the text under the bars | The digits under linear codes |
| Group, Require PIN to display | As for OTP entries |
The watch encodes the code to check it, and warns if it won't fit your screen at a scannable size. 2D codes are drawn the first time you open them ("Encoding… 40%"); after that they open at once.
## Change, move or delete an entry
Use the **Manage entry** form with the entry's **ID**: four characters such as `K7F2`, shown at the bottom of each entry on the watch, under **Entry IDs** in the watch menu, and in the **About** group of the settings (the catalog). Case doesn't matter.
| Action | Fields used |
| --- | --- |
| Edit details | New name, account or group; blank keeps a field, `-` clears it |
| Move to position | The new position, 1 = top |
| Require PIN / Don't require PIN | — |
| Replace secret or contents | The new secret (Base32) or code contents |
| Set HOTP counter | The counter |
| Delete | — |
| Mark as favourite (glance) / Remove favourite | — |
**Changes to locked entries wait for you.** Anything that would change or reveal a locked entry (everything except moving it) is not applied when it arrives. The watch shows "Phone changes: N to confirm" at the top of its menu and in the list title. Open it ("Reading…" shows briefly), check the list, and choose **Confirm all** (it asks for the PIN, even if the app is unlocked) or **Reject all**. This way, someone with access to your phone alone can't unlock or rewrite your locked entries. A replacement secret or code contents is checked when it arrives, so a typo is reported in the Result at once. **Require PIN** sent while the watch is locked needs no confirming: it applies the next time you enter the PIN, and the list then says "Results ready (menu)".
## The PIN and locked entries
- **Set PIN** in the watch menu creates it (exactly 4 digits; the 4th digit enters it, there is no OK key). A PIN set with version 0.8.0 or earlier still works once; the watch then asks you for a new 4-digit PIN. You are also asked for one the first time an entry needs it.
- Entering the PIN unlocks all locked entries until the app locks again: when you leave the app, after the auto-lock time (option), or with **Lock now** in the menu.
- **Wrong PINs:** after 5 wrong PINs the watch makes you wait, longer with each further mistake. With the option **Erase locked entries after 10 wrong PINs**, the tenth wrong PIN erases the locked entries (unlocked entries stay).
- **Change PIN** keeps your entries. **Reset PIN**, for a forgotten PIN, erases the locked entries and lets you set a new PIN; there is no other way back into them.
- Locked entries are encrypted with a key that only your PIN opens. A 4-digit PIN stops someone using your watch (wrong PINs make it wait, and it can erase the locked entries after 10), but it can be guessed in seconds by someone who copies the watch's storage to a computer. Keep that in mind for very sensitive accounts.
## Daily use on the watch
- **List:** your entries in your order. Unlocked TOTP and HOTP entries show the current code and a small countdown; locked entries show a lock; barcodes show an icon.
- **Open an entry** (tap it, or START): the code in large digits with a countdown ring; in the last 5 seconds the next code is shown too. For HOTP, tap the code (or START) for the next one.
- **Barcodes** open black on white, at the largest size that fits. They are shown at full brightness for about a minute on AMOLED watches (the screen's own limit; tap to brighten it again), and with the backlight on MIP watches. You can turn this off under Options.
- **Menu** (hold MENU, or long-press the list on touch-only watches): Phone changes, Lock now, Set/Change/Reset PIN, Settings results, Entry IDs, Clock status, About (version, storage, self-test result).
- **BACK** on the list leaves the app and locks everything.
## The glance
In the watch's glance loop, OTP Wallet shows the number of entries. With the option **Show the favourite code in the glance** on, it shows instead the current code of your first **favourite** TOTP or Steam entry that doesn't require the PIN, with a bar for the seconds left:
- Mark an entry as favourite with Manage entry, "Mark as favourite (glance)" (Aegis favourites already are).
- Locked entries and HOTP entries never appear in the glance.
- The glance updates the code once you've opened the app after changing your favourites; turning the option off hides the code at once.
- Anyone who can see your glance loop sees that code, so choose an entry for which that's acceptable ([SECURITY.md](SECURITY.md#glance-code)).
## Options
| Option | Default | Meaning |
| --- | --- | --- |
| Lock after | 1 min | How long locked entries stay open without input: immediately, 15 s, 30 s, 1, 2 or 5 minutes |
| Erase locked entries after 10 wrong PINs | Off | See [the PIN](#the-pin-and-locked-entries) |
| Time offset in seconds | 0 | Seconds added to the watch clock if your codes are consistently early or late (−120 to 120) |
| Code size | Large | Size of the digits on the detail screen |
| Theme | Dark | Dark or light screens (barcodes are always black on white) |
| Show groups | On | The group in front of the account in the list |
| Encrypt names on the watch | Off | Keeps names and accounts in the encrypted part too; locked entries then show only "Locked entry" and the About catalog only IDs |
| Show the favourite code in the glance | Off | See [the glance](#the-glance) |
| Full brightness while showing a barcode | On | See [daily use](#daily-use-on-the-watch) |
## Moving to a new watch, and backups
OTP Wallet never exports your secrets: they only flow into the watch. Your backup is your authenticator app or Aegis vault (and the recovery codes the websites gave you). To move to a new watch, install OTP Wallet there and import or add your entries again, then delete the app from the old watch (uninstalling removes all its data).
## Troubleshooting
| Problem | What to do |
| --- | --- |
| Codes are rejected | Open **Clock status** in the menu. The watch clock must be right to within a few seconds; sync the watch (GPS activity or phone sync). If it is always off by the same amount, set **Time offset** |
| Nothing happens after saving a form | Open OTP Wallet on the watch; forms are applied when the app runs. Check that the form's switch was on |
| "Already added" or "Already imported" | The watch has already applied exactly that form; change something to send a new one |
| "Waiting for more" after an import | The JSON was incomplete: paste the rest and send again, or choose the mode "Cancel waiting import" |
| "Damaged entry K7F2" | That entry's stored data can't be read. Delete it with Manage entry and add it again |
| A barcode says "Too large for this screen" | The contents are too long for a scannable code on this watch. Use a shorter text, or a 2D symbology for long text |
| A barcode doesn't scan | Turn brightness up (the option is on by default), hold the watch steady and flat, and try both closer and further away. Laser scanners at tills often can't read screens |
| "Data from a newer version" | The app was downgraded; install the newest version again |
| "Crypto self-test failed" | The watch's crypto didn't give the expected results; codes are hidden to be safe. Update the watch firmware and report it |
## Known limitations
- **Connect IQ limits**
- No unlocking with the watch's own passcode; the app has its own PIN.
- App settings only work with the Store version, which is why the app is a private beta rather than sideloaded.
- The phone shows what the watch writes back (Result fields, cleared forms, the About catalog) only after you reopen the settings.
- Garmin Connect refuses to save text that contains a backslash `\` ([SETTINGS_SPIKE.md](SETTINGS_SPIKE.md#follow-up-for-test-c)). `otpauth://` links never contain one. Aegis on Android writes every `/` as `\/`, which happens in entry icons and in names with a `/`. If the phone refuses to save an Aegis export, replace every `\/` with `/` in a text editor (the file stays valid), or remove the icons in Aegis and export again, or use `otpauth://` links. A name with a `"` in it can only come over as an `otpauth://` link.
- **Import**
- Encrypted Aegis exports can't be decrypted on a watch (it would need 32 MiB of memory); export unencrypted.
- Google Authenticator export links aren't supported; mOTP and Yandex entries are skipped.
- Icons and notes aren't imported.
- Pasted text is limited to the eight import fields per sync, and a waiting import to 32 KB.
- **PIN**
- The PIN is fixed at 4 digits. It stops someone using your watch, but someone who copies the watch's storage to a computer can try all 10,000 PINs in seconds.
- **Storage**
- At most 150 entries and about 100 KB in total.
- At most 16 phone changes can wait for the PIN at once.
- The About catalog shows up to 800 characters; the rest are under Entry IDs on the watch.
- **Security**
- Locked entries are only as strong as the PIN against someone who copies the watch's storage; unlocked entries rely on the watch keeping its storage private.
- Secrets and the import text pass through Garmin Connect's settings on your phone.
- See [SECURITY.md](SECURITY.md) and [README section 8](../README.md#8-threat-model).
- **Barcodes**
- Data Matrix uses ASCII and Base256 encoding only (no C40, Text, X12 or EDIFACT), and Aztec a simple mode choice. Their symbols are valid, but sometimes larger than another encoder's.
- PDF417 only fits short contents on small round screens (about 170 codewords on a 218-pixel screen).
- Large 2D codes take a while to encode the first time: seconds for typical cards, up to about a minute for the largest QR codes on the Forerunner 255 and 255S. After that they open from the cache.
- **Glance:** only the first favourite TOTP or Steam entry without the PIN.
- **No backup or export** from the watch, by design.
|