147 lines
4.6 KiB
JavaScript
147 lines
4.6 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const redemption = require('../components/redemption');
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/redeem:
|
|
* post:
|
|
* summary: Redeem a Cashu token to Lightning address
|
|
* description: |
|
|
* Redeem a Cashu token to a Lightning address (optional - uses default if not provided).
|
|
*
|
|
* The redemption process includes:
|
|
* 1. Token validation and parsing
|
|
* 2. Getting exact melt quote from mint to determine precise fees
|
|
* 3. Invoice creation for net amount (token amount - exact fees)
|
|
* 4. Spendability checking at the mint
|
|
* 5. Token melting and Lightning payment
|
|
*
|
|
* **Important**: The system gets the exact fee from the mint before creating the invoice.
|
|
* The `invoiceAmount` field shows the actual amount sent to the Lightning address.
|
|
* No sats are lost to fee estimation errors.
|
|
* tags: [Token Operations]
|
|
* requestBody:
|
|
* required: true
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* $ref: '#/components/schemas/RedeemRequest'
|
|
* responses:
|
|
* 200:
|
|
* description: Token redeemed successfully
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* $ref: '#/components/schemas/RedeemResponse'
|
|
* 400:
|
|
* $ref: '#/components/responses/BadRequest'
|
|
* 409:
|
|
* description: Token already spent
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* success:
|
|
* type: boolean
|
|
* example: false
|
|
* error:
|
|
* type: string
|
|
* example: "This token has already been spent and cannot be redeemed again"
|
|
* errorType:
|
|
* type: string
|
|
* example: "token_already_spent"
|
|
* 422:
|
|
* description: Insufficient funds or unprocessable token
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* success:
|
|
* type: boolean
|
|
* example: false
|
|
* error:
|
|
* type: string
|
|
* example: "Token amount is insufficient to cover the minimum fee"
|
|
* errorType:
|
|
* type: string
|
|
* example: "insufficient_funds"
|
|
* 429:
|
|
* $ref: '#/components/responses/TooManyRequests'
|
|
* 500:
|
|
* $ref: '#/components/responses/InternalServerError'
|
|
*/
|
|
router.post('/redeem', async (req, res) => {
|
|
const { token, lightningAddress } = req.body;
|
|
|
|
const validation = await redemption.validateRedemptionRequest(token, lightningAddress);
|
|
|
|
if (!validation.valid) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: validation.errors.join(', ')
|
|
});
|
|
}
|
|
|
|
try {
|
|
const result = await redemption.performRedemption(token, lightningAddress);
|
|
|
|
if (result.success) {
|
|
const response = {
|
|
success: true,
|
|
paid: result.paid,
|
|
amount: result.amount,
|
|
invoiceAmount: result.invoiceAmount,
|
|
to: result.to,
|
|
fee: result.fee,
|
|
actualFee: result.actualFee,
|
|
netAmount: result.netAmount,
|
|
mint_url: result.mint,
|
|
format: result.format
|
|
};
|
|
|
|
if (result.usingDefaultAddress) {
|
|
response.usingDefaultAddress = true;
|
|
response.message = `Redeemed to default Lightning address: ${result.to}`;
|
|
}
|
|
|
|
if (result.preimage) {
|
|
response.preimage = result.preimage;
|
|
}
|
|
|
|
res.json(response);
|
|
} else {
|
|
let statusCode = 400;
|
|
|
|
if (result.error && (
|
|
result.error.includes('cannot be redeemed') ||
|
|
result.error.includes('already been used') ||
|
|
result.error.includes('not spendable') ||
|
|
result.error.includes('already spent') ||
|
|
result.error.includes('invalid proofs')
|
|
)) {
|
|
statusCode = 409;
|
|
} else if (result.error && result.error.includes('insufficient')) {
|
|
statusCode = 422;
|
|
}
|
|
|
|
res.status(statusCode).json({
|
|
success: false,
|
|
error: result.error,
|
|
errorType: statusCode === 409 ? 'token_already_spent' :
|
|
statusCode === 422 ? 'insufficient_funds' : 'validation_error'
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Error in redemption:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Internal server error during redemption'
|
|
});
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|