Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Purpose

This repository publishes `@minmo/sdk`, the server-side, API-key-only facade for
This repository publishes `@minmoto/sdk`, the server-side, API-key-only facade for
the Minmo Partner API. The implementation is built in private `minmoto/mini`;
this repository owns the stable public constructor, export boundary,
documentation, packaging, and releases.
Expand Down Expand Up @@ -52,7 +52,7 @@ required.
## Updating the upstream artifact

Use a detached, clean Mini worktree at the intended commit. Build `@minmo/core`
before `@minmo/sdk`, then run:
before the private SDK workspace, then run:

```sh
npm run vendor:upstream -- --worktree /path/to/mini-worktree
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Changelog

Notable changes to `@minmo/sdk` will be recorded here by Release Please.
Notable changes to `@minmoto/sdk` will be recorded here by Release Please.
210 changes: 162 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,92 +1,206 @@
# @minmo/sdk
# @minmoto/sdk

The server-side TypeScript client for Minmo Partners.
Build server-side Minmo Partner integrations in TypeScript.

The public package is a small API-key-only facade over the SDK implementation
maintained in the private Minmo monorepo. It binds one Partner when constructed
and defaults to the Minmo production API.
`@minmoto/sdk` is the typed Node.js client for the
[Minmo](https://www.minmo.to) Partner API. It gives you one Partner-bound
interface for bringing Bitcoin and local-currency workflows into your own
services: quote rates, coordinate OTC swaps and agents, operate wallets and
escrow, create Minmo Pay stores and invoices, and consume live domain events.

Create a client with your Partner ID and API key, then start calling
Partner-scoped resources. The production API is configured by default, so
there is no separate `forPartner()` step or browser-oriented authentication
flow.

## Why use the SDK?

- **Partner-bound by default.** Every client starts in the Partner context you
provide, making the intended request scope explicit.
- **Typed from request to response.** TypeScript declarations cover inputs,
resources, events, errors, currencies, payment channels, and domain states.
- **Built for backend services.** Authentication is intentionally limited to
Partner API keys and the package targets Node.js server runtimes.
- **Small deployment surface.** The package ships as ESM with no runtime
dependencies.
- **Useful defaults.** Minmo's production API URL is configured automatically,
with an optional `baseUrl` for another Minmo deployment.

## Requirements

- Node.js 22 or newer
- A Minmo Partner ID
- A Partner API key with the capabilities required by your integration

Partner API keys are server secrets. Keep them in a secrets manager or private
server environment; never expose them to browser or mobile clients.

## Install

```sh
npm install @minmo/sdk
npm install @minmoto/sdk
```

## Quick start

```ts
import { MinmoClient } from "@minmo/sdk";
import { MinmoClient } from "@minmoto/sdk";

const minmo = new MinmoClient({
partnerId: process.env.MINMO_PARTNER_ID!,
apiKey: process.env.MINMO_API_KEY!,
});

const account = await minmo.account.get();
const stores = await minmo.integrations.pay.listStores();
const partner = await minmo.account.get();

console.log(`Connected to ${partner.displayName}`);
```

`baseUrl` is optional and defaults to `https://api.minmo.to/api/v1`:
`MinmoClient` requires a non-empty Partner ID and API key, then sends the key in
the `X-API-Key` header. Calls such as `account.get()`, `settings.get()`, and
`integrations.pay.listStores()` automatically use the configured Partner.

## What you can build

| Area | Client | Use it to |
| --- | --- | --- |
| Partner operations | `account`, `settings`, `analytics`, `members`, `invitations`, `apiKeys`, `referrals` | Manage the Partner account, team access, reporting, API keys, and referrals |
| Minmo Pay | `integrations.pay` | Create stores and invoices, connect settlement wallets, read authoritative invoice state, and subscribe to Pay events |
| OTC | `otc.rates`, `otc.swap`, `otc.agents` | Quote Bitcoin and local-currency rates, coordinate swaps, and operate an agent network |
| Wallets | `wallet` | Create and manage wallets, receive or send funds, transfer balances, and inspect wallet activity |
| Escrow | `escrow` | Create and reconcile escrows, verify funding, release or refund funds, and manage disputes |
| Events | `events` and domain subscriptions | React to Partner, Pay, OTC, wallet, and escrow changes with typed events |

For example, request a typed BTC/KES quote and list the Partner's Minmo Pay
stores:

```ts
const staging = new MinmoClient({
import { Currency, MinmoClient } from "@minmoto/sdk";

const minmo = new MinmoClient({
partnerId: process.env.MINMO_PARTNER_ID!,
apiKey: process.env.MINMO_API_KEY!,
baseUrl: "https://api.staging.minmo.to/api/v1",
});

const [quote, stores] = await Promise.all([
minmo.otc.rates.quote(Currency.BTC, Currency.KES),
minmo.integrations.pay.listStores(),
]);
```

The constructor produces the Partner-bound SDK interface directly. Calls such
as `account.get()`, `settings.get()`, and `integrations.pay.listStores()` use the
configured Partner without a separate `forPartner()` setup call.
### Subscribe to live changes

## Authentication and security
Domain clients expose typed subscriptions alongside their command and query
methods. With a cursor store, a subscription can resume after reconnecting and
reports when an authoritative resync is required.

```ts
const subscription = minmo.integrations.pay.events({
onEvent(event) {
console.log(event.type, event.id);
},
onError(error) {
console.error("Minmo Pay event stream failed", error.message);
},
});

await subscription.ready;

`@minmo/sdk` supports only Partner API-key authentication. It sends the key in
the `X-API-Key` request header and does not export bearer-token or token-provider
authentication helpers.
// Close the stream during application shutdown.
await subscription.close();
```

Treat an event as a signal that something changed. When current state matters,
read the corresponding resource again through the SDK.

## Configuration

```ts
import {
MINMO_PRODUCTION_BASE_URL,
MinmoClient,
type MinmoClientOptions,
} from "@minmoto/sdk";

const options: MinmoClientOptions = {
partnerId: process.env.MINMO_PARTNER_ID!,
apiKey: process.env.MINMO_API_KEY!,
baseUrl: process.env.MINMO_BASE_URL ?? MINMO_PRODUCTION_BASE_URL,
};

const minmo = new MinmoClient(options);
```

Partner API keys are server secrets:
| Option | Required | Description |
| --- | --- | --- |
| `partnerId` | Yes | Partner scope used by Partner-bound resources |
| `apiKey` | Yes | Secret Partner API key sent as `X-API-Key` |
| `baseUrl` | No | Minmo API base URL; defaults to `https://api.minmo.to/api/v1` |

- Do not construct this client in browser code.
- Do not expose the key through `NEXT_PUBLIC_`, `VITE_`, or equivalent public
environment variables.
- Do not log client options, request headers, or API keys.
- Store production keys in a secrets manager and grant only the capabilities
required by the integration.
## Error handling

The Partner ID selects request scope. Authorization remains enforced by the
Minmo API; selecting a Partner never grants access to it.
The SDK exposes typed errors so your service can distinguish authentication,
authorization, rate-limit, transport, and other API failures.

## Upstream implementation
```ts
import { MinmoSdkError } from "@minmoto/sdk";

try {
await minmo.integrations.pay.listStores();
} catch (error) {
if (error instanceof MinmoSdkError) {
console.error({
code: error.code,
status: error.status,
requestId: error.requestId,
retryable: error.retryable,
});
}

throw error;
}
```

The SDK implementation and shared contracts are built in the private
`minmoto/mini` repository. This repository vendors an immutable build artifact
under `vendor/upstream` and publishes only the constrained facade in `dist`.
Use `retryable` as an input to your retry policy, not as permission to retry
without limits. Apply bounded backoff and preserve idempotency keys for
money-moving commands.

## Authentication and security

`vendor/upstream/manifest.json` records the exact Mini commit and a SHA-256
checksum covering every vendored file. CI verifies the checksum, ensures the
public output has no private package imports, installs the packed tarball into
an empty consumer, and confirms that private authentication providers are not
exported.
`@minmoto/sdk` supports only Partner API-key authentication. It does not export
bearer-token or token-provider authentication helpers.

To update from a clean, built Mini worktree:
- Construct the client only in trusted server code.
- Never place the API key in `NEXT_PUBLIC_`, `VITE_`, or an equivalent public
environment variable.
- Do not log client options, request headers, API keys, or sensitive response
bodies.
- Grant each key only the capabilities its integration needs, and rotate or
revoke keys according to your operational policy.
- Keep idempotency keys stable when retrying commands that can move money or
create payable resources.

The Partner ID selects request scope; it does not grant access. The Minmo API
continues to enforce the API key's Partner access and capability policy for
every request.

## Contributing

The public SDK is built from a pinned Minmo SDK artifact. CI verifies its
checksum, public export boundary, generated declarations, and packed consumer
experience.

```sh
npm run vendor:upstream -- --worktree /path/to/clean/mini-worktree
npm install
npm run check
npm run test:package
npm pack --dry-run
```

Never vendor from a dirty worktree or publish `dist` from a different revision
than the one recorded in the upstream manifest.

## Contributing and releasing
See [`AGENTS.md`](./AGENTS.md) for contributor guidance and
[`RELEASING.md`](./RELEASING.md) for the release process. Bugs and feature
requests are welcome in [GitHub Issues](https://github.com/minmoto/sdk/issues).

Contributor instructions are in [`AGENTS.md`](./AGENTS.md). The repository uses
Conventional Commits, Release Please, semantic versioning, and npm trusted
publishing with provenance. See [`RELEASING.md`](./RELEASING.md) for the release
and one-time registry setup.
## License

The package is MIT-licensed; see [`LICENSE`](./LICENSE).
MIT © Minmo. See [`LICENSE`](./LICENSE).
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@minmo/sdk",
"name": "@minmoto/sdk",
"version": "0.1.0",
"description": "Server-side TypeScript client for the Minmo Partner API.",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"packages": {
".": {
"release-type": "node",
"package-name": "@minmo/sdk",
"package-name": "@minmoto/sdk",
"include-v-in-tag": true,
"changelog-sections": [
{ "type": "feat", "section": "Features" },
Expand Down
6 changes: 3 additions & 3 deletions scripts/test-package.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ execFileSync("npm", ["install", "--ignore-scripts", join(fixture, packed.filenam
writeFileSync(
join(fixture, "consumer.mjs"),
[
'import * as sdk from "@minmo/sdk";',
'import * as sdk from "@minmoto/sdk";',
'if (typeof sdk.MinmoClient !== "function") throw new Error("MinmoClient missing");',
'if ("MinmoAuth" in sdk) throw new Error("MinmoAuth must not be public");',
'new sdk.MinmoClient({ partnerId: "partner-1", apiKey: "secret" });',
Expand All @@ -36,7 +36,7 @@ execFileSync("node", [join(fixture, "consumer.mjs")], { stdio: "inherit" });
writeFileSync(
join(fixture, "consumer.ts"),
[
'import { Currency, MinmoClient, PaymentChannel, type MinmoClientOptions } from "@minmo/sdk";',
'import { Currency, MinmoClient, PaymentChannel, type MinmoClientOptions } from "@minmoto/sdk";',
"const options: MinmoClientOptions = { partnerId: \"partner-1\", apiKey: \"secret\" };",
"const client: MinmoClient = new MinmoClient(options);",
"void client.account.get;",
Expand Down Expand Up @@ -66,7 +66,7 @@ execFileSync(
);

const installedPackage = JSON.parse(
readFileSync(join(fixture, "node_modules", "@minmo", "sdk", "package.json"), "utf8"),
readFileSync(join(fixture, "node_modules", "@minmoto", "sdk", "package.json"), "utf8"),
);
if (installedPackage.dependencies && Object.keys(installedPackage.dependencies).length) {
throw new Error("The public SDK must not have runtime dependencies");
Expand Down
4 changes: 2 additions & 2 deletions scripts/vendor-upstream.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ const sdkSource = join(worktree, "packages", "sdk", "src");
const coreSource = join(worktree, "packages", "core", "src", "index.ts");

assertCleanWorktree(worktree);
assertDirectory(sdkDist, "Build @minmo/sdk in the clean worktree first");
assertDirectory(coreDist, "Build @minmo/core in the clean worktree first");
assertDirectory(sdkDist, "Build the private SDK workspace in the clean worktree first");
assertDirectory(coreDist, "Build the private core workspace in the clean worktree first");

rmSync(vendorRoot, { recursive: true, force: true });
mkdirSync(vendorRoot, { recursive: true });
Expand Down