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

@epoch-protocol/epoch-flows-sdk

v0.1.7

Published

Headless SDK for Epoch Protocol intent flows (Pay, Swap, Earn). Framework-agnostic core extracted from epoch-intent-widget.

Readme

@epoch-protocol/epoch-flows-sdk

Headless SDK for Epoch Protocol intent flows — Pay, Swap, and Earn (deposit + withdraw). Framework-agnostic core extracted from @epoch-protocol/epoch-intent-widget.

Use this package when you want Epoch's business logic — intent building, market/position fetching, and the full quote → submit → poll-until-settled orchestration — without the React UI. The widget itself is just a UI layer over this SDK; everything the widget does, you can do here with your own components (or no components at all).

your UI  ─────────────►  epoch-flows-sdk  ─────────────►  Epoch allocator + solver network
(React, Vue, Svelte,      (builders, fetchers,            (smallocator, 1delta, SIO)
 vanilla, a CLI, …)        sessions/state machines)

Contents


Install

pnpm add @epoch-protocol/epoch-flows-sdk viem

viem is a peer dependency (^2). You supply a viem WalletClient to the sessions for signing.


Configure

Everything hangs off one EpochFlowsSDK instance:

import { EpochFlowsSDK } from "@epoch-protocol/epoch-flows-sdk";

const sdk = new EpochFlowsSDK({
  apiBaseUrl: "https://allocator.example.com", // Epoch allocator (smallocator). Required.
  positionsBaseUrl: "https://positions.example.com", // 1delta pools/positions proxy. Earn only.
  rpcUrls: { 8453: "https://base-mainnet.my-node.com" }, // per-chain RPC overrides. Optional.
});
interface EpochFlowsSDKConfig {
  apiBaseUrl: string; // all quote/solve/status calls go here
  positionsBaseUrl?: string; // /pools + positions; fetchers degrade to mocks without it
  rpcUrls?: Record<number, string>; // override built-in public RPCs for on-chain reads
}

All exports are also available as free functions (see Full export map) if you'd rather not hold an instance.


Two ways to use it

The SDK splits cleanly into pure pieces (no network, no wallet — easy to unit test) and stateful pieces:

| Kind | What | Examples | | --------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------- | | Pure builders | Turn inputs into an IntentProps / validation result | buildPayIntentFromFlatProps, earn.buildDepositIntent | | Pure adapters/helpers | Reshape 1delta data | flattenConfigsToMarkets, toEpochEarnMarket, estimatedAnnualYield | | Async fetchers | Hit your positionsBaseUrl | earn.fetchLendingPools, earn.fetchUserPositions | | Sessions | Full quote → sign → submit → poll state machine, event-driven | createPaySession, createEarnSession |


Pure builders

No wallet or network needed — pass data in, get an intent (or an error) out.

// Flat pay: validates inputs, looks up token metadata, returns a ready IntentProps
const built = sdk.pay.buildIntent({
  toAddress: "0x4235…89a9",
  toAmount: "0.15", // human/decimal string
  toChainId: 8453,
  toToken: "0x8335…2913",
});
if (built.ok) {
  built.intent; // IntentProps — feed into a PaySession
} else {
  built.error; // human-readable reason
}

// Earn deposit/withdraw: validates the amount against the market/position
const deposit = sdk.earn.buildDepositIntent(market, "100");
const withdraw = sdk.earn.buildWithdrawIntent(position, "50");
// → { ok: true, intent, title, submitButtonText } | { ok: false, error }

// APR-only yield preview (not a guarantee)
const perYear = sdk.earn.estimatedAnnualYield(100, 0.0525); // 5.25% APR → 5.25

Async fetchers (Earn data)

These call your positionsBaseUrl. Without one configured, they degrade to bundled mock data so flows still render.

// Lending pools — fans out one /pools request per chain, in parallel.
const { configs } = await sdk.earn.fetchLendingPools({
  chainIds: [1, 8453, 42161],
  lender: "AAVE_V3,MORPHO", // CSV of 1delta lender keys
  sortBy: "totalDepositsUsd", // depositRate | variableBorrowRate | totalDepositsUsd | totalLiquidityUsd | utilization
  sortDir: "DESC",
  count: 100,
});

// Turn configs into flat markets for a picker
const markets = sdk.earn.flattenConfigsToMarkets(configs);

// A user's open positions (for the withdraw side)
const { positions, summary } = await sdk.earn.fetchUserPositions({
  address: "0x…",
  network: "mainnet",
  configs,
});

Each fetcher accepts an AbortSignal (signal) and a per-call positionsBaseUrl override. Bundled fallbacks: MOCK_MAINNET_MARKETS, MOCK_TESTNET_MARKETS, mockPositionsForAddress, and HARDCODED_ONEDELTA_CONFIGS.


Sessions: the quote/submit/poll state machine

Sessions are where the real work happens. Each one is a TypedEventEmitter — you subscribe with .on(event, fn) (which returns an unsubscribe function) and drive it with quote() / submit().

Wallet client is type-erased to unknown. Pass a viem WalletClient. The alias avoids structural-type mismatches in monorepos that have multiple viem copies installed; the session casts internally.

PaySession (pay/swap)

const session = sdk.createPaySession({
  walletClient, // viem WalletClient
  address: "0xUser…",
  sessionId: crypto.randomUUID(),
  mode: "pay", // 'pay' | 'swap'
  requiredToken: { address: "0x8335…2913", symbol: "USDC", decimals: 6 },
  requiredAmount: 5_000_000n, // atomic units
  isTestnet: false,
  intentConfig: {
    protocol: "transfer", // 'transfer'|'swap'|'bridge' → simple route; else protocol interaction
    action: "pay",
    fixedOutput: true,
    destinationChainId: 8453,
    slippageBps: 100, // 1% output slippage; 0 = strict
  },
});

// Subscribe
const unsub = session.on("statusChange", (s) => console.log("status", s));
session.on("quote", (q) => console.log("pay-side quote", q)); // human + raw input amount
session.on("intentSent", ({ nonce }) => {});
session.on("success", ({ nonce, status }) => {});
session.on("error", (msg) => {});
session.on("requestClose", () => {}); // fires ~2s after settle

// (optional) preview the input cost for fixedOutput intents
await session.fetchQuote({ sourceChainId: 42161, sourceToken });

// Submit → signs, solves, then polls until settled
await session.submit({ sourceChainId: 42161, sourceToken });

// Cleanup
unsub(); // remove one listener
session.dispose(); // clear timers + all listeners

PaySession status: idle → submitting → sent → polling → complete (or error).

EarnSession (deposit/withdraw)

const session = sdk.createEarnSession({
  walletClient,
  address: "0xUser…",
  sessionId: crypto.randomUUID(),
});

session.on("statusChange", (s) => {}); // idle → quoting → submitting → sent → polling → complete | error
session.on("quote", (q) => {}); // EarnQuote: tokenIn/out, executionTransactions, resourceLockRequired
session.on("quoteError", (m) => {});
session.on("progress", (p) => {}); // 0–100
session.on("pollTick", () => {}); // each settle poll with no completion yet
session.on("success", ({ nonce, status }) => {});

const input = {
  tab: "deposit", // 'deposit' | 'withdraw'
  amount: "100", // human string
  market, // EpochEarnMarket (must carry oneDeltaMarketUid)
  sourceChainId: 8453,
  sourceToken, // EpochToken
  network: "mainnet",
};

const quote = await session.quote(input); // optional preview
await session.submit({ ...input, quote }); // reuses the quote if present

Withdraw extras on the input: isAll (protocol max-withdraw), smartWithdraw + smartDestChainId + smartDestTokenAddress (cross-chain delivery).

Event reference

Both sessions share a common spine. Each .on() returns an unsubscribe function.

| Event | Payload | Pay | Earn | Meaning | | ---------------- | ---------------------------- | :-: | :--: | ------------------------------------------ | | statusChange | status string | ✅ | ✅ | Lifecycle transition | | progress | number (0–100) | ✅ | ✅ | Progress bar value | | activeStep | number | ✅ | ✅ | Stepper index | | quote | {raw, human} / EarnQuote | ✅ | ✅ | Quote settled | | quoteError | string | ✅ | ✅ | Quote failed | | isQuoting | boolean | ✅ | — | Quote in flight (pay) | | nonce | string \| null | ✅ | ✅ | Intent nonce | | error | string \| null | ✅ | ✅ | Error message | | start | OnStartCtx | ✅ | ✅ | Submit began | | sign | OnSignCtx | ✅ | ✅ | Signature requested | | intentSent | {nonce} | ✅ | ✅ | Submitted, pre-settle | | intentComplete | {nonce, status} | ✅ | ✅ | Settle finished | | success | OnSuccessCtx | ✅ | ✅ | Settled OK | | errorCtx | OnErrorCtx | ✅ | ✅ | Error with session context | | pollTick | void | — | ✅ | Settle poll, not complete yet | | requestClose | void | ✅ | ✅ | Fires shortly after settle (close your UI) |

Lifecycle methods on both: getStatus(), reset() (back to idle), dispose() (clear timers + listeners). Sessions poll the allocator every 3s after submit until a status of completed / finalized / success appears.


Chain helpers & registries

import {
  EPOCH_SUPPORTED_CHAINS,
  EPOCH_TESTNET_CHAINS,
  EPOCH_SUPPORTED_TOKENS,
  EPOCH_TESTNET_TOKENS,
  getEpochChainById,
  getChainName,
  getEpochTokensByChainEnv,
  getEpochTokensBySymbol,
} from "@epoch-protocol/epoch-flows-sdk";

// On-chain reads via the SDK instance (uses config.rpcUrls, else built-in public RPCs)
const client = sdk.chain.getPublicClient(8453);
const balance = await sdk.chain.fetchTokenBalance(
  8453,
  tokenAddress,
  userAddress,
);
const rpc = sdk.chain.resolveRpcUrl(8453);

Using it from React

The SDK is framework-agnostic, but the bridge is small: create the session, mirror events into state, dispose on unmount.

import { useEffect, useRef, useState } from "react";
import {
  EpochFlowsSDK,
  type PayIntentFlowStatus,
} from "@epoch-protocol/epoch-flows-sdk";
import { useWalletClient, useAccount } from "wagmi";

function usePayFlow(sdk: EpochFlowsSDK, intentArgs) {
  const { address } = useAccount();
  const { data: walletClient } = useWalletClient();
  const [status, setStatus] = useState<PayIntentFlowStatus>("idle");
  const sessionRef = useRef<ReturnType<EpochFlowsSDK["createPaySession"]>>();

  useEffect(() => {
    if (!walletClient || !address) return;
    const session = sdk.createPaySession({
      walletClient,
      address,
      ...intentArgs,
    });
    const unsubs = [
      session.on("statusChange", setStatus),
      // session.on('success', …), session.on('error', …)
    ];
    sessionRef.current = session;
    return () => {
      unsubs.forEach((u) => u());
      session.dispose();
    };
  }, [walletClient, address]);

  return { status, submit: (input) => sessionRef.current?.submit(input) };
}

If you're building in React and don't need a bespoke UI, just use @epoch-protocol/epoch-intent-widget — it ships exactly this wiring plus a polished, themeable component.


Full export map

// Main class + session factories
EpochFlowsSDK;
// Sessions (and their config/event/quote types)
(PaySession, EarnSession, TypedEventEmitter);

// Pure builders
(buildPayIntentFromFlatProps, buildEarnDepositIntent, buildEarnWithdrawIntent);

// Earn adapters / helpers
(toEpochEarnMarket,
  flattenConfigsToMarkets,
  deriveChainsAndLenders,
  oneDeltaPositionsToEpoch,
  oneDeltaPositionsSummary,
  unwrapPoolsArray,
  poolsResponseToConfigs,
  byConfigResponseToConfigs,
  estimatedAnnualYield,
  chainLabelFor,
  HARDCODED_ONEDELTA_CONFIGS);

// Earn fetchers
(fetchLendingPools, fetchLendingPoolsByConfig, fetchUserPositions);

// Mock data
(MOCK_MAINNET_MARKETS, MOCK_TESTNET_MARKETS, mockPositionsForAddress);

// Chain helpers + registries
(getChainPublicClient,
  getDefaultRpcUrl,
  resolveRpcUrl,
  fetchTokenBalanceOnChain,
  EPOCH_SUPPORTED_CHAINS,
  EPOCH_TESTNET_CHAINS,
  getEpochChainById,
  getEpochChains,
  getChainName,
  EPOCH_SUPPORTED_TOKENS,
  EPOCH_TESTNET_TOKENS,
  getEpochTokensByChainEnv,
  getEpochTokensBySymbol);

// Formatting + ids
(formatAmount,
  formatRawAmount,
  formatTokenIn,
  formatBalancePortionForInput,
  trimAmountInput,
  truncateAddress,
  makeId);

All domain types (IntentConfig, IntentProps, EpochToken, EpochChain, EpochEarnMarket, EpochEarnPosition, OneDeltaConfig, the session config/event/quote types, etc.) are re-exported from the package root via export type * from './types' plus the per-module type exports above. See src/index.ts for the authoritative surface.


Concepts & gotchas

  • Amount units. Builder *Human / amount params and flat-pay toAmount are decimal strings. requiredAmount on an intent and session config is a bigint in atomic units. Don't mix them.
  • fixedOutput. true = "deliver exactly requiredAmount; the user pays the quoted input." slippageBps (default 100 = 1%) lets the solver land marginally under the display amount on cross-chain/cross-token routes; set 0 for strict pinning.
  • protocol routing. transfer / swap / bridge map to the solver's simple token-in→token-out task. Any other protocol string is treated as a custom protocol interaction (raffles, vault deposits, etc.).
  • Earn is mainnet-only. The 1delta upstream doesn't index testnet pools.
  • Markets need a oneDeltaMarketUid. EarnSession derives the destination chain + underlying from it and validates them against market.chainId / market.token.address.
  • Listener errors are swallowed. TypedEventEmitter logs (but doesn't rethrow) if one listener throws, so a bad subscriber can't break the others or the flow.
  • Always dispose(). Sessions hold polling/progress intervals; dispose on unmount/teardown to stop them.

License

MIT