diff --git a/src/app.ts b/src/app.ts index 8904ae3..907d233 100644 --- a/src/app.ts +++ b/src/app.ts @@ -32,10 +32,9 @@ export let providerManager: ProviderManager = undefined; export let logger: winston.Logger; async function run() { - const { combine, timestamp, label, printf, prettyPrint } = winston.format; + const { combine, timestamp, printf, prettyPrint } = winston.format; - const myFormat = printf(({ level, message, label, timestamp }) => { - if (label !== undefined) return `${timestamp} ${level} (${label}) ${message}`; + const myFormat = printf(({ level, message, timestamp }) => { return `${timestamp} ${level} ${message}`; }); diff --git a/src/controllers/invoice.ts b/src/controllers/invoice.ts index acf5bbd..369e231 100644 --- a/src/controllers/invoice.ts +++ b/src/controllers/invoice.ts @@ -90,7 +90,7 @@ export async function createInvoice(req: Request, res: Response) { const dueBy = new Date(Date.now() + 1000 * 60 * 15); Invoice.create({ - selector: randomString(128), + selector: randomString(32), paymentMethods, successUrl, cancelUrl, diff --git a/src/helper/backendProvider.ts b/src/helper/backendProvider.ts index c0e7a55..ab35c12 100644 --- a/src/helper/backendProvider.ts +++ b/src/helper/backendProvider.ts @@ -1,3 +1,4 @@ +import { invoiceManager, providerManager } from '../app'; import { IInvoice } from '../models/invoice/invoice.interface'; import { CryptoUnits } from './types'; @@ -34,9 +35,10 @@ export abstract class BackendProvider { /** * Get a transaction from the blockchain. * @param txId Hash of the transcation you're looking for. + * @param context Invoice for context (required to calculate correct amount) * @returns See https://developer.bitcoin.org/reference/rpc/gettransaction.html for reference */ - abstract getTransaction(txId: string): Promise; + abstract getTransaction(txId: string, context?: IInvoice): Promise; /** * Decode a raw transcation that was broadcasted in the network. @@ -66,11 +68,6 @@ export abstract class BackendProvider { */ abstract listener(): void; - /** - * Keep track of unconfirmed transactions. - */ - abstract watchConfirmations(): void; - /** * Provided is an array with pending invoices that have to be check. * @@ -93,21 +90,21 @@ export interface ITransactionDetails { } export interface ITransaction { - amount: number; - fee: number; + id: string; + blockhash: string; + amount: number; // Total transaction amount + fee?: number; confirmations: number; - time: number; // Unix timestamp - details: ITransactionDetails[]; - hex: string; + time: number; // Unix timestamp + details?: ITransactionDetails[]; // In-/and Outputs of an transaction } // Special interface for RPC call `listreceivedbyaddress` export interface ITransactionList { - address: string; - amount: number; + address: string; // Address that performed that action + amount: number; // Amount that got transfered confirmation: number; - label: string; - txids: string[]; + txids?: string[]; } export interface IRawTransaction { diff --git a/src/helper/invoiceManager.ts b/src/helper/invoiceManager.ts index 223ce55..c588d21 100644 --- a/src/helper/invoiceManager.ts +++ b/src/helper/invoiceManager.ts @@ -21,15 +21,24 @@ export class InvoiceManager { logger.info(`There are ${invoices.length} invoices that are pending or unconfirmed`); invoices.forEach(invoice => { + if (invoice.status === PaymentStatus.DONE || invoice.status === PaymentStatus.CANCELLED) { + this.removeInvoice(invoice); + return; + } + + if (invoice.status === PaymentStatus.PENDING) { this.pendingInvoices.push(invoice); } + if (invoice.status === PaymentStatus.UNCONFIRMED) { this.unconfirmedTranscations.push(invoice); } + providerManager.getProvider(invoice.paymentMethod).validateInvoice(invoice); }); }); this.expireScheduler(); + this.watchConfirmations(); } /** - * This function is basicly close all invoices that have not been paid in time. + * This function will basicly close all invoices that have not been paid in time. */ private expireScheduler() { setInterval(async () => { @@ -144,8 +153,26 @@ export class InvoiceManager { } } + /** + * This mehtod, once started, will check every n-seconds if the confirmation + * count of one unconfirmed transcation has changed. + */ + async watchConfirmations() { + setInterval(() => { + this.unconfirmedTranscations.forEach(async invoice => { + const transcation = invoice.transcationHash; + + const provider = providerManager.getProvider(invoice.paymentMethod); + const tx = await provider.getTransaction(transcation); + this.setConfirmationCount(invoice, tx.confirmations); + }); + }, 2_000); + } + /** * This method checks if a payment has been made in time and that the right amount was sent. + * + * **Only issue this method in the moment the payment has been made.** */ async validatePayment(invoice: IInvoice, tx: string): Promise { if (invoice.dueBy.getTime() < Date.now() && invoice.status <= PaymentStatus.PENDING && invoice.status >= PaymentStatus.REQUESTED) { @@ -155,16 +182,13 @@ export class InvoiceManager { return; // Payment is too late } - const txInfo = await providerManager.getProvider(invoice.paymentMethod).getTransaction(tx); - const receivedTranscation = txInfo.details.find(detail => { - return (detail.address == invoice.receiveAddress && detail.amount > 0); // We only want receiving transactions - }); + const txInfo = await providerManager.getProvider(invoice.paymentMethod).getTransaction(tx, invoice); const price = this.getPriceByInvoice(invoice); - if (price === undefined) return; + if (price === 0) return; - // Transaction sent enough funds - if (receivedTranscation.amount == price || receivedTranscation.amount > price) { + // Sent enough funds + if (txInfo.amount == price || txInfo.amount > price) { invoice.transcationHash = tx; await invoice.save(); @@ -176,40 +200,12 @@ export class InvoiceManager { * know who the original sender was. Therefore if a customer sent not the right amount, he/she * should contact the support of the shop. */ - logger.warning(`Transaction (${tx}) did not sent requested funds. (sent: ${receivedTranscation.amount} BTC, requested: ${price} BTC)`); + logger.warning(`Transaction (${tx}) did not sent requested funds. (sent: ${txInfo.amount}, requested: ${price})`); invoice.status = PaymentStatus.TOOLITTLE; await invoice.save(); return; - - // This is dead code and only exists because I'm yet unsure what to do with such payments. - let sendBack = receivedTranscation.amount; - - // If the amount was too much, mark invoice as paid and try to send remaining funds back. - if (receivedTranscation.amount > price) { - sendBack = price - txInfo.amount; - - // Sent amount was too much but technically the bill is paid (will get saved in upgradeInvoice) - invoice.transcationHash = tx; - - this.upgradeInvoice(invoice); - } - - // We only have one input, we can be sure that the sender will receive the funds - if (txInfo.details.length === 1) { - if (txInfo.details[0].address.length !== 1) return; - - const txBack = await providerManager.getProvider(invoice.paymentMethod).sendToAddress(txInfo.details[0].address[0], receivedTranscation.amount, null, null, true); - logger.info(`Sent ${receivedTranscation.amount} ${invoice.paymentMethod} back to ${txInfo.details[0].address[0]}: ${txBack}`); - } else { - // If we cannot send the funds back, save transaction id and mark invoice as failed. - invoice.transcationHash = tx; - invoice.status = PaymentStatus.TOOLITTLE; - this.removeInvoice(invoice); - - await invoice.save(); - } } } } \ No newline at end of file diff --git a/src/helper/providerManager.ts b/src/helper/providerManager.ts index 985c0e4..c486c03 100644 --- a/src/helper/providerManager.ts +++ b/src/helper/providerManager.ts @@ -1,7 +1,7 @@ import { readdirSync } from 'fs'; import { join } from 'path'; import { config } from '../../config'; -import { invoiceManager, logger } from '../app'; +import { invoiceManager, logger, providerManager } from '../app'; import { BackendProvider } from './backendProvider'; import { CryptoUnits } from './types'; @@ -57,15 +57,16 @@ export class ProviderManager { /** * This provider will be no longer be used. */ - disable(providerFor: CryptoUnits) { - if (!this.cryptoProvider.has(providerFor)) { - return; - } - - const provider = this.getProvider(providerFor); - logger.warn(`Provider "${provider.NAME}" will be disabled ...`); - - this.cryptoProvider.delete(providerFor); + disable(name: string) { + this.cryptoProvider.forEach(provider => { + if (provider.NAME === name) { + // Disable all coins that are supported by this provider. + provider.CRYPTO.forEach(crypto => { + this.cryptoProvider.delete(crypto); + }); + logger.warning(`Provider "${provider.NAME}" is now disabled.`); + } + }); } } \ No newline at end of file diff --git a/src/helper/providers/bitcoinCore.ts b/src/helper/providers/bitcoinCore.ts index 3ace3eb..91c9686 100644 --- a/src/helper/providers/bitcoinCore.ts +++ b/src/helper/providers/bitcoinCore.ts @@ -3,7 +3,7 @@ import { Subscriber } from 'zeromq'; import * as rpc from 'jayson'; import { invoiceManager, logger } from '../../app'; import { IInvoice } from '../../models/invoice/invoice.interface'; -import { BackendProvider, IRawTransaction, ITransaction, ITransactionList } from '../backendProvider'; +import { BackendProvider, IRawTransaction, ITransaction, ITransactionDetails, ITransactionList } from '../backendProvider'; import { CryptoUnits, PaymentStatus } from '../types'; export class Provider implements BackendProvider { @@ -29,11 +29,8 @@ export class Provider implements BackendProvider { }); this.listener(); - this.watchConfirmations(); return true; - - //logger.info('The Bitcoin Core backend is now available!'); } async getNewAddress(): Promise { @@ -49,15 +46,36 @@ export class Provider implements BackendProvider { }); } - async getTransaction(txId: string): Promise { + async getTransaction(txId: string, context?: IInvoice): Promise { return new Promise((resolve, reject) => { this.rpcClient.request('gettransaction', [txId], (err, message) => { if (err) { reject(err); return; } + + // Calculate received funds + const details: ITransactionDetails[] = message.result.details; + let amount = 0; + + if (context !== undefined) { + for (let i = 0; i < details.length; i++) { + if (details[i].category == 'receive' && details[i].address == context.receiveAddress) { + amount += details[i].amount; + } + } + } + + const ret: ITransaction = { + id: message.result.txid, + amount, + blockhash: message.result.blockhash, + confirmations: message.result.confirmations, + time: message.result.time, + fee: message.result.fee + } - resolve(message.result); + resolve(ret); }); }); } @@ -107,7 +125,6 @@ export class Provider implements BackendProvider { logger.debug(`${output.scriptPubKey.addresses} <-> ${invoice.receiveAddress}`); // We found our transaction (https://developer.bitcoin.org/reference/rpc/decoderawtransaction.html) if (output.scriptPubKey.addresses.indexOf(invoice.receiveAddress) !== -1) { - const senderAddress = output.scriptPubKey.addresses[output.scriptPubKey.addresses.indexOf(invoice.receiveAddress)]; logger.info(`Transcation for invoice ${invoice.id} received! (${tx.hash})`); // Change state in database @@ -118,21 +135,8 @@ export class Provider implements BackendProvider { } } - - async watchConfirmations() { - setInterval(() => { - invoiceManager.getUnconfirmedTransactions().filter(item => { return item.paymentMethod === CryptoUnits.BITCOIN }).forEach(async invoice => { - if (invoice.transcationHash.length === 0) return; - const transcation = invoice.transcationHash; - - const tx = await this.getTransaction(transcation); - invoiceManager.setConfirmationCount(invoice, tx.confirmations); - }); - }, 2_000); - } async validateInvoice(invoice: IInvoice) { - if (invoice.status === PaymentStatus.DONE || invoice.status === PaymentStatus.CANCELLED) return; if (invoice.paymentMethod !== CryptoUnits.BITCOIN) return; this.rpcClient.request('listreceivedbyaddress', [0, false, false, invoice.receiveAddress], async (err, message) => { diff --git a/src/helper/providers/dogecoinCore.ts b/src/helper/providers/dogecoinCore.ts index 92617f7..1fbfca7 100644 --- a/src/helper/providers/dogecoinCore.ts +++ b/src/helper/providers/dogecoinCore.ts @@ -29,7 +29,6 @@ export class Provider implements BackendProvider { }); this.listener(); - this.watchConfirmations(); return true; @@ -49,15 +48,34 @@ export class Provider implements BackendProvider { }); } - async getTransaction(txId: string): Promise { + async getTransaction(txId: string, context?: IInvoice): Promise { return new Promise((resolve, reject) => { this.rpcClient.request('gettransaction', [txId], (err, message) => { if (err) { reject(err); return; } + + // Calculate received funds + const details: ITransactionDetails[] = message.result.details; + let amount = 0; + + details.forEach(detail => { + if (detail.category === 'receive' && detail.address === context.receiveAddress) { + amount += detail.amount; + } + }) + + const ret: ITransaction = { + id: message.result.txid, + amount, + blockhash: message.result.blockhash, + confirmations: message.result.confirmations, + time: message.result.time, + fee: message.result.fee + } - resolve(message.result); + resolve(ret); }); }); } @@ -118,21 +136,8 @@ export class Provider implements BackendProvider { } } - - async watchConfirmations() { - setInterval(() => { - invoiceManager.getUnconfirmedTransactions().filter(item => { return item.paymentMethod === CryptoUnits.DOGECOIN }).forEach(async invoice => { - if (invoice.transcationHash.length === 0) return; - const transcation = invoice.transcationHash; - - const tx = await this.getTransaction(transcation); - invoiceManager.setConfirmationCount(invoice, tx.confirmations); - }); - }, 2_000); - } async validateInvoice(invoice: IInvoice) { - if (invoice.status === PaymentStatus.DONE || invoice.status === PaymentStatus.CANCELLED) return; if (invoice.paymentMethod !== CryptoUnits.DOGECOIN) return; this.rpcClient.request('listreceivedbyaddress', [0, false, false], async (err, message) => { diff --git a/src/helper/providers/litecoinCore.ts b/src/helper/providers/litecoinCore.ts index bd18752..3ee57ef 100644 --- a/src/helper/providers/litecoinCore.ts +++ b/src/helper/providers/litecoinCore.ts @@ -3,7 +3,7 @@ import { Subscriber } from 'zeromq'; import * as rpc from 'jayson'; import { invoiceManager, logger } from '../../app'; import { IInvoice } from '../../models/invoice/invoice.interface'; -import { BackendProvider, IRawTransaction, ITransaction, ITransactionList } from '../backendProvider'; +import { BackendProvider, IRawTransaction, ITransaction, ITransactionDetails, ITransactionList } from '../backendProvider'; import { CryptoUnits, PaymentStatus } from '../types'; export class Provider implements BackendProvider { @@ -29,7 +29,6 @@ export class Provider implements BackendProvider { }); this.listener(); - this.watchConfirmations(); return true; } @@ -47,15 +46,34 @@ export class Provider implements BackendProvider { }); } - async getTransaction(txId: string): Promise { + async getTransaction(txId: string, context?: IInvoice): Promise { return new Promise((resolve, reject) => { this.rpcClient.request('gettransaction', [txId], (err, message) => { if (err) { reject(err); return; } + + // Calculate received funds + const details: ITransactionDetails[] = message.result.details; + let amount = 0; + + details.forEach(detail => { + if (detail.category === 'receive' && detail.address === context.receiveAddress) { + amount += detail.amount; + } + }) + + const ret: ITransaction = { + id: message.result.txid, + amount, + blockhash: message.result.blockhash, + confirmations: message.result.confirmations, + time: message.result.time, + fee: message.result.fee + } - resolve(message.result); + resolve(ret); }); }); } @@ -116,18 +134,6 @@ export class Provider implements BackendProvider { } } - - async watchConfirmations() { - setInterval(() => { - invoiceManager.getUnconfirmedTransactions().filter(item => { return item.paymentMethod === CryptoUnits.LITECOIN }).forEach(async invoice => { - if (invoice.transcationHash.length === 0) return; - const transcation = invoice.transcationHash; - - const tx = await this.getTransaction(transcation); - invoiceManager.setConfirmationCount(invoice, tx.confirmations); - }); - }, 2_000); - } async validateInvoice(invoice: IInvoice) { if (invoice.status === PaymentStatus.DONE || invoice.status === PaymentStatus.CANCELLED) return; diff --git a/src/helper/providers/moneroCLI.js b/src/helper/providers/moneroCLI.js deleted file mode 100644 index abbe058..0000000 --- a/src/helper/providers/moneroCLI.js +++ /dev/null @@ -1,85 +0,0 @@ -import * as rpc from 'jayson'; -import { Subscriber } from 'zeromq'; - -import { logger, providerManager } from '../../app'; -import { BackendProvider, ITransaction } from '../backendProvider'; -import { CryptoUnits } from '../types'; - -export class Provider implements BackendProvider { - - private sock: Subscriber; - private rpcClient: rpc.HttpClient; - - NAME = 'Monero CLI'; - DESCRIPTION = 'This provider queries the Monero daemon running on your computer'; - AUTHOR = 'LibrePay Team'; - VERSION = '1.0'; - CRYPTO = CryptoUnits.MONERO; - - onEnable() { - logger.info('Monero CLI provider is now availabe!'); - - if (process.env.MONERO_WALLET_PASSWORD === undefined) { - logger.error(`Enviroment variable MONERO_WALLET_PASSWORD is required but not set!`); - return false; - } - - if (process.env.MONERO_WALLET_NAME === undefined) { - logger.error(`Enviroment variable MONERO_WALLET_FILEPATH is required but not set!`); - return false; - } - - if (process.env.MONERO_RPC_ADDRESS === undefined) { - logger.error(`Enviroment variable MONERO_RPC_ADDRESS is required but not set!`); - return false; - } - - this.rpcClient = rpc.Client.http({ - port: 18082, - version: 2, - auth: 'admin:admin' - }); - this.rpcClient.request('open_wallet', { - filename: process.env.MONERO_WALLET_NAME, - password: process.env.MONERO_WALLET_PASSWORD - }, (err, message) => { - if (err) { - logger.error(`Failed to open Monero wallet: ${err.message}\nMaybe a wrong password or path?`); - providerManager.disable(this.CRYPTO); - - return; - } - console.log(message); - }); - - this.listener(); - - return true; - } - - listener() { - this.sock = new Subscriber(); - this.sock.connect(process.env.MONERO_RPC_ADDRESS); - this.sock.subscribe('rawtx'); - } - - // Since we can safely use the same address everytime, we just need to return a address - // with an integrated payment id. - getNewAddress(): Promise { - return new Promise((resolve, reject) => { - const account_index = Number(process.env.MONERO_WALLET_ACCOUNT_INDEX) || 0; - this.rpcClient.request('make_integrated_address', {}, async (err, message) => { - if (err) { - reject(err); - return; - } - - resolve(message.result.integrated_address); - }); - }); - } - - async getTransaction(txId: string): Promise { - - } -} \ No newline at end of file diff --git a/src/helper/providers/moneroCLI.ts b/src/helper/providers/moneroCLI.ts new file mode 100644 index 0000000..f44065c --- /dev/null +++ b/src/helper/providers/moneroCLI.ts @@ -0,0 +1,217 @@ +import * as rpc from 'jayson'; +import { Subscriber } from 'zeromq'; + +import { invoiceManager, logger } from '../../app'; +import { IInvoice } from '../../models/invoice/invoice.interface'; +import { Invoice } from '../../models/invoice/invoice.model'; +import { BackendProvider, ITransaction } from '../backendProvider'; +import { CryptoUnits } from '../types'; + +export class Provider implements BackendProvider { + + private sock: Subscriber; + private rpcClient: rpc.HttpClient; + + NAME = 'Monero RPC Wallet'; + DESCRIPTION = 'This provider queries the Monero daemon running on your computer'; + AUTHOR = 'LibrePay Team'; + VERSION = '0.1'; + CRYPTO = [CryptoUnits.MONERO]; + + onEnable() { + if (process.env.MONERO_WALLET_PASSWORD === undefined) { + logger.error(`Enviroment variable MONERO_WALLET_PASSWORD is required but not set!`); + return false; + } + + if (process.env.MONERO_WALLET_NAME === undefined) { + logger.error(`Enviroment variable MONERO_WALLET_FILEPATH is required but not set!`); + return false; + } + + if (process.env.MONERO_ZMQ_ADDRESS === undefined) { + logger.error(`Enviroment variable MONERO_ZMQ_ADDRESS is required but not set!`); + return false; + } + + this.rpcClient = rpc.Client.http({ + path: '/json_rpc', + port: 38085 + }); + this.rpcClient.request('open_wallet', { + filename: process.env.MONERO_WALLET_NAME, + password: process.env.MONERO_WALLET_PASSWORD + }, (err, message) => { + if (err) { + console.log(err); + logger.error(`Failed to open Monero wallet: ${err}\nMaybe a wrong password or path?`); + return; + } + }); + + this.listener(); + + return true; + } + + async listener() { + // Since we can't really use the ZeroMQ interface, we have to query every n-seconds. + // Technically there is a ZeroMQ interface but there is almost no to zero documentation for it. + setInterval(() => { + invoiceManager.getPendingInvoices().forEach(async invoice => { + if (invoice.paymentMethod !== CryptoUnits.MONERO) return; + + const tx = await this.getPaymentById(((await this.splitAddress(invoice.receiveAddress)).paymentId)); + if (tx === null) { + return; + } + + logger.info(`Transcation for invoice ${invoice.id} received!`); + invoiceManager.validatePayment(invoice, tx.id); + }); + }, 5_000); + } + + // Since we can safely use the same address everytime, we just need to return a address + // with an integrated payment id. + getNewAddress(): Promise { + return new Promise((resolve, reject) => { + const account_index = Number(process.env.MONERO_WALLET_ACCOUNT_INDEX) || 0; + this.rpcClient.request('make_integrated_address', {}, async (err, message) => { + if (err) { + reject(err); + return; + } + + resolve(message.result.integrated_address); + }); + }); + } + + /** + * @returns If a payment has not been made yet, `null` will be returned. + */ + async getTransaction(txid: string, context?: IInvoice): Promise { + return new Promise(async (resolve, reject) => { + // We're still missing the confirmation count, since we don't get it with this function. + this.rpcClient.request('get_transfer_by_txid', { txid }, async (err, message) => { + if (err) { + reject(err); + return; + } + + const paymentTransaction = message.result.transfer; + if (paymentTransaction === undefined) { + console.log(message) + logger.warning(`Tried to get transfer by txid but failed: ${message}`); + resolve(null); + return; + } + + // Renaming properties to make them fit into interface. + const ret: ITransaction = { + id: paymentTransaction.txid, + blockhash: paymentTransaction.txid, + amount: this.decimalToFloat(paymentTransaction.amount), + confirmations: paymentTransaction.confirmations, + time: paymentTransaction.timestamp, + fee: paymentTransaction.fee + }; + resolve(ret); + }); + }); + } + + sendToAddress( + recipient: string, + amount: number, + comment?: string, + commentTo?: string, + subtractFeeFromAmount?: boolean): Promise { + return new Promise((resolve, reject) => { + const account_index = Number(process.env.MONERO_WALLET_ACCOUNT_INDEX) || 0; + this.rpcClient.request('transfer', { destinations: [{ amount, address: recipient }] }, async (err, message) => { + if (err) { + reject(err); + return; + } + + logger.debug(`[Monero] Transaction has been made: ${message.result.tx_hash}`); + resolve(message.result.tx_hash); + }); + }); + } + + async validateInvoice(invoice: IInvoice) { + if (invoice.paymentMethod !== CryptoUnits.MONERO) return; + + const split = await this.splitAddress(invoice); + if (split === null) { + return; + } + + const transaction = await this.getPaymentById(split.paymentId); + + if (transaction === null) { + return; // Transaction has not been yet made. + } + + invoiceManager.validatePayment(invoice, transaction.blockhash); + /*this.rpcClient.request('get_payments', { payment_id }, async (err, message) => { + if (err) { + logger.error(`[Monero] There was an error while gettings payments of ${payment_id}: ${err}`); + return; + } + + const payment = message.result.payments[0]; + invoiceManager.validatePayment(invoice, payment.tx_hash); + });*/ + } + + private getPaymentById(payment_id: string): Promise { + return new Promise(resolve => { + this.rpcClient.request('get_payments', { payment_id: payment_id }, async (err, message) => { + if (err) { + resolve(null); + return; + } + + console.log(payment_id, message); + + // The payment has not been made yet + if (message.result.payments === undefined) { + resolve(null); + return; + } + + resolve(await this.getTransaction(message.result.payments[0].tx_hash)); + }); + }) + } + + /** + * This method will take the full receive address and will return the payment id and orignal address. + * @returns Will return `null` if input was invalid. + */ + private splitAddress(context: IInvoice | string): Promise<{ address: string, paymentId: string } | null> { + return new Promise(resolve => { + const address = typeof(context) === 'string' ? context : context.receiveAddress; + this.rpcClient.request('split_integrated_address', { integrated_address: address }, async (err, message) => { + if (err) { + logger.error(`[Monero] There was an error while splitting the address ${address}: ${err}`); + resolve(null); + return; + } + resolve({ paymentId: message.result.payment_id, address: message.result.standard_address }); + }); + }) + } + + /** + * When querying the Monero RPC wallet we get full decimals back instead of floats. Maybe because + * floats can be a hussle sometimes. Anyway, we have to convert them back into the original format. + */ + private decimalToFloat(int: number) { + return int / 1000000000000; + } +} \ No newline at end of file diff --git a/src/models/invoice/invoice.schema.ts b/src/models/invoice/invoice.schema.ts index fc29f47..f2d2470 100644 --- a/src/models/invoice/invoice.schema.ts +++ b/src/models/invoice/invoice.schema.ts @@ -19,7 +19,7 @@ const schemaPaymentMethods = new Schema({ }, { _id: false }); const schemaInvoice = new Schema({ - selector: { type: String, length: 128, required: true }, + selector: { type: String, length: 32, required: true }, paymentMethods: [{ type: schemaPaymentMethods, required: true }], paymentMethod: { type: String, enum: Object.values(CryptoUnits), required: false }, receiveAddress: { type: String, required: false },