A Chrome extension wallet for Tapyrus.
- Wallet creation (BIP39 mnemonic generation)
- Wallet restoration (from mnemonic phrase)
- Password-protected encryption
- Tapyrus address display
- Plasmo - Browser extension framework
- React - UI library
- Tailwind CSS - Styling
- tapyrusjs-lib - Tapyrus library
- @noble/secp256k1 - Elliptic curve cryptography
- Tapyrus API / Mainnet (NetworkId: 15215628) — default
- Tapyrus Testnet (NetworkId: 1939510133)
The network is switched at runtime from the settings screen and the choice is persisted. Pending transactions and issued token records are stored per network, so switching never mixes one chain's data into the other.
An install that already holds a wallet the first time this build runs starts on Testnet rather than the default: testnet was the only network the extension could reach before the switch existed, and that is where its data is. The choice is recorded on that first run, so a wallet created later — on the default — is not mistaken for one of those installs.
- Node.js 18+
- pnpm
pnpm installpnpm dev- Open
chrome://extensionsin Chrome - Enable "Developer mode"
- Click "Load unpacked"
- Select the
build/chrome-mv3-devfolder
pnpm buildBuild artifacts will be generated in build/chrome-mv3-prod.
Create a .env.local file to point the testnet entry at a different
explorer. Mainnet is not overridable: a local explorer stands in for the test
chain, never for mainnet.
# Explorer API endpoint (default: https://testnet-explorer.tapyrus.dev.chaintope.com/api)
PLASMO_PUBLIC_EXPLORER_API_URL=http://localhost:3001/api
# Explorer URL for transaction/color links (default: https://testnet-explorer.tapyrus.dev.chaintope.com)
PLASMO_PUBLIC_EXPLORER_URL=http://localhost:4200This is useful for local development with tapyrus-explorer.
Select "Testnet" in the settings screen to use it, and add the local origin to
manifest.host_permissions in package.json — Chrome blocks requests to hosts
the extension has not declared.
Compliant with TIP-0044:
m/44'/1939510133'/0'/0/0
└── NetworkId (Testnet)
The path does not change when the network is switched: the same address is used on every Tapyrus network, so switching does not re-derive the wallet.
Both documents are published from docs/ on GitHub Pages, one file per version.
Publishing a version that changes what a document says asks every user to
respond to it the next time the side panel opens; publishing one that does not
simply replaces the text.
| Location | Holds |
|---|---|
docs/terms/v<version>.html |
The terms of service |
docs/privacy/v<version>.html |
The privacy policy |
docs/terms.html, docs/privacy.html |
A page that sends the version-less URL to the version in effect |
docs/legal.json |
Which version is in effect, what changed, and what is announced |
src/extension/legal.ts |
The versions this build shipped with, and what each document asks of the user |
src/extension/i18n/locales/*.json |
The names of the documents (legal.docs.*.label) |
The version-less URLs are the ones handed out to the outside world, so they have to keep working across a bump. GitHub Pages has no redirect rules, hence the standing page at each of them.
A version is two numbers (2.1), and which one is raised decides what the user sees.
| Raised | The revision | What the user sees |
|---|---|---|
| Major (2.1 → 3.0) | Rights or obligations change; the purpose of use or the sharing of personal data changes; the service rests on different terms | A banner announces it beforehand, and once it takes effect everyone is asked to agree (acknowledge) again |
| Minor (2.0 → 2.1) | Typos, wording, contact details, section numbering — the meaning is untouched | Nothing. The text is simply replaced |
Which one it is, is a question for legal. When in doubt, treat it as major. Asking for consent that was not needed costs some inconvenience; failing to ask for consent that was needed cannot be made good afterwards.
Only the major version decides whether a user is asked again (outdatedDocs()).
A new user is always shown, and recorded against, the text in effect — minor
included.
Superseded versions are linked from nowhere but stay readable to anyone who
knows the URL (.../terms/v1.0.html), so a user can be shown the text they
agreed to at the time. Where one of them points at the other document, it names
the version that stood alongside it (../privacy/v1.0.html) rather than the
version-less URL, which would follow whatever is in effect today and leave the
record showing a pairing that never existed.
A version that has been published is never edited. It is the record of what
somebody agreed to. Correcting a typo means a new minor version; changing what
the document says means a new major one. A file under docs/ that is not the
version in effect is history, whatever it says.
"Agree" and "acknowledge" are not the same thing (kind in
BUNDLED_LEGAL_DOCS).
| Document | Asks for | Why |
|---|---|---|
| Terms of service | Agreement (agree) |
A promise between the user and the company, which takes effect by both sides agreeing |
| Privacy policy | Acknowledgement (acknowledge) |
A notice from the company saying how personal data is handled. It does not take effect by agreement; the Act on the Protection of Personal Information asks for notification or publication |
The wording on screen follows this, and each document gets its own checkbox. A combined checkbox would take agreement to something that is only a notice, and neither consent could then be pointed to on its own.
The terms of service do not link to the privacy policy. Saying in the terms
that personal data is handled "as set out in the separate privacy policy" makes
the privacy policy part of the contract, so agreeing to the terms agrees to the
privacy policy as well — separate checkboxes on screen change nothing if the
text has already tied them together. What has become part of the contract also
falls outside the procedure for revising standard terms of business (Civil Code
article 548-4). The terms say only that personal data is handled under the law
and that the handling is published separately — naming the privacy policy is
fine, linking to it as a term of the agreement is not.
test/legalDocs.test.ts reads the published text and holds the version in
effect (and any announced version) to this. Superseded versions are left as they
are, under the rule above, and so is version 1.0 of the terms: it was published
with the link in it and cannot be edited now, so version 2.0 is what removes
it.
Where the user is asked. The welcome screen carries the checkboxes, and
neither creating nor restoring a wallet is reachable without them. A user who
already has a wallet meets ConsentUpdateScreen before the unlock screen, which
lists what changed alongside a link to the text. There is no way past it: the
wallet cannot be unlocked until the user has responded.
Checkboxes start unticked and are ticked by the user. Nothing forces the text to be scrolled to the end first — ticking each document separately says more, as a record, than proof that a page was scrolled.
Where it is recorded. On the device, in the extension's own storage
(consentStore). There is no server and nothing about the user leaves the
device, so this is the whole record: clearing the extension's data clears it,
and the user is asked again. Only the documents the screen put to the user are
written — a screen asking about the terms says nothing about the privacy policy,
and recording both would put a version on record that nobody was shown.
An install that predates this record is credited with the versions in force when
the welcome screen first asked (adoptLegacyConsent). Those users did tick both
boxes; there was simply nowhere to keep it. Without that they would be met, on
update, with a screen announcing a revision that has not happened.
Announce first, then switch. The terms of service undertake to give notice
of the content of a change and of when it takes effect, by the time it takes
effect (section 7). The banner (LegalUpdateNotice) is that notice, so it
carries what changes, when, and a link to the new text. The user can dismiss it;
the dismissed version is remembered, so the same banner does not come back,
while the next announcement does.
The banner stays off the two screens that carry consent checkboxes themselves
(the welcome screen and ConsentUpdateScreen). There the version being ticked
now and the version announced for later would sit side by side with nothing to
say which is which, and a user who has yet to agree to anything is owed no
notice of a change to it.
A new version takes effect when the manifest says so, not when the date on the device passes. Leaving the switch to the device clock would flip a user early or late depending on whose clock is off; the date says when the document changes, not when one browser notices.
Revising a document does not need a release. docs/legal.json names the
version of each document that is in effect, and the extension reads it at
startup. Publishing to GitHub Pages is the whole of it — a Web Store release
would otherwise put a review and Chrome's own update schedule between the
decision and the user.
{
"docs": {
"terms": {
"version": "2.0",
"changes": { "ja": ["…"], "en": ["…"] },
"upcoming": {
"version": "3.0",
"effectiveFrom": "2026-10-01",
"changes": { "ja": ["…"], "en": ["…"] }
}
}
}
}The change lists travel with the version rather than sitting in the bundled translations, because the text is written when the revision is. They go straight to the user, so write them in the user's words. A language the manifest does not carry falls back to English, and then to no summary at all — the link to the text itself is always there.
Raising a major version
- Put the new text at
docs/<doc>/v<new version>.html. Leave the old file. - Announce it: add
upcoming(version,effectiveFrom,changes) to that document indocs/legal.json. Leaveversionalone. Deploy. The banner appears; the old version is still in effect and nobody is asked to respond. - On the day it takes effect, move
upcomingintoversionandchanges, dropupcoming, and pointdocs/<doc>.htmlat the new file so the version-less URL follows. - Run
pnpm testand deploy. Users are asked to respond from their next launch.
Raising a minor version
- Put the new text at
docs/<doc>/v<new version>.html. Leave the old file. - Raise the version in
docs/legal.json, and pointdocs/<doc>.htmlat the new file. - Run
pnpm testand deploy. Nothing appears on screen and no existing user is asked to respond.
pnpm test checks the published files against the manifest: that the version in
effect and any announced version exist, that file names match their versions,
that the version-less page points at the version in effect, that a document
links to the paired version of the other one, and that the terms do not link to
the privacy policy.
What the build ships with is the floor. BUNDLED_LEGAL_DOCS in
src/extension/legal.ts holds the versions that were in effect when the
extension was built, and the manifest can only move a document forward from
there. A manifest that is stale, rolled back or tampered with cannot undo a
consent the build already knows to ask for, and a fresh install that has never
reached the network still holds to something. Bring these values up to the
manifest whenever a release happens — pnpm test fails if the manifest has
fallen behind them.
Nothing about the gate depends on the network. The manifest is fetched
beside the screen rather than in front of it: the version in effect for this
launch is the one last read (legalManifestStore), and today's copy is stored
for the next one. A refusal, a timeout, a body that is not the manifest — each
leaves what was already there. What a document says can never be a reason a
wallet will not open, so a revision reaches a user one launch after their
browser first sees it.
The manifest also cannot say just anything. Its versions and dates have to be
readable as such, a change list is capped in length and in the length of each
line, and kind — what a document asks of the user — is never taken from it at
all: that follows from what the document is in law.
The mobile app (chaintope/tapylet-for-mobile) has its own terms of service and
privacy policy, separate from the ones above. Its repository is private and so
cannot have GitHub Pages, while the App Store and Google Play listings need a
public URL, so the documents are published from here, under docs/app/
alongside min-version.json.
| Location | Holds |
|---|---|
docs/app/terms/v<version>.html |
The terms of service |
docs/app/privacy/v<version>.html |
The privacy policy |
docs/app/terms.html, docs/app/privacy.html |
A page that sends the version-less URL to the version in effect |
The app links to the version-less URLs and does not record which version a user agreed to, so there is no manifest for these documents: the version in effect is whichever file the version-less page points at. A revision reaches every user as soon as it is deployed, and nobody is asked to respond to it. Publishing one is:
- Put the new text at
docs/app/<doc>/v<new version>.html. Leave the old file. - Point
docs/app/<doc>.htmlat the new file. - Run
pnpm testand deploy.
The rules the extension's documents follow hold here too: a published version is
never edited, and superseded versions stay readable at their own URL.
test/mobileLegalDocs.test.ts checks that file names match their versions, that
the version-less page points at a file that exists, and that the terms do not
link to the privacy policy.
Swapping the file is not itself notice. Section 8 of the mobile terms undertakes to announce a revision, and its effective date, before it takes effect — and to ask for consent where the law requires it. The app has nothing to announce it with: no manifest, no banner, no record of what was agreed to. A revision that changes rights or obligations therefore has to be carried by something else — an announcement on the company site ahead of the date, or a release of the app that asks again on the screen. Deploying here is the last step of that, not the whole of it. Wording and typos need none of this.
MIT