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

@covrfun/maker-sdk

v0.1.1

Published

Builders, signing helpers and a gateway client for external market makers on covr's V3 covered-call program.

Readme

@covrfun/maker-sdk

TypeScript building blocks for an external market maker on covr's V3 covered-call program and RFQ gateway. It builds instructions, signs gateway messages and speaks the gateway's WebSocket protocol. It does not price, hold keys, pick an RPC, or submit transactions. Those stay yours.

Every function wraps covr's own reviewed code. covr's tests prove each builder emits the same bytes as covr's builders, and that each signed message passes the gateway's own verifiers. The package ships golden vectors so you can check your own signer the same way.

Status. covr turns gateway access on per desk. Until covr lists your desk on its markets, your bids are verified exactly like real ones and then refused with Bid intent verified but not entered: this market does not list this maker yet (practice mode). They never win and never move prices. Desks can quote JitoSOL today; tokenized stocks stay covr-only for now.

Install

npm install @covrfun/maker-sdk        # Node 24 or later

Runtime dependencies (exact versions): @solana/kit, ethers, ws. The package is ESM with TypeScript types; use it from an ES module project ("type": "module").

Getting started

You need three things from covr: the gateway URL, your bearer token, and your quote key registered with covr. Send covr your quote key address (0x…) and your Solana authority; covr sends the URL and the token. Keep the token secret. You also need your own Solana RPC URL.

  1. Check your signer. Sign the inputs in vectors/golden-vectors.json with your own signer (KMS, HSM) and compare byte for byte (see Golden vectors).

  2. Dry run (sends nothing). The package ships an example bot. Start in a new folder (the bot is an ES module and imports ethers and @solana/kit itself; npm pkg set type=module would change an existing project), copy the bot out of node_modules and run it with your URL, token and RPC. With no MAKER_QUOTE_KEY it signs with a throwaway key and only logs.

    mkdir covr-maker && cd covr-maker
    npm init -y && npm pkg set type=module
    npm install @covrfun/maker-sdk [email protected] @solana/[email protected]
    cp -r node_modules/@covrfun/maker-sdk/examples/maker ./maker
    GATEWAY_WS_URL=wss://<from covr> GATEWAY_TOKEN=<from covr> \
    RPC_URL=https://<your rpc> SPREAD_BPS=500 \
    npx tsx maker/index.ts

    Each JitoSOL RFQ prints DRY_RUN would send bid_intent {...}. maker/README.md lists every setting.

  3. Replace the pricing stub. referencePremiumE18() in maker/index.ts returns a constant 1.00 USDC per token. Put your model there.

  4. Register and fund (one time, your authority signs). Use the setup builders below. Check every step on chain at finalized commitment before the next.

  5. Go live in practice mode. Add MAKER_QUOTE_KEY and NONCE_JOURNAL, then set DRY_RUN=0. The bot reads your on-chain min_nonce, starts at or above it and re-reads it every 5 s; MIN_NONCE can only raise that floor. Until covr lists your desk, every bid is refused as "verified but not entered".

  6. First real order. Once covr lists your desk, the same bot wins orders. A fill shows up only on chain, as your position PDA (derivePosition). The gateway sends no fill message.

  7. After expiry. Run the example keeper for the exercise window (see Expiry keeper).

Expiry keeper

An option you sold expires into an exercise window, expiry to expiry + window (60 s to 1 hour, set by the market). Inside it only your authority or your operator can exercise, paying the strike from your vault. After it anyone can release, which returns the collateral to the writer and frees your exposure. Nobody can release inside the window.

The package ships an example keeper that does both. Each pass it lists your open positions with one getProgramAccounts, exercises a JitoSOL position when a fresh spot price is at least MIN_EXERCISE_EDGE_BPS above the strike and your vault holds the strike, and releases once the window has closed. It only prints what it would send unless DRY_RUN=0. Its default price source reads Jupiter's public Price API: replace it with yours.

cp -r node_modules/@covrfun/maker-sdk/examples/keeper ./keeper
RPC_URL=https://<your rpc> MAKER_ETH_ADDRESS=0x<your quote key address> \
MAKER_AUTHORITY=<your authority> MIN_EXERCISE_EDGE_BPS=100 ONCE=1 \
npx tsx keeper/index.ts

keeper/README.md lists every setting and what each pass checks. Flash exercise (the program pays the strike from the collateral itself) has no builder in the SDK yet: until then an exercise needs the strike in your vault.

Withdraw

withdraw({authority, maker, destination, amount}) needs one Solana signature, your authority's (your operator cannot withdraw), and no EIP-712 signature (signWithdrawal takes a quote back from the gateway; it is unrelated to the vault). The program reserves nothing in the vault: withdraw the USDC your open positions need and their exercises fail; withdraw the premium of a quote you signed and its fill fails. The premium budget and exposure limit only gate new fills.

examples/keeper/withdraw.ts checks your Maker, the vault and the destination, refuses to leave less than the strikes your open positions may still draw (unless ALLOW_SHORTFALL=1), and only prints the instruction unless DRY_RUN=0:

RPC_URL=https://<your rpc> MAKER_ETH_ADDRESS=0x<your quote key address> \
MAKER_AUTHORITY=<your authority> AMOUNT_USDC=250 \
npx tsx keeper/withdraw.ts

API

Constants

PROGRAM_ID, DEPLOYMENT_ID, GENESIS_HASH, USDC_MINT, USDC_DECIMALS, JITOSOL_MINT, JITO_STAKE_POOL, TOKEN_PROGRAM, TOKEN_2022_PROGRAM, CHAIN_ID, MAINNET, EIP712_DOMAIN_BASE, ORDINARY_TERMS_ID, RESTRICTED_TERMS_ID, RESTRICTED_CANCELLATION_TIMEOUT_SECONDS, REGISTRATION_SCOPE, assetAlias(), JITOSOL_ALIAS, USDC_ALIAS, GATEWAY_MAX_FRAME_BYTES, and the auction timing COLLECT_MS, AWARD_MS, TOTAL_MS, REVIEW_SECONDS.

Every function that touches a program address takes an optional last Deployment argument (default MAINNET), for a local validator or tests.

Setup (your Solana authority signs and pays rent)

| Order | Builder | Notes | | --- | --- | --- | | 1 | signRegistrationGrant(quoteKey, authority) then registerMakerV3({authority, makerEthAddress, signature}) | The grant is checked locally first. | | 2 | initializeVault({authority, maker}) | Premium budget starts disabled. | | 3 | initializeExposureV3({authority, makerEthAddress, collateral, limitAtoms}) | Per collateral mint. | | 4 | initializeMintPolicyV1({authority, maker, collateral, settings}) | Build settings with mintPolicySettingsFor(market, caps). | | 5 | bootstrapMintUsageV1({authority, makerEthAddress, collateral}) | Fills fail until this runs. | | 6 | setPremiumBudget({authority, maker, enabled, limit}) | Optional lifetime cap. | | 7 | deposit({authority, maker, source, amount}) | USDC into the vault. | | 8 | configureMaker({authority, makerEthAddress, operator, minNonce}) | Replaces both fields. |

Later changes, signed by the same authority:

  • withdraw({authority, maker, destination, amount}) moves vault USDC to your own USDC token account. Nothing in the vault is reserved: exercises pay the strike from it, so keep enough there for the positions you will exercise (see Withdraw).
  • setExposureLimit({authority, makerEthAddress, collateral, limitAtoms}) takes the same input as initializeExposureV3. The cap only gates new fills. Legacy SPL Token collateral only: the program cannot change a Token-2022 cap.
  • updateMintPolicyV1({authority, maker, collateral, expectedGeneration, settings}) replaces every setting. pauseMintPolicyV1({signer, maker, collateral, expectedGeneration}) sets enabled to false; the policy's watcher may sign it. Both fail unless the policy is still at expectedGeneration, then move it up by one.

PDAs: deriveMaker, deriveVault, deriveExposure, deriveMintPolicy, deriveMarket, derivePosition.

decodeMaker(makerAddress, raw, makerEthAddress) decodes your Maker account (read it at finalized commitment, owner PROGRAM_ID) and checks it is the PDA of that key. Its minNonce is the floor every fill compares a quote nonce to. decodeMintPolicy(policyAddress, raw, {authority, maker, collateral}) and decodeMintUsage(usageAddress, raw, {maker, collateral}) work the same way: the policy's generation (the expectedGeneration above) and settings (enabled, watcher, caps), and the usage counters.

Markets

  • decodeMarketV3(marketAddress, raw) returns typed terms (settlementMode 0 ordinary, 1 restricted). Read the account at finalized commitment and check its owner is PROGRAM_ID.
  • marketDomain(marketAddress, raw) / quoteDomain(decoded) give the EIP-712 domain for that one market. The domain commits to every term, so a signature only verifies for the market you decoded.
  • createMarketV3(input) / createRestrictedMarketV3(input) check the program's bounds (expiry 60 s to 90 days from nowUnix, window 60 to 3,600 s, fee at most 1,000 bps). A market you create is not shown to customers until covr adds it to its catalog and app pins.

Quoting

  • buildBidIntent(snapshot, {maker, price, validUntil}) copies the RFQ fields verbatim. price is USDC x 1e18 per whole token. defaultIntentValidUntil() matches covr's own runner.
  • signIntent(signer, domain, intent) and signQuote(signer, domain, quote) return the exact wire objects. They refuse a signature that does not recover to maker, or a high-s signature the program would reject.
  • buildQuote(snapshot, awardedIntent, {nonce, validUntil}). The gateway admits a quote only if now < validUntil <= now + 60 (its clock) and validUntil <= expiry; leave a few seconds of margin. Write the nonce to durable storage before signing. A timed-out reply can still leave a live quote. The nonce must be at least your on-chain min_nonce (decodeMaker) and never reused.
  • Cancelling. covr's gateway offers desks no quote withdrawal. To cancel signed quotes, raise min_nonce on chain (configureMaker); every fill below the floor fails. Then sign only nonces at or above it (the example bot re-reads the floor every reconcile tick). A quote is valid for at most 60 s anyway. (signWithdrawal builds the body for covr's own internal withdrawal route; it is not served to desks.)

signer is anything with signTypedData(domain, types, value): an ethers Wallet, or an adapter over your KMS or HSM.

Settlement

  • Math, exact like the program (throws where the program refuses): strikePayment, grossPremium, fee, writerPremium, priceForGross, wireQuantity, atomsFromWire, minimumExactLot, settlementAmounts.
  • exactPriceAtOrBelow(priceE18, atoms, decimals): the greatest price at or below your model whose premium for exactly these atoms is a whole USDC atom (covr's own rounding). 0n means none is above zero: skip the RFQ. Bid this, not a price derived from a rounded premium: for most sizes no price pays an arbitrary premium exactly.
  • decodePositionV3({position, maker, slot, makerEthAddress, makerAuthority, nonce, collateral}).
  • fillV3 (the writer signs fills; use it to simulate or audit), exerciseV3 (ordinary), releaseV3, fundRestrictedExerciseV3 and completePendingDeliveryV3 (restricted), encodeCancellationRequestV1.
  • exerciseV3Instructions({market, position, authority, rentLamports, makerCollateral?}) returns the instructions of one exercise transaction: SetComputeUnitLimit at 400,000 (the 200,000 default is too low and the window is short), fresh writer USDC and collateral accounts that only your signer can create, then exercise_v3 paying into them. Sign with authority (or your operator) as fee payer. You may add a SetComputeUnitPrice, never a second limit. That fits one packet with room to spare (1,111 of 1,232 bytes at worst); a separate fee payer adds a signature (1,207 bytes), leaving no room for a Jito tip. Both new accounts are WRITER_ACCOUNT_SPACE (165) bytes: ordinary markets only take collateral mints without extensions. rentLamports is the rent for one of them; your signer pays it twice and the writer keeps it. releaseV3Instructions({market, position, payer, rentLamports}) does the same for a release after the window, at 400,000 CU in both modes.
  • makerCollateral receives the collateral you buy. The program requires a token account for the collateral mint owned by your authority, even when your operator signs, that is not frozen, has no delegate and no close authority other than its owner. Any other account fails every attempt. It defaults to the authority's associated token account for the mint (under the market's collateral program), which must already exist: create it before the exercise window. The helper refuses a wallet, your operator's associated token account, or another account of the exercise here.
  • Releasing Token-2022 collateral needs its account size. Pass collateralAccountSpace to releaseV3Instructions: the token account size with every account extension the mint requires (175 for the stock mints of restricted markets: TransferHook and Pausable; getTokenSize from @solana-program/token-2022 computes it), and rentLamports priced for that size. The helper refuses Token-2022 collateral without it.
  • Rent: read getMinimumBalanceForRentExemption(size) from your RPC at finalized commitment for each account size, every time. The SDK hard-codes no rate (mainnet rent has dropped before) and only checks that rent is positive; too little fails the transaction atomically, which simulation shows.

Gateway

const client = await GatewayClient.connect(wsUrl, token, {assets: [JITOSOL_MINT]});
const signed = new Map<string, BidIntent>(); // RFQ id -> the intent you signed and sent
for await (const event of client.events()) {
  // Do not await handlers here: a slow RFQ read must never delay an award.
  if (event.type === 'rfq') void bid(event).catch(console.error); // getRfq, price, signIntent, sendBidIntent, signed.set
  else if (event.type === 'award') void answer(event).catch(console.error);
  else if (event.type === 'notice') console.log('gateway notice', event.message);
  else if (event.type === 'closed') break; // single-use: connect a new client (with backoff)
}
client.close();

async function answer(award: Award) {
  const intent = signed.get(award.id);
  // Answer only an award whose intent equals the one you signed, and build the quote
  // from YOUR stored intent, never from the copy in the award.
  if (!intent || JSON.stringify(intent) !== JSON.stringify(award.intent)) return client.sendAwardDecline(award.id, award.intent.signature);
  // Check award.awardUntil, durably record nonce, terms and holds, then:
  // await client.sendQuote(intent.signature, await signQuote(signer, domain, buildQuote(snapshot, intent, {nonce, validUntil})));
}

sendBidIntent resolves "BID_ACCEPTED", sendQuote resolves "OK", sendAwardDecline resolves "DECLINED". Gateway refusals reject with GatewayError (code -32602). "OK" is not a fill: the gateway sends no fill notice, so watch for your position PDA on chain.

The client never reconnects; you must. A closed event means a socket ended: the gateway closed it, a heartbeat ping got no pong, it errored (reason says which), or the client closed itself because more than maxQueuedEvents (1000) events went unread (socket: 'client', always the last event). Every socket is pinged every 15 s and ended 30 s after a ping goes unanswered, so a path that dies silently is reported within about 45 s: up to 15 s until the next ping, then 30 s for its pong. Close the client and connect a new one with capped exponential backoff and jitter, as the example bot does. Upgrades time out after 10 s and getRfq after 5 s (handshakeTimeoutMs, httpTimeoutMs).

One request per RFQ is in flight at a time, and its reply carries the RFQ id. If a request times out (requestTimeoutMs, 5 s), its outcome is unknown: a quote may have been admitted. The client then refuses every further request for that RFQ, and the late reply arrives as a notice. A request still awaiting its reply when /maker closes, when the socket fails while sending it, or when you call close() is just as unknown: frames queued behind one write share its result, so the gateway may have read a frame whose send failed. All of these reject with OutcomeUnknownError (method, rfqId, and a send error as cause), never as "not sent": keep a quote's holds until the chain settles it.

Golden vectors

vectors/golden-vectors.json holds a registration grant, an ordinary and a restricted market domain, a bid intent, a quote, a gateway withdrawal and a restricted cancellation request. They are signed with a throwaway key: privateKey = keccak256(utf8("covr-maker-sdk-golden-vectors-v1")).

The file is in the package at @covrfun/maker-sdk/vectors/golden-vectors.json. Recompute each entry with your own signer stack and compare the bytes: domains, digests, signatures and the encoded cancellation request.

Connecting to covr's gateway

Pass covr's URL and your token to GatewayClient.connect(url, token). It sends Authorization: Bearer <token> on every socket upgrade and RFQ read. Desks get three routes: the /maker command socket, the /rfqs/<asset alias> feed, and GET /api/rfqs/<id>. There is no market list: each RFQ snapshot names its market, so read that account from chain (decodeMarketV3).

| Close code | Meaning | | --- | --- | | 4401 | No token, or an unknown one. Closed before any RFQ is seen. | | 4403 | A feed for an asset your token does not allow. | | 4429 Too many connections | More than 16 sockets for your maker, across all its tokens. | | 4429 Too many frames | More than 100 frames at once, or 20 a second after that, on one connection. |

  • The gateway pings every socket every 30 s and closes one that missed the previous ping or has more than 1 MiB of unread data queued.
  • One connection acts as one maker: its token maps to one quote key and one Solana authority.
  • Bids are collected for 3 s and ranked by price. The winner must return a signed quote before the hard deadline (6 s after the RFQ). A decline, a disconnect or any failed quote answer passes the award to the next bidder at once. A silent winner holds it to the deadline and the customer gets no quote, so answer every award: a quote or award_decline.

Not decided by covr yet

  • How maker-created markets reach customers.

Changes

See CHANGELOG.md in the package.

Support

Report problems to covr directly: the bugs link in package.json.