kira-wallet 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -4
- package/docs/CLI.md +11 -5
- package/docs/KIRA_DISCOVERY_ACCEPTANCE.md +95 -0
- package/docs/RELEASE.md +24 -9
- package/kira_jobs.py +1 -1
- package/onchain.cjs +6 -1
- package/package.json +3 -2
- package/pipeline-onchain.cjs +19 -2
- package/viewer/model.py +10 -0
- package/viewer/static/app.js +30 -6
- package/viewer/static/index.html +3 -1
- package/viewer/static/jobs.js +9 -2
- package/viewer/static/report.js +2 -2
- package/viewer/static/style.css +2 -0
- package/viewer/static/watching.js +5 -1
- package/viewer/static/workspace-model.js +22 -1
- package/wallet.py +17 -5
package/README.md
CHANGED
|
@@ -43,11 +43,12 @@ For a viewer without wallet or research actions, use `kira start --read-only`.
|
|
|
43
43
|
Prefer an archive? [GitHub releases](https://github.com/realproject7/kira-wallet/releases/tag/v0.1.2)
|
|
44
44
|
include the same package and its checksum.
|
|
45
45
|
|
|
46
|
-
1. **
|
|
47
|
-
wallets whenever you need them.
|
|
48
|
-
2. **Set up discovery.** In **Workspace settings**, connect an indexer for broad
|
|
46
|
+
1. **Set up discovery.** In **Workspace settings**, connect an indexer for broad
|
|
49
47
|
ERC20 discovery. Public RPC alone has limited discovery; incomplete coverage
|
|
50
|
-
stays visible.
|
|
48
|
+
stays visible. An AI or browser wallet connection does not enable an indexer.
|
|
49
|
+
2. **Add a watching wallet.** Paste a public address and give it a name. Add more
|
|
50
|
+
wallets whenever you need them. Without discovery, the form explicitly
|
|
51
|
+
offers limited research of native and Mint Club assets.
|
|
51
52
|
3. **Connect your AI.** Install and sign in to the
|
|
52
53
|
[Codex CLI](https://developers.openai.com/codex/cli) or
|
|
53
54
|
[Claude Code](https://code.claude.com/docs/en/quickstart), then choose your
|
|
@@ -117,6 +118,12 @@ package. Use `--data-dir /absolute/private/directory` before a command to choose
|
|
|
117
118
|
another workspace. For connection, model or provider help, see the
|
|
118
119
|
[CLI guide](docs/CLI.md) and [viewer guide](viewer/README.md).
|
|
119
120
|
|
|
121
|
+
From 0.1.4, a partial wallet analysis explains disabled discovery, missing
|
|
122
|
+
credentials and network failures directly beside the holdings. After connecting
|
|
123
|
+
discovery, use **Refresh holdings** to create a new analysis. **Refresh prices**
|
|
124
|
+
does not discover missing tokens. Existing workspace provider settings and
|
|
125
|
+
published snapshots are preserved.
|
|
126
|
+
|
|
120
127
|
## Build and contribute
|
|
121
128
|
|
|
122
129
|
```sh
|
package/docs/CLI.md
CHANGED
|
@@ -10,8 +10,9 @@ npm install -g kira-wallet
|
|
|
10
10
|
kira setup
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
The
|
|
14
|
-
are available from the
|
|
13
|
+
The operator published npm version 0.1.3 on 2026-10-06. GitHub v0.1.3 remains
|
|
14
|
+
a draft; the previous archive and checksum are available from the
|
|
15
|
+
[0.1.2 GitHub release](https://github.com/realproject7/kira-wallet/releases/tag/v0.1.2).
|
|
15
16
|
Registry publication remains an operator action. The code and original character assets use the MIT license.
|
|
16
17
|
|
|
17
18
|
## Lifecycle
|
|
@@ -33,7 +34,7 @@ read-only and control modes requires an explicit stop and restart. Starting
|
|
|
33
34
|
an already-running app in the same mode reuses its URL. The old `--controls`
|
|
34
35
|
option remains accepted for existing scripts but is no longer needed.
|
|
35
36
|
|
|
36
|
-
These startup defaults apply from 0.1.3. In the
|
|
37
|
+
These startup defaults apply from published 0.1.3. In the older 0.1.2 package, use
|
|
37
38
|
`kira setup` or `kira start --controls` for the full app.
|
|
38
39
|
|
|
39
40
|
The 0.1.2 GitHub release starts with a wallet checklist. Add a public EVM
|
|
@@ -41,6 +42,11 @@ address or use the browser wallet picker. Registration starts one explicit
|
|
|
41
42
|
research job; follow it in Activity and read the saved portfolio report.
|
|
42
43
|
No signature, seed phrase or private key is needed.
|
|
43
44
|
|
|
45
|
+
Candidate 0.1.4 explains limited discovery before wallet registration and beside
|
|
46
|
+
holdings. Missing coverage keeps the portfolio estimate partial. It is not yet
|
|
47
|
+
published to npm. Existing snapshots require a holdings refresh after connecting
|
|
48
|
+
an indexer; a price refresh does not discover missing tokens.
|
|
49
|
+
|
|
44
50
|
Public RPC checks known assets and Mint Club. For broader ERC20 discovery,
|
|
45
51
|
open Research connections, select Alchemy and name the private environment
|
|
46
52
|
variable holding its key. Never paste the key into the browser. `kira doctor`
|
|
@@ -50,8 +56,8 @@ prove complete coverage; inspect the recorded results and gaps.
|
|
|
50
56
|
Connecting Codex or Claude is optional. Use Connect your AI when ready, verify
|
|
51
57
|
a generic response and explicitly choose context permissions. Assistant answers
|
|
52
58
|
render a safe Markdown subset; raw HTML, images and links remain inert text.
|
|
53
|
-
|
|
54
|
-
0.1.0 package
|
|
59
|
+
The wallet-first setup is included in published npm 0.1.3. The historical
|
|
60
|
+
0.1.0 package uses the earlier account-first setup.
|
|
55
61
|
|
|
56
62
|
## Your model account
|
|
57
63
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Token discovery patch acceptance
|
|
2
|
+
|
|
3
|
+
Version: 0.1.4 candidate. Published npm 0.1.3 remains immutable.
|
|
4
|
+
|
|
5
|
+
## Problem and correction
|
|
6
|
+
|
|
7
|
+
A new personal data directory defaults to public RPC and no general ERC20
|
|
8
|
+
indexer. The installed app does not inherit discovery settings from a separate
|
|
9
|
+
research workspace. Public RPC reads native balances and enumerates Mint Club
|
|
10
|
+
assets, but cannot identify arbitrary ERC20 contracts for a wallet. Previously,
|
|
11
|
+
a finished analysis could show only native holdings without explaining this
|
|
12
|
+
configuration at the point of use.
|
|
13
|
+
|
|
14
|
+
The wallet registration dialog now explains limited discovery and offers a
|
|
15
|
+
connection action before research. The wallet overview explains disabled
|
|
16
|
+
discovery, missing credentials, stale analysis or incomplete provider checks.
|
|
17
|
+
It labels incomplete portfolio estimates as partial. Network details retain
|
|
18
|
+
separate discovery, RPC and registry coverage. Read-only and sample views do
|
|
19
|
+
not acquire research permissions.
|
|
20
|
+
|
|
21
|
+
RPC and candidate-balance failures now receive an explicit warning with
|
|
22
|
+
expanded affected-network details. Retry holdings starts the existing gated
|
|
23
|
+
research action; Review RPC settings opens the existing connection dialog.
|
|
24
|
+
The warning says to wait briefly before retrying and review the connection
|
|
25
|
+
or choose another provider if failures persist. Missing balances remain
|
|
26
|
+
unknown. Successful discovery alone cannot mark failed balance reads complete,
|
|
27
|
+
and old snapshots can project their fixed balance-failure note safely.
|
|
28
|
+
|
|
29
|
+
Connecting an indexer does not change an immutable saved analysis. Refresh
|
|
30
|
+
holdings runs discovery again. Refresh prices only updates existing holdings.
|
|
31
|
+
Discovery settings preserve existing explorer preferences, and the UI rereads
|
|
32
|
+
readiness after the settings job succeeds.
|
|
33
|
+
|
|
34
|
+
Registry diagnostics now retain up to five redacted error examples and numeric
|
|
35
|
+
HTTP or RPC codes in local evidence. The public model exposes only status and
|
|
36
|
+
counts. Oversized multicalls split at the same observed block, including errors
|
|
37
|
+
that viem converts into failed result rows. Transport outages and throttling
|
|
38
|
+
remain bounded instead of retrying every contract separately.
|
|
39
|
+
|
|
40
|
+
## Verification
|
|
41
|
+
|
|
42
|
+
- Required `npm test` suite passed: 152 Python tests, required Node behavior
|
|
43
|
+
checks and browser script syntax checks.
|
|
44
|
+
- `scripts/check-install.py` passed with a fresh 73-member package installation
|
|
45
|
+
and startup/shutdown lifecycle in a temporary synthetic workspace.
|
|
46
|
+
- The tracked-source privacy scan passed with the active private data directory
|
|
47
|
+
included. The full-history scan retains the previously recorded non-noreply
|
|
48
|
+
merge identity at `2ed52a2`; no new finding was introduced. That historical
|
|
49
|
+
identity is not reproduced here and public history was not rewritten.
|
|
50
|
+
- Actual viem with an in-memory aggregate3 transport: eight calls rejected
|
|
51
|
+
above two calls per batch succeed in seven requests, all at block 103.
|
|
52
|
+
Independent review also checked that HTTP 429 and 503 stay at one request
|
|
53
|
+
and isolated contract failures remain isolated. No live RPC is used by this
|
|
54
|
+
regression.
|
|
55
|
+
- Missing, unreadable and disabled discovery credentials produce explicit
|
|
56
|
+
coverage status without exposing credentials or attempting discovery.
|
|
57
|
+
- Viewer projection keeps raw provider diagnostics private and preserves
|
|
58
|
+
unknown prices. UI behavior checks cover disabled discovery, missing
|
|
59
|
+
credentials, stale analysis after connection, read-only actions and
|
|
60
|
+
partially successful explorer coverage.
|
|
61
|
+
- RPC recovery regressions cover unavailable chains, registry failures,
|
|
62
|
+
non-Mint candidate-balance failures, mixed missing connections, unknown
|
|
63
|
+
valuations, read-only controls and removal after a successful analysis.
|
|
64
|
+
- Chrome fixture checks at 1440 x 900 and 390 x 844: partial estimate and
|
|
65
|
+
discovery notice render without horizontal overflow. Both connection actions
|
|
66
|
+
open Research connections. The add-wallet dialog says Add with limited
|
|
67
|
+
discovery when the provider is absent. Fixture actions submitted no research,
|
|
68
|
+
account authorization or provider requests.
|
|
69
|
+
- RPC-failure fixtures at the same desktop/mobile sizes show expanded Base
|
|
70
|
+
and BNB Chain details, both recovery actions and a partial estimate without
|
|
71
|
+
horizontal overflow. Review RPC settings opens the connection dialog.
|
|
72
|
+
|
|
73
|
+
The design references were Mixpanel's dashboard connection guidance and
|
|
74
|
+
Databricks' inline data-check failure state, retrieved
|
|
75
|
+
with [Lazyweb](https://www.lazyweb.com/agentic-search/51280761-491a-4a3f-a78e-ed2dcd427ee8).
|
|
76
|
+
No operator wallet information or screenshots were submitted to that service.
|
|
77
|
+
|
|
78
|
+
Independent review found a viem failed-result handling defect during
|
|
79
|
+
implementation. The correction and actual-client regression were reviewed
|
|
80
|
+
again and the finding was closed. Screenshots and operator test evidence
|
|
81
|
+
remain in the ignored local ticket ledger.
|
|
82
|
+
|
|
83
|
+
## Acceptance limits and release boundary
|
|
84
|
+
|
|
85
|
+
Operator-approved reuse of an existing discovery connection recovered
|
|
86
|
+
non-native holdings in a new saved analysis. Some provider and network gaps
|
|
87
|
+
remain; this does not establish complete holdings or verify every expected
|
|
88
|
+
token. Those limits remain visible in coverage. The existing operator server
|
|
89
|
+
was not restarted or globally upgraded during patch verification.
|
|
90
|
+
|
|
91
|
+
The source/package privacy scan is required before preparation.
|
|
92
|
+
`scripts/prepare-release.py`
|
|
93
|
+
pins the clean source commit and archive checksum. Registry publication
|
|
94
|
+
remains operator-only. No npm publication, GitHub release publication or PR
|
|
95
|
+
merge is part of preparing this candidate.
|
package/docs/RELEASE.md
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
GitHub version releases and npm registry publication are separate. The GitHub
|
|
6
6
|
0.1.2 release provides the reviewed `kira-wallet-0.1.2.tgz`, its SHA-256 file and
|
|
7
7
|
release manifest. Its tag pins the source used to create those assets. Install
|
|
8
|
-
that archive from GitHub
|
|
8
|
+
that archive from GitHub. The operator published npm 0.1.3 on 2026-10-06;
|
|
9
|
+
its archive and installed bytes were verified separately. GitHub v0.1.3
|
|
10
|
+
remains a draft. GitHub and registry status must not be inferred from each other.
|
|
9
11
|
Version 0.1.2 was published to GitHub on 2026-10-05 from
|
|
10
12
|
`f994e99acb4e2d778fb4c95479f04355c88c00b3`. Its archive SHA-256 is
|
|
11
13
|
`866b7f1ad543bff1ff59d3bf840eca6313eb6e206e78b8611186e5a971153f28`.
|
|
@@ -21,7 +23,7 @@ their invented wallet addresses, balances and seeded chat are documented in
|
|
|
21
23
|
|
|
22
24
|
## npm publication preparation
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
Published **0.1.3** makes `kira start` open the full local
|
|
25
27
|
app by default. `--read-only` preserves the viewer-only mode, `--no-open`
|
|
26
28
|
supports terminal-only use, and `--controls` remains compatible with existing
|
|
27
29
|
scripts. A running instance never changes modes silently. Local session,
|
|
@@ -30,9 +32,22 @@ creation now identifies Open Wallet Standard with its official locally served
|
|
|
30
32
|
logo and explains encrypted local storage and public-account research. The
|
|
31
33
|
OWS integration and wallet-operation permissions are unchanged.
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
The next candidate is **0.1.4**. It makes incomplete token discovery visible
|
|
36
|
+
before registration and beside wallet holdings, separates disabled discovery
|
|
37
|
+
from missing credentials and provider failures, and offers a connection or
|
|
38
|
+
holdings-refresh action. Registry failures retain bounded redacted examples
|
|
39
|
+
and RPC error codes. Oversized multicalls split into smaller batches while
|
|
40
|
+
transport outages remain bounded. Existing explorer preferences are preserved
|
|
41
|
+
when discovery settings are saved. Connecting an indexer and refreshing
|
|
42
|
+
holdings are still required to recover tokens absent from an older snapshot.
|
|
43
|
+
RPC failures show affected networks, retry guidance and a settings action.
|
|
44
|
+
Failed candidate balance reads keep the saved result and valuation partial
|
|
45
|
+
even when general token discovery succeeded.
|
|
46
|
+
|
|
47
|
+
Version 0.1.4 requires independent review and operator registry publication.
|
|
48
|
+
It is not yet available from the registry. The showcase HTML animation is
|
|
49
|
+
deployed separately and requires no npm release. Keep published 0.1.2 and
|
|
50
|
+
0.1.3 package bytes immutable. See [discovery acceptance](KIRA_DISCOVERY_ACCEPTANCE.md).
|
|
36
51
|
|
|
37
52
|
The operator published npm **0.1.2** on 2026-10-05. Its registry archive was
|
|
38
53
|
downloaded and matched the reviewed GitHub archive above, including SHA-256,
|
|
@@ -54,8 +69,8 @@ The earlier unpublished 0.1.2 candidate is preserved in the ignored
|
|
|
54
69
|
`dist/preserved-0.1.2-bad8f02/` directory. Use the current `dist/release.json`
|
|
55
70
|
and its matching archive for publication; do not mix manifests from candidates.
|
|
56
71
|
|
|
57
|
-
Keep GitHub v0.1.1, its tag and its released archive immutable.
|
|
58
|
-
|
|
72
|
+
Keep GitHub v0.1.1, its tag and its released archive immutable. Versions 0.1.2
|
|
73
|
+
and 0.1.3 are also published and immutable. Do not rebuild them from later documentation
|
|
59
74
|
commits. Future package changes need a new version. Release preparation does not
|
|
60
75
|
publish a GitHub release, merge PR 19, promote the website or publish to npm.
|
|
61
76
|
Registry publication remains operator-only. The coding agent never publishes.
|
|
@@ -70,12 +85,12 @@ python3 scripts/prepare-release.py
|
|
|
70
85
|
```
|
|
71
86
|
|
|
72
87
|
`prepare-release.py` scans the package boundary and creates
|
|
73
|
-
`dist/kira-wallet-0.1.
|
|
88
|
+
`dist/kira-wallet-0.1.4.tgz`. The ignored `dist/release.json` records its source
|
|
74
89
|
commit, SHA-256 and member count. It never publishes. After reviewing that
|
|
75
90
|
manifest, the operator's final command from the project root is:
|
|
76
91
|
|
|
77
92
|
```sh
|
|
78
|
-
npm publish ./dist/kira-wallet-0.1.
|
|
93
|
+
npm publish ./dist/kira-wallet-0.1.4.tgz --access public
|
|
79
94
|
```
|
|
80
95
|
|
|
81
96
|
Use the operator's npm account and complete authentication or OTP personally.
|
package/kira_jobs.py
CHANGED
|
@@ -403,7 +403,7 @@ class JobStore:
|
|
|
403
403
|
previous=config['rpc']['chains'].get(key,{})
|
|
404
404
|
if row['url_env']==previous.get('url_env') and previous.get('alchemy_network'):row['alchemy_network']=previous['alchemy_network']
|
|
405
405
|
config['rpc']=updated
|
|
406
|
-
else:config['discovery']={**job['input'],'explorers':False}
|
|
406
|
+
else:config['discovery']={**job['input'],'explorers':config['discovery'].get('explorers',False)}
|
|
407
407
|
atomic(config_path(),config)
|
|
408
408
|
result={'status':'completed','setting':job['operation']};atomic(self.jobs/'results'/(job['job_id']+'.json'),result);return result
|
|
409
409
|
env = {**os.environ, 'KIRA_DATA_DIR': str(self.root), 'KIRA_JOB_ID': job['job_id'], 'KIRA_JOB_SNAPSHOT': job['checkpoint'], 'KIRA_ANALYSIS_FD': str(writer.fileno()), 'PYTHONUNBUFFERED': '1'}
|
package/onchain.cjs
CHANGED
|
@@ -52,7 +52,12 @@ function safeError(error){
|
|
|
52
52
|
message=message.replace(/https?:\/\/[^\s"<>]+/g,'[RPC URL]');
|
|
53
53
|
message=message.replace(/(bearer\s+)[^\s,;"<>]+/gi,'$1[redacted]');
|
|
54
54
|
message=message.replace(/(authorization|api[-_]?key|bearer)([\s:=]+)[^\s,;"<>]+/gi,'$1$2[redacted]');
|
|
55
|
-
|
|
55
|
+
const diagnostic={error_type:error.name,message:message.slice(0,500)};
|
|
56
|
+
for(let cause=error,depth=0;cause&&depth<6;cause=cause.cause,depth++){
|
|
57
|
+
if(Number.isInteger(cause.status))diagnostic.http_status=cause.status;
|
|
58
|
+
if(Number.isInteger(cause.code))diagnostic.rpc_code=cause.code;
|
|
59
|
+
}
|
|
60
|
+
return diagnostic;
|
|
56
61
|
}
|
|
57
62
|
function rpcEndpointsUsed(c){return [...(endpointReads.get(c)||[])].filter(i=>i>=0).sort((a,b)=>a-b);}
|
|
58
63
|
module.exports={viem,root,assets,networks,bondAbi,endpoints,client,connect,resilientTransport,rpcEndpointsUsed,serialize,safeError};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kira-wallet",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Manage your wallets with your own AI. Track tokens, research markets and refresh holdings in a local workspace.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -55,7 +55,8 @@
|
|
|
55
55
|
"kira_research_tools.py",
|
|
56
56
|
"kira_ows.py",
|
|
57
57
|
"ows_bridge.cjs",
|
|
58
|
-
"docs/KIRA_LIVE_QA.md"
|
|
58
|
+
"docs/KIRA_LIVE_QA.md",
|
|
59
|
+
"docs/KIRA_DISCOVERY_ACCEPTANCE.md"
|
|
59
60
|
],
|
|
60
61
|
"scripts": {
|
|
61
62
|
"test": "python3 -m unittest discover -s . -p 'test_*.py' && python3 -m unittest discover -s viewer -p 'test_*.py' && node test-onchain.cjs && node --check viewer/static/app.js && node --check viewer/static/jobs.js && node test-workspace.cjs && node --check viewer/static/workspace.js && node --check viewer/static/workspace-model.js && node test-watching.cjs && node test-watching-ui.cjs && node --check viewer/static/watching.js && node --check viewer/static/watching-model.js && node --check viewer/static/agent.js && node test-agent-ui.cjs && node test-markdown.cjs && node --check viewer/static/report.js && node test-report-ui.cjs && node test-holdings-ui.cjs && node test-rpc-fallback.cjs && node test-ows.cjs && node test-ows-ui.cjs && node --check viewer/static/ows.js && node --check viewer/static/chat-workspace.js && node --check viewer/static/selects.js && node --check site/demo.js"
|
package/pipeline-onchain.cjs
CHANGED
|
@@ -48,12 +48,28 @@ async function connect(n) {
|
|
|
48
48
|
}
|
|
49
49
|
throw error || new Error('No RPC endpoint configured; public fallback is disabled.');
|
|
50
50
|
}
|
|
51
|
+
function rpcFailure(error) {
|
|
52
|
+
let sizeLimit=false,transportFailure=false;
|
|
53
|
+
for(let cause=error,depth=0;cause&&depth<10;cause=cause.cause,depth++) {
|
|
54
|
+
if(['HttpRequestError','TimeoutError'].includes(cause.name))transportFailure=true;
|
|
55
|
+
if(cause.status===413||/\b(batch|payload|response|request body)\b.*\b(large|size|limit|exceed)/i.test(cause.shortMessage||cause.message||'')||/gas.*(cap|limit).*exceed|exceed.*gas.*(cap|limit)/i.test(cause.shortMessage||cause.message||''))sizeLimit=true;
|
|
56
|
+
if(cause.status===429)return {sizeLimit:false,transportFailure:true};
|
|
57
|
+
}
|
|
58
|
+
return {sizeLimit,transportFailure};
|
|
59
|
+
}
|
|
51
60
|
async function calls(c, contracts, block) {
|
|
52
61
|
if (!contracts.length) return [];
|
|
53
62
|
try {
|
|
54
|
-
|
|
63
|
+
const results=await c.multicall({contracts,blockNumber:block,batchSize:0,multicallAddress:MULTICALL});
|
|
64
|
+
// viem allowFailure also wraps an aggregate RPC failure as per-contract rows.
|
|
65
|
+
if(results.length&&results.every(r=>r.status==='failure')) {
|
|
66
|
+
const limited=results.find(r=>rpcFailure(r.error).sizeLimit);
|
|
67
|
+
if(limited)throw limited.error;
|
|
68
|
+
}
|
|
69
|
+
return results;
|
|
55
70
|
} catch (e) {
|
|
56
|
-
|
|
71
|
+
const {sizeLimit,transportFailure}=rpcFailure(e);
|
|
72
|
+
if (!sizeLimit&&transportFailure) return contracts.map(()=>({status:'failure',error:e}));
|
|
57
73
|
if (contracts.length > 1) {
|
|
58
74
|
const mid = Math.ceil(contracts.length/2);
|
|
59
75
|
return [...await calls(c,contracts.slice(0,mid),block), ...await calls(c,contracts.slice(mid),block)];
|
|
@@ -135,6 +151,7 @@ async function scanChain(input, n, output) {
|
|
|
135
151
|
cache_reused:reg.cache_reused,balance_errors:errors.filter(t=>t.registered_mintclub).length,
|
|
136
152
|
positive_balances:held.filter(t=>t.registered_mintclub).length,block_number:block,complete:reg.errors===0 && checked===reg.count,
|
|
137
153
|
source:'On-chain Mint Club bond tokenCount()/tokens(index), then direct wallet balance calls',bond_address:n.mintclub_bond_address};
|
|
154
|
+
row.registry_scan.error_examples=reg.entries.filter(t=>t.error||!t.address).slice(0,5).map(t=>({index:t.index,error:t.error||{message:'Registry address unavailable'}}));
|
|
138
155
|
row.balance_errors = errors;
|
|
139
156
|
for (const t of held) {
|
|
140
157
|
const item = {chain_id:n.chain_id,network:n.network,token_address:t.address,token_type:t.token_type,token_id:t.token_type==='ERC1155'?'0':null,
|
package/viewer/model.py
CHANGED
|
@@ -196,6 +196,11 @@ def project_wallet(entry, snapshot, root=ROOT, images=None):
|
|
|
196
196
|
'links':[{'label':'Explorer','url':EXPLORERS[c['chain_id']]+'/address/'+entry['address']}] if c['chain_id'] in EXPLORERS else []})
|
|
197
197
|
chains=[]
|
|
198
198
|
for c in coverage:
|
|
199
|
+
# Older snapshots retained this count only in a fixed pipeline note.
|
|
200
|
+
failed_balances=c.get('candidate_balance_errors')
|
|
201
|
+
if type(failed_balances) is not int or failed_balances<0:
|
|
202
|
+
failed_balances=max((int(m.group(1)) for note in c.get('notes',[]) or [] if isinstance(note,str)
|
|
203
|
+
and (m:=re.fullmatch(r'([0-9]{1,9}) candidate token balances could not be read\.',note))),default=0)
|
|
199
204
|
rows=[a for a in assets if a['chain_id']==c['chain_id']]
|
|
200
205
|
for a in rows:
|
|
201
206
|
a['environment']=c['environment']
|
|
@@ -204,6 +209,11 @@ def project_wallet(entry, snapshot, root=ROOT, images=None):
|
|
|
204
209
|
'image_url':chain_image(c['chain_id']),
|
|
205
210
|
'assets':len(rows),'value_usd':sum(a['value_usd'] for a in rows if a['value_usd'] is not None) if c['environment']=='mainnet' and any(a['value_usd'] is not None for a in rows) else None,
|
|
206
211
|
'complete':c['general_erc20_discovery']=='indexer_checked','rpc_available':c['rpc_status']=='available',
|
|
212
|
+
'discovery_status':c.get('discovery_status') if c.get('discovery_status') in ('checked','disabled','missing_credential','unsupported','provider_error') else 'incomplete',
|
|
213
|
+
'registry_complete':(c.get('mintclub_registry_scan') or {}).get('complete') is True,
|
|
214
|
+
'registry_errors':(c.get('mintclub_registry_scan') or {}).get('registry_errors',0),
|
|
215
|
+
'balance_errors':(c.get('mintclub_registry_scan') or {}).get('balance_errors',0),
|
|
216
|
+
'candidate_balance_errors':failed_balances,
|
|
207
217
|
'native_symbol':c.get('native_symbol'),
|
|
208
218
|
'native_balance':c.get('native_balance') if c['rpc_status']=='available' and number(c.get('native_balance')) is not None else None,
|
|
209
219
|
'native_observed_at':c.get('native_observed_at'),
|
package/viewer/static/app.js
CHANGED
|
@@ -129,19 +129,43 @@ function renderWallet(){
|
|
|
129
129
|
$('wallet-tags').innerHTML=w.tags.slice(1).map(t=>`<span class="tag">${escapeHTML(t)}</span>`).join(' ');
|
|
130
130
|
$('report-link').hidden=!w.report_url;if(w.report_url)$('report-link').href=safeURL(w.report_url);
|
|
131
131
|
$('total-value').textContent=w.analysed_at?money(w.known_value_usd):'—';
|
|
132
|
-
|
|
132
|
+
const partial=w.chains.some(c=>c.environment==='mainnet'&&KiraView.coverageGap(c));
|
|
133
|
+
$('valuation-note').textContent=w.analysed_at?(partial?'Partial portfolio · ':'')+(w.known_value_usd==null?(w.assets.length?'Value is unknown because prices are unavailable.':'No positive holdings recorded · unchecked networks remain unknown.'):'Market and curve spot estimates · excludes unpriced assets'):'Analysis will appear here when it is ready.';
|
|
133
134
|
$('asset-count').textContent=w.assets.length;$('unpriced-count').textContent=w.unpriced_count+' unpriced';
|
|
134
135
|
const mainnets=w.chains.filter(c=>c.environment==='mainnet');
|
|
135
136
|
$('chain-count').textContent=mainnets.filter(c=>c.assets>0).length;
|
|
136
137
|
const coverage=mainnets.filter(c=>c.complete).length;
|
|
137
|
-
$('coverage-note').textContent=coverage+' of '+mainnets.length+' checked
|
|
138
|
-
$('coverage-note').title=
|
|
138
|
+
$('coverage-note').textContent='Token discovery: '+coverage+' of '+mainnets.length+' mainnets checked';
|
|
139
|
+
$('coverage-note').title='Token discovery and on-chain balance checks are separate. See network coverage details for failures.';
|
|
139
140
|
$('sync-label').textContent=w.analysed_at?'Analysis '+stamp(w.analysed_at):'Awaiting analysis';
|
|
140
141
|
$('price-time').textContent=w.prices_at?'Prices '+stamp(w.prices_at):'';
|
|
141
|
-
const chainOptions=w.chains.filter(c=>c.assets>0
|
|
142
|
+
const chainOptions=w.chains.filter(c=>c.assets>0||KiraView.coverageGap(c));
|
|
142
143
|
if(selectedChain!=='all'&&!chainOptions.some(c=>String(c.id)===selectedChain))selectedChain='all';
|
|
143
|
-
$('chain-filter').innerHTML='<option value="all">All chains</option>'+chainOptions.map(c=>`<option value="${c.id}">${escapeHTML(c.name)}${c.environment==='testnet'?' · Testnet':''}${
|
|
144
|
-
$('chain-filter').value=selectedChain;renderHoldings();
|
|
144
|
+
$('chain-filter').innerHTML='<option value="all">All chains</option>'+chainOptions.map(c=>`<option value="${c.id}">${escapeHTML(c.name)}${c.environment==='testnet'?' · Testnet':''}${KiraView.coverageGap(c)?' · Incomplete':''}</option>`).join('');
|
|
145
|
+
$('chain-filter').value=selectedChain;renderHoldings();renderDiscoveryCoverage(w);
|
|
146
|
+
}
|
|
147
|
+
function renderDiscoveryCoverage(w) {
|
|
148
|
+
const readiness=typeof setupReadiness!=='undefined'?setupReadiness:null;
|
|
149
|
+
const notice=KiraView.discoveryNotice(w,readiness);
|
|
150
|
+
$('discovery-notice').hidden=!notice;
|
|
151
|
+
if(!notice)return;
|
|
152
|
+
$('discovery-notice-title').textContent=notice.title;
|
|
153
|
+
$('discovery-notice-text').textContent=notice.message;
|
|
154
|
+
const controls=typeof localSession!=='undefined'&&localSession?.controls===true&&state?.demo!==true;
|
|
155
|
+
$('discovery-settings').hidden=!controls||!(notice.action==='settings'||notice.settings);
|
|
156
|
+
$('discovery-settings').textContent=notice.settingsLabel||'Connect token discovery';
|
|
157
|
+
$('discovery-refresh').hidden=!controls||notice.action!=='refresh';
|
|
158
|
+
$('discovery-refresh').textContent=notice.refreshLabel||'Refresh holdings';
|
|
159
|
+
$('discovery-refresh').disabled=$('refresh-wallet').disabled;
|
|
160
|
+
$('discovery-notice').querySelector('details').open=notice.rpcIssues===true;
|
|
161
|
+
$('discovery-network-details').textContent=w.chains.filter(c=>c.environment==='mainnet'&&KiraView.coverageGap(c)).map(c=>{
|
|
162
|
+
const reasons=[];
|
|
163
|
+
if(!c.rpc_available)reasons.push('balance reads failed; retry holdings, then review RPC settings if this continues');
|
|
164
|
+
if(!c.complete)reasons.push(({disabled:'token discovery off',missing_credential:'indexer credential unavailable',unsupported:'indexer unsupported',provider_error:'indexer request failed'})[c.discovery_status]||'token discovery incomplete');
|
|
165
|
+
if(c.registry_complete===false&&c.rpc_available)reasons.push('Mint Club balances partly unchecked; retry holdings'+(c.registry_errors||c.balance_errors?' ('+(c.registry_errors||0)+' asset checks and '+(c.balance_errors||0)+' balance reads failed)':''));
|
|
166
|
+
if(c.candidate_balance_errors>0)reasons.push(c.candidate_balance_errors+' token balance reads failed; missing balances remain unknown');
|
|
167
|
+
return c.name+': '+reasons.join('; ');
|
|
168
|
+
}).join('\n');
|
|
145
169
|
}
|
|
146
170
|
function icon(asset){
|
|
147
171
|
if(typeof asset.symbol!=='string'||!asset.symbol)return ['', '?'];
|
package/viewer/static/index.html
CHANGED
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
<div id="wallet-actions" class="wallet-actions" hidden><button id="refresh-wallet" class="quiet-button" type="button"><svg class="ui-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M3 11a9 9 0 1 1 2.6 7M3 5v6h6"/></svg>Refresh holdings</button><button id="refresh-prices" class="quiet-button" type="button"><svg class="ui-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M4 17l5-5 4 3 7-10M15 5h5v5"/></svg>Refresh prices</button><button id="edit-tags" class="quiet-button" type="button"><svg class="ui-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M4 20l4-1L20 7l-3-3L5 16zM14 7l3 3"/></svg>Edit names</button><button id="view-history" class="quiet-button" type="button"><svg class="ui-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M5 4h14v16H5zM8 8h8M8 12h8M8 16h5"/></svg>Saved analyses</button></div>
|
|
51
51
|
<section class="wallet-heading"><div><div class="eyebrow">WALLET OVERVIEW</div><h1 id="wallet-name">Your wallets</h1><div class="wallet-address"><span id="address"></span><button id="copy-address" class="icon-button" aria-label="Copy wallet address" title="Copy wallet address"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="8" y="8" width="11" height="12" rx="2"/><path d="M15 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h3"/></svg></button><span id="wallet-tags"></span></div></div><a id="report-link" class="text-link" target="_blank" rel="noreferrer">Research notes <span aria-hidden="true">↗</span></a></section>
|
|
52
52
|
<section class="portfolio-summary" aria-label="Portfolio summary"><div class="valuation"><div class="summary-label">Estimated portfolio value</div><div id="total-value" class="total-value">—</div><div id="valuation-note" class="valuation-note">Loading the latest analysis</div></div><div class="summary-stat"><span class="summary-label">Assets</span><strong id="asset-count">—</strong><span id="unpriced-count" class="stat-note"></span></div><div class="summary-stat"><span class="summary-label">Networks</span><strong id="chain-count">—</strong><span id="coverage-note" class="stat-note"></span></div></section>
|
|
53
|
+
<section id="discovery-notice" class="discovery-notice" aria-labelledby="discovery-notice-title" hidden><strong id="discovery-notice-title"></strong><p id="discovery-notice-text" role="status"></p><div class="discovery-actions"><button id="discovery-refresh" class="quiet-button" type="button" hidden>Refresh holdings</button><button id="discovery-settings" class="quiet-button" type="button" hidden>Connect token discovery</button></div><details><summary>Network coverage details</summary><p id="discovery-network-details"></p></details></section>
|
|
53
54
|
<section class="holdings" aria-labelledby="holdings-title"><div class="holdings-toolbar"><div><h2 id="holdings-title">Holdings <span id="visible-count"></span></h2></div><div class="filters"><label class="chain-select"><span class="sr-only">Filter by chain</span><select id="chain-filter"><option value="all">All chains</option></select><svg viewBox="0 0 16 16" aria-hidden="true"><path d="m4 6 4 4 4-4"/></svg></label><div class="segment" role="group" aria-label="Minimum token value"><button data-min="0" aria-pressed="true">All</button><button data-min="5" aria-pressed="false">≥ $5</button><button data-min="10" aria-pressed="false">≥ $10</button></div></div></div>
|
|
54
55
|
<div id="holdings-filter-note" class="holdings-filter-note" role="status" hidden></div><div id="asset-groups"></div>
|
|
55
56
|
<div id="empty" class="empty" hidden><div class="empty-mark" aria-hidden="true">∅</div><h3>No assets in this view</h3><p>Choose another chain or lower the minimum value.</p></div>
|
|
@@ -120,7 +121,8 @@
|
|
|
120
121
|
<label>Wallet name<input id="new-wallet-tag" required maxlength="200" placeholder="My research wallet" autocomplete="off"></label>
|
|
121
122
|
<section id="wallet-preview" class="wallet-preview" aria-label="Wallet registration preview" hidden><h3>Ready to watch</h3><dl><dt>Public address</dt><dd id="wallet-preview-address"></dd><dt>Exact wallet name</dt><dd id="wallet-preview-name"></dd></dl></section>
|
|
122
123
|
<p id="wallet-existing" class="wallet-existing" hidden>This Watching wallet is already saved. <a id="wallet-existing-link">Open the saved wallet</a> to keep its current names.</p>
|
|
123
|
-
<
|
|
124
|
+
<section id="wallet-discovery-warning" class="discovery-notice" aria-label="Token discovery connection"><strong id="wallet-discovery-warning-title">Checking token discovery</strong><p id="wallet-discovery-warning-text">An indexer is needed to find other ERC20 holdings. Public RPC alone checks native balances and Mint Club assets.</p><button id="wallet-discovery-settings" class="quiet-button" type="button">Connect token discovery</button></section>
|
|
125
|
+
<p class="form-note">Research can consume provider allowance. No signature is requested.</p>
|
|
124
126
|
<button id="wallet-register" class="primary-button" type="submit" disabled>Add and research</button>
|
|
125
127
|
</div>
|
|
126
128
|
<section id="wallet-submission" class="wallet-preview" aria-label="Submitted wallet request" hidden><h3 id="wallet-submission-title" role="status"></h3><dl><dt>Submitted public address</dt><dd id="wallet-submission-address"></dd><dt>Exact submitted name</dt><dd id="wallet-submission-name"></dd></dl><p id="wallet-submission-note" class="form-note"></p><div class="wallet-submission-actions"><button type="button" id="wallet-submission-activity" class="quiet-button">View Activity</button><button type="button" id="wallet-choose-again" class="quiet-button" disabled>Choose wallet again</button></div></section>
|
package/viewer/static/jobs.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
let localSession=null, jobList=[], jobsSignature='', localPolling=false;
|
|
3
|
+
let discoverySettingsRevision='';
|
|
3
4
|
const pendingActions=new Set();
|
|
4
5
|
const jobLabels={'wallet.add':'Wallet research','wallet.refresh':'Holdings refresh','prices.refresh':'Price refresh','wallet.setTags':'Wallet names','settings.rpc':'RPC settings','settings.discovery':'Token discovery'};
|
|
5
6
|
const stageLabels={queued:'Waiting to start',starting:'Opening saved evidence',registered:'Wallet registered',discovery:'Token discovery',chain:'Checking on-chain holdings',dex_discovery:'Checking markets',token_images:'Preparing token artwork',published:'Evidence saved',failed:'Research stopped',stopped:'Research stopped',interrupted:'Ready for recovery'};
|
|
@@ -28,6 +29,7 @@ function refreshControlState(){
|
|
|
28
29
|
$(id).disabled=(sample&&id.startsWith('refresh'))||active||(id==='refresh-prices'&&!currentWallet()?.analysed_at);
|
|
29
30
|
$(id).title=sample?'Sample research cannot contact providers.':active?'A research job is already active for this wallet.':'';
|
|
30
31
|
}
|
|
32
|
+
$('discovery-refresh').disabled=$('refresh-wallet').disabled;
|
|
31
33
|
if(typeof renderWatching==='function')renderWatching();
|
|
32
34
|
}
|
|
33
35
|
let jobsPage=1, jobsConnected=true, lastJobsPoll=null;
|
|
@@ -74,13 +76,15 @@ async function pollJobs(){
|
|
|
74
76
|
if(!localSession){const response=await fetch('/api/session',{cache:'no-store',signal:AbortSignal.timeout(5000)});if(!response.ok)return;localSession=await response.json();}
|
|
75
77
|
refreshControlState();if(!localSession.controls){renderJobs();return;}
|
|
76
78
|
const jobs=await localAPI('/api/jobs');jobList=jobs;const reconnected=!jobsConnected;jobsConnected=true;lastJobsPoll=new Date().toISOString();
|
|
79
|
+
const setting=jobs.filter(j=>j.operation==='settings.discovery'&&j.state==='succeeded').map(j=>j.job_id+':'+j.updated_at).sort().join(',');
|
|
80
|
+
if(setting!==discoverySettingsRevision){discoverySettingsRevision=setting;if(typeof loadSetupReadiness==='function')await loadSetupReadiness();}
|
|
77
81
|
const signature=JSON.stringify(jobs)+JSON.stringify(jobs.map(j=>KiraView.jobPresentation(j).quiet));if(signature!==jobsSignature||reconnected){jobsSignature=signature;renderJobs();}else{$('jobs-freshness').textContent='Checked '+stamp(lastJobsPoll);}
|
|
78
82
|
refreshControlState();
|
|
79
83
|
}catch{jobsConnected=false;renderJobs();}
|
|
80
84
|
finally{localPolling=false;}
|
|
81
85
|
}
|
|
82
86
|
for(const button of document.querySelectorAll('[data-close-dialog]'))button.addEventListener('click',()=>button.closest('dialog').close());
|
|
83
|
-
function openAdd(){if(!localSession?.controls)return;if(!watchingSubmission)$('wallet-form-error').textContent='';if(typeof renderWatching==='function')renderWatching();$('wallet-dialog').showModal();}
|
|
87
|
+
function openAdd(){if(!localSession?.controls)return;if(!watchingSubmission)$('wallet-form-error').textContent='';if(typeof renderWatching==='function')renderWatching();$('wallet-dialog').showModal();if(typeof loadSetupReadiness==='function')loadSetupReadiness();}
|
|
84
88
|
$('open-wallet').addEventListener('click',openAdd);$('empty-add-wallet').addEventListener('click',openAdd);
|
|
85
89
|
function formAction(form,error,action){
|
|
86
90
|
form.addEventListener('submit',async e=>{e.preventDefault();error.textContent='';const buttons=[...form.querySelectorAll('button[type="submit"]')];if(buttons.some(b=>b.disabled))return;buttons.forEach(b=>b.disabled=true);
|
|
@@ -104,7 +108,10 @@ $('refresh-prices').addEventListener('click',async()=>{try{await submitOperation
|
|
|
104
108
|
$('edit-tags').addEventListener('click',()=>{if(!currentWallet())return;$('tags-dialog').dataset.wallet=selectedWallet;$('tag-values').value=currentWallet().tags.join('\n');$('tags-error').textContent='';$('tags-dialog').showModal();});
|
|
105
109
|
formAction($('tags-form'),$('tags-error'),async()=>{await submitOperation('wallet.setTags',{wallet:$('tags-dialog').dataset.wallet,tags:$('tag-values').value.split('\n').filter(t=>t.length)});$('tags-dialog').close();});
|
|
106
110
|
$('jobs-list').addEventListener('click',async event=>{const button=event.target.closest('[data-job-action]');if(!button)return;button.disabled=true;try{await submitOperation('job.'+button.dataset.jobAction,{job_id:button.dataset.jobId});}catch(error){button.disabled=false;toast(error.message);}});
|
|
107
|
-
$('open-settings').addEventListener('click',async()=>{try{const config=await localAPI('/api/settings');$('rpc-mode').value=config.rpc.mode;$('rpc-custom-only').checked=!config.rpc.allow_public_fallback;$('rpc-references').value=Object.entries(config.rpc.chains).map(([chain,row])=>chain+' '+row.url_env).join('\n');$('discovery-provider').value=config.discovery.provider;$('discovery-key').value=config.discovery.key_env;$('settings-error').textContent='';if(typeof KiraSelect!=='undefined')KiraSelect.refresh();$('settings-dialog').showModal();}catch(error){toast(error.message);}});
|
|
111
|
+
$('open-settings').addEventListener('click',async()=>{try{const config=await localAPI('/api/settings');$('rpc-mode').value=config.rpc.mode;$('rpc-custom-only').checked=!config.rpc.allow_public_fallback;$('rpc-references').value=Object.entries(config.rpc.chains).map(([chain,row])=>chain+' '+row.url_env).join('\n');$('discovery-provider').value=config.discovery.provider;$('discovery-key').value=config.discovery.key_env;$('settings-error').textContent='';if(typeof KiraSelect!=='undefined')KiraSelect.refresh();$('settings-dialog').showModal();if(typeof loadSetupReadiness==='function')loadSetupReadiness();}catch(error){toast(error.message);}});
|
|
112
|
+
$('discovery-settings').addEventListener('click',()=>$('open-settings').click());
|
|
113
|
+
$('wallet-discovery-settings').addEventListener('click',()=>{$('wallet-dialog').close();$('open-settings').click();});
|
|
114
|
+
$('discovery-refresh').addEventListener('click',()=>$('refresh-wallet').click());
|
|
108
115
|
formAction($('rpc-form'),$('settings-error'),async()=>{const lines=$('rpc-references').value.split('\n').filter(s=>s.trim());const chains=lines.map(line=>{const parts=line.trim().split(/\s+/);if(parts.length!==2)throw new Error('Enter a chain ID and environment variable name on each line.');return {chain_id:Number(parts[0]),url_env:parts[1]};});await submitOperation('settings.rpc',{mode:$('rpc-mode').value,allow_public_fallback:!$('rpc-custom-only').checked,chains});});
|
|
109
116
|
formAction($('discovery-form'),$('settings-error'),async()=>{await submitOperation('settings.discovery',{provider:$('discovery-provider').value,key_env:$('discovery-key').value});if(typeof loadSetupReadiness==='function')await loadSetupReadiness();});
|
|
110
117
|
$('view-history').addEventListener('click',async()=>{try{const rows=await localAPI('/api/snapshots?wallet='+encodeURIComponent(selectedWallet));$('history-list').innerHTML=rows.map(row=>`<article class="history-row"><strong>${stamp(row.compiled_at)}</strong><span>${escapeHTML(row.status==='completed'?'Saved':'Saved with coverage gaps')}</span><small>${escapeHTML(row.snapshot_id)}</small></article>`).join('')||'<p>No analysis has been published yet.</p>';$('compare-form').hidden=rows.length<2;for(const id of ['compare-before','compare-after'])$(id).innerHTML=rows.map(row=>`<option value="${escapeHTML(row.snapshot_id)}">${stamp(row.compiled_at)} · ${escapeHTML(row.snapshot_id.split('/').at(-1))}</option>`).join('');$('compare-after').selectedIndex=Math.max(0,rows.length-1);$('comparison-result').textContent='';$('history-error').textContent='';$('history-dialog').showModal();}catch(error){toast(error.message);}});
|
package/viewer/static/report.js
CHANGED
|
@@ -15,7 +15,7 @@ function renderSetupPath() {
|
|
|
15
15
|
const discovery=setupReadiness?.discovery_key_available===true,configured=setupReadiness?.discovery_configured===true,ai=typeof agentState!=='undefined'&&!!agentState?.config;
|
|
16
16
|
$('setup-path').hidden=state.demo||!localSession?.controls||(wallet&&analysed&&discovery&&ai);
|
|
17
17
|
$('home-empty').hidden=wallet||!$('setup-path').hidden;
|
|
18
|
-
$('setup-path').innerHTML='<details '+(!wallet?'open':'')+'><summary>'+ (wallet?'Finish your setup':'Set up your first wallet')+'</summary><div class="section-heading"><h2>'+ (wallet?'Finish your setup':'Start with one wallet')+'</h2><span class="section-note">Your keys stay yours</span></div><ol class="setup-checklist">'+
|
|
18
|
+
$('setup-path').innerHTML='<details '+(!wallet||!discovery?'open':'')+'><summary>'+ (wallet?'Finish your setup':'Set up your first wallet')+'</summary><div class="section-heading"><h2>'+ (wallet?'Finish your setup':'Start with one wallet')+'</h2><span class="section-note">Your keys stay yours</span></div><ol class="setup-checklist">'+
|
|
19
19
|
'<li><span class="setup-check">'+(wallet?'✓':'1')+'</span><div><strong>Add a public wallet</strong><p>'+(wallet?'Saved locally.':'Paste an EVM address or choose a browser wallet. No signature or seed phrase.')+'</p></div><button class="quiet-button" id="setup-wallet" type="button">'+(wallet?'Add another':'Add wallet')+'</button></li>'+
|
|
20
20
|
'<li><span class="setup-check">'+(discovery?'✓':'2')+'</span><div><strong>Connect token discovery</strong><p>'+(!setupReadiness?(setupUnavailable?'Could not check the connection. Retrying…':'Checking the discovery connection…'):discovery?'Indexer key detected. Actual chain coverage is confirmed by research results.':configured?'Indexer selected, but its key is unavailable. Use a private environment reference.':'Public RPC checks known assets. Connect an indexer for broader ERC20 discovery, or continue with limited coverage.')+'</p></div><button class="quiet-button" id="setup-discovery" type="button">Review connection</button></li>'+
|
|
21
21
|
'<li><span class="setup-check">'+(analysed?'✓':'3')+'</span><div><strong>Read your first report</strong><p>'+(analysed?'Saved balances, prices and coverage are ready.':'Adding a wallet starts one explicit research job. Follow its progress in Activity; gaps are shown in the report.')+'</p></div><a class="quiet-button" href="#/activity">View Activity</a></li>'+
|
|
@@ -26,7 +26,7 @@ function renderSetupPath() {
|
|
|
26
26
|
}
|
|
27
27
|
async function loadSetupReadiness(){
|
|
28
28
|
if(setupLoading||!localSession?.controls)return false;setupLoading=true;
|
|
29
|
-
try{setupReadiness=await localAPI('/api/onboarding');setupUnavailable=false;setupRetryAfter=0;renderSetupPath();return true;}catch{setupReadiness=null;setupUnavailable=true;setupRetryAfter=Date.now()+5000;renderSetupPath();return false;}finally{setupLoading=false;}
|
|
29
|
+
try{setupReadiness=await localAPI('/api/onboarding');setupUnavailable=false;setupRetryAfter=0;renderSetupPath();if(typeof renderWatching==='function')renderWatching();if(typeof currentWallet==='function'&¤tWallet())renderDiscoveryCoverage(currentWallet());return true;}catch{setupReadiness=null;setupUnavailable=true;setupRetryAfter=Date.now()+5000;renderSetupPath();return false;}finally{setupLoading=false;}
|
|
30
30
|
}
|
|
31
31
|
window.addEventListener('hashchange',()=>{if(selectedView==='home')renderHomeTokens();});
|
|
32
32
|
const setupStartup=setInterval(async()=>{if(state&&localSession?.controls&&!setupReadiness&&Date.now()>=setupRetryAfter){await loadSetupReadiness();renderHomeTokens();}},1000);
|
package/viewer/static/style.css
CHANGED
|
@@ -49,3 +49,5 @@ body{background:#fcfbf8;color:#292733}main{background:#fcfbf8}.sidebar{backgroun
|
|
|
49
49
|
.brand-logo,.nav-logo,.note-logo{object-fit:contain;flex-shrink:0}.brand-logo{width:46px;height:46px}.nav-logo{width:28px;height:28px}.note-logo{width:42px;height:42px}.research-controls{display:flex;align-items:center;justify-content:space-between;gap:16px;margin:0 0 26px}.research-controls p{font-size:12px;color:#686174;margin:6px 0 0}.control-buttons,.wallet-actions{display:flex;flex-wrap:wrap;gap:9px}.wallet-actions{justify-content:flex-end;margin:0 0 22px}.primary-button,.quiet-button{font:inherit;font-size:12px;font-weight:600;min-height:40px;padding:10px 15px;border-radius:10px;cursor:pointer}.primary-button{background:#6755CE;color:white;border:1px solid #6755CE}.quiet-button{background:#FCFBF8;color:#51465F;border:1px solid #DED7E7}.primary-button:hover{background:#5543B8}.quiet-button:hover{background:#F0EDF9}button:disabled{opacity:.55;cursor:default}.research-controls[hidden],.research-jobs[hidden],.wallet-actions[hidden]{display:none}.research-jobs{background:#fff;border:1px solid #E6DFEB;border-radius:18px;padding:20px 23px;margin-bottom:26px}.research-jobs .section-heading{margin-bottom:7px}.job-row{display:flex;gap:18px;justify-content:space-between;align-items:center;padding:15px 0;border-top:1px solid #EFEAF2}.job-identity{min-width:0;font-size:12px}.job-identity strong{font-weight:600}.job-identity a{margin-left:8px;color:#6755CE;font-size:11px}.job-identity p{margin:7px 0;color:#686174;font-size:12px}.job-identity small{color:#81778D;font-size:10px}.job-state{display:inline-block;font-size:10px;padding:4px 7px;margin-right:9px;border-radius:6px;background:#F0EDF9;color:#6755CE;text-transform:capitalize}.job-state.partial,.job-state.failed,.job-state.interrupted{background:#FAEFE0;color:#775529}.job-state.succeeded{background:#EDF4EF;color:#366047}.job-identity .job-error{color:#8B513D;line-height:1.6}.job-actions{flex-shrink:0}.research-dialog{width:min(490px,calc(100% - 32px));max-height:85dvh;padding:28px;border:1px solid #E2DCE8;border-radius:22px;background:#FCFBF8;color:#292536;box-shadow:0 25px 90px #29253625;overflow:auto}.research-dialog::backdrop{background:#29253666}.dialog-heading{display:flex;align-items:center;gap:12px;margin-bottom:16px}.dialog-heading h2{font-size:19px;line-height:1.35;margin:0;flex:1}.dialog-heading button{border:0;background:none;color:#686174;font-size:23px;min-width:36px;min-height:36px;cursor:pointer}.research-dialog p{font-size:13px;line-height:1.7;color:#686174}.research-dialog label{display:block;font-size:12px;font-weight:600;margin:18px 0 10px}.research-dialog input:not([type=checkbox]),.research-dialog textarea,.research-dialog select{display:block;margin-top:9px;padding:12px;border:1px solid #DAD3E3;border-radius:9px;background:white;color:#292536;width:100%;font:inherit;line-height:1.5}.research-dialog textarea{resize:vertical}.research-dialog .form-note{font-size:11px}.research-dialog .form-error{color:#9B4437;min-height:0}.research-dialog .form-error:empty{display:none}.research-dialog form+form,.assistant-gate{border-top:1px solid #E2DCE8;margin-top:23px;padding-top:17px}.research-dialog .checkbox-label{display:flex;gap:10px;align-items:center;font-weight:400}.checkbox-label input{width:17px;height:17px;margin:0}.assistant-gate strong{font-size:12px}.assistant-gate p{font-size:11px}.history-dialog{width:min(720px,calc(100% - 32px))}.history-row{display:grid;gap:7px;padding:15px 0;border-top:1px solid #E2DCE8;font-size:12px}.history-row small{word-break:break-all;color:#81778D}.comparison-result{margin-top:24px}.comparison-result table{width:100%;font-size:12px;text-align:left;border-collapse:collapse}.comparison-result td,.comparison-result th{padding:10px 12px;border-bottom:1px solid #E2DCE8}.comparison-result small{display:block;color:#81778D;font-size:10px;margin-top:5px}.comparison-result td:not(:first-child){font-variant-numeric:tabular-nums}.empty .primary-button{margin-top:15px}.research-dialog :focus-visible,.research-controls :focus-visible,.wallet-actions :focus-visible,.job-actions :focus-visible{outline:3px solid #AFA1E7;outline-offset:3px}
|
|
50
50
|
@media(max-width:700px){.brand-logo{width:36px;height:36px}.nav-logo{width:23px;height:23px}.research-controls{align-items:flex-start;flex-direction:column;gap:13px}.research-controls .eyebrow{font-size:9px}.control-buttons{width:100%}.control-buttons button{flex:1}.wallet-actions{justify-content:flex-start}.wallet-actions button{flex:1;white-space:nowrap}.research-jobs{padding:17px}.job-row{align-items:flex-start;gap:8px}.job-identity a{display:block;margin:7px 0}.job-actions .quiet-button{font-size:10px;padding:9px}.research-dialog{padding:21px}.dialog-heading h2{font-size:17px}.note-logo{width:34px;height:34px}.research-controls p{line-height:1.6}.comparison-result .table-wrap{overflow-x:auto}.comparison-result table{min-width:540px}}
|
|
51
51
|
.job-stages{margin:11px 0;font-size:11px;color:#686174}.job-stages summary{cursor:pointer;color:#6755CE}.job-stages ol{padding-left:18px}.job-stages li{padding:5px 0}.job-stages li span{display:block;font-size:10px;line-height:1.7;word-break:break-word}
|
|
52
|
+
|
|
53
|
+
.discovery-notice{margin:22px 0;padding:20px 22px;border:1px solid #e8d8b4;border-radius:12px;background:#fffaf0;color:#534426;line-height:1.6}.discovery-notice>strong{display:block;font-size:14px;font-weight:600}.discovery-notice p{margin:7px 0 0;font-size:13px}.discovery-actions{display:flex;gap:10px;margin-top:14px}.discovery-notice>.quiet-button{margin-top:12px}.discovery-notice details{margin-top:13px;font-size:12px}.discovery-notice summary{cursor:pointer;font-weight:500}.discovery-notice details p{white-space:pre-line;overflow-wrap:anywhere;font-size:12px}.discovery-notice .quiet-button{background:#fff;border-color:#e8d8b4}.discovery-notice [hidden]{display:none!important}@media(max-width:620px){.discovery-notice{padding:16px}.discovery-actions{flex-wrap:wrap}}
|
|
@@ -125,7 +125,11 @@ function renderWatching() {
|
|
|
125
125
|
$('wallet-existing').hidden = !valid || !existing;
|
|
126
126
|
$('wallet-existing-link').href = '#/wallet/'+address.toLowerCase();
|
|
127
127
|
$('wallet-register').disabled = !!submission || !permitted || !valid || !!existing;
|
|
128
|
-
|
|
128
|
+
const discovery=typeof setupReadiness!=='undefined'?setupReadiness:null,ready=discovery?.discovery_key_available===true;
|
|
129
|
+
$('wallet-discovery-warning').hidden=ready||!!submission||state?.demo===true;
|
|
130
|
+
$('wallet-discovery-warning-title').textContent=!discovery?'Token discovery is not verified':discovery.discovery_configured?'Token discovery needs a connection':'Token discovery is off';
|
|
131
|
+
$('wallet-discovery-warning-text').textContent=discovery?.discovery_configured?'The selected indexer credential is unavailable. Connect it before researching, or continue with native and Mint Club assets only.':'Other ERC20 holdings may be missing. Connect an indexer before researching, or continue with native and Mint Club assets only.';
|
|
132
|
+
$('wallet-register').textContent = watchingSubmitting ? 'Saving wallet…' : ready||state?.demo?'Add and research':'Add with limited discovery';
|
|
129
133
|
if (browser && $('wallet-dialog').open && watchingLastStatus === 'requesting' && current.status === 'accounts')
|
|
130
134
|
$('wallet-accounts').querySelector('button')?.focus();
|
|
131
135
|
watchingLastStatus = current.status;
|
|
@@ -17,6 +17,27 @@
|
|
|
17
17
|
return { rows: rows.slice((page - 1) * pageSize, page * pageSize), count: rows.length, page, pages };
|
|
18
18
|
}
|
|
19
19
|
const active = job => ['queued', 'running'].includes(job.state);
|
|
20
|
+
const rpcGap = chain => chain.rpc_available===false||chain.registry_complete===false||chain.candidate_balance_errors>0;
|
|
21
|
+
const coverageGap = chain => !chain.complete||rpcGap(chain);
|
|
22
|
+
function discoveryNotice(wallet, readiness = null) {
|
|
23
|
+
const chains=(wallet?.chains||[]).filter(c=>c.environment==='mainnet');
|
|
24
|
+
const gaps=chains.filter(coverageGap);
|
|
25
|
+
if(!wallet?.analysed_at||!gaps.length)return null;
|
|
26
|
+
const missing=chains.some(c=>!c.complete);
|
|
27
|
+
const disabled=(readiness?.discovery_provider==='none'&&chains.every(c=>!c.complete))||(!readiness&&chains.every(c=>c.discovery_status==='disabled'));
|
|
28
|
+
const noKey=readiness?.discovery_configured&&!readiness.discovery_key_available;
|
|
29
|
+
const rpc=chains.filter(rpcGap);
|
|
30
|
+
if(rpc.length){
|
|
31
|
+
const connection=missing&&(disabled||noKey);
|
|
32
|
+
return {title:'Some balances could not be checked',
|
|
33
|
+
message:'On-chain checks did not finish on '+rpc.length+(rpc.length===1?' network.':' networks.')+' Missing balances are unknown, so this portfolio is partial. Wait a moment, then retry holdings. If errors continue, review RPC settings or use a different provider.'+(connection?' Token discovery also needs a connection to find other ERC20 holdings.':''),
|
|
34
|
+
action:'refresh',settings:true,rpcIssues:true,refreshLabel:'Retry holdings',settingsLabel:connection?'Review connections':'Review RPC settings'};
|
|
35
|
+
}
|
|
36
|
+
if(missing&&disabled)return {title:'Token discovery is off',message:'This analysis checks native balances and Mint Club assets only. Other ERC20 holdings may be missing. Connect token discovery, then refresh holdings.',action:'settings'};
|
|
37
|
+
if(missing&&noKey)return {title:'Token discovery needs a connection',message:'The selected indexer credential is unavailable. Review its connection, then refresh holdings. This portfolio is partial.',action:'settings'};
|
|
38
|
+
if(missing&&readiness?.discovery_key_available&&chains.some(c=>['disabled','missing_credential'].includes(c.discovery_status)))return {title:'Refresh holdings to discover other tokens',message:'Your discovery connection is now available. This saved analysis predates it and remains partial until you refresh holdings.',action:'refresh'};
|
|
39
|
+
return {title:'Your portfolio is partial',message:'Some token discovery or on-chain checks did not complete. Missing holdings remain unknown. Review the network details below before refreshing holdings.',action:readiness?.discovery_key_available?'refresh':'settings'};
|
|
40
|
+
}
|
|
20
41
|
function jobPresentation(job, now = Date.now(), connected = true) {
|
|
21
42
|
const labels = { queued: 'Queued', running: 'Running', succeeded: 'Completed', partial: 'Saved with gaps', interrupted: 'Interrupted', failed: 'Failed', cancelled: 'Stopped' };
|
|
22
43
|
const start = job.state === 'queued' ? job.updated_at || job.created_at : job.started_at || job.created_at;
|
|
@@ -37,7 +58,7 @@
|
|
|
37
58
|
const h = Math.floor(seconds / 3600), m = Math.floor(seconds % 3600 / 60), s = seconds % 60;
|
|
38
59
|
return (h ? h + 'h ' : '') + m + 'm ' + s + 's';
|
|
39
60
|
}
|
|
40
|
-
const api = { catalog, pageSize, active, jobPresentation, duration };
|
|
61
|
+
const api = { catalog, pageSize, active, jobPresentation, duration, discoveryNotice, coverageGap };
|
|
41
62
|
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
|
42
63
|
else root.KiraView = api;
|
|
43
64
|
})(typeof globalThis !== 'undefined' ? globalThis : this);
|
package/wallet.py
CHANGED
|
@@ -41,7 +41,9 @@ def atomic(path, data):
|
|
|
41
41
|
temp = path.with_suffix(path.suffix+'.tmp')
|
|
42
42
|
text = data if isinstance(data, str) else json.dumps(data, indent=2, ensure_ascii=False)+'\n'
|
|
43
43
|
# Providers occasionally include credentialed URLs in an error response.
|
|
44
|
-
|
|
44
|
+
try:values=h.secrets().values()
|
|
45
|
+
except ValueError:values=[]
|
|
46
|
+
for secret in values:
|
|
45
47
|
if secret:
|
|
46
48
|
text=text.replace(secret,'[redacted]')
|
|
47
49
|
temp.write_text(text)
|
|
@@ -92,9 +94,10 @@ def discover(wallet, n, folder):
|
|
|
92
94
|
cached=read(evidence)
|
|
93
95
|
if cached['wallet'].lower()==wallet.lower() and cached['complete']:
|
|
94
96
|
return cached
|
|
95
|
-
row = {'wallet':wallet,'chain_id':cid,'observed_at':h.now(),'complete':False,'tokens':[],'native':[], 'pages':[],'source':None}
|
|
97
|
+
row = {'wallet':wallet,'chain_id':cid,'observed_at':h.now(),'complete':False,'tokens':[],'native':[], 'pages':[],'source':None,'discovery_status':'incomplete'}
|
|
96
98
|
cfg=load_config()
|
|
97
|
-
key=h.secrets().get('ALCHEMY_CUSTOM_APY_KEY') if cfg['discovery']['provider']=='alchemy' else None
|
|
99
|
+
try:key=h.secrets().get('ALCHEMY_CUSTOM_APY_KEY') if cfg['discovery']['provider']=='alchemy' else None
|
|
100
|
+
except ValueError:key=None
|
|
98
101
|
if cid in ALCHEMY and key:
|
|
99
102
|
row['source']='Alchemy Portfolio Tokens By Wallet'
|
|
100
103
|
network=ALCHEMY[cid]
|
|
@@ -147,7 +150,14 @@ def discover(wallet, n, folder):
|
|
|
147
150
|
else:
|
|
148
151
|
row['error']=response
|
|
149
152
|
else:
|
|
150
|
-
|
|
153
|
+
status='disabled' if cfg['discovery']['provider']=='none' else 'missing_credential' if cid in ALCHEMY and not key else 'unsupported'
|
|
154
|
+
row['discovery_status']=status
|
|
155
|
+
messages={'disabled':'Token discovery is off. Configure an indexer to discover other ERC20 holdings.',
|
|
156
|
+
'missing_credential':'The selected indexer credential is unavailable. Configure its private environment reference, then refresh holdings.',
|
|
157
|
+
'unsupported':'No general ERC20 indexer is available for this network.'}
|
|
158
|
+
row['error']={'message':messages[status]+' Mint Club registry and native balances are queried independently.'}
|
|
159
|
+
if row['complete']:row['discovery_status']='checked'
|
|
160
|
+
elif row['source']:row['discovery_status']='provider_error'
|
|
151
161
|
row['tokens']=list({t['address'].lower():t for t in row['tokens']}.values())
|
|
152
162
|
atomic(evidence,row)
|
|
153
163
|
return row
|
|
@@ -228,6 +238,8 @@ def finish(wallet, folder, networks, discovered):
|
|
|
228
238
|
'rpc_endpoint_indices':c.get('rpc_endpoint_indices',[]),
|
|
229
239
|
'general_erc20_discovery':'indexer_checked' if d['complete'] else 'incomplete',
|
|
230
240
|
'indexer_source':d['source'],'indexer_pages':len(d['pages']),'indexer_complete':d['complete'],
|
|
241
|
+
'discovery_status':d.get('discovery_status','checked' if d['complete'] else 'incomplete'),
|
|
242
|
+
'candidate_balance_errors':len(c.get('balance_errors',[])),
|
|
231
243
|
'positive_erc20_count_discovered':sum(t['chain_id']==n['chain_id'] and t['token_type']=='ERC20' for t in tokens),
|
|
232
244
|
'mintclub_inventory_scan':None,'mintclub_registry_scan':c.get('registry_scan'),'notes':[]}
|
|
233
245
|
if not d['complete']:row['notes'].append('General token discovery incomplete. See discovery evidence; this is not proof of no holdings.')
|
|
@@ -264,7 +276,7 @@ def finish(wallet, folder, networks, discovered):
|
|
|
264
276
|
'dex_tokens_with_liquidity_evidence':sum(t['dex_liquidity_found'] for t in tokens),'mintclub_tokens':len(minted),
|
|
265
277
|
'mintclub_tokens_with_nonzero_reserve':sum(t['mintclub']['funded'] for t in minted),
|
|
266
278
|
'mintclub_registry_assets_checked':sum(c.get('mintclub_registry_scan',{}).get('checked',0) if c.get('mintclub_registry_scan') else 0 for c in coverage)}
|
|
267
|
-
gaps=any(not c['indexer_complete'] or not (c.get('mintclub_registry_scan') or {}).get('complete') or c['rpc_status']!='available' for c in coverage)
|
|
279
|
+
gaps=any(not c['indexer_complete'] or not (c.get('mintclub_registry_scan') or {}).get('complete') or c['rpc_status']!='available' or c['candidate_balance_errors'] for c in coverage)
|
|
268
280
|
result={'schema_version':1,'wallet_address':wallet['address'],'tags':wallet['tags'],'compiled_at':h.now(),'status':'completed_with_coverage_gaps' if gaps else 'completed',
|
|
269
281
|
'scope':'Direct ERC20, registered Mint Club ERC1155 and native holdings across all configured Mint Club EVM networks. Testnets are separate.',
|
|
270
282
|
'counts':counts,'coverage':coverage,'tokens':tokens,'nested_curve_independent_redemption_estimates':[], 'price_references':refs,
|