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

@vulcx/sdk

v0.7.1

Published

TypeScript client for Vulcx, the best-price swap router for Fogo — multi-hop split routing, firm quotes, and ready-to-sign transactions.

Downloads

335

Readme

@vulcx/sdk

TypeScript client for Vulcx, the best-price swap router for Fogo. Give it a token pair and an amount; it searches every pool on the chain — up to five hops, splitting across pools when splitting wins — and hands back a transaction you can sign. Works in Node.js, browsers, and any runtime with fetch.

Install

npm install @vulcx/sdk

Or via CDN:

<script src="https://unpkg.com/@vulcx/sdk/dist/index.umd.js"></script>

Quick start — no key required

quote, swap and instructions all answer anonymous requests, so the first call needs no setup at all:

import { VulcxSDK } from "@vulcx/sdk";

const vulcx = new VulcxSDK();

const quote = await vulcx.quote({
  inputMint: "So11111111111111111111111111111111111111112", // FOGO
  outputMint: "uSd2czE61Evaf76RNbq4KPpXnkiL3irdzgLFUMe3NoG", // USDC on Fogo
  amount: "1000000000", // 1 FOGO (9 decimals)
  swapMode: "ExactIn",
  slippageBps: 50,
});

console.log(`Output: ${quote.amountOut}, Impact: ${quote.priceImpactPercent}%`);

Anonymous callers get a small per-IP budget: 2 requests/second, burst 20, and a /swap build costs 5 of those units. A key raises that to your plan's budget — 20 cost units/second on the free plan, up to 2000 on Pro — and is required for the WebSocket quote stream, which also needs a plan that includes stream connections.

const vulcx = new VulcxSDK({ apiKey: process.env.VULCX_KEY });

Keys are self-serve and the free plan costs nothing: sign in at portal.vulcx.xyz with Google or GitHub and create one. The portal is also where a key's default integrator fee and the wallet that fee pays live.

Runnable examples

examples/ has three self-contained programs and a cookbook of the same calls as raw curl:

  • node-quote-to-swap — quote → build → sign → submit → confirm, in one file, with the error handling an integration actually needs. Dry-runs by default.
  • browser-wallet — the browser signing flow. No wallet advertises a Fogo chain, so the wallet signs only and the page submits to mainnet.fogo.io itself.
  • integrator-fee — charging your own fee, and reconciling what you receive.

examples/README.md is the cookbook: every endpoint as curl, and the full table of error code values with what to do about each.

Firm quotes — the price you saw is the price you commit

Every quote carries a quoteId. Hand it back and the engine replays that exact route rather than re-routing against whatever the book looks like a moment later:

const quote = await vulcx.quote({ /* … */ });

const swap = await vulcx.swap({
  userWallet: "9WzDX...",
  inputMint: quote.inputMint,
  outputMint: quote.outputMint,
  amount: quote.amountIn,
  swapMode: "ExactIn",
  quoteId: quote.quoteId, // replay this route, min-out anchored to this price
  firm: true,             // price-or-fail, within quote.firmForMs
});

With firm: true, slippage collapses to a fixed margin around the quoted price. If the market moved past it you get a QuoteStaleError before a transaction is built — not a signed transaction that reverts on chain.

quote.validForMs is how long quoteId stays redeemable; quote.firmForMs is the shorter window in which firm: true is accepted.

API

new VulcxSDK(config?)

| Parameter | Type | Default | Description | | --------- | -------- | ------------------------- | ----------- | | apiKey | string | optional | Raises you to the published per-key budget. Without one, requests are served anonymously at a much smaller per-IP limit. /api/v1/stream always requires one. | | baseUrl | string | "https://api.vulcx.xyz" | Point this at your own instance if you self-host | | timeout | number | 30000 | Request timeout in ms | | retries | number | 2 | Retry count for 429/5xx |

sdk.quote(params): Promise<QuoteResponse>

Best route and estimated output. Returns amountIn, amountOut, priceImpactBps, priceImpactPercent, priceImpactSeverity, hopCount, a routes array, and the firm-quote fields above.

sdk.swap(params): Promise<SwapResponse>

A base64 unsigned transaction, plus lastValidBlockHeight and a simulation result.

const swap = await vulcx.swap({
  userWallet: "9WzDX...",
  inputMint: "So11111111111111111111111111111111111111112",
  outputMint: "uSd2czE61Evaf76RNbq4KPpXnkiL3irdzgLFUMe3NoG",
  amount: "1000000000",
  swapMode: "ExactIn",
  slippageBps: 50,
});

The server simulates the transaction before returning it, and a simulation that fails is an error, not a 200 you have to inspect. You get no transaction, and the reason is typed:

try {
  const swap = await vulcx.swap({ /* … */ });
  // Reaching here means the simulation passed. Sign and submit.
} catch (err) {
  if (err instanceof QuoteStaleError) {
    // 409 — the price moved between quoting and building. Re-quote and retry.
  } else if (err instanceof BadRequestError) {
    // 400 — insufficient funds. Retrying changes nothing.
  } else if (err instanceof VulcxError && err.statusCode === 422) {
    // The route cannot execute as shaped.
  }
}

The SimulationResult rides along on the thrown error's body at data.simulation, with insufficientFunds and slippageExceeded set. The error's code says the same thing more directly — SIM_INSUFFICIENT (400), SIM_SLIPPAGE (409), SIM_FAILED (422) — so branch on either, never on message text.

skipSimulation: true opts out entirely: nothing is simulated, so nothing gates, and swap.simulation is absent. You are then responsible for whatever the transaction does on chain.

sdk.instructions(params): Promise<InstructionsResponse>

Raw instructions plus addressLookupTableAddresses, for composing the swap into a transaction of your own — adding a memo, creating accounts, batching.

Errors

Every error extends VulcxError, which carries statusCode and — the thing to branch on — code, the API's stable reason string:

catch (err) {
  if (err instanceof VulcxError) {
    switch (err.code) {
      case "SIM_INSUFFICIENT": /* wallet is short; retrying changes nothing */ break;
      case "SIM_SLIPPAGE":
      case "QUOTE_STALE":      /* the price moved; re-quote and retry */       break;
      case "POOL_DATA_MISSING":/* engine still loading; retry shortly */       break;
      default:                 /* fall back to err.statusCode */               break;
    }
  }
}

code is never renamed or reused once shipped; message is prose the server may reword, so do not match on it. New codes get added — treat one you do not recognise as its HTTP status class. It is undefined on a transport failure with no response body. The full table is in the cookbook.

The typed classes below cover a status each, which is enough when a status has only one meaning. Several do not: 409 is both QUOTE_STALE and SIM_SLIPPAGE, 404 is NO_POOL, NO_ROUTE or POOL_DATA_MISSING. That is what code is for.

import {
  VulcxSDK, VulcxError, NoRouteError, RateLimitError, AuthError,
  BadRequestError, QuoteExpiredError, QuoteStaleError,
} from "@vulcx/sdk";

try {
  const quote = await vulcx.quote({ /* … */ });
} catch (err) {
  if (err instanceof NoRouteError)          console.log("No route for this pair or size");
  else if (err instanceof RateLimitError)   console.log("Backed off — retry shortly");
  else if (err instanceof AuthError)        console.log("Key is invalid or revoked");
  else if (err instanceof QuoteExpiredError) console.log("quoteId aged out — re-quote");
  else if (err instanceof QuoteStaleError)   console.log("Price drifted past the firm margin");
}

QuoteExpiredError (410) only appears when redeeming a quoteId. QuoteStaleError (409) has two causes: a redeemed route that drifted past the firm margin, and a plain swap()/instructions() call — no quoteId involved — whose simulation failed on slippage. Both mean the same recovery: fetch a fresh quote and retry.

Quotes expire in about three seconds (validForMs), and the firm window (firmForMs) is roughly 400 ms. Both are measured from when the quote was minted, not from when you send it, so do not hold a quoteId across a user interaction — a wallet popup will outlive it and swap() throws QuoteExpiredError. Either build on the same tick you quote, re-quote when the user clicks, or omit quoteId entirely and let the server re-quote at build time under your slippageBps. The firm window is only reachable from machine flows: session-key signing, bots, keepers.

Note also that this SDK retries 429s and 5xx with a one-second first backoff, which on its own already exceeds firmForMs — so a firm redemption that hits one transient 429 is guaranteed to land outside its window. retries is set per client, not per call, so use a second instance for firm flows:

const firm = new VulcxSDK({ apiKey: process.env.VULCX_KEY, retries: 0 });

React

import { VulcxSDK } from "@vulcx/sdk";
import { useEffect, useState } from "react";

const sdk = new VulcxSDK({ apiKey: process.env.NEXT_PUBLIC_VULCX_KEY });

function SwapPage() {
  const [quote, setQuote] = useState(null);

  useEffect(() => {
    sdk
      .quote({
        inputMint: "So11111111111111111111111111111111111111112",
        outputMint: "uSd2czE61Evaf76RNbq4KPpXnkiL3irdzgLFUMe3NoG",
        amount: "1000000000",
        swapMode: "ExactIn",
      })
      .then(setQuote);
  }, []);

  return <div>{quote && <p>Output: {quote.amountOut}</p>}</div>;
}

A browser-visible key is readable by anyone using the page. Ask for an origin-locked key for client-side use: the edge enforces the allowlist on both the REST chain and the /stream handshake, which is the only thing making a public key safe. An unlocked key in page source is a key you have given away.

Links

MIT