FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Add withdrawal endpoint for portable token exit by melvincarvalho · Pull Request #180 · JavaScriptSolidServer/JavaScriptSolidServer · GitHub

Repository navigation

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .js  (1) All 1 file type selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
106 changes: 102 additions & 4 deletions src/handlers/pay.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
* Authentication via NIP-98. Balance tracking via Web Ledgers spec.
*
* Routes:
* GET /pay/.balance — check your balance
* POST /pay/.deposit — deposit sats (TXO URI) or tokens (MRC20 state proof)
* GET /pay/* — paid resource access (requires balance >= cost)
* PUT /pay/* — upload resources (standard auth)
* GET /pay/.balance — check your balance
* POST /pay/.deposit — deposit sats (TXO URI) or tokens (MRC20 state proof)
* POST /pay/.buy — buy tokens with sat balance (primary market)
* POST /pay/.withdraw — withdraw balance as tokens (portable MRC20 proof)
* GET /pay/* — paid resource access (requires balance >= cost)
* PUT /pay/* — upload resources (standard auth)
*
* Ledger: /.well-known/webledgers/webledgers.json (webledgers.org spec)
*
Expand Down Expand Up @@ -363,6 +365,102 @@ export function createPayHandler(options = {}) {
});
}

// --- POST /pay/.withdraw — withdraw balance as tokens ---
if (url === '/pay/.withdraw' && request.method === 'POST') {
const pubkey = await getNostrPubkey(request);
if (!pubkey) {
return reply.code(401).send({ error: 'NIP-98 authentication required' });
}

if (!payToken) {
return reply.code(400).send({ error: 'Withdrawal not configured (no --pay-token set)' });
}

// Parse withdraw request
let body = request.body;
if (Buffer.isBuffer(body)) body = JSON.parse(body.toString('utf8'));
if (typeof body === 'string') body = JSON.parse(body);

const didUri = pubkeyToDidNostr(pubkey);
const ledger = await readLedger();
const balance = getBalance(ledger, didUri);

// Calculate withdrawal amount
let satCost, tokenAmount;
if (body?.all) {
satCost = balance;
tokenAmount = Math.floor(balance / payRate);
} else if (body?.sats) {
satCost = Math.floor(body.sats);
tokenAmount = Math.floor(satCost / payRate);
} else if (body?.tokens) {
tokenAmount = Math.floor(body.tokens);
satCost = tokenAmount * payRate;
} else {
return reply.code(400).send({
error: 'Specify tokens, sats, or all: true',
balance,
rate: payRate,
unit: 'sat/token'
});
}

if (tokenAmount <= 0) {
return reply.code(400).send({ error: 'Nothing to withdraw', balance, rate: payRate });
}

if (balance < satCost) {
return reply.code(402).send({
error: 'Insufficient balance',
balance,
cost: satCost,
rate: payRate
});
}

// Load token trail
const trail = await loadTrail(payToken);
if (!trail) {
return reply.code(500).send({ error: `Token ${payToken} not minted on this pod` });
}

// Transfer tokens to user
let result;
try {
result = await transferToken({
ticker: payToken,
to: pubkey,
amount: tokenAmount,
mempoolUrl
});
} catch (err) {
return reply.code(500).send({ error: `Transfer failed: ${err.message}` });
}

// Debit balance
debit(ledger, didUri, satCost);
await writeLedger(ledger);

return reply.send({
withdrawn: tokenAmount,
ticker: payToken,
cost: satCost,
rate: payRate,
balance: getBalance(ledger, didUri),
unit: 'sat',
txid: result.txid,
proof: {
state: result.state,
prevState: result.prevState,
anchor: {
pubkey: result.trail.pubkeyBase,
stateStrings: result.trail.stateStrings,
network: result.trail.network
}
}
});
}

// --- GET/HEAD /pay/* — paid resource access ---
if (request.method === 'GET' || request.method === 'HEAD') {
const pubkey = await getNostrPubkey(request);
Expand Down

Back | FazBrowse Home | New Git URL