Skip to content

moonpay/moonpay-platform-sdk

Repository files navigation

MoonPay Platform SDK

Official TypeScript SDKs for the MoonPay Developer Platform.

Integrate crypto on/off-ramp capabilities into your web or mobile application with type-safe APIs, managed iframe/WebView frames, and encrypted credential exchange.

Packages

Package Description
@moonpay/platform-protocol Shared protocol types — zero runtime, used by both MoonPay frames and SDKs
@moonpay/platform-sdk-core Platform-agnostic logic — API client, crypto, frame orchestration
@moonpay/platform-sdk-web Browser SDK — iframe + postMessage
@moonpay/platform-sdk-react-native React Native SDK — WebView-based
@moonpay/platform-sdk-node Server-side SDK — session creation

Quick start

Server-side: create a session

import { createServerClient } from '@moonpay/platform-sdk-node';

const server = createServerClient({
  apiKey: process.env.MOONPAY_SECRET_KEY,
});

const result = await server.createSession({
  externalCustomerId: 'user-123',
  deviceIp: req.ip,
});

if (!result.ok) {
  // handle error
}

const { sessionToken } = result.value;
// Pass sessionToken to your frontend

Web: full integration

import { createClient } from '@moonpay/platform-sdk-web';

// 1. Create client with session token from your server
const client = createClient({ sessionToken });

// 2. Check if the customer has an active connection
const conn = await client.getConnection();

if (conn.ok && conn.value.status === 'connectionRequired') {
  // 3. Open the connect flow
  const connect = await client.connect({
    container: document.getElementById('moonpay-connect'),
    onEvent: (event) => console.log(event),
  });
}

// 4. Get a quote
const quote = await client.getQuote({
  source: { asset: { code: 'USD' }, amount: '100' },
  destination: { asset: { code: 'ETH' } },
  wallet: { address: '0x...' },
  paymentMethod: { type: 'apple_pay' },
  feeBehavior: 'inclusive',
});

if (!quote.ok) {
  // handle error
}

// 5. Set up a payment frame
const widget = await client.setupWidget({
  quote: quote.value.data.signature,
  container: document.getElementById('moonpay-widget'),
  onEvent: (event) => {
    if (event.kind === 'complete') {
      console.log('Transaction complete:', event.payload.transaction);
    }
  },
});

React Native

import { MoonPayProvider, useMoonPay } from '@moonpay/platform-sdk-react-native';

// Wrap your app
<MoonPayProvider sessionToken={sessionToken}>
  <App />
</MoonPayProvider>

// Use in components
function BuyScreen() {
  const { client } = useMoonPay();

  const quote = await client.getQuote({
    source: { asset: { code: 'USD' }, amount: '100' },
    destination: { asset: { code: 'ETH' } },
    wallet: { address: '0x...' },
    paymentMethod: { type: 'apple_pay' },
  });
}

Error handling

All SDK methods return a Result<T, E> instead of throwing exceptions:

const result = await client.getQuote(params);

if (result.ok) {
  console.log(result.value);
} else {
  console.error(result.error);
}

Documentation

Full guides and API reference: moonpay.mintlify.app

Development

Prerequisites

  • Node.js >= 18
  • Bun

Setup

bun install
bun run build

Commands

bun run build       # Build all packages
bun run typecheck   # Typecheck all packages
bun run clean       # Remove dist/ from all packages
bun run openapi     # Regenerate API types from OpenAPI spec

Local consumer linking

To test SDK changes in a Bun consumer app without publishing:

bun run dev
# Link all dependened on packages
bun run link:local -- ~/dev/sdk-consumer-app
# Link a specific package
bun run link:local -- ~/dev/sdk-consumer-app @moonpay/platform-sdk-node
bun run unlink:local -- ~/dev/sdk-consumer-app

Currently Bun-only. link:local uses bun link --no-save, so it does not rewrite the consumer's package.json. unlink:local removes the local links and reinstalls from the consumer's existing manifest.

Architecture

protocol  ←  core  ←  web
                   ←  react-native
          ←  node

Build order matters: protocol must build before core, and core before web/react-native/node. Running bun run build at the root handles this automatically.

See AGENTS.md for detailed development guides covering:

  • Adding a new REST API endpoint
  • Adding a new frame type
  • Adding a new connection status
  • Updating API types from OpenAPI
  • Adding parent-to-child commands

Releasing

This repo publishes all 5 @moonpay/* packages to npm via changesets and npm Trusted Publishers (OIDC — no static publish token).

Two release channels, served by one workflow:

Channel Branch Tag npm install
stable main latest @moonpay/platform-sdk-web
pre-release next next @moonpay/platform-sdk-web@next

Both channels run the same .github/workflows/publish.yml (triggered on push to either branch). A single workflow is intentional: npm Trusted Publishers matches the OIDC workflow_ref claim, which points at the calling workflow filename — not a reusable callee. One workflow → one TP binding per package → covers both channels.

The channel selection happens inside changesets: when .changeset/pre.json is present (on next), changeset publish ships under the pre-tag from that file (next) automatically. On main, no pre.json, so it ships to @latest.

Stable releases (mainlatest)

  1. Land your code change as a PR. The Changeset Required check enforces that every PR includes a changeset.
  2. Add a changeset describing the user-facing impact:
    bunx changeset
    All 5 packages are linked in .changeset/config.json, so they version together.
  3. When your PR merges to main, the Release workflow opens (or updates) a Version PR titled Version Packages. This PR contains the version bumps and changelog updates.
  4. Merging the Version PR triggers the Release workflow again, this time publishing all bumped packages to npm.

Packages currently publish with access: "restricted" (org-only). They will become public via DEVXP-1098.

Pre-release channel (nextnext)

The next branch is long-lived and runs changesets pre mode. Versions look like 0.3.0-next.0, 0.3.0-next.1, etc., and resolve via the next dist-tag. Stable @latest is unaffected.

PR work targets next exactly like it would target main — add a changeset, merge, the Publish workflow opens a "Version Packages (next)" PR, merging it publishes pre-releases.

Initialising the channel (one-off, when next doesn't exist yet):

git checkout -b next main
bunx changeset pre enter next   # writes .changeset/pre.json
git push -u origin next

The .changeset/pre.json file is what makes changeset version produce pre-release numbers. It must remain on the next branch and must not exist on main.

Resyncing next after a stable release. When main ships a stable release, the next branch's base is behind. Until DEVXP-1095 automates this, the manual recovery is:

git checkout next
bunx changeset pre exit        # remove .changeset/pre.json
git rebase main                # bring in stable
bunx changeset pre enter next  # re-enter pre mode on top of main
git push -f origin next

Yes, this force-pushes next. The branch is a publishing channel, not a development branch — that's expected.

Promoting a next release to stable. Cherry-pick or merge the relevant commits onto main (with a fresh changeset describing the stable bump). The next release of main ships them under @latest.

Manual run: the workflow supports workflow_dispatch for emergencies / bootstrap. Trigger from the Actions tab and pick either main or next.

License

MIT — see individual package package.json files.

About

Official TypeScript SDKs for the MoonPay Developer Platform (Web, React Native, Node)

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors