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.
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.
Base URL
All API requests go to the following base URL. All request and response bodies are application/json.
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.
fromAddress, toAddress, amount, tokenSymbol, timestamp.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.
| Status | Meaning |
|---|---|
| 400 | Bad request — missing or invalid fields |
| 401 | Unauthorized — invalid ECDSA signature |
| 404 | Resource not found |
| 500 | Server error |
// Error response shape {"error" :"Invalid signature" }
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.
invoiceId.MooCheddaPay.mount() renders the items, total and QR, and watches for payment.invoice.paid webhook or a status check — not the browser callback alone.Merchant-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'
Quick Start — MooCheddaSDK
Drop-in JavaScript client for the MooChedda exchange. One file, zero build step, works in browser and Node.js.
// ── 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 MooCheddaSDKfrom './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();
Method Reference
| Method | Auth | Description |
|---|---|---|
| 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 |
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/createGenerate 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.
| Field | Type | Required | Description |
|---|---|---|---|
| label | string | Optional | Human-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/recoverRestore a wallet from a BIP39 mnemonic phrase. Returns the wallet address so you can verify before proceeding. The server does not store the mnemonic.
| Field | Type | Required | Description |
|---|---|---|---|
| mnemonic | string | Required | Space-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/ , { headers: {${address} `'x-view-token' : viewToken } });const { balances } =await res2.json();// { "USDC": 500.00, "CHEDDA": 1000 }
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.
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/ , { headers: {${address} `'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; }
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.
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.
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/transferTransfer 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.
| Field | Type | Required | Description |
|---|---|---|---|
| fromAddress | string | Required | Sender's wallet address (public key) |
| toAddress | string | Required | Recipient's wallet address |
| amount | number | Required | Token amount to send |
| tokenSymbol | string | Required | Token symbol e.g. USDC, CHEDDA |
| timestamp | number | Required | Unix ms timestamp used when signing — must match signed payload exactly |
| signature | string | Required | DER 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.
| Field | Type | Required | Description |
|---|---|---|---|
| signature | string | Required | The DER hex signature the payer disclosed to you as proof of payment |
| fromAddress | string | Optional | Asserted sender — must match exactly if provided |
| toAddress | string | Optional | Asserted recipient — must match exactly if provided |
| amount | number | Optional | Asserted amount — must match exactly if provided |
| tokenSymbol | string | Optional | Asserted token, e.g. USDC — case-insensitive, must match if provided |
| timestamp | number | Optional | Asserted 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/createGenerate 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.
| Field | Type | Required | Description |
|---|---|---|---|
| merchantAddress | string | Required | Merchant's wallet address — funds go directly here |
| merchantName | string | Optional | Display name shown on the payment page |
| items | array | Required | Array of { name, price, quantity } line items |
| taxRate | number | Optional | Tax rate as a decimal e.g. 0.08 for 8% |
| acceptedTokens | string[] | Optional | Accepted token symbols. Defaults to ["USDC"] |
| preferredToken | string | Optional | Pre-selected token on the payment page |
| expiresIn | number | Optional | Expiry 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/payPay 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.
| Field | Type | Required | Description |
|---|---|---|---|
| fromAddress | string | Required | Payer's wallet address |
| tokenSymbol | string | Optional | Token to pay with — must be in invoice's acceptedTokens. Defaults to first accepted token. |
| timestamp | number | Required | Unix ms timestamp used when signing |
| signature | string | Required | DER 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/ )).json();${invoiceId} `const timestamp = Date.now();const signature =await signPayload ( myPrivateKey, fromAddress, invoice.merchantAddress, invoice.total, tokenSymbol, timestamp );const res =await fetch(`https://moochedda.com/invoice/ , { method:${invoiceId} /pay`'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/statusPoll 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" }
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/instructionsReturns 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)" } }
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.
/merchant/auth/challenge. The server returns a one-time nonce and a canonical message string to sign.message string (not a hash — sign the raw UTF-8 bytes) with your secp256k1 private key. DER-encode the result as hex.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.POST /merchant/auth/logout to invalidate a session early.
Get Auth Challenge
POST /merchant/auth/challengeIssues a one-time challenge for a wallet address. The returned message is the exact string you must sign — do not hash it first.
| Field | Type | Required | Description |
|---|---|---|---|
| address | string | Required | Merchant wallet address (public key) |
| purpose | string | Optional | Defaults 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/verifySubmits the signed challenge. On success the server sets an mc_session cookie that authenticates all subsequent merchant write requests.
| Field | Type | Required | Description |
|---|---|---|---|
| address | string | Required | Merchant wallet address |
| nonce | string | Required | The nonce returned by /merchant/auth/challenge |
| signature | string | Required | DER hex ECDSA signature over the raw message string (sign UTF-8 bytes, not a pre-hash) |
| purpose | string | Optional | Must 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/registerCreates 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.
| Field | Type | Required | Description |
|---|---|---|---|
| address | string | Required | Merchant wallet address — must match the authenticated session |
| name | string | Optional | Store display name (defaults to "My Store") |
| logo | string | Optional | Logo 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/:addressReturns 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/:addressUpdate 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.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Optional | New store display name |
| logo | string | Optional | Logo 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/ , { method:${address} `'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/productAdd 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.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Product name |
| price | number | Required | Price in the chosen token |
| token | string | Optional | Token symbol for pricing (default: CHEDDA) |
| desc | string | Optional | Product description |
| photo | string | Optional | Photo URL or base64 data URI |
| emoji | string | Optional | Emoji icon shown in the store (default: 📦) |
const res =await fetch(`https://moochedda.com/merchant/ , { method:${address} /product`'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" } }
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.
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.
| Field | Type | Required | Description |
|---|---|---|---|
| symbol | string | Required | Ticker symbol e.g. MYTOKEN — must be unique on the platform |
| name | string | Required | Human-readable token name e.g. My Token |
| totalSupply | number | Required | Total number of tokens to mint and credit to the creator |
| creator | string | Required | Wallet address that will receive the minted supply |
| privateKey | string | Required | 64-char hex private key of the creator — used to sign the on-chain mint transaction |
| decimals | number | Optional | Token precision (default: 18) |
| metadata | object | Optional | Arbitrary 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 }
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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| address | path | Required | Wallet address to query |
| tokenSymbol | path | Optional | Filter by token symbol e.g. USDC, CHEDDA |
| page | query | Optional | Page number, 1-based (default: 1) |
| limit | query | Optional | Results 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 ) ).json(); all.push(...transactions);${path} ?page=${page} &limit=100`if (!pagination.hasNext)break ; page++; }while (true );return all; }
Tokens & Prices
Read-only endpoints for token metadata and real-time prices. No authentication required.
List All Tokens
GET /tokensReturns 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 /pricesReturns 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 }