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

@gitbondhq/mppx-stake

v0.5.6

Published

An [`mppx`](https://github.com/wevm/mppx) stake method that proves an [MPPEscrow](https://github.com/gitbondhq/mpp-stake-demo) is **already active** for a given scope and beneficiary.

Readme

@gitbondhq/mppx-stake

An mppx stake method that proves an MPPEscrow is already active for a given scope and beneficiary.

In the default BENEFICIARY_BOUND mode, the credential is an off-chain EIP-712 signature. The server verifies it by recovering the signer and reading isEscrowActive / getActiveEscrow from chain. In OWNER_AGNOSTIC mode, no signature is produced — only on-chain state is checked. No gas is spent during the credential round-trip.

client                                              server
  │  GET /resource                                    │
  │ ────────────────────────────────────────────────► │
  │  402 + stake challenge                            │
  │ ◄──────────────────────────────────────────────── │
  │                                                   │
  │  signTypedData over { challengeId, expires,       │
  │                       scope, beneficiary }        │
  │                                                   │
  │  retry with credential                            │
  │ ────────────────────────────────────────────────► │
  │                                                   │
  │                              recover signer       │
  │                              read isEscrowActive  │
  │                              read getActiveEscrow │
  │                              assert state         │
  │                                                   │
  │  200 + receipt                                    │
  │ ◄──────────────────────────────────────────────── │

⚠️ What this package does not do

It does not create escrows. It only attests that one already exists. Your escrow must be funded on chain before the credential round-trip — if it isn't, assertEscrowOnChain will reject the credential with Escrow is not active for the expected beneficiary.

Anything that touches gas — escrow funding, fee-payer cosigning, transaction submission — is the consumer's responsibility. The package re-exports escrowAbi so you can build that path with viem directly.

Install

npm install @gitbondhq/mppx-stake mppx viem

mppx and viem are peer-adjacent — install them yourself so you control the versions.

Server

Configure a stake method, plug it into Mppx.create, and mount the handler on your route. Per-route fields (amount, scope, anything else specific to that resource) are passed at handler-construction time.

import { serverStake } from '@gitbondhq/mppx-stake/server'
import { Mppx } from 'mppx/server'
import { keccak256, toHex } from 'viem'

const mppx = Mppx.create({
  methods: [
    serverStake({
      name: 'tempo',
      chainId: 42431, // tempoModerato
      contract: '0xe1c4d3dce17bc111181ddf716f75bae49e61a336',
      counterparty: '0x2222222222222222222222222222222222222222',
      token: '0x20C0000000000000000000000000000000000000',
      description: 'Bond required to merge',
    }),
  ],
  secretKey: process.env.MPP_SECRET_KEY,
})

// In your route handler:
const handler = Mppx.toNodeListener(
  mppx.stake({
    amount: '20000', // 0.02 USDC, base units
    scope: keccak256(toHex(`bond:${owner}/${repo}#${pr}`)),
    externalId: `github:${owner}/${repo}#${pr}`,
    resource: `${owner}/${repo}#${pr}`,
  }),
)

await handler(req, res)

The first call returns 402 with a stake challenge. The second call (same URL, with the credential in Authorization) runs verification: HMAC-binds the challenge, recovers the typed-data signer, validates the source DID, reads chain state, and returns the receipt.

The server supports two authorization modes via the mode parameter (StakeAuthorizationMode enum):

  • BENEFICIARY_BOUND (default) — the client signs an EIP-712 proof and the server recovers the signer to verify the beneficiary.
  • OWNER_AGNOSTIC — the client skips signature creation; only on-chain escrow state is checked. Because the bundled verifier is keyed by (scope, beneficiary), this mode requires a custom assertEscrowActive implementation.

Server parameters

| Parameter | Type | Required | Notes | | ------------------ | ------------------------ | -------- | -------------------------------------------------------------------------------------------- | | name | string | yes | Method name shared with the client (e.g. 'tempo'). | | chainId | number | yes | Must be in supportedChains. | | rpcUrl | string | no | Override viem's default public RPC (use a paid endpoint). | | contract | Address | no | Default escrow contract for this route. | | counterparty | Address | no | Default counterparty. | | token | Address | no | Default ERC-20 token. | | mode | StakeAuthorizationMode | no | Defaults to BENEFICIARY_BOUND; set to OWNER_AGNOSTIC with a custom assertEscrowActive. | | description | string | no | Shown to the client in the challenge UI. | | consumeChallenge | (id) => Promise<void> | no | Replay-protection hook — see below. Stateless by default. |

contract, counterparty, and token are defaults — they can be overridden per-route. Anything you don't set in the configuration must be passed at the call site.

Replay protection

verify is stateless by default — a captured credential can be replayed against the same route until its expires lapses. For production, plug in the consumeChallenge hook with a TTL'd store keyed on the challenge id:

import { createClient } from 'redis'

const redis = createClient({ url: process.env.REDIS_URL })
await redis.connect()

serverStake({
  name: 'tempo',
  chainId: 42431,
  contract: '0x...',
  consumeChallenge: async (challengeId) => {
    // Atomic claim — `SET NX` returns null if the key already exists.
    const claimed = await redis.set(
      `mppx:stake:challenge:${challengeId}`,
      '1',
      { NX: true, EX: 600 }, // 10 min, > the challenge `expires` window
    )
    if (!claimed) throw new Error('Challenge already consumed.')
  },
})

The hook fires after HMAC binding and signature recovery succeed (so junk credentials don't burn challenge ids) but before the on-chain read (so a transient RPC failure leaves the slot consumed rather than reusable). Use any atomic claim primitive your store supports — Redis SET NX, Postgres INSERT ... ON CONFLICT, DynamoDB conditional writes — so two concurrent verifies of the same credential can't both succeed. Throw to reject; the verify call will surface your error to the client.

Client

The client method optionally takes a viem Account (or anything with signTypedData) and signs the proof when the server returns a 402. The account's address is what gets bound into the typed-data proof and the did:pkh:eip155:{chainId}:{address} source — so pass the beneficiary's signing account, not a payer or relayer.

import { clientStake } from '@gitbondhq/mppx-stake/client'
import { Mppx } from 'mppx/client'
import { privateKeyToAccount } from 'viem/accounts'

const beneficiaryAccount = privateKeyToAccount(
  process.env.PRIVATE_KEY as `0x${string}`,
)

const mppx = Mppx.create({
  methods: [clientStake({ name: 'tempo', beneficiaryAccount })],
})

// `mppx.fetch` follows the 402 → credential → retry flow automatically.
const res = await mppx.fetch('https://api.example.com/resource', {
  method: 'POST',
})

The server-issued challenge mode is authoritative for client behavior: BENEFICIARY_BOUND requires a beneficiaryAccount to sign the proof, while OWNER_AGNOSTIC skips signature creation entirely (no beneficiaryAccount needed). The client will throw if a BENEFICIARY_BOUND challenge is received without a beneficiaryAccount.

Schema

The challenge request shape both sides agree on:

type StakeChallengeRequest = {
  amount: string                       // base-unit integer string
  beneficiary?: Address                // defaults to the credential signer
  contract: Address                    // escrow contract
  counterparty: Address                // the other party
  description?: string
  externalId?: string                  // application-side identifier
  mode: StakeAuthorizationMode         // 'scope-beneficiary-active' | 'scope-active'
  policy?: string                      // application-side policy tag
  resource?: string                    // application-side resource tag
  scope: Hex                           // bytes32, the per-resource identifier
  token: Address                       // ERC-20 token address
  methodDetails: { chainId: number }
}

The credential payload is a discriminated union based on mode:

type StakeCredentialPayload =
  | { signature: Hex; type: 'scope-beneficiary-active' }  // BENEFICIARY_BOUND
  | { type: 'scope-active' }                              // OWNER_AGNOSTIC

The scope is whatever bytes32 your application uses to uniquely identify "the thing being staked against" — typically keccak256 of a stable identifier (PR number, document ID, session key, etc.).

Parsing a challenge from a 402 response

import { parseStakeChallenge } from '@gitbondhq/mppx-stake'

const challenge = parseStakeChallenge(response, { methodName: 'tempo' })
// challenge.request.scope, challenge.request.amount, ...

Useful when your client needs to render the challenge to a user before deciding whether to sign — e.g. showing the bond amount on a payment page.

Chains

import {
  supportedChains,
  isChainSupported,
  getChain,
} from '@gitbondhq/mppx-stake'

supportedChains is the read-only list of viem Chain definitions this package will create read-only clients for (mainnet, sepolia, base, baseSepolia, tempo, tempoModerato). getChain(chainId) throws on unsupported chains; isChainSupported(chainId) is the non-throwing predicate.

Pass a chainId you already know is supported and the package handles the rest — there's no NetworkPreset or per-chain config object to wire.

ABI

import { escrowAbi } from '@gitbondhq/mppx-stake/abi'

The MPPEscrow ABI as a viem-compatible as const. Useful when you build the escrow-creation flow yourself with viem/actions (e.g. writeContract or simulateContract against createEscrow).

Wire compatibility

The EIP-712 domain (MPP Scope Active Stake / 1), primary type (ScopeActiveStake { challengeId, expires, scope, beneficiary, counterparty, token, amount }), and DID source format (did:pkh:eip155:{chainId}:{address}) still match mpp-stake-demo/packages/mppx-stake byte-for-byte. Challenge requests in this package now also carry mode, so full wire compatibility depends on the peer understanding that request field.

Subpath exports

| Entry | Use | | -------------------------------- | ---------------------------------------------------------------- | | @gitbondhq/mppx-stake | Schema, types, chain helpers, challenge parser. | | @gitbondhq/mppx-stake/client | clientStake() — configures a client method that signs proofs. | | @gitbondhq/mppx-stake/server | serverStake() — configures a server method that verifies them. | | @gitbondhq/mppx-stake/abi | escrowAbi. |