@eos3/connect
Framework-neutral browser SDK for EOS Passkey Connect.
Use this package inside a Telegram Mini App to create or restore an EOS wallet, store the Bot-scoped local payment key in Telegram SecureStorage, unlock it with Telegram biometrics, sign paylimit transfers locally, and push them through the EOS Passkey API.
Install
npm install @eos3/connectThe SDK expects a browser runtime with fetch, Web Crypto, and Telegram WebApp
APIs when real wallet creation or payment signing is used.
It does not bundle Telegram's script into the main SDK bundle; call
loadEosConnectTelegramWebAppSdk() or include Telegram's official
https://telegram.org/js/telegram-web-app.js script before wallet flows.
Minimal Setup
import {
createEosConnect,
isEosConnectError,
normalizeEosConnectError
} from '@eos3/connect';
const eosConnect = createEosConnect({
network: 'testnet',
apiBaseUrl: 'https://your-wallet-api.example.com',
botUsername: 'your_bot',
locale: 'zh-CN',
defaultConnectOptions: {
replaceWallet: true,
assetLimits: [
{
tokenContract: 'core.vaulta',
symbol: 'A',
precision: 4,
perTxLimit: '1',
dailyLimit: '10',
totalBudget: '100'
}
]
}
});
eosConnect.subscribe((state) => {
console.log('EOS wallet state:', state);
});
await eosConnect.bootstrapTelegram();
const quickPay = await eosConnect.checkQuickPay();
if (!quickPay.enabled) {
await eosConnect.enableQuickPay();
}network can be testnet or mainnet. It selects browser signing RPC
failover list, chain id metadata, and balance asset. The SDK does not include a
hosted wallet API or a Telegram bot. If apiBaseUrl is omitted, requests use
same-origin paths such as /api/tg-wallet/me. apiBaseUrl points to the EOS
Passkey API or to your same-origin proxy for that API; it is not the passkey bind
page URL.
When botUsername is set, the SDK sends it as x-telegram-bot-username so a
shared backend can choose the correct Telegram bot token for initData
verification.
enableQuickPay(), startTelegramWalletFlow(), and connectTelegram() open the bindUrl returned by the API. The API should
generate that URL from its WEB_BASE_URL, so users leave the Mini App and finish
passkey authentication on the hosted passkey binding page.
Internationalization
Quick payment labels, Wallet ViewModel text, and the built-in payment confirmation sheet support
English and Simplified Chinese.
The SDK uses explicit locale, then Telegram language_code, then
navigator.language, and falls back to English:
const eosConnect = createEosConnect({
telegramWebApp,
locale: 'zh-CN',
messages: {
walletSetupTitle: '开启钱包',
paymentConfirmTitle: '确认付款',
paymentConfirmAction: '支付'
}
});Quick Payment Flow
For app UIs, prefer checkQuickPay() and enableQuickPay(). They hide
low-level wallet capability statuses such as needs_local_key, open pending
bind URLs, rebind the current device when the local payment key is missing, and
can poll until quick payment is enabled:
const quickPay = await eosConnect.checkQuickPay();
renderQuickPay({
enabled: quickPay.enabled,
account: quickPay.account,
balance: quickPay.balance,
buttonLabel: quickPay.enabled ? '快捷支付已开启' : '开启快捷支付'
});
if (!quickPay.enabled) {
const next = await eosConnect.enableQuickPay({
pollIntervalMs: 1500,
timeoutMs: 120_000
});
renderQuickPay(next);
}enableQuickPay() performs the common Telegram setup sequence:
- loads Telegram's WebApp SDK when needed;
- initializes
BiometricManagerand checks SecureStorage support; - calls
checkWallet(); - opens an existing
pendingbindUrl; - calls
connectTelegram({ replaceWallet: true })when the current device is missing the local payment key; - polls wallet readiness until
ready, another terminal state, or timeout.
Advanced UIs can still call getWalletView() or startTelegramWalletFlow()
when they need debug-level states and labels. Pass waitForReady: false if
your UI should return immediately after opening the binding page.
Connect a Telegram Wallet
const state = await eosConnect.connectTelegram({
assetLimits: [
{
tokenContract: 'core.vaulta',
symbol: 'A',
precision: 4,
perTxLimit: '1',
dailyLimit: '10',
totalBudget: '100'
}
]
});
if (state.status === 'pending' && state.bindUrl) {
// The SDK opens bindUrl by default. Keep this branch for custom UI.
console.log('Continue passkey binding:', state.bindUrl);
}connectTelegram() generates a local EOS K1 payment key, encrypts the private
key with AES-GCM, stores the encrypted envelope in Telegram SecureStorage, saves
the unlock key as the Telegram biometric token, and starts the external passkey
binding flow.
Do not fall back to localStorage for the private key. If Telegram
SecureStorage or BiometricManager is missing, ask the user to open the Mini App
in a supported Telegram mobile client.
Before showing a real connect action, prefer the async storage check. It calls
BiometricManager.init() before reading Telegram's capability flags:
import { checkEosConnectTelegramPayStorage } from '@eos3/connect';
const storage = await checkEosConnectTelegramPayStorage(telegramWebApp);
if (!storage.supported) {
console.table(storage.diagnostics);
}Check Readiness
const capability = await eosConnect.checkWallet();
if (capability.status === 'ready') {
console.log('Ready to pay from', capability.account);
}
if (capability.status === 'needs_local_key') {
// The server wallet exists, but this Telegram device does not have the
// matching encrypted local payment key. Prompt the user to rebind.
await eosConnect.connectTelegram({ replaceWallet: true });
}
if (capability.status === 'pending' && capability.bindUrl) {
telegramWebApp?.openLink?.(capability.bindUrl);
}Common capability statuses:
unsupported: missing Telegram initData or secure Telegram APIs.not_connected: no wallet exists yet.pending: account creation or tgpay binding is not finished.needs_local_key: wallet exists, but this device cannot sign quick payments.ready: wallet and local payment key match.
Pay
try {
const result = await eosConnect.pay({
to: 'merchant1111',
amount: '1.0000',
memo: 'order-123',
tokenContract: 'core.vaulta',
symbol: 'A'
});
console.log('Pushed transaction:', result.txid);
} catch (error) {
const normalized = normalizeEosConnectError(error);
console.error(normalized.code, normalized.message, normalized.retryable);
}pay() uses these API endpoints:
POST /api/tg-wallet/transfer/buildPOST /api/tg-wallet/transfer/push
The default confirmation sheet can be disabled or replaced:
const eosConnect = createEosConnect({
network: 'testnet',
telegramWebApp,
confirmPayment: async (details) => {
return window.confirm(`Pay ${details.quantity} to ${details.to}?`);
}
});For production business actions, prefer project-specific build and push endpoints that validate contract, action, authorization, amount, memo, and any business parameters before broadcasting.
Custom Transaction Signing
Use signEosConnectTransaction() when your own backend builds a validated
transaction and expects a signed payload:
import { signEosConnectTransaction } from '@eos3/connect';
const signedTransaction = await signEosConnectTransaction(transaction, {
telegramWebApp,
rpcUrls: ['https://jungle4.cryptolions.io:443']
});
await fetch('https://wallet.example.com/api/market/push', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
initData: telegramWebApp?.initData,
signedTransaction
})
});API Summary
createEosConnect(options): creates the SDK client.client.getProviders(): returns wallet provider metadata.client.getSnapshot(): returns the current state.client.subscribe(listener): subscribes to state changes.client.bootstrapTelegram(): loads Telegram WebApp SDK, initializes biometrics, and checks secure storage support.client.restore(): loads wallet state from the API.client.refreshWallet(): refreshes wallet readiness and returns a UI-ready ViewModel.client.checkWallet(): checks server wallet plus local key readiness.client.checkQuickPay(): returns{ enabled, account, balance, reason }for business UI.client.getWalletView(): returns the current wallet ViewModel for app UIs.checkEosConnectTelegramPayStorage(app): initializes Telegram biometrics and returns secure storage diagnostics.client.connectTelegram(options): starts or resumes Telegram binding.client.connectTokenPocket(options): starts TokenPocket binding.client.startTelegramWalletFlow(options): runs the high-level Telegram wallet setup flow.client.enableQuickPay(options): runs the high-level setup flow and returns quick payment readiness.client.pay(options): builds, signs, confirms, and pushes a paylimit payment.client.disconnect(): removes the local Telegram payment key from SecureStorage, clears the biometric token, and resets the SDK state.
Network Presets
import { EOS_CONNECT_NETWORKS } from '@eos3/connect';
console.log(EOS_CONNECT_NETWORKS.testnet.rpcUrls);
console.log(EOS_CONNECT_NETWORKS.mainnet.chainId);Preset values:
testnet: Jungle4 chain id, Jungle4 RPC failover list, and default assetcore.vaulta:A:4.mainnet: EOS mainnet chain id, mainnet RPC failover list, and default assetcore.vaulta:A:4.
Explicit options always win:
createEosConnect({
network: 'testnet',
apiBaseUrl: 'https://my-testnet-wallet.example.com',
rpcUrls: ['https://my-jungle-rpc.example.com'],
telegramWebApp
});Error Handling
Use normalizeEosConnectError(error) at UI boundaries. It maps raw API, EOS RPC,
Telegram biometric, and contract errors into stable codes:
DAILY_LIMIT_EXCEEDEDPER_TX_LIMIT_EXCEEDEDTOTAL_BUDGET_EXCEEDEDINSUFFICIENT_BALANCERAM_INSUFFICIENTPAYMENT_PERMISSION_MISSINGSIGNATURE_REJECTEDSESSION_EXPIREDNETWORK_ERRORUNKNOWN_CHAIN_ERROR
catch (error) {
const normalized = normalizeEosConnectError(error);
if (normalized.retryable) {
// Show a retry action.
}
}Security Notes
- Never send the local payment private key or biometric unlock token to a server.
- Never store the private key in
localStorage, IndexedDB, cookies, or plain Telegram CloudStorage. - Treat missing Telegram SecureStorage or BiometricManager as unsupported.
- Keep
TELEGRAM_BOT_TOKEN, account creator keys, resource sponsor keys, and contract deployment keys on the backend only.