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

@stablechain/enterprise

v1.0.0

Published

Stable Enterprise SDK

Readme

Server-side SDK for Stable's enterprise transaction rails: gas waiver (relay gas-waived transactions), guaranteed blockspace (premium Enterprise-lane inclusion), and the composition of the two.

This is a stateless, server-side library that signs with private keys. Never run it in a browser — keep waiver and signing keys on your backend.

For end-user transfer / bridge / swap / earn, use @stablechain/sdk.

Installation

bun add @stablechain/enterprise viem
# or
npm install @stablechain/enterprise viem

viem is a peer dependency (>= 2.0.0). @aws-sdk/client-kms is an optional peer dependency, needed only for the AWS KMS signer (@stablechain/enterprise/aws-kms).

The three modules

| Module | Client | What it does | Gateway? | Signer pays gas? | |---------------------|-------------------------|------------------------------------------------------------------------------|----------|------------------| | gasWaiver | enterprise.gasWaiver | Relay a user's tx with gas waived by a whitelisted account. | No | No (waived) | | guaranteedBlock | enterprise.guaranteedBlock | Send a 0x3F CustomTx through the Enterprise RPC gateway for premium blockspace. | Yes | Yes (funded EOA) | | guaranteedWaiver | enterprise.guaranteedWaiver | Gas-waived tx routed through guaranteed blockspace (composition of both). | Yes | No (waived) |

Enable any combination on one createStableEnterprise call — each module is independent and carries its own signing key (or shares one; see Signing).

Networks

Both rails are live on Stable Mainnet. The SDK is chain-agnostic — pass the chain you want and nothing else changes:

| Network | Export | Chain ID | RPC | Explorer | |-----------------|-----------------|----------|---------------------------------|---------------------------------| | Stable Mainnet | stable | 988 | https://rpc.stable.xyz | https://stablescan.xyz | | Stable Testnet | stableTestnet | 2201 | https://rpc.testnet.stable.xyz| https://testnet.stablescan.xyz|

Both are re-exported from @stablechain/enterprise (they originate in viem/chains), so you need no separate viem/chains import.

Two things are network-scoped and must match the chain you pass:

  • The waiver key. A waiver account is whitelisted in governance per network — a testnet-registered address is not registered on mainnet. Register the mainnet address before your first mainnet relay, or the validator rejects the WaiverTx.
  • The Enterprise API key. The gateway host (https://enterprise-rpc.stable.xyz/<api-key>) is the same on both networks; the key you were issued selects the network. Use the mainnet key with chain: stable.

Everything else — the waiver precompile, the NonceKey precompile backing 2D nonces, lane ids, transaction encoding — is identical across networks.

Quick start

import { createStableEnterprise, stable } from "@stablechain/enterprise";
import { privateKeyToAccount } from "viem/accounts";

const enterprise = createStableEnterprise({
  chain: stable, // `stable` (mainnet) or `stableTestnet`, re-exported from viem/chains
  gasWaiver: {
    account: privateKeyToAccount(process.env.WAIVER_PRIVATE_KEY),
  },
});

const gw = enterprise.gasWaiver!;
const { txHash } = await gw.send(sender, { to: token, data, gas: 150_000n });
// txHash is H_inner — the user-facing inner-tx hash

StableEnterpriseConfig

| Param | Type | Description | |--------------------------|-----------------|-----------------------------------------------------------------------------| | chain | StableChain | Target chain — stable (mainnet) or stableTestnet, re-exported from the package. See Networks. | | rpcEndpoints | string[] | (optional) Stable RPC endpoints, tried in order on failure. Defaults to the chain's built-in RPC URLs. | | enterpriseRpcEndpoints | string[] | (optional) Enterprise RPC gateway endpoints for guaranteed blockspace (holds your API key). | | batchSizeLimit | number | (optional) Max transactions per batched RPC call. Default 100. | | signer | Signer | (optional) Default signer every module signs through when it has no key of its own. See Signing. | | gasWaiver | GasWaiverConfig | (optional) Enable the Gas Waiver module. | | guaranteedBlock | GuaranteedBlockConfig | (optional) Enable the Guaranteed Blockspace module. | | guaranteedWaiver | GuaranteedWaiverConfig | (optional) Enable the composed Guaranteed Blockspace + Gas Waiver module. |

Every module client exposes the same four methods: send / sendBatch (build + sign + relay) and relay / relayBatch (relay pre-signed hex). sendBatch / relayBatch return one BatchResultItem per input, in input order — a failed item is recorded, not thrown.


Gas Waiver

Relay a user's transaction with its gas waived by a whitelisted, governance-registered account.

user EOA  ──signs──▶  InnerTx (gasPrice = 0)
                          │
                          ▼
waiver account ──wraps & signs──▶  WaiverTx ──broadcast──▶  Stable node
(whitelisted)                                                   │
                                                                ▼
                                            validator accepts (waiver is
                                            registered in governance); the
                                            InnerTx executes, gas waived

Two keys are involved:

| Key | Role | |----------------------|---------------------------------------------------------------------------------------| | Waiver key | The whitelisted, governance-registered account that signs the outer WaiverTx. | | User (sender) key | Any EOA that signs the InnerTx. Its gas is waived. |

The call returns H_inner (the user-facing inner-tx hash), not the wrapper hash.

GasWaiverConfig — the waiver key source (SignerSource) plus per-partner InnerTx policy limits: maxGasLimit, maxDataLength, allowedTargets.

Simplest path — send / sendBatch

Pass the signer account and the fields that vary; gasPrice: 0, legacy type, chainId, and the nonce are handled for you.

// single — build + sign + wrap + relay in one call; throws on rejection
const { txHash } = await gw.send(sender, { to: token, data, gas: 150_000n });

// batch — nonces auto-sequenced from the account's pending nonce
const results = await gw.sendBatch(sender, [
  { to: token, data: dataA, gas: 150_000n },
  { to: token, data: dataB, gas: 150_000n },
]);

Low-level — relay / relayBatch (pre-signed)

For the non-custodial flow where the sender signs the InnerTx in their own environment and hands you only the signed hex (the waiver operator never sees the sender key). Build one with buildWaiverInnerTx, signing through a Signer — toSigner(account) for a local key, or a custody signer (KMS/HSM):

import { buildWaiverInnerTx, toSigner } from "@stablechain/enterprise";

const signed = await buildWaiverInnerTx(toSigner(sender), stable.id, { to, data, gas: 150_000n, nonce });
const { txHash } = await gw.relay(signed);
const results = await gw.relayBatch([signed0, signed1]); // one result per input, in order

Guaranteed Blockspace

Relay a GuaranteedTx (type 0x3F CustomTx) through the Enterprise RPC gateway for premium, Enterprise-lane blockspace. Independent of gas waiver.

Unlike gas waiver, the signer pays its own gas, so it must be funded. Each GuaranteedTx carries a 2D nonce keyed to an Enterprise lane, and broadcasting goes only through the gateway (which holds your Enterprise API key).

GuaranteedBlockConfig

| Field | Type | Description | |-----------|----------------|------------------------------------------------------| | account | LocalAccount | Funded account that signs GuaranteedTxs. | | laneId | bigint | Enterprise lane id — must be in [0, ENTERPRISE_MASK - 1]. |

const enterprise = createStableEnterprise({
  chain: stable,
  enterpriseRpcEndpoints: [process.env.ENTERPRISE_RPC_URL!], // gateway (holds the API key)
  guaranteedBlock: {
    account: privateKeyToAccount(process.env.USER_PRIVATE_KEY), // any funded EOA
    laneId: 0n,
  },
});

const gb = enterprise.guaranteedBlock!;

Simplest path — send / sendBatch

Pass the signer and the varying fields. The 1559 fee fields are required (the signer pays gas); chainId, the enterprise nonceKey, and the 2D nonce are handled for you.

The lane is strict-sequential (one uncommitted tx at a time, no mempool queuing) and the 2D nonce is read from the NonceKey precompile at call time, so fire rapid same-lane send()s only after the previous one is committed — or use sendBatch, which reads the base nonce once and sequences the whole batch locally.

const gasPrice = await publicClient.getGasPrice();

const { txHash } = await gb.send(signer, {
  to: recipient,
  gas: 21_000n,
  gasFeeCap: gasPrice * 2n,
  gasTipCap: gasPrice,
});

const results = await gb.sendBatch(signer, [
  { to: a, gas: 21_000n, gasFeeCap: gasPrice * 2n, gasTipCap: gasPrice },
  { to: b, gas: 21_000n, gasFeeCap: gasPrice * 2n, gasTipCap: gasPrice },
]);

Low-level — relay / relayBatch (pre-signed)

Build and sign the GuaranteedTx elsewhere with buildGuaranteedTx, then hand the operator only the signed hex. The enterprise nonce key comes from nonceKeyForLane(laneId):

import { buildGuaranteedTx, nonceKeyForLane, toSigner } from "@stablechain/enterprise";

const signed = await buildGuaranteedTx(toSigner(account), stable.id, {
  to,
  gas: 21_000n,
  gasFeeCap,
  gasTipCap,
  nonce, // the account's current 2D-lane nonce
  nonceKey: nonceKeyForLane(0n), // Enterprise lane 0
});
const { txHash } = await gb.relay(signed);

Guaranteed Blockspace + Gas Waiver

The composition of the two flows: a gas-waived transaction routed through guaranteed blockspace. Both the inner and outer transaction are 0x3F CustomTxs sharing one Enterprise nonceKey, so it broadcasts through the gateway like guaranteedBlock — but the waiver sponsors gas, so the user needs no balance and no fee fields, like gasWaiver.

user EOA  ──signs──▶  inner 0x3F CustomTx (Enterprise nonceKey, fees = 0)
                          │
                          ▼
waiver account ──wraps & signs──▶  outer 0x3F CustomTx ──broadcast──▶  Enterprise
(whitelisted)                      (SAME nonceKey)                       gateway

The call returns H_inner (the inner-tx hash), like gas waiver.

GuaranteedWaiverConfig — the waiver (wrapper) key source (SignerSource) plus laneId and the same maxGasLimit / maxDataLength / allowedTargets inner-tx limits as gasWaiver.

const enterprise = createStableEnterprise({
  chain: stable,
  enterpriseRpcEndpoints: [process.env.ENTERPRISE_RPC_URL!],
  guaranteedWaiver: {
    account: privateKeyToAccount(process.env.WAIVER_PRIVATE_KEY), // whitelisted wrapper signer
    laneId: 0n,
  },
});

const gw = enterprise.guaranteedWaiver!;

// single — build + sign inner (from any EOA) + wrap + relay; no fee fields (gas waived)
const { txHash } = await gw.send(user, { to: recipient });

// batch — inner nonces auto-sequenced from the precompile's base
const results = await gw.sendBatch(user, [{ to: a }, { to: b }]);

Low-level — relay / relayBatch (pre-signed)

The user signs the inner 0x3F CustomTx in their own environment with buildGuaranteedTx (using nonceKeyForLane(laneId)) and hands the operator only the signed hex; the operator wraps it with the waiver key and relays:

import { buildGuaranteedTx, nonceKeyForLane, toSigner } from "@stablechain/enterprise";

const signedInner = await buildGuaranteedTx(toSigner(user), stable.id, {
  to,
  gas: 100_000n,
  gasFeeCap: 0n, // waived — fees are 0 on the inner
  gasTipCap: 0n,
  nonce,
  nonceKey: nonceKeyForLane(0n),
});
const { txHash } = await gw.relay(signedInner); // waiver wraps + broadcasts → H_inner

Signing

Every module resolves its signing key from a SignerSource, in order:

  1. signer — a custody-grade Signer (KMS / HSM / vendor). Set per-module, or set the top-level config.signer once to drive all modules from a single custody backend.
  2. account — an in-process viem LocalAccount (e.g. privateKeyToAccount(...)).
  3. The env signer — STABLE_ENTERPRISE_PRIVATE_KEY — used when neither is set.

Resolution is lazy: a client that only uses guaranteedBlock, or gives each module its own key, never needs an env key set.

Signer helpers exported from the package:

import { toSigner, privateKeySigner, envSigner } from "@stablechain/enterprise";

| Export | Description | |--------------------|--------------------------------------------------------------------| | toSigner(account)| Adapt a viem LocalAccount to a Signer. | | privateKeySigner | Build a Signer from a raw private key. | | envSigner() | Build a Signer from STABLE_ENTERPRISE_PRIVATE_KEY. |

AWS KMS signer

A custody-grade Signer whose private key never leaves AWS KMS, exported from the optional subpath @stablechain/enterprise/aws-kms (requires the @aws-sdk/client-kms peer dependency):

import { createStableEnterprise, stable } from "@stablechain/enterprise";
import { awsKmsSigner } from "@stablechain/enterprise/aws-kms";

const signer = await awsKmsSigner({ keyId: process.env.AWS_KMS_KEY_ID! });

const enterprise = createStableEnterprise({
  chain: stable,
  gasWaiver: { signer }, // waiver key lives in KMS
});

Errors

| Class | Thrown when | |--------------------------------|-------------------------------------------------------------------------| | StableEnterpriseError | Base class for all enterprise errors. | | StableEnterpriseRelayError | A relay was rejected (broadcast failure, gateway rejection, etc.). Carries an ErrorCode. | | WaiverValidationError | An InnerTx failed per-partner validation (bad target, gas/data limits, non-zero gas price). |

Batch methods never throw for individual failures — each BatchResultItem carries either a txHash or an error with a stable ErrorCode. Codes are exported as ErrorCode (e.g. BROADCAST_FAILED, TARGET_NOT_ALLOWED, GATEWAY_UNAUTHORIZED, QUOTA_EXCEEDED).


Example

A runnable backend example covering all three flows lives in examples/enterprise — single and batch sends, a real ERC-20 relay, and the AWS KMS custody path. See its README for setup and .env configuration.

The example targets Stable Testnet, so it can be run end to end without spending real funds. Point it at mainnet by swapping stableTestnet for stable in its shared.ts — and note its ERC-20 relay uses the testnet USD₮0 address, which differs on mainnet.