@anywalletkit/extension-kit

中文文档
0.3.5发布于 2026/11/05Apache-2.0TypeScriptESM + CJS构建来源已验证近 30 天下载 2626

Chromium (MV3) wallet-extension core for Any Wallet Kit: scrypt + AES-256-GCM vault with passkey (WebAuthn PRF) unlock, auto-locking session, dApp request router with per-origin permissions and an approval queue, signing-request checks, swap / Earn plan r

原始使用文档
文档目录4 节
  1. Install
  2. What it provides
  3. Security model (summary)
  4. Using the chrome adapters

@anywalletkit/extension-kit

npm

The security-relevant core of a non-custodial Chromium (MV3) wallet extension, built on the Any Wallet Kit chain and protocol packages: an encrypted vault, the unlocked-key session with auto-lock, the dApp request router with per-origin permissions and an approval queue, signing-request classification, and the review helpers for swaps and Earn plans. No UI, no @types/chrome, no network code of its own: the chrome adapters take structural objects (StorageAreaLike, AlarmsLike), so everything runs under Vitest with in-memory adapters.

Install

npm install @anywalletkit/extension-kit
# or: pnpm add @anywalletkit/extension-kit  ·  yarn add @anywalletkit/extension-kit

On npm: @anywalletkit/extension-kit, published from the release workflow with provenance.

This package is for wallet developers who build their own extension. It is a library, not an extension: the popup, approval windows, injected providers, service-worker wiring, build and release pipeline are the wallet's. The Vaulty browser extension is one product built on it.

pnpm add @anywalletkit/extension-kit @anywalletkit/core @anywalletkit/keyring

What it provides

AreaExportsWhat
VaultVault, chromeLocalVaultStorage, memoryVaultStorage, VAULT_SCRYPT, VAULT_LIMITS, VAULT_MIN_PASSWORD_LENGTHevery secret at rest in chrome.storage.local, two key layers (a KEK wraps a DEK, the DEK seals each secret); passkey (WebAuthn PRF) unlock as a second wrap of the same DEK
SessionSession, chromeSessionStorage, chromeAlarms, SESSION_DEFAULT_AUTOLOCK_MSthe DEK in chrome.storage.session with an expiry and an alarm; getKey() re-checks the expiry itself
Keyring adapterschromeKeyringStorage, vaultSecretStorage@anywalletkit/keyring metadata as plain JSON, secrets through the vault, one DEK use per call
RelayPageRequest / RelayRequest shapes and guards, isRelayMethod, pageRequestFrom, stampOrigin, checkSenderOrigin, RELAY_ERRORSpage ⇄ content script ⇄ service worker messages; the content script takes only messages the page window itself posted (pageRequestFrom(event, window): an iframe's parent.postMessage is not the site), and the origin is the browser's (port.sender), never the page's
RouterRequestRouter, READ_METHODS, SIGNING_METHODS, toRelayErrorwhat a dApp may call: accounts only for connected origins, chain add / switch through the approval window, reads from the wallet's own RPCs, eth_sign never offered, everything else 4200
PermissionsPermissionStore, CHAIN_ACCOUNT_KEYSper-origin accounts (EVM, Solana, TON, Tron, Bitcoin, NEAR) and chain
ApprovalsApprovalQueue, ReviewDeclined, QueueLimitError, APPROVALS_MAX_PENDING_PER_ORIGINone approval window at a time, at most 3 pending requests per origin and one pending connect; transport notes (setVia) for WalletConnect peers
SigningclassifySignRequest, prepareTransaction, createSignHandler, messageTextconnected-address and chain checks, EIP-7702 delegations from sites refused, nonce / gas / fees from the wallet's RPC
NetworksNetworksTable, DEFAULT_NETWORKS, parseAddChainParams, Solana / TON / Tron endpointsEVM networks with public RPC failover lists; wallet_addEthereumChain only for catalog chains, https only, eth_chainId verified, site-supplied RPCs stamped custom
Review gateReviewGate, evmCallMatcher, chainTxMatcherafter one sheet reviewed a whole plan, exactly those operations pass once within a TTL
SwapsquoteRoutes, reviewSwapPlan, executionFloor, SwapsStore, SwapTracker, parseSwapIdprogressive quotes over swap2 providers, one review sheet per plan (a gasless order is shown and matched by its EIP-712 hash), pre-approvals bound to the reviewing account (meta.account), pending swaps resumed across worker restarts
EarnreviewEarnPlan, earnPlanMatchersone sheet for an Earn module's plan (approvals + call), bound to meta.account
TON ConnectparseConnectRequest, parseSendTransaction, parseSignData, tonProofMessage, tonSignDataMessagethe pure-data half of TON Connect v2 (the digests ton_proof and signData sign, to verify them); the account signs with signTonProof / signTonConnectData (wallet-ton) — sign() refuses 32-byte digests
PhishingPhishingDetectorMetaMask eth-phishing-detect list format, shipped with the build (never fetched at runtime); a listed site is refused (refusal → refuse on ApprovalQueue and RequestRouter, 4100, no window); fuzzy lookalikes, brand-in-label, punycode warn
SIWEparseSiwe, siweProblems, describeSiweEIP-4361 sign-in messages: domain vs. the asking site, address, chain, expiry, nonce (warnings)
Address poisoninglookalikeOf, RecipientBookwarns when a new recipient imitates one the user confirmed
WebAuthnverifyAssertion, approvalChallenge, derToRaw, rawToDerES256 assertion check for "passkey on every signature"; the worker never trusts the page's word

Security model (summary)

  • Keys at rest. Envelope v2: scrypt N=2^17 r=8 p=1 (@noble/hashes) → AES-256-GCM wraps a random 32-byte DEK; the DEK seals each secret under AAD forgewallet-vault/secret/<id> (boxes cannot be swapped). The DEK box's AAD binds the exact KDF parameters, so an envelope whose salt or cost was edited on disk fails to open; unlock refuses sub-floor costs (weak-kdf), and costs or sizes above VAULT_LIMITS (scrypt N ≤ 2^20, r ≤ 16, p ≤ 4; PBKDF2 ≤ 5 000 000 iterations; 1 000 secrets, 32 passkeys, 4 MiB) before any KDF runs (unsupported-envelope). Writes to the envelope are serialized (one Vault per storage area), so parallel imports keep every secret. v1 envelopes (PBKDF2-SHA256, ≥ 600 000 iterations) still open and are re-sealed as v2. Passwords shorter than 8 characters are refused by the vault itself. Wrong password and tampering are the same failure (wrong-password). create never overwrites; reset is an explicit destroy().
  • Passkeys. A passkey wraps the same DEK under HKDF-SHA256(PRF output, salt = credential id); the password stays the recovery path.
  • Re-keying. changePassword re-wraps the same DEK, and removePasskey does not take back a DEK the passkey already unwrapped. After a suspected compromise, rotateVaultKey(password) makes a fresh DEK, re-seals every secret, drops the passkeys (register them again) and writes one envelope; hand the returned DEK to session.unlock, which replaces the old one. setSecret refuses a DEK that does not open the vault's secrets.
  • Unlocked state. Only the DEK is kept, in chrome.storage.session (memory-only, trusted contexts), with an expiry enforced on every read and an alarm that locks; key buffers this package owns are zeroized after use. unlock / lock / touch / getKey run one at a time, so activity racing a lock never brings the key back.
  • Errors to pages. toRelayError(e, { log }) passes the wallet's own refusals (EIP-1193 codes) and a node's JSON-RPC error (code, revert data) with URLs removed; anything else is -32603 Internal error on the page and goes to log.
  • Origins. The content script relays only what the page window itself posted (pageRequestFrom: event.source is the page, event.origin its origin), so an ad or embed in an iframe cannot speak as the site. The service worker answers a relay message only when the content script's stamped origin equals port.sender.origin. Permissions are keyed by that origin, never by a URL a page or a WalletConnect peer claims.
  • Requests. Everything that reveals accounts or moves funds goes through the approval queue; blind signing (eth_sign) is not offered; typed data for another chain, deployments and site-originated EIP-7702 delegations are refused; read-only RPC goes to the wallet's own endpoints.
  • Network. The kit makes no requests of its own beyond the RPC / provider calls the wallet wires in; the phishing list is data the wallet ships.

What stays the wallet's job: the extension-page CSP (script-src 'self', no remote images), a host allowlist for the worker's fetch, isolating third-party network SDKs (WalletConnect, Omniston) in a sandbox page, the reproducible release build and the store account.

Using the chrome adapters

import {
  Vault, Session, PermissionStore, NetworksTable, ApprovalQueue, RequestRouter,
  chromeLocalVaultStorage, chromeSessionStorage, chromeAlarms, chromeJsonStorage,
  chromeKeyringStorage, vaultSecretStorage, checkSenderOrigin, isRelayRequest, PORT_NAME,
} from '@anywalletkit/extension-kit'

// service worker
const vault = new Vault({ storage: chromeLocalVaultStorage(chrome.storage.local) })
const session = new Session({ storage: chromeSessionStorage(chrome.storage.session), alarms: chromeAlarms(chrome.alarms) })

// onboarding
const dek = await vault.create(password)          // refuses if a vault exists or the password is too short
await vault.setSecret(dek, 'main', utf8(seedPhrase))
await session.unlock(dek)                         // dek is zeroized here

// @anywalletkit/keyring on top of the vault
const keyringStorage = chromeKeyringStorage(chrome.storage.local)
const secrets = vaultSecretStorage(vault, session) // VaultError('locked') once the session expired

// the dApp side
const permissions = new PermissionStore(chromeJsonStorage(chrome.storage.local, 'my-wallet.permissions'))
const networks = new NetworksTable(chromeJsonStorage(chrome.storage.local, 'my-wallet.networks'))
const approvals = new ApprovalQueue(myApprovalWindow) // chrome.windows.create({ type: 'popup' }) behind ApprovalWindow
const router = new RequestRouter({ permissions, networks, approvals, accounts, rpc, emit, sign })

chrome.runtime.onConnect.addListener((port) => {
  if (port.name !== PORT_NAME) return
  port.onMessage.addListener(async (msg) => {
    if (!isRelayRequest(msg)) return
    const origin = checkSenderOrigin(port.sender, msg.origin)
    if (!origin) return // forged origin: dropped, not answered
    const result = await router.handle(origin, msg.chain, msg.method, msg.params)
    // post a RelayResponse { kind: 'response', id: msg.id, … } back on the port
  })
})

memoryVaultStorage, memorySessionStorage, memoryAlarms, memoryJsonStorage and memoryApprovalWindow are the in-memory twins for tests.

版本与发布记录

@anywalletkit/extension-kit 共发布 15 个版本,首次发布于 2026/10/25;过去 12 个月发布了 15 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.3.5latest2026/11/052 天前
0.3.42026/11/035 天前
0.3.22026/10/308 天前
0.3.12026/10/309 天前
0.3.02026/10/2910 天前
0.2.22026/10/2810 天前

关于 @anywalletkit/extension-kit 的常见问题

@anywalletkit/extension-kit 是什么?

Chromium (MV3) wallet-extension core for Any Wallet Kit: scrypt + AES-256-GCM vault with passkey (WebAuthn PRF) unlock, auto-locking session, dApp request router with per-origin permissions and an approval queue, signing-request checks, swap / Earn plan r

如何安装 @anywalletkit/extension-kit?

在项目目录运行 npm install @anywalletkit/extension-kit,或使用 pnpm add / yarn add / bun add。

@anywalletkit/extension-kit 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@anywalletkit/extension-kit 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@anywalletkit/extension-kit 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@anywalletkit/extension-kit 可以商用吗?

@anywalletkit/extension-kit 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@anywalletkit/extension-kit 的下载量有多少?

根据 npm 官方下载统计,@anywalletkit/extension-kit 在 2026/10/06 – 2026/11/04 这 30 天内被下载 2,626 次,日均约 88 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@anywalletkit/extension-kit 还在维护吗?

最新版本 0.3.5 发布于 2026/11/05(2 天前);至今共发布 15 个版本,其中过去 12 个月发布了 15 个;首次发布于 2026/10/25。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。