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

@cantor8/wallet-connect-sdk

v0.9.0

Published

C8 Wallet Connect SDK for dApp developers: postMessage API to interact with C8 Wallet.

Readme

C8 Wallet Connect SDK

Lightweight SDK for dApp developers to integrate the C8 Wallet via a popup window and postMessage communication.

Quick Start

Installation

npm i @cantor8/wallet-connect-sdk

Import the provider into your dApp:

import { C8WalletProvider } from "./provider";

Configuration

Create a new provider instance:

const c8 = new C8WalletProvider({
  dappUrl: window.location.href,
  dappName: "Cantor8 Wallet Connect SDK Demo",
  network: "devnet",
});

After approval, the wallet supplies validator.backendApiUrl alongside validator.scannerApiUrl in the account metadata. The SDK persists both URLs with the session and uses each selected account's backend URL for balance, transfer status, and contract setup requests, including after a page reload. Older sessions without backendApiUrl must disconnect and reconnect.

The approval message supplies authToken on each account, with no top-level token. Account tokens are persisted with the session; backend requests use the selected account's token and Scanner requests use the primary account's token. Sessions stored with a legacy shared token must reconnect. The approval message's expireAt still applies to the whole session.

Balance refresh and session restore fetch balances for all connected accounts. HttpClient.getBalance() returns an array of balance responses in account order. getTransferStatus(txId, partyId?) and getContractsSetupState(partyId?) select the matching connected account, defaulting to the primary account when omitted. An unknown party ID is rejected without sending a request.

Events

You can subscribe to wallet events using c8.on():

c8.on("connected", (e) => console.log("Connected", e));
c8.on("disconnected", (e) => console.log("Disconnected", e));
c8.on("accountChanged", (e) => console.log("Account's balance changed", e));
c8.on("txInitiated", (e) => console.log("TX initiated", e));
c8.on("txChanged", (e) => console.log("TX changed", e));

Basic Usage

const c8 = new C8WalletProvider({
  dappUrl: window.location.href,
  dappName: "Cantor8 Wallet Connect SDK Demo",
  network: "devnet",
});

await c8.connect();

// Instruments list type: InstrumentListPayload
const instruments = await c8.getInstruments();
console.log("Instruments", instruments);

const firstInstrumentId = instruments.instruments[0]?.instrumentId;

// Accounts list type: AccountListPayload
const accounts = await c8.getAccounts(firstInstrumentId);
console.log("Accounts", accounts);

const senderPartyId = accounts.accounts[0]?.partyId;

// Registration and validator-managed contract setup state
// Return type: PartyContractSetupResponse
const contractsSetupState = await c8.getContractsSetupState();
console.log("Contracts setup state", contractsSetupState);

const senderHoldingInstrumentId =
  accounts.accounts[0]?.holdings[0]?.instrumentId || firstInstrumentId;

// Submit a transfer
// SubmitTransferResultPayload
const res = await c8.send({
  senderPartyId,
  instrumentId: senderHoldingInstrumentId,
  amount: 1.23,
  receiverPartyId: "Party::Receiver",
});

console.log("Send result", res); // { txId }

// Create a swap offer
const swapRes = await c8.createSwapOffer({
  partyId: senderPartyId,
  commandId: "command-" + Date.now(), // optional
  swap: {
    swapId: "swap-" + Date.now(),
    acceptorPartyId: "Party::Acceptor",
    creatorLegs: [
      {
        receiver: "Party::Acceptor",
        instrument: {
          admin: "Admin::Out",
          id: senderHoldingInstrumentId,
        },
        amount: "1.0",
        legId: "creator-leg-1",
        reason: "Swap offer", // optional
      },
    ],
    acceptorLegs: [
      {
        receiver: senderPartyId,
        instrument: {
          admin: "Admin::In",
          id: "Inst::In",
        },
        amount: "2.0",
        legId: "acceptor-leg-1",
      },
    ],
    creatorTransferLegs: [],
    settleBefore: new Date(Date.now() + 3600 * 1000).toISOString(),
    meta: {},
  },
});

console.log("Swap offer result", swapRes); // { txId }

// Sign an arbitrary string, optionally for a specific party
const signature = await c8.signMessage("Message to sign", senderPartyId);
console.log("Signature", signature);

API Reference

Public API: C8WalletProvider

| Method | Parameters | Return Type | | --------------------- | ------------------------------------------ | --------------------------------------------------------- | | connect() | None | Promise<void> | | disconnect() | None | Promise<void> | | status() | None | Promise<{ connected: boolean; walletVersion?: string }> | | getContractsSetupState() | partyId?: string | Promise<PartyContractSetupResponse> | | getInstruments() | None | Promise<InstrumentListPayload> | | getActiveContracts() | input?: GetActiveContractsInput | Promise<ActiveContractsPayload> | | getAccounts() | instrumentId?: string | Promise<AccountListPayload> | | send() | input: SendInput | Promise<SubmitTransferResultPayload> | | createSwapOffer() | input: { partyId, swap, commandId? } | Promise<SubmitTransferResultPayload> | | cancelSwapOffer() | input: { partyId, swapId \| proposalCid, commandId? } | Promise<SubmitTransferResultPayload> | | getOutgoingSwapOffers() | partyId?: string | Promise<OutgoingSwapOffersPayload> | | signMessage() | message: string, partyId?: string | Promise<string> | | signAndExecute() | input: SignAndExecuteInput | Promise<void> | | checkTxStatusById() | input: { txId: string; partyId?: string } | Promise<TransferStatusResponsePayload> | | on() | event: T, cb: Listener<C8EventByType<T>> | () => boolean |

getContractsSetupState()

Returns the authenticated account's contract setup transactions, including all statuses, from the wallet backend's /api/register/contract-setup endpoint:

const state = await c8.getContractsSetupState();
// Or select a connected account: await c8.getContractsSetupState(partyId);

console.log("Party:", state.partyId);
for (const contract of state.contracts) {
  console.log(contract.code, contract.status, contract.contractId);
}

The returned PartyContractSetupResponse contains partyId and a contracts array. Each ContractSetupItem includes id, code, partyId, commandId, contractId, status, submittedAt, and completedAt. The contract ID and timestamps may be null; statuses are returned as supplied by the backend.

This replaces the previous registration-flag response (RegisterStatusResponse). Consumers must read contracts instead of the old is... flags. Embedded wallet hosts must also return this response shape for the getContractsSetupState RPC.

getActiveContracts()

Reads Scanner ACS using the Wallet App's authenticated account:

const contracts = await c8.getActiveContracts({
  templateIds: ["package-id:Module:Template"],
  includeCreatedEventBlob: true,
  limit: 10000,
});
console.log(contracts);

For direct popup-provider reads, optional partyId must match the connected primary account; the stored token belongs to that account. Embedded Wallet App reads can select an account owned by the wallet. The primary account is used by default. interfaceIds filters interfaces. activeAtOffset requests a point-in-time snapshot using a nonnegative signed 64-bit decimal string. Offsets beyond Scanner ingestion are rejected. limit must be a positive integer and defaults to 10000. paginated defaults to true so the first page is ordered by contract_id even when after is absent. Pass a nonempty after contract ID unchanged to request rows after it; explicit paginated: false is also forwarded. Scanner returns at most limit rows without completeness metadata. Base contract and interface rows can share a contract_id; if the limit splits that group, the next after request skips its remaining rows. These parameters do not guarantee complete traversal.

The result is the Scanner contract array with its original field names and created-event blobs. JSON numbers use normal JavaScript numeric precision. After connect(), the popup provider calls the primary account's Scanner endpoint directly with the stored wallet token, even after the popup closes. No additional token is supplied to this method. Older sessions missing validator.scannerApiUrl must disconnect and reconnect. Embedded providers retain the existing Wallet App RPC.

send() Input

{
  senderPartyId: string;
  instrumentId: string;
  amount: number;
  receiverPartyId: string;
  memo?: string;
  metadata?: Record<string, string>;
}

createSwapOffer() Input

{
  partyId: string;
  commandId?: string;
  swap: {
    swapId: string;
    acceptorPartyId: string;
    creatorLegs: Array<{
      receiver: string;
      instrument: {
        admin: string;
        id: string;
      };
      amount: string;
      legId: string;
      reason?: string;
    }>;
    acceptorLegs: Array<{
      receiver: string;
      instrument: {
        admin: string;
        id: string;
      };
      amount: string;
      legId: string;
      reason?: string;
    }>;
    creatorTransferLegs: Array<{
      receiver: string;
      instrument: {
        admin: string;
        id: string;
      };
      amount: string;
      legId: string;
      reason?: string;
    }>;
    settleBefore: string;
    meta: Record<string, string>;
  };
}

partyId identifies the party creating the offer. acceptorPartyId identifies the party allowed to accept it. Amounts are decimal strings, and settleBefore is an ISO 8601 timestamp. The method resolves to { txId } after the wallet submits the offer.

cancelSwapOffer() Input

{
  partyId: string;
  swapId?: string; // the swapId passed to createSwapOffer
  proposalCid?: string; // or the offer's contractId from getOutgoingSwapOffers
  commandId?: string;
}

Withdraws an open swap offer created by partyId and returns the locked funds to it in the same transaction. Set exactly one of swapId or proposalCid; the method throws before opening the wallet otherwise. Only the creator can cancel, and offers past settleBefore can be cancelled too. The acceptor sees the cancellation as an archived entry on its incoming swap offer updates feed.

Like createSwapOffer(), it opens the wallet for confirmation and resolves to { txId } (the command id) once the wallet submits the transaction:

c8.on("txInitiated", (e) => console.log("Cancel initiated", e.txId));

const { txId } = await c8.cancelSwapOffer({ partyId, swapId: order.swapId });
const status = await c8.checkTxStatusById({ txId, partyId });

It rejects with:

  • INVALID_PARAMS when partyId is missing or not exactly one of swapId / proposalCid is set.
  • ACCOUNT_NOT_FOUND when partyId is not an account of the wallet.
  • USER_REJECTED when the user dismisses the confirmation.
  • SWAP_OFFER_CANCEL_PREPARATION_FAILED when partyId has no open offer matching the id (or a swapId matches several offers).
  • EXECUTION_FAILED when the transaction fails.

getOutgoingSwapOffers()

Lists the open swap offers created by the account, from the wallet backend's /api/swap/v2/offer/outgoing endpoint. Like getContractsSetupState(), the popup provider calls the backend directly and does not open the popup:

const { offers } = await c8.getOutgoingSwapOffers();
// Or select a connected account: await c8.getOutgoingSwapOffers(partyId);

for (const offer of offers) {
  if (offer.kind === "single") {
    console.log(offer.swap.swapId, offer.swap.amountOut, offer.swap.amountIn);
  } else {
    console.log(offer.swap.swapId, offer.swap.creatorLegs, offer.swap.acceptorLegs);
  }
}

Each SwapOfferPayload has a kind:

  • "single": contractId, allocationOutCid, and swap with instrumentOut, amountOut, instrumentIn, amountIn.
  • "multi_leg": contractId, allocationOutCids, and swap with creatorLegs and acceptorLegs, each leg { receiverPartyId, instrument, amount, legId, reason? }.

Both kinds also carry swap.swapId, provider, creator, acceptor, createdAt, settleBefore, and meta. Amounts are decimal strings and timestamps are ISO 8601 strings.

Offers past settleBefore are not filtered out. They can no longer be accepted, but they still lock the creator's funds until withdrawn, and the creator can still withdraw them.

On failure the call rejects with code FETCH_OUTGOING_SWAP_OFFERS_FAILED.

signAndExecute() Input

{
  note: string;
  partyId: string;
  commandId: string;
  commandsJson: string;
  disclosedContracts: string;
}

signMessage()

Signs an arbitrary string and returns its signature. The party ID is optional:

const signature = await c8.signMessage("Message to sign", partyId);

Events Reference

Use c8.on(eventType, callback) to listen for changes emitted by the wallet.

| Event Type | Payload Properties | Description | | ------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | connected | meta?: Json | Fired when the wallet successfully connects. | | disconnected | reason?: string, meta?: Json | Fired when the session disconnects. | | txInitiated | txId?: string, partyId?: string, meta?: Json | Fired when a transaction request is initiated. | | txChanged | txId: string, status: "pending" \| "confirmed" \| "failed" \| string, meta?: Json | Fired when a transaction's completion status updates. | | accountChanged | accounts: AccountInfoPayload[], meta?: Json | Fired when internal account asset holdings scale or modify. | | themeChanged | theme: "dark" \| "light", meta?: Json | Fired when the wallet interface changes its UI display theme. | | operationCanceled | reason: string, meta?: Json | Fired when an action or modal interface is explicitly rejected by the user. |

Unsubscribing from Events

The on() method returns an unsubscribe function:

const unsubscribe = c8.on("connected", (event) => {
  console.log("Connected", event);
});

unsubscribe();

License

MIT

Ledger updates

getLedgerUpdates reads one page from Scanner's authenticated /ledger-nodes feed. External providers use direct HTTP with the stored session token and the primary account’s configured Scanner URL, including after the wallet popup closes. Embedded providers use the existing App RPC and can select another owned account. Like ACS, old sessions without Scanner metadata must disconnect and reconnect.

const page = await provider.getLedgerUpdates({
  partyId, // optional; defaults to the primary wallet account
  templateIds: ["package:Module:Template"],
  limit: 500, // 1–1000
});
const nextPage = page.nextCursor
  ? await provider.getLedgerUpdates({ partyId, cursor: page.nextCursor,
      templateIds: ["package:Module:Template"] })
  : undefined;

The result is Scanner's { nodes, nextCursor?, bounds?, gap?, complete } page, with typed LedgerNode fields and unchanged JSON event payloads. Optional eventType (created or exercised) and consuming filters pass through. Reuse nextCursor verbatim with the same filters: it retains exact offsets even beyond JavaScript's safe integer range. To start at an offset, use "<offset>:-2147483648". Other numeric JSON fields have ordinary JavaScript precision.

An empty page can still advance the cursor. complete means the current Scanner fetch frontier was reached; new updates may arrive later. gap reports lost history and must not be treated as an empty successful history. Requests before retained history fail. This is a paginated Scanner feed, not Ledger's streaming /v2/updates response. Scanner must enable this feed and support user-token party authorization (PR #125); the Wallet App must support the new RPC.