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

@botanary/sdk

v0.1.0-alpha.12

Published

Typed Botanary application, connection and operation API client.

Readme

@botanary/sdk

Alpha version 0.1.0-alpha.12, API contract 2026-09-12. MIT licensed. The package has not been verified against a deployed platform API. Node 22 or later is the server target.

// Server only. The value comes from a secret environment variable, never a browser bundle.
import { createBotanary } from '@botanary/sdk';

const botanary = createBotanary({ apiKey: process.env.BOTANARY_SECRET_KEY! });
const context = await botanary.app.context();
console.log(context.publicAppId, context.environment, context.chainIds);

Create an organization, application and scoped Test key in the console. app:read is required for this authenticated request. Test exposes only served testnet capabilities; Live exposes only served mainnet capabilities. This call verifies a credential but grants no wallet authority.

client.rest exposes generated typed methods from the reviewed developer OpenAPI contract. It uses openapi-fetch; unsafe URLs, unreviewed routes and owner-only routes are refused before a key is sent. Each client instance retains its own key and context. Keys and payloads are not attached to errors.

Reads have up to two additional attempts by default. Retries respect Retry-After and the overall 30-second deadline. A Retry-After longer than the configured maximum is returned to the caller, not shortened. Mutations retry only if their reviewed route explicitly requires idempotency and the same valid key is present; operation creation supports this contract. Cancellation stops retries. BotanaryError carries status, code, request ID, retry delay and available operation reconciliation handles. A timeout never proves that an operation failed to submit.

@botanary/sdk/browser contains browser-safe errors, exact base-unit amounts and generated types. It has no server-key constructor. The package never signs for an owner. Account/connection actions are added with their consent contracts; do not use an app key on existing owner API routes.

When rotating a key, create a replacement with the required scopes, verify it server-side and revoke the old key after the chosen overlap. If the secret was lost before saving, create another and revoke the uncertain key. App/key revocation stops API access; on-chain permission revocation is a separate owner-signed operation.

Hosted customer connections

The server creates the request and stores its verification record in the initiating customer's server session. Send only request.url to the browser:

const { request, verification } = await botanary.connections.begin({
  redirectUri: 'https://your-app.example/botanary/callback',
  scopes: ['accounts:read'],
});

After the owner returns, atomically consume that server session's verification record and pass the callback URL to botanary.connections.exchangeCallback(callbackUrl, verification). It checks state, callback destination and result shape before exchanging the PKCE code. Store returned tokens on the server. createConnectionClient({ accessToken }).account.context() reads only the selected account. Neither an app key nor a connection token is an owner signer. These clients are not browser exports.

Request balances:read and activity:read in consent and the app key to use customer.account.balances() and customer.account.activity({ limit: 25, cursor }). Both select the consented account and chain without caller account overrides. Balances retain exact amountRaw strings and nullable metadata/prices. building is unknown, not a confirmed empty account; degraded keeps stored holdings. readAt is calculation time, not last chain sync. Activity pages expose recorded Botanary audit events with settlement: 'unverified', not complete chain history or confirmed operation receipts. Pagination cursors bind to the connection and consent version. These safe reads support the client's bounded retry, timeout and cancellation policy.

connections.refresh(refreshToken) rotates both tokens. Coordinate concurrent refreshes with shared server storage: reuse revokes the token family, including its newest access token. The SDK never retries refresh or code exchange automatically. Reconnect after an uncertain token response. Use a replacement active same-app/environment key with every consented scope during key rotation.

connections.list(), get(id) and revoke(id) manage connection API access. Revocation does not revoke existing on-chain grants. For a local consent page, configure consentOrigin explicitly, for example http://localhost:3000; the default is https://app.botanary.xyz. The API base URL is configured separately. This custom hosted protocol does not claim OAuth standards compliance.

To request access for a registered agent, pass agentRegistrationId to connections.begin with accounts:read, agents:read and the additional customer scopes your integration needs. The app key must also carry agents:write and connections:write. The owner reviews the exact agent key and chooses its API access separately. Account consent alone does not connect the agent. Use a distinct agent key for each customer connection. agents.get(id) reports the current API access status and connection ID; neither field proves an on-chain grant.

Customer operation requests

Request operations:write and operations:read in both the server key and customer consent. Persist an idempotency key with each customer intent before making the request. The selected connection fixes the account and network; all amounts are positive integer base-unit strings.

import { createConnectionClient } from '@botanary/sdk';

const customer = createConnectionClient({ accessToken: process.env.BOTANARY_CONNECTION_TOKEN! });
const operation = await customer.operations.create({
  kind: 'send', chainId: 84532, assetRef: 'eip155:84532/slip44:60',
  amountRaw: '1000000000000000', to: '0x1111111111111111111111111111111111111111', gasMethod: 'native',
}, { idempotencyKey: 'persisted-customer-action-id' });
const current = await customer.operations.get(operation.id);

Send and same-chain swap intents return a durable operation ID and an origin-checked approvalUrl. The hosted /sign screen reviews the selected account, exact amount, fees and expiry before local owner signing and relay. App and connection credentials cannot build or sign for the owner. A confirmed or failed result is returned only when its non-simulated receipt matches the operation, submission attempt, UserOp hash, account, chain and finalized execution. Confirmed effects also match the approved asset identities and exact base-unit amounts. Local API and browser preview tests do not establish a deployed customer journey or settlement.

operations.wait(id, { timeoutMs: 60000, signal }) polls only the existing operation. A timeout or access error preserves BotanaryError.operationId; a create transport error preserves its idempotencyKey. Reuse the same creation key and identical intent to recover a lost response. Never start another financial action merely because a wait timed out. The SDK accepts keys of 8 to 128 letters, digits, underscores or hyphens. Read/build/relay acknowledgments remain settlement: 'unverified'. Keep submission.attemptId, submission.userOpHash and any transactionHashHint while status is unresolved. The hint helps discovery and is not proof. After a reversal, the same operation returns to unresolved; reload it by ID and do not request a fresh signature for the earlier uncertain attempt.

Public grants

Register and connect a distinct agent key, then use client.grants.create, list, get and revoke from the server-held app client. Creation takes a complete Test-only grant policy and an idempotency key. The response includes a durable grant ID, owner approval URL, permanent permission ID, exact compiled calls, independent API access status and strictly observed chain state. Preserve the ID and original idempotency key after uncertainty. Revocation is another durable owner operation and is not complete until its exact on-chain effect is finalized. The browser entry exports only the closed parsePublicGrant projection and public types, never an app credential constructor.

Webhook endpoints and raw-body verification

This package includes server-side webhook controls. Give an app key webhooks:read for endpoint/history reads and webhooks:write for creation, changes, rotation, tests and redelivery. Console owners, admins and developers can manage endpoints; viewers can inspect them. Destinations must use public HTTPS on port 443. Test and Live endpoints and secrets are separate.

const created = await client.webhooks.create({
  url: 'https://your-app.example/botanary-webhook',
  eventTypes: ['connection.created', 'connection.updated', 'connection.revoked'],
});
// Store created.secret on the receiver server immediately. Reads never return it again.
const delivery = await client.webhooks.test(created.endpoint.id);
const history = await client.webhooks.deliveries(created.endpoint.id, { limit: 50 });
const details = await client.webhooks.delivery(created.endpoint.id, delivery.id);

webhooks.list, get, update, delete, rotate and redeliver use the same instance's app/environment credential. Delivery history uses nextCursor for stable pagination. A deleted endpoint keeps scoped delivery history and loses its encrypted signing secrets. A pending delivery cannot be manually redelivered. Delivery/rotation creation is not automatically retried after an ambiguous response; inspect history or rotate again explicitly if a one-time secret response is lost.

Use the standalone server export in your HTTP handler, before any JSON body parser:

import { WebhookVerifier } from '@botanary/sdk/webhooks';

const verifier = new WebhookVerifier({
  secrets: process.env.BOTANARY_WEBHOOK_SECRET!,
  appId: process.env.BOTANARY_APP_ID!,
  environment: 'test',
});
// request is the incoming Fetch API Request from your server framework.
const rawBody = new Uint8Array(await request.arrayBuffer());
const event = verifier.verify(rawBody, request.headers);

For Express, install express.raw({ type: 'application/json', limit: '1mb' }) on this route before express.json() and pass its Buffer plus req.headers. Never parse and reserialize the signed body. Verification checks the HMAC, default five-minute past/future timestamp tolerance, event ID, payload version, app and environment. Signature headers follow Standard Webhooks.

Commit the event ID to a durable transactional inbox before returning a 2xx acknowledgement; a retry can deliver the same event again. Deduplicate on (appId, environment, event.id). The unsigned botanary-delivery-id header is diagnostic, not a deduplication or authorization boundary. Event IDs and bodies survive redelivery, while signature timestamps change. Events can arrive out of order: fetch current resource state when necessary, and never infer financial success from a diagnostic webhook.test event or an HTTP relay acknowledgement.

webhooks.rotate(id, overlapSeconds) accepts zero to 86400 seconds. During overlap the sender emits both signatures; receivers may configure secrets: [newSecret, oldSecret] during their own migration. Remove the old receiver secret when the overlap ends. A second positive overlap is refused until the first ends. Zero overlap immediately removes all previous sender keys. Rotating secrets does not change an event's identity. Automatic delivery retries last up to three days, honor bounded Retry-After, refuse redirects, and disable a matching endpoint on HTTP 410. Simulated local transport is labeled in attempts and never completes real webhook onboarding progress.

Connection lifecycle and durable approval required/resolved producers are implemented in this checkpoint. Financial operation and grant notification producers still require authoritative settlement integration. Approval resolution by itself does not establish financial success.

Embedded account proof preview

The package exports client.embedded.challenge({ jwt, signerAddress, chainId, accountId?, scopes }) and client.embedded.complete({ challenge, signature }). Keep the original parsed challenge in the authenticated customer BFF session; completion validates the returned exact account and scopes against it. A lost completion response requires a fresh challenge, never a retry to recover the bearer.

createEmbeddedClient({ accessToken }) exposes session(), account(), portability(), revoke(), agents.list(), agents.approve(registrationId), grants.create/list/get/revoke and the existing financial operation methods. The same public-grant resource is available under the developer-first mandates.create/list/get/revoke name. grants remains a compatibility alias because the wire resource and durable grantId are unchanged. An embedded mandate response must carry null approval URLs and is parsed without any consumer-app origin. client.embedded.list(), .get(bindingId) and .revoke(bindingId) inspect or revoke public app access. Credentials remain valid for the issuing key's lifetime. They still fail immediately when the session, binding or key is revoked, the app or identity configuration is disabled or changed, or the exact owner and account relationship no longer verifies on chain.

The browser entry exports createEmbeddedProofAdapter({ provider, sessionId, getSessionId, review }). Use a non-secret customer session revision. The review callback must display the purpose, exact account, app, environment, scopes and expiry before approval. The helper returns only { challengeId, signature }. Server app keys, identity JWTs and owner bearers stay in the BFF.

Same-key account claims preserve the exact account; identity login alone cannot replace a signer. Stored signer/current sole on-chain owner mismatches are unsupported in this first profile. See the embedded account portability guide for provider-dependent key export and recovery limits. Final packed artifacts are refreshed separately after the complete embedded integration stabilizes.

Embedded financial access

createEmbeddedClient({ accessToken, baseUrl? }) stays on the authenticated application server. The revocable embedded credential admits balances(), gasMethods(), activity({ cursor?, limit? }) and operations.create(intent, { idempotencyKey }), operations.get(id), operations.build(id, version), operations.submit(id, { attemptId, signed }) and operations.reject(id). Grant create and revoke are separate resources: grants.create(input, { idempotencyKey }) after agents.approve(registrationId). Each grant create or revoke retains a grant_enable / grant_revoke operation on this binding; the owner signs that operation through operations.build / operations.submit. No on-chain authority exists until that operation is confirmed and observed. App keys and JWTs cannot read customer financial data or authorize builds.

Persist the intent and idempotency key before creation, then retain the operation ID, attempt ID, original build hash and completed UserOp hash before relay. Submission has no automatic retry. After a lost response, read the original operation ID. Fresh proof to the same subject and exact account can read prior operations even after key/configuration/binding changes; the operation keeps its original attribution. Renewed access cannot revive an invalidated build or re-sign submitted work.

Browser-safe parseEmbeddedOperation, parseEmbeddedOperationBuild, parseEmbeddedBalances and parseEmbeddedActivity project embedded data without a hosted connection or approval URL. Pass the expected binding/account/app context at the BFF boundary. For old operation recovery, bindingVersion is the original operation version; do not require it to equal the new session version. The build parser checks the transport envelope only. Use @botanary/wallet canonical review to validate calldata, deployment, hashes, fee limits and exact swap terms before invoking a signer. Server code never signs. The existing wallet flow signs a raw USDC permit before the EIP-191 UserOp when that explicit signer capability is available.

Only a finalized nonsimulated financial-effect receipt proves settlement. A relay acknowledgment or simulated receipt does not. Simulation contributes no real customer onboarding or paid usage. React inline mode, authenticated embedded BFF examples and final archive refresh remain Task 4; existing package archives do not contain these new source exports yet.

What ships in the npm tarball

Allowlisted declarations (no declaration maps), one bundled and minified JavaScript file per documented entry (., ./browser, ./webhooks, ./types; no source maps, no comments), this README, the changelog, and LICENSE. No .ts/.tsx source, no tests, no fixtures, no build config. pnpm run audit:artifact verifies the exact tarball before publication and -- --registry <file.tgz> re-verifies the downloaded copy after.

Every runtime export is classified in docs/specs/2026-09-10-protected-sdk-integration-boundary.inventory.md: all integration except createEmbeddedProofAdapter, which is a signer-defense check with a written threat argument. No product, routing, policy, simulation, reconciliation, billing or provider logic is distributed - it lives behind the reviewed developer API. Minification is not concealment; the inventory is. bsk_* keys are server-only and never enter @botanary/sdk/browser.