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

@absol-labs/agent

v0.9.1

Published

Metrik agent layer: x402 verified-streaming payments, an MCP server, framework tools, and spend mandates so AI agents can hire and pay verified services safely.

Readme

@absol-labs/agent

The Metrik agent layer: everything an autonomous agent needs to hire and pay for third-party services safely, where money is released only for delivery that is independently verified. Metrik is an infra-agnostic verified-service marketplace and payment rail for AI agents, settling in USDC on Base. The tagline is the design contract: x402 proves the payment, Metrik proves the delivery — and delivery, honestly scoped, is what this package makes an agent able to enforce, never "proven-correct output."

It builds on @absol-labs/sdk, which speaks to the on-chain metered escrow. This layer wraps that client in the guardrails and integration surfaces an agent actually needs: owner-signed spend mandates that gate every fund move, an x402 facilitator that turns a 402 into a verified stream, an MCP server that exposes hire/monitor/reclaim as agent tools, adapters for the major agent frameworks, Reclaim consumer zkTLS for buyer-side delivery proofs, and non-custodial Coinbase CDP/Privy wallets so Metrik never holds a key.

Live on Base Sepolia (chainId 84532), testnet only. App: app.metrik.live. Docs: metrik.live. Frontdoor: github.com/Absol-Labs.

Install

pnpm add @absol-labs/agent

The package is ESM-only ("type": "module") and targets Node >=20 <21. It ships @absol-labs/sdk, @absol-labs/shared, viem, zod, the Reclaim zkTLS SDKs, the Coinbase CDP SDK, and the MCP SDK as dependencies. The autonomous wallet path also installs a prebuilt native OS-keyring binding; it is loaded only when that secure-store path is used. The agent-framework packages (@coinbase/agentkit, @elizaos/core, @langchain/core) are optional peer dependencies — install only the one you use.

What it does

Spend mandates are the "an agent can't run away with the wallet" guarantee. The wallet owner signs an EIP-712 SpendMandate carrying per-stream, total, rate, and duration caps plus an operator allowlist and expiry. Every buyer-side fund move checks the mandate first and hard-stops on denial; MandateDecision returns a machine-readable reason. Buyer-recovery actions (close, reclaim) verify only the signature — never expiry or caps — so a lapsed mandate can never strand the buyer's own funds in escrow.

The x402 facilitator (VerifiedStreamX402Facilitator) turns an HTTP 402 challenge into a real on-chain stream open instead of a one-shot payment. The agent receives a verified-streaming requirement, signs a payload bound to its mandate, and the facilitator opens the escrowed stream through the SDK.

The MCP server exposes discover_services, hire_verified_service, check_stream_status, reclaim_unspent, list_streams, and — when Reclaim credentials are configured — prove_https_response, so any MCP-native agent transacts directly. It reads settlement config from the environment and never accepts a raw key as a tool argument.

Framework adapters put the same mandate-gated client behind AgentKit, ElizaOS, LangChain, and CrewAI, each importable from its own subpath.

Consumer zkTLS (ReclaimConsumerProofService) lets the buyer prove the exact HTTPS response it received, bound to its stream context. This is the L2 delivery signal in Metrik's verification stack — dispute evidence that complements, and never replaces, the oracle's L1 observations.

Non-custodial wallets: resolveAgentWallet() accepts an injected viem signer, a provisioned Coinbase CDP Server Wallet v2 account, or an authenticated Privy embedded EOA. CDP mode uses Coinbase-managed remote signing, while Privy mode adapts the host application's authenticated EIP-1193 provider; neither path reads or exports a private key. See docs/privy-embedded-wallet.md. The Privy adapter does not itself authenticate users. The optional scoped-session broker performs unattended signing behind a user-approved Privy policy and signed Metrik mandate; its first write can request Privy gas sponsorship.

For a fresh autonomous agent, the default headless path is provisionMetrikAutonomousWallet(): it generates a P-256 authorization key locally, stores it in the host OS credential manager, and asks the public Metrik broker to create a policy-bound Privy EOA owned by that key. No Privy secret, OTP, browser, clipboard, or plaintext key file is required, and every later write is signed locally over the exact prepared Privy request. Provisioning refuses a credential store that would not survive a host reboot — including the Linux kernel keyring a headless host silently falls back to — and ships EncryptedFileCredentialStore as the durable headless option. See docs/autonomous-privy-wallet.md. The authenticated user-owned Privy flow remains available as an optional recoverable mode.

Usage

Sign and check a spend mandate

import { privateKeyToAccount } from "viem/accounts";
import {
  spendMandateSchema,
  signedSpendMandateSchema,
  createSpendMandateTypedData,
  checkMandate,
} from "@absol-labs/agent";

const owner = privateKeyToAccount(
  process.env.METRIK_AGENT_PRIVATE_KEY as `0x${string}`,
);

const mandate = spendMandateSchema.parse({
  maxPerStreamUsdc: 5_000_000n, // 5 USDC (6 decimals)
  maxTotalUsdc: 50_000_000n,
  maxRatePerSecondUsdc: 1_000n,
  maxDurationSeconds: 86_400,
  allowedOperators: ["0x28ea4eF61ac4cca3ed6a64dBb5b2D4be1aDC9814"],
  expiresAt: 2_000_000_000,
});

const unsigned = {
  mandateId: `0x${"77".repeat(32)}` as const,
  owner: owner.address,
  chainId: 84532,
  issuedAt: Math.floor(Date.now() / 1000),
  mandate,
};

const signature = await owner.signTypedData(
  createSpendMandateTypedData(unsigned),
);
const signedMandate = signedSpendMandateSchema.parse({
  ...unsigned,
  signature,
});

// Every fund-moving action gates on this. Fail-closed on any denial.
const decision = await checkMandate(
  signedMandate,
  {
    operator: "0x28ea4eF61ac4cca3ed6a64dBb5b2D4be1aDC9814",
    budgetUsdc: 2_000_000n,
    ratePerSecondUsdc: 500n,
    maxDurationSeconds: 3_600,
  },
  0n, // spentSoFarUsdc under this mandate
  { nowSeconds: Math.floor(Date.now() / 1000) },
);

if (!decision.allowed) throw new Error(`mandate denied: ${decision.reason}`);

Agent wallet lifecycle: create/restore → fund → sign

An agent gets a usable wallet entirely through this package — no out-of-band key. resolveAgentWallet() accepts an injected viem private key OR a Coinbase CDP config; the CDP path is create-or-restore: the same ownerName always resolves to the same CDP-managed address, so re-running with the same env restores the identical wallet instead of minting a new one.

import {
  resolveAgentWallet,
  parseAgentWalletEnv,
  getWalletBalances,
  faucetHint,
  requestCdpFaucet,
} from "@absol-labs/agent";
import { MetrikClient } from "@absol-labs/sdk";
import { CdpClient } from "@coinbase/cdp-sdk";

// 1) Create or restore, from env (CDP_API_KEY_ID/CDP_API_KEY_SECRET/CDP_WALLET_SECRET +
//    METRIK_AGENT_CDP_OWNER_NAME, or METRIK_AGENT_PRIVATE_KEY for an injected key).
const wallet = await resolveAgentWallet(parseAgentWalletEnv());
console.log("address:", wallet.account.address);

// 2) Read balance.
const metrik = MetrikClient.baseSepolia({ account: wallet.account });
const balances = await getWalletBalances({
  publicClient: metrik.publicClient,
  address: wallet.account.address,
  usdc: metrik.config.usdc,
});

// 3) Fund (testnet only) — a faucet hint, or request funds programmatically for a CDP wallet.
console.log(faucetHint(84532)); // { cdpFaucetUrl, usdcFaucetUrl, ... }
if (wallet.source === "cdp" && balances.nativeWei === 0n) {
  const cdp = new CdpClient({
    /* same CDP_* creds */
  });
  await requestCdpFaucet({
    cdp,
    address: wallet.account.address,
    token: "eth",
  });
}

// 4) Sign — `wallet.account` is a plain viem `Account`; no extra wrapper needed.
const signature = await wallet.account.signTypedData?.(/* ... */);

getWalletBalances and faucetHint/requestCdpFaucet are deliberately thin: they don't introduce a second wallet system, they just fill in the "read balance" and "get testnet funds" gaps around the existing resolveAgentWallet() / createWalletBackedAgentClient() surface. faucetHint and requestCdpFaucet only support Base Sepolia (84532) — Metrik is testnet-only, single-chain.

See scripts/e2e-hire.ts for the full journey (create/restore wallet → fund → hire → invoke → settle) run against real Base Sepolia infrastructure: pnpm e2e:hire (needs real CDP creds — see .env.example).

Hire via x402: 402 challenge → signed payload → open stream

import {
  VerifiedStreamX402Facilitator,
  encodeX402PayloadHeader,
} from "@absol-labs/agent";

const facilitator = new VerifiedStreamX402Facilitator({
  sdkConfig, // @absol-labs/sdk StreamProofClientConfig (chain, transport, account, escrow, usdc)
});

const challenge = facilitator.challenge({
  operator: "0x28ea4eF61ac4cca3ed6a64dBb5b2D4be1aDC9814",
  serviceRef: `0x${"ab".repeat(32)}`,
  chainId: 84532,
  escrow: sdkConfig.escrow,
  ratePerSecondUsdc: 500n,
  maxDurationSeconds: 3_600,
  maxBudgetUsdc: 2_000_000n,
});

const header = encodeX402PayloadHeader({ challenge, signedMandate });
const { streamId } = await facilitator.open(header); // mandate-gated, opens on-chain

The scheme name is exported as X402_SCHEME; parseX402ChallengeJson, parseX402PayloadHeader, and encodeX402PayloadHeader handle the wire format on both sides.

Run the MCP server

The server runs over stdio and reads its settlement config from the environment; it never asks for a raw key in a tool argument.

pnpm mcp:stdio   # or: pnpm mcp:http

Programmatically:

import {
  METRIK_MCP_TOOLS,
  startVerifiedStreamMcpServerStdio,
  createVerifiedStreamMcpServerFromEnv,
} from "@absol-labs/agent";

console.log(METRIK_MCP_TOOLS.map((t) => t.name));
// discover_services, hire_verified_service, check_stream_status,
// reclaim_unspent, list_streams, prove_https_response

await startVerifiedStreamMcpServerStdio(); // reads process.env, connects stdio
// or, for custom transport wiring:
// const runtime = await createVerifiedStreamMcpServerFromEnv();

Core env surface: METRIK_AGENT_RPC_URL, METRIK_AGENT_ESCROW, METRIK_AGENT_USDC, METRIK_AGENT_CHAIN_ID (84532), and one wallet source — either METRIK_AGENT_PRIVATE_KEY (injected signer) or the CDP set (CDP_API_KEY_ID, CDP_API_KEY_SECRET, CDP_WALLET_SECRET, METRIK_AGENT_CDP_OWNER_NAME). Set RECLAIM_APP_ID / RECLAIM_APP_SECRET to enable the prove_https_response zkTLS tool. See docs/quickstart.md for the full list.

Closed loop: hire → invoke → (prove) → settle

Opening and funding a stream only pays for a service - it does not, on its own, authorize the buyer to call it. The caller-auth gateway (metrik-protocol#62/#63/#64) closes that gap: a seller runs a small reverse-proxy gateway in front of its real service, and a buyer's SDK calls it directly with a short-lived, single-use, stream-bound InvocationCapability - no separate credential, no manual API key.

Seller side - front any HTTP service with the reference gateway:

import {
  CallerAuthGateway,
  createCallerAuthGatewayServer,
} from "@absol-labs/agent";

const gateway = new CallerAuthGateway({
  escrowAddress: "0x...", // the StreamEscrowV2 this service's streams settle on
  rpcUrl: process.env.METRIK_GATEWAY_RPC_URL!,
  serviceRef: "0x...", // this gateway's serviceRef - must match the listing
});

const server = createCallerAuthGatewayServer({
  gateway,
  upstreamUrl: "https://my-real-service.example.com",
});
await server.listen(8787);

Or run the ready-made standalone server from env (METRIK_GATEWAY_ESCROW_ADDRESS, METRIK_GATEWAY_RPC_URL, METRIK_GATEWAY_SERVICE_REF, METRIK_GATEWAY_UPSTREAM_URL, optional comma-separated METRIK_GATEWAY_SERVICE_REFS migration aliases, optional METRIK_GATEWAY_CHAIN_ID / METRIK_GATEWAY_PORT / METRIK_GATEWAY_HOST, optional METRIK_GATEWAY_CORROBORATING_RPC_URLS / METRIK_GATEWAY_MAX_STATE_STALENESS_SECONDS):

pnpm gateway

Every request must carry a valid capability; every failure mode (missing header, bad signature, wrong buyer, closed/expired/underfunded stream, replayed nonce, wrong method/path, wrong serviceRef) is rejected with a distinct machine-readable reason and a 402/403 - the real upstream is never touched on a rejection. Access is revoked automatically: once a stream is closed, expired, or reclaimed, the on-chain read fails closed with no extra bookkeeping.

Stream state is never trusted from a single unverified view. Every configured RPC view is read per request (in parallel, at most one read each) and a request is authorized only if every view authorizes - so a stale view cannot keep serving a stream the buyer just closed. A view reporting a chain head older than METRIK_GATEWAY_MAX_STATE_STALENESS_SECONDS (default 30) is rejected outright (stream-state-stale), and because close/expiry/full-claim are irreversible on-chain, an observed terminal state is remembered for the process lifetime - a later stale active read can never re-authorize. Set METRIK_GATEWAY_CORROBORATING_RPC_URLS to one or more independent providers to collapse the post-close window to the freshest of them.

Buyer side - after hireVerifiedService/open(), call the purchased service directly:

import { privateKeyToAccount } from "viem/accounts";
import {
  createSdkInvokeStreamReader,
  discoverServices,
  invoke,
} from "@absol-labs/agent";

const buyer = privateKeyToAccount(
  process.env.METRIK_AGENT_PRIVATE_KEY as `0x${string}`,
);
const streamReader = createSdkInvokeStreamReader({
  escrowAddress: "0x...",
  rpcUrl: process.env.METRIK_AGENT_RPC_URL!,
});
const { serviceRef } = await streamReader.getStreamV2(streamId);
const listing = (await discoverServices()).find(
  (service) => service.serviceRef.toLowerCase() === serviceRef.toLowerCase(),
);
if (!listing) throw new Error("verified signed listing unavailable");

const { response } = await invoke(
  streamId, // from hireVerifiedService()
  {
    method: "POST",
    path: "/v1/infer",
    body: JSON.stringify({ prompt: "..." }),
  },
  {
    streamReader,
    buyer,
    domain: { chainId: 84532, verifyingContract: "0x..." }, // same escrow as above
    listing, // verified signature + signed callerAuth.accessUrl; no manual credential/URL
  },
);

console.log(await response.json());

invoke() loads the stream, fails closed BEFORE any network call if it is not active, is expired, or does not belong to the signing account, then builds and signs a capability scoped to exactly that one method+path (short expiry, single-use nonce) and attaches it as the x-metrik-capability header. Use capabilityFor() directly if you only need the signed capability without the SDK also performing the fetch.

For a public T2 (consumer-zkTLS) service, invokeWithT2DeliveryProof() attaches the SAME capability to the exact request the buyer's Reclaim attestor proves, so the capability-authorized call IS the delivery evidence - usage and proof become one action:

import { discoverServices, invokeWithT2DeliveryProof } from "@absol-labs/agent";

const { serviceRef: t2ServiceRef } = await streamReader.getStreamV2(streamId);
const publicT2Listing = (await discoverServices()).find(
  (service) => service.serviceRef.toLowerCase() === t2ServiceRef.toLowerCase(),
);
if (!publicT2Listing || publicT2Listing.access !== "public") {
  throw new Error("verified public T2 listing unavailable");
}

const { t2 } = await invokeWithT2DeliveryProof(streamId, {
  streamReader,
  buyer,
  domain: { chainId: 84532, verifyingContract: "0x..." },
  request: {
    url: "https://public-t2-service.example.com/v1/infer",
    method: "POST",
    responseMatches: [{ type: "regex", value: '"result":"(?<result>.*)"' }],
    nonceInjection: { in: "query", name: "nonce" },
  },
  intervalIndex: 0n,
  nonce: deliveryNonce, // from GET /delivery/nonce
  listing: publicT2Listing,
});

// t2.body is the exact POST /delivery/proof request payload.

Gated consumer-attested invocation fails closed for now: proof-target pinning does not yet distinguish the public oracle origin from a signed access gateway. Gated services use output-probe verification until that protocol support exists.

Consumer zkTLS (delivery proofs)

ReclaimConsumerProofService (and createReclaimConsumerProofServiceFromEnv) generate a Reclaim-backed proof of the exact HTTPS response the agent consumed, verified locally against the declared URL, method, body, match, and redaction policy before it is trusted. VerifiedStreamX402Facilitator.proveConsumedHttpsResponse() binds that proof to the same buyer/mandate context that opened the stream. When credentials require private headers or cookies, use the programmatic service or the facilitator directly rather than the MCP tool, so secrets never pass through model-visible tool arguments. Treat the result as an L2 signal: the verifier cross-checks it with L1/oracle evidence before any settlement decision.

Framework adapters

Framework adapters are intentionally not re-exported from the top-level barrel — some pull heavy optional dependency graphs — so import each from its own subpath:

import { metrikActionProvider } from "@absol-labs/agent/agentkit";
import { metrikElizaPlugin } from "@absol-labs/agent/eliza";
import { createLangChainVerifiedStreamTools } from "@absol-labs/agent/langchain";
import { createCrewAiVerifiedStreamMcpConfig } from "@absol-labs/agent/crewai";

Per-framework guides: docs/langchain.md, docs/eliza.md, docs/crewai.md.

Requirements

  • Node >=20 <21, package manager [email protected].
  • ESM only — this package has no CommonJS build; consume it from an ESM context.
  • Framework adapters need their matching optional peer dependency installed. Known caveat: the AgentKit adapter pulls a heavy graph (@coinbase/agentkit / ZeroDev) that can break a flat npm install; prefer pnpm and import the adapter only from its @absol-labs/agent/agentkit subpath.

Develop

corepack enable && corepack prepare [email protected] --activate
pnpm install
pnpm typecheck
pnpm test
pnpm build

The x402 facilitator, zkTLS proof path, signed-mandate engine, MCP server, and framework adapters are all real and tested in-repo. The *.int.test.ts integration suites deploy the hardened StreamEscrow on a throwaway anvil node and self-skip when anvil is not on PATH, so default CI stays green. A full hire → stream → settle walkthrough lives in examples/hire-stream-settle.ts.

Links