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

@kehtus/proportion-sdk

v9.0.0

Published

TypeScript SDK for Proportion Protocol on Hyperliquid

Readme

Proportion SDK

TypeScript modules for Proportion contracts on HyperEVM, an optional Envio GraphQL indexer, and separate clients for the two challenge services: cs-submitter (gasless relay, offer catalogue) and cs-paper (exam trading). The package uses ESM and requires Node.js 18 or later when used in Node.

Install

npm install @kehtus/proportion-sdk viem

viem is a peer dependency. Use the same installed copy for SDK types and application wallet/public clients.

Configure the SDK

Every contract address is a pin of addresses; the SDK reads no environment variables and discovers no address. Pin the contracts the application uses: MAIN_VAULT, FACTORY, FORWARDER, USDC (MainVault's token), CHALLENGE_FACTORY, CHALLENGE_VAULT, FINANCE_MARKET.

import { ProportionSDK } from '@kehtus/proportion-sdk';

const sdk = new ProportionSDK({
  rpcUrl: config.rpcUrl,
  graphqlUrl: config.graphqlUrl,
  addresses: { MAIN_VAULT: config.mainVault, USDC: config.usdc, CHALLENGE_VAULT: config.challengeVault, CHALLENGE_FACTORY: config.challengeFactory },
});
try {
  const stats = await sdk.vault.getStats();
  const ledger = await sdk.getPaperLedger(); // ChallengeVault.ledger(), checked against the ledger's VAULT()
} finally {
  sdk.disconnect();
}
  • A malformed pin throws in the constructor. A module whose pin is missing rejects on use with "X is not configured". getAddresses() returns a copy of the pins.
  • PaperLedger is never pinned: sdk.getPaperLedger() takes it from the ChallengeVault. The challenges' USDC is sdk.challengeVault.getUsdc().
  • Without graphqlUrl, sdk.indexer is absent; check it once at start-up. graphqlWsUrl is derived from graphqlUrl when omitted. Call disconnect() to close indexer subscriptions.
  • rpcUrl defaults to the public HyperEVM RPC; an injected publicClient takes precedence. Pass walletClient for signing and contract writes.

Challenges and the paper exam

A backer signs an offer (no gas) and publishes it to the cs-submitter catalogue. A trader buys a challenge from it; the exam is a paper account in PaperLedger under the same challengeId, traded with a session key and relayed by cs-paper.

  • Offers: signOffer, hashOffer, allocationGrid; challengeFactory.quote prices one before checkout; ChallengeApiClient.listOffers / publishOffer is the catalogue.
  • Purchase: await getPurchaseSessionKey(store, scope, previousAuthorities) reads the pending purchase key or creates and saves a fresh EOA before checkout (SessionKeyStore, MemorySessionKeyStore for tests). The required history contains all keys the application knows became authority; keep it durable and current, recording activation on a successful receipt before another checkout or scope handoff. The helper rejects an activated key even while it remains in purchase scope, preserving the stored key for recovery. Buy directly or gasless (forwarder.signChallengePurchaseWithPermit → ChallengeApiClient.relayPurchaseWithPermit). Repeating an unresolved scope keeps the key, and every call checks the authority history. The application serializes these calls with checkout and receipt finalization, within a tab and across tabs/processes: two concurrent first calls would each save a key. After parsePurchaseReceipt, put the key under the challengeId scope, then delete the purchase scope. Delete it without moving the key only after explicitly confirming that no attempt can still buy with it; a timeout or pending response does not establish failure. A subsequent purchase gets a fresh EOA. Rotations use createSessionKey(); a former authority must never be reused.
  • Trading: build a trade (marketOrder, limitOrder, stopLoss, cancel, …), sign it with the session key (signTrade) and send it with PaperRelayerClient.submitIntent. subscribe streams the account's state, intents and fills (frames sent while the socket was down are not replayed, so re-read the orders on the first state frame of a connection, not on every state frame); getAccount is the latest state for a key rotation (forwarder.signChallengeVaultCall with action setSessionKey, relayed by ChallengeApiClient.relaySetSessionKey) and for self-pay. When the relayer does not sponsor (503 sponsorship_unavailable), the trader can send the batch item (packItem) with PaperLedgerModule.selfExecute, which sets the relayer's gas bound: a wallet's estimate may fall short on the first trade of a UTC day, which also reads the oracle of every other position, and the gas would be spent without a trade. Exits (close-all and reduce-only) are sponsored in every lane.
  • Key rotation: both an intake 422 state_mismatch and an accepted setSessionKey forward whose status becomes reverted mean stale state. Read GET /paper/accounts/{id} and re-sign with the same new key that has not yet become authority. Keep the active key until activation is confirmed. A pending forward (including gas deferral or send failure) keeps its requestId: poll getForwarderStatus; purchase, withdraw and promote are never retried automatically.
  • Nonces and cancels: sign new orders from nextNonce up (getAccount, every intake answer and every WS state frame carry it), in any order; intake takes nonces below nextNonce + 64 (else 422 nonce_too_far). A nonce is dead below the state's nonceBase, the cancel-all floor, at 2^64 − 1, or once PaperLedgerModule.nonceUsed is true. cancel in mode rest is a soft cancel: cs-paper takes the order off without a transaction and answers 200 with it, while its signature stays valid on chain until its deadline. In mode now the cancel goes on chain; cancelAllBelow (now only) raises the floor past its nonce. 409 target_in_flight while the order is sent: retry after the outcome.
  • Stream: the first state frame of each connection is the account snapshot, including a lower revision after a proven resync: state deduplication restarts from it, and a fill at or below it counts as delivered, while a fill not yet seen still passes below a snapshot that did not roll the cursor back. Later frames apply at once: a state needs a newer revision, or at the same revision a later stage, another holder, another synced or another nextNonce; every intent and lane frame is delivered. Callback failures are isolated. Reconnect waits grow from 1 s to 30 s, each multiplied by a factor in [0.5, 1.5], and return to 1 s on that first state frame. Intent revision is its executing StateChanged revision, otherwise null (GET, WS, orders and intake responses). Intent reason (GET, WS, orders) is the relayer's cause of the outcome. The relayer's cancel (key_changed, account_closed, position_gone, reduce_only, soft_cancel) does not spend the nonce: the signature stays executable on chain until its deadline unless the ledger refuses it for another reason, such as SIGNATURE after key_changed, INACTIVE once the verdict is PASSED or FAILED (a forfeit, reject or refund may leave it ACTIVE) or POSITION_REF while its market has no position opened by its ref.
  • Truth: PaperLedgerModule reads the chain (verdict, check, equityOf, simulate, nonceUsed); readQuotes reads the prices it trades at. The helpers of paper/math.ts only estimate for the UI. challengeStatus turns stage, verdict and chain time into one status.
  • History: the indexer keeps challenges, paper accounts, trades, days and books. latestState(a, b) picks the newer of cs-paper's and the indexer's view of an account.

Service clients

Construct them yourself; ProportionSDK holds neither.

import { ChallengeApiClient, PaperRelayerClient, PaperApiError } from '@kehtus/proportion-sdk';

const submitter = new ChallengeApiClient(config.submitterUrl);
const paper = new PaperRelayerClient(config.paperUrl);
try {
  const accepted = await paper.submitIntent(signedTrade, 'now');
} catch (error) {
  if (error instanceof PaperApiError && error.code === 'sponsorship_unavailable') {
    // The relayer does not pay now: the trader may send the batch item with ledger.selfExecute,
    // which sets the relayer's gas bound rather than a wallet estimate.
  }
  throw error;
}
  • ChallengeApiClient relays already-signed ForwardRequests (purchase, setSessionKey, withdraw, forfeit, promote, funded actions), reads relay status and serves the offer catalogue. ChallengeApiHttpError is an HTTP refusal; body keeps its extra fields.
  • PaperRelayerClient speaks cs-paper's /paper/* API. Writes are authorized by the trade signature; orders, release and the hedge report need paperAuthHeader (a personal_sign valid up to 24 h). PaperApiError carries the service code and the whole answer in body.
  • Network errors pass through unchanged. An error alone does not prove whether a request was accepted: keep request IDs and transaction hashes, read the status before signing again. Purchases, withdrawals and promotions are never retried automatically.

FinanceMarket

Pin addresses.FINANCE_MARKET; it cannot be discovered from MainVault. sdk.financeMarket works through RPC without GraphQL, also with that pin alone.

Use getFinanceFillQuote(id) for { fee, remaining, excess, minimumTraderPay }. Pass the accepted remaining as the explicit cap to fillFinanceRequest(id, cap); do not replace it silently if the price changes before signing. For a new listing, calculateActivationEconomics(size, drawdownBps) reads the exact reserve and premium from the market's own MainVault. Trader escrow must cover the premium.

getReserveContribution(account) reads the contribution snapshot. getClaimableReserveRefund(recipient) and claimReserveRefund(recipient) handle the market's separate refund balance. Only TARGET returns the trader's share of surviving reserve; other close reasons credit the entire remainder to the builder. The premium is never refundable.

Referrals and paying actions

Every paying action carries the referrer the user was shown: challengeFactory.buyChallenge, buyChallengeWithPermit, vault.deposit, vault.activateFunded and the matching forwarder.sign* helpers take a required expectedReferrer. There is no optional argument and no implicit zero; an explicit zero address means "no referrer".

const shown = await sdk.vault.resolveReferrer(user, candidate); // review this and show it to the user
await sdk.vault.deposit(amount, shown); // pass the same frozen value when the user signs

If the referrer in force has changed by the time the call executes, it reverts with ReferralChanged. Review the referrer again before requesting a new signature. A purchase credits the referrer a fixed 5% of the challenge fee (referralCredited6 in the indexer); without a referrer the share goes to the backer's book.

Amounts and returned values

Contract monetary amounts use bigint; USDC amounts use six decimals, perp prices the raw scale (fromRawPrice prints one in USD). Keep exact amounts as integers or decimal strings: viem's parseUnits('123.456789', 6) produces 123456789n, and formatUnits(amount, 6) formats it without a JavaScript number. GraphQL monetary fields are decimal integer strings; cs-paper's decimal strings arrive as bigint.

Contract write methods return a transaction hash. Await and inspect its receipt before treating the operation as complete; for PaperLedger, a refused trade still lands in a successful transaction (a Rejected log), so read the account state, not the receipt status.