UNPKG

9.65 kBSource Map (JSON)View Raw
1{"version":3,"file":"ecb.js","sourceRoot":"","sources":["ecb.ts"],"names":[],"mappings":";;AAAA,2CAAiC;AACjC,2BAA0B;AAC1B,+CAAoC;AAIpC,uCAAsD;AACtD,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,CAAA;AAE/B,MAAM,SAAS,GAAG,+DAA+D,CAAA;AA4BjF,MAAqB,UAAU;IAmB7B,YAAa,IAAuB,EAAE,GAAoB;QACxD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAA;QAC9B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,SAAS,CAAA;QAChD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAA;QAC7B,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAA;QAG1B,IAAI,CAAC,KAAK,GAAG,EAAE,CAAA;QACf,IAAI,CAAC,UAAU,GAAG,EAAE,CAAA;IACtB,CAAC;IAOD,KAAK,CAAC,OAAO;QACX,IAAI,OAAmB,CAAA;QACvB,IAAI,IAAI,CAAC,QAAQ,EAAE;YACjB,GAAG,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAA;YACpC,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAA;SACxB;aAAM;YACL,GAAG,CAAC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,WAAW,CAAC,CAAA;YAC5C,IAAI,MAAM,GAAG,MAAM,oBAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;YAC7C,OAAO,GAAG,MAAM,gBAAgB,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAA;SACtD;QACD,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAA;QAC1B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAC5B,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACzC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAA;QACtB,GAAG,CAAC,IAAI,CAAC,6BAA6B,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;IAClE,CAAC;IAED,aAAa,CAAE,MAAc;QAC3B,OAAO,IAAI,sBAAS,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IACzC,CAAC;IAED,iBAAiB,CAAE,MAAc;QAC/B,OAAO,IAAI,sBAAS,CAAC,MAAM,CAAC,CAAC,aAAa,CAAC,CAAC,EAAE,sBAAS,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IAChF,CAAC;IASD,KAAK,CAAC,OAAO,CAAE,aAAqB,EAAE,kBAA0B;QAC9D,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAA;QAC9C,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAA;QAExD,IAAI,CAAC,UAAU,EAAE;YACf,GAAG,CAAC,KAAK,CAAC,+DAA+D,EAAE,aAAa,CAAC,CAAA;YACzF,MAAM,IAAI,KAAK,CAAC,6DAA6D,GAAG,aAAa,CAAC,CAAA;SAC/F;QACD,IAAI,CAAC,eAAe,EAAE;YACpB,GAAG,CAAC,KAAK,CAAC,oEAAoE,EAAE,kBAAkB,CAAC,CAAA;YACnG,MAAM,IAAI,KAAK,CAAC,kEAAkE,GAAG,kBAAkB,CAAC,CAAA;SACzG;QAED,MAAM,cAAc,GAAG,UAAU,CAAC,SAAS,CAAA;QAC3C,MAAM,mBAAmB,GAAG,eAAe,CAAC,SAAS,CAAA;QAGrD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;QAC7C,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAA;QAEvD,IAAI,CAAC,UAAU,EAAE;YACf,GAAG,CAAC,KAAK,CAAC,oDAAoD,EAAE,cAAc,CAAC,CAAA;YAC/E,MAAM,IAAI,KAAK,CAAC,8BAA8B,GAAG,cAAc,CAAC,CAAA;SACjE;QAED,IAAI,CAAC,eAAe,EAAE;YACpB,GAAG,CAAC,KAAK,CAAC,yDAAyD,EAAE,mBAAmB,CAAC,CAAA;YACzF,MAAM,IAAI,KAAK,CAAC,8BAA8B,GAAG,mBAAmB,CAAC,CAAA;SACtE;QAQD,MAAM,IAAI,GAAG,IAAI,sBAAS,CAAC,eAAe,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,UAAU,CAAC;aAC9E,GAAG,CAAC,IAAI,sBAAS,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;aAC/D,KAAK,CAAC,IAAI,sBAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;aAC1C,WAAW,CAAC,EAAE,CAAC,CAAA;QAElB,GAAG,CAAC,KAAK,CAAC,kEAAkE,EAAE,aAAa,EAAE,kBAAkB,EAAE,cAAc,EAAE,mBAAmB,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;QAExK,OAAO,MAAM,CAAC,IAAI,CAAC,CAAA;IACrB,CAAC;IAOD,KAAK,CAAC,aAAa;QACjB,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IACnC,CAAC;CACF;AAzHD,6BAyHC;AAED,SAAS,gBAAgB,CAAE,IAAY;IACrC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IACnC,MAAM,OAAO,GAAe,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAA;IACtD,MAAM,CAAC,SAAS,GAAG,CAAC,IAAgB,EAAE,EAAE;QACtC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE;YAChD,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAA;SACpC;QACD,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE;YAC5E,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAA;SAC/D;IACH,CAAC,CAAA;IACD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,CAAC,OAAO,GAAG,MAAM,CAAA;QACvB,MAAM,CAAC,KAAK,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QACrC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAA;IAC5B,CAAC,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import fetchUri from 'node-fetch'\nimport * as sax from 'sax'\nimport BigNumber from 'bignumber.js'\nimport { AccountInfo } from '../types/accounts'\nimport { BackendInstance, BackendServices } from '../types/backend'\n\nimport { create as createLogger } from '../common/log'\nconst log = createLogger('ecb')\n\nconst RATES_API = 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'\n\nexport interface ECBBackendOptions {\n spread: number,\n ratesApiUrl: string,\n mockData: ECBAPIData\n}\n\nexport interface ECBSaxNode {\n name: string,\n attributes: {\n time?: number\n currency?: string\n rate?: number\n }\n}\n\nexport interface ECBAPIData {\n base: string\n date?: number\n rates: {\n [key: string]: number\n }\n}\n\n/**\n * Dummy backend that uses the ECB API for FX rates\n */\nexport default class ECBBackend implements BackendInstance {\n protected spread: number\n protected ratesApiUrl: string\n protected getInfo: (accountId: string) => AccountInfo | undefined\n\n protected rates: {\n [key: string]: number\n }\n protected currencies: string[]\n private mockData: ECBAPIData\n\n /**\n * Constructor.\n *\n * @param opts.spread The spread we will use to mark up the FX rates\n * @param opts.ratesApiUrl The URL for querying the ECB API\n * @param api.getInfo Method which maps account IDs to AccountInfo objects\n * @param api.getAssetCode Method which maps account IDs to asset code\n */\n constructor (opts: ECBBackendOptions, api: BackendServices) {\n this.spread = opts.spread || 0\n this.ratesApiUrl = opts.ratesApiUrl || RATES_API\n this.mockData = opts.mockData\n this.getInfo = api.getInfo\n // this.ratesCacheTtl = opts.ratesCacheTtl || 24 * 3600000\n\n this.rates = {}\n this.currencies = []\n }\n\n /**\n * Get the rates from the API\n *\n * Mock data can be provided for testing purposes\n */\n async connect () {\n let apiData: ECBAPIData\n if (this.mockData) {\n log.info('connect using mock data.')\n apiData = this.mockData\n } else {\n log.info('connect. uri=' + this.ratesApiUrl)\n let result = await fetchUri(this.ratesApiUrl)\n apiData = await parseXMLResponse(await result.text())\n }\n this.rates = apiData.rates\n this.rates[apiData.base] = 1\n this.currencies = Object.keys(this.rates)\n this.currencies.sort()\n log.info('data loaded. numCurrencies=' + this.currencies.length)\n }\n\n _formatAmount (amount: string) {\n return new BigNumber(amount).toFixed(2)\n }\n\n _formatAmountCeil (amount: string) {\n return new BigNumber(amount).decimalPlaces(2, BigNumber.ROUND_CEIL).toFixed(2)\n }\n\n /**\n * Get a rate for the given parameters.\n *\n * @param sourceAccount The account ID of the source account\n * @param destinationAccount The account ID of the next hop account\n * @returns Exchange rate with spread applied\n */\n async getRate (sourceAccount: string, destinationAccount: string) {\n const sourceInfo = this.getInfo(sourceAccount)\n const destinationInfo = this.getInfo(destinationAccount)\n\n if (!sourceInfo) {\n log.error('unable to fetch account info for source account. accountId=%s', sourceAccount)\n throw new Error('unable to fetch account info for source account. accountId=' + sourceAccount)\n }\n if (!destinationInfo) {\n log.error('unable to fetch account info for destination account. accountId=%s', destinationAccount)\n throw new Error('unable to fetch account info for destination account. accountId=' + destinationAccount)\n }\n\n const sourceCurrency = sourceInfo.assetCode\n const destinationCurrency = destinationInfo.assetCode\n\n // Get ratio between currencies and apply spread\n const sourceRate = this.rates[sourceCurrency]\n const destinationRate = this.rates[destinationCurrency]\n\n if (!sourceRate) {\n log.error('no rate available for source currency. currency=%s', sourceCurrency)\n throw new Error('no rate available. currency=' + sourceCurrency)\n }\n\n if (!destinationRate) {\n log.error('no rate available for destination currency. currency=%s', destinationCurrency)\n throw new Error('no rate available. currency=' + destinationCurrency)\n }\n\n // The spread is subtracted from the rate when going in either direction,\n // so that the DestinationAmount always ends up being slightly less than\n // the (equivalent) SourceAmount -- regardless of which of the 2 is fixed:\n //\n // SourceAmount * Rate * (1 - Spread) = DestinationAmount\n //\n const rate = new BigNumber(destinationRate).shiftedBy(destinationInfo.assetScale)\n .div(new BigNumber(sourceRate).shiftedBy(sourceInfo.assetScale))\n .times(new BigNumber(1).minus(this.spread))\n .toPrecision(15)\n\n log.trace('quoted rate. from=%s to=%s fromCur=%s toCur=%s rate=%s spread=%s', sourceAccount, destinationAccount, sourceCurrency, destinationCurrency, rate, this.spread)\n\n return Number(rate)\n }\n\n /**\n * This method is called to allow statistics to be collected by the backend.\n *\n * The ECB backend does not support this functionality.\n */\n async submitPayment () {\n return Promise.resolve(undefined)\n }\n}\n\nfunction parseXMLResponse (data: string): Promise<ECBAPIData> {\n const parser = sax.parser(true, {})\n const apiData: ECBAPIData = { base: 'EUR', rates: {} }\n parser.onopentag = (node: ECBSaxNode) => {\n if (node.name === 'Cube' && node.attributes.time) {\n apiData.date = node.attributes.time\n }\n if (node.name === 'Cube' && node.attributes.currency && node.attributes.rate) {\n apiData.rates[node.attributes.currency] = node.attributes.rate\n }\n }\n return new Promise((resolve, reject) => {\n parser.onerror = reject\n parser.onend = () => resolve(apiData)\n parser.write(data).close()\n })\n}\n"]}
\No newline at end of file