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

@gasfree-kit/bridge

v0.2.0

Published

Standalone LayerZero USDT0 bridge for gasfree-kit — production-grade, with idempotency and resilient status tracking

Readme

@gasfree-kit/bridge

Production-grade USDT0 bridge for EVM chains, powered by LayerZero and ERC-4337 smart accounts.

Standalone cross-chain USDT transfers with required slippage guards, idempotent submission, resilient status tracking, and full observability. Used internally by @gasfree-kit/intent and consumable directly when you only need bridging.

What you get

  • Required slippage cap on every quote and send — submissions throw BridgeSlippageError rather than silently accepting any fee
  • Idempotent submission via caller-supplied keys (Stripe-style); retries return the original result instead of double-submitting
  • Resilient status tracking — LayerZero Scan first, balance-poll fallback. Cache → circuit breaker → retry with exponential backoff and full jitter
  • Pluggable signer / status tracker / idempotency store — defaults work out of the box; swap in custom implementations as you grow
  • Typed errors and events — 12 typed error classes, 14 BridgeEvent shapes for observability sinks
  • Strict TypeScriptnoUncheckedIndexedAccess clean, no any in public types, zod-validated inputs at every boundary

Supported chains

Ethereum, Base, Arbitrum, Optimism, Polygon, Celo, Plasma — the same set as @gasfree-kit/core.

Install

pnpm add @gasfree-kit/bridge @gasfree-kit/core @gasfree-kit/evm-4337

After installing, your package manager will prompt you to install the required peer dependencies listed in package.json (an upstream bridge-protocol module and an upstream ERC-4337 wallet module). Both are required — install-time failure beats opaque runtime errors when a peer is missing.

Quick start

import { createBridge, BalancePollTracker, createDefaultStatusTracker } from '@gasfree-kit/bridge';

const config = {
  chains: ['base', 'arbitrum'],
  signer: { type: 'seedPhrase', seedPhrase: process.env.SEED_PHRASE! },
  bundlerUrl: process.env.BUNDLER_URL!,
  paymasterUrl: process.env.PAYMASTER_URL!,
  isSponsored: true,
  sponsorshipPolicyId: process.env.SPONSORSHIP_POLICY_ID!,
};

const bridge = createBridge({
  ...config,
  statusTracker: createDefaultStatusTracker(new BalancePollTracker(config)),
});

// Capture balance before submission so the balance-poll fallback can detect landing.
const balanceBefore = await bridge.snapshotDestBalance(myAddress, 'arbitrum');

const result = await bridge.send({
  sourceChain: 'base',
  destinationChain: 'arbitrum',
  amount: '50.00',
  maxFee: { type: 'percent', value: 0.5 }, // required
  idempotencyKey: `payout-${jobId}`, // optional but recommended
});

const status = await bridge.waitForCompletion(result, {
  sender: myAddress,
  balanceBefore,
  timeout: 300_000,
});

console.log(status.state, status.destTxHash);

Security model

| Concern | Mitigation | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------- | | Slippage / fee griefing | maxFee is required on every quote and send. Re-validated immediately before submission. | | Recipient spoofing | EIP-55 address validation at the boundary. Default recipient = sender. | | Amount overflow / dust | zod-enforced 6-decimal max, positive-only. Optional maxBridgeAmount cap on BridgeConfig. | | USDT-on-Ethereum allowance race | Preserved from the upstream protocol — surface returns resetAllowanceHash. | | Secret leakage in errors | redactSeedPhrase strips any 12/24-word BIP-39 sequence from error messages before they reach observability sinks. | | RPC URL injection | http(s)-only validation. file://, javascript:, etc. rejected at config time. | | Supply chain | Required peer deps — install-time failure, not opaque runtime import error. | | Replay attacks | LayerZero handles cross-chain nonce. Idempotency layer on top prevents application-level double-submits. | | Cancellation | AbortSignal plumbed through quote, send, waitForCompletion. Polling loops respect aborts mid-cycle. | | Observability sinks crashing the SDK | onEvent is wrapped in try/catch — your logger throwing never breaks a bridge submission. |

Idempotency

Pass idempotencyKey on BridgeRequest. Retries with the same key return the cached result; concurrent callers serialize on the store's atomic acquire.

// First call — submits.
const r1 = await bridge.send({ ...request, idempotencyKey: 'invoice-99' });

// Network blip → app retries with the same key.
const r2 = await bridge.send({ ...request, idempotencyKey: 'invoice-99' });

// r1.hash === r2.hash — protocol.bridge() was called exactly once.

The default InMemoryIdempotencyStore protects against double-submits within a single process. For multi-instance deployments, implement IdempotencyStore against Redis / Postgres / a KV store and pass it as config.idempotencyStore:

import type { IdempotencyStore, IdempotencyRecord } from '@gasfree-kit/core';

class RedisIdempotencyStore implements IdempotencyStore {
  async acquire(key: string, lockedUntil: number, ttlMs: number): Promise<boolean> {
    // SET key payload NX PX ttl — atomic; returns true only if the slot was free.
  }
  async get<T>(key: string): Promise<IdempotencyRecord<T> | null> {
    /* GET + JSON.parse */
  }
  async set<T>(key: string, record: IdempotencyRecord<T>, ttlMs: number): Promise<void> {
    /* SET PX ttl */
  }
  async delete(key: string): Promise<void> {
    /* DEL */
  }
}

The interface requires an atomic acquire so non-trivial stores can't accidentally race.

Status tracking

Bridge.waitForCompletion polls the configured StatusTracker. The default CompositeStatusTracker tries LayerZero Scan first with a 2-second aggregate timeout, then falls through to balance polling. Once a tx falls through, subsequent reads skip Scan entirely for that hash.

The LayerZeroScanTracker is wrapped in a four-layer resilience stack:

  TtlCache (30s)
    └─► CircuitBreaker (5 failures / 60s cooldown / half-open trial)
          └─► retry (3 attempts, exponential backoff + full jitter, cap 2s)
                └─► fetch with per-request AbortController (5s timeout)
                      └─► zod-validated response (schema-mismatch falls through)

Every layer emits an event you can subscribe to via BridgeConfig.onEvent:

const bridge = createBridge({
  ...config,
  onEvent: (event) => {
    switch (event.type) {
      case 'bridge:submitted':
        metrics.increment('bridge.submitted');
        break;
      case 'scan:circuit-open':
        logger.warn('LZ Scan circuit open', { cooldownMs: event.cooldownMs });
        break;
      case 'scan:schema-mismatch':
        sentry.captureMessage('LZ Scan schema drift', event);
        break;
      case 'bridge:idempotent-hit':
        metrics.increment('bridge.idempotent_hit');
        break;
      case 'bridge:landed':
        logger.info('Bridge landed', { srcTxHash: event.srcTxHash, elapsedMs: event.elapsedMs });
        break;
    }
  },
});

Custom signers

Only seed-phrase is shipped in v0.1.0. The BridgeSigner type is a discriminated union ready for passkey and external-wallet adapters:

export type BridgeSigner = SeedPhraseSigner;
// future: | PasskeySigner | ExternalWalletSigner;

Use the seedPhraseSigner factory or pass the discriminated-union literal directly. Both work.

Error model

All errors extend GasfreeError from @gasfree-kit/core and carry a stable code:

| Error | Code | When | | --------------------------------- | ------------------------------- | -------------------------------------------------------- | | BridgeValidationError | BRIDGE_VALIDATION | Config or request fails zod schema | | BridgeQuoteError | BRIDGE_QUOTE | Underlying protocol rejects the quote | | BridgeSlippageError | BRIDGE_SLIPPAGE | Quoted fee exceeds the request's maxFee | | BridgeTimeoutError | BRIDGE_TIMEOUT | waitForCompletion deadline elapsed | | BridgeStatusSchemaError | BRIDGE_STATUS_SCHEMA | LayerZero Scan response failed schema validation | | BridgeCircuitOpenError | BRIDGE_CIRCUIT_OPEN | Status tracker's circuit breaker is open | | BridgeIdempotencyConflictError | BRIDGE_IDEMPOTENCY_CONFLICT | Another caller holds the lock and didn't resolve in time | | BridgeProtocolNotInstalledError | BRIDGE_PROTOCOL_NOT_INSTALLED | Bridge-protocol peer dep not present at runtime | | BridgeError | BRIDGE_ERROR | Catch-all for protocol-layer failures |

Versioning

Starting at 0.1.0. The public API surface is stable for everything documented above. Internal modules (protocol/, status/circuit-breaker, etc.) may evolve between minor versions.

License

MIT