Quickstart

The fastest path to a working payment — no on-chain registry, no merchant registration step. Good for prototyping and for merchants who'd rather store settlement config themselves than register on-chain.

1. Install

Requires no peer dependencies beyond what's already bundled (viem, the pinned Particle Universal Account SDK).

View package on npm

2. Get a Particle project

Universal Accounts are how the SDK reads a buyer's cross-chain balance and constructs the transfer. Create a project at dashboard.particle.network and grab three values: Project ID, Client Key, and an App ID (the App ID lives one level under the project — inside a specific Web/iOS/ Android application you create there, not on the project overview page itself).

3. Build a signer

The SDK never touches a private key directly — it needs an object implementing MorvaSigner, which can sign messages and produce EIP-7702 authorizations. For scripts and tests, use the bundled LocalSigner:

import { LocalSigner } from "@morva/sdk";

const signer = new LocalSigner(process.env.BUYER_PRIVATE_KEY! as `0x${string}`);

For a real frontend, don't use LocalSigner

A raw private key has no place in browser JS. Real integrations implement MorvaSigner against an embedded wallet provider instead — see building a signer in the integration guide for a complete Magic-backed example.

4. Build an intent and pay

A PaymentIntentis a plain description of what's being paid — amount, settlement token, settlement recipient. createDirectIntent builds one synchronously, no network call:

import { createMorva } from "@morva/sdk";

const morva = createMorva({
  particle: {
    projectId: process.env.PARTICLE_PROJECT_ID!,
    projectClientKey: process.env.PARTICLE_CLIENT_KEY!,
    projectAppUuid: process.env.PARTICLE_APP_UUID!,
  },
});

const session = await morva.connect(signer);

const intent = morva.createDirectIntent({
  amount: "4.99",
  orderId: "order-123",
  settlementToken: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // native USDC, Arbitrum One
  settlementRecipient: "0xYourMerchantAddress",
});

const result = await session.pay(intent, {
  onStatus: (status) => console.log(status),
});

console.log(result.transactionId, result.explorerUrl);

session.pay() resolves once funds are confirmed at settlementRecipienton the intent's settlement chain (Arbitrum One here, since settlementChainIdwasn't set above), or throws one of the typed errors documented on the errors page. onStatus reports building → authorizing → signing → submitted → settled (or failed) as the payment progresses — see the status lifecycle for what each stage means.

Next

That's a full payment. For how the pieces underneath it actually work — and why they were chosen — read the infrastructure page. To wire this into a real product with a real merchant onboarding flow and a real buyer wallet, read the integration guide.