/ API Reference Back to home
Developer Reference

MooChedda API

Standard REST endpoints with JSON request/response. Integrate digital asset payments into any stack — Node, Python, PHP, Ruby, Go — without proprietary SDKs or libraries.

Instant settlement
0% transaction fees
No API keys required
secp256k1 ECDSA signing

No SDK, just HTTP

Every operation is a plain JSON REST call. Any HTTP client works — fetch, axios, curl, requests, Guzzle.

Client-side signing

Transactions are signed locally before sending. Your private key never leaves your device or server.

Self-custody wallets

secp256k1 keypairs. You own the private key — MooChedda cannot freeze or access your funds.

Overview

Base URL

All API requests go to the following base URL. All request and response bodies are application/json.

Base URL https://moochedda.com
// Example: full request URL
const BASE = 'https://moochedda.com';
const res = await fetch(`${BASE}/balance/${address}`);

Authentication

MooChedda uses cryptographic signatures rather than API keys. For write operations (transfers, marketplace actions), you sign the payload client-side with your secp256k1 private key and send the signature with the request. The server verifies the signature against your public wallet address — your private key never leaves your device.

Self-custody by design Read-only endpoints (balances, prices, tokens) require no authentication at all. Write endpoints require a valid ECDSA signature tied to the sender's wallet address.
1
Build the payload
Construct the transaction fields: fromAddress, toAddress, amount, tokenSymbol, timestamp.
2
Hash the payload locally
SHA-256 over the concatenated string of the fields. Use the Web Crypto API or any standard SHA-256 library.
3
Sign with your private key
ECDSA sign the hash using the secp256k1 curve. DER-encode the result as a hex string.
4
Send only the signature
Include timestamp and signature in the JSON body. The private key stays local — always.

Errors

All errors return a JSON body with an error string and an HTTP status code.

StatusMeaning
400Bad request — missing or invalid fields
401Unauthorized — invalid ECDSA signature
404Resource not found
500Server error
// Error response shape
{
  "error": "Invalid signature"
}

Checkout (scan to pay)

Accept payments on your website — MooCheddaPay

Your checkout shows a QR code. The customer scans it with the MooChedda wallet, sees your store, the items and the total, and pays. Your page updates the moment the payment lands.

1
Get an API key
Merchant portal → store settings → API Keys. It can only create invoices paying your store — it can't move funds. Keep it on your server.
2
Your server creates the invoice
Prices come from your server, never the browser. You get back an invoiceId.
3
Your page shows the QR
MooCheddaPay.mount() renders the items, total and QR, and watches for payment.
4
Confirm on your server, then fulfil
Via the signed invoice.paid webhook or a status check — not the browser callback alone.
Verified merchantInvoices created with your API key show your registered store name with a ✓ Verified merchant badge in the wallet. Invoices created without authentication are shown to payers as unverified.
Working exampleMerchant-Examples/qr-checkout/ — a demo shop (page + server) you can run with node Merchant-Examples/qr-checkout/server.js.

Server: create the invoice

// Node 18+ — or call POST /invoice/create from any language (below)
import './moochedda-pay.js';   // defines globalThis.MooCheddaPay

const { invoiceId } = await MooCheddaPay.createInvoice({
  apiUrl:   'https://moochedda.com',
  apiKey:   process.env.MOOCHEDDA_API_KEY,        // mk_live_…
  items:    [{ name: 'Moo T-shirt', price: 25, quantity: 1 }],
  acceptedTokens: ['CHEDDA'],              // first token prices the invoice
  reference: order.id,                          // your order id — echoed in status + webhook
  expiresInMinutes: 30
});
// send only invoiceId to the browser
# Any language
curl -X POST https://moochedda.com/invoice/create \
  -H "Authorization: Bearer $MOOCHEDDA_API_KEY" -H "Content-Type: application/json" \
  -d '{"items":[{"name":"Moo T-shirt","price":25,"quantity":1}],"reference":"order_123"}'
# → { "invoiceId": "inv_…", "payUrl": "https://moochedda.com/pay/inv_…", "invoice": { … } }

Page: show the QR

<div id="moochedda-pay"></div>
<script src="https://moochedda.com/moochedda-pay.js"></script>
<script>
  MooCheddaPay.mount('#moochedda-pay', {
    invoiceId,                                   // from your server
    onPaid:    () => confirmWithYourServer(),     // then show "thank you"
    onExpired: () => restartCheckout()
  });
</script>

Options: apiUrl (defaults to the node that served the script), pollIntervalMs, qrSize, showItems, onError, renderQr (bring your own QR renderer). The QR holds the pay link, so a phone camera also works — it opens the MooChedda pay page.

Confirm the payment

// Webhook (set the URL + secret in the merchant portal). Use the RAW body.
app.post('/webhooks/moochedda', express.text({ type: '*/*' }), async (req, res) => {
  const event = await MooCheddaPay.verifyWebhook({
    secret: process.env.MOOCHEDDA_WEBHOOK_SECRET,
    body: req.body,
    signatureHeader: req.get('X-MooChedda-Signature')   // t=…,v1=… (HMAC-SHA256)
  });
  if (event.event === 'invoice.paid') markOrderPaid(event.data.reference, event.data.invoiceId);
  res.sendStatus(200);
});

// Or poll from your server:
const s = await MooCheddaPay.getStatus({ apiUrl, invoiceId });   // s.status === 'paid'

JavaScript SDK

Quick Start — MooCheddaSDK

Drop-in JavaScript client for the MooChedda exchange. One file, zero build step, works in browser and Node.js.

Download moochedda-sdk.js — or load from the exchange server directly.
// ── Browser ──────────────────────────────────────────
<script src="https://exchange.moochedda.com/moochedda-sdk.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/elliptic/6.5.4/elliptic.min.js"></script>

const client = new MooCheddaSDK({
  apiUrl:        'https://exchange.moochedda.com',
  walletAddress: '04abc...',   // your 130-char public key
  privateKey:    '7e2c...'     // 64-char hex — never expose in public code
});
// ── Node.js ───────────────────────────────────────────
// npm install elliptic node-fetch
import MooCheddaSDK from './moochedda-sdk.js';

const client = new MooCheddaSDK({
  apiUrl:        'https://exchange.moochedda.com',
  walletAddress: process.env.WALLET_ADDRESS,
  privateKey:    process.env.WALLET_PRIVATE_KEY
});
// ── Market data (no auth required) ───────────────────
const markets = await client.getMarkets();
const price   = await client.getPrice('CHEDDA', 'USDC');
const book    = await client.getOrderBook('CHEDDA', 'USDC');
const trades  = await client.getTrades('CHEDDA', 'USDC', 20);
const candles = await client.getCandles('CHEDDA', 'USDC', '1h', 100);
// ── Place orders (auth required) ─────────────────────
// Limit buy: 500 CHEDDA at 1.20 USDC each, Good Till Cancelled
const buyOrder = await client.buy({
  base:   'CHEDDA',
  quote:  'USDC',
  amount: 500,
  price:  1.20,
  tif:    'gtc'
});

// Market sell: sell 100 CHEDDA at best price immediately
const sellOrder = await client.sell({
  base:   'CHEDDA',
  quote:  'USDC',
  amount: 100
  // no price = market order
});

// IOC sell: fill what's possible right now, cancel the rest
const iocOrder = await client.sell({
  base: 'CHEDDA', quote: 'USDC', amount: 200, price: 1.15, tif: 'ioc'
});
// ── Manage orders ────────────────────────────────────
const openOrders = await client.getOpenOrders();
await client.cancelOrder(openOrders[0].orderId);
await client.cancelAllOrders('CHEDDA', 'USDC'); // cancel all on this pair
// ── Account ──────────────────────────────────────────
const balances = await client.getBalance();
// { CHEDDA: 1000, USDC: 500, ... }

await client.transfer('04xyz...', 50, 'USDC');
// ── Real-time WebSocket ──────────────────────────────
const unsubscribe = client.subscribe(snapshot => {
  console.log('Markets:', snapshot.markets);
  console.log('Prices:',  snapshot.prices);
});

// Later:
unsubscribe();
client.disconnect();
JavaScript SDK

Method Reference

MethodAuthDescription
getMarkets()—All trading pairs with 24h stats
getTicker(base, quote)—24h ticker for a pair
getPrice(base, quote)—Current last price
getOrderBook(base, quote, levels?)—Bids and asks depth
getTrades(base, quote, limit?)—Recent executed trades
getCandles(base, quote, tf?, limit?)—OHLCV candlestick data
getPrices()—All token prices from oracle
getToken(symbol)—Token metadata and supply
getBalance(address?)—Token balances for a wallet
getOpenOrders(address?)—Active orders for a wallet
getOrderHistory(address?)—Full order history
buy({ base, quote, amount, price?, tif? })✓Place a buy order
sell({ base, quote, amount, price?, tif? })✓Place a sell order
cancelOrder(orderId)✓Cancel a specific order
cancelAllOrders(base?, quote?)✓Cancel all open orders
transfer(toAddress, amount, symbol)✓Send tokens to another wallet
subscribe(callback)—WebSocket real-time snapshots
disconnect()—Close WebSocket connection
Getting Started

Wallets & Balances

Every user or merchant needs a wallet. Wallets are secp256k1 keypairs — the address is the uncompressed 130-char hex public key. Keep the private key and mnemonic offline; they cannot be recovered from the server.

Create Wallet

POST /wallet/create

Generate a new secp256k1 keypair. Returns the wallet address (public key), private key, and a BIP39 12-word mnemonic. Store both the private key and mnemonic securely — neither can be recovered from the server.

Never expose the private key. Store it encrypted at rest. Use it only client-side to sign transactions — never send it to any server.
FieldTypeRequiredDescription
labelstringOptionalHuman-readable label stored with the wallet
// Request
const res = await fetch('https://moochedda.com/wallet/create', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ label: 'my-wallet' })
});
const { address, privateKey, mnemonic } = await res.json();

// Response
{
  "address":    "04a3f9c2...",  // 130-char uncompressed secp256k1 public key
  "privateKey": "7e2c1b3d...",  // 64-char hex — never transmit this
  "mnemonic":   "word1 word2 ... word12"
}

Import Wallet from Mnemonic

POST /wallet/recover

Restore a wallet from a BIP39 mnemonic phrase. Returns the wallet address so you can verify before proceeding. The server does not store the mnemonic.

FieldTypeRequiredDescription
mnemonicstringRequiredSpace-separated 12-word BIP39 mnemonic
const res = await fetch('https://moochedda.com/wallet/recover', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ mnemonic: 'word1 word2 ... word12' })
});
const { address, privateKey } = await res.json();

// Response
{
  "address":    "04a3f9c2...",
  "privateKey": "7e2c1b3d..."
}

Get Balance

GET /balance/:address

Returns all token balances for a wallet address. Redacted by default — an anonymous caller gets shape only (hasBalance, tokenCount), no amounts. To see the real numbers, prove you hold the address's private key and pass the resulting token — see Account Privacy below.

// Anonymous — redacted
const res = await fetch(`https://moochedda.com/balance/${address}`);
const data = await res.json();
// { address, redacted: true, hasBalance: true, tokenCount: 2, transactionCount: 7 }

// Authenticated as the address owner — full detail
const viewToken = await getViewToken(address, privateKeyHex);
const res2 = await fetch(`https://moochedda.com/balance/${address}`, {
  headers: { 'x-view-token': viewToken }
});
const { balances } = await res2.json();
// { "USDC": 500.00, "CHEDDA": 1000 }

Account Privacy

How It Works

/balance, /transactions, and the raw /blockchain feed no longer hand out exact amounts and counterparties to an anonymous caller. A wallet unlocks its own data with a signed challenge/response — the same ECDSA scheme used everywhere else in this API, just applied to read access instead of a write. No new key material, no separate account: the wallet's existing private key proves ownership.

What this is (and isn't) This is selective disclosure via signature-gated access control — amounts are still stored in the ledger, this just controls what the API returns to an unauthenticated caller. It is not zero-knowledge cryptography.

POST /privacy/view/challenge → issues a one-time nonce for an address

POST /privacy/view/verify → exchanges a signed nonce for a view token

// 1. Request a one-time challenge for the address
const ch = await (await fetch('https://moochedda.com/privacy/view/challenge', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ address })
})).json();
// { address, nonce, expiresAt, message: "view:
:" } // 2. Sign ch.message with the address's private key (same signPayload() from Signing Transactions) const signature = await signPayload(privateKeyHex, ch.message); // 3. Exchange the signature for a short-lived view token (~30 min) const { token } = await (await fetch('https://moochedda.com/privacy/view/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address, nonce: ch.nonce, signature }) })).json(); // 4. Attach it to /balance, /transactions, and /blockchain requests for that address const res = await fetch(`https://moochedda.com/balance/${address}`, { headers: { 'x-view-token': token } }); // Convenience wrapper — cache the token and only re-request near expiry let _viewToken = null; async function getViewToken(address, privateKeyHex) { if (_viewToken && _viewToken.address === address && Date.now() < _viewToken.expiresAt - 30_000) { return _viewToken.token; } const ch = await (await fetch('https://moochedda.com/privacy/view/challenge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address }) })).json(); const signature = await signPayload(privateKeyHex, ch.message); const v = await (await fetch('https://moochedda.com/privacy/view/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address, nonce: ch.nonce, signature }) })).json(); _viewToken = { address, token: v.token, expiresAt: v.expiresAt }; return v.token; }
Third-party access (regulators, auditors, support staff) A separate, logged, dual-approval workflow exists for viewing an address you don't hold the key for — POST /operations/disclosure/request and /:id/approve. It requires operations-level access (IP allowlist or bearer token) and isn't part of the public SDK surface — ask your platform admin.

Payments

Signing Transactions

All write payment operations require a client-side ECDSA signature (secp256k1, DER-encoded hex). Any library that produces standard secp256k1 signatures works — the two most common choices are shown below.

Why two examples MooChedda's own server moved from elliptic to @noble/curves in 2026 — elliptic has open, unpatched security advisories (a malformed input can leak bits of the private key when signing). The bundled moochedda-sdk.js still uses elliptic internally for backward compatibility, and it remains perfectly safe to verify against — the server accepts standard secp256k1/DER signatures from either library, since the two are cross-compatible by construction. If you're starting a fresh integration, @noble/curves is the better default: no known advisories, one small audited dependency, and it's what MooChedda's own server now runs.
// ── Option A: elliptic (what moochedda-sdk.js uses internally) ──
// npm install elliptic — or load from CDN for browser use
//   privateKeyHex: 64-char hex private key
//   fields: the exact fields and values sent in the request body
async function signPayload(privateKeyHex, ...parts) {
  // 1. Hash the concatenated field values with SHA-256
  const raw = parts.map(p => String(p)).join('');
  const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(raw));
  const hashHex = Array.from(new Uint8Array(buf))
    .map(b => b.toString(16).padStart(2, '0')).join('');

  // 2. ECDSA sign with secp256k1
  const ec = new elliptic.ec('secp256k1');
  const key = ec.keyFromPrivate(privateKeyHex, 'hex');
  const sig = key.sign(hashHex);
  return sig.toDER('hex');  // DER-encoded hex string
}

// Usage for a token transfer
const timestamp = Date.now();
const signature = await signPayload(
  myPrivateKey,
  fromAddress, toAddress, amount, tokenSymbol, timestamp
);
// ── Option B: @noble/curves (no known advisories, recommended for new integrations) ──
// npm install @noble/curves
import { secp256k1 } from '@noble/curves/secp256k1.js';
import { hexToBytes, bytesToHex } from '@noble/curves/utils.js';

//   privateKeyHex: 64-char hex private key
//   fields: the exact fields and values sent in the request body
async function signPayload(privateKeyHex, ...parts) {
  // 1. Hash the concatenated field values with SHA-256
  const raw = parts.map(p => String(p)).join('');
  const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(raw));
  const hashBytes = new Uint8Array(buf);

  // 2. ECDSA sign with secp256k1 — prehash:false because we already hashed
  //    it ourselves above; format:'der' gives the DER-encoded output the
  //    server expects (@noble/curves defaults to a compact format otherwise)
  const derBytes = secp256k1.sign(hashBytes, hexToBytes(privateKeyHex), { prehash: false, format: 'der' });
  return bytesToHex(derBytes);  // DER-encoded hex string
}

// Usage for a token transfer
const timestamp = Date.now();
const signature = await signPayload(
  myPrivateKey,
  fromAddress, toAddress, amount, tokenSymbol, timestamp
);

// Deriving your wallet address (uncompressed public key) with @noble/curves —
// pass isCompressed:false explicitly, since it compresses by default:
const walletAddress = bytesToHex(secp256k1.getPublicKey(hexToBytes(myPrivateKey), false));

Token Transfer

POST /token/transfer

Transfer tokens from one wallet to another. Sign the transaction client-side — only the signature travels to the server, never the private key. The server verifies the ECDSA signature against the sender's public address before executing. 0% fee, instant settlement.

FieldTypeRequiredDescription
fromAddressstringRequiredSender's wallet address (public key)
toAddressstringRequiredRecipient's wallet address
amountnumberRequiredToken amount to send
tokenSymbolstringRequiredToken symbol e.g. USDC, CHEDDA
timestampnumberRequiredUnix ms timestamp used when signing — must match signed payload exactly
signaturestringRequiredDER hex ECDSA signature over fromAddress + toAddress + amount + tokenSymbol + timestamp
// Step 1 — sign locally (private key never leaves the device)
const timestamp = Date.now();
const signature = await signPayload(
  myPrivateKey,
  fromAddress, toAddress, amount, tokenSymbol, timestamp
);

// Step 2 — send only the signed payload
const res = await fetch('https://moochedda.com/token/transfer', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    fromAddress:  '04a3f9...',
    toAddress:    '04b7c2...',
    amount:       100,
    tokenSymbol:  'USDC',
    timestamp,
    signature
  })
});

// Response — 0% fee, instant
{
  "success": true,
  "message": "Token transfer confirmed!",
  "transaction": {
    "from":      "04a3f9...",
    "to":        "04b7c2...",
    "amount":    100,
    "token":     "USDC",
    "timestamp": 1710000000000
  }
}

Verify Transfer

POST /transactions/verify

Confirms that one specific, already-known transfer really settled — built for merchant/relay-style third-party payment verification, where a payer signs and submits a transfer, then discloses the resulting signature to you as proof of payment.

This is deliberately not a general transaction-history lookup. GET /transactions/{address} requires a view token proving you hold that address's private key — a merchant or relay verifying someone else's payment can never legitimately obtain one, and the disclosure/request workflow under /operations/disclosure/* is an operator-only, out-of-band process, not something a payment integration calls automatically. This endpoint needs neither: the signature field is the access key. A real secp256k1 DER signature is ~140 hex characters — computationally infeasible to guess — so the only way you legitimately hold one for a given transfer is that the payer generated it (to submit the transfer in the first place) and then deliberately disclosed it to you. Confirming "yes, a transfer matching what you were told about really settled" doesn't leak anything you weren't already told, and it can never be used to browse or discover a wallet's history.

signature is the only required field. If you also pass any of fromAddress, toAddress, tokenSymbol, amount, or timestamp, every field you pass must match the real settled transfer exactly, or the response is {"exists": false} — this catches a payer who discloses a genuine signature but lies about the amount or parties in the side-channel message (e.g. claiming "I paid you 100 USDC" while pointing at a signature that actually settled for 1 USDC). Passing the full claim, not just the signature, is the safer way to call this for real payment verification.

FieldTypeRequiredDescription
signaturestringRequiredThe DER hex signature the payer disclosed to you as proof of payment
fromAddressstringOptionalAsserted sender — must match exactly if provided
toAddressstringOptionalAsserted recipient — must match exactly if provided
amountnumberOptionalAsserted amount — must match exactly if provided
tokenSymbolstringOptionalAsserted token, e.g. USDC — case-insensitive, must match if provided
timestampnumberOptionalAsserted unix ms timestamp — must match exactly if provided
// Recommended: assert the full claim you were told, not just the signature
const res = await fetch('https://moochedda.com/transactions/verify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    fromAddress: '04a3f9...',   // what the payer told you — optional, but recommended
    toAddress:   '04b7c2...',
    amount:      100,
    tokenSymbol: 'USDC',
    timestamp:   1710000000000,
    signature:   '3045022100...'  // what the payer disclosed as proof
  })
});
const { exists, settled } = await res.json();
// exists === true, settled === true  ->  the full claim matches a real settled transfer
// exists === false                     ->  no match: wrong amount, wrong parties, unknown
//                                            signature, or the transfer never happened

// Minimal form — signature only, no claim verification
const minimal = await fetch('https://moochedda.com/transactions/verify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ signature: '3045022100...' })
});
// -> { "exists": true, "settled": true } or { "exists": false }

Create Invoice

POST /invoice/create

Generate a payment invoice with line items, optional tax, and accepted tokens. Returns an invoiceId and a hosted payment URL to redirect the customer to. No authentication required — invoices are public payment requests.

FieldTypeRequiredDescription
merchantAddressstringRequiredMerchant's wallet address — funds go directly here
merchantNamestringOptionalDisplay name shown on the payment page
itemsarrayRequiredArray of { name, price, quantity } line items
taxRatenumberOptionalTax rate as a decimal e.g. 0.08 for 8%
acceptedTokensstring[]OptionalAccepted token symbols. Defaults to ["USDC"]
preferredTokenstringOptionalPre-selected token on the payment page
expiresInnumberOptionalExpiry in seconds (default: 3600)
const res = await fetch('https://moochedda.com/invoice/create', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    merchantAddress: '04a3f9...',
    merchantName:    'Acme Store',
    items: [
      { name: 'Widget Pro', price: 49.99, quantity: 2 },
      { name: 'Shipping',   price: 5.00,  quantity: 1 }
    ],
    taxRate:        0.08,
    acceptedTokens: ['USDC', 'CHEDDA'],
    preferredToken: 'USDC',
    expiresIn:      3600
  })
});
const { invoiceId, paymentUrl, total } = await res.json();

// Redirect the customer to pay:
window.location.href = paymentUrl;

// Response
{
  "invoiceId":  "inv_9x2kq...",
  "paymentUrl": "https://moochedda.com/pay/inv_9x2kq...",
  "total":      112.47,
  "status":     "pending",
  "expiresAt":  1710003600000
}

Pay Invoice

POST /invoice/:id/pay

Pay an existing invoice directly via the API. The payer signs the transaction client-side. This is an alternative to redirecting the customer to the hosted payment page.

FieldTypeRequiredDescription
fromAddressstringRequiredPayer's wallet address
tokenSymbolstringOptionalToken to pay with — must be in invoice's acceptedTokens. Defaults to first accepted token.
timestampnumberRequiredUnix ms timestamp used when signing
signaturestringRequiredDER hex ECDSA signature over fromAddress + merchantAddress + amount + tokenSymbol + timestamp
const invoiceId = 'inv_9x2kq...';

// Fetch invoice to get amount and merchantAddress
const invoice = await (await fetch(`https://moochedda.com/invoice/${invoiceId}`)).json();

const timestamp = Date.now();
const signature = await signPayload(
  myPrivateKey,
  fromAddress, invoice.merchantAddress, invoice.total, tokenSymbol, timestamp
);

const res = await fetch(`https://moochedda.com/invoice/${invoiceId}/pay`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ fromAddress, tokenSymbol: 'USDC', timestamp, signature })
});

// Response
{
  "success": true,
  "invoiceId": "inv_9x2kq...",
  "paidAt":    1710000000000,
  "paidBy":    "04a3f9...",
  "amount":    112.47,
  "token":     "USDC"
}

Invoice Status

GET /invoice/:id/status

Poll this endpoint after the customer returns from the hosted payment page to confirm on-chain finality before fulfilling an order. No authentication required.

const res = await fetch(
  `https://moochedda.com/invoice/${invoiceId}/status`
);
const data = await res.json();

// Possible status values:
//  "pending"  — waiting for payment
//  "paid"     — confirmed on-chain
//  "expired"  — invoice timed out

if (data.status === 'paid') {
  fulfillOrder(data.paidBy, data.paidAt);
}

// Response
{
  "invoiceId": "inv_9x2kq...",
  "status":    "paid",
  "paidBy":    "04a3f9...",
  "paidAt":    1710000000000,
  "amount":    112.47,
  "token":     "USDC"
}

Deposits

Funding Wallets

Users fund their wallets by depositing USD via ACH or wire transfer. The deposit is credited as USDC at 1:1 — no conversion fee. The wallet address must be included as the memo/reference on the bank transfer.

Bank Deposit Instructions

GET /usdc/deposit/instructions

Returns ACH and wire bank details to display to users who want to fund their wallet with USDC. Deposits arrive at 0% fee, credited 1:1 USD. The user must include their wallet address as the memo/reference.

const { wire, ach } = await (
  await fetch('https://moochedda.com/usdc/deposit/instructions')
).json();

// Response — same shape for wire and ach
{
  "wire": {
    "bankName":       "Wells Fargo Bank, N.A.",
    "beneficiary":    "MooChedda Inc.",
    "routingABA":     "125009548",
    "accountNumber":  "••••3456",
    "swiftCode":      "WFBIUS6S",
    "processingTime": "Same day or next business day",
    "fee":            "0%",
    "memo":           "Your wallet address (required)"
  },
  "ach": {
    "bankName":       "Wells Fargo Bank, N.A.",
    "routingABA":     "125009548",
    "accountNumber":  "••••3456",
    "processingTime": "1–2 business days",
    "fee":            "0%",
    "memo":           "Your wallet address (required)"
  }
}

Merchants

Merchant Authentication

Merchant write endpoints (register, update, products, staff) require a session established via a challenge–response ECDSA flow. No passwords — ownership is proved by signing a server-issued challenge with your wallet's private key.

1
Request a challenge
POST your wallet address to /merchant/auth/challenge. The server returns a one-time nonce and a canonical message string to sign.
2
Sign the message locally
ECDSA-sign the message string (not a hash — sign the raw UTF-8 bytes) with your secp256k1 private key. DER-encode the result as hex.
3
Verify to get a session cookie
POST the nonce and signature to /merchant/auth/verify. On success the server sets an mc_session cookie. Include it (automatically via browser, or manually via Cookie header in server-to-server calls) on all subsequent merchant requests.
Session lifetime Sessions expire after a fixed TTL. Re-authenticate by repeating the challenge flow. Call POST /merchant/auth/logout to invalidate a session early.

Get Auth Challenge

POST /merchant/auth/challenge

Issues a one-time challenge for a wallet address. The returned message is the exact string you must sign — do not hash it first.

FieldTypeRequiredDescription
addressstringRequiredMerchant wallet address (public key)
purposestringOptionalDefaults to "merchant-portal"
const res = await fetch('https://moochedda.com/merchant/auth/challenge', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ address: '04a3f9...' })
});
const { challenge } = await res.json();
// challenge.message  — sign this string
// challenge.nonce    — include in /verify

// Response
{
  "challenge": {
    "address":   "04a3f9...",
    "nonce":     "a1b2c3d4...",
    "message":   "Sign in to MooChedda merchant portal\n...",
    "issuedAt":  "2026-01-01T00:00:00.000Z",
    "expiresAt": "2026-01-01T00:05:00.000Z"
  }
}

Verify & Login

POST /merchant/auth/verify

Submits the signed challenge. On success the server sets an mc_session cookie that authenticates all subsequent merchant write requests.

FieldTypeRequiredDescription
addressstringRequiredMerchant wallet address
noncestringRequiredThe nonce returned by /merchant/auth/challenge
signaturestringRequiredDER hex ECDSA signature over the raw message string (sign UTF-8 bytes, not a pre-hash)
purposestringOptionalMust match the value used in /merchant/auth/challenge

elliptic shown below for brevity — see Signing Transactions above for an equivalent @noble/curves version; either works.

// Sign the raw message bytes (not a hash)
const msgBytes = new TextEncoder().encode(challenge.message);
const hashBuf  = await crypto.subtle.digest('SHA-256', msgBytes);
const hashHex  = Array.from(new Uint8Array(hashBuf))
  .map(b => b.toString(16).padStart(2, '0')).join('');

const ec  = new elliptic.ec('secp256k1');
const key = ec.keyFromPrivate(myPrivateKey, 'hex');
const signature = key.sign(hashHex).toDER('hex');

const res = await fetch('https://moochedda.com/merchant/auth/verify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',  // stores the mc_session cookie
  body: JSON.stringify({
    address:   '04a3f9...',
    nonce:     challenge.nonce,
    signature
  })
});

// Response — mc_session cookie is set automatically
{
  "success":   true,
  "address":   "04a3f9...",
  "expiresAt": "2026-01-01T01:00:00.000Z"
}

Create Merchant

POST /merchant/register

Creates a new merchant profile linked to the authenticated wallet address. If a profile already exists for that address this call acts as an update. Requires an active mc_session cookie.

FieldTypeRequiredDescription
addressstringRequiredMerchant wallet address — must match the authenticated session
namestringOptionalStore display name (defaults to "My Store")
logostringOptionalLogo URL or base64 image string
const res = await fetch('https://moochedda.com/merchant/register', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',  // sends mc_session cookie
  body: JSON.stringify({
    address: '04a3f9...',
    name:    'Acme Store'
  })
});
const { merchant } = await res.json();

// Response
{
  "merchant": {
    "address":   "04a3f9...",
    "name":      "Acme Store",
    "logo":      null,
    "createdAt": "2026-01-01T00:00:00.000Z",
    "products":  [],
    "staff":     []
  }
}

Get Merchant

GET /merchant/:address

Returns a merchant's public profile. No authentication required.

const res = await fetch(
  `https://moochedda.com/merchant/${address}`
);
const { merchant } = await res.json();

// Response
{
  "merchant": {
    "address":   "04a3f9...",
    "name":      "Acme Store",
    "logo":      null,
    "createdAt": "2026-01-01T00:00:00.000Z",
    "products":  [],
    "staff":     []
  }
}

Update Merchant / Upload Logo

PUT /merchant/:address

Update a merchant's name and/or logo. The logo field accepts either a URL string or a base64-encoded image (include the data URI prefix, e.g. data:image/png;base64,...). Only the owner's session is accepted. Omit a field to leave it unchanged.

FieldTypeRequiredDescription
namestringOptionalNew store display name
logostringOptionalLogo URL or base64 data URI e.g. data:image/png;base64,iVBOR.... Pass null to remove.
// Convert a File object to base64 (browser)
function toBase64(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload  = () => resolve(reader.result); // data:image/...;base64,...
    reader.onerror = reject;
    reader.readAsDataURL(file);
  });
}

const logo = await toBase64(fileInputElement.files[0]);

const res = await fetch(`https://moochedda.com/merchant/${address}`, {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',
  body: JSON.stringify({ name: 'Acme Store', logo })
});
const { merchant } = await res.json();

// Response
{
  "merchant": {
    "address": "04a3f9...",
    "name":    "Acme Store",
    "logo":    "data:image/png;base64,iVBOR..."
  }
}

Add Product

POST /merchant/:address/product

Add a product to the merchant's catalogue. Accessible by the owner or any staff member. The product is assigned a unique id and immediately available on the merchant's store page.

FieldTypeRequiredDescription
namestringRequiredProduct name
pricenumberRequiredPrice in the chosen token
tokenstringOptionalToken symbol for pricing (default: CHEDDA)
descstringOptionalProduct description
photostringOptionalPhoto URL or base64 data URI
emojistringOptionalEmoji icon shown in the store (default: 📦)
const res = await fetch(`https://moochedda.com/merchant/${address}/product`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',
  body: JSON.stringify({
    name:  'Widget Pro',
    price: 49.99,
    token: 'USDC',
    desc:  'The best widget on the market.',
    emoji: '🔧'
  })
});
const { product } = await res.json();

// Response
{
  "product": {
    "id":        "p_m3k9x2",
    "name":      "Widget Pro",
    "price":     49.99,
    "token":     "USDC",
    "desc":      "The best widget on the market.",
    "emoji":     "🔧",
    "photo":     null,
    "createdAt": "2026-01-01T00:00:00.000Z"
  }
}

Tokens

Minting Tokens

Create and mint a new token on the MooChedda blockchain. The creator's private key is used server-side solely to sign the on-chain mint transaction — the token supply is credited directly to the creator's wallet address.

Private key is transmitted for this call. This endpoint requires your private key to sign the mint transaction on your behalf. Only use this over TLS and from a trusted server environment — never from untrusted client code.

Mint Token

POST /token/create

Creates a new token and mints the entire totalSupply to the creator wallet in one atomic operation. The private key is verified to match the creator address before the mint is committed — supply cannot be created by anyone else.

FieldTypeRequiredDescription
symbolstringRequiredTicker symbol e.g. MYTOKEN — must be unique on the platform
namestringRequiredHuman-readable token name e.g. My Token
totalSupplynumberRequiredTotal number of tokens to mint and credit to the creator
creatorstringRequiredWallet address that will receive the minted supply
privateKeystringRequired64-char hex private key of the creator — used to sign the on-chain mint transaction
decimalsnumberOptionalToken precision (default: 18)
metadataobjectOptionalArbitrary JSON metadata stored with the token
// Mint a new token
const res = await fetch('https://moochedda.com/token/create', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    symbol:      'MYTOKEN',
    name:        'My Token',
    totalSupply: 1000000,
    creator:     '04a3f9c2...',   // your wallet address
    privateKey:  '7e2c1b3d...',   // 64-char hex — use over TLS only
    decimals:    18,
    metadata:    { description: 'My custom token' }
  })
});
const data = await res.json();

// Response
{
  "success": true,
  "message": "Token MYTOKEN created successfully!",
  "token": {
    "tokenId":     "a3b9f1...",
    "symbol":      "MYTOKEN",
    "name":        "My Token",
    "totalSupply": 1000000,
    "decimals":    18,
    "creator":     "04a3f9c2...",
    "createdAt":   1710000000000
  },
  "asset": null
}

Transactions

Transaction History

Retrieve the on-chain transaction history for any wallet address, optionally filtered by token. Results are paginated — default page size is 50, maximum is 100. Redacted by default — pass an Account Privacy view token to see the address's own history in full.

Transaction History

GET /transactions/:address/:tokenSymbol?

Returns paginated transactions involving :address as sender, recipient, or creator. Optionally narrow results to a single token by appending /:tokenSymbol. Results are sorted newest-first. Without a valid x-view-token for this address, the response has redacted: true and an empty transactions array — pagination.total still reflects the real count.

ParameterTypeRequiredDescription
addresspathRequiredWallet address to query
tokenSymbolpathOptionalFilter by token symbol e.g. USDC, CHEDDA
pagequeryOptionalPage number, 1-based (default: 1)
limitqueryOptionalResults per page (default: 50, max: 100)
// All transactions for an address — first page
const res = await fetch(
  `https://moochedda.com/transactions/${address}?page=1&limit=50`
);
const data = await res.json();

// Filter by token
const res2 = await fetch(
  `https://moochedda.com/transactions/${address}/USDC?page=1&limit=50`
);

// Response
{
  "success": true,
  "transactions": [
    {
      "type":        "TOKEN_TRANSFER",
      "fromAddress": "04a3f9...",
      "toAddress":   "04b7c2...",
      "amount":      100,
      "tokenSymbol": "USDC",
      "timestamp":   1710000000000,
      "signature":   "3045..."
    }
  ],
  "pagination": {
    "total":      243,
    "totalPages": 5,
    "page":       1,
    "limit":      50,
    "hasNext":    true,
    "hasPrev":    false
  }
}

// Iterate all pages
async function fetchAllTransactions(address, token = '') {
  const path = token ? `/transactions/${address}/${token}` : `/transactions/${address}`;
  let page = 1, all = [];
  do {
    const { transactions, pagination } = await (
      await fetch(`https://moochedda.com${path}?page=${page}&limit=100`)
    ).json();
    all.push(...transactions);
    if (!pagination.hasNext) break;
    page++;
  } while (true);
  return all;
}

Market Data

Tokens & Prices

Read-only endpoints for token metadata and real-time prices. No authentication required.

List All Tokens

GET /tokens

Returns all tokens registered on the platform, including symbol, name, total supply, creator address, and decimals.

const { tokens } = await (
  await fetch('https://moochedda.com/tokens')
).json();

// Each token object:
{
  "tokenId":     "a3b9f1...",
  "symbol":      "USDC",
  "name":        "USD Coin",
  "totalSupply": 1000000,
  "decimals":    6,
  "creator":     "04a3f9...",
  "type":        "CURRENCY",
  "createdAt":   1710000000000
}

Token Prices

GET /prices

Returns current prices for all tokens denominated in USDT. Prices update in real time based on on-chain swap activity via the built-in price oracle.

const { prices } = await (
  await fetch('https://moochedda.com/prices')
).json();

// Response
{
  "prices": {
    "USDC":   1.00,
    "CHEDDA": 0.042
  },
  "updatedAt": 1710000000000
}