Payments (MooChedda)
Every payment on Nodofi — node commissions, relay access fees, submission fees, priced apps — settles in USDC on MooChedda, the only payment network Nodofi uses. Nodofi itself never holds the money: buyers, publishers, node operators and relay operators all pay each other directly, wallet to wallet.
Overview
The shape of a paid-app sale, the pattern every other payment on Nodofi (submission fees, relay access) follows too:
Buyer's own wallet
│ two direct USDC transfers, signed by the buyer, sent straight to MooChedda
├──────────────► Publisher's wallet (price − commission)
└──────────────► Node's wallet (commission)
Studio (and a node, and a relay) never holds funds in escrow. Whoever needs to confirm a payment happened — a node checking it's been paid before unlocking an app, Studio recording a purchase, a relay checking an access fee — verifies it after the fact against MooChedda's own ledger, then records what that verification means (an entitlement, a queued submission, access granted). See API & code examples below for exactly how that verification works, if you're integrating with it directly.
A MooChedda wallet is self-custody — a 12-word recovery phrase and a private key you control, secured the same way as Bitcoin or Ethereum (secp256k1 signatures). No KYC, ready in seconds. MooChedda charges nothing to send USDC — no percentage, no per-transaction fee — so what a buyer sends is exactly what a publisher and node receive (split only by the commission rate the publisher agreed to). Each USDC is backed one-to-one by USD held in a full-reserve account and redeemable back to USD at any time; transfers confirm in the next block, typically under a second, on MooChedda's own payment-grade chain.
Wallets
Anywhere Studio or the App Browser asks for a wallet, there's a "Don't have a wallet? Create one" button — creates a real MooChedda wallet without leaving the page. Its recovery phrase and private key are shown once; save them immediately (a download button is offered) — neither is ever sent anywhere else or stored by Studio. Losing them means losing access to that wallet's funds for good; there is no password reset for self-custody.
Fund a wallet by ACH (2–3 days) or wire (same day) at moochedda.com — no deposit fees either way — then spend or earn it anywhere on Nodofi.
Creating one yourself (outside Studio)
curl -s -X POST https://moochedda.com/wallet/create -H "Content-Type: application/json" -d '{}'
# { "address": "04...", "privateKey": "...", "mnemonic": "twelve words ..." }
A wallet's address is its private key's public key — plain secp256k1 math, no call to MooChedda needed to derive one from a key you already have:
// Node.js — using @noble/curves (the same library this project's own code uses; see below)
const { secp256k1 } = require('@noble/curves/secp256k1.js');
const privateKey = '...64 hex characters...';
const address = Buffer.from(secp256k1.getPublicKey(Buffer.from(privateKey, 'hex'), false)).toString('hex');
// address is 130 hex characters — that IS the wallet address, an uncompressed public key
Getting paid
As a publisher
Set a price and your payout wallet in Studio's "Send to Node…" dialog, and tick the box agreeing to that node's current commission rate. That rate is locked in at send time — the node stamps it into the app's own record right then, so the node changing its default commission later never retroactively changes an app that's already live. Re-sending the app (and re-agreeing) is the only way to pick up a new rate.
As a node operator
On the node's own admin page, under Payments: set a payout wallet and a commission rate (0–50%) to start accepting priced apps — both are required, a node with only one of the two still refuses payments. Optionally set a submission fee too, charged to no-key submitters before their app is even queued for review (see Submission fees below, and Node: Reviewing submissions).
curl -s -X POST http://localhost:8800/admin/pricing -H "Content-Type: application/json" -H "X-Node-Admin: 1" -d '{
"wallet": "04...your MooChedda address...",
"commissionRate": 0.15,
"submissionFee": 2
}'
As a relay operator
A relay can charge nodes a periodic fee to use it (never requesters, only nodes claiming a permanent
identity) — ACCESS_FEE_AMOUNT, ACCESS_FEE_WALLET,
ACCESS_FEE_PERIOD_DAYS in its .env. See
Relay: Paid access & directory.
Buying a priced app
In the App Browser, buying an app signs and sends both transfers described in Overview straight from the buyer's own wallet — Studio and the node only verify, never custody. A purchase is a lifetime entitlement to that app's UID, tied to the buyer's Studio account rather than to whichever node the purchase happened through, so re-opening the same app later (even via a different node hosting the same UID) never asks for payment again.
Submission fees
When Studio sends an app with no key (see Studio: Submitting without a key) to a node that charges a submission fee, the fee is paid before the submission is even accepted into the review queue:
- Studio signs one transfer — from a wallet whose private key you provide right there (pasted, or a wallet file), held only in memory for the moment it takes to sign, never stored — straight to the node's own operator wallet, for the exact fee amount that node's admin page currently shows.
- That signed transfer is submitted to MooChedda and, once confirmed, included with the submission itself as proof of payment.
- The node independently re-verifies the payment (signature and that it actually settled — see API & code examples) before it will queue the submission at all — never trusting the claim alone.
The same wallet used to pay the fee doubles as the payout wallet if the app is also priced, since paying the fee is itself proof of who's submitting. The fee is not refunded on rejection, but fixing and resubmitting the same app afterward needs no new payment — only a genuinely different app does.
API & code examples
How this project verifies a payment without ever holding the money, for anyone integrating with MooChedda directly (a node, a relay, or your own service):
1. Sign a transfer
// The exact hash MooChedda signs: sha256(fromAddress + toAddress + amount + tokenSymbol + timestamp),
// each field turned into a string and joined with nothing in between.
const crypto = require('crypto');
const { secp256k1 } = require('@noble/curves/secp256k1.js');
function payloadHash({ fromAddress, toAddress, amount, tokenSymbol, timestamp }) {
const raw = [fromAddress, toAddress, amount, tokenSymbol, timestamp].map(String).join('');
return crypto.createHash('sha256').update(raw).digest();
}
function signLeg(privateKeyHex, leg) {
const hash = payloadHash(leg);
const sig = secp256k1.sign(hash, Buffer.from(privateKeyHex, 'hex'), { prehash: false, format: 'der' });
return { ...leg, signature: Buffer.from(sig).toString('hex') };
}
const leg = signLeg(myPrivateKey, {
fromAddress: myAddress, toAddress: recipientAddress,
amount: 2, tokenSymbol: 'USDC', timestamp: Date.now()
});
2. Submit it
curl -s -X POST https://moochedda.com/token/transfer -H "Content-Type: application/json" -d '<leg from above>'
# { "success": true, ... } — or { "success": false, "message": "..." }
3. Whoever's receiving it verifies — signature first, then settlement
curl -s -X POST https://moochedda.com/transactions/verify -H "Content-Type: application/json" -d '{
"fromAddress": "...", "toAddress": "...", "amount": 2, "tokenSymbol": "USDC",
"timestamp": 1234567890, "signature": "..."
}'
# { "exists": true } — a validly-signed transfer that never actually settled comes back false, not true
Recomputing the signature locally (step 1's payloadHash, checked against the claimed
wallet's own public key) rules out a forged claim with no network call at all; the settlement check above
is what rules out a real signature over a payment that was never actually sent. Both matter — see
server/moochedda.js and docs/moochedda-payments.md in the source for the full
set of edge cases this project's own code checks (stale/future timestamps, wrong amounts, wrong
recipients, a signature reused to double-count one payment).
Signing library
This project uses
@noble/curves's secp256k1 implementation throughout, not elliptic — audited
(Trail of Bits, Cure53), no open advisories, and what MooChedda's own SDK docs now recommend. If you're
integrating against an older MooChedda client still signing with elliptic: @noble/curves
enforces canonical low-S signatures on verify() by default, which some (not all) valid
elliptic-produced signatures will fail to pass — same curve, same address format, but not
every old signature verifies under the new library without extra handling.
server/moochedda.js and the App Browser's client honor a MOOCHEDDA_API override
(an env var server-side, a window.MOOCHEDDA_API global client-side) pointed at a compatible
stand-in.