207 lines
6.2 KiB
Markdown
207 lines
6.2 KiB
Markdown
|
|
# Contractless Browser Wallet
|
||
|
|
|
||
|
|
This is the WebExtension application for the Contractless Web3 wallet. It uses
|
||
|
|
the repository's `core` package for wallet generation, encryption,
|
||
|
|
transaction inspection, signing, message proofs, and wallet registration.
|
||
|
|
|
||
|
|
The private key is encrypted in extension-local storage. Decrypted key
|
||
|
|
material exists only in the background service worker while the wallet is
|
||
|
|
unlocked and is cleared when the wallet locks or the worker is suspended.
|
||
|
|
|
||
|
|
## Requirements
|
||
|
|
|
||
|
|
Install:
|
||
|
|
|
||
|
|
- Rust and Cargo
|
||
|
|
- `wasm-pack`
|
||
|
|
- Node.js 20 or later
|
||
|
|
- pnpm
|
||
|
|
|
||
|
|
Install the JavaScript dependencies from this directory:
|
||
|
|
|
||
|
|
```text
|
||
|
|
cargo install wasm-pack
|
||
|
|
pnpm install
|
||
|
|
```
|
||
|
|
|
||
|
|
## Build
|
||
|
|
|
||
|
|
Build both browser packages:
|
||
|
|
|
||
|
|
```text
|
||
|
|
pnpm run build
|
||
|
|
```
|
||
|
|
|
||
|
|
This compiles separate testnet and mainnet versions of the Rust core to
|
||
|
|
WebAssembly and creates:
|
||
|
|
|
||
|
|
```text
|
||
|
|
dist/chrome/
|
||
|
|
dist/firefox/
|
||
|
|
```
|
||
|
|
|
||
|
|
Build only one browser when developing:
|
||
|
|
|
||
|
|
```text
|
||
|
|
pnpm run build:chrome
|
||
|
|
pnpm run build:firefox
|
||
|
|
```
|
||
|
|
|
||
|
|
Chrome and Firefox use the same TypeScript, HTML, CSS, SDK provider, and two
|
||
|
|
network-specific WebAssembly cores. Each output receives its own manifest because Chrome uses a
|
||
|
|
Manifest V3 background service worker while Firefox uses a Manifest V3
|
||
|
|
background event script.
|
||
|
|
|
||
|
|
### Load in Chrome
|
||
|
|
|
||
|
|
Open `chrome://extensions`, enable **Developer mode**, select **Load
|
||
|
|
unpacked**, and choose `dist/chrome`.
|
||
|
|
|
||
|
|
### Load in Firefox
|
||
|
|
|
||
|
|
Open `about:debugging`, select **This Firefox**, select **Load Temporary
|
||
|
|
Add-on**, and choose `dist/firefox/manifest.json`.
|
||
|
|
|
||
|
|
The Firefox build requires Firefox 149 or later. Firefox 149 removed the user
|
||
|
|
gesture restriction from `action.openPopup`, allowing website connection and
|
||
|
|
transaction requests to open the wallet approval interface automatically in
|
||
|
|
the same way as the Chrome build.
|
||
|
|
|
||
|
|
Run Mozilla's validator after building Firefox:
|
||
|
|
|
||
|
|
```text
|
||
|
|
pnpm run lint:firefox
|
||
|
|
```
|
||
|
|
|
||
|
|
Create the Firefox submission archive:
|
||
|
|
|
||
|
|
```text
|
||
|
|
pnpm run package:firefox
|
||
|
|
```
|
||
|
|
|
||
|
|
The archive is written to `web-ext-artifacts`. Firefox requires Mozilla signing
|
||
|
|
before the packaged extension can be installed permanently in standard
|
||
|
|
Firefox releases.
|
||
|
|
|
||
|
|
Safari requires conversion through Apple's Safari Web Extension tooling and
|
||
|
|
Xcode.
|
||
|
|
|
||
|
|
## Networks
|
||
|
|
|
||
|
|
The wallet presents a Testnet/Mainnet selector before wallet creation and
|
||
|
|
unlock. Addresses, active wallets, WebAssembly transaction codecs, API URLs,
|
||
|
|
and website sessions are isolated by the selected network. Switching networks
|
||
|
|
locks the wallet and expires all application access keys.
|
||
|
|
|
||
|
|
Testnet uses `.cltc` addresses and defaults to
|
||
|
|
`https://api.contractless.dev`. Mainnet uses `.clc` addresses and has no API
|
||
|
|
configured by default. A mainnet wallet may still be created, imported, and
|
||
|
|
backed up while that endpoint is empty; network lookups, registration, and
|
||
|
|
transaction broadcasts remain unavailable until a mainnet API is configured.
|
||
|
|
|
||
|
|
## Provider
|
||
|
|
|
||
|
|
Websites receive `window.contractless` with a Promise-based `request` method.
|
||
|
|
Applications should normally use the downloadable client in
|
||
|
|
`../sdk/contractless.js` instead of calling the provider directly:
|
||
|
|
|
||
|
|
```js
|
||
|
|
import { ContractlessWallet } from "./contractless.js";
|
||
|
|
|
||
|
|
const wallet = new ContractlessWallet({
|
||
|
|
appId: "example-application"
|
||
|
|
});
|
||
|
|
|
||
|
|
const connection = await wallet.connect({
|
||
|
|
message: "Sign in to Example Application"
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
Transaction and message requests are routed through the background worker and
|
||
|
|
wallet approval screen. Websites never receive wallet private keys or
|
||
|
|
unrestricted raw-signing access. Single-signer transactions are signed and
|
||
|
|
broadcast by the wallet. The transaction ID and signed bytes are returned only
|
||
|
|
after the configured API accepts the broadcast.
|
||
|
|
|
||
|
|
Supported provider methods are:
|
||
|
|
|
||
|
|
- `contractless_detect`
|
||
|
|
- `contractless_connect`
|
||
|
|
- `contractless_accounts`
|
||
|
|
- `contractless_balances`
|
||
|
|
- `contractless_disconnect`
|
||
|
|
- `contractless_signMessage`
|
||
|
|
- `contractless_sendTransaction`
|
||
|
|
|
||
|
|
Connection, message signing, and transaction submission wait for an explicit
|
||
|
|
approval or rejection. A successful connection creates a random 256-bit
|
||
|
|
access key bound to the browser-derived origin, stable application ID, active
|
||
|
|
wallet, and current wallet unlock session.
|
||
|
|
|
||
|
|
The website stores its copy in browser `sessionStorage`. The extension stores
|
||
|
|
the matching authorization in extension session storage. The key is reusable
|
||
|
|
until the wallet locks, changes active wallets, or the application
|
||
|
|
disconnects. It is not a permanent connected-site permission.
|
||
|
|
|
||
|
|
Every authorized request includes the claimed origin, application ID, and
|
||
|
|
access key. The extension independently derives the real origin from the
|
||
|
|
browser message sender and rejects mismatches.
|
||
|
|
|
||
|
|
See `../sdk/README.md` for complete website integration examples.
|
||
|
|
|
||
|
|
## Wallet registration
|
||
|
|
|
||
|
|
The **Register wallet** button creates the command-38 registration signature
|
||
|
|
and the fixed API handshake proof locally. It sends the address, public key,
|
||
|
|
and signatures to:
|
||
|
|
|
||
|
|
```text
|
||
|
|
POST /api/v1/addresses/register
|
||
|
|
```
|
||
|
|
|
||
|
|
The private key and wallet encryption key never leave the extension. The API
|
||
|
|
requires the authenticated address and public key to match the registration
|
||
|
|
being submitted before forwarding it to a Contractless node.
|
||
|
|
|
||
|
|
The API operator must include the installed extension origin in
|
||
|
|
`API_CORS_ORIGINS`, for example:
|
||
|
|
|
||
|
|
```text
|
||
|
|
API_CORS_ORIGINS=chrome-extension://extension-id
|
||
|
|
```
|
||
|
|
|
||
|
|
Keep any existing allowed website origins in the comma-separated list.
|
||
|
|
Changing the API endpoint in the wallet prompts the user for permission to
|
||
|
|
contact that HTTPS origin.
|
||
|
|
|
||
|
|
Firefox extension pages use a `moz-extension://` origin. The production API
|
||
|
|
must allow the installed Firefox extension to make the same wallet requests as
|
||
|
|
the Chrome build.
|
||
|
|
|
||
|
|
## Firefox Submission Source
|
||
|
|
|
||
|
|
Mozilla reviewers must receive the readable source because Vite bundles the
|
||
|
|
TypeScript and `wasm-pack` generates the WebAssembly package. Submit the built
|
||
|
|
Firefox archive as the add-on and a separate source archive containing at
|
||
|
|
least:
|
||
|
|
|
||
|
|
```text
|
||
|
|
core/
|
||
|
|
extension/src/
|
||
|
|
extension/package.json
|
||
|
|
extension/pnpm-lock.yaml
|
||
|
|
extension/pnpm-workspace.yaml
|
||
|
|
extension/tsconfig.json
|
||
|
|
extension/vite.config.ts
|
||
|
|
README.md
|
||
|
|
```
|
||
|
|
|
||
|
|
Do not include `extension/node_modules`, `extension/dist`, or `core/target` in
|
||
|
|
the source archive. Reviewers can reproduce the Firefox build by entering the
|
||
|
|
`extension` directory and running:
|
||
|
|
|
||
|
|
```text
|
||
|
|
pnpm install --frozen-lockfile
|
||
|
|
pnpm run build:firefox
|
||
|
|
```
|