Merge pull request 'Upgrade to cashu-ts v4 and drop unused deps.' (#1) from dev into main
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
+45
-91
@@ -1,8 +1,25 @@
|
|||||||
const { Wallet, Mint, getDecodedToken, CheckStateEnum } = require('@cashu/cashu-ts');
|
const { Wallet, getDecodedToken, CheckStateEnum } = require('@cashu/cashu-ts');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a cashu-ts amount into a plain JavaScript number.
|
||||||
|
*
|
||||||
|
* As of cashu-ts v4, amounts returned by the library (proof amounts, melt quote
|
||||||
|
* `amount`/`fee_reserve`, etc.) are `Amount` class instances rather than plain
|
||||||
|
* numbers. `Amount` has no `valueOf()`, so arithmetic/comparison against numbers
|
||||||
|
* silently breaks (string concatenation or NaN). Always funnel library amounts
|
||||||
|
* through this before doing math with them.
|
||||||
|
*/
|
||||||
|
function toNumber(value) {
|
||||||
|
if (value == null) return 0;
|
||||||
|
if (typeof value === 'number') return value;
|
||||||
|
if (typeof value === 'bigint') return Number(value);
|
||||||
|
if (typeof value.toNumber === 'function') return value.toNumber();
|
||||||
|
const n = Number(value);
|
||||||
|
return Number.isFinite(n) ? n : 0;
|
||||||
|
}
|
||||||
|
|
||||||
class CashuComponent {
|
class CashuComponent {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.mints = new Map();
|
|
||||||
this.wallets = new Map();
|
this.wallets = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13,26 +30,15 @@ class CashuComponent {
|
|||||||
return /^cashu[abAB][a-zA-Z0-9-_]+$/.test(token);
|
return /^cashu[abAB][a-zA-Z0-9-_]+$/.test(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get token mint URL from decoded token
|
|
||||||
*/
|
|
||||||
async getTokenMintUrl(token) {
|
|
||||||
try {
|
|
||||||
const decoded = getDecodedToken(token);
|
|
||||||
if (!decoded) return null;
|
|
||||||
return decoded.mint || null;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting token mint URL:', error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode token structure (v3/v4 format always has { mint, proofs } at top level)
|
* Decode token structure (v3/v4 format always has { mint, proofs } at top level)
|
||||||
*/
|
*/
|
||||||
async decodeTokenStructure(token) {
|
async decodeTokenStructure(token) {
|
||||||
try {
|
try {
|
||||||
const decoded = getDecodedToken(token);
|
// cashu-ts v4 requires a `keysetIds` argument used only to resolve short
|
||||||
|
// (NUT-16 v2) keyset IDs. Tokens with full v0 keyset IDs — the common case —
|
||||||
|
// need no mapping, so we pass an empty list. Passing nothing throws.
|
||||||
|
const decoded = getDecodedToken(token, []);
|
||||||
if (!decoded) throw new Error('Failed to decode token');
|
if (!decoded) throw new Error('Failed to decode token');
|
||||||
if (!decoded.proofs || !Array.isArray(decoded.proofs)) {
|
if (!decoded.proofs || !Array.isArray(decoded.proofs)) {
|
||||||
throw new Error('Invalid token structure - no proofs found');
|
throw new Error('Invalid token structure - no proofs found');
|
||||||
@@ -47,14 +53,6 @@ class CashuComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate fee according to NUT-05 specification
|
|
||||||
*/
|
|
||||||
calculateFee(amount) {
|
|
||||||
const fee = Math.ceil(amount * 0.02);
|
|
||||||
return Math.max(1, fee);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse and validate a Cashu token
|
* Parse and validate a Cashu token
|
||||||
*/
|
*/
|
||||||
@@ -76,13 +74,13 @@ class CashuComponent {
|
|||||||
throw new Error('Invalid token structure - no proofs found');
|
throw new Error('Invalid token structure - no proofs found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalAmount = decoded.proofs.reduce((sum, proof) => sum + (proof.amount || 0), 0);
|
const totalAmount = decoded.proofs.reduce((sum, proof) => sum + toNumber(proof.amount), 0);
|
||||||
|
|
||||||
if (totalAmount <= 0) {
|
if (totalAmount <= 0) {
|
||||||
throw new Error('Token has no value');
|
throw new Error('Token has no value');
|
||||||
}
|
}
|
||||||
|
|
||||||
const denominations = decoded.proofs.map(proof => proof.amount);
|
const denominations = decoded.proofs.map(proof => toNumber(proof.amount));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mint: decoded.mint,
|
mint: decoded.mint,
|
||||||
@@ -98,30 +96,6 @@ class CashuComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get total amount from a token
|
|
||||||
*/
|
|
||||||
async getTotalAmount(token) {
|
|
||||||
const parsed = await this.parseToken(token);
|
|
||||||
return parsed.totalAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get or create a Mint instance
|
|
||||||
*/
|
|
||||||
async getMint(mintUrl) {
|
|
||||||
if (!this.mints.has(mintUrl)) {
|
|
||||||
try {
|
|
||||||
const mint = new Mint(mintUrl);
|
|
||||||
await mint.getInfo();
|
|
||||||
this.mints.set(mintUrl, mint);
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Failed to connect to mint ${mintUrl}: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.mints.get(mintUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or create a Wallet instance for a specific mint
|
* Get or create a Wallet instance for a specific mint
|
||||||
*/
|
*/
|
||||||
@@ -148,15 +122,18 @@ class CashuComponent {
|
|||||||
|
|
||||||
const meltQuote = await wallet.createMeltQuoteBolt11(bolt11);
|
const meltQuote = await wallet.createMeltQuoteBolt11(bolt11);
|
||||||
|
|
||||||
|
const amount = toNumber(meltQuote.amount);
|
||||||
|
const feeReserve = toNumber(meltQuote.fee_reserve);
|
||||||
|
|
||||||
console.log('Melt quote created:', {
|
console.log('Melt quote created:', {
|
||||||
amount: meltQuote.amount,
|
amount,
|
||||||
fee_reserve: meltQuote.fee_reserve,
|
fee_reserve: feeReserve,
|
||||||
quote: meltQuote.quote
|
quote: meltQuote.quote
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
amount: meltQuote.amount,
|
amount,
|
||||||
fee_reserve: meltQuote.fee_reserve,
|
fee_reserve: feeReserve,
|
||||||
quote: meltQuote.quote
|
quote: meltQuote.quote
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -176,19 +153,21 @@ class CashuComponent {
|
|||||||
const proofs = decoded.proofs;
|
const proofs = decoded.proofs;
|
||||||
|
|
||||||
const meltQuote = await wallet.createMeltQuoteBolt11(bolt11);
|
const meltQuote = await wallet.createMeltQuoteBolt11(bolt11);
|
||||||
|
const quoteAmount = toNumber(meltQuote.amount);
|
||||||
|
const quoteFeeReserve = toNumber(meltQuote.fee_reserve);
|
||||||
console.log('Melt quote created:', {
|
console.log('Melt quote created:', {
|
||||||
amount: meltQuote.amount,
|
amount: quoteAmount,
|
||||||
fee_reserve: meltQuote.fee_reserve,
|
fee_reserve: quoteFeeReserve,
|
||||||
quote: meltQuote.quote
|
quote: meltQuote.quote
|
||||||
});
|
});
|
||||||
console.log('Paying invoice:', bolt11.substring(0, 50) + '...');
|
console.log('Paying invoice:', bolt11.substring(0, 50) + '...');
|
||||||
|
|
||||||
const total = meltQuote.amount + meltQuote.fee_reserve;
|
const total = quoteAmount + quoteFeeReserve;
|
||||||
console.log('Total required:', total, 'sats (amount:', meltQuote.amount, '+ fee:', meltQuote.fee_reserve, ')');
|
console.log('Total required:', total, 'sats (amount:', quoteAmount, '+ fee:', quoteFeeReserve, ')');
|
||||||
console.log('Available in token:', parsed.totalAmount, 'sats');
|
console.log('Available in token:', parsed.totalAmount, 'sats');
|
||||||
|
|
||||||
if (total > parsed.totalAmount) {
|
if (total > parsed.totalAmount) {
|
||||||
throw new Error(`Insufficient funds. Required: ${total} sats (including ${meltQuote.fee_reserve} sats fee), Available: ${parsed.totalAmount} sats`);
|
throw new Error(`Insufficient funds. Required: ${total} sats (including ${quoteFeeReserve} sats fee), Available: ${parsed.totalAmount} sats`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Selecting proofs with includeFees: true for', total, 'sats');
|
console.log('Selecting proofs with includeFees: true for', total, 'sats');
|
||||||
@@ -198,13 +177,11 @@ class CashuComponent {
|
|||||||
console.log('Selected', proofsToSend.length, 'proofs for melting');
|
console.log('Selected', proofsToSend.length, 'proofs for melting');
|
||||||
|
|
||||||
console.log('Performing melt operation...');
|
console.log('Performing melt operation...');
|
||||||
const meltResponse = await wallet.meltProofs(meltQuote, proofsToSend);
|
const meltResponse = await wallet.meltProofsBolt11(meltQuote, proofsToSend);
|
||||||
|
|
||||||
console.log('Melt response:', JSON.stringify(meltResponse, null, 2));
|
|
||||||
|
|
||||||
const quote = meltResponse.quote || {};
|
const quote = meltResponse.quote || {};
|
||||||
const paymentSuccessful = quote.state === 'PAID' ||
|
const paymentSuccessful = quote.state === 'PAID' ||
|
||||||
quote.payment_preimage ||
|
!!quote.payment_preimage ||
|
||||||
meltResponse.paid === true;
|
meltResponse.paid === true;
|
||||||
|
|
||||||
if (!paymentSuccessful) {
|
if (!paymentSuccessful) {
|
||||||
@@ -212,7 +189,9 @@ class CashuComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const preimage = quote.payment_preimage || meltResponse.preimage;
|
const preimage = quote.payment_preimage || meltResponse.preimage;
|
||||||
const actualFeeCharged = quote.fee_reserve || meltQuote.fee_reserve;
|
const actualFeeCharged = quote.fee_reserve != null
|
||||||
|
? toNumber(quote.fee_reserve)
|
||||||
|
: quoteFeeReserve;
|
||||||
const actualNetAmount = parsed.totalAmount - actualFeeCharged;
|
const actualNetAmount = parsed.totalAmount - actualFeeCharged;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -220,7 +199,7 @@ class CashuComponent {
|
|||||||
paid: paymentSuccessful,
|
paid: paymentSuccessful,
|
||||||
preimage,
|
preimage,
|
||||||
change: meltResponse.change || [],
|
change: meltResponse.change || [],
|
||||||
amount: meltQuote.amount,
|
amount: quoteAmount,
|
||||||
fee: actualFeeCharged,
|
fee: actualFeeCharged,
|
||||||
netAmount: actualNetAmount,
|
netAmount: actualNetAmount,
|
||||||
quote: meltQuote.quote,
|
quote: meltQuote.quote,
|
||||||
@@ -244,31 +223,6 @@ class CashuComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate if a token is properly formatted and has valid proofs
|
|
||||||
*/
|
|
||||||
async validateToken(token) {
|
|
||||||
try {
|
|
||||||
if (!this.isValidTokenFormat(token)) return false;
|
|
||||||
const parsed = await this.parseToken(token);
|
|
||||||
return parsed.totalAmount > 0 && parsed.proofs.length > 0;
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get mint info for a given mint URL
|
|
||||||
*/
|
|
||||||
async getMintInfo(mintUrl) {
|
|
||||||
try {
|
|
||||||
const mint = await this.getMint(mintUrl);
|
|
||||||
return await mint.getInfo();
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Failed to get mint info: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if proofs are spendable at the mint using NUT-07 state check
|
* Check if proofs are spendable at the mint using NUT-07 state check
|
||||||
*/
|
*/
|
||||||
|
|||||||
+20
-39
@@ -1,6 +1,21 @@
|
|||||||
const axios = require('axios');
|
|
||||||
const bolt11 = require('bolt11');
|
const bolt11 = require('bolt11');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET a JSON resource with a 10s timeout using native fetch
|
||||||
|
*/
|
||||||
|
async function fetchJson(url) {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
signal: AbortSignal.timeout(10000),
|
||||||
|
headers: { 'User-Agent': 'Cashu-Redeem-API/1.0.0' }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
class LightningComponent {
|
class LightningComponent {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.allowedDomains = process.env.ALLOW_REDEEM_DOMAINS
|
this.allowedDomains = process.env.ALLOW_REDEEM_DOMAINS
|
||||||
@@ -82,17 +97,8 @@ class LightningComponent {
|
|||||||
*/
|
*/
|
||||||
async fetchLNURLpResponse(lnurlpUrl) {
|
async fetchLNURLpResponse(lnurlpUrl) {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(lnurlpUrl, {
|
const data = await fetchJson(lnurlpUrl);
|
||||||
timeout: 10000,
|
|
||||||
headers: { 'User-Agent': 'Cashu-Redeem-API/1.0.0' }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status !== 200) {
|
|
||||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = response.data;
|
|
||||||
|
|
||||||
if (data.status === 'ERROR') {
|
if (data.status === 'ERROR') {
|
||||||
throw new Error(data.reason || 'LNURLp endpoint returned error');
|
throw new Error(data.reason || 'LNURLp endpoint returned error');
|
||||||
}
|
}
|
||||||
@@ -103,7 +109,8 @@ class LightningComponent {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') {
|
const code = error.cause?.code;
|
||||||
|
if (code === 'ECONNREFUSED' || code === 'ENOTFOUND') {
|
||||||
throw new Error('Unable to connect to Lightning address provider');
|
throw new Error('Unable to connect to Lightning address provider');
|
||||||
}
|
}
|
||||||
throw new Error(`LNURLp fetch failed: ${error.message}`);
|
throw new Error(`LNURLp fetch failed: ${error.message}`);
|
||||||
@@ -122,17 +129,8 @@ class LightningComponent {
|
|||||||
url.searchParams.set('comment', comment.substring(0, 144));
|
url.searchParams.set('comment', comment.substring(0, 144));
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await axios.get(url.toString(), {
|
const data = await fetchJson(url.toString());
|
||||||
timeout: 10000,
|
|
||||||
headers: { 'User-Agent': 'Cashu-Redeem-API/1.0.0' }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status !== 200) {
|
|
||||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = response.data;
|
|
||||||
|
|
||||||
if (data.status === 'ERROR') {
|
if (data.status === 'ERROR') {
|
||||||
throw new Error(data.reason || 'Invoice generation failed');
|
throw new Error(data.reason || 'Invoice generation failed');
|
||||||
}
|
}
|
||||||
@@ -226,23 +224,6 @@ class LightningComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode Lightning invoice (basic parsing)
|
|
||||||
*/
|
|
||||||
parseInvoice(bolt11Invoice) {
|
|
||||||
try {
|
|
||||||
if (!bolt11Invoice.toLowerCase().startsWith('lnbc') && !bolt11Invoice.toLowerCase().startsWith('lntb')) {
|
|
||||||
throw new Error('Invalid Lightning invoice format');
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
bolt11: bolt11Invoice,
|
|
||||||
network: bolt11Invoice.toLowerCase().startsWith('lnbc') ? 'mainnet' : 'testnet'
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Invoice parsing failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify that a Lightning invoice is valid and for the expected amount
|
* Verify that a Lightning invoice is valid and for the expected amount
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
const { v4: uuidv4 } = require('uuid');
|
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const cashu = require('./cashu');
|
const cashu = require('./cashu');
|
||||||
const lightning = require('./lightning');
|
const lightning = require('./lightning');
|
||||||
@@ -116,7 +115,7 @@ class RedemptionComponent {
|
|||||||
* Perform the complete redemption process
|
* Perform the complete redemption process
|
||||||
*/
|
*/
|
||||||
async performRedemption(token, lightningAddress) {
|
async performRedemption(token, lightningAddress) {
|
||||||
const redeemId = uuidv4();
|
const redeemId = crypto.randomUUID();
|
||||||
const tokenHash = this.generateTokenHash(token);
|
const tokenHash = this.generateTokenHash(token);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -268,45 +267,6 @@ class RedemptionComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get redemption status for API response
|
|
||||||
*/
|
|
||||||
getRedemptionStatus(redeemId) {
|
|
||||||
const redemption = this.getRedemption(redeemId);
|
|
||||||
|
|
||||||
if (!redemption) return null;
|
|
||||||
|
|
||||||
const response = {
|
|
||||||
success: true,
|
|
||||||
status: redemption.status,
|
|
||||||
details: {
|
|
||||||
amount: redemption.amount,
|
|
||||||
to: redemption.lightningAddress,
|
|
||||||
paid: redemption.paid,
|
|
||||||
createdAt: redemption.createdAt,
|
|
||||||
updatedAt: redemption.updatedAt
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (redemption.paidAt) response.details.paidAt = redemption.paidAt;
|
|
||||||
if (redemption.fee) response.details.fee = redemption.fee;
|
|
||||||
if (redemption.error) response.details.error = redemption.error;
|
|
||||||
if (redemption.mint) response.details.mint = redemption.mint;
|
|
||||||
if (redemption.domain) response.details.domain = redemption.domain;
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all redemptions (for admin/debugging)
|
|
||||||
*/
|
|
||||||
getAllRedemptions() {
|
|
||||||
return Array.from(this.redemptions.entries()).map(([id, data]) => ({
|
|
||||||
redeemId: id,
|
|
||||||
...data
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clean up old redemptions (should be called periodically)
|
* Clean up old redemptions (should be called periodically)
|
||||||
*/
|
*/
|
||||||
|
|||||||
Generated
+621
-1004
File diff suppressed because it is too large
Load Diff
+2
-3
@@ -33,15 +33,14 @@
|
|||||||
"url": "https://github.com/Michilis"
|
"url": "https://github.com/Michilis"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cashu/cashu-ts": "^3.4.1",
|
"@cashu/cashu-ts": "^4.7.2",
|
||||||
"axios": "^1.8.1",
|
"axios": "^1.8.1",
|
||||||
"bolt11": "^1.4.1",
|
"bolt11": "^1.4.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"swagger-jsdoc": "^6.2.8",
|
"swagger-jsdoc": "^6.2.8",
|
||||||
"swagger-ui-express": "^5.0.1",
|
"swagger-ui-express": "^5.0.1"
|
||||||
"uuid": "^10.0.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^9.9.1",
|
"eslint": "^9.9.1",
|
||||||
|
|||||||
+3
-3
@@ -30,7 +30,7 @@ const cashu = require('../components/cashu');
|
|||||||
* $ref: '#/components/responses/InternalServerError'
|
* $ref: '#/components/responses/InternalServerError'
|
||||||
*/
|
*/
|
||||||
router.post('/decode', async (req, res) => {
|
router.post('/decode', async (req, res) => {
|
||||||
const { token } = req.body;
|
const { token } = req.body ?? {};
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
@@ -48,8 +48,8 @@ router.post('/decode', async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const decoded = await cashu.parseToken(token);
|
const decoded = await cashu.parseToken(token);
|
||||||
const mintUrl = await cashu.getTokenMintUrl(token);
|
const mintUrl = decoded.mint;
|
||||||
|
|
||||||
let spent = false;
|
let spent = false;
|
||||||
try {
|
try {
|
||||||
const spendabilityCheck = await cashu.checkTokenSpendable(token);
|
const spendabilityCheck = await cashu.checkTokenSpendable(token);
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@ const lightning = require('../components/lightning');
|
|||||||
* $ref: '#/components/responses/TooManyRequests'
|
* $ref: '#/components/responses/TooManyRequests'
|
||||||
*/
|
*/
|
||||||
router.post('/validate-address', async (req, res) => {
|
router.post('/validate-address', async (req, res) => {
|
||||||
const { lightningAddress } = req.body;
|
const { lightningAddress } = req.body ?? {};
|
||||||
|
|
||||||
if (!lightningAddress) {
|
if (!lightningAddress) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ const redemption = require('../components/redemption');
|
|||||||
* $ref: '#/components/responses/InternalServerError'
|
* $ref: '#/components/responses/InternalServerError'
|
||||||
*/
|
*/
|
||||||
router.post('/redeem', async (req, res) => {
|
router.post('/redeem', async (req, res) => {
|
||||||
const { token, lightningAddress } = req.body;
|
const { token, lightningAddress } = req.body ?? {};
|
||||||
|
|
||||||
const validation = await redemption.validateRedemptionRequest(token, lightningAddress);
|
const validation = await redemption.validateRedemptionRequest(token, lightningAddress);
|
||||||
|
|
||||||
|
|||||||
@@ -33,18 +33,6 @@ app.use(cors({
|
|||||||
optionsSuccessStatus: 200
|
optionsSuccessStatus: 200
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Additional middleware for Swagger UI preflight requests
|
|
||||||
app.use((req, res, next) => {
|
|
||||||
if (req.method === 'OPTIONS') {
|
|
||||||
res.header('Access-Control-Allow-Origin', '*');
|
|
||||||
res.header('Access-Control-Allow-Methods', 'GET', 'POST', 'OPTIONS');
|
|
||||||
res.header('Access-Control-Allow-Headers', 'Content-Type', 'Authorization', 'Accept', 'Origin', 'X-Requested-With');
|
|
||||||
res.status(200).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Debug endpoint to test CORS
|
// Debug endpoint to test CORS
|
||||||
app.get('/api/cors-test', (req, res) => {
|
app.get('/api/cors-test', (req, res) => {
|
||||||
res.json({
|
res.json({
|
||||||
@@ -93,7 +81,7 @@ const rateLimitMap = new Map();
|
|||||||
const RATE_LIMIT = parseInt(process.env.RATE_LIMIT) || 100;
|
const RATE_LIMIT = parseInt(process.env.RATE_LIMIT) || 100;
|
||||||
|
|
||||||
function rateLimit(req, res, next) {
|
function rateLimit(req, res, next) {
|
||||||
const clientId = req.ip || req.connection.remoteAddress;
|
const clientId = req.ip || req.socket.remoteAddress;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const windowStart = now - 60000;
|
const windowStart = now - 60000;
|
||||||
|
|
||||||
@@ -159,7 +147,7 @@ app.use('/api', lightningRoutes);
|
|||||||
app.use('/api', healthRoutes);
|
app.use('/api', healthRoutes);
|
||||||
|
|
||||||
// 404 handler
|
// 404 handler
|
||||||
app.use('*', (req, res) => {
|
app.use((req, res) => {
|
||||||
res.status(404).json({
|
res.status(404).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Endpoint not found'
|
error: 'Endpoint not found'
|
||||||
|
|||||||
Reference in New Issue
Block a user