@hypermid/sdk
v3.3.0
Published
Hypermid SDK — payments (checkout, deposit, withdrawal), the embeddable payments widget with optional connected-wallet bridge, and the read client for chains/tokens/quote/status/balances.
Readme
@hypermid/sdk
Hypermid SDK — payments (checkout, deposit, withdrawal), the embeddable payments widget with an optional connected-wallet bridge, and a read client for chains, tokens, quotes, status and balances.
npm i @hypermid/sdkESM only. viem is the sole runtime dependency.
Upgrading from 2.x?
3.0.0is a breaking release on a name that previously belonged to a separately maintained client. Read VERSIONING.md before upgrading — most of the 2.x surface was removed, and the defaultbaseUrlchanged.
Read client
import { Hypermid } from "@hypermid/sdk";
// `apiKey` is optional — the read endpoints serve anonymous traffic. When you
// do pass one it goes out as `X-API-Key`, so use a PUBLISHABLE key: a secret
// one in a browser bundle is extractable by every visitor.
const hypermid = new Hypermid({ apiKey: "pk_live_…" });
await hypermid.getChains();
await hypermid.getTokens();
await hypermid.getStatus("…");
await hypermid.getBalances({ /* … */ });Hypermid.getQuote() remains as a deprecated compatibility adapter. New code
must use quoteSwap() below; it returns a discriminated transaction-or-deposit
result instead of the old provider-shaped payload.
Payments
Creating a session needs a secret key, so these run server-side only — calling them from a browser throws rather than leaking the key.
import { createCheckout, createDeposit, createWithdrawal, getPayment } from "@hypermid/sdk";
const session = await createCheckout(
{
chain: 8453,
token: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
amount: "1000000", // base units
recipient: "0x…",
orderId: "order-1041", // also the idempotency key
},
{ secretKey: process.env.HYPERMID_SECRET_KEY! },
);
// session.url → hand this to the payerThe key is the SECOND argument, not a field on the request. On failure these
throw HypermidError, so branch on err.code rather than the message text:
import { HypermidError } from "@hypermid/sdk";
try {
await createCheckout(req, { secretKey });
} catch (err) {
if (err instanceof HypermidError && err.code === "RECIPIENT_NOT_ALLOWLISTED") {
// the recipient needs adding to your payout allowlist for this chain
}
}Payment links
A payment link is a stored checkout configuration behind a stable id, so the same link can bill many payers (or exactly one). Managing links needs the same secret key as payments, so these also run server-side only.
import {
createPaymentLink,
listPaymentLinks,
getPaymentLink,
updatePaymentLink,
revokePaymentLink,
} from "@hypermid/sdk/payment-links";
const link = await createPaymentLink(
{
chain: 8453,
token: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
recipient: "0x…",
amount: "25000000", // base units, required
singleUse: true, // stops after the first CONFIRMED payment
},
{ secretKey: process.env.HYPERMID_SECRET_KEY! },
);Four behaviours are worth knowing before you wire this up, because the types alone cannot say them:
revokePaymentLinkis a soft revoke. The link is marked revoked, not deleted; the id is never reused.updatePaymentLinkversions the link and cancels every unpaid session minted from the prior version — a payer mid-checkout on the old version loses it. The returnedcancelledSessionscounts how many were killed. The same count comes back from a revoke.singleUseis consumed by the first confirmed payment, not by opening the link.consumedBySessionIdis set once that happens.- De-allowlisting a payout address permanently revokes links using it, and re-adding the address does not revive them.
consumedBySessionId being set is not, by itself, proof that money moved —
read the session (getPayment) or your dashboard for authoritative status.
Publishable-key deposits (no backend)
Method 2: create a Deposit directly from the browser with a publishable key —
no merchant server. Unlike payments.*, this belongs in front-end code; the key
travels as x-api-key.
import {
createPublishableDeposit,
issueDepositChallenge,
} from "@hypermid/sdk/publishable-deposit";
// The easiest path: your server pre-registers destination candidates, the
// browser selects one by id. Nothing secret in the browser, nothing to sign.
const session = await createPublishableDeposit(
{ orderId: "order-1041", candidateId: "cand_abc" },
{ publishableKey: "pk_live_…" },
);
// session.id / session.embedUrl → render with the widget or hosted page,
// exactly as you would a server-created session.You supply exactly one destination-binding artifact; the destination is never a value you set. Supplying none or two is a compile error (and is re-checked before any request):
candidateId— no secret, no signature. The one most likely to work first.challenge— a payer SIWE signature (EVM-only). Issue withissueDepositChallenge({ chainId, address }), have the payer sign the returnedmessage, then pass{ nonce, signature }aschallenge. Fails closed unless the proven chain has exactly one configured deposit asset.grant— a single-use JWS your server mints first, so this one is not a no-backend path.
Two errors every first integration hits, made legible (branch on err.code):
| err.code | Cause |
| --- | --- |
| ORIGIN_NOT_CONFIGURED | the key has no allowed origins set — every production key starts this way (403) |
| PUBLISHABLE_DEPOSIT_NOT_ENABLED | publishable deposits aren't enabled for your partner (403) |
A returned session is not proof of payment — that's a separate, server-side question.
Recording what the payer broadcast
import { recordPayment } from "@hypermid/sdk/pay-session";
const txHash = await walletClient.sendTransaction(quote.transactionRequest);
await recordPayment(session.id, { txHash, payer: address });Unlike payments.*, this one takes no key and is meant to run in the
browser — the payer's wallet is what broadcast the transaction, so the browser
knows the hash first. Call it as soon as the network accepts the transaction;
waiting for confirmations only delays settlement.
It is also how you correct a hash. If the payer's first attempt was dropped, replaced or sped up, send the hash of the one that actually landed. Every hash is verified on-chain before the session settles, so a resubmit is safe and a wrong one is inert.
Widget bridge
import { createParentBridge, createIframeBridge } from "@hypermid/sdk";createParentBridge runs on the merchant page and brokers signing requests from
the embedded widget. It verifies every request before it reaches a wallet: the
to address of a router transaction is checked against the trusted-contract
allowlist, and direct transfers are reconstructed from server-authoritative
data rather than trusted as sent.
Subpath exports
| Import | Contents |
| --- | --- |
| @hypermid/sdk | everything below, re-exported |
| @hypermid/sdk/client | Hypermid read client |
| @hypermid/sdk/payments | createCheckout, createDeposit, createWithdrawal, getPayment |
| @hypermid/sdk/payment-links | createPaymentLink, listPaymentLinks, getPaymentLink, updatePaymentLink, revokePaymentLink |
| @hypermid/sdk/publishable-deposit | createPublishableDeposit, issueDepositChallenge (browser, pk_) |
| @hypermid/sdk/swap | quoteSwap |
| @hypermid/sdk/iframe | widget-side bridge |
| @hypermid/sdk/trustedContracts | pre-sign contract allowlist |
| @hypermid/sdk/errors | HypermidError and friends |
| @hypermid/sdk/types | message and payload types |
| @hypermid/sdk/api-types | generated API types |
