# @eternl/hub-wallet-sdk 中文文档

规范地址：https://npmcn.com/package/%40eternl/hub-wallet-sdk/

版本：0\.55\.0
npm 元数据更新时间：2026-11-10T15:46:44.290Z
中文译文更新时间：2026-11-14T02:41:03.860Z
软件包许可：BUSL-1\.1

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40eternl/hub-wallet-sdk
原文对照：https://npmcn.com/package/%40eternl/hub-wallet-sdk/?lang=original
项目主页：https://github.com/Tastenkunst/eternl-hub-wallet-sdk#readme
源码仓库：https://github.com/Tastenkunst/eternl-hub-wallet-sdk

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：450 kB，9 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- 运行依赖：1 个
- peer 依赖：@nats-io/kv 3\.4\.0，@nats-io/jetstream 3\.4\.0，@nats-io/nats-core 3\.4\.0
- 首次发布：2026-05-08T13:46:56.673Z
- 最新版本发布：2026-11-10T15:46:44.091Z
- 近 30 天下载量：2487（2026-10-14 至 2026-11-12，npm 官方下载统计）
- 已发布版本数：69（近 12 个月 69 个）

## 中文简介

Eternl Hub SDK用于钱包开发者.

## 安装

```sh
npm install @eternl/hub-wallet-sdk
```

也可以使用：`pnpm add @eternl/hub-wallet-sdk`、`yarn add @eternl/hub-wallet-sdk`、`bun add @eternl/hub-wallet-sdk`

## README

# @eternl/hub-wallet-sdk (英语)

一个基于可堆肥的SDK,供钱包开发者与Eternl Hub集成. 它为安全设备/dApp配对,端到端加密消息,存在,以及NATS WebSocket传输上的远程签名提供了钱包侧构件.

主要的高级切入点是 **`createWalletConnectorSession`** ——由它组成对称配对协议 `@eternl/hub-core` 并添加钱包作用逻辑(对应 dApp) `apiCall`s,账户切换,存在). 低级堆肥(`useNatsClient`, (中文(简体) ), `useEncryption`, (中文(简体) ), `useReplayProtection`,.)也为高级或自定义集成出口.

> **状态 :** bUSL-1.1项下的源数据(见 [许可证](https://npmcn.com/package/%40eternl/hub-wallet-sdk/#readme-license)) (中文(简体) ). API正稳定在1.0发布 `0.x`,小版本可能包含断开的更改——检查 [变化](https://npmcn.com/package/%40eternl/hub-wallet-sdk/CHANGELOG.md) 升级前.

## 安装

```bash
npm install @eternl/hub-wallet-sdk
# or
pnpm add @eternl/hub-wallet-sdk
```

这取决于 `@eternl/hub-core` 和模块 `@nats-io/*@3.x` 客户(`@nats-io/nats-core`, (中文(简体) ), `@nats-io/jetstream`, (中文(简体) ), `@nats-io/kv`)作为依赖性自动安装.

## 运行时间要求

SDK需要安全的WebCrypto环境(现代浏览器或Node.js 18+). `initHub()` 电话 `assertCryptoCapabilities()` 并投掷,如果 `crypto.subtle` 编号 : `crypto.getRandomValues` 无法使用。 没有 `Math.random()` 在SDK的任何地方都掉头.

你可以自己探测能力:

```typescript
import { checkCryptoCapabilities } from '@eternl/hub-wallet-sdk'

const caps = checkCryptoCapabilities()
// {
//   hasSubtleCrypto: boolean
//   hasRandomValues: boolean
//   hasRandomUUID:   boolean
//   isSecure:        boolean   // hasSubtleCrypto && hasRandomValues
// }
if (!caps.isSecure) {
  throw new Error('WebCrypto unavailable — SDK cannot run here')
}
```

## 限制——模块级单子状态

> 在设计您的整合之前先读一下.

几个核心模块 **整个进程单子状态**,而不是逐个状态:

-   **`useNatsClient`** ——一个NATS连接,一组回调,每个过程一个订阅地图. 通话 `useNatsClient()` 反复返回到 _不变_ 基础连接. `configure()` 覆盖先前配置的提供者/召回 。 每个过程只有一个连接.
-   **`useEncryption`** —— 说 `getCurrentKeyPair()` 编号 : `getCurrentSessionKey()` 跟踪单个“当前”密钥对 / 会话密钥. `generateKeyPair()` 财务报告和审定财务报表 `deriveSessionKeyHKDF()` 覆盖他们.
-   **`signingTransport`** —— 说 `configureSigningTransport(...)` 设置一个全局配置, 由所有签名可混凝土消耗 .
-   **`getWalletPresenceBeacon()`** ——返回全过程的单通信标(因此所有会话都共享一个生命周期-发送器订阅).
-   **`useReplayProtection` 编号 : `useSessionKeyCache`** — 模块级缓存按键 `pairId` 编号 : `deviceId`。 。 。 .

实际后果: SDK 设计用于 **每个 JS 上下文一个钱包客户端** (一个浏览器标签/一个节点进程). 没错 _没有_ 安全运行两个独立,不同配置的钱包客户端在同一过程中. 需要隔离的测试应当通过导出测试帮助器(例如. `__resetWalletPresenceBeaconForTests`, (中文(简体) ), `clearAllReplayCaches`, (中文(简体) ), `clearAllCachedKeys`)或以不同的工人流程运行.

`createWalletConnectorSession` 本身返回独立的每平方对象,但它构成单通NATS传输和(默认情况下)下单通存在信标.

## 配置

### 1\. 初始化SDK

```typescript
import { initHub, isInitialized, getConfig } from '@eternl/hub-wallet-sdk'

initHub({ network: 'mainnet' })

// With options:
initHub({
  network: 'preprod',                              // 'mainnet' | 'preprod' | 'preview'
  hubUrl: 'wss://custom-hub.example.com/nats',     // default: wss://hub.de-5.eternl.art/nats
  storage: myStorage,                              // optional HubStorage adapter
  storagePrefix: 'my-wallet',                      // optional localStorage key prefix
  constants: { CONNECTION_TIMEOUT_MS: 20_000 },    // optional protocol-constant overrides
})

if (isInitialized()) {
  console.log('network:', getConfig().network)
}
```

### 2\. 所需NATS配置 - `getNatsUrl` + 键 `getAuthenticator`

`useNatsClient` 已经 **没有** 从 `initHub`您必须拨打 `configure()` 至少有一个 `getNatsUrl` 连接前提供方 。 生产经纪人以违约方式进行交易,所以你还必须提供 `getAuthenticator` 那个薄荷糖 a **每笔捐款**。 。 。 .

```typescript
import { useNatsClient } from '@eternl/hub-wallet-sdk'
import { jwtAuthenticator } from '@nats-io/nats-core'

const nats = useNatsClient()

nats.configure({
  // Required. Returns the broker WebSocket URL.
  getNatsUrl: () => 'wss://hub.de-5.eternl.art/nats',

  // Per-pairing JWT model (R1.5 / C1.e).
  // Called immediately before EVERY connect attempt — including reconnects —
  // so you can mint a fresh per-pairing CLIENTS user JWT (e.g. POST /nats/token
  // on be-hub) each time. Returning `undefined` produces an anonymous
  // connection, which only works against local-dev / pre-R1.2 brokers.
  //
  // YOU are responsible for refresh-before-expiry: when the JWT nears `exp`,
  // call `disconnect()` then `connect()` to swap in a fresh token. The SDK
  // re-invokes this provider on the new connect.
  getAuthenticator: async () => {
    const { jwt, seed } = await mintPerPairingToken()  // your call to be-hub
    return jwtAuthenticator(jwt, new TextEncoder().encode(seed))
  },

  // Optional lifecycle / error callbacks:
  onConnected: () => console.log('connected'),
  onDisconnected: () => console.log('disconnected'),
  onError: (msg: string) => console.error('nats error:', msg),

  // Broker rejected the whole connection (AUTHORIZATION_VIOLATION). The SDK
  // does NOT auto-retry — clear your cached JWT, then either mint a fresh one
  // and call retry(), or surface a re-pair prompt.
  onPairRevoked: () => clearCachedJwtAndPromptRepair(),

  // Broker rejected a publish/subscribe (PERMISSIONS_VIOLATION). The
  // connection stays up; this fires per-subject for ACL denials.
  onPermissionsViolation: (subject?: string) => console.warn('ACL denied:', subject),
})

await nats.connect()
```

## 快速启动

最小端到端流:连接,确认连接是活的,然后运行钱包连接器会话进行对接.

```typescript
import {
  initHub,
  useNatsClient,
  configureSigningTransport,
  createWalletConnectorSession,
  type ApiCallResult,
} from '@eternl/hub-wallet-sdk'

// 1. Init
initHub({ network: 'mainnet' })

// 2. Configure + connect transport
const nats = useNatsClient()
nats.configure({
  getNatsUrl: () => 'wss://hub.de-5.eternl.art/nats',
  getAuthenticator: async () => myJwtAuthenticator(),  // see "Required NATS configuration"
})
await nats.connect()

// connectionStatus() is a getter, not getConnectionStatus()
if (nats.connectionStatus() !== 'connected') {
  throw new Error(`NATS not connected: ${nats.connectionStatus()}`)
}

// 3. Run a wallet connector session for an established pairing.
//    `encryption` and `transport` here implement hub-core's SessionEncryptionApi
//    and Transport — typically built from the pairing's derived session key and
//    the NATS client. See "WalletConnectorSession" below.
const session = createWalletConnectorSession({
  pairId,                       // string — the established pairing id
  encryption,                   // SessionEncryptionApi (from @eternl/hub-core)
  transport,                    // Transport (from @eternl/hub-core)
  grantedPermissions,           // Permission[]
  walletName: 'My Wallet',
  apiCallHandler: async ({ method, args, accountId }): Promise<ApiCallResult> => {
    // Invoke the underlying CIP-30 method and return its result.
    const result = await myWallet.invoke(method, args, accountId)
    return { success: true, result }
  },
})

session.on('apiCallReceived', ({ method }) => console.log('dApp called', method))
```

## API-钱包连接会议

`createWalletConnectorSession(options)` 在一个对齐上构建一个钱包作用会话 。 它拥有入境权 `apiCall` 调度、账户交换处理、访问/同意状态和存在。 导出 `WalletConnectorSession` 类是薄薄的包装器,可以返回工厂的结果.

### 选项( E)`WalletConnectorSessionOptions`(中文(简体) )

```typescript
interface WalletConnectorSessionOptions {
  pairId: string
  encryption: SessionEncryptionApi          // from @eternl/hub-core
  transport: Transport                       // from @eternl/hub-core
  grantedPermissions: Permission[]           // enforced for signing-method ACLs

  requireConsent?: boolean                   // default false → auto-grants all categories on start
  autoEstablishAllowed?: boolean             // default true
  walletName?: string

  apiCallHandler: ApiCallHandler             // REQUIRED
  accountSwitchHandler?: AccountSwitchHandler

  onPeerDisconnect?: (reason: string) => void
  onPeerPairingRemoved?: (reason: string) => void
  onError?: (err: Error) => void

  // Opt-in raw-stream presence (Option C):
  presenceC?: { deviceId: string; tabId: string; getNc: () => NatsConnection | null }
  presenceBeacon?: WalletPresenceBeaconApi   // override singleton (tests only)
}

type ApiCallResult =
  | { success: true; result: unknown }
  | { success: false; errorCode: string; errorMessage: string }

type ApiCallHandler = (params: {
  method: string
  args: unknown[]
  accountId: string
  requestId: string
}) => Promise<ApiCallResult>

type AccountSwitchHandler = (params: { accountId: string }) => Promise<boolean>
```

### 返回的 API (`WalletConnectorSessionApi`(中文(简体) )

```typescript
const session = createWalletConnectorSession(options)

session.pairId                  // string
session.accessGranted           // boolean
session.grantedCategories       // CIP30Category[] (copy)
session.grantedPermissions      // readonly Permission[]

// Consent (only needed when requireConsent: true; otherwise auto-granted on start)
await session.grantAccess(['core', 'governance', 'extension'], /* autoEstablishAllowed */ true)
await session.denyAccess('User declined')

// Push account changes to the dApp
await session.notifyAccountsChanged(accounts, activeAccountId, 'updated')

// Presence
await session.requestPresenceCheck(/* timeoutMs? */ 5000)        // Promise<boolean>
await session.requestPresenceCSnapshot()                         // Option C only; else resolves null
await session.notifyOnline()
await session.notifyOffline('tab hidden')

// Teardown
await session.disconnect('Wallet disconnected')   // sends DISCONNECT, then disposes
await session.remove('Pairing removed')           // sends PAIRING_REMOVED, then disposes
session.dispose()                                 // local cleanup, no message

// Events (returns an unsubscribe fn)
const off = session.on('apiCallReceived', ({ method, accountId, requestId }) => { /* … */ })
session.on('accountSwitchRequested', ({ accountId }) => { /* … */ })
session.on('pingReceived', ({ timestamp }) => { /* … */ })
session.on('heartbeatReceived', ({ timestamp }) => { /* … */ })
session.on('peerPresence', ({ status, reason, sentAt }) => { /* … */ })
session.on('peerPresenceC', (event) => { /* … */ })          // Option C only
session.on('accessStateChange', ({ granted, reason }) => { /* … */ })
session.on('error', (err) => { /* … */ })
off()
```

注释:

-   与 `requireConsent` 删除/`false`自动赠款 `['core','governance','extension']` 开始.
-   当未获准进入(或该方法的类别未获准)时,入境 `apiCall`s 以 `errorCode: '-3'` {\\fn黑体\\fs20\\shad2\\2ah82\\3ah20\\4ah33\\fscx95\\3ch592001\\be1}在你之前 {\\fn黑体\\fs20\\shad2\\2ah82\\3ah20\\4ah33\\fscx95\\3ch592001\\be1}你已经.. `apiCallHandler` 运行.
-   扔出一个处理器被浮出DApp `errorCode: '-2'`。 。 。 .
-   会议执行 `Symbol.dispose`这样 `using session = createWalletConnectorSession(...)` 在支持的地方工作.

## 远程签名( 请求方)

`useSigningRequestSender` 让钱包要求另一个对齐实例来签名交易。 这需要 `configureSigningTransport(...)` 和活跃的NATS连接.

```typescript
import {
  configureSigningTransport,
  useSigningRequestSender,
} from '@eternl/hub-wallet-sdk'

// Configure once, globally:
configureSigningTransport({
  getDeviceInfo:        () => myDeviceInfo,
  getMyDeviceId:        () => myDeviceId,
  getMyPeerId:          () => myPeerId,
  getSessionKey:        (deviceId) => sessionKeysByDevice[deviceId],   // base64 raw key | undefined
  getPairCryptoContext: async (deviceId) => ({
    pairingId,
    pinAadBytes,        // Uint8Array — first 8 bytes of SHA-256(pin)
  }),
  getNextSeq:           (pairingId) => nextSeqFor(pairingId),          // bigint, monotonic per pairing
})

const { requestRemoteSigning } = useSigningRequestSender()

const result = await requestRemoteSigning(targetInstance, transactionCBOR, {
  walletPubKeyHash: 'sha256-hex-of-wallet-pubkey',
  // walletId?: explicit id to skip the pubKeyHash lookup
  // timeoutMs?: overrides SIGNING_TIMEOUT_MS (default 120_000)
})

// result.status: 'idle' | 'requesting' | 'waiting' | 'approved' | 'rejected' | 'timeout' | 'error'
if (result.status === 'approved') {
  console.log('signed tx:', result.signedTxCBOR, 'witness:', result.witnessSet)
} else {
  console.error('signing failed:', result.error)
}
```

反应方请看 `useSigningRequestHandler` (解码/解码输入) `signing.request.encrypted`和 `useSigningResponseSender` (发布加密响应).

## 存在信标和原始流存在

```typescript
import {
  getWalletPresenceBeacon,
  createWalletPresenceCClient,
} from '@eternl/hub-wallet-sdk'

// Process-wide beacon — fans tab-visibility / page-unload transitions to all
// registered sessions. WalletConnectorSession registers itself automatically.
const beacon = getWalletPresenceBeacon()
console.log('registered sessions:', beacon.size)

// Standalone raw-stream (Option C) presence client (usually you'd use the
// session's `presenceC` option instead of building this directly):
const presenceC = createWalletPresenceCClient({
  pairId,
  deviceId,
  tabId,
  getNc: () => useNatsClient().getNatsConnection(),  // live nats.ws connection or null
  onPeerEvent: (event) => console.log('peer presence:', event.classification),
})
presenceC.start()
// … later
presenceC.stop()
```

## 低级可堆肥

### 使用 Nats 客户端

一个全流程的NATS WebSocket客户端. 状态通过 **获取器**,以及请求/答复用途 **`subscribeAndRequest`** (没有) `request()` 页:1.

```typescript
import { useNatsClient } from '@eternl/hub-wallet-sdk'

const nats = useNatsClient()
nats.configure({ getNatsUrl: () => 'wss://hub.de-5.eternl.art/nats', getAuthenticator })
await nats.connect()

// State getters
nats.connectionStatus()   // 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error'
nats.isConnected()        // boolean
nats.errorMessage()       // string
nats.reconnectAttempts()  // number

// Subscribe (JSON-parsed + schema-validated). Returns a Subscription | null.
const sub = nats.subscribe('hub.device.peer-id.message', (data) => {
  console.log('received:', data)
})

// Raw subscribe (no JSON parse/validation) — returns { unsubscribe } | null
const rawSub = nats.subscribeRaw('presence_out.pair.dapp', (bytes: Uint8Array) => { /* … */ })

// Publish
await nats.publish('hub.device.peer-id.message', { type: 'hello' })
await nats.publishRaw('subject', new Uint8Array([1, 2, 3]))

// Request/response (unique reply subject per call)
const res = await nats.subscribeAndRequest<MyResponse>(
  'hub.responses',          // responseSubject (a unique suffix is appended)
  'hub.requests',           // requestSubject
  { query: 'status' },      // requestData (a `replyTo` field is injected)
  { timeout: 5000 },
)
if (res.success) console.log(res.data)
else console.error(res.error)

// Cleanup
nats.unsubscribe('hub.device.peer-id.message')
nats.unsubscribeAll()
await nats.disconnect()
```

### 使用复制

ECDH(P-256) + HKDF-SHA256 会话键衍生和AES-256-GCM AEAD, 与 `@eternl/hub-core` v2. 现有 **没有** `deriveSharedSecret`; v2 管道为 `deriveSharedSecretRaw` → `deriveSessionKeyHKDF`加密为 **PIN 输入**: 每个帧的 AAD 是 `utf8(pairingId) || SHA-256(pin)[:8] || be64(seq)`。 。 。 .

```typescript
import { useEncryption } from '@eternl/hub-wallet-sdk'

const enc = useEncryption()

// Handshake
const keyPair    = await enc.generateKeyPair()                       // KeyPair | null
const myPubB64   = await enc.exportPublicKey(keyPair!.publicKey)      // string | null
const peerPub    = await enc.importPublicKey(peerPubB64)             // CryptoKey | null

// v2 derivation
const shared     = await enc.deriveSharedSecretRaw(keyPair!.privateKey, peerPub!) // Uint8Array | null
const sas6       = await enc.deriveSAS6(shared!, pin, pairingId)     // 6-digit string for UI confirm
const session    = await enc.deriveSessionKeyHKDF(shared!, pairingId, pin)        // SessionKey | null
// session.key — non-extractable CryptoKey;  session.raw — base64 raw key (persist with care)

// Encrypt — pass a CryptoKey and a context { pairingId, pin | pinAadBytes, seq }
let seq = 1n
const payload = await enc.encryptJSON(
  { hello: 'world' },
  session!.key,
  { pairingId, pin, seq },          // OR { pairingId, pinAadBytes, seq } for PIN-less callers
)

// Decrypt — seq is read from the payload, so the context omits it
const plain = await enc.decryptJSON<{ hello: string }>(
  payload!,
  session!.key,
  { pairingId, pin },               // OR { pairingId, pinAadBytes }
)

// Re-import a persisted raw key as a non-extractable CryptoKey
const imported = await enc.importSessionKey(session!.raw)            // CryptoKey | null

enc.cleanup()
```

`EncryptedPayload` 是一个 v2 信封 : `{ version: 2, iv, authTag, encrypted, algorithm, timestamp, messageId, pairingId, seq }` (何处) `seq` 是一个小数字符串。 解密任何非v2载荷的硬故障.

### 重放保护

记忆中,每..`pairId` 以 TTL 窗口消除信件复制. `checkAndRecordMessage` 实值 **同步** 返回时 `{ valid, error? }` (没有) `.reason` 和无所等待.

```typescript
import {
  generateMessageId,
  checkAndRecordMessage,
  clearReplayCache,
} from '@eternl/hub-wallet-sdk'

const messageId = generateMessageId()              // 'msg_…'
const timestamp = new Date().toISOString()

// Signature: checkAndRecordMessage(pairId, messageId, timestamp)
const check = checkAndRecordMessage('pair-id-123', messageId, timestamp)
if (!check.valid) {
  console.warn('rejected:', check.error)           // e.g. 'Replay attack detected: duplicate messageId'
}

clearReplayCache('pair-id-123')
```

(关于命名兼容性, `checkAndRecordMessage` 也作为 `checkAndRecordMessageAsync` ——尽管有名字,它仍然同步. )

### 使用会话密钥缓存

切片不可提取 `CryptoKey`键入 `deviceId`说明 `importSessionKey` 带 **`deviceId` 第一个**,然后是基础64生键.

```typescript
import {
  importSessionKey,
  getCachedKey,
  hasCachedKey,
  clearCachedKey,
  getCacheStats,
} from '@eternl/hub-wallet-sdk'

const key = await importSessionKey(deviceId, rawBase64)   // (deviceId, rawBase64) → CryptoKey | null

if (hasCachedKey(deviceId)) {
  const cached = getCachedKey(deviceId)                   // CryptoKey | null
}

const stats = getCacheStats()                             // { size: number; deviceIds: DeviceId[] }
clearCachedKey(deviceId)
```

### 使用MessageAuth 数据

HMAC-SHA256认证在 RFC-8785-canonicalized messages 上,由 Base64 生会话密钥字符串键入.

```typescript
import {
  signMessage,
  verifyMessage,
  createSignedMessage,
  verifyAndUnwrap,
} from '@eternl/hub-wallet-sdk'

const sig    = await signMessage({ type: 'presence', online: true }, sessionKeyRaw) // string | null
const ok     = await verifyMessage({ type: 'presence', online: true, signature: sig! }, sessionKeyRaw)
const signed = await createSignedMessage({ type: 'presence', online: true }, sessionKeyRaw) // T & { signature } | null
const orig   = await verifyAndUnwrap(signed!, sessionKeyRaw)  // T | null (null if invalid)
```

### 使用 Nats 校正

消息结构验证和类型守护.

```typescript
import {
  validateNatsMessage,
  isValidEncryptedPayload,
  parseAndValidatePayload,
} from '@eternl/hub-wallet-sdk'

const v = validateNatsMessage(message)   // { valid: boolean; error?: string; type?: string }
if (isValidEncryptedPayload(payload)) { /* … */ }
const parsed = parseAndValidatePayload(jsonString)
```

## 存储

这个 `HubStorage` 界面使用 **`getItem` 编号 : `setItem` 编号 : `removeItem`** (带有可选内容) `clear`),并同时支持同步(localStorage)和Async(IndexedDB)后端.

```typescript
import {
  createLocalStorage,
  createMemoryStorage,
  isLocalStorageAvailable,
  type HubStorage,
} from '@eternl/hub-wallet-sdk'

interface HubStorage {
  getItem(key: string): string | null | Promise<string | null>
  setItem(key: string, value: string): void | Promise<void>
  removeItem(key: string): void | Promise<void>
  clear?(): void | Promise<void>
}

// Built-in adapters
initHub({ network: 'mainnet', storage: createLocalStorage('my-wallet') })
initHub({ network: 'mainnet', storage: createMemoryStorage() })

// Custom (e.g. IndexedDB) adapter
const customStorage: HubStorage = {
  getItem:    (key) => myDB.get(key),
  setItem:    (key, value) => myDB.set(key, value),
  removeItem: (key) => myDB.delete(key),
}
initHub({ network: 'mainnet', storage: customStorage })
```

如果没有提供存储, `initHub` 可用时使用本地Storage,否则将返回内存.

## 公用事业

### 加农化(RFC 8785)

```typescript
import { canonicalize, createSigningPayload } from '@eternl/hub-wallet-sdk'

canonicalize({ b: 2, a: 1 })                       // '{"a":1,"b":2}'
createSigningPayload(message, ['signature'])       // canonical string with fields excluded
```

### 安全身份证

```typescript
import { generateSecureId, generateSecureMessageId, assertCryptoCapabilities } from '@eternl/hub-wallet-sdk'

assertCryptoCapabilities()         // throws if WebCrypto is unavailable
const id  = generateSecureId()     // 32-char hex (derived from crypto.randomUUID / getRandomValues)
const mid = generateSecureMessageId()  // 'msg_…' (alias of generateMessageId)
```

## 协议常数

所有协议常数导出为 **获取函数** 返回已解析值(默认值或通过 `initHub({ constants: … })`:

```typescript
import {
  CONNECTION_TIMEOUT_MS,           // () => 10_000
  SIGNING_TIMEOUT_MS,              // () => 120_000
  REPLAY_PROTECTION_TTL_MS,        // () => 300_000
  MAX_PAYLOAD_SIZE_BYTES,          // () => 1_048_576
  PRESENCE_HEARTBEAT_INTERVAL_MS,  // () => 5_000
  DEFAULT_HUB_URL,                 // 'wss://hub.de-5.eternl.art/nats' (string constant)
} from '@eternl/hub-wallet-sdk'

const timeout = CONNECTION_TIMEOUT_MS()   // call them
```

## 协议类型和权限

SDK从中再导出全套协议类型 `@eternl/hub-core` 及其自己的模块,加上 `Permission` 页:1 `AccountInfo`数字 :

```typescript
import type {
  DeviceId, PeerId, PairId, PairingId,
  DeviceInfo, PairedInstance, WalletRegistryEntry,
  SigningRequestMessage, EncryptedSigningRequestMessage,
  EncryptedPayload, SessionKey, KeyPair,
  ValidationResult, ReplayValidationResult,
  AccountInfo,
} from '@eternl/hub-wallet-sdk'

import { Permission } from '@eternl/hub-wallet-sdk'

const granted = [Permission.ADDRESS_READ, Permission.SIGN_TX, Permission.SIGN_DATA]
```

## 安保模式(摘要)

-   **端到端加密** ——AES-256-GCM 有随机的12字节IV和16字节Auth标记.
-   **PIN 绑定会话键** —— 说 `HKDF-SHA256` (P-256)共同的秘密,与 `SHA-256(utf8(pin) || pairingId)`; AAD 绑定 `pairingId`, (中文(简体) ), `SHA-256(pin)[:8]`单调 `seq`. 没有PIN,会议键无法仅取自欧洲人权法院的记录.
-   **标准6** ——6位数的短认证字符串,用于波段外配对确认.
-   **重放保护** 每个`pairId` 以 TTL 窗口和时钟- swew 容忍来消除信件重复 .
-   **HMAC 认证** ——HMAC-SHA256上传了RFC-8785卡通化消息.
-   **有效载荷验证** ——大小限制和对入境的NATS消息的检查.
-   **严格 v2 线** ——预v2加密有效载荷被硬拒绝(无静降级).

## 许可证

商业来源许可证1.1(`BUSL-1.1`) (中文(简体) ). 见 [自由主义](https://npmcn.com/package/%40eternl/hub-wallet-sdk/LICENSE)。 。 。 .

许可是Tastenkunst GmbH。 额外使用赠款 : `None` - 你可以复制、修改和制作 **非生产** 使用此软件包。 生产/商业用途需要从塔斯滕昆斯特获得单独的许可证。 在"更改日期"(每个版本首次出版的四周年)上,该版本会转换为GPL-2.0-或-后期.

联系人 : [https://tastenkunst.com/ (中文(简体) )](https://tastenkunst.com/)

## 常见问题

### @eternl/hub-wallet-sdk 是什么？

Eternl Hub SDK用于钱包开发者\.

### 如何安装 @eternl/hub-wallet-sdk？

在项目目录运行 npm install @eternl/hub-wallet-sdk，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：@nats-io/kv（3\.4\.0）、@nats-io/jetstream（3\.4\.0）、@nats-io/nats-core（3\.4\.0）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @eternl/hub-wallet-sdk 支持 TypeScript 吗？

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

### @eternl/hub-wallet-sdk 支持 ESM 还是 CommonJS？

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

### @eternl/hub-wallet-sdk 可以商用吗？

@eternl/hub-wallet-sdk 使用 BUSL-1\.1 许可。声明的许可为 BUSL-1\.1，请阅读许可原文确认使用条件。以上为概括，不构成法律意见，请以许可原文为准。

### @eternl/hub-wallet-sdk 的下载量有多少？

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

### @eternl/hub-wallet-sdk 还在维护吗？

最新版本 0\.55\.0 发布于 2026/11/10（3 天前）；至今共发布 69 个版本，其中过去 12 个月发布了 69 个；首次发布于 2026/05/08。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

