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

@funkit/connect-js

v0.5.0

Published

Framework-agnostic build of the Funkit Connect SDK

Readme

Install

pnpm add @funkit/connect-js

Two things to import: the code, and the stylesheet. The stylesheet is the fallback the modal uses when shadow-DOM encapsulation is unavailable, so always ship it.

Svelte

Importing the package on the server is safe, so use a normal top-level import. Creating the instance still needs a real document — createFunkitCheckout() throws during server rendering — so call it from the browser only.

Create one shared SDK instance for the application:

// src/lib/funkit.ts
import {
  type FunkitCheckoutHandle,
  createFunkitCheckout,
} from '@funkit/connect-js'

let funkit: FunkitCheckoutHandle | undefined

// Built on first use, so it doesn't matter which component asks first
export function getFunkit(): FunkitCheckoutHandle {
  funkit ??= createFunkitCheckout({
    apiKey: '…',
    appName: 'Variational',
    walletConnectProjectId: '…',
    theme: 'dark',
  })
  return funkit
}

export function destroyFunkit(): void {
  funkit?.destroy()
  funkit = undefined
}

Initialize it from the root layout:

<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import { onMount, onDestroy, type Snippet } from 'svelte'
  import '@funkit/connect-js/styles.css'
  import { destroyFunkit, getFunkit } from '$lib/funkit'

  let { children }: { children: Snippet } = $props()

  onMount(() => void getFunkit())
  onDestroy(() => destroyFunkit())
</script>

{@render children()}

Routes and components reuse that instance. Each component owns and removes its event subscriptions:

<script lang="ts">
  import { onMount } from 'svelte'
  import { getFunkit } from '$lib/funkit'

  // `on()` returns its own unsubscribe, which is what onMount wants for cleanup
  onMount(() =>
    getFunkit().on('checkout:success', (result) =>
      console.log('deposited', result),
    ),
  )
</script>

<button
  onclick={() =>
    getFunkit().openCheckout({
      checkoutItemTitle: 'USDC on Base',
      targetChain: '8453',
      targetAsset: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
      targetAssetTicker: 'USDC',
      targetAssetAmount: 5,
    })}
>
  Deposit
</button>

Update the shared instance when the application theme changes; do not destroy and recreate it:

getFunkit().update({ theme: isDarkMode ? 'dark' : 'light' })

For a branded theme, use the same pure factories as @funkit/connect:

import { createFunkitCheckout, darkTheme } from '@funkit/connect-js'

const brandTheme = darkTheme({
  accentColor: '#ff5a19',
  customColors: {
    modalBackground: '#181818',
    primaryText: '#ffffff',
  },
})

const funkit = createFunkitCheckout({
  // ...
  theme: brandTheme,
})

Custom theme objects are opaque: only pass the exact object returned by lightTheme() or darkTheme(). Manually constructed, cloned, or serialized theme objects are unsupported and may fail when Connect renders them. Store and reuse the factory result when passing it to update({ theme }).

The same ownership model applies to Vue and Angular: initialize once at the application root and share the handle. destroy() is for full application teardown and tests, not route navigation.

Vite HMR: Some framework/plugin update boundaries can re-evaluate the singleton module without first destroying the mounted SDK instance. In that case, the module-scoped funkit variable resets and a second createFunkitCheckout() call correctly triggers the one-instance guard. This only affects those development-time HMR paths, not production builds or every Vite integration. If you encounter it, preserve the handle with Vite's import.meta.hot.data or destroy the existing instance before recreating it.

API

createFunkitCheckout(config)

Mounts the SDK on the page and returns a handle. Create one instance in the root application lifecycle and reuse it across routes and components. A second active instance throws; call destroy() before creating another.

| Field | Required | Notes | | --- | --- | --- | | apiKey | yes | Your Funkit API key | | appName | yes | Shown in wallet prompts. 32 characters max | | walletConnectProjectId | | From WalletConnect Cloud. Omit it and the wallet picker skips WalletConnect QR codes and mobile deep links; browser extensions, the adopted wallet and Coinbase are unaffected | | appDescription, appUrl, appIcon | | Shown alongside appName in wallet prompts. Description falls back to appName, URL to the current page, and no icon is sent unless you give one | | theme | | 'light', 'dark', or a complete theme returned by lightTheme(options) or darkTheme(options); omitted follows the system color scheme | | locale | | e.g. 'en', 'ko', 'zh' | | modalSize | | 'compact', 'medium', 'wide' or 'extraWide' | | rpcUrls | | Your own HTTPS RPC endpoints keyed by chain id, e.g. { 8453: 'https://…' }. Pass an array for one chain to try endpoints in order. Chains you omit keep the SDK's defaults | | initialChain | | Chain id the wallet is asked to be on when it connects, e.g. 8453. Defaults to Ethereum mainnet — set it to the chain your users check out on so they skip a network-switch prompt | | adoptedWallet | | A wallet your page already holds, adopted at startup — see Adopting a host wallet | | debug | | Verbose SDK logs in the console | | textCustomizations | | Partial plain-data equivalent of Connect's text customization configuration | | uiCustomizations | | Plain-data UI customization fields. React component slots are intentionally unavailable; use confirmationScreen.destinationConfig.iconSrc for destination branding | | prefillFiatEmail, disableLogging, source | | Same meaning as funkitConfig in @funkit/connect |

Adopting a host wallet

If your page already holds a wallet connection — your own connect button, a WalletConnect session inside your modal library, anything the SDK can't discover on its own — hand it over at creation time and the SDK adopts it: the wallet is listed in the connect modal and connected automatically at startup, so the user lands in checkout already connected without picking a wallet a second time.

const funkit = createFunkitCheckout({
  // ...the usual config
  adoptedWallet: {
    provider,             // the live EIP-1193 provider your app holds; must
                          // have request, on, and removeListener functions
    name: 'MetaMask',     // shown in the wallet list
    rdns: 'io.metamask',  // reverse-DNS wallet id
    iconUrl: 'https://…', // optional; generic wallet icon when omitted
  },
})

How it behaves:

  • Your wallet is the only wallet. Setting adoptedWallet turns off the SDK's own session restore, so a wallet remembered from a previous visit can never race or override the one your page hands over. The SDK always agrees with what your page shows the user — connected to the adopted wallet, or not connected at all.
  • Chain alignment. Connecting asks the wallet for initialChain. A wallet that can't or won't switch (e.g. a WalletConnect session scoped to fewer chains) still connects on its own chain; checkout switches on demand later.
  • Failure falls back. If the provider rejects, the SDK is simply disconnected — even for a returning user, since no previous session is restored — and the normal connect modal remains available. openCheckout() and openWithdrawal() called while adoption is still connecting wait for it to settle first. If adoption fails, withdrawal requires another connected wallet or a supplied wallet parameter.
  • De-duplication. The rdns also matches the wallet against EIP-6963 announcements, so the same wallet is never listed twice.
  • checkout:login-required never fires for a user whose wallet was adopted — they are already connected. Keep the subscription if you have one; it still covers users whose adoption failed.
  • disconnect() is unchanged: it clears only the SDK's session. Your page's own wallet connection is untouched.

adoptedWallet is creation-time only — to hand over a wallet acquired later, destroy() and create a new instance.

The handle

| Method | What it does | | --- | --- | | openCheckout(params) | Opens the checkout modal. Resolves with { isActivated } once the SDK has taken the request | | openWithdrawal(params) | Opens the withdrawal modal. Resolves once the SDK has taken the request; rejects if no wallet is connected and none is supplied | | openConnect() | Opens the wallet connection modal. Rejects when a wallet is already connected, and also while a page-load session restore is still settling — the two carry different messages | | disconnect() | Disconnects the wallet and clears the SDK session. Resolves once getAccount() reads as disconnected; openConnect() becomes available on the next render. Call it from your app's own logout | | getAccount() | Returns the latest { isConnected, address? } wallet snapshot | | update({ theme }) | Changes the mounted SDK theme without resetting wallet or checkout state | | on(event, handler) | Subscribe to an event below. Returns the unsubscribe function | | destroy() | Unmounts the SDK and removes everything it added to the page. Safe to call twice |

Calls made before the SDK has finished mounting wait for it; they don't fail. After destroy(), openCheckout, openWithdrawal, openConnect and disconnect reject, update throws, and on logs a warning and returns a no-op unsubscribe — a destroyed instance never emits events.

openCheckout(params)

The React SDK's FunkitCheckoutConfig, minus fields that are deprecated or customer-specific — test/types.test-d.ts names each exclusion. Callback-valued fields (title resolvers, action generators, minimum-deposit functions) are plain functions and work from any framework.

| Field | Required | Notes | | --- | --- | --- | | checkoutItemTitle | yes | e.g. 'USDC on Base' | | targetChain | yes | Chain id as a string, e.g. '8453' | | targetAsset | yes | Destination token address | | targetAssetTicker | yes | e.g. 'USDC' | | targetAssetAmount | | Human units. Omit to let the user choose | | iconSrc, modalTitle, modalTitleMeta | | Modal header. modalTitle also takes a resolver function receiving the target token and active routing id | | sourceTokenSymbol, sourceChain, sourceTokenAddress | | Preselect the source token. The type requires all three together | | dynamicRoutingId, dynamicTargetAssetCandidates, bridgeOverride | | Dynamic routing, agreed with Fun.xyz for multi-route integrations | | customRecipient | | Pay out to a different recipient. Not recommended unless the Fun.xyz team advises it | | disabledSourceTokens | | Tokens that can't be used as the payment source | | generateActionsParams, qrcodeActionType | | Checkout with custom action: contract calls to execute with the deposit. Actions take pre-encoded rawCalldata (the React SDK's ABI form needs viem's types) | | addToWalletToken | | Token to offer adding to the wallet on the success screen | | getMinDepositUSD | | Minimum USD needed to deposit, per asset/route | | destinationYieldInfo | | Display-only lending information shown on the confirmation screen | | resolveDestinationLineItems | | Synchronous callback returning custom confirmation rows; only the first three are displayed |

Custom confirmation rows

Pass resolveDestinationLineItems to openCheckout alongside your deposit parameters. It receives the destination token amount as a string in human units, plus sourceTokenSymbol, sourceTokenAddress, and sourceChainId (each nullable). Return plain objects; no React dependency is needed.

await funkit.openCheckout({
  ...depositParams,
  resolveDestinationLineItems: ({ targetAssetHumanAmount }) => [
    {
      id: 'deposit-amount',
      label: {
        text: 'Deposit amount',
        tooltip: 'Amount arriving in the destination token.',
      },
      value: { text: targetAssetHumanAmount, color: 'default' },
    },
  ],
})

For margin usage rows, load your account snapshot before opening checkout and calculate the projected usage synchronously from that snapshot and targetAssetHumanAmount. Your app owns the calculation, formatting, and translations. Use distinct stable id values for each row.

Both label and value accept a tooltip. Value colors are default, success, caution, or danger. Set a row's isLoading to show a skeleton; quote loading also skeletons values. Return null or [] to hide the rows. A throwing resolver is caught by the SDK, which logs a warning and hides the rows.

The resolver runs when the destination amount or source-token details change. Changes to your app's account data alone do not refresh the rows, including isLoading; update() currently only updates the theme. Async resolvers and independent host-data refresh are not supported. Before an exact-input quote arrives, the destination amount is unknown and the resolver is not called.

openWithdrawal(params)

Moves a token out of the user's wallet to an address they choose, on any supported chain. The token has to be in the wallet by the time the withdrawal executes, but it doesn't have to start there: preWithdrawalAction runs first and can move it in, and withdrawalSourceTokenBalance reports what is withdrawable in the meantime. The React SDK's WalletWithdrawalConfig, minus the multi-source dropdown — test/types.test-d.ts names each exclusion.

await funkit.openWithdrawal({
  modalTitle: 'Withdraw',
  sourceTokenSymbol: 'USDC',
  sourceChainId: '42161',
  sourceTokenAddress: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
})

By default, the connected wallet signs and sends. That includes an adopted wallet: openWithdrawal() called while adoption is still connecting waits for it to settle first. With nothing connected and no wallet supplied, the call rejects. Connect a wallet with openConnect(), configure adoptedWallet when creating the SDK, or supply the wallet parameter for this withdrawal.

The returned promise resolves when the SDK accepts the request, before the withdrawal completes. Subscribe to withdrawal:confirmation and withdrawal:error for execution results.

| Field | Required | Notes | | --- | --- | --- | | modalTitle | yes | Modal header. modalTitleMeta and iconSrc are optional extras | | sourceTokenSymbol, sourceChainId, sourceTokenAddress | yes | The token leaving the wallet. Chain id as a string, e.g. '42161' | | withdrawalSourceTokenBalance | | Returns the balance shown as withdrawable, replacing what the SDK reads from the wallet. Use it when the funds are still in your system and only arrive in the wallet during preWithdrawalAction | | preWithdrawalAction | | Runs after the quote is accepted and before anything is sent. Return transactions to prepend to a supplied wallet’s sendTransactions batch, or [] after a side effect such as moving funds into the wallet. Throw to abort with your message | | onBeforeSign | | Runs when the user commits, before any signature. Throw to abort | | getMinWithdrawalUSD, getMinWithdrawalAmount, getMaxWithdrawalUSD | | Amount limits | | defaultReceiveToken, disableConnectedWallet | | Receive-side defaults | | swappedTargetTokenAddress, swappedTargetChainId, swappedTargetTokenSymbol | | Fiat off-ramp token, when it differs from the source token | | wallet | | A FunkitWithdrawalWallet that executes this withdrawal. Omit to use the connected wallet, including an adopted one — see below |

Supplying your own wallet

Supply openWithdrawal({ ...params, wallet }) only when the connected wallet can't execute the withdrawal itself. The wallet must be an EVM account that can send transactions and sign typed data and messages; relayers or non-EVM signers that can't do all three are not supported here. This is separate from adoptedWallet: adoption connects an EIP-1193 provider at SDK creation, while the wallet parameter supplies execution methods for one withdrawal and does not change the SDK’s connected account.

The parameter accepts a plain object or a class instance implementing FunkitWithdrawalWallet. Methods retain their original this binding. The shape follows the React SDK's WithdrawalClient with two differences:

  • signTypedData and signMessage are required. Gasless routes (permit, EIP-3009) ask for a signature instead of a transaction, and the withdrawal aborts without one. They are routine for USDC.
  • confirmTransaction resolves with { status: 'success' | 'reverted' }. A full receipt is optional; the adapter supplies the transaction hash it waited on. An ethers receipt reports status: 1 | 0 and does not compile here — otherwise a reverted withdrawal would read as a success. A viem receipt already has this shape; from ethers, map it: status === 1 ? 'success' : 'reverted'.

sendTransactions is optional, and it is the only way to batch. With it, an approval and the withdrawal go out together and the user signs once. Without it, each transaction is sent on its own and the user signs each one. Either way, it must return a transaction hash that confirmTransaction can wait on.

The connected wallet — what you get when you omit wallet — sends one transaction at a time. Batching is only available on a wallet you supply.

That is also why preWithdrawalAction can only return transactions when you supply a wallet with sendTransactions. With the connected wallet, return [].

Events

| Event | Payload | | --- | --- | | checkout:open | — | | checkout:close | { isNewDeposit, isSoftHidden? } | | checkout:success | { type: 'success', message, metadata } | | checkout:error | { type: 'error', message, metadata } | | checkout:validation | { isValid, message } | | checkout:estimation | The complete checkout quote and fee estimation | | checkout:confirmation | Checkout ID string | | checkout:login-required | { onLoginFinished? } for resuming a custom host login. Subscribing takes over the login flow: the SDK then shows nothing and waits for the host. With no subscriber, a disconnected openCheckout() opens the wallet connection modal automatically | | checkout:blocked | { blockedReason } | | withdrawal:confirmation | { withdrawalId, formData } once the withdrawal transaction is sent. withdrawalId is the destination address for this wallet-based flow. formData carries the destination address, chain and token, the amount, and the quote | | withdrawal:error | { type: 'error', message, metadata } | | account:change | { isConnected, address? }, emitted only when either value changes |

The withdrawal modal also emits checkout:close when it closes, but not checkout:open; there is no withdrawal:success event.

getAccount() reads as disconnected until the bridge mounts. The initial disconnected snapshot is not emitted as account:change.

How it's built

  • dist/index.js — ESM for bundlers, code-split so wallet SDKs load on demand
  • dist/styles.css — the SDK stylesheet, re-exported as @funkit/connect-js/styles.css
  • dist/index.d.ts — self-contained declarations; no @funkit/connect install needed for types. The public types are a hand-maintained contract, not a copy of the SDK's: test/types.test-d.ts pins shared fields to the SDK's definitions and lists every upstream field that is deliberately unsupported, so an SDK change forces an explicit decision here. test/pack.test.ts packs the tarball into an empty project and runs tsc with skipLibCheck: false.

@funkit/connect, React and the wallet libraries are devDependencies on purpose: they're compiled in, so listing them as dependencies would make every consumer install a second copy they never use.

Development

pnpm --filter=@funkit/connect-js build   # esbuild + bundled .d.ts + typecheck
pnpm --filter=@funkit/connect-js test    # smoke tests, type-contract tests, packed-package tsc check