repository cleanup and scrollbar fix
This commit is contained in:
parent
cd531a2218
commit
f181442c67
|
|
@ -1,4 +1,5 @@
|
|||
core/target/
|
||||
extension/node_modules/
|
||||
extension/dist/
|
||||
extension/web-ext-artifacts/
|
||||
tests/storage/dual-signatures/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,142 @@
|
|||
# Contractless Web3 Wallet Privacy Policy
|
||||
|
||||
Effective date: August 2, 2026
|
||||
|
||||
Contractless Web3 Wallet is a browser extension for creating and managing
|
||||
Contractless blockchain wallets, connecting to compatible Web3 applications,
|
||||
and reviewing, signing, and broadcasting Contractless transactions.
|
||||
|
||||
## Information Contractless Does Not Collect
|
||||
|
||||
Contractless Web3 Wallet does not include advertising, analytics, telemetry,
|
||||
tracking, or behavioral profiling. The developer does not collect or sell:
|
||||
|
||||
- names, email addresses, telephone numbers, or other identifying information;
|
||||
- browsing history or general website activity;
|
||||
- health information;
|
||||
- personal communications;
|
||||
- precise or approximate location information; or
|
||||
- wallet encryption keys or private keys.
|
||||
|
||||
## Information Processed Locally
|
||||
|
||||
The wallet must process financial and authentication information to provide its
|
||||
requested functionality. This processing occurs locally inside the browser
|
||||
extension and does not make the information available to the developer.
|
||||
|
||||
Locally processed information may include:
|
||||
|
||||
- encrypted Contractless wallet files;
|
||||
- wallet addresses, public keys, and vanity addresses;
|
||||
- private keys and wallet encryption keys while the wallet is unlocked;
|
||||
- balances, assets, NFTs, RWAs, loans, and transaction history;
|
||||
- unsigned and signed Contractless transactions;
|
||||
- website connection approvals and temporary application access keys; and
|
||||
- wallet labels, network selection, API endpoints, and automatic-lock settings.
|
||||
|
||||
Private keys are encrypted before being placed in persistent extension storage.
|
||||
Decrypted private keys exist only while the wallet is unlocked. Locking the
|
||||
wallet clears decrypted wallet material and expires active website sessions.
|
||||
|
||||
## Contractless API Communication
|
||||
|
||||
The extension communicates with the Contractless API selected in the wallet's
|
||||
settings. Testnet defaults to `https://api.contractless.dev`. Users may replace
|
||||
the API endpoint, and mainnet uses a separately configured endpoint.
|
||||
|
||||
The extension sends only the information required to perform a user-requested
|
||||
blockchain operation. Depending on the request, this may include:
|
||||
|
||||
- a wallet address, public key, and authentication signature;
|
||||
- an address, asset identifier, transaction identifier, or block identifier
|
||||
required for a blockchain lookup; or
|
||||
- a complete signed transaction submitted for validation and broadcast.
|
||||
|
||||
Private keys and wallet encryption keys are never sent to a Contractless API,
|
||||
website, blockchain node, or the extension developer.
|
||||
|
||||
An API endpoint chosen by the user is operated independently and may have its
|
||||
own privacy and logging practices. Users should only configure API endpoints
|
||||
they trust.
|
||||
|
||||
## Website Connections
|
||||
|
||||
Contractless-compatible websites may request access to the wallet through the
|
||||
Contractless Web3 provider. A website does not receive the active wallet address
|
||||
until the user explicitly approves its connection request.
|
||||
|
||||
After approval, the website receives a temporary access key associated with
|
||||
that website, application identifier, wallet, and unlock session. The access
|
||||
expires when the wallet locks or the user disconnects the application.
|
||||
|
||||
Websites may request message signatures or transactions. The wallet displays
|
||||
the request for review and requires user approval before signing. A connected
|
||||
website receives only the address, signatures, transaction information, and
|
||||
other results required to complete the approved request. Websites never receive
|
||||
the wallet private key or encryption key.
|
||||
|
||||
Contractless does not control third-party websites. Their handling of
|
||||
information received after user approval is governed by their own privacy
|
||||
policies.
|
||||
|
||||
## Browser Permissions
|
||||
|
||||
Contractless Web3 Wallet uses the following browser permissions:
|
||||
|
||||
- **Storage:** Stores encrypted wallets, wallet settings, labels, and temporary
|
||||
application sessions.
|
||||
- **Alarms:** Enforces the user-configured automatic wallet lock timeout.
|
||||
- **Clipboard write:** Copies information only after the user selects a copy
|
||||
action, such as copying an address or transaction identifier.
|
||||
- **Host access:** Makes the Contractless provider available to compatible
|
||||
websites and communicates with the configured Contractless API. The extension
|
||||
does not read or collect general webpage content.
|
||||
|
||||
## Data Retention and Deletion
|
||||
|
||||
Wallet information remains in browser extension storage until the user removes
|
||||
it, clears the extension's storage, or uninstalls the extension. Temporary
|
||||
website access information expires when the wallet locks or the application is
|
||||
disconnected.
|
||||
|
||||
Contractless does not maintain a separate developer-controlled database of
|
||||
extension wallets or user activity.
|
||||
|
||||
## Sharing and Sale of Information
|
||||
|
||||
Contractless does not sell user information. Information is not used for
|
||||
advertising, credit decisions, behavioral profiling, or purposes unrelated to
|
||||
the wallet's user-facing functionality.
|
||||
|
||||
Information is transmitted only when necessary to complete a user-requested
|
||||
wallet operation, communicate with the configured Contractless API, or respond
|
||||
to an explicitly approved website request.
|
||||
|
||||
The use of information by Contractless Web3 Wallet is limited to providing and
|
||||
improving its disclosed wallet functionality. Information is not transferred
|
||||
for advertising or other unrelated purposes, and humans are not permitted to
|
||||
read private wallet information except when specifically requested by the user
|
||||
for support, required for security, or required by law.
|
||||
|
||||
## Security
|
||||
|
||||
Contractless Web3 Wallet performs cryptographic operations locally using code
|
||||
and WebAssembly bundled with the extension. API communication must use HTTPS,
|
||||
except when a user deliberately configures a local development endpoint.
|
||||
|
||||
No system can guarantee absolute security. Users are responsible for protecting
|
||||
their wallet images, private keys, encryption keys, devices, and configured API
|
||||
endpoints.
|
||||
|
||||
## Changes to This Policy
|
||||
|
||||
This policy may be updated when wallet functionality or privacy requirements
|
||||
change. Material changes will be reflected by updating this document and its
|
||||
effective date.
|
||||
|
||||
## Contact
|
||||
|
||||
Questions or privacy concerns may be submitted through the Contractless Web3
|
||||
Wallet repository:
|
||||
|
||||
https://contractless.dev/contractless/Contractless-Web3-Wallets
|
||||
|
|
@ -89,6 +89,9 @@ Private keys remain inside the extension. Websites receive only approved
|
|||
addresses, signatures, signed transaction bytes, and session-scoped access
|
||||
keys.
|
||||
|
||||
The wallet's data-handling practices are described in the
|
||||
[Privacy Policy](PRIVACY_POLICY.md).
|
||||
|
||||
## Integration Test
|
||||
|
||||
The `tests/tests.html` page verifies that a website can detect the extension,
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
*{box-sizing:border-box}body{max-width:760px;margin:0 auto;padding:34px;color:#111820;font:14px/1.45 Arial,Helvetica,sans-serif}header{display:flex;align-items:center;gap:15px;padding-bottom:18px;border-bottom:3px solid #2167a5}header img{width:58px}h1{margin:0;font-size:24px}header p{margin:2px 0 0;color:#5c6972}.warning{margin:22px 0;border:2px solid #9b1c1c;padding:12px;color:#7a1111;font-weight:700}section{margin-top:18px}h2{margin:0 0 7px;color:#42515b;font-size:12px;text-transform:uppercase}pre{margin:0;overflow-wrap:anywhere;white-space:pre-wrap;border:1px solid #aebac2;padding:11px;font:10px/1.35 Consolas,monospace}.print-error{color:#9b1c1c;font-weight:700}@media print{body{max-width:none;padding:0}}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -6,7 +6,7 @@
|
|||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
|
||||
},
|
||||
"permissions": ["storage", "alarms", "activeTab", "clipboardWrite"],
|
||||
"permissions": ["storage", "alarms", "clipboardWrite"],
|
||||
"host_permissions": ["https://api.contractless.dev/*"],
|
||||
"optional_host_permissions": ["https://*/"],
|
||||
"action": {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<script type="module" crossorigin src="/popup.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/chunks/modulepreload-polyfill-B5Qt9EMX.js">
|
||||
<link rel="modulepreload" crossorigin href="/chunks/core-D2IK1U91.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/popup-B3vY8vDf.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/popup-DTeT6cow.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="wallet-shell">
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))i(e);new MutationObserver(e=>{for(const r of e)if(r.type==="childList")for(const o of r.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&i(o)}).observe(document,{childList:!0,subtree:!0});function s(e){const r={};return e.integrity&&(r.integrity=e.integrity),e.referrerPolicy&&(r.referrerPolicy=e.referrerPolicy),e.crossOrigin==="use-credentials"?r.credentials="include":e.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function i(e){if(e.ep)return;e.ep=!0;const r=s(e);fetch(e.href,r)}})();
|
||||
|
|
@ -1 +0,0 @@
|
|||
const o=document.createElement("script");o.src=chrome.runtime.getURL("provider.js");o.onload=()=>o.remove();(document.head||document.documentElement).appendChild(o);window.addEventListener("message",e=>{var t;e.source!==window||((t=e.data)==null?void 0:t.source)!=="contractless-page"||chrome.runtime.sendMessage({type:"provider-request",method:e.data.method,params:e.data.params},a=>{window.postMessage({source:"contractless-wallet",id:e.data.id,response:a},window.location.origin)})});
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.2 KiB |
|
|
@ -18,7 +18,7 @@
|
|||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
|
||||
},
|
||||
"permissions": ["storage", "alarms", "activeTab", "clipboardWrite"],
|
||||
"permissions": ["storage", "alarms", "clipboardWrite"],
|
||||
"host_permissions": ["https://api.contractless.dev/*"],
|
||||
"optional_host_permissions": ["https://*/"],
|
||||
"action": {
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Contractless Wallet",
|
||||
"version": "0.1.1",
|
||||
"description": "A local-signing Web3 wallet for Contractless.",
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
|
||||
},
|
||||
"permissions": ["storage", "alarms", "activeTab", "clipboardWrite"],
|
||||
"host_permissions": ["https://api.contractless.dev/*"],
|
||||
"optional_host_permissions": ["https://*/"],
|
||||
"action": {
|
||||
"default_popup": "popup.html",
|
||||
"default_title": "Contractless Wallet",
|
||||
"default_icon": {
|
||||
"16": "contractless-logo.png",
|
||||
"32": "contractless-logo.png",
|
||||
"48": "contractless-logo.png",
|
||||
"128": "contractless-logo.png"
|
||||
}
|
||||
},
|
||||
"icons": {
|
||||
"16": "contractless-logo.png",
|
||||
"32": "contractless-logo.png",
|
||||
"48": "contractless-logo.png",
|
||||
"128": "contractless-logo.png"
|
||||
},
|
||||
"background": {
|
||||
"service_worker": "background.js",
|
||||
"type": "module"
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["https://*/*", "http://localhost/*", "http://127.0.0.1/*"],
|
||||
"js": ["content.js"],
|
||||
"run_at": "document_start"
|
||||
}
|
||||
],
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"resources": ["provider.js"],
|
||||
"matches": ["https://*/*", "http://localhost/*", "http://127.0.0.1/*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,635 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Contractless Wallet</title>
|
||||
<script type="module" crossorigin src="/popup.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/chunks/modulepreload-polyfill-B5Qt9EMX.js">
|
||||
<link rel="modulepreload" crossorigin href="/chunks/core-CvwXoH-U.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/popup-C5oUQoSX.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="wallet-shell">
|
||||
<section id="loading" class="screen loading-screen">
|
||||
<div class="loading-brand">
|
||||
<img src="/assets/contractless-logo-BgfMS-Kf.png" alt="Contractless" />
|
||||
<div class="spinner" aria-label="Loading"></div>
|
||||
</div>
|
||||
<p>Opening wallet</p>
|
||||
</section>
|
||||
|
||||
<section id="create-screen" class="screen" hidden>
|
||||
<header class="wallet-header">
|
||||
<img src="/assets/contractless-logo-BgfMS-Kf.png" alt="" />
|
||||
<div><strong>Contractless</strong><small>Browser Wallet</small></div>
|
||||
<span>1 of 3</span>
|
||||
</header>
|
||||
<div class="screen-content">
|
||||
<span class="eyebrow">New wallet</span>
|
||||
<h1>Create your wallet</h1>
|
||||
<p class="intro">Choose an encryption key used to unlock and recover this wallet.</p>
|
||||
|
||||
<label for="new-password">Encryption key</label>
|
||||
<div class="input-row">
|
||||
<input id="new-password" type="password" autocomplete="new-password" />
|
||||
<button class="reveal" type="button" data-input="new-password">Show</button>
|
||||
</div>
|
||||
|
||||
<div class="note">
|
||||
<strong>This key is not stored for you.</strong>
|
||||
<span>You will verify it before the wallet is created.</span>
|
||||
</div>
|
||||
|
||||
<button id="continue-create" class="primary" type="button">Continue <span>→</span></button>
|
||||
<div class="divider"><span>or restore an existing wallet</span></div>
|
||||
<button class="secondary navigation" data-screen="image-screen" type="button">
|
||||
Import wallet from image
|
||||
</button>
|
||||
<button class="secondary navigation" data-screen="private-screen" type="button">
|
||||
Import wallet from private key
|
||||
</button>
|
||||
<button id="cancel-wallet-add" class="text-button" type="button" hidden>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="image-screen" class="screen" hidden>
|
||||
<header class="wallet-header">
|
||||
<img src="/assets/contractless-logo-BgfMS-Kf.png" alt="" />
|
||||
<div><strong>Contractless</strong><small>Browser Wallet</small></div>
|
||||
<span>Import</span>
|
||||
</header>
|
||||
<div class="screen-content">
|
||||
<span class="eyebrow">Existing wallet</span>
|
||||
<h1>Import wallet image</h1>
|
||||
<p class="intro">
|
||||
Select your encrypted private-key image and enter the encryption key
|
||||
originally used to create it.
|
||||
</p>
|
||||
|
||||
<label for="image-password">Encryption key</label>
|
||||
<div class="input-row">
|
||||
<input id="image-password" type="password" autocomplete="current-password" />
|
||||
<button class="reveal" type="button" data-input="image-password">Show</button>
|
||||
</div>
|
||||
|
||||
<label class="file-heading" for="wallet-image">Wallet image</label>
|
||||
<label class="file-picker" for="wallet-image">
|
||||
<span class="file-icon">+</span>
|
||||
<span><strong id="image-file-name">Choose wallet image</strong><small>PNG encrypted private-key image</small></span>
|
||||
</label>
|
||||
<input id="wallet-image" class="visually-hidden" type="file" accept="image/png" />
|
||||
|
||||
<button id="import-image" class="primary" type="button">Import wallet</button>
|
||||
<button class="text-button navigation" data-screen="create-screen" type="button">Back</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="private-screen" class="screen" hidden>
|
||||
<header class="wallet-header">
|
||||
<img src="/assets/contractless-logo-BgfMS-Kf.png" alt="" />
|
||||
<div><strong>Contractless</strong><small>Browser Wallet</small></div>
|
||||
<span>Import</span>
|
||||
</header>
|
||||
<div class="screen-content compact">
|
||||
<span class="eyebrow">Existing wallet</span>
|
||||
<h1>Import private key</h1>
|
||||
<p class="intro">
|
||||
Paste the private key, then choose the encryption key that will protect
|
||||
this wallet from now on.
|
||||
</p>
|
||||
|
||||
<label for="private-key">Private key</label>
|
||||
<textarea id="private-key" rows="4" placeholder="Paste private key"></textarea>
|
||||
|
||||
<label for="private-password">New encryption key</label>
|
||||
<div class="input-row">
|
||||
<input id="private-password" type="password" autocomplete="new-password" />
|
||||
<button class="reveal" type="button" data-input="private-password">Show</button>
|
||||
</div>
|
||||
|
||||
<div class="note compact-note">
|
||||
<strong>This key protects the new wallet image.</strong>
|
||||
<span>Store it securely and separately from the image.</span>
|
||||
</div>
|
||||
|
||||
<button id="import-private" class="primary" type="button">Import and create wallet</button>
|
||||
<button class="text-button navigation" data-screen="create-screen" type="button">Back</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="verify-screen" class="screen" hidden>
|
||||
<header class="wallet-header">
|
||||
<img src="/assets/contractless-logo-BgfMS-Kf.png" alt="" />
|
||||
<div><strong>Contractless</strong><small>Browser Wallet</small></div>
|
||||
<span>2 of 3</span>
|
||||
</header>
|
||||
<div class="screen-content">
|
||||
<span class="eyebrow">Recovery</span>
|
||||
<h1>Verify your encryption key</h1>
|
||||
<p class="intro">
|
||||
Enter the same key again. Keep it somewhere secure and separate from
|
||||
your wallet image.
|
||||
</p>
|
||||
|
||||
<label for="verify-password">Re-enter encryption key</label>
|
||||
<div class="input-row">
|
||||
<input id="verify-password" type="password" autocomplete="new-password" />
|
||||
<button class="reveal" type="button" data-input="verify-password">Show</button>
|
||||
</div>
|
||||
|
||||
<div class="warning">
|
||||
<strong>There is no password reset.</strong>
|
||||
Losing this key means the wallet image cannot restore your wallet.
|
||||
</div>
|
||||
|
||||
<button id="create-wallet" class="primary" type="button">Create wallet <span>→</span></button>
|
||||
<button class="text-button navigation" data-screen="create-screen" type="button">Back</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="backup-screen" class="screen" hidden>
|
||||
<header class="wallet-header">
|
||||
<img src="/assets/contractless-logo-BgfMS-Kf.png" alt="" />
|
||||
<div><strong>Contractless</strong><small>Browser Wallet</small></div>
|
||||
<span>3 of 3</span>
|
||||
</header>
|
||||
<div class="screen-content backup-content">
|
||||
<span class="success-mark">✓</span>
|
||||
<h1>Your wallet is ready</h1>
|
||||
<p class="intro">
|
||||
Save the private-key image now. You need both this image and your
|
||||
encryption key to recover the wallet.
|
||||
</p>
|
||||
|
||||
<div class="image-preview">
|
||||
<img id="wallet-image-preview" alt="Encrypted private-key wallet image" />
|
||||
</div>
|
||||
|
||||
<button id="save-image" class="primary" type="button">↓ Save wallet image</button>
|
||||
<label class="confirmation">
|
||||
<input id="backup-confirmed" type="checkbox" disabled />
|
||||
<span>I saved the image and stored my encryption key separately.</span>
|
||||
</label>
|
||||
<button id="open-wallet" class="secondary" type="button" disabled>Open wallet</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="locked-screen" class="screen" hidden>
|
||||
<form id="unlock-form" class="centered-content">
|
||||
<img class="large-logo" src="/assets/contractless-logo-BgfMS-Kf.png" alt="Contractless" />
|
||||
<h1>Unlock wallet</h1>
|
||||
<p class="intro">Enter your encryption key to continue.</p>
|
||||
<label for="unlock-password">Encryption key</label>
|
||||
<div class="input-row">
|
||||
<input id="unlock-password" type="password" autocomplete="current-password" />
|
||||
<button class="reveal" type="button" data-input="unlock-password">Show</button>
|
||||
</div>
|
||||
<button id="unlock-wallet" class="primary" type="submit">Unlock wallet</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section id="provider-approval-screen" class="screen provider-approval-screen" hidden>
|
||||
<header class="wallet-header">
|
||||
<img src="/assets/contractless-logo-BgfMS-Kf.png" alt="" />
|
||||
<div><strong>Contractless</strong><small>Wallet request</small></div>
|
||||
<span>Review</span>
|
||||
</header>
|
||||
<div class="screen-content">
|
||||
<div class="approval-context">
|
||||
<span class="eyebrow" id="provider-approval-origin"></span>
|
||||
<span id="provider-approval-risk" class="risk-label"></span>
|
||||
</div>
|
||||
<h1 id="provider-approval-title">Review request</h1>
|
||||
<p class="intro" id="provider-approval-intro">Review this request before approving it.</p>
|
||||
<dl id="provider-approval-details" class="approval-details"></dl>
|
||||
</div>
|
||||
<div class="approval-actions">
|
||||
<button id="reject-provider-approval" class="secondary" type="button">Reject</button>
|
||||
<button id="approve-provider-approval" class="primary" type="button">Approve</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="dashboard-screen" class="screen" hidden>
|
||||
<header class="wallet-header dashboard-header">
|
||||
<img src="/assets/contractless-logo-BgfMS-Kf.png" alt="" />
|
||||
<div><strong>Contractless</strong><small data-network-name>Testnet</small></div>
|
||||
<button
|
||||
id="account-menu-button"
|
||||
class="account-button"
|
||||
type="button"
|
||||
title="Wallets and settings"
|
||||
aria-expanded="false"
|
||||
aria-controls="account-drawer"
|
||||
>
|
||||
<span class="network-dot"></span>
|
||||
<span id="wallet-address-short">Account</span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div class="assets-dashboard">
|
||||
<nav class="wallet-actions" aria-label="Wallet actions">
|
||||
<button id="open-send" type="button">
|
||||
<span class="action-icon">↗</span>
|
||||
<span>Send</span>
|
||||
</button>
|
||||
<button id="open-receive" type="button">
|
||||
<span class="action-icon">↙</span>
|
||||
<span>Receive</span>
|
||||
</button>
|
||||
<button id="open-nfts" type="button">
|
||||
<span class="action-icon">#</span>
|
||||
<span>NFTs/RWAs</span>
|
||||
</button>
|
||||
<button id="open-activity" type="button">
|
||||
<span class="action-icon">◷</span>
|
||||
<span>Activity</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<section class="portfolio-balance" aria-labelledby="portfolio-title">
|
||||
<span id="portfolio-title">Available balance</span>
|
||||
<strong><span id="base-balance">0.00000000</span> <small data-network-symbol>CLTC</small></strong>
|
||||
<p id="wallet-address"></p>
|
||||
</section>
|
||||
|
||||
<section class="asset-ledger" aria-labelledby="assets-title">
|
||||
<div class="asset-ledger-heading">
|
||||
<h1 id="assets-title">Assets</h1>
|
||||
<button id="refresh-balances" type="button" title="Refresh balances" aria-label="Refresh balances">↻</button>
|
||||
</div>
|
||||
|
||||
<div class="asset-filter" role="group" aria-label="Token visibility">
|
||||
<button class="active" data-token-filter="held" type="button">Held tokens</button>
|
||||
<button data-token-filter="all" type="button">All tokens</button>
|
||||
</div>
|
||||
|
||||
<div class="asset-row base-asset">
|
||||
<div class="asset-symbol contractless-symbol">
|
||||
<img src="/assets/contractless-logo-BgfMS-Kf.png" alt="" />
|
||||
</div>
|
||||
<div class="asset-name">
|
||||
<strong id="base-asset-name">Contractless Testnet</strong>
|
||||
<span data-network-symbol>CLTC</span>
|
||||
</div>
|
||||
<div class="asset-value">
|
||||
<strong id="base-asset-balance">0.00000000</strong>
|
||||
<span data-network-symbol>CLTC</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="token-list" class="token-list">
|
||||
<div class="empty-assets">
|
||||
<strong>Loading token balances...</strong>
|
||||
<span>Balances are retrieved from the selected Contractless API.</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="nfts-screen" class="screen nfts-screen" hidden>
|
||||
<header class="wallet-header">
|
||||
<button id="close-nfts" class="header-back" type="button" title="Back to assets" aria-label="Back to assets">←</button>
|
||||
<div><strong>NFTs and RWAs</strong><small>Assets owned by this wallet</small></div>
|
||||
<button id="refresh-nfts" class="header-refresh" type="button" title="Refresh assets" aria-label="Refresh assets">↻</button>
|
||||
</header>
|
||||
|
||||
<div class="nfts-content">
|
||||
<div class="nfts-summary">
|
||||
<span id="nfts-count">Loading ownership...</span>
|
||||
<span data-network-name>Testnet</span>
|
||||
</div>
|
||||
<div id="nfts-list" class="nfts-list"></div>
|
||||
<div id="nfts-pagination" class="nfts-pagination" hidden>
|
||||
<button id="nfts-previous" type="button" aria-label="Previous NFT page">←</button>
|
||||
<span>Page <strong id="nfts-page">1</strong> of <strong id="nfts-pages">1</strong></span>
|
||||
<button id="nfts-next" type="button" aria-label="Next NFT page">→</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="nft-detail-screen" class="screen nft-detail-screen" hidden>
|
||||
<header class="wallet-header">
|
||||
<button id="close-nft-detail" class="header-back" type="button" title="Back to NFTs and RWAs" aria-label="Back to NFTs and RWAs">←</button>
|
||||
<div><strong id="nft-detail-title">NFT/RWA</strong><small id="nft-detail-kind">On-chain asset</small></div>
|
||||
<span data-network-name>Testnet</span>
|
||||
</header>
|
||||
|
||||
<div class="nft-detail-content">
|
||||
<div class="nft-detail-media">
|
||||
<img id="nft-detail-image" alt="" />
|
||||
</div>
|
||||
<div class="nft-detail-heading">
|
||||
<div>
|
||||
<span id="nft-detail-badge">NFT</span>
|
||||
<h1 id="nft-detail-name">NFT/RWA</h1>
|
||||
</div>
|
||||
<strong id="nft-detail-ownership"></strong>
|
||||
</div>
|
||||
<p id="nft-detail-description" class="nft-detail-description"></p>
|
||||
<div id="nft-detail-attributes" class="nft-detail-attributes"></div>
|
||||
<dl id="nft-detail-fields" class="nft-detail-fields"></dl>
|
||||
<div class="nft-detail-actions">
|
||||
<button id="transfer-nft" class="primary" type="button">Transfer asset</button>
|
||||
</div>
|
||||
<section class="nft-provenance">
|
||||
<h2>Provenance</h2>
|
||||
<div id="nft-detail-history"></div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="send-screen" class="screen send-screen" hidden>
|
||||
<header class="wallet-header">
|
||||
<button id="close-send" class="header-back" type="button" title="Back to assets" aria-label="Back to assets">←</button>
|
||||
<div><strong>Send</strong><small>Create a transfer</small></div>
|
||||
<span data-network-name>Testnet</span>
|
||||
</header>
|
||||
|
||||
<form id="send-form" class="send-content">
|
||||
<div>
|
||||
<label for="send-asset">Asset</label>
|
||||
<select id="send-asset"></select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="send-recipient">Receiving address</label>
|
||||
<input id="send-recipient" type="text" autocomplete="off" spellcheck="false" placeholder="Wallet or vanity address" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="send-amount">Amount</label>
|
||||
<div class="send-amount-row">
|
||||
<input id="send-amount" type="text" inputmode="decimal" autocomplete="off" placeholder="0.00000000" />
|
||||
<span id="send-amount-symbol" data-network-symbol>CLTC</span>
|
||||
</div>
|
||||
<span id="send-available" class="field-note">Available: 0.00000000 CLTC</span>
|
||||
</div>
|
||||
<div>
|
||||
<label for="send-fee">Transaction fee</label>
|
||||
<div class="send-amount-row">
|
||||
<input id="send-fee" type="text" inputmode="decimal" autocomplete="off" placeholder="0.00000000" />
|
||||
<span data-network-symbol>CLTC</span>
|
||||
</div>
|
||||
<span id="send-fee-note" class="field-note">Minimum fee: 1% of the transfer amount.</span>
|
||||
</div>
|
||||
<button class="primary send-continue" type="submit">Review transfer</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section id="send-review-screen" class="screen send-review-screen" hidden>
|
||||
<header class="wallet-header">
|
||||
<button id="edit-send" class="header-back" type="button" title="Edit transfer" aria-label="Edit transfer">←</button>
|
||||
<div><strong>Review transfer</strong><small>Confirm before signing</small></div>
|
||||
<span data-network-name>Testnet</span>
|
||||
</header>
|
||||
|
||||
<div class="send-review-content">
|
||||
<div class="send-review-amount">
|
||||
<span>You are sending</span>
|
||||
<strong><span id="review-amount">0.00000000</span> <small id="review-asset">CLTC</small></strong>
|
||||
</div>
|
||||
<dl class="send-review-details">
|
||||
<div><dt>From</dt><dd id="review-sender"></dd></div>
|
||||
<div><dt>To</dt><dd id="review-recipient"></dd></div>
|
||||
<div><dt>Resolved address</dt><dd id="review-resolved-recipient"></dd></div>
|
||||
<div><dt>Transaction fee</dt><dd><span id="review-fee"></span> <span data-network-symbol>CLTC</span></dd></div>
|
||||
<div><dt>Total deducted</dt><dd id="review-total"></dd></div>
|
||||
</dl>
|
||||
<p class="signing-notice">Your private key remains inside this wallet. The transaction is signed locally and only the completed transaction is sent to the API.</p>
|
||||
<button id="sign-broadcast-transfer" class="primary" type="button">Sign and broadcast</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="send-success-screen" class="screen send-success-screen" hidden>
|
||||
<header class="wallet-header">
|
||||
<div><strong>Transfer broadcast</strong><small>Submitted to Contractless</small></div>
|
||||
<span data-network-name>Testnet</span>
|
||||
</header>
|
||||
<div class="send-success-content">
|
||||
<span class="success-mark">✓</span>
|
||||
<h1>Transaction sent</h1>
|
||||
<p>The signed transfer was accepted for broadcast.</p>
|
||||
<div class="send-txid">
|
||||
<span>Transaction ID</span>
|
||||
<strong id="send-result-txid"></strong>
|
||||
</div>
|
||||
<button id="copy-send-txid" class="secondary" type="button">Copy transaction ID</button>
|
||||
<button id="finish-send" class="primary" type="button">Return to assets</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="activity-screen" class="screen activity-screen" hidden>
|
||||
<header class="wallet-header">
|
||||
<button id="close-activity" class="header-back" type="button" title="Back to assets" aria-label="Back to assets">←</button>
|
||||
<div><strong>Activity</strong><small>Latest 25 transactions</small></div>
|
||||
<span data-network-name>Testnet</span>
|
||||
</header>
|
||||
|
||||
<div class="activity-content">
|
||||
<div class="activity-summary">
|
||||
<span>Newest transactions first</span>
|
||||
<div>
|
||||
<strong id="activity-count">0 shown</strong>
|
||||
<button id="refresh-activity" type="button" title="Refresh activity" aria-label="Refresh activity">↻</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="activity-list" class="activity-list"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="transaction-screen" class="screen transaction-screen" hidden>
|
||||
<header class="wallet-header">
|
||||
<button id="close-transaction" class="header-back" type="button" title="Back to activity" aria-label="Back to activity">←</button>
|
||||
<div><strong id="transaction-title">Transaction</strong><small>Complete on-chain details</small></div>
|
||||
<span id="transaction-header-status"></span>
|
||||
</header>
|
||||
|
||||
<div class="transaction-content">
|
||||
<section class="transaction-identity">
|
||||
<div>
|
||||
<span>Transaction ID</span>
|
||||
<strong id="transaction-full-txid"></strong>
|
||||
</div>
|
||||
<button id="copy-transaction-txid" type="button">Copy TXID</button>
|
||||
</section>
|
||||
|
||||
<div id="transaction-overview" class="transaction-fields"></div>
|
||||
|
||||
<section class="transaction-section">
|
||||
<h2>Transaction Fields</h2>
|
||||
<div id="transaction-fields" class="transaction-fields"></div>
|
||||
</section>
|
||||
|
||||
<section id="miner-earnings-section" class="transaction-section" hidden>
|
||||
<h2>Miner Earnings</h2>
|
||||
<div id="transaction-miner-earnings" class="transaction-fields"></div>
|
||||
</section>
|
||||
|
||||
<details class="transaction-technical">
|
||||
<summary>Technical Details</summary>
|
||||
<div id="transaction-technical-fields" class="transaction-fields"></div>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="receive-screen" class="screen receive-screen" hidden>
|
||||
<header class="wallet-header">
|
||||
<button id="close-receive" class="header-back" type="button" title="Back to assets" aria-label="Back to assets">←</button>
|
||||
<div><strong>Receive</strong><small>Contractless wallet address</small></div>
|
||||
<span data-network-name>Testnet</span>
|
||||
</header>
|
||||
|
||||
<div class="receive-content">
|
||||
<span class="receive-label">Receive CLTC and Contractless assets</span>
|
||||
<h1>Share your address</h1>
|
||||
<p>
|
||||
Scan the QR code or copy the address below. Only send assets issued on
|
||||
the Contractless testnet to this address.
|
||||
</p>
|
||||
|
||||
<div class="qr-frame">
|
||||
<canvas id="receive-qr" width="220" height="220" aria-label="Wallet address QR code"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="receive-address">
|
||||
<span id="receive-address-label">Wallet address</span>
|
||||
<strong id="receive-address-value"></strong>
|
||||
</div>
|
||||
|
||||
<button id="copy-receive-address" class="primary receive-copy" type="button">
|
||||
Copy address
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div id="drawer-backdrop" class="drawer-backdrop" hidden></div>
|
||||
<aside id="account-drawer" class="account-drawer" aria-hidden="true">
|
||||
<header class="drawer-header">
|
||||
<div>
|
||||
<strong>Wallets</strong>
|
||||
<span>Accounts and settings</span>
|
||||
</div>
|
||||
<button id="close-account-drawer" type="button" title="Close menu" aria-label="Close menu">×</button>
|
||||
</header>
|
||||
|
||||
<section class="drawer-section">
|
||||
<span class="drawer-label">Known addresses</span>
|
||||
<div id="known-addresses" class="known-addresses"></div>
|
||||
</section>
|
||||
|
||||
<nav class="drawer-navigation" aria-label="Wallet menu">
|
||||
<button id="menu-create-wallet" type="button">
|
||||
<span class="menu-icon">+</span>
|
||||
<span><strong>Create another address</strong><small>Generate a new Contractless wallet</small></span>
|
||||
<span class="menu-arrow">›</span>
|
||||
</button>
|
||||
<button id="menu-import-wallet" type="button">
|
||||
<span class="menu-icon">↓</span>
|
||||
<span><strong>Import another address</strong><small>Use an image or private key</small></span>
|
||||
<span class="menu-arrow">›</span>
|
||||
</button>
|
||||
<button id="menu-settings" type="button">
|
||||
<span class="menu-icon">⚙</span>
|
||||
<span><strong>Settings</strong><small>API endpoint and lock timeout</small></span>
|
||||
<span class="menu-arrow">›</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<button id="drawer-lock-wallet" class="drawer-lock" type="button">
|
||||
<span>⌁</span>
|
||||
Lock wallet
|
||||
</button>
|
||||
</aside>
|
||||
|
||||
<section id="settings-screen" class="screen settings-screen" hidden>
|
||||
<header class="wallet-header">
|
||||
<button id="close-settings" class="header-back" type="button" title="Back to assets" aria-label="Back to assets">←</button>
|
||||
<div><strong>Settings</strong><small>Wallet preferences and backup</small></div>
|
||||
</header>
|
||||
|
||||
<div class="settings-content">
|
||||
<section class="settings-group">
|
||||
<div class="settings-heading">
|
||||
<strong>Security</strong>
|
||||
<span>Control how long the active wallet stays unlocked.</span>
|
||||
</div>
|
||||
<label for="settings-lock-timeout">Automatic lock</label>
|
||||
<select id="settings-lock-timeout">
|
||||
<option value="15">15 minutes</option>
|
||||
<option value="30">30 minutes</option>
|
||||
<option value="60">60 minutes</option>
|
||||
<option value="240">4 hours</option>
|
||||
<option value="720">12 hours</option>
|
||||
<option value="1440">24 hours</option>
|
||||
</select>
|
||||
</section>
|
||||
|
||||
<section class="settings-group">
|
||||
<div class="settings-heading">
|
||||
<strong>Contractless API</strong>
|
||||
<span>Choose the API used for lookups and transaction broadcasts.</span>
|
||||
</div>
|
||||
<label for="settings-api-url">API URL</label>
|
||||
<input id="settings-api-url" type="url" value="https://api.contractless.dev" />
|
||||
</section>
|
||||
|
||||
<button id="save-wallet-settings" class="primary settings-save" type="button">Save settings</button>
|
||||
|
||||
<section class="settings-group backup-settings">
|
||||
<div class="settings-heading">
|
||||
<strong>Active wallet backup</strong>
|
||||
<span>These actions require the active wallet encryption key.</span>
|
||||
</div>
|
||||
<button class="settings-action" data-backup-action="image" type="button">
|
||||
<span class="settings-action-icon">▧</span>
|
||||
<span><strong>Save wallet image</strong><small>Download an encrypted PNG backup</small></span>
|
||||
<span>›</span>
|
||||
</button>
|
||||
<button class="settings-action" data-backup-action="private" type="button">
|
||||
<span class="settings-action-icon">⌘</span>
|
||||
<span><strong>View private key</strong><small>Reveal and copy the active private key</small></span>
|
||||
<span>›</span>
|
||||
</button>
|
||||
<button class="settings-action" data-backup-action="print" type="button">
|
||||
<span class="settings-action-icon">▤</span>
|
||||
<span><strong>Print wallet backup</strong><small>Private key and encryption key</small></span>
|
||||
<span>›</span>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section id="private-key-result" class="private-key-result" hidden>
|
||||
<div>
|
||||
<strong>Private key</strong>
|
||||
<button id="clear-private-key" type="button" title="Clear private key" aria-label="Clear private key">×</button>
|
||||
</div>
|
||||
<textarea id="revealed-private-key" readonly></textarea>
|
||||
<button id="copy-private-key" class="secondary" type="button">Copy private key</button>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div id="backup-key-backdrop" class="modal-backdrop" hidden></div>
|
||||
<section id="backup-key-modal" class="backup-key-modal" hidden aria-modal="true" role="dialog" aria-labelledby="backup-key-title">
|
||||
<header>
|
||||
<div>
|
||||
<strong id="backup-key-title">Confirm encryption key</strong>
|
||||
<span>Required to decrypt this wallet locally.</span>
|
||||
</div>
|
||||
<button id="close-backup-key" type="button" title="Cancel" aria-label="Cancel">×</button>
|
||||
</header>
|
||||
<div>
|
||||
<label for="backup-key-input">Encryption key</label>
|
||||
<div class="input-row">
|
||||
<input id="backup-key-input" type="password" autocomplete="current-password" />
|
||||
<button class="reveal" type="button" data-input="backup-key-input">Show</button>
|
||||
</div>
|
||||
<button id="confirm-backup-key" class="primary" type="button">Continue</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<p id="notice" role="alert" aria-live="polite"></p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,44 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Contractless Wallet Backup</title>
|
||||
<script type="module" crossorigin src="/print.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/chunks/modulepreload-polyfill-B5Qt9EMX.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/print-DTb91-bp.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<img src="/assets/contractless-logo-BgfMS-Kf.png" alt="" />
|
||||
<div>
|
||||
<h1>Contractless Wallet Backup</h1>
|
||||
<p>Private recovery information</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<p class="warning">
|
||||
Keep this page private. Anyone with the private key and encryption key can
|
||||
control this wallet.
|
||||
</p>
|
||||
|
||||
<section>
|
||||
<h2>Wallet</h2>
|
||||
<pre id="wallet-label"></pre>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Address</h2>
|
||||
<pre id="wallet-address"></pre>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Private Key</h2>
|
||||
<pre id="wallet-private-key"></pre>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Encryption Key</h2>
|
||||
<pre id="wallet-encryption-key"></pre>
|
||||
</section>
|
||||
|
||||
<p id="print-error" class="print-error"></p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1 +0,0 @@
|
|||
import"./chunks/modulepreload-polyfill-B5Qt9EMX.js";async function a(){const e=new URLSearchParams(location.search).get("id");if(!e)throw new Error("Missing printable wallet backup.");const n=`print-backup:${e}`,o=await chrome.storage.session.get(n);await chrome.storage.session.remove(n);const t=o[n];if(!t)throw new Error("The printable wallet backup has expired.");document.getElementById("wallet-label").textContent=t.label,document.getElementById("wallet-address").textContent=t.address,document.getElementById("wallet-private-key").textContent=t.privateKey,document.getElementById("wallet-encryption-key").textContent=t.encryptionKey,setTimeout(()=>{window.focus(),window.print()},150)}a().catch(e=>{document.getElementById("print-error").textContent=e instanceof Error?e.message:String(e)});
|
||||
|
|
@ -1 +0,0 @@
|
|||
const a=new Map;window.addEventListener("message",e=>{var s,t,o;if(e.source!==window||((s=e.data)==null?void 0:s.source)!=="contractless-wallet")return;const r=a.get(e.data.id);r&&(a.delete(e.data.id),(t=e.data.response)!=null&&t.ok?r.resolve(e.data.response.result):r.reject(new Error(((o=e.data.response)==null?void 0:o.error)??"Wallet request failed.")))});const n=Object.freeze({isContractless:!0,request({method:e,params:r}){const s=crypto.randomUUID();return new Promise((t,o)=>{a.set(s,{resolve:t,reject:o}),window.postMessage({source:"contractless-page",id:s,method:e,params:r},window.location.origin)})}});Object.defineProperty(window,"contractless",{value:n,writable:!1,configurable:!1});
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
|
||||
},
|
||||
"permissions": ["storage", "alarms", "activeTab", "clipboardWrite"],
|
||||
"permissions": ["storage", "alarms", "clipboardWrite"],
|
||||
"host_permissions": ["https://api.contractless.dev/*"],
|
||||
"optional_host_permissions": ["https://*/"],
|
||||
"action": {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
|
||||
},
|
||||
"permissions": ["storage", "alarms", "activeTab", "clipboardWrite"],
|
||||
"permissions": ["storage", "alarms", "clipboardWrite"],
|
||||
"host_permissions": ["https://api.contractless.dev/*"],
|
||||
"optional_host_permissions": ["https://*/"],
|
||||
"action": {
|
||||
|
|
|
|||
|
|
@ -120,6 +120,8 @@ let pendingTransfer:
|
|||
let lastSentTxid = "";
|
||||
let pendingProviderApprovalId = "";
|
||||
const CONFIRMED_AFTER_BLOCKS = 20;
|
||||
const BALANCE_REFRESH_INTERVAL_MS = 15_000;
|
||||
let balanceLoadInFlight = false;
|
||||
|
||||
const TRANSACTION_NAMES: Record<number, string> = {
|
||||
1: "Mining Reward",
|
||||
|
|
@ -532,12 +534,26 @@ function renderTokenBalances(): void {
|
|||
});
|
||||
}
|
||||
|
||||
async function loadWalletBalances(): Promise<void> {
|
||||
if (!walletStatus?.unlocked) return;
|
||||
walletBalances = undefined;
|
||||
byId("base-balance").textContent = "--";
|
||||
byId("base-asset-balance").textContent = "--";
|
||||
renderTokenBalances();
|
||||
async function loadWalletBalances(
|
||||
options: { showLoading?: boolean; reportErrors?: boolean } = {}
|
||||
): Promise<void> {
|
||||
if (!walletStatus?.unlocked || balanceLoadInFlight) return;
|
||||
|
||||
const requestedAddress = walletStatus.address;
|
||||
const requestedNetwork = walletStatus.selectedNetwork;
|
||||
const requestedApiUrl = walletStatus.apiUrl;
|
||||
const hadBalances = walletBalances !== undefined;
|
||||
const showLoading = options.showLoading ?? !hadBalances;
|
||||
const reportErrors = options.reportErrors ?? true;
|
||||
|
||||
balanceLoadInFlight = true;
|
||||
if (showLoading) {
|
||||
walletBalances = undefined;
|
||||
byId("base-balance").textContent = "--";
|
||||
byId("base-asset-balance").textContent = "--";
|
||||
renderTokenBalances();
|
||||
}
|
||||
|
||||
try {
|
||||
if (!walletStatus.apiUrl) {
|
||||
throw new Error(`No ${walletStatus.networkName} API is configured.`);
|
||||
|
|
@ -555,6 +571,16 @@ async function loadWalletBalances(): Promise<void> {
|
|||
tokenCatalogError?: string;
|
||||
nftCatalogError?: string;
|
||||
};
|
||||
|
||||
if (
|
||||
!walletStatus?.unlocked
|
||||
|| walletStatus.address !== requestedAddress
|
||||
|| walletStatus.selectedNetwork !== requestedNetwork
|
||||
|| walletStatus.apiUrl !== requestedApiUrl
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
walletBalances = {
|
||||
balances: Array.isArray(result.balances) ? result.balances : [],
|
||||
tokens: Array.isArray(result.tokens) ? result.tokens : [],
|
||||
|
|
@ -572,20 +598,35 @@ async function loadWalletBalances(): Promise<void> {
|
|||
byId("base-asset-balance").textContent = baseBalance;
|
||||
renderTokenBalances();
|
||||
} catch (error) {
|
||||
const list = byId("token-list");
|
||||
list.replaceChildren();
|
||||
const empty = document.createElement("div");
|
||||
empty.className = "empty-assets balance-error";
|
||||
const title = document.createElement("strong");
|
||||
title.textContent = "Unable to load balances";
|
||||
const description = document.createElement("span");
|
||||
description.textContent = errorText(error);
|
||||
empty.append(title, description);
|
||||
list.append(empty);
|
||||
notice.textContent = errorText(error);
|
||||
if (!hadBalances) {
|
||||
const list = byId("token-list");
|
||||
list.replaceChildren();
|
||||
const empty = document.createElement("div");
|
||||
empty.className = "empty-assets balance-error";
|
||||
const title = document.createElement("strong");
|
||||
title.textContent = "Unable to load balances";
|
||||
const description = document.createElement("span");
|
||||
description.textContent = errorText(error);
|
||||
empty.append(title, description);
|
||||
list.append(empty);
|
||||
}
|
||||
if (reportErrors) notice.textContent = errorText(error);
|
||||
} finally {
|
||||
balanceLoadInFlight = false;
|
||||
}
|
||||
}
|
||||
|
||||
function refreshVisibleDashboard(): void {
|
||||
if (
|
||||
document.hidden
|
||||
|| byId("dashboard-screen").hidden
|
||||
|| !walletStatus?.unlocked
|
||||
) {
|
||||
return;
|
||||
}
|
||||
void loadWalletBalances({ showLoading: false, reportErrors: false });
|
||||
}
|
||||
|
||||
function nftKey(name: string, series: number): string {
|
||||
return `${name.trim().toUpperCase()}|${series}`;
|
||||
}
|
||||
|
|
@ -2285,6 +2326,9 @@ byId("reject-provider-approval").addEventListener("click", () => {
|
|||
}).catch(() => setApprovalProcessing(null));
|
||||
});
|
||||
|
||||
window.setInterval(refreshVisibleDashboard, BALANCE_REFRESH_INTERVAL_MS);
|
||||
document.addEventListener("visibilitychange", refreshVisibleDashboard);
|
||||
|
||||
send("status")
|
||||
.then((status) => showStatus(status as WalletStatus))
|
||||
.catch((error) => {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,32 @@ button:disabled { cursor: not-allowed; }
|
|||
|
||||
.screen[hidden] { display: none; }
|
||||
|
||||
#create-screen,
|
||||
#image-screen,
|
||||
#private-screen,
|
||||
#verify-screen,
|
||||
#backup-screen {
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#create-screen::-webkit-scrollbar,
|
||||
#image-screen::-webkit-scrollbar,
|
||||
#private-screen::-webkit-scrollbar,
|
||||
#verify-screen::-webkit-scrollbar,
|
||||
#backup-screen::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
#create-screen::-webkit-scrollbar-thumb,
|
||||
#image-screen::-webkit-scrollbar-thumb,
|
||||
#private-screen::-webkit-scrollbar-thumb,
|
||||
#verify-screen::-webkit-scrollbar-thumb,
|
||||
#backup-screen::-webkit-scrollbar-thumb {
|
||||
border: 2px solid var(--blue);
|
||||
background: rgb(255 255 255 / 65%);
|
||||
}
|
||||
|
||||
.wallet-header {
|
||||
height: 72px;
|
||||
display: flex;
|
||||
|
|
|
|||
Loading…
Reference in New Issue