ElementsProject / lightning-charge

A simple drop-in solution for accepting lightning payments
MIT License
553 stars 80 forks source link

Exchange rate should be cached #58

Closed Fi3 closed 5 years ago

Fi3 commented 5 years ago

I donno the bitcoinaverage's rate limit, but sometimes in my test env it happen that I get a 429 that is pretty annoying. Also there is no reason to overload bitcoinaverage of request when they update the ticket only every 15s.

exchange-rate.js could be replaced with:

import big from 'big.js'                                                                                                                                                                                                                                                        
import request from 'superagent'                                                                                                                                                                                                                                                

const FIXED_RATES    = { BTC: 1 }                                                                                                                                                                                                                                               
    , BTC_MSAT_RATIO = big('100000000000')                                                                                                                                                                                                                                      

const enc = encodeURIComponent                                                                                                                                                                                                                                                  

const now = () => Date.now()                                                                                                                                                                                                                                                    

// Fetch current exchange rate from BitcoinAverage                                                                                                                                                                                                                              
const getRate_ = millisecDelta => {                                                                                                                                                                                                                                            
  const cache = {}                                                                                                                                                                                                                                                              
  return (async (currency) => {                                                                                                                                                                                                                                                 
    const cached = cache[currency]                                                                                                                                                                                                                                              
    if (cached && (now() - cached.time) <= millisecDelta) {                                                                                                                                                                                                                     
      return cached.rate                                                                                                                                                                                                                                                        
    }                                                                                                                                                                                                                                                                           
    else {                                                                                                                                                                                                                                                                      
      try {                                                                                                                                                                                                                                                                     
        const rate  = (await request.get(`https://apiv2.bitcoinaverage.com/indices/global/ticker/short?crypto=BTC&fiat=${enc(currency)}`))                                                                                                                                      
          .body['BTC'+currency].last                                                                                                                                                                                                                                            
        cache[currency] = {rate, time: now()}                                                                                                                                                                                                                                   
        return rate                                                                                                                                                                                                                                                             
      }                                                                                                                                                                                                                                                                         
      catch(err) {                                                                                                                                                                                                                                                              
        return Promise.reject(err.status == 404 ? new Error('Unknown currency: '+currency) : err)                                                                                                                                                                               
      }                                                                                                                                                                                                                                                                         
    }                                                                                                                                                                                                                                                                           
  })                                                                                                                                                                                                                                                                            
}                                                                                                                                                                                                                                                                               

// bitcoinaverage update the ticker every 15 seconds                                                                                                                                                                                                                            
// one minute old prices seems resonable?                                                                                                                                                                                                                                       
const cacheDelta = Number.isNaN(parseInt(process.env.CACHE_D)) ? 0 : parseInt(process.env.CACHE_D)                                                                                                                                                                              

const getRate = getRate_(cacheDelta)                                                                                                                                                                                                                                            

// Convert `amount` units of `currency` to msatoshis                                                                                                                                                                                                                            
const toMsat = async (currency, amount) =>                                                                                                                                                                                                                                      
  big(amount)                                                                                                                                                                                                                                                                   
    .div(FIXED_RATES[currency] || await getRate(currency))                                                                                                                                                                                                                      
    .mul(BTC_MSAT_RATIO)                                                                                                                                                                                                                                                        
    .round(0, 3) // round up to nearest msatoshi                                                                                                                                                                                                                                
    .toFixed(0)                                                                                                                                                                                                                                                                 

module.exports = { getRate, toMsat }  

Note that we use env varse to set our cache, for example declared in a docker-compose file. And that there are no breaking changes cause the default behavior is to set the cache to 0ms.

shesek commented 5 years ago

Implemented slightly differently in 25ea644643c67c2b9fe7fcf0ee6f361e168f8c06, with a default TTL of 30 seconds.