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

@wandevs/ca-sdk

v0.3.0

Published

Chain Abstraction SDK for DApps — unified wallet & session-key signing, batched cross-chain execution against the CARouter / Router-API stack.

Readme

@wandevs/ca-sdk

Frontend SDK for the Compass / Wandevs Chain Abstraction stack (chain-abstraction-router-api + chain-abstraction-contracts CARouter).

A DApp that integrates this SDK gets:

  • One-line wallet connect + EIP-712 batch signing
  • Wallet mode (sign every batch with the main wallet) and Session mode (sign once, automate the rest with a scoped session key)
  • Same external API for both — caller code does not change when you toggle the mode
  • Pluggable wallet adapter (viem / ethers), pluggable storage (sessionStorage / localStorage / memory / custom)
  • React provider + hooks subpackage

Install

bun add @wandevs/ca-sdk
# peer deps as needed
bun add viem            # required
bun add ethers          # only if you use the ethers adapter
bun add react           # only if you use the React subpackage

Quick start (vanilla, viem + session mode)

import { createChainAbstraction, ops, CA_ROUTER_ADDRESS, CA_API_URL } from '@wandevs/ca-sdk';
import {
  viemAdapter,
  viemSessionAuthVersionResolver,
  viemSessionKeyRevoker,
} from '@wandevs/ca-sdk/adapters/viem';
import { createPublicClient, createWalletClient, custom, http, parseEther, toFunctionSelector } from 'viem';
import { mainnet } from 'viem/chains';

const publicClient = createPublicClient({ chain: mainnet, transport: http() });
const wallet = createWalletClient({ chain: mainnet, transport: custom(window.ethereum!) });

const ca = createChainAbstraction({
  apiUrl: CA_API_URL,                      // https://chain-abstraction.wanscan.org/api/v1
  routerAddress: CA_ROUTER_ADDRESS,        // 0x534b95E0780B434026C8A9bfceFA7a9A4dF846cc — same on every chain via CREATE2
  wallet: viemAdapter(wallet),
  mode: 'session',
  session: {
    storage: 'sessionStorage',
    authVersion: viemSessionAuthVersionResolver(publicClient),
    revokeSessionKey: viemSessionKeyRevoker(wallet),
    defaultDeadline: 24 * 3600,
    scope: {
      allowedSelectors: [
        toFunctionSelector('stake(uint256)'),
        toFunctionSelector('claim()'),
      ],
      allowedTargets: ['0xVault1', '0xVault2'],
      maxValuePerTx: parseEther('1'),
      maxValueTotal: parseEther('10'),
      // allowedRefundTos defaults to [user] — leave it alone unless you really need to broaden it.
    },
  },
  // Browsers should NOT see apiSecret. Proxy through your own BFF instead.
  // For server-side / trusted environments:
  // auth: { apiKey: process.env.CA_API_KEY, apiSecret: process.env.CA_API_SECRET },
});

// 1. Connect — in session mode this prompts the wallet ONCE to sign a SessionAuth.
await ca.connect();

// 2. Submit a batch — no further wallet popups.
const { batchId } = await ca.execute([
  ops.contractCall({
    chainId: 1, target: '0xVault1', abi: VAULT_ABI, fn: 'stake', args: [amount],
  }),
]);

// 3. Watch progress
for await (const status of ca.watchBatch(batchId)) {
  console.log(status.status, status.chainTasks);
  if (status.status === 'completed') break;
}

In wallet mode the only change is mode: 'wallet' (and you can drop the session block). The signature surface — connect, execute, getBatchStatus, watchBatch — is identical.


React

import { ChainAbstractionProvider, useExecuteBatch, useSession } from '@wandevs/ca-sdk/react';
import { ops } from '@wandevs/ca-sdk';

const config = { /* same shape as above */ };

function App() {
  return (
    <ChainAbstractionProvider config={config}>
      <StakeButton />
    </ChainAbstractionProvider>
  );
}

function StakeButton() {
  const { execute, loading, status } = useExecuteBatch();
  const session = useSession();

  return (
    <button
      disabled={!session.isActive || loading}
      onClick={() => execute([ops.contractCall({ /* ... */ })])}
    >
      {loading ? 'Submitting…' : 'Stake'}
    </button>
  );
}

useSession() exposes { isActive, timeRemaining, auth, refresh, revoke }.


What the SDK signs

The router-api / CARouter pair uses three EIP-712 typed structs (the SDK pins the typestrings — they MUST match the on-chain definitions byte-for-byte):

EIP712Domain(string name,string version,address verifyingContract)
Operation(uint256 chainId,uint256 nonce,address target,bytes data,uint256 value,
         address refundTo,address refundToken,uint256 deadline)
Batch(Operation[] contents)
SessionAuth(address user,address sessionKey,uint256 authVersion,uint256 deadline,
           bytes4[] allowedSelectors,address[] allowedTargets,address[] allowedRefundTos,
           uint256 maxValuePerTx,uint256 maxValueTotal)
SessionOp(address user,bytes32 batchHash)

Notes:

  • The domain has no chainIdSessionAuth is intentionally chain-agnostic (CARouter is at the same address on every chain via CREATE2). Cross-chain replay is prevented by Operation.chainId inside the batch.
  • SessionOp embeds user to block cross-user opSig replay.
  • Pure ETH transfers (data == "0x") require opting in by adding 0x00000000 to allowedSelectors. The SDK's default scope does not include it.

Operation builders

import { ops, randomNonce } from '@wandevs/ca-sdk';
import { flattenOps } from '@wandevs/ca-sdk/ops';

const entries = [
  ops.erc20Approve({
    chainId: 1, token: USDT, spender: VAULT, amount: 1_000_000n,
    resetAllowanceFirst: true,    // USDT requires approve(0) first — the SDK emits two ops.
  }),
  ops.contractCall({
    chainId: 1, target: VAULT, abi: VAULT_ABI, fn: 'deposit', args: [1_000_000n],
  }),
  ops.bridge({
    fromChainId: 1, toChainId: 56,
    fromTokenAddress: USDT, toTokenAddress: USDT_BSC,
    fromAddress: user, toAddress: user, fromAmount: 1_000_000n,
    bridgeAdapter: '0xBridgeAdapter', data: bridgeCalldata,
    bridge: 'wanbridge',
  }),
];

const { drafts, metadata } = flattenOps(entries);
await ca.execute(drafts, { metadata });

Session lifecycle

ca.session?.isActive();        // boolean — has unexpired SessionAuth in storage
ca.session?.timeRemaining();   // seconds until SessionAuth.deadline
await ca.session?.refresh();   // re-sign immediately, prompting the wallet
await ca.session?.revoke();    // calls CARouter.revokeSessionKey, then clears local state
await ca.session?.clearLocalSession(); // local-only cleanup; does not revoke on-chain

The SDK emits events you can subscribe to for UX prompts:

ca.on((e) => {
  if (e.type === 'session:resign-required') showResignBanner(e.reason);
  if (e.type === 'batch:submitted') trackEvent('batch_submitted', { batchId: e.batchId });
});

If execute() is called and the SessionAuth has expired, the SDK transparently re-signs (one wallet popup) and continues — the call resolves once the batch is submitted.


Security notes

  • Never bundle apiSecret into a public web app. Proxy through your own backend.
  • Session mode must read CARouter.sessionAuthVersions(user) before signing; use viemSessionAuthVersionResolver(publicClient) or pass an equivalent BFF-backed resolver.
  • session.revoke() requires a revokeSessionKey callback and only clears local credentials after the on-chain revoke transaction is submitted. Use clearLocalSession() for local-only cleanup.
  • The session keypair lives in the storage backend you choose. sessionStorage is the default and clears on tab close; use localStorage only when you intentionally want a longer-lived session key. For SSR / Node, use 'memory' or a custom SDKStorage.
  • The SDK runs the same scope checks the contract enforces (precheckScope) so invalid batches fail locally before the HTTP roundtrip — but the contract is the source of truth.
  • High-risk actions (vault sweep, raw token transfers, raw approvals, generic swaps, revokeAllSessions) should remain on wallet mode unless Router-API has strict calldata policy for the exact action. Don't include broad asset-redirection selectors in allowedSelectors.

Reference

| Surface | Purpose | |---|---| | createChainAbstraction(config) | Construct a client. | | client.connect() | Resolve user; load/create SessionAuth (session mode). | | client.execute(drafts, { metadata? }) | Sign + submit a batch. | | client.getBatchStatus(id) / client.watchBatch(id) | Poll status (one-shot / async iterator). | | client.session.{isActive,timeRemaining,refresh,revoke,getAuth} | Session lifecycle. | | client.on(listener) | Subscribe to SDK events. | | ops.{erc20Approve, contractCall, bridge, nativeTransfer, flatten} | Operation templates. | | precheckScope(ops, auth) | Same scope rules the contract enforces. | | viemAdapter(walletClient) / ethersAdapter(signer) | Wallet adapters. | | <ChainAbstractionProvider> + useChainAbstraction / useExecuteBatch / useSession | React entry. |


Versioning

0.x tracks the router-api / CARouter feat/session rollout. 1.0 will freeze the public surface once both are GA. Major bumps follow a deprecation-warning + one-minor overlap window.