diff --git a/handler.js b/handler.js index f6863b9..217ef69 100644 --- a/handler.js +++ b/handler.js @@ -97,6 +97,19 @@ const getHistoryHandler = async (event) => { return addHeader(result); }; +const { getQuote, getFiatCurrencyLimits } = require("./src/api/fiatOnRamp"); +const getQuoteHandler = async (event) => { + const bodyParams = JSON.parse(event.body ?? {}); + const result = await getQuote(bodyParams); + return addHeader(result); +}; +const getFiatCurrencyLimitsHandler = async (event) => { + const { queryStringParameters } = event; + const queryParams = queryStringParameters ? queryStringParameters : {}; + const result = await getFiatCurrencyLimits(queryParams); + return addHeader(result); +}; + module.exports = { pairsUpdaterHandler, getAccountBalanceChartHandler, @@ -108,6 +121,8 @@ module.exports = { getHistoryHandler, getAccountTransactionHistoryHandler, getPerformanceSummaryHandler, + getQuoteHandler, + getFiatCurrencyLimitsHandler, }; // module.exports.hello = async (event) => { diff --git a/package-lock.json b/package-lock.json index 38d0005..19e3eea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@kadena/cryptography-utils": "^0.4.2", "axios": "^1.2.2", "dotenv": "^16.0.3", + "jsonwebtoken": "^9.0.2", "luxon": "^3.2.1", "node-fetch": "^3.3.0", "pg": "^8.8.0", @@ -6390,6 +6391,11 @@ "node": "*" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", @@ -7317,6 +7323,14 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -9057,6 +9071,27 @@ "node": "*" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -9095,6 +9130,25 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/jwt-decode": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz", @@ -9187,17 +9241,46 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, "node_modules/lodash.isempty": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==", "dev": true }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "peer": true + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, "node_modules/lodash.union": { "version": "4.6.0", @@ -17131,6 +17214,11 @@ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==" }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", @@ -17828,6 +17916,14 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -19059,6 +19155,23 @@ "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==" }, + "jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + } + }, "jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -19099,6 +19212,25 @@ } } }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "jwt-decode": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz", @@ -19190,17 +19322,46 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, "lodash.isempty": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==", "dev": true }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, "lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "peer": true + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, "lodash.union": { "version": "4.6.0", diff --git a/package.json b/package.json index 35634bd..8065ea3 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@kadena/cryptography-utils": "^0.4.2", "axios": "^1.2.2", "dotenv": "^16.0.3", + "jsonwebtoken": "^9.0.2", "luxon": "^3.2.1", "node-fetch": "^3.3.0", "pg": "^8.8.0", diff --git a/serverless.yml b/serverless.yml index 759cd0b..6191939 100644 --- a/serverless.yml +++ b/serverless.yml @@ -98,6 +98,29 @@ functions: origin: "*" # <-- Specify allowed origin headers: "*" # <-- Specify allowCredentials: false + getQuote: + handler: handler.getQuoteHandler + description: get fiat on ramp quote + events: + - http: + path: /api/fiat-on-ramp/quote + method: post + cors: + origin: "*" # <-- Specify allowed origin + headers: "*" # <-- Specify + allowCredentials: false + getFiatCurrencyLimits: + handler: handler.getFiatCurrencyLimitsHandler + description: get fiat on ramp currency limits + timeout: 200 + events: + - http: + path: /api/fiat-on-ramp/currencies + method: get + cors: + origin: "*" # <-- Specify allowed origin + headers: "*" # <-- Specify + allowCredentials: false config: handler: handler.getConfigHandler description: udf config handler diff --git a/src/api/fiatOnRamp/index.js b/src/api/fiatOnRamp/index.js new file mode 100644 index 0000000..0f994d9 --- /dev/null +++ b/src/api/fiatOnRamp/index.js @@ -0,0 +1,81 @@ +const { providers } = require("./providers"); + +/** + * Get a quote for a specific provider + * @param {*} body + * @returns {Promise<{statusCode: number, body: string}>} + */ +const getQuote = async (body) => { + const { account, fiatCurrency, amountToSpend, cryptoToBuy, provider } = body; + if (!account || !fiatCurrency || !amountToSpend || !provider) { + return { + statusCode: 400, + body: JSON.stringify({ + error: + "Please define params: account, fiatCurrency, amountToSpend, cryptoToBuy, provider", + }), + }; + } + if (!providers[provider]) { + return { + statusCode: 400, + body: JSON.stringify({ + error: "Invalid provider", + }), + }; + } + const result = await providers[provider].getQuote({ + account, + fiatCurrency, + amountToSpend, + cryptoToBuy, + }); + if (result.error) { + return { + statusCode: 400, + body: JSON.stringify({ + error: result.error ?? "Failed to get quote", + }), + }; + } + return { + statusCode: 200, + body: JSON.stringify({ + ...result, + }), + }; +}; + +/** + * Get currency list and limits for a specific provider + * @param {*} queryParams + * @returns {Promise<{statusCode: number, body: string}>} + */ +const getFiatCurrencyLimits = async (queryParams = {}) => { + const { provider } = queryParams; + if (!providers[provider]) { + return { + statusCode: 400, + body: JSON.stringify({ + error: "Invalid provider", + }), + }; + } + const result = await providers[provider].getFiatCurrencyLimits(); + if (result.error) { + return { + statusCode: 400, + body: JSON.stringify({ + error: result.error ?? "Failed to get fiat currency limits", + }), + }; + } + return { + statusCode: 200, + body: JSON.stringify({ + ...result, + }), + }; +}; + +module.exports = { getQuote, getFiatCurrencyLimits }; diff --git a/src/api/fiatOnRamp/providers/index.js b/src/api/fiatOnRamp/providers/index.js new file mode 100644 index 0000000..33e28c5 --- /dev/null +++ b/src/api/fiatOnRamp/providers/index.js @@ -0,0 +1,19 @@ +const { getQuote, getFiatCurrencyLimits } = require("./topper"); + +const providers = { + simplex: { + getQuote: async (body) => { + const { account, fiatCurrency, amountToSpend, cryptoToBuy, provider } = + body; + }, + getFiatCurrencyLimits: (body) => { + const { payment, provider } = body; + }, + }, + topper: { + getQuote, + getFiatCurrencyLimits, + }, +}; + +module.exports = { providers }; diff --git a/src/api/fiatOnRamp/providers/simplex.js b/src/api/fiatOnRamp/providers/simplex.js new file mode 100644 index 0000000..59a1c4b --- /dev/null +++ b/src/api/fiatOnRamp/providers/simplex.js @@ -0,0 +1,3 @@ +/** + * For future simplex implementations + */ diff --git a/src/api/fiatOnRamp/providers/topper.js b/src/api/fiatOnRamp/providers/topper.js new file mode 100644 index 0000000..e8978e7 --- /dev/null +++ b/src/api/fiatOnRamp/providers/topper.js @@ -0,0 +1,146 @@ +const { randomUUID, createPrivateKey } = require("crypto"); +const { promisify } = require("util"); +const jsonwebtoken = require("jsonwebtoken"); +const axios = require("axios"); + +const TOPPER_API_URL = "https://api.sandbox.topperpay.com"; +const TOPPER_CHECKOUT_URL = "https://app.sandbox.topperpay.com"; + +const getTopperPayload = ({ account, amountToSpend, fiatCurrency }) => ({ + jti: randomUUID(), + sub: process.env.FIAT_ON_RAMP_TOPPER_WIDGET_ID, + source: { + amount: amountToSpend.toFixed(2), + asset: fiatCurrency, + }, + target: { + address: account, + asset: "ETH", + network: "ethereum", + label: "Ecko Wallet - Buy Crypto", + }, +}); + +const getTopperBootstrapToken = async ({ + account, + amountToSpend, + fiatCurrency, +}) => { + const payload = getTopperPayload({ account, amountToSpend, fiatCurrency }); + const privateKeyJwk = JSON.parse( + Buffer.from( + process.env.FIAT_ON_RAMP_TOPPER_BASE64_PRIVATE_KEY, + "base64" + ).toString("utf-8") + ); + const sign = promisify(jsonwebtoken.sign); + const privateKey = createPrivateKey({ + format: "jwk", + key: privateKeyJwk, + }); + const options = { + algorithm: "ES256", + keyid: process.env.FIAT_ON_RAMP_TOPPER_KEY_ID, + }; + return await sign(payload, privateKey, options); +}; + +const sendTopperRequest = async ({ method, url, data }) => { + const config = { + method, + url: `${TOPPER_API_URL}${url}`, + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + data: JSON.stringify(data), + }; + try { + const response = await axios.request(config); + return response.data; + } catch (error) { + throw error.response?.data || error.message; + } +}; + +const getQuote = async ({ + account, + amountToSpend, + fiatCurrency, + cryptoToBuy, +}) => { + try { + const bootstrapToken = await getTopperBootstrapToken({ + account, + amountToSpend, + fiatCurrency, + }); + + const response = await sendTopperRequest({ + method: "POST", + url: "/simulations", + data: { bootstrapToken }, + }); + + const freshBootstrapToken = await getTopperBootstrapToken({ + account, + amountToSpend, + fiatCurrency, + }); + + const totalFees = response.simulation.fees + .reduce((total, fee) => total + parseFloat(fee.amount), 0) + ?.toFixed(2); + + return { + account, + fiatCurrency, + fiatTotalAmount: parseFloat(response.simulation.origin.amount).toFixed(2), + fiatBaseAmount: ( + parseFloat(response.simulation.origin.amount) - totalFees + ).toFixed(2), + cryptoCurrency: cryptoToBuy, + cryptoAmount: response.simulation.destination.amount, + totalFees, + bootstrapToken: freshBootstrapToken, + }; + } catch (error) { + return { + error: error || "Failed to get quote from Topper", + }; + } +}; + +const getFiatCurrencyLimits = async () => { + try { + const paymentMethodsResponse = await sendTopperRequest({ + method: "GET", + url: "/payment-methods/crypto-onramp", + }); + const paymentMethodsData = paymentMethodsResponse; + const currencies = paymentMethodsData.paymentMethods.filter( + (method) => method.type === "credit-card" + ); + const assetsResponse = await sendTopperRequest({ + method: "GET", + url: "/assets/crypto-onramp", + }); + + return { + currencies: assetsResponse.assets.source.map((ass) => { + const limit = + currencies.find((c) => c.billingAsset === ass.code) || + currencies.find((c) => c.billingAsset === "USD"); + return { + ...ass, + maximum: limit.limits.find((l) => l.asset === ass.code).maximum, + minimum: limit.limits.find((l) => l.asset === ass.code).minimum, + }; + }), + }; + } catch (error) { + throw error.response?.data || error.message; + } +}; + +module.exports = { getQuote, getFiatCurrencyLimits };