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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bootnodedev/canton-connect

v0.4.0

Published

wagmi-style React hooks for connecting Canton dApps to CIP-0103 wallets

Readme

canton-connect

wagmi-style React hooks for connecting Canton dApps to CIP-0103 wallets, over @canton-network/dapp-sdk's DappSDK facade.

Why

dapp-sdk handles wallet discovery, the connect picker, the session and every transport (browser extension, WalletConnect, remote gateway), but ships no React hooks. This package adds that layer. A consumer never calls the SDK: it installs it as a peer, and the hooks take the SDK's own parameter types.

@partylayer/react is the alternative, built over its own wallet adapters. This one wraps Digital Asset's official SDK, the dependency these dApps already carry, and stays thin enough to delete. How the hook results relate to wagmi's is in coming-from-wagmi.md.

Why a state machine

The connection lifecycle looks like four states (idle, connecting, connected, disconnected) and isn't. The hard part isn't holding state, it's canceling work when the state that started it is gone: a picker the user abandoned, a lock that races the account read, a connect asked for mid-disconnect, a wallet that answers late or never. Handled one at a time these were five separate races (#76). A state machine folds them into one model: a state's invoked work is canceled when the state is left, and the combinations that used to be bugs (connected with no party, an error beside a live session) are states that name their case: session.unauthenticated, session.authenticated.unavailable.

Most of that weight works around @canton-network/dapp-sdk gaps, not domain complexity (below). When those close upstream, this layer collapses to its floor (idle-vs-disconnected, the account-read states, the CIP-0103 lock/disconnect ambiguity), small enough that a lighter store wins on bundle size. A spike reimplementing it on zustand confirmed this: full behavioral parity, but the win is bundle size and one fewer dependency, not less logic, and it points to switching only once those gaps close. Until then, the machine earns its cost.

| dapp-sdk gap | what it costs us | gone when | |---|---|---| | connect() can't be aborted; a closed popup hangs it (#49) | guardedConnect, settleAbandonedConnect, PickerClosedError, the retiring state, retireSdk's picker swap | connect(signal) truly aborts | | init() caches a rejected promise forever | retireSdk, the retiring state, InitFailedError | init() retries after a failure | | disconnect() has no timeout (#105) | DISCONNECT_TIMEOUT_MS, and retireSdk when it fires | disconnect() times out itself | | lock and wallet-side disconnect are one push | session.unauthenticated, party-dropped-on-lock | CIP-0103 separates them (spec, not SDK) |

Status

Published to npm as @bootnodedev/canton-connect. Also consumed by dapp/frontend in this repo, which links the local folder while its version satisfies the declared range.

Install

Peers a consumer installs beside it: @canton-network/dapp-sdk, @canton-network/core-types, react 19 and @walletconnect/sign-client. The last is required here, though dapp-sdk marks it optional: dapp-sdk imports it statically at the top of its bundle (checked on 1.5.1), so it has to be present whether or not you set walletConnectProjectId. Only the session is lazy: SignClient.init() runs when a pairing starts, not at import.

Usage

import {
  CantonConnectProvider,
  useConnect,
  useParty,
  useWalletStatus,
  useSignMessage,
  useExecute,
  useLedger,
} from '@bootnodedev/canton-connect'

function App() {
  return (
    <CantonConnectProvider config={{ appName: 'My dApp', networkId: 'canton:local' }}>
      <Dapp />
    </CantonConnectProvider>
  )
}

function Dapp() {
  const { connect, isPending, isConnected, error } = useConnect()
  const { party } = useParty()
  const { isLocked } = useWalletStatus()
  const { signMessage } = useSignMessage()
  const { execute } = useExecute()
  const { ledgerApi } = useLedger()

  if (!isConnected) {
    return (
      <div>
        <button onClick={() => connect().catch(() => undefined)} disabled={isPending}>
          Connect
        </button>
        {error !== undefined && <p>{error.message}</p>}
      </div>
    )
  }

  if (isLocked) {
    return <p>Wallet locked. Unlock it to continue.</p>
  }

  // ... your dApp: party.partyId, signMessage(text), execute(params), ledgerApi(params)
}

connect() opens the SDK's wallet picker, a popup by default. There is no mode argument: the picker is what chooses the wallet. Dismissing it rejects with ConnectCancelledError, which you filter by instanceof, never by message. Whether error records it as well depends on which side saw the close, so do not gate on that.

signMessage, execute and ledgerApi refuse with no session, and refuse again while the wallet reports it is not authenticated; that is isLocked, and it happens after a successful connect. signMessage and execute also refuse with no party, which ledgerApi does not need. The reference gateway also refuses signMessage for a local party; usePartyType().readPartyType() tells local from external when you ask. The SDK's status carries one isConnected flag, so a lock and a wallet-side disconnect look the same here. useLedger().isReady covers both, and useParty().party is undefined for the duration: gate session content on the party, and use isLocked only to explain why it went away.

Connecting through a Wallet Gateway

import { RemoteAdapter } from '@canton-network/dapp-sdk'

const config = {
  appName: 'My dApp',
  additionalAdapters: [
    new RemoteAdapter({ name: 'Gateway', rpcUrl: 'http://localhost:3030/api/v0/dapp' }),
  ],
}

[!NOTE] The dapp-sdk wallet picker also lets a user paste any gateway URL and connect to it without the dApp listing it. That session does not survive a reload; one with a gateway listed here does.

Details in architecture.md.

Connecting through WalletConnect

const config = {
  appName: 'My dApp',
  networkId: 'canton:devnet',
  walletConnectProjectId: 'YOUR_REOWN_PROJECT_ID',
}

networkId is the Canton network the dApp targets, as a CAIP-2 chain id.

Details in architecture.md.

Reference

Every hook and every config field is documented in JSDoc, which your editor surfaces at the call site and which is published at docs.dappbooster.cc. Start at CantonConnectProvider and CantonConnectConfig.

Testing helpers

  • createMockAdapter(), from the package root: a ProviderAdapter answering connect, disconnect, status and listAccounts with no wallet installed, so a dApp or a test can connect and show a party. Anything else throws, naming the method.
  • From @bootnodedev/canton-connect/testing: createFakeWallet(), a CIP-0103 extension driven over postMessage that exercises the SDK's real announce and detect path; createAutoPicker(), a headless picker so connect() runs without a popup; FakeSessionProvider, the context rehydrated at an asked-for session with no SDK behind it; and pause(ms), a real-timer sleep.
import { CantonConnectProvider, createMockAdapter } from '@bootnodedev/canton-connect'
import { createAutoPicker } from '@bootnodedev/canton-connect/testing'

const config = {
  appName: 'My dApp',
  additionalAdapters: [createMockAdapter()],
  walletPicker: createAutoPicker(),
}

Architecture

architecture.md maps the seams; its architecture/ chapters carry the connection machine and the popup close guard.

Testing

pnpm test: vitest + jsdom + Testing Library. pnpm coverage runs the same suite under v8, with testing/, mock/ and the barrel excluded.