npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@ton/walletkit

v0.0.3

Published

Wallet kit for TON Connect

Readme

TonWalletKit

A production-ready wallet-side integration layer for TON Connect, designed for building TON wallets at scale

npm @ton/walletkit version Release Basic build and tests e2e extension e2e web

Overview

  • 🔗 TON Connect Protocol - Handle connect/disconnect/transaction/sign-data requests
  • 💼 Wallet Management - Multi-wallet support with persistent storage
  • 🌉 Bridge & JS Bridge - HTTP bridge and browser extension support
  • 🎨 Previews for actions - Transaction emulation with money flow analysis
  • 🪙 Asset Support - TON, Jettons, NFTs with metadata

Live Demo: https://walletkit-demo-wallet.vercel.app/

Documentation

DeepWiki

Tutorials

Quick start

This guide shows how to integrate @ton/walletkit into your app with minimal boilerplate. It abstracts TON Connect and wallet implementation details behind a clean API and UI-friendly events.

After you complete this guide, you'll have your wallet fully integrated with the TON ecosystem. You'll be able to interact with dApps, NFTs, and jettons.

npm install @ton/walletkit

Initialize the kit

import { 
  TonWalletKit,      // Main SDK class
  Signer,            // Handles cryptographic signing
  WalletV5R1Adapter, // Latest wallet version (recommended)
  CHAIN,             // Network constants (MAINNET/TESTNET)
} from '@ton/walletkit';

const kit = new TonWalletKit({
  // Multi-network API configuration
  networks: {
    [CHAIN.MAINNET]: {
      apiClient: {
        // Optional API key for Toncenter get on https://t.me/toncenter
        key: process.env.APP_TONCENTER_KEY,
        url: 'https://toncenter.com', // default
        // or use self-hosted from https://github.com/toncenter/ton-http-api
      },
    },
    // Optionally configure testnet as well
    // [CHAIN.TESTNET]: {
    //   apiClient: {
    //     key: process.env.APP_TONCENTER_KEY_TESTNET,
    //     url: 'https://testnet.toncenter.com',
    //   },
    // },
  },
  bridge: {
    // TON Connect bridge for dApp communication
    bridgeUrl: 'https://connect.ton.org/bridge',
    // or use self-hosted bridge from https://github.com/ton-connect/bridge
  },
});

// Wait for initialization to complete
await kit.waitForReady();

// Add a wallet from mnemonic (24-word seed phrase) ton or bip39
const mnemonic = process.env.APP_MNEMONIC!.split(' ');
const signer = await Signer.fromMnemonic(mnemonic, { type: 'ton' });

const walletV5R1Adapter = await WalletV5R1Adapter.create(signer, {
  client: kit.getApiClient(CHAIN.MAINNET),
  network: CHAIN.MAINNET,
});

const walletV5R1 = await kit.addWallet(walletV5R1Adapter);
if (walletV5R1) {
  console.log('V5R1 Address:', walletV5R1.getAddress());
  console.log('V5R1 Balance:', await walletV5R1.getBalance());
}

Understanding previews (for your UI)

Before handling requests, it's helpful to understand the preview data that the kit provides for each request type. These previews help you display user-friendly confirmation dialogs.

  • ConnectPreview (req.preview): Information about the dApp asking to connect. Includes manifest (name, description, icon), requestedItems, and permissions your UI can show before approval.
  • TransactionPreview (tx.preview): Human-readable transaction summary. On success, preview.moneyFlow.ourTransfers contains an array of net asset changes (TON and jettons) with positive amounts for incoming and negative for outgoing. preview.moneyFlow.inputs and preview.moneyFlow.outputs show raw TON flow, and preview.emulationResult has low-level emulation details. On error, preview.result === 'error' with an emulationError.
  • SignDataPreview (sd.preview): Shape of the data to sign. kind is 'text' | 'binary' | 'cell'. Use this to render a safe preview.

You can display these previews directly in your confirmation modals.

Listen for requests from dApps

Register callbacks that show UI and then approve or reject via kit methods. Note: getSelectedWalletAddress() is a placeholder for your own wallet selection logic.

// Connect requests - triggered when a dApp wants to connect
kit.onConnectRequest(async (req) => {
  try {
    // Use req.preview to display dApp info in your UI
    const name = req.dAppInfo?.name;
    if (confirm(`Connect to ${name}?`)) {
      // Set wallet address on the request before approving
      req.walletAddress = getSelectedWalletAddress(); // Your wallet selection logic
      await kit.approveConnectRequest(req);
    } else {
      await kit.rejectConnectRequest(req, 'User rejected');
    }
  } catch (error) {
    console.error('Connect request failed:', error);
    await kit.rejectConnectRequest(req, 'Error processing request');
  }
});

// Transaction requests - triggered when a dApp wants to execute a transaction
kit.onTransactionRequest(async (tx) => {
  try {
    // Use tx.preview.moneyFlow.ourTransfers to show net asset changes
    // Each transfer shows positive amounts for incoming, negative for outgoing
    if (confirm('Do you confirm this transaction?')) {
      await kit.approveTransactionRequest(tx);
    } else {
      await kit.rejectTransactionRequest(tx, 'User rejected');
    }
  } catch (error) {
    console.error('Transaction request failed:', error);
    await kit.rejectTransactionRequest(tx, 'Error processing request');
  }
});

// Sign data requests - triggered when a dApp wants to sign arbitrary data
kit.onSignDataRequest(async (sd) => {
  try {
    // Use sd.preview.kind to determine how to display the data
    if (confirm('Sign this data?')) {
      await kit.signDataRequest(sd);
    } else {
      await kit.rejectSignDataRequest(sd, 'User rejected');
    }
  } catch (error) {
    console.error('Sign data request failed:', error);
    await kit.rejectSignDataRequest(sd, 'Error processing request');
  }
});

// Disconnect events - triggered when a dApp disconnects
kit.onDisconnect((evt) => {
  // Clean up any UI state related to this connection
  console.log(`Disconnected from wallet: ${evt.walletAddress}`);
});

Handle TON Connect links

When users scan a QR code or click a deep link from a dApp, pass the TON Connect URL to the kit. This will trigger your onConnectRequest callback.

// Example: from a QR scanner, deep link, or URL parameter
async function onTonConnectLink(url: string) {
  // url format: tc://connect?...
  await kit.handleTonConnectUrl(url);
}

Basic wallet operations

// Get wallet instance (getSelectedWalletAddress is your own logic)
const address = getSelectedWalletAddress();
const current = kit.getWallet(address);
if (!current) return;

// Query balance
const balance = await current.getBalance();
console.log(address, balance.toString());

Rendering previews (reference)

The snippets below mirror how the demo wallet renders previews in its modals. Adapt them to your UI framework.

Render Connect preview:

function renderConnectPreview(req: EventConnectRequest) {
  const name = req.preview.manifest?.name ?? req.dAppInfo?.name;
  const description = req.preview.manifest?.description;
  const iconUrl = req.preview.manifest?.iconUrl;
  const permissions = req.preview.permissions ?? [];

  return {
    title: `Connect to ${name}?`,
    iconUrl,
    description,
    permissions: permissions.map((p) => ({ title: p.title, description: p.description })),
  };
}

Render Transaction preview (money flow overview):

import type { MoneyFlowSelf } from '@ton/walletkit';

function summarizeTransaction(preview: TransactionPreview) {
  if (preview.result === 'error') {
    return { kind: 'error', message: preview.emulationError.message } as const;
  }

  // MoneyFlow now provides ourTransfers - a simplified array of net asset changes
  const transfers = preview.moneyFlow.ourTransfers; // Array of MoneyFlowSelf

  // Each transfer has:
  // - type: 'ton' | 'jetton'
  // - amount: string (positive for incoming, negative for outgoing)
  // - jetton?: string (jetton master address, if type === 'jetton')

  return {
    kind: 'success' as const,
    transfers: transfers.map((transfer) => ({
      type: transfer.type,
      jettonAddress: transfer.type === 'jetton' ? transfer.jetton : 'TON',
      amount: transfer.amount, // string, can be positive or negative
      isIncoming: BigInt(transfer.amount) >= 0n,
    })),
  };
}

Example UI rendering:

function renderMoneyFlow(transfers: MoneyFlowSelf[]) {
  if (transfers.length === 0) {
    return <div>This transaction doesn't involve any token transfers</div>;
  }

  return transfers.map((transfer) => {
    const amount = BigInt(transfer.amount);
    const isIncoming = amount >= 0n;
    const jettonAddress = transfer.type === 'jetton' ? transfer.jetton : 'TON';

    return (
      <div key={jettonAddress}>
        <span>{isIncoming ? '+' : ''}{transfer.amount}</span>
        <span>{jettonAddress}</span>
      </div>
    );
  });
}

Render Sign-Data preview:

function renderSignDataPreview(preview: SignDataPreview) {
  switch (preview.kind) {
    case 'text':
      return { type: 'text', content: preview.content };
    case 'binary':
      return { type: 'binary', content: preview.content };
    case 'cell':
      return {
        type: 'cell',
        content: preview.content,
        schema: preview.schema,
        parsed: preview.parsed,
      };
  }
}

Tip: For jetton names/symbols and images in transaction previews, you can enrich the UI using:

const info = kit.jettons.getJettonInfo(jettonAddress);
// info?.name, info?.symbol, info?.image

Sending assets programmatically

You can create transactions from your wallet app (not from dApps) and feed them into the regular approval flow via handleNewTransaction. This triggers your onTransactionRequest callback, allowing the same UI confirmation flow for both dApp and wallet-initiated transactions.

Send TON

import type { TonTransferParams } from '@ton/walletkit';

const from = kit.getWallet(getSelectedWalletAddress());
if (!from) throw new Error('No wallet');

const tonTransfer: TonTransferParams = {
  toAddress: 'EQC...recipient...',
  amount: (1n * 10n ** 9n).toString(), // 1 TON in nanotons
  // Optional comment OR body (base64 BOC), not both
  comment: 'Thanks!'
};

// 1) Build transaction content
const tx = await from.createTransferTonTransaction(tonTransfer);

// 2) Route into the normal flow (triggers onTransactionRequest)
await kit.handleNewTransaction(from, tx);

Send Jettons (fungible tokens)

import type { JettonTransferParams } from '@ton/walletkit';

const wallet = kit.getWallet(getSelectedWalletAddress());
if (!wallet) throw new Error('No wallet');

const jettonTransfer: JettonTransferParams = {
  toAddress: 'EQC...recipient...',
  jettonAddress: 'EQD...jetton-master...',
  amount: '1000000000', // raw amount per token decimals
  comment: 'Payment'
};

const tx = await wallet.createTransferJettonTransaction(jettonTransfer);
await kit.handleNewTransaction(wallet, tx);

Notes:

  • amount is the raw integer amount (apply jetton decimals yourself)
  • The transaction includes TON for gas automatically

Send NFTs

import type { NftTransferParamsHuman } from '@ton/walletkit';

const wallet = kit.getWallet(getSelectedWalletAddress());
if (!wallet) throw new Error('No wallet');

const nftTransfer: NftTransferParamsHuman = {
  nftAddress: 'EQD...nft-item...',
  toAddress: 'EQC...recipient...',
  transferAmount: 10000000n, // TON used to invoke NFT transfer (nanotons)
  comment: 'Gift'
};

const tx = await wallet.createTransferNftTransaction(nftTransfer);
await kit.handleNewTransaction(wallet, tx);

Fetching NFTs:

const items = await wallet.getNfts({ offset: 0, limit: 50 });
// items.items is an array of NftItem

Example: minimal UI state wiring

type AppState = {
  connectModal?: { request: any };
  txModal?: { request: any };
};

const state: AppState = {};

kit.onConnectRequest((req) => {
  state.connectModal = { request: req };
});

kit.onTransactionRequest((tx) => {
  state.txModal = { request: tx };
});

async function approveConnect() {
  if (!state.connectModal) return;
  const address = getSelectedWalletAddress();
  const wallet = kit.getWallet(address);
  if (!wallet) return;
  // Set wallet address on the request
  state.connectModal.request.walletAddress = wallet.getAddress();
  await kit.approveConnectRequest(state.connectModal.request);
  state.connectModal = undefined;
}

async function rejectConnect() {
  if (!state.connectModal) return;
  await kit.rejectConnectRequest(state.connectModal.request, 'User rejected');
  state.connectModal = undefined;
}

async function approveTx() {
  if (!state.txModal) return;
  await kit.approveTransactionRequest(state.txModal.request);
  state.txModal = undefined;
}

async function rejectTx() {
  if (!state.txModal) return;
  await kit.rejectTransactionRequest(state.txModal.request, 'User rejected');
  state.txModal = undefined;
}

Demo wallet reference

Live Demo: https://walletkit-demo-wallet.vercel.app/

See apps/demo-wallet for the full implementation. The store slices walletCoreSlice.ts and tonConnectSlice.ts show how to:

  • Initialize the kit and add a wallet from mnemonic
  • Wire onConnectRequest and onTransactionRequest to open modals
  • Approve or reject requests using the kit methods

Resources

License

MIT License - see LICENSE file for details