@eternl/hub-dapp-sdk (英语)
将您的 dApp 连接到 Cardano 钱包 埃特尔枢纽 ——不需要浏览器扩展. 对齐发生在一个使用短连接代码(或QR/深连接)的中继端到端加密通道上,由此产生的会话暴露出一个 CIP- 30 兼容的 API 加上几个Eternl扩展.
SDK飞船是框架不可知核加薄, 只有状态 反应和Vue的帮助者.
安装
npm install @eternl/hub-dapp-sdk
# or
pnpm add @eternl/hub-dapp-sdk
# or
yarn add @eternl/hub-dapp-sdk
react 财务报告和审定财务报表 vue 为: 选项 端点依赖性 – 只安装您的应用程序使用的一个 。 核心切入点@eternl/hub-dapp-sdk两者都不需要.
如何连接
有一个 没有 EternlConnect.connect() 外相为相. 你用两个核心功能驱动流动:
| 函数 | 作用 | 回返 |
|---|---|---|
createDAppConnection(options) | 供养者. DApp生成代码并等待钱包加入. | (单位:千美元) DAppConnectionHandle (单位:千美元){ code, dappId, expiresAt, channel, waitForWallet, cancel }(中文(简体) ) |
joinWithCode(code, options) | 验出者. DApp 输入钱包显示的代码 . | (单位:千美元) Promise<EternlSession> |
大多数 dApps 使用 createDAppConnection: 显示 handle.code 到用户,然后 await handle.waitForWallet() 接收 EternlSession。 。 。 .
安全模型: 您必须显示 SAS6
期间 waitForWallet,连接到 status: 'sas_ready' 带有 6 位数的短验证字符串( S)sas6(中文(简体) )。您的密码 必须使 sas6 切换到用户 这样他们就可以比较一下钱包中的数字.
如果两个代码吻合,没有男中音取代握手键. 这种比较是配对协议的核心防御—— 不要跳过渲染它. 一样 sas6 也出现在随后的 'confirmed' 活动.
快速启动(框架-不可知核心)
import { createDAppConnection } from '@eternl/hub-dapp-sdk';
import type { ConnectionStatusEvent, EternlSession } from '@eternl/hub-dapp-sdk';
async function connect(): Promise<EternlSession> {
const handle = await createDAppConnection({
appName: 'My DApp',
appUrl: 'https://mydapp.com',
appIcon: 'https://mydapp.com/icon.png',
permissions: ['read', 'sign'],
network: 'mainnet',
// Required against production hubs — see "NATS authentication" below.
getAuthenticator: myGetAuthenticator,
});
// 1. Show the connection code to the user. They enter it in their wallet.
console.log('Enter this code in Eternl:', handle.code);
console.log('Code expires at:', new Date(handle.expiresAt).toLocaleTimeString());
// 2. Wait for the wallet. The onStatus callback drives your UI, and it is
// where the SAS6 verification step surfaces.
const session = await handle.waitForWallet((event: ConnectionStatusEvent) => {
switch (event.status) {
case 'waiting_for_wallet':
console.log('Waiting for a wallet to enter the code…');
break;
case 'wallet_connected':
console.log('Wallet connected, exchanging keys…');
break;
case 'sas_ready':
// SECURITY-CRITICAL: render event.sas6 to the user so they can compare
// it with the 6 digits shown in their wallet before continuing.
console.log('Verify these digits match your wallet:', event.sas6);
break;
case 'awaiting_user':
console.log(`${event.walletName ?? 'Wallet'}: user is selecting accounts…`);
break;
case 'confirmed':
console.log('Connection confirmed.');
break;
case 'error':
case 'cancelled':
case 'timeout':
console.log('Connection ended:', event.status, event.message);
break;
}
});
// 3. Use the session (CIP-30 + Eternl extensions).
const networkId = await session.getNetworkId();
const ada = await session.getAdaBalance();
console.log('Connected:', session.activeAccount.name, networkId, `${ada} ADA`);
return session;
}
要在钱包加入前中止, 请打电话 handle.cancel()。 。 。 .
加入钱包显示的代码
如果你的流转相反 钱包 显示代码, 使用 joinWithCode数字 :
import { joinWithCode } from '@eternl/hub-dapp-sdk';
const session = await joinWithCode(userEnteredCode, {
appName: 'My DApp',
permissions: ['read', 'sign'],
getAuthenticator: myGetAuthenticator,
});
渲染 QR 代码/ 深链接
handle.code 是能展示的神话 如果您想要将其编码为可扫描的QR或移动深链接,请构建一个 PairingV2Payload 并使用编码器 :
import { generateQRData, generateDeepLink } from '@eternl/hub-dapp-sdk';
import type { PairingV2Payload } from '@eternl/hub-dapp-sdk';
const payload: PairingV2Payload = {
v: 2,
type: 'dapp',
pin: handle.code,
expiresAt: handle.expiresAt,
};
const qrString = generateQRData(payload); // "eternl-pair:<base64url(JSON)>" — feed to any QR encoder
const deepLink = generateDeepLink(payload); // "eternl://pair?p=<base64url(JSON)>"
NATS认证(生产时需要)
与一个硬化的NATS经纪人对流 默认拒绝匿名连接。您必须提供认证器:
- 提供
getAuthenticator- 一个async () => Authenticator | undefined从@nats-io/nats-core。 。 。 . - 若为
getAuthenticator省略(或决定undefined和unsafeAllowAnonymousNats未设置, 连接呼叫 掷出EternlConnectError与code === ErrorCode.AUTH_REQUIRED。 。 。 .
import { createDAppConnection, EternlConnectError, ErrorCode } from '@eternl/hub-dapp-sdk';
try {
const handle = await createDAppConnection({
appName: 'My DApp',
getAuthenticator: async () => myAuthenticator, // from @nats-io/nats-core
});
// …
} catch (err) {
if (err instanceof EternlConnectError && err.code === ErrorCode.AUTH_REQUIRED) {
console.error('No NATS authenticator was supplied.');
}
}
unsafeAllowAnonymousNats: true 是一个 仅供本地/dev经纪人使用. 对制作经纪人而言,匿名连接无声地降级——会话看起来对齐了,但不能发布——所以SDK默认拒绝.
注意:在标准生产设置中,SDK可以在内部铸造一个瞄准符,所以你可能不需要
getAuthenticator一点也不 以您的中枢部署确认哪个模式适用 。 不管怎样,规则是: 一个真正的连接需要认证, 或者它扔AUTH_REQUIRED。 。 。 .
反应
反应帮助者是 只有状态。 。 。 . useEternl() 已经 没有 连接 —— 它维持会话并暴露反应状态. 你的应用电话 createDAppConnection 编号 : joinWithCode 然后将会议交给 setSession。 。 。 .
useEternl() 返回 :
{
session: EternlSession | null;
error: EternlConnectError | null;
isConnected: boolean;
activeAccount: AccountInfo | null;
accounts: readonly AccountInfo[];
setSession: (session: EternlSession) => void; // install a connected session
disconnect: () => Promise<void>;
switchAccount: (accountId: string) => Promise<void>;
}
驱动模式——与核心连接,然后 setSession数字 :
import { useState } from 'react';
import { useEternl, EternlConnectionBadge } from '@eternl/hub-dapp-sdk/react';
import { createDAppConnection } from '@eternl/hub-dapp-sdk';
import type { ConnectionStatusEvent } from '@eternl/hub-dapp-sdk';
function App() {
const { isConnected, activeAccount, setSession, disconnect } = useEternl();
const [code, setCode] = useState<string | null>(null);
const [sas6, setSas6] = useState<string | null>(null);
async function handleConnect() {
const handle = await createDAppConnection({
appName: 'My DApp',
permissions: ['read', 'sign'],
getAuthenticator: myGetAuthenticator,
});
setCode(handle.code); // show the code to the user
const session = await handle.waitForWallet((event: ConnectionStatusEvent) => {
if (event.status === 'sas_ready') {
// SECURITY-CRITICAL: show this so the user can compare with their wallet.
setSas6(event.sas6 ?? null);
}
});
setSession(session); // hand the connected session to the hook
setCode(null);
setSas6(null);
}
if (!isConnected) {
return (
<div>
<button onClick={handleConnect}>Connect Wallet</button>
{code && <p>Enter this code in Eternl: <strong>{code}</strong></p>}
{sas6 && <p>Verify these digits match your wallet: <strong>{sas6}</strong></p>}
</div>
);
}
return (
<div>
<p>Connected: {activeAccount?.name}</p>
<button onClick={disconnect}>Disconnect</button>
{/* Floating badge with account switcher + disconnect */}
<EternlConnectionBadge
position="top-right"
onAccountChange={(account) => console.log('Switched to:', account.name)}
onDisconnect={() => console.log('Disconnected')}
/>
</div>
);
}
EternlConnectionBadge (反应)道具
interface EternlConnectionBadgeProps {
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; // default 'top-right'
className?: string;
style?: React.CSSProperties;
onAccountChange?: (account: AccountInfo) => void;
onDisconnect?: () => void;
}
徽章读取其状态 从相同的 useEternl() singleton),因此它使活期账户并提供一个账户切换器/断开一次 setSession 已被调用.
阴道
Vue 混凝土也一样 只有状态 —— 说 useEternl() 已经 没有 连接。 您驱动与核心函数的连接并调用 setSession。 。 。 .
useEternl() 返回(refs为被动反应):
{
session: Readonly<Ref<EternlSession | null>>;
error: Readonly<Ref<EternlConnectError | null>>;
isConnected: ComputedRef<boolean>;
activeAccount: ComputedRef<AccountInfo | null>;
accounts: ComputedRef<readonly AccountInfo[]>;
setSession: (session: EternlSession) => void; // install a connected session
disconnect: () => Promise<void>;
switchAccount: (accountId: string) => Promise<void>;
}
SFC 中的驱动模式 :
<script setup lang="ts">
import { ref } from 'vue';
import { useEternl, EternlConnectionBadge } from '@eternl/hub-dapp-sdk/vue';
import { createDAppConnection } from '@eternl/hub-dapp-sdk';
import type { ConnectionStatusEvent } from '@eternl/hub-dapp-sdk';
const { isConnected, activeAccount, setSession, disconnect } = useEternl();
const code = ref<string | null>(null);
const sas6 = ref<string | null>(null);
async function handleConnect() {
const handle = await createDAppConnection({
appName: 'My DApp',
permissions: ['read', 'sign'],
getAuthenticator: myGetAuthenticator,
});
code.value = handle.code; // show the code to the user
const session = await handle.waitForWallet((event: ConnectionStatusEvent) => {
if (event.status === 'sas_ready') {
// SECURITY-CRITICAL: show this so the user can compare with their wallet.
sas6.value = event.sas6 ?? null;
}
});
setSession(session); // hand the connected session to the composable
code.value = null;
sas6.value = null;
}
</script>
<template>
<div v-if="!isConnected">
<button @click="handleConnect">Connect Wallet</button>
<p v-if="code">Enter this code in Eternl: <strong>{{ code }}</strong></p>
<p v-if="sas6">Verify these digits match your wallet: <strong>{{ sas6 }}</strong></p>
</div>
<div v-else>
<p>Connected: {{ activeAccount?.name }}</p>
<button @click="disconnect">Disconnect</button>
<!-- Floating badge with account switcher + disconnect -->
<EternlConnectionBadge
position="top-right"
@account-change="(account) => console.log('Switched to:', account.name)"
@disconnect="() => console.log('Disconnected')"
/>
</div>
</template>
EternlConnectionBadge (Vue) 道具和事件
- 道具 :
position(单位:千美元)'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'默认值'top-right', (中文(简体) ),className(弦声). - 活动 :
account-change(包括新的AccountInfo, (中文(简体) ),disconnect。 。 。 .
EternlSession 参考文献
一旦连接, 会话是您的 CIP- 30 表面加上一些 Eternl 扩展 。 除非注意,否则所有方法都是协同的.
状态( 只读属性)
| 财产 | 类型 | 页:1 |
|---|---|---|
pairId | string | 本次会话的64hex配对标识符. |
transport | TransportType | 使用中的基础运输. |
expiresAt | Date | 届会结束后. |
isConnected | boolean | 交通运输互联互通. |
walletOnline | boolean | 配对钱包目前是否可以到达 . |
accessGranted | boolean | 钱包是否允许此 dApp 访问( 同意层, 区别于 isConnected) (中文(简体) ). |
grantedCategories | ReadonlyArray<'core' | 'governance' | 'extension'> | 给予钱包的方法分类 . |
grantedPermissions | readonly Permission[] | 获得CIP-30/CIP-95许可. |
deviceInfo | WalletDeviceInfo | null | 连接钱包的设备信息 . |
availableAccounts | readonly AccountInfo[] | 钱包共享所有账户 . |
activeAccount | AccountInfo | 当前活动账户 . |
CIP-30 核心项目
session.getNetworkId(); // Promise<number>
session.getUtxos(amount?, paginate?); // Promise<string[] | null>
session.getBalance(); // Promise<string> (CBOR-encoded value)
session.getUsedAddresses(paginate?); // Promise<string[]>
session.getUnusedAddresses(); // Promise<string[]>
session.getChangeAddress(); // Promise<string>
session.getRewardAddresses(); // Promise<string[]>
session.signTx(txCbor, partialSign?); // Promise<string> (witness set / signed tx CBOR)
session.signData(address, payloadHex); // Promise<DataSignature>
session.submitTx(txCbor); // Promise<string> (tx hash)
CIP-30 扩展
session.getExtensions(); // Promise<{ cip: number }[]>
session.getCollateral(params?); // Promise<string[] | null>
CIP-95 治理
session.getPubDRepKey(); // Promise<string>
session.getRegisteredPubStakeKeys(); // Promise<string[]>
session.getUnregisteredPubStakeKeys(); // Promise<string[]>
永久扩展
session.getAdaBalance(); // Promise<number> — balance in ADA, already parsed
session.requestPresenceCheck(); // Promise<boolean> — actively probe wallet reachability
session.switchAccount(accountId); // Promise<void>
session.disconnect(); // Promise<void> — tear down this session
session.remove(); // Promise<void> — disconnect and forget the pairing
活动 —— 说 session.on(event, listener) 返回未订阅的函数 :
const off = session.on('accountChange', (account) => { /* AccountInfo */ });
session.on('accountsChanged', (e) => { /* { accounts, activeAccountId } */ });
session.on('walletStatusChange', (e) => { /* { online: boolean } */ });
session.on('connectionChange', (e) => { /* { isConnected: boolean } */ });
session.on('accessGranted', (e) => { /* { grantedCategories, autoEstablishAllowed, walletName? } */ });
session.on('accessDenied', (e) => { /* { reason: string } */ });
session.on('peerPresenceC', (e) => { /* fast (~5s) presence event; requires enablePresenceC */ });
session.on('error', (err) => { /* Error */ });
off(); // unsubscribe
这个 peerPresenceC 仅当您通过时发生火灾 enablePresenceC: true 内 DAppConnectionOptions它提供比后遗症心跳路径更快的(~5s)离线检测,并与其并列运行.
DAppConnectionOptions
共享 createDAppConnection 财务报告和审定财务报表 joinWithCode数字 :
interface DAppConnectionOptions {
appName: string; // required — shown to the user
appUrl?: string; // defaults to window.location.origin
appIcon?: string;
permissions?: string[]; // e.g. ['read', 'sign'] (default)
network?: string; // defaults to 'mainnet'
hubUrl?: string; // NATS WebSocket URL; defaults to the public hub
hubRestUrl?: string; // Hub REST URL; defaults to the public hub
getAuthenticator?: () => Promise<Authenticator | undefined>; // from @nats-io/nats-core
unsafeAllowAnonymousNats?: boolean; // dev/local only — see "NATS authentication"
enablePresenceC?: boolean; // opt into fast raw-stream presence (peerPresenceC)
}
处理错误
import { EternlConnectError, ErrorCode } from '@eternl/hub-dapp-sdk';
try {
const handle = await createDAppConnection({ appName: 'My DApp', getAuthenticator });
const session = await handle.waitForWallet();
} catch (error) {
if (error instanceof EternlConnectError) {
switch (error.code) {
case ErrorCode.AUTH_REQUIRED: /* no NATS authenticator supplied */ break;
case ErrorCode.TIMEOUT: /* code expired before a wallet joined */ break;
case ErrorCode.REJECTED: /* user rejected the connection */ break;
case ErrorCode.CANCELLED: /* connection was cancelled */ break;
case ErrorCode.TRANSPORT_ERROR: /* network / NATS error */ break;
default: console.error(error.code, error.message);
}
}
}
公用事业结余
import {
parseLovelaceBalance,
lovelaceToAda,
formatAda,
parseAndFormatAda,
} from '@eternl/hub-dapp-sdk';
const lovelace = parseLovelaceBalance(await session.getBalance()); // CBOR → lovelace
const ada = lovelaceToAda(lovelace); // 1_000_000 → 1
const formatted = formatAda(lovelace); // "1.234567 ADA"
const display = parseAndFormatAda(await session.getBalance()); // CBOR → "1.234567 ADA"
(单位:千美元)session.getAdaBalance() 以一召而为你们的教诲
会话持久性和配对钱包( C)
核心还输出帮助者坚持并恢复连接而无需重新铺平:
import {
hasStoredSession,
getStoredSessionInfo,
restoreSession,
clearStoredSession,
getPairedWallets,
reconnectToWallet,
removePairedWallet,
clearPairedWallets,
} from '@eternl/hub-dapp-sdk';
if (hasStoredSession()) {
const session = await restoreSession({ getAuthenticator: myGetAuthenticator });
}
const wallets = getPairedWallets(); // PairedWallet[]
const session = await reconnectToWallet(wallets[0].walletId, { getAuthenticator: myGetAuthenticator });
restoreSession 财务报告和审定财务报表 reconnectToWallet 接受相同的建议 getAuthenticator 编号 : unsafeAllowAnonymousNats 验证上述选项,并同样投出 AUTH_REQUIRED 没有他们.
子路径导出
| 导入路径 | 目录 |
|---|---|
@eternl/hub-dapp-sdk | 核心 : createDAppConnection, (中文(简体) ), joinWithCode, (中文(简体) ), generateQRData, (中文(简体) ), generateDeepLink、会话/助演者/助演者, Permission, (中文(简体) ), EternlConnectError, (中文(简体) ), ErrorCode,平衡公用事业,以及各类公用事业. |
@eternl/hub-dapp-sdk/react | useEternl, (中文(简体) ), EternlConnectionBadge (状态唯一的反应帮助者). |
@eternl/hub-dapp-sdk/vue | useEternl, (中文(简体) ), EternlConnectionBadge (仅限状态Vue 3助攻). |
类型脚本
软件包已完全输入 。 实际出口类型包括:
import type {
// Core connection
DAppConnectionOptions,
DAppConnectionHandle,
DAppSession,
ConnectionStatus,
ConnectionStatusEvent,
PairingV2Payload,
// Session
EternlSession,
AccountInfo,
ConnectionState,
// Events
SessionEventType,
SessionEventListener,
ConnectionChangeEvent,
WalletStatusEvent,
AccessGrantedEvent,
AccessDeniedEvent,
AccountsChangedEvent,
// Wallet device
WalletDeviceInfo,
WalletPlatform,
// Paired wallets
PairedWallet,
// CIP-30
NetworkType,
TransportType,
Paginate,
DataSignature,
CollateralParams,
} from '@eternl/hub-dapp-sdk';
// Value exports
import { Permission, EternlConnectError, ErrorCode } from '@eternl/hub-dapp-sdk';
先前记录
EternlConnectConfig财务报告和审定财务报表ConnectionRequest类型做 没有 已存在,不导出.
许可证
商业来源许可证1.1(BUSL-1.1) (中文(简体) ). 见 自由主义。 。 。 .
许可是Tastenkunst GmbH。 额外使用赠款 : None - 你可以复制、修改和制作 非生产 使用此软件包。 生产/商业用途需要从塔斯滕昆斯特获得单独的许可证。 在"更改日期"(每个版本首次出版的四周年)上,该版本会转换为GPL-2.0-或-后期.