kira-wallet 0.1.14 → 0.1.16

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.
Files changed (44) hide show
  1. package/THIRD_PARTY_NOTICES.md +18 -0
  2. package/cashout-quotes.cjs +12 -3
  3. package/cashout.py +1 -0
  4. package/chain_lifecycle.py +5 -5
  5. package/docs/AGENT_TOOLS.md +35 -4
  6. package/docs/JOB_CONTRACT.md +16 -4
  7. package/docs/ONBOARDING_CHAT_CONTRACT.md +1 -1
  8. package/docs/RELEASE.md +101 -4
  9. package/docs/WALLET_CAPABILITIES.md +65 -0
  10. package/holdings_query.py +74 -0
  11. package/kira_agent.py +68 -18
  12. package/kira_jobs.py +46 -1
  13. package/kira_research_tools.py +36 -8
  14. package/market-discovery.cjs +48 -0
  15. package/market-links.cjs +9 -0
  16. package/market_discovery.py +120 -0
  17. package/market_records.py +101 -0
  18. package/package.json +10 -3
  19. package/pipeline-onchain.cjs +6 -5
  20. package/sell-quotes.cjs +51 -0
  21. package/sell_quotes.py +157 -0
  22. package/viewer/README.md +1 -1
  23. package/viewer/details.py +12 -5
  24. package/viewer/model.py +60 -12
  25. package/viewer/refresh-curve-prices.cjs +1 -1
  26. package/viewer/refresh_prices.py +19 -5
  27. package/viewer/server.py +1 -1
  28. package/viewer/static/app.js +68 -31
  29. package/viewer/static/chat-workspace.css +121 -17
  30. package/viewer/static/chat-workspace.js +1 -1
  31. package/viewer/static/explorers/etherscan.png +0 -0
  32. package/viewer/static/index.html +23 -7
  33. package/viewer/static/jobs.js +51 -20
  34. package/viewer/static/markdown.js +12 -1
  35. package/viewer/static/selects.js +9 -5
  36. package/viewer/static/style.css +3 -8
  37. package/viewer/static/venues/aerodrome.svg +32 -0
  38. package/viewer/static/venues/mintclub.png +0 -0
  39. package/viewer/static/venues/uniswap.svg +6 -0
  40. package/viewer/static/workspace-model.js +53 -26
  41. package/viewer/static/workspace.css +87 -0
  42. package/viewer/static/workspace.js +10 -5
  43. package/viewer/token_images.py +5 -1
  44. package/wallet.py +27 -23
@@ -47,3 +47,21 @@ per-contract provenance is in the catalog.
47
47
  ## Alchemy setup illustration
48
48
 
49
49
  `viewer/static/alchemy-create-app.png` is an unmodified example screenshot from Alchemy’s public API key walkthrough, used to explain app setup. Source: https://www.alchemy.com/docs/create-an-api-key . The image contains no Kira operator account or credential. Alchemy retains rights to its interface and illustration.
50
+
51
+ ## Market platform marks
52
+
53
+ The local market cards identify their sources using the official marks retrieved
54
+ on October 19, 2026: [Mint Club app icon](https://mint.club/apple-touch-icon.png),
55
+ [Uniswap logo](https://github.com/Uniswap/interface/blob/main/packages/ui/src/assets/icons/uniswap-logo.svg),
56
+ and [Aerodrome favicon](https://aerodrome.finance/svg/AERO/favicon.svg).
57
+ These marks identify external services and remain their respective owners' marks.
58
+ Kira does not claim affiliation or endorsement. The original files are served
59
+ locally from `viewer/static/venues/`.
60
+
61
+ ## Explorer mark
62
+
63
+ The Etherscan icon in `viewer/static/explorers/` is used for explorer links.
64
+ Each link retains its destination explorer name or the generic Explorer label.
65
+ It was retrieved unchanged on October 19, 2026 from
66
+ [Etherscan documentation](https://docs.etherscan.io/mintlify-assets/_mintlify/favicons/etherscan/PMbbZQeU1A_1WqNP/_generated/favicon/android-chrome-192x192.png).
67
+ The mark belongs to its owner and does not imply affiliation or endorsement.
@@ -25,7 +25,7 @@ function contractRevert(error) {
25
25
  }
26
26
  return reverted;
27
27
  }
28
- async function quotePool(client,network,token,pool,block,blockHash,deployments,{batchRead}={}) {
28
+ async function quotePool(client,network,token,pool,block,blockHash,deployments,{batchRead,fullOnly=false}={}) {
29
29
  const state=pool.factory_measurement?.state||{},balance=integer(token.wallet_balance_raw);
30
30
  const unknown=reason=>({status:'unknown',reason,gas_included:false,execution_verified:false});
31
31
  if(!balance||!address(token.token_address)||!address(state.token0)||!address(state.token1)||state.token0.toLowerCase()===state.token1.toLowerCase()
@@ -43,7 +43,16 @@ async function quotePool(client,network,token,pool,block,blockHash,deployments,{
43
43
  if(canonical('v3')&&quoter&&Number.isInteger(state.fee)&&state.fee>=0&&state.fee<1_000_000) {
44
44
  kind='uniswap_v3_quoter';
45
45
  request=amount=>({address:quoter.address,abi:v3Abi,functionName:'quoteExactInputSingle',args:[{tokenIn:token.token_address,tokenOut:output,amountIn:amount,fee:state.fee,sqrtPriceLimitX96:0n}]});
46
- decode=result=>result[0];
46
+ decode=result=>{
47
+ // QuoterV2's default limit is MIN_SQRT_RATIO+1 / MAX_SQRT_RATIO-1
48
+ // (Uniswap v3-core TickMath). At that boundary input can be unspent.
49
+ if(fullOnly){
50
+ const after=integer(result[1]);
51
+ if(after===null||after<=4295128740n||after>=1461446703485210103287273052203988822378723970341n)
52
+ throw Error('Full input consumption is not established at the pool price limit');
53
+ }
54
+ return result[0];
55
+ };
47
56
  }else if(network.chain_id===8453&&state.factory?.toLowerCase()==='0x420dd381b31aef6683db6b902084cb0ffece40da'&&pool.venue==='aerodrome') {
48
57
  kind='aerodrome_pool';
49
58
  request=amount=>({address:pool.pool,abi:aeroAbi,functionName:'getAmountOut',args:[amount,token.token_address]});
@@ -54,7 +63,7 @@ async function quotePool(client,network,token,pool,block,blockHash,deployments,{
54
63
  kind='uniswap_v2_reserves';quote=async amount=>v2AmountOut(amount,reserveIn,reserveOut);
55
64
  }else return unknown('Quote adapter unavailable for this verified pool');
56
65
  if(request)quote=async amount=>decode(await client.readContract({...request(amount),blockNumber:block}));
57
- const amounts=inputAmounts(balance);let failures=0,buffer=[];
66
+ const amounts=fullOnly?[balance]:inputAmounts(balance);let failures=0,buffer=[];
58
67
  for(let index=0;index<amounts.length;index++) {
59
68
  const amount=amounts[index];
60
69
  let result;
package/cashout.py CHANGED
@@ -136,6 +136,7 @@ def classify(token, market, *, environment='mainnet'):
136
136
  proceeds = output * Decimal(price['usd'])
137
137
  result['quotes'].append({'kind': quote.get('kind'), 'input_amount_raw': str(quote['input_amount_raw']),
138
138
  'output_amount': format(output, 'f'), 'output_address': quote['output_address'].lower(),
139
+ 'output_symbol': quote.get('output_symbol'),
139
140
  'quoted_output_usd': format(proceeds, 'f'), 'qualifies': proceeds >= Decimal('1'),
140
141
  'block_number': block, 'block_hash': block_hash, 'observed_at': quote['observed_at'],
141
142
  'output_price': price, 'partial_input': amount < holding,
@@ -22,9 +22,9 @@ NOTICES={
22
22
  'notice_url':'https://www.alchemy.com/docs/reference/degen-deprecation-notice',
23
23
  'network_shutdown_date':None,'provider_support_ended_on':'2026-08-31',
24
24
  'note':'Alchemy describes Degen L3 as shut down. Its provider support ended August 31. The exact network shutdown date is not independently established. Base DEGEN is a separate token.'},
25
- 5112:{'status':'retirement_reported','verified':False,'reviewed_on':REVIEWED_ON,
26
- 'notice_url':None,'reference_url':'https://docs.ham.fun/',
27
- 'note':'The operator reports Ham has stopped. A primary closure notice has not been located. Failed reads do not establish closure.'},
25
+ **{cid:{'status':'retired','verified':False,'confirmation':'operator','reviewed_on':'2026-10-19',
26
+ 'notice_url':None,'note':'Excluded from new research by operator confirmation. Historical observations are preserved; no independent closure notice is claimed.'}
27
+ for cid in (5112,54176)},
28
28
  }
29
29
 
30
30
 
@@ -32,7 +32,7 @@ def lifecycle(chain_id):
32
32
  state=dict(NOTICES.get(chain_id,{'status':'unconfirmed','verified':False,
33
33
  'notice_url':None,'note':'No verified retirement notice is recorded. Connectivity and discovery are separate observations.'}))
34
34
  state.update(reference_url=REFERENCES.get(chain_id,state.get('reference_url')),
35
- reviewed_on=REVIEWED_ON if chain_id in REFERENCES or chain_id in NOTICES else None,
35
+ reviewed_on=state.get('reviewed_on') or REVIEWED_ON if chain_id in REFERENCES or chain_id in NOTICES else None,
36
36
  audit_result='reference_unavailable' if chain_id in UNAVAILABLE_REFERENCES else 'reference_retrieved' if chain_id in REFERENCES else 'notice_reviewed' if chain_id in NOTICES else 'not_reviewed')
37
37
  if chain_id==8217:
38
38
  state['provider_notice']={'provider':'The Graph','support_ended_on':'2026-08-31','url':'https://docs.kaia.io/build/tools/indexers/','note':'Indexer support ended. This does not establish network retirement.'}
@@ -41,4 +41,4 @@ def lifecycle(chain_id):
41
41
 
42
42
  def retired(chain_id):
43
43
  state=lifecycle(chain_id)
44
- return state['status']=='retired' and state['verified'] is True
44
+ return state['status']=='retired' and (state['verified'] is True or state.get('confirmation')=='operator')
@@ -32,7 +32,7 @@ wallet scope and research permission. It sends no wallet facts. Authentication
32
32
  stays with the installed CLI. No desktop tokens or authentication files are copied.
33
33
 
34
34
  With research enabled, a bounded host-mediated loop provides portfolio_read,
35
- wallet_read, token_read, snapshots_list, snapshot_read, snapshots_compare,
35
+ wallet_read, token_read, holdings_query, snapshots_list, snapshot_read, snapshots_compare,
36
36
  job_list, job_read, holdings_refresh and prices_refresh. Every operation is
37
37
  scoped to registered wallets allowed by the conversation. Historical reads use
38
38
  normalized financial projections. Unknown values and historical quote times
@@ -82,15 +82,46 @@ on-chain checkpoint status. Deferred candidates and pending networks are not
82
82
  failed reads or zero holdings. Historical observations cannot establish current
83
83
  execution, transfers or realized profit.
84
84
 
85
- `records_read` executes one bounded plan of up to eight declared read tools.
86
- Each child has its own host receipt. Literal JSON read requests are matched
87
- against their actual arguments, including chain and contract. Full inventory
85
+ Before answering, scoped research chat must declare a `records_read` plan
86
+ containing every requested source, up to 32 declared read tools. An empty plan
87
+ is for greetings or action-only requests. Planning cannot start a refresh.
88
+ Each child has its own host receipt. The declared plan and literal JSON read
89
+ requests are matched against actual arguments, including chain and contract.
90
+ An incomplete delivered plan receives one bounded continuation attempt before
91
+ the turn is marked partial. Full inventory
88
92
  requests require contiguous delivered pages through `next_offset: null`.
89
93
  Failed or undelivered pages remain unread, with retry arguments in the turn's
90
94
  `read_completion`. Automatic context is not a requested tool receipt. This
91
95
  bounded check does not verify every semantic requirement in natural prose.
92
96
 
97
+ Token-specific queries read all approved wallets with `token_read`, without
98
+ enumerating unrelated inventory pages. Its optional `minimum_value_usd` filters
99
+ recorded mainnet values. Unknown and outside-USD positions have separate counts.
100
+ `position_count` is computed by the host; a read-plan count deduplicates returned
101
+ wallet/chain/asset identities rather than asking the model to tally its table.
102
+
93
103
  rpc_status pending means a network has not yet been researched. It does not
94
104
  prove an RPC outage. For a curve comparison, match chain and contract and
95
105
  compare reserve, supply, next-mint price and royalty observations. A newer
96
106
  block or a different wallet-sized burn quote alone cannot prove curve movement.
107
+
108
+ ## Bounded full-inventory queries
109
+
110
+ Connected chat uses `holdings_query` for names, rankings and aggregates. Its
111
+ `mode` is `search`, `rank` or `aggregate`. It scans every approved saved position
112
+ on the host and returns at most 25 results per page. This response bound never
113
+ limits holdings discovery. Optional filters include wallet, chain, environment
114
+ and minimum recorded USD value. Exact symbols, names and contracts take
115
+ precedence over substring matches; multiple chain/contract identities remain
116
+ explicitly ambiguous. `group_by: asset` combines the priced subset by identity,
117
+ while `position` keeps each wallet position separate. Unknown and excluded
118
+ positions have independent counts. Ranking cannot create a price for them.
119
+
120
+ Top-N completion requires a delivered full-inventory ranking receipt, including
121
+ natural requests such as “top five.” Search pages must be delivered through the
122
+ last match before claiming that all matches were read. A host count or sum does
123
+ not require sending every position to the model. These reads never queue jobs.
124
+ The existing 32-read plan, eight-round, prompt/context and time limits remain.
125
+ Detailed internal receipts are kept locally; partial-result messages use plain
126
+ language. Safe absolute HTTP(S) Markdown links are clickable; raw HTML remains
127
+ escaped and images, credential URLs and active URL schemes stay disabled.
@@ -44,10 +44,18 @@ no absolute paths or arbitrary file reads are supported.
44
44
 
45
45
  The optional `started_at` timestamp records when the current attempt was
46
46
  claimed by the worker. Resuming replaces it on the next claim. Older envelopes
47
- remain valid without it. The viewer then labels its timer "Since request"
48
- instead of claiming an exact execution time. Terminal timers stop at the last
49
- recorded update. A queued job uses its latest queue transition, not a previous
50
- attempt's start.
47
+ remain valid without it. The viewer then labels its active timer "Since request"
48
+ and its terminal timer "Request duration" instead of claiming an exact execution
49
+ time. Terminal attempt and total timers stop at the last recorded event, falling
50
+ back to the saved update time. Total duration includes waiting and pauses across
51
+ attempts. A queued job uses its latest queue transition, not a previous attempt's
52
+ start.
53
+
54
+ Completed sell analysis leaves the wallet's progress card. An observed transition
55
+ from queued or running to succeeded or partial shows a brief completion notice.
56
+ Loading saved history does not repeat that notice. Activity retains the result,
57
+ unknown checks, stages and stopped timers. Failed, paused and interrupted work
58
+ keeps its recovery controls.
51
59
 
52
60
  States: queued -> running -> succeeded | partial | failed | cancelled.
53
61
  Recovery converts unfinished running jobs to interrupted. Explicit resume may
@@ -246,6 +254,10 @@ The optional asset_progress map persists total, checked, quoted and unknown
246
254
  counts per mainnet chain. checked equals quoted plus unknown, not successful
247
255
  valuation. It survives process restarts, pause and resume and current-step count
248
256
  resets. The total can grow with discovered holdings.
257
+ For a published sell analysis, job reads project these counters from the final
258
+ per-token cash-out classifications. Testnet/outside-USD records are excluded.
259
+ This also corrects older completed summaries without rewriting their original
260
+ progress events or snapshots. Running and paused jobs retain checkpoint counts.
249
261
  Each slice registers the known inventory for all mainnet chains before market
250
262
  checks, so the first completed chain does not imply all discovered work is done.
251
263
  The viewer separately shows saved asset checks, available prices and unresolved checks. It does not derive
@@ -37,7 +37,7 @@ Kira uses fixed executable argv, stdin for prompts, a private empty working dire
37
37
 
38
38
  Native CLI and administrator policy are a separate trust boundary. These settings are invocation controls, not an OS container. Claude managed policy can still apply. Setup requires explicit acknowledgement of the native CLI trust boundary. Kira-local retention controls Kira's conversation files only; vendor and managed CLI data policies are separate. History-off conversations stay in memory and the browser draft is not persisted. Previous saved conversations are preserved but never automatically attached to a new scope.
39
39
 
40
- Chat cannot sign, trade, run arbitrary commands or change provider settings. With the separate wallet research capability enabled, a bounded host loop validates typed tool requests from the native response. It reads allowlisted current and historical facts inside the approved wallet scope and admits holdings or price jobs through the existing durable API. Legacy settings default this new capability off. New setup recommends portfolio context and research tools. No-context chat receives no wallet tools. Epoch, cancellation and configuration are rechecked under the store lock before job admission. Duplicate refreshes within a turn reuse their job. Queued or running work is never a completed publication. Research already admitted persists in Activity after chat cancellation. The tool loop is limited to eight calls, 150 KB of accumulated tool results, a 400 KB prompt and 420 seconds total. Native shell, arbitrary files, browser and MCP tools remain disabled. Responses and token metadata are untrusted text. Assistant responses use a bounded Markdown subset with escaped HTML, no active links or images, and scrollable tables. Token metadata and user messages remain escaped text.
40
+ Chat cannot sign, trade, run arbitrary commands or change provider settings. With the separate wallet research capability enabled, a bounded host loop validates typed tool requests from the native response. It reads allowlisted current and historical facts inside the approved wallet scope and admits holdings or price jobs through the existing durable API. Legacy settings default this new capability off. New setup recommends portfolio context and research tools. No-context chat receives no wallet tools. Epoch, cancellation and configuration are rechecked under the store lock before job admission. Duplicate refreshes within a turn reuse their job. Queued or running work is never a completed publication. Research already admitted persists in Activity after chat cancellation. The first response declares a read-only plan of up to 32 sources, or an empty plan for a greeting or action-only request. Every declared read is checked against delivery receipts; an incomplete plan gets one bounded continuation and remains partial if unresolved. The tool loop is limited to eight calls, 280 KB of accumulated tool results, a 400 KB prompt and 420 seconds total. Native shell, arbitrary files, browser and MCP tools remain disabled. Responses and token metadata are untrusted text. Assistant responses use a bounded Markdown subset with escaped HTML, no active links or images, and scrollable tables. Token metadata and user messages remain escaped text.
41
41
 
42
42
  All agent and chat endpoints require the existing same-origin local session, with bounded requests. Conversation jobs are separate from research jobs. Idempotent requests prevent duplicate sends. Cancellation, reset and configuration changes invalidate late responses. Tests cover disclosure projections, scope history isolation, retention, cancellation races, bounded output, unsupported CLIs, failures, duplicate sends and origin/session protection.
43
43
 
package/docs/RELEASE.md CHANGED
@@ -79,7 +79,7 @@ The earlier unpublished 0.1.2 candidate is preserved in the ignored
79
79
  `dist/preserved-0.1.2-bad8f02/` directory. Use the current `dist/release.json`
80
80
  and its matching archive for publication; do not mix manifests from candidates.
81
81
 
82
- Keep published GitHub tags, release assets and npm versions through 0.1.13
82
+ Keep published GitHub tags, release assets and npm versions through 0.1.15
83
83
  immutable. Do not rebuild them from later documentation commits. Future package
84
84
  changes need a new version. Release preparation does not publish a GitHub
85
85
  release, promote the website or publish to npm.
@@ -95,12 +95,12 @@ python3 scripts/prepare-release.py
95
95
  ```
96
96
 
97
97
  `prepare-release.py` scans the package boundary and creates
98
- `dist/kira-wallet-0.1.14.tgz`. The ignored `dist/release.json` records its source
98
+ `dist/kira-wallet-0.1.16.tgz`. The ignored `dist/release.json` records its source
99
99
  commit, SHA-256 and member count. It never publishes. After reviewing that
100
100
  manifest, the operator's final command from the project root is:
101
101
 
102
102
  ```sh
103
- npm publish ./dist/kira-wallet-0.1.14.tgz --access public
103
+ npm publish ./dist/kira-wallet-0.1.16.tgz --access public
104
104
  ```
105
105
 
106
106
  Use the operator's npm account and complete authentication or OTP personally.
@@ -353,7 +353,7 @@ The operator published 0.1.13 on October 17, 2026. Its registry archive matched
353
353
  the frozen manifest and all 90 installed package members. The published archive
354
354
  remains immutable.
355
355
 
356
- ## Release candidate: 0.1.14
356
+ ## Published 0.1.14
357
357
 
358
358
  Version 0.1.14 includes merged PR33. Private Alchemy RPC requests use a shared
359
359
  250 ms lane across hosts, with cooldown after HTTP or JSON-RPC rate limits.
@@ -392,3 +392,100 @@ Prepare and freeze the final archive from the landed version PR, then dry-run
392
392
  that exact archive. npm publication remains operator-only. Keep the personal
393
393
  0.1.13 installation, published archives and paused automation unchanged. This
394
394
  preparation does not publish a GitHub release or deploy the website.
395
+
396
+ The operator published 0.1.14 on October 18, 2026. Its registry archive matched
397
+ the frozen manifest, including SHA-256
398
+ `c9802e76f05ae7e560fd29051beea3855e7006e99670f131de2a6322f4b1e8b8`
399
+ and all 90 installed members. Actual acceptance checked important explorer
400
+ balances, prices, artwork and sell-only pause/resume. A subsequent pass completed
401
+ the sell analysis naturally and exposed the two issues addressed below.
402
+
403
+ ## Release candidate: 0.1.15
404
+
405
+ Scoped AI research declares its complete read plan before answering. A plan
406
+ can contain up to 32 read-only requests, so a nine-chain question is not cut
407
+ off by the former eight-read batch bound. Host receipts cover declared plans
408
+ in natural-language conversations as well as literal tool requests. Missing
409
+ reads receive one bounded continuation attempt and unresolved requests stay
410
+ partial. An empty plan supports greetings and action-only requests. Planning
411
+ cannot enqueue research; scope, cancellation, context and time limits remain.
412
+ Token-specific reads cover all approved wallets without unrelated inventory
413
+ pagination. Optional USD thresholds and distinct matched-position counts are
414
+ computed from saved values by the host; unknown and testnet values stay separate.
415
+ This checks the declared plan, not every possible semantic interpretation of
416
+ a question. Unknown values and recorded valuation timestamps remain explicit.
417
+
418
+ Completed sell summaries use final saved per-token classifications rather than
419
+ intermediate progress counters. Older completed jobs also show corrected totals
420
+ without rewriting snapshots or original events. Paused/running progress remains
421
+ resumable, and testnet values stay outside USD classifications. This changes
422
+ neither balances nor quote execution, gas or best-route guarantees.
423
+
424
+ Required gates are the full repository suite, clean installation, actual native
425
+ AI nine-source and research-action checks, completed-summary browser checks,
426
+ tracked/history privacy scans and an exact final archive installation. Preserve
427
+ raw strict history findings and assess only the authorized normal CLI merge
428
+ metadata under the ruling above. Review not selected.
429
+
430
+ Prepare and freeze the final archive only from the landed release source, then
431
+ dry-run that exact archive. npm publication remains operator-only. Preserve the
432
+ personal 0.1.14 installation, credentials, paused history and published archives.
433
+ Use a separate private workspace for candidate acceptance. No GitHub release,
434
+ website deployment or personal app upgrade is part of this preparation.
435
+
436
+ ## Release candidate: 0.1.16
437
+
438
+ This version repairs Mint Club symbol routes, including ERC1155 NFT routes and
439
+ encoded symbols, without rewriting historical snapshots. Market lookup attaches
440
+ both pair members and price updates persist new pools. Pool identity preserves
441
+ Uniswap v4 IDs, versions and fee tiers. Visible market records are independent
442
+ from price eligibility and executable proceeds. Existing thin-market, testnet
443
+ and unknown-value protections remain.
444
+
445
+ The basic market pass supplements Mint Club ERC20 holdings with official
446
+ Uniswap v2/v3/v4 index records and bounded Base common-quote factory reads.
447
+ Provider errors and incomplete pages remain partial. This is not proof of an
448
+ exhaustive blockchain-wide pool inventory. It does not require sell analysis.
449
+ Prior successful market observations survive missing sources; metadata-only
450
+ factory reads cannot refresh the date of an old price or liquidity observation.
451
+
452
+ Connected AI can search symbols and names, rank the full saved inventory, and
453
+ compute exact filtered aggregates without reading every inventory page into its
454
+ context. Official market marks and token/reserve artwork identify pool cards.
455
+ Ham and OverProtocol are excluded by operator confirmation, distinguished from
456
+ independently verified notices. Historical evidence is preserved. Successful
457
+ price metadata batches are reused for artwork to avoid redundant source reads.
458
+
459
+ Holdings give markets a separate row, retain every saved pool and open known
460
+ venue destinations directly. Token-title Explorer links use the locally served
461
+ Etherscan mark. Chain selectors show network artwork, sidebar actions align,
462
+ and All tokens adds the same value filters as wallet holdings.
463
+
464
+ Wallet, All tokens and network lists compare known value with saved sell-out
465
+ estimates. Basic holdings and explicit price refreshes collect bounded direct
466
+ pool quotes without viewer-triggered RPC reads or additional route discovery.
467
+ Uniswap v2/v3 and Base Aerodrome classic pools are supported. Full-balance
468
+ quantities are checked at the quote block. Unknown, unsupported, changed-input,
469
+ testnet and archived positions cannot become a USD sale estimate. Output-price
470
+ and quote clocks remain distinct. Independent quotes sharing liquidity are not
471
+ summed into portfolio proceeds; gas and execution guarantees remain excluded.
472
+
473
+ The wallet header separates identity, holdings status, valuation and optional
474
+ sell analysis. New wallets can show progress before their first snapshot. A
475
+ retired-network catalog mismatch no longer stops new holdings jobs. Failed work
476
+ offers typed recovery guidance and preserves prior results and failure history.
477
+ Completed sell analysis dismisses its progress card and briefly announces saved
478
+ results. Activity retains stages, unresolved checks and durations frozen at the
479
+ terminal event. Paused, interrupted and failed work retains recovery actions.
480
+
481
+ Imported wallets and autonomous execution are deferred to post-MVP. The decision
482
+ and reconsideration conditions are recorded in [wallet capabilities](WALLET_CAPABILITIES.md).
483
+ This release adds no transaction signing, token approval or trading capability.
484
+
485
+ Release acceptance covers the repository suite, clean installation, source and
486
+ history privacy checks, exact archive membership, real unchanged natural-language
487
+ questions and responsive market interactions. Review not selected. Publication
488
+ remains operator-only. A feature-branch candidate is not a merged release or a
489
+ published package; the final manifest must identify the accepted source commit.
490
+ Preserve the superseded unpublished archive before creating the final 0.1.16
491
+ candidate. Only the archive matching the final manifest is a publication target.
@@ -1,5 +1,70 @@
1
1
  # Wallet capability direction
2
2
 
3
+ ## Post-MVP decision: Imported wallets and execution, 2026-10-19
4
+
5
+ The operator accepted deferring the proposed Imported wallet execution stack
6
+ after the architecture, dependency and security discussion. It is post-MVP
7
+ research, not an implementation ticket or permission to request wallet grants.
8
+ This decision supersedes any earlier suggestion to begin a three-group rollout.
9
+
10
+ Keep the current Watching and OWS experiences for the MVP. External browser
11
+ connections remain public-address sources for Watching wallets. Existing
12
+ human-submitted encrypted OWS creation and public-descriptor connection are
13
+ implemented; transaction signing, transfers and trading are not implemented.
14
+ Do not add a third wallet group before its distinct capability is approved.
15
+
16
+ The evaluated future groups are Watching wallet, OWS wallet and Imported wallet.
17
+ For this proposal, Imported means connecting an existing external account and
18
+ explicit execution permissions while retaining its original keys in that wallet.
19
+ It does not mean importing a seed phrase or private key into Kira. Keep account
20
+ origin separate from execution capability. A future conversion must preserve the
21
+ same account's holdings and history without double-counting the portfolio.
22
+
23
+ The recommendation is to defer autonomous execution. Dependency size can be
24
+ bounded, but grant enforcement, signer isolation, transaction validation, nonce
25
+ ownership, duplicate prevention, revocation and uncertain-result recovery create
26
+ substantial security and maintenance obligations. Saved sell-out estimates are
27
+ research evidence and cannot be treated as ready-to-submit transactions.
28
+
29
+ A metadata-only dependency experiment using Smart Accounts Kit 2.0.0 increased
30
+ the lockfile package entries, excluding the root, from 18 to 53. Existing package
31
+ versions did not change. The SDK's own reported unpacked size was 3,156,894 bytes,
32
+ excluding its dependencies. This is not a browser bundle, memory or runtime
33
+ benchmark. No SDK was installed in the project and no signing code was run.
34
+
35
+ Reconsider only after a concrete recurring action justifies execution support:
36
+
37
+ - Begin with one chain and one supported action, confirmed per transaction in
38
+ the external wallet. Native key import remains outside this proposal.
39
+ - Consider limited automation later as an optional execution component. Define
40
+ actual on-chain restrictions for amounts, recipients, contracts and expiry;
41
+ local application checks alone do not constrain a compromised signing key.
42
+ - Keep private signing material outside model processes, prompts, browser
43
+ storage, research records and logs. Package or process separation alone is
44
+ not proof of an effective security boundary.
45
+ - Verify the granted permissions rather than assuming they equal the request.
46
+ Treat pause, disconnect and confirmed permission revocation as separate states.
47
+ - Validate current balances, exact calldata, fees and minimum output before
48
+ submission. Recover ambiguous sends without automatic duplicate transactions.
49
+ - Verify expiry, revocation, rejected actions and recovery with synthetic and
50
+ testnet evidence before considering mainnet. Independent security review is
51
+ recommended for that future scope and remains operator-selected.
52
+
53
+ MetaMask Advanced Permissions was a researched candidate, not an operator-selected
54
+ integration. ERC-7715 remains a draft and supported permission types and networks
55
+ must be checked again when this work is scheduled. OWS execution would need the
56
+ same security assessment; its current in-process policy and key handling are not
57
+ an on-chain or hardware isolation guarantee. Review not selected.
58
+
59
+ Primary references checked on 2026-10-19:
60
+ [ERC-7715](https://eips.ethereum.org/EIPS/eip-7715),
61
+ [EIP-7702 security](https://eips.ethereum.org/EIPS/eip-7702#security-considerations),
62
+ [MetaMask supported permissions](https://docs.metamask.io/smart-accounts-kit/get-started/supported-advanced-permissions/),
63
+ [delegation revocation](https://docs.metamask.io/smart-accounts-kit/guides/delegation/disable-delegation/),
64
+ [OWS key isolation](https://github.com/open-wallet-standard/core/blob/main/docs/05-key-isolation.md).
65
+
66
+ ## Historical design context: 2026-10-04
67
+
3
68
  Agreed with the operator on 2026-10-04: begin with Watching wallet connection.
4
69
  Transfers, swaps and OWS Managed wallets need separate design and verification
5
70
  before implementation. This is a future direction, not a delivered capability.
@@ -0,0 +1,74 @@
1
+ """Bounded results from the complete authorized saved projection, without model paging."""
2
+ from decimal import Decimal, localcontext
3
+ import json
4
+ from kira_jobs import JobError
5
+
6
+
7
+ def query_holdings(wallets, args):
8
+ mode=args['mode'];query=args.get('query','').strip().casefold()
9
+ if mode not in ('search','rank','aggregate'):raise JobError('invalid_tool','Choose search, rank or aggregate.')
10
+ if mode=='search' and not query:raise JobError('invalid_tool','Supply a token symbol, name, contract or native.')
11
+ group_by=args.get('group_by','asset')
12
+ if group_by not in ('asset','position'):raise JobError('invalid_tool','Group by asset or position.')
13
+ environment=args.get('environment','mainnet')
14
+ if environment not in ('mainnet','testnet','all'):raise JobError('invalid_tool','Choose mainnet, testnet or all.')
15
+ limit=args.get('limit',10);offset=args.get('offset',0)
16
+ if limit>25:raise JobError('invalid_tool','Use at most 25 query results; this does not limit the scanned inventory.')
17
+ minimum=Decimal(str(args['minimum_value_usd'])) if 'minimum_value_usd' in args else None
18
+ def eligible(asset):
19
+ return not asset.get('archived') and (environment=='all' or asset.get('environment')==environment) and ('chain_id' not in args or asset['chain_id']==args['chain_id'])
20
+ def exact(asset):
21
+ return query in {str(asset.get('symbol','')).casefold(),str(asset.get('name','')).casefold(),str(asset.get('address','')).casefold()} or query=='native' and asset.get('is_native')
22
+ exact_match=bool(query) and any(eligible(a) and exact(a) for w in wallets for a in w['assets'])
23
+ scanned=0;selected=[];outside=0;unpriced=0
24
+ for wallet in wallets:
25
+ for asset in wallet['assets']:
26
+ scanned+=1
27
+ if 'chain_id' in args and asset['chain_id']!=args['chain_id']:continue
28
+ if query and not (exact(asset) if exact_match else query in str(asset.get('name','')).casefold() or query in str(asset.get('symbol','')).casefold()):continue
29
+ if asset.get('archived') or environment!='all' and asset.get('environment')!=environment:
30
+ outside+=1;continue
31
+ value=asset.get('value_usd') if asset.get('environment')=='mainnet' else None
32
+ if value is None:unpriced+=1
33
+ if minimum is not None and (value is None or Decimal(str(value))<minimum):continue
34
+ selected.append((wallet,asset))
35
+ from kira_agent import recorded_valuation, wallet_facts
36
+ valuation=recorded_valuation([a for _,a in selected])
37
+ groups={}
38
+ for wallet,asset in selected:
39
+ key=asset['id'] if group_by=='asset' else wallet['address'].lower()+':'+asset['id']
40
+ groups.setdefault(key,[]).append((wallet,asset))
41
+ ranked=[]
42
+ for key,positions in groups.items():
43
+ values=[Decimal(str(a['value_usd'])) for _,a in positions if a.get('value_usd') is not None and a.get('environment')=='mainnet']
44
+ with localcontext() as context:
45
+ context.prec=100;total=sum(values,Decimal(0)) if values else None
46
+ ranked.append((key,positions,total))
47
+ if mode=='rank':ranked=[r for r in ranked if r[2] is not None]
48
+ ranked.sort(key=lambda r:(r[2] is None,-r[2] if r[2] is not None else Decimal(0),r[0]))
49
+ page=ranked[offset:offset+limit] if mode!='aggregate' else []
50
+ rows=[];remaining=40000
51
+ for key,positions,total in page:
52
+ facts=[]
53
+ for wallet,asset in positions:
54
+ fact=wallet_facts([{**wallet,'assets':[asset]}],pool_budget=max(0,remaining))[0]['assets'][0]
55
+ remaining-=len(json.dumps(fact.get('pools',[])).encode())
56
+ facts.append({'wallet':wallet['address'],'wallet_name':wallet['name'],'snapshot_id':wallet.get('snapshot_id'),'asset':fact})
57
+ rows.append({'identity':positions[0][1]['id'],'value_usd':format(total,'f') if total is not None else None,
58
+ 'position_count':len(positions),'unpriced_position_count':len(positions)-sum(a.get('value_usd') is not None and a.get('environment')=='mainnet' for _,a in positions),
59
+ 'positions':facts})
60
+ count=len(ranked)
61
+ response={'mode':mode,'match_type':'exact' if exact_match else 'substring' if query else 'all','group_by':group_by,'environment':environment,'scanned_position_count':scanned,
62
+ 'matched_position_count':len(selected),'matched_asset_count':len({a['id'] for _,a in selected}),
63
+ 'unpriced_position_count':unpriced,'outside_scope_count':outside,'valuation':valuation,
64
+ 'result_count':count,'results':rows,'offset':offset,'next_offset':offset+len(rows) if mode!='aggregate' and offset+len(rows)<count else None,
65
+ 'ambiguous':mode=='search' and len({a['id'] for _,a in selected})>1,
66
+ 'query_scope':{'mode':mode,'full_inventory_scanned':True,'requested_limit':limit,'offset':offset,'result_count':count},
67
+ 'note':'Exact query over approved saved holdings, including records beyond the first page. Ranking sums the priced mainnet subset per chain and contract; unknown values remain separate. Not live wealth or sale proceeds. Search may match multiple identities; report the ambiguity rather than guessing. Missing records are unknown.'}
68
+ # Never enlarge the native context to accommodate a pathological result.
69
+ while len(json.dumps(response,ensure_ascii=False).encode())>120000 and rows:
70
+ rows.pop();response['next_offset']=offset+len(rows)
71
+ if mode!='aggregate' and page and not rows:raise JobError('context_limit','This match exceeds the detail limit. Use a wallet filter or token_read.')
72
+ response['query_scope']['returned_count']=len(rows)
73
+ response['query_scope']['complete']=mode=='aggregate' or len(rows)==len(page)
74
+ return response