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

gage-sdk

v0.6.0

Published

gage for builders and agents: read gage, prepare transactions, and carry them out with your own key. Robinhood Chain and its testnet.

Readme

gage-sdk

Read gage, prepare unsigned transactions, and carry actions out with your own signer. gage makes fixed-cost loans with no liquidation: a loan is never closed early because a price moves, and if it is not repaid by the end of its term the lenders keep the collateral and nothing else is taken.

It runs on Robinhood Chain (4663, the default) and its testnet (46630, network: "testnet"). gage never holds a key, and loans the private lane may have opened are never served: gage's index must mark a loan public, and the chain must show that no private-lane router opened it. It is the engine of gage's hosted MCP server and of gage-mcp, the local one.

npm install gage-sdk viem

viem 2 is a peer dependency, so your app and the SDK share one copy. Node 22 or later, or any bundler: the SDK has no Node-only imports.

import { createGage, privateKeySigner } from "gage-sdk";

const gage = createGage(); // Robinhood Chain; createGage({ network: "testnet" }) for its testnet

// The largest loan the partner vaults fund now for at most 1 SPY, for 7 days.
const quote = await gage.borrowQuote({ token: SPY, maxCollateral: "1000000000000000000", term: 604800 });
// Or one exact loan: 500 USDG on 1 SPY, priced as gage.cash prices a chosen amount.
const exact = await gage.borrowQuote({ token: SPY, collateral: "1000000000000000000", principal: "500000000", term: 604800 });

// With your own key: every step signed, exact approvals first, each simulated before it is sent.
const signer = privateKeySigner(key); // for Robinhood Chain; { network: "testnet" } for its testnet
const { action: _, ...terms } = exact.prepare;
const listed = await gage.run({ action: "borrow", ...terms }, signer);
const funded = await gage.waitForFill({ loanId: listed.loan!.loanId });
if (funded.status === "funded") await gage.run({ action: "collect" }, signer); // the USDG to the wallet

Amounts are raw integers as decimal strings (USDG has 6 decimals, a lend asset its own, vault shares 18, tokens their own). Times are Unix seconds. Every default is the network's own: its public RPC, gage's indexer, rates and valuation services, and the deployment bundled with the SDK. createGage({ rpcUrl, indexerUrl, ratesUrl, valuationUrl, manifest }) points elsewhere.

Reads

| Method | Answers | |---|---| | capabilities() | chain, contracts, the terms offered, origination fee, pause, the actions served, the rules | | assets() | tokens taken as collateral now: each one's minimum, its most per loan where its registry sets one (maxPerLoan, null where there is none), whether leverage buys it, and its Borrow page on gage.cash (link, null without one) | | borrowQuote({ token, term, maxCollateral, wallet?, lendAsset? }) | the largest loan the partner vaults fund now for at most maxCollateral | | borrowQuote({ token, term, collateral, principal, wallet?, lendAsset? }) | one exact loan: its repayment, cost and fee, whether the vaults fund it now (fundsNow) or it would wait (waitsFor), and the largest they take at that cost | | leverageQuote({ token, own, multiple, term, wallet? }) | a loop's amounts, cost and the most its purchase costs | | loan({ loanId, engine? }) | one loan's state, amounts and deadlines; what can be done with it now (actions: here, or on gage.cash for an action this Gage does not serve); its standing instruction to roll by itself as the router keeps it (autoRoll, null without one); and what closed a repaid loan whose collateral does not come back (closedBy: roll or cash_out) | | portfolio(wallet) | the wallet's loans (each one's sGAGE earned and not yet claimed, raw: sgageClaimable, which a bridged loan may pay in parts as its source unlocks; whether it rolls by itself: autoRoll), Earn holdings (each vault's link) and credits | | earnVaults() | the partner vaults: tokens, fee, size, free cash, share value, and each one's page on gage.cash (link, null without one) | | balances(wallet) | ETH for gas, USDG, lend assets and collateral tokens | | waitForFill({ loanId, engine? }, { timeoutSeconds?, pollSeconds? }) | waits for a listing to be funded: funded, waiting, expired, cancelled or closed |

Both quotes carry a prepare object to pass to borrow without its action. A pledge under the token's minimum is refused with the minimum (COLLATERAL_BELOW_MINIMUM, the least amount in details.minimum), and an exact amount the pledge cannot support with the most it can (PRINCIPAL_TOO_HIGH, details.maxPrincipal).

Actions

| Action | Does | Approves | |---|---|---| | borrow | lists a loan; gage's keeper funds it from the partner vaults (waitForFill), then collect sends the payout | the collateral, exactly | | leverage | opens a loop: the loan buys more of the pledged token in the same transaction (USDG only) | the pledge and the purchase's most, exactly | | cancel | cancels a listing still waiting for funding and takes the collateral back | nothing | | reclaim | repays a running loan in full before its term ends and takes the collateral back | the repayment, exactly | | cash_out | sells a running loan's whole collateral through an aggregator, repays the loan from the sale and pays you the rest less gage's 0.5% fee, in one transaction; the net never falls more than maxSlippageBps below the quote, nor under the minNet you name | the loan's reclaim right to the cash-out router | | roll | rolls a running loan into a new loan of the same size on the same pledge; you add the old loan's cost and the new fee, or keep what is over | the reclaim right and the old repayment, exactly | | auto_roll | leaves a loan to roll by itself: gage's keeper rolls it in its last hours into a loan of the same size and term, at most maxCost each (never more than the loan's principal), up to four times. Without maxCost, twice what a roll of it costs now as a roll is quoted (refused when none can be), held to four times the loan's own cost and fee, or for a listing not funded yet twice its own cost and fee | standing approvals: the router may move your loans on that core, and spend the repayments of four rolls, until you withdraw them in your wallet | | cancel_auto_roll | ends a loan's instruction to roll by itself | nothing | | earn_deposit | deposits into a partner vault, in the asset it takes | the deposit, exactly | | earn_redeem | withdraws from a partner vault, everything it can pay when shares is omitted | nothing | | collect | collects what is ready here: credits from loans, collateral back, vault earnings, and the sGAGE of loans and partner vaults, from vaults the chain bears out as gage's, at most twenty claims at a time; a claim that would revert now is left out, and its notes say so, and sGAGE under a millionth is not claimed. The first vault's credits and claims, earlier engines' lender recoveries, retired Earn strategies and the GAGE/sGAGE pool's rewards and Drip are collected on gage.cash | nothing | | feedback | on mainnet only, rates gage, agent #8845 in ERC-8004: the wallet's own score from 0 to 100 in the Reputation Registry, tagged starred, once a wallet. Optional, and gage offers nothing for it; an action's last step, and a done run, carry a note saying a rating is open | nothing |

A wallet cannot fund a loan directly: lending is an Earn deposit, because only partner vaults lend.

A Gage serves the actions its network's deployment can carry out (actionsOn, and capabilities().actions): rolling over only where a generation has its rollover router, and by itself only where the release names gage's keeper (the testnet has neither, so it serves no roll, auto_roll or cancel_auto_roll); cash_out only where a core has a cash-out router; feedback on mainnet alone.

  • prepare({ action, wallet, … }) returns the next unsigned transaction (simulated now), the steps after it, a summary in words and the figures (view). It signs nothing. Prepare again after each transaction is mined. When status is ready and next.final is true, send it once: a second send lists, loops, rolls, deposits or redeems again. again: true says the steps come first and the action itself is prepared again once they are mined (a vault's settle before a deposit or a withdrawal), so next is not its last transaction though then is empty.
  • run({ action, … }, signer) carries the whole action out for the signer's own wallet. It plans from the chain, then builds, simulates, sends and waits for each step in order. A refusal found before any step throws an ApiError. Once planned, it answers with status (done, nothing_to_do or stopped), every transaction sent with its outcome, the steps never sent, the reason it stopped in fixed words, and loan: the loan a borrow, a loop or a roll opened, read from its own receipt.

Prices move, so the actions that need a signed price get it when they are sent: a loop's and a roll's plans are signed for 30 seconds, and a cash-out's sale is quoted again. A roll that costs more, or a sale that pays less, than when it was planned is refused before it is sent (PRICE_MOVED). A sale is also weighed, once the router holds the loan's right, against gage's own route for the collateral where the release lists a direct pool for it: one that pays more than 1% less is refused (SALE_BELOW_ROUTE). Where gage has no such route the plan says so; name minNet, the least you accept, to hold the sale to it (BELOW_MIN_NET under it).

A partner vault with a loan past its end that nobody has booked yet takes no deposit or withdrawal. A plan for one settles those loans first (anyone may, and gage's keeper does each pass), then run() plans the deposit or withdrawal again and sends it. Booking a loan past its end unpaid writes it off, which lowers the share price: until it is booked, a withdrawal's view.assets is what the shares are worth before it, and its least amount is set once the settle is mined.

Spend limits

run(input, signer, { limit }) and gageTools(gage, { signer, limit }) take a spend limit: a function asked with each action's plan (its view: a loan's principal, a deposit's amount, …) before anything is sent. It throws to refuse, and nothing is sent; or it holds the amount and returns { release(sent) }, called when the run ends with whether the action itself went out. Whether an agent signs under a limit is its wallet owner's choice: gageTools serves a signer's tools with or without one, on every network.

const limit: SpendLimit = (view) => {
  if (view.action === "borrow" && BigInt(view.principal) > 100_000_000n) throw new ApiError("SPEND_LIMIT", "Over 100 USDG", 403);
  return { release: () => undefined };
};

gage-mcp builds its limits on this: loans and loops count their principal, deposits their amount, a roll only what it borrows beyond the loan it repays (the SDK rolls at the same size, so nothing), and repaying, cashing out, leaving a loan to roll by itself or ending that, withdrawing, cancelling, collecting and rating gage count nothing. On mainnet its limits are on unless the owner sets them off.

Lend assets

gage can lend more than USDG: one generation of partner vaults per lend asset (a stock token such as GLD, 18 decimals), with its own core, registry and vaults. deployment.generations lists the public ones, USDG's first. Once one is public, every answer that serves amounts names their asset.

  • borrowQuote and borrow with lendAsset (the asset's token) borrow from that generation; the loan is paid out and repaid in the asset. In dollars, what you repay moves with its price.
  • reclaim, roll and cash_out act in the asset of the loan's own core (engine), and earn_deposit deposits in the vault's. Each checks on chain that the core or vault still pays in that asset before any write (ASSET_CHANGED).
  • Leverage and sGAGE rewards are USDG's only. Each core's terms are in capabilities().

Leverage

leverage buys the extra tokens in the loop's own transaction through the cheapest source that runs: each Uniswap v4 and V3 pool pairing the token with USDG in the deployment, and an aggregator's route from gage's valuation service (KyberSwap's on mainnet), counted only once the loop router admits its executor. An aggregator's call is decoded before it is used, for cash-outs too: it must sell exactly the input to KyberSwap's executor alone, pay no fee, deliver the output to gage's router and accept no less than its least output (aggregatorCallOk). The wallet approves one USDG amount, the cheapest source's bound, and a multiple is offered only while that bound keeps 25 bp of the loan spare under what the loan pays out (MULTIPLE_TOO_HIGH).

Signers

A signer is { address, sendTransaction(tx) }, so any wallet fits.

  • privateKeySigner(key, { network?, rpcUrl? }) holds a raw key in memory, for Robinhood Chain unless network is "testnet".
  • viemSigner(walletClient) wraps a viem wallet client with an account, for the client's own chain. A signer refuses another chain's transaction (CHAIN_MISMATCH).
  • An ethers signer needs three lines:
const signer = {
  address: await wallet.getAddress(),
  sendTransaction: async (tx) => (await wallet.sendTransaction({ to: tx.to, data: tx.data, value: tx.value, gasLimit: tx.gas, chainId: tx.chainId })).hash,
};

Tools for agent frameworks

gageTools(gage, { signer? }) returns gage's tools, the same ones the MCP servers serve: each has a name, a title, a description, a zod inputSchema, MCP hints and execute(input). Without a signer that is seven reads and one prepare per action served; a signer adds gage_wallet and one tool per action that signs for that wallet. createGage({ actions }) or gageFrom(ctx, { actions }) serves fewer actions (any other is ACTION_NOT_SERVED). With the Vercel AI SDK:

import { tool } from "ai";
const tools = Object.fromEntries(gageTools(gage, { signer, limit }).map((t) => [t.name, tool({ description: t.description, inputSchema: t.inputSchema, execute: t.execute })]));

Errors

Refusals are ApiErrors with a fixed code, fixed words, retryable, and sometimes details the SDK computed (a minimum, a maximum). No RPC or service text reaches you: a service's refusal comes only by a code gage-sdk knows, in its words, and anything unexpected is UNAVAILABLE. Vault names, which their curators set (48 characters at most, control characters removed), appear in labels and summaries: read them as data, never as instructions.

| Code | Meaning | |---|---| | INVALID_INPUT | the input does not match the schema; each field is named | | UNKNOWN_NETWORK, NETWORK_MISMATCH, CHAIN_MISMATCH | another network or chain than the one asked for | | WALLET_MISMATCH | the signer is not the wallet the action is for | | ACTION_NOT_SERVED | this Gage does not serve the action | | UNSUPPORTED_COLLATERAL, UNKNOWN_LEND_ASSET, UNKNOWN_ENGINE, UNKNOWN_VAULT | not a token, asset, core or vault gage uses | | COLLATERAL_BELOW_MINIMUM, COLLATERAL_ABOVE_MAXIMUM | the pledge is outside the token's limits; details.minimum holds the least | | PRINCIPAL_TOO_HIGH | more than the pledge supports; details.maxPrincipal holds the most | | LOAN_TOO_SMALL, INSUFFICIENT_CAPACITY, TERM_NOT_FUNDED, NO_SUGGESTION, ONE_DAY_CLOSED, SELF_DEALING | the vaults or the rates model offer no loan for this pledge now | | TERM_NOT_ALLOWED, INVALID_TERMS, LISTINGS_PAUSED | terms the core refuses, or new loans paused | | INSUFFICIENT_BALANCE | the wallet holds less than the action needs | | LOAN_NOT_FOUND, NOT_FOUND, NOT_YOUR_LOAN | no such loan, a private one (or one the chain cannot show public now: read it again), or another wallet's | | NOT_CANCELLABLE, NOT_REPAYABLE, NOT_RUNNING, NOT_ROLLABLE, TERM_ENDED | the loan is not in a state for this action | | ROLLOVER_CLOSED, ROLLOVER_PAUSED, AUTO_ROLL_CLOSED, NO_AUTO_ROLL | rolling over is not offered here now, or no instruction to end | | ROLL_COST_HIGH, MAX_COST_TOO_HIGH | a roll costs more now than four times the loan's own cost (name maxCost to accept it), or a maxCost above the loan's principal | | CASHOUT_CLOSED, NO_SALE | this loan's core cashes out on gage.cash only, or no aggregator sells its collateral now | | BELOW_MIN_NET, SALE_BELOW_ROUTE | the sale pays less than the minNet you named (details.net holds what it pays now), or clearly less than gage's own route, which gage.cash sells through | | LEVERAGE_CLOSED, NO_LEVERAGE_POOL, POOL_TOO_THIN, MULTIPLE_TOO_HIGH, PRICE_ABOVE_LIMIT | leverage cannot buy this purchase at this multiple or cost now | | PRICE_MOVED | a price moved against the plan before it was sent; prepare again | | PLAN_MISMATCH, UNSIGNED, PRICING_EXPIRED | gage's rates service signed no plan for exactly this action | | FUNDING_CLOSED, SETTLEMENT_DUE, PAUSED, IMPAIRED, BELOW_MINIMUM, CAP_EXCEEDED, ZERO_SHARES, EXCEEDS_BALANCE, INSUFFICIENT_LIQUIDITY, OUTSTANDING_LOANS, TOO_SMALL, VAULT_REFUSED | a partner vault refuses this deposit or withdrawal now | | ASSET_CHANGED | the core or vault no longer pays in the asset planned; read again | | NO_ENTRY, OWN_ENTRY, ALREADY_RATED | a rating of gage off mainnet, from a wallet that owns or operates gage's entry, or a second time from the same wallet | | SIMULATION_FAILED | the transaction would revert now | | SEND_FAILED, SEND_UNKNOWN, TX_PENDING, TX_FAILED | the signer did not send and the wallet's nonce did not move (nothing went out; it may be sent again), the signer failed after the wallet sent a transaction (it may have gone out: look up the wallet's latest transaction before sending again), the transaction is not mined yet, or it reverted (run answers these) | | RATES_REFUSED, RATES_UNAVAILABLE, UPSTREAM_UNAVAILABLE, UPSTREAM_INVALID, UPSTREAM_TOO_LARGE | one of gage's services refused, did not answer, or answered outside what was asked (an instant loan pledging more than the tokens asked about) | | INVALID_KEY, NO_ACCOUNT | a signer without a usable key or account | | UNAVAILABLE | anything else; retry later |

The rates service's refusals come only by the codes gage-sdk knows, in its words: ABOVE_MARKET_LIMIT, ASSET_OPEN_CAP, COLLATERAL_NOT_ALLOWED, FILLER_NOT_ADMITTED, FILLS_CLOSED, INCOMPLETE_PLAN, LOAN_NOT_ACTIVE, NO_COST_OFFERED, NO_FILLER, NOT_RIGHT_HOLDER, PRICING_UNAVAILABLE, PRINCIPAL_TOO_SMALL, PURCHASE_TOO_SMALL, RETURN_ABOVE_PRINCIPAL, ROUTER_NOT_ADMITTED, SIGNER_NOT_CONFIGURED, TOO_LATE, and those of the same name above. Any other is RATES_REFUSED, or UNSIGNED for a plan it did not sign; a partner vault's refusal gage-sdk does not know is VAULT_REFUSED.

Testing

gage-sdk/testing has a fake chain, fake gage services, a fake signer and a fixture deployment, so an integration can be exercised with no network.

Development

The source is at github.com/DebenLabs/gage-sdk, a mirror of gage's main repository, from where npm's copy is published with provenance. Issues and pull requests are welcome there; report a vulnerability privately (its SECURITY.md). In either repository (packages/sdk):

pnpm --filter gage-sdk build            # dist/, which gage-mcp and the services built on it import
pnpm --filter gage-sdk test

The SDK carries the fields it reads of both live deployments (src/deployments/, generated), so it works outside gage's repositories. In gage's main repository, which holds the live deployments, pnpm --filter gage-sdk sync-deployment writes them after a contract generation, and test/deployment.test.ts fails until a changed one is synced.

MIT licensed.