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

@groftylabs/dapp-sdk

v0.2.0

Published

Connect a dApp to the Grofty Wallet browser extension over CIP-0103, the Canton dApp standard.

Readme

@groftylabs/dapp-sdk

Connect a dApp to Grofty Wallet over CIP-0103, the Canton dApp standard.

  • Zero runtime dependencies. Nothing is pulled into your bundle but this package.
  • SSR-safe. Importing it on a server does nothing; every window access is guarded.
  • Typed end to end, including the places where the wallet diverges from the bare spec.
  • Errors carry stable numeric codes. Branch on code, never on message text.

You do not need this package to support Grofty. Grofty announces itself over canton:announceProvider, so any CIP-0103 aggregator — PartyLayer, for one — already reaches it with no wallet-specific code. Reach for this SDK when you want to talk to Grofty directly, with types.

Install

npm install @groftylabs/dapp-sdk

Quick start

import { createGroftyClient, isUserRejection } from '@groftylabs/dapp-sdk';

const grofty = await createGroftyClient();
if (!grofty) {
  // Not installed, or this page is server-rendered.
  return;
}

try {
  await grofty.connect();
  const account = await grofty.getPrimaryAccount();
  console.log('connected as', account?.partyId);
} catch (error) {
  if (isUserRejection(error)) {
    console.log('the user said no');
  } else {
    throw error;
  }
}

React

import { GroftyProvider, useGroftyAccount, useConnect } from '@groftylabs/dapp-sdk/react';

function App() {
  return (
    <GroftyProvider>
      <Wallet />
    </GroftyProvider>
  );
}

function Wallet() {
  const { isConnected, account, networkId } = useGroftyAccount();
  const { connect, isConnecting, error } = useConnect();

  if (!isConnected) {
    return (
      <>
        <button onClick={() => connect()} disabled={isConnecting}>
          {isConnecting ? 'Connecting…' : 'Connect Grofty'}
        </button>
        {error && <p>Error {error.code}: {error.message}</p>}
      </>
    );
  }

  return <p>{account?.partyId} on {networkId}</p>;
}

React is an optional peer dependency. The core entry point never imports it.

API

Discovery

| Function | Returns | |---|---| | createGroftyClient(options?) | Promise<GroftyClient \| null> — null when not installed or during SSR | | requireGroftyClient(options?) | Promise<GroftyClient> — throws GroftyNotFoundError instead | | getGroftyClient(options?) | GroftyClient \| null, synchronous, only if already injected | | getGroftyProvider() | the raw CIP-0103 provider, or null | | collectAnnouncedWallets(options?) | every wallet answering canton:requestProvider |

Options: discoveryTimeoutMs (default 1000) bounds discovery; timeoutMs (default 240 000) bounds a single request afterwards. They are separate on purpose — one waits for the extension to show up, the other waits for the user to answer a prompt.

Client

client.connect()                       // → ConnectResult, prompts the user
client.disconnect()
client.isConnected()                   // never prompts
client.status()                        // → StatusEvent, answers before connecting
client.getActiveNetwork()              // → { networkId: 'canton:da-mainnet' }
client.listAccounts()                  // → CantonAccount[]
client.getPrimaryAccount()             // → the account that will sign
client.signMessage(message)            // → signature string
client.prepareExecute(params)          // submit; resolves with undefined
client.prepareExecuteAndWait(params)   // submit and resolve with { tx: TxExecutedEvent }
client.submitAndWait(params)           // same receipt, correlated via events; see Quirks
client.ledgerApi({ resource, body? })  // narrow read surface, see Quirks
client.getBalance()                    // ledgerApi({ resource: 'balance' })
client.getUpdateById(updateId)         // a submitted tx, with createdEventBlobs
client.getActiveContracts({ … })       // your party's contracts, optionally by template
client.request(method, params?)        // escape hatch, still normalizes errors
client.on(event, handler)              // → unsubscribe function
client.once(event, handler)

Events

statusChanged, accountsChanged, txChanged, connected — all typed through GroftyEventMap.

const off = client.on('txChanged', (event) => {
  if (event.status === 'executed') console.log(event.payload.updateId);
});

Errors

Every rejection is a GroftyRpcError with a numeric code.

| Code | Meaning | |---|---| | 4001 | user rejected the request | | 4100 | unauthorized — origin not connected, or the user is signed out of the wallet | | -32601 | method (or ledgerApi resource) not found | | -32602 | invalid params | | -32603 | internal error, including an approval that timed out |

import { isUserRejection, isUnauthorized, UNAUTHORIZED } from '@groftylabs/dapp-sdk';

Note that a timed-out approval is -32603, not 4001. The wallet distinguishes "the user declined" from "the user never answered", and so should you.

Quirks

Grofty's surface differs from the plain reading of CIP-0103 in the places below. Each one is encoded in the types, but they are worth knowing.

ledgerApi is a narrow reader, not a Ledger API proxy. It is read-only and serves four real Ledger API paths — /v2/state/ledger-end, /v2/state/active-contracts, /v2/updates/update-by-id, /v2/events/events-by-contract-id — plus the older wallets and balance shorthands. Any other path returns -32601.

Every path read is scoped to the connected wallet's own party, server-side. Party filters in your body are ignored, not honoured: the wallet rebuilds each request around your party, so these answer about your contracts and nobody else's. body carries only the non-authority arguments — updateId, contractId, templateIds, activeAtOffset, and includeCreatedEventBlob (default true).

Because it is a subset rather than the whole surface, Grofty still does not claim the ledgerApi capability in the PartyLayer registry — a dApp feature-detecting it would expect more than this.

prepareExecute resolves with undefined, per the spec — it waits for execution to finish, but the ledger's updateId arrives on the txChanged event rather than as a return value. Use prepareExecuteAndWait() to get { tx } back from the call itself. submitAndWait() returns the same receipt but correlates by watching the pending event it triggered, so concurrent submissions from one page can be mixed up; it remains for wallets that predate prepareExecuteAndWait, and because it reports a failed transaction as a terminal event instead of throwing.

prepareExecute accepts two shapes. Either a plain transfer, { receiver, amount, tokenSymbol?, memo? }, or generic Daml commands with the CIP-0103 envelope: commands, disclosedContracts, commandId, readAs, synchronizerId, packageIdSelectionPreference.

actAs is refused, and readAs may only name your own party. Grofty submits as a single party, always the connected wallet's own. Supplying another party is rejected outright rather than silently dropped, so a dApp relying on multi-party submission finds out on the first call.

Cross-participant settlement, end to end. Submit, read back what you created, then hand those contracts to the counterparty as disclosedContracts:

const { tx } = await client.prepareExecuteAndWait({
  commands,
  disclosedContracts: registryContext,   // Amulet rules, factories, …
});

const update = await client.getUpdateById(tx.payload.updateId);
// each created event carries createdEventBlob, ready to pass on

Pass all four fields of a disclosed contract. CIP-0103 marks only createdEventBlob as required, but the Canton Ledger API also needs contractId and synchronizerId, so send templateId, contractId, createdEventBlob, and synchronizerId together.

signMessage versus wallet_signMessage. The spec method resolves to { signature }; the legacy method returns a bare string. client.signMessage() accepts either and always hands you the string.

Mainnet only. Grofty reports canton:da-mainnet and does not implement network switching.

Session restore

Grofty implements status and getPrimaryAccount without prompting, so a page reload can restore a session silently: call status(), and if connection.isConnected is true the session is live. The React bindings do this for you in useGroftyAccount.

Compatibility

Requires Grofty Wallet 2.0.4 or newer. That build is the first to resolve prepareExecuteAndWait() with { tx }, to carry the full CIP-0103 prepare envelope, and to serve the Ledger API read paths. Against 2.0.2 and 2.0.3 the SDK still loads, but those three return undefined, drop the extra envelope fields, and answer -32601 respectively — failures that look like your code rather than the wallet's.

Below 2.0.2 nothing works at all: the dApp bridge never reached web pages, because the content script shipped as an ES module the browser refuses to run. Against those, createGroftyClient() resolves to null.

Security

  • The SDK never sees a key, a seed or a password. It speaks postMessage to the extension, which does all signing behind its own approval prompts.
  • Inbound messages are checked for origin and source before being trusted.
  • Every request has a ceiling, so a wallet that stops answering surfaces an error instead of hanging your UI forever.
  • No dynamic code evaluation, and no runtime dependencies — there is no third-party code in the path between your dApp and a signature prompt.

License

Apache-2.0. See LICENSE.