From 927b366d170d1e94be77b20d8e65217b585aae71 Mon Sep 17 00:00:00 2001 From: peachbits Date: Fri, 6 Oct 2023 16:30:27 -0700 Subject: [PATCH 1/2] Replace deprecated currency codes in `SwapCurrencyError` with request --- CHANGELOG.md | 2 + src/swap-helpers.ts | 6 +-- src/swap/changenow.ts | 4 +- src/swap/defi/lifi.ts | 8 +-- src/swap/defi/thorchain.ts | 10 ++-- src/swap/defi/thorchainDa.ts | 17 +++---- src/swap/exolix.ts | 94 ++++++++++++++++++------------------ src/swap/godex.ts | 12 +---- src/swap/letsexchange.ts | 6 +-- src/swap/sideshift.ts | 10 ++-- src/swap/swapuz.ts | 12 ++--- src/swap/transfer.ts | 6 +-- 12 files changed, 80 insertions(+), 107 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 189741f6..b9812f11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- changed: Replace deprecated currency codes in `SwapCurrencyError` with requests + ## 0.21.9 (2023-10-02) - changed: Throw `SwapCurrencyError` if Uniswap-based defi swap providers return zero for `amountToSwap` or `expectedAmountOut` diff --git a/src/swap-helpers.ts b/src/swap-helpers.ts index e8e480f4..17b0200f 100644 --- a/src/swap-helpers.ts +++ b/src/swap-helpers.ts @@ -301,11 +301,7 @@ export function checkInvalidCodes( ) || isSameAsset(request) ) - throw new SwapCurrencyError( - swapInfo, - request.fromCurrencyCode, - request.toCurrencyCode - ) + throw new SwapCurrencyError(swapInfo, request) } export interface CurrencyCodeTranscriptions { diff --git a/src/swap/changenow.ts b/src/swap/changenow.ts index 99813b3e..21e897e7 100644 --- a/src/swap/changenow.ts +++ b/src/swap/changenow.ts @@ -112,7 +112,7 @@ export function makeChangeNowPlugin( const exchangeAmountResponseJson = await exchangeAmountResponse.json() if (exchangeAmountResponseJson.error != null) - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) const { rateId, validUntil } = asExchange(exchangeAmountResponseJson) @@ -164,7 +164,7 @@ export function makeChangeNowPlugin( const marketRangeResponseJson = await marketRangeResponse.json() if (marketRangeResponseJson.error != null) - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) const { minAmount, maxAmount } = asMarketRange(marketRangeResponseJson) diff --git a/src/swap/defi/lifi.ts b/src/swap/defi/lifi.ts index 675b5422..ba39ecaf 100644 --- a/src/swap/defi/lifi.ts +++ b/src/swap/defi/lifi.ts @@ -180,7 +180,7 @@ export function makeLifiPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin { quoteFor } = request if (quoteFor !== 'from') { - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) } const fromToken = fromWallet.currencyConfig.allTokens[fromTokenId ?? ''] @@ -202,7 +202,7 @@ export function makeLifiPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin { } if (fromContractAddress == null || toContractAddress == null) { - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) } const { appId, integrator } = asInitOptions(opts.initOptions) @@ -213,7 +213,7 @@ export function makeLifiPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin { fromWallet.currencyInfo.pluginId === toWallet.currencyInfo.pluginId && request.fromCurrencyCode === request.toCurrencyCode ) { - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) } let lifiServers: string[] = LIFI_SERVERS_DEFAULT @@ -230,7 +230,7 @@ export function makeLifiPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin { MAINNET_CODE_TRANSCRIPTION[toWallet.currencyInfo.pluginId] if (fromMainnetCode == null || toMainnetCode == null) { - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) } const now = Date.now() diff --git a/src/swap/defi/thorchain.ts b/src/swap/defi/thorchain.ts index 5188ca30..8a8e7c05 100644 --- a/src/swap/defi/thorchain.ts +++ b/src/swap/defi/thorchain.ts @@ -317,7 +317,7 @@ export function makeThorchainPlugin( fromWallet.currencyInfo.pluginId === toWallet.currencyInfo.pluginId && request.fromCurrencyCode === request.toCurrencyCode ) { - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) } let midgardServers: string[] = MIDGARD_SERVERS_DEFAULT @@ -342,7 +342,7 @@ export function makeThorchainPlugin( MAINNET_CODE_TRANSCRIPTION[toWallet.currencyInfo.pluginId] if (fromMainnetCode == null || toMainnetCode == null) { - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) } const now = Date.now() @@ -444,7 +444,7 @@ export function makeThorchainPlugin( return asset === `${fromMainnetCode}.${fromCurrencyCode}` }) if (sourcePool == null) { - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) } const [ sourceAsset, @@ -461,7 +461,7 @@ export function makeThorchainPlugin( return asset === `${toMainnetCode}.${toCurrencyCode}` }) if (destPool == null) { - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) } let calcResponse: CalcSwapResponse @@ -556,7 +556,7 @@ export function makeThorchainPlugin( } else { // Cannot yet do tokens on non-EVM chains if (fromMainnetCode !== fromCurrencyCode) { - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) } } diff --git a/src/swap/defi/thorchainDa.ts b/src/swap/defi/thorchainDa.ts index 90fe541f..c2b0de5c 100644 --- a/src/swap/defi/thorchainDa.ts +++ b/src/swap/defi/thorchainDa.ts @@ -145,7 +145,7 @@ export function makeThorchainDaPlugin( fromWallet.currencyInfo.pluginId === toWallet.currencyInfo.pluginId && request.fromCurrencyCode === request.toCurrencyCode ) { - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) } const reverseQuote = quoteFor === 'to' const isEstimate = false @@ -166,7 +166,7 @@ export function makeThorchainDaPlugin( MAINNET_CODE_TRANSCRIPTION[toWallet.currencyInfo.pluginId] if (fromMainnetCode == null || toMainnetCode == null) { - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) } const now = Date.now() @@ -207,7 +207,7 @@ export function makeThorchainDaPlugin( // Get Quote // if (reverseQuote) { - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) } const sellAmount = await fromWallet.nativeToDenomination( @@ -284,14 +284,13 @@ export function makeThorchainDaPlugin( addrObj => !addrObj.halted && addrObj.chain === fromMainnetCode ) if (inAddressObject == null) { - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) } const { router, address: thorAddress } = inAddressObject const { routes } = thorSwapQuote const [thorSwap] = routes - if (thorSwap == null) - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + if (thorSwap == null) throw new SwapCurrencyError(swapInfo, request) const { providers, @@ -303,7 +302,7 @@ export function makeThorchainDaPlugin( const calldata = asCalldata(thorSwap.calldata) if (providers.length <= 1) { - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) } const tcDirect = providers[0] === 'THORCHAIN' @@ -339,7 +338,7 @@ export function makeThorchainDaPlugin( // } // if (customNetworkFee == null || customNetworkFeeKey == null) { - // throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + // throw new SwapCurrencyError(swapInfo, request) // } let memo = calldata.tcMemo ?? calldata.memo ?? '' @@ -399,7 +398,7 @@ export function makeThorchainDaPlugin( } else { // Cannot yet do tokens on non-EVM chains if (fromMainnetCode !== fromCurrencyCode) { - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) } } diff --git a/src/swap/exolix.ts b/src/swap/exolix.ts index b8441935..7dc374a9 100644 --- a/src/swap/exolix.ts +++ b/src/swap/exolix.ts @@ -87,58 +87,58 @@ export function makeExolixPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin { const { fetchCors = io.fetch } = io const { apiKey } = asInitOptions(opts.initOptions) - async function call( - method: 'GET' | 'POST', - route: string, - params: any - ): Promise { - const headers: { [header: string]: string } = { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: `${apiKey}` - } - - let response: Awaited> + const getFixedQuote = async ( + request: EdgeSwapRequestPlugin, + _userSettings: Object | undefined + ): Promise => { + async function call( + method: 'GET' | 'POST', + route: string, + params: any + ): Promise { + const headers: { [header: string]: string } = { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: `${apiKey}` + } - if (method === 'POST') { - const body = JSON.stringify(params) - response = await fetchCors(uri + route, { - method, - headers, - body - }) - } else { - const url = `${uri}${route}?${new URLSearchParams(params).toString()}` - response = await fetchCors(url, { - method, - headers - }) - } + let response: Awaited> + + if (method === 'POST') { + const body = JSON.stringify(params) + response = await fetchCors(uri + route, { + method, + headers, + body + }) + } else { + const url = `${uri}${route}?${new URLSearchParams(params).toString()}` + response = await fetchCors(url, { + method, + headers + }) + } - if ( - !response.ok && - !(await response.text()).includes( - 'Amount to exchange is below the possible min amount to exchange' - ) // HACK: Exolix inconsistently returns a !ok response for a 'from' quote - // under minimum amount, while the status is OK for a 'to' quote under - // minimum amount. - // Handle this inconsistency and ensure parse the proper under min error - // and we don't exit early with the wrong 'unsupported' error message. - ) { - log.warn(`Error retrieving Exolix quote: ${await response.text()}`) - if (response.status === 422) { - throw new SwapCurrencyError(swapInfo, params.coinFrom, params.coinTo) + if ( + !response.ok && + !(await response.text()).includes( + 'Amount to exchange is below the possible min amount to exchange' + ) // HACK: Exolix inconsistently returns a !ok response for a 'from' quote + // under minimum amount, while the status is OK for a 'to' quote under + // minimum amount. + // Handle this inconsistency and ensure parse the proper under min error + // and we don't exit early with the wrong 'unsupported' error message. + ) { + log.warn(`Error retrieving Exolix quote: ${await response.text()}`) + if (response.status === 422) { + throw new SwapCurrencyError(swapInfo, request) + } + throw new Error(`Exolix returned error code ${response.status}`) } - throw new Error(`Exolix returned error code ${response.status}`) - } - return await response.json() - } + return await response.json() + } - const getFixedQuote = async ( - request: EdgeSwapRequestPlugin, - _userSettings: Object | undefined - ): Promise => { const [fromAddress, toAddress] = await Promise.all([ getAddress(request.fromWallet), getAddress(request.toWallet) diff --git a/src/swap/godex.ts b/src/swap/godex.ts index aae2904a..4698b910 100644 --- a/src/swap/godex.ts +++ b/src/swap/godex.ts @@ -126,11 +126,7 @@ export function makeGodexPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin { const response = await fetchCors(url, { method: 'POST', body, headers }) if (!response.ok) { if (response.status === 422) { - throw new SwapCurrencyError( - swapInfo, - request.fromCurrencyCode, - request.toCurrencyCode - ) + throw new SwapCurrencyError(swapInfo, request) } throw new Error(`godex returned error code ${response.status}`) } @@ -217,11 +213,7 @@ export function makeGodexPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin { reply.networks_to?.find(network => network.network === toMainnetCode) == null ) { - throw new SwapCurrencyError( - swapInfo, - request.fromCurrencyCode, - request.toCurrencyCode - ) + throw new SwapCurrencyError(swapInfo, request) } const { promoCode } = opts diff --git a/src/swap/letsexchange.ts b/src/swap/letsexchange.ts index 630b836a..a21633df 100644 --- a/src/swap/letsexchange.ts +++ b/src/swap/letsexchange.ts @@ -116,11 +116,7 @@ export function makeLetsExchangePlugin( const response = await fetchCors(url, { method: 'POST', body, headers }) if (!response.ok) { if (response.status === 422) { - throw new SwapCurrencyError( - swapInfo, - request.fromCurrencyCode, - request.toCurrencyCode - ) + throw new SwapCurrencyError(swapInfo, request) } throw new Error(`letsexchange returned error code ${response.status}`) } diff --git a/src/swap/sideshift.ts b/src/swap/sideshift.ts index 0057202a..321f08dc 100644 --- a/src/swap/sideshift.ts +++ b/src/swap/sideshift.ts @@ -55,7 +55,7 @@ async function checkQuoteError( request: EdgeSwapRequestPlugin, quoteErrorMessage: string ): Promise { - const { fromCurrencyCode, fromWallet, toCurrencyCode } = request + const { fromCurrencyCode, fromWallet } = request if (quoteErrorMessage === 'Amount too low') { const nativeMin = await fromWallet.denominationToNative( @@ -77,7 +77,7 @@ async function checkQuoteError( /method/i.test(quoteErrorMessage) && /disabled/i.test(quoteErrorMessage) ) { - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) } if (/country-blocked/i.test(quoteErrorMessage)) { @@ -150,11 +150,7 @@ const fetchSwapQuoteInner = async ( ) if ('error' in rate) { - throw new SwapCurrencyError( - swapInfo, - request.fromCurrencyCode, - request.toCurrencyCode - ) + throw new SwapCurrencyError(swapInfo, request) } const permissions = asPermissions( diff --git a/src/swap/swapuz.ts b/src/swap/swapuz.ts index 37bb735c..db4e55ae 100644 --- a/src/swap/swapuz.ts +++ b/src/swap/swapuz.ts @@ -119,7 +119,7 @@ export function makeSwapuzPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin { const getRateJson = asApiResponse(asGetRate)(await getRateResponse.json()) if (getRateJson.result == null) - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) const { minAmount } = getRateJson.result @@ -161,7 +161,7 @@ export function makeSwapuzPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin { ) if (createOrderJson.result == null) { - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, request) } const { @@ -245,11 +245,7 @@ export function makeSwapuzPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin { } else { // Exit early if trade isn't like kind assets if (!isLikeKind(fromCurrencyCode, toCurrencyCode)) { - throw new SwapCurrencyError( - swapInfo, - fromCurrencyCode, - toCurrencyCode - ) + throw new SwapCurrencyError(swapInfo, requestTop) } // Must make a copy of the request because this is a shared object // reused between requests to other exchange plugins @@ -281,7 +277,7 @@ export function makeSwapuzPlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin { fromQuoteNativeAmount = mul(diffMultiplier, fromQuoteNativeAmount) } } - throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + throw new SwapCurrencyError(swapInfo, requestTop) } } } diff --git a/src/swap/transfer.ts b/src/swap/transfer.ts index 924b4b5b..5e6c9900 100644 --- a/src/swap/transfer.ts +++ b/src/swap/transfer.ts @@ -70,11 +70,7 @@ export function makeTransferPlugin( request.toWallet.currencyInfo.pluginId || request.fromCurrencyCode !== request.toCurrencyCode ) { - throw new SwapCurrencyError( - swapInfo, - request.fromCurrencyCode, - request.toCurrencyCode - ) + throw new SwapCurrencyError(swapInfo, request) } const newRequest = await getMaxSwappable(fetchSwapQuoteInner, request) From 2ebd1bf2931e7c7331e9df4f18521ab70bcf3886 Mon Sep 17 00:00:00 2001 From: peachbits Date: Fri, 6 Oct 2023 14:32:56 -0700 Subject: [PATCH 2/2] Whitelist plugins for ChangeHero plugin We can't rely on their blockchain names to match ours so we need to whitelist them manually --- CHANGELOG.md | 1 + src/swap/changehero.ts | 56 +++++++++++++++++++++++++++++++++--------- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9812f11..7e8045cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- changed: Restrict ChangeHero trading to whitelisted plugins - changed: Replace deprecated currency codes in `SwapCurrencyError` with requests ## 0.21.9 (2023-10-02) diff --git a/src/swap/changehero.ts b/src/swap/changehero.ts index 93a2ad64..b4c43b48 100644 --- a/src/swap/changehero.ts +++ b/src/swap/changehero.ts @@ -21,14 +21,14 @@ import { import { checkInvalidCodes, - getCodes, + getCodesWithTranscription, getMaxSwappable, InvalidCurrencyCodes, makeSwapPluginQuote, SwapOrder } from '../swap-helpers' import { convertRequest, getAddress } from '../util/utils' -import { EdgeSwapRequestPlugin } from './types' +import { EdgeSwapRequestPlugin, StringMap } from './types' const pluginId = 'changehero' @@ -43,6 +43,33 @@ const asInitOptions = asObject({ apiKey: asString }) +const MAINNET_CODE_TRANSCRIPTION: StringMap = { + ethereum: 'ethereum', + binancesmartchain: 'binance_smart_chain', + solana: 'solana', + algorand: 'algorand', + avalanche: 'avalanche_(c-chain)', + bitcoincash: 'bitcoin_cash', + bitcoinsv: 'bitcoin_sv', + bitcoin: 'bitcoin', + tron: 'tron', + polygon: 'polygon', + dash: 'dash', + digibyte: 'digibyte', + dogecoin: 'doge', + polkadot: 'polkadot', + ethereumclassic: 'ethereum_classic', + optimism: 'optimism', + hedera: 'hedera', + litecoin: 'litecoin', + qtum: 'qtum', + stellar: 'stellar', + monero: 'monero', + ripple: 'ripple', + tezos: 'tezos', + zcash: 'zcash' +} + // See https://changehero.io/currencies for list of supported currencies const INVALID_CURRENCY_CODES: InvalidCurrencyCodes = { from: { @@ -95,11 +122,7 @@ function checkReply( reply.error.code === -32602 || (reply.error.message?.includes('Invalid currency:') ?? false) ) { - throw new SwapCurrencyError( - swapInfo, - request.fromCurrencyCode, - request.toCurrencyCode - ) + throw new SwapCurrencyError(swapInfo, request) } throw new Error('ChangeHero error: ' + JSON.stringify(reply.error)) } @@ -134,11 +157,22 @@ export function makeChangeHeroPlugin( getAddress(request.fromWallet), getAddress(request.toWallet) ]) - const { fromCurrencyCode, toCurrencyCode } = getCodes(request) - // The chain codes are undocumented but ChangeHero uses Edge pluginIds for these values (confirmed via Slack) - const fromMainnetCode = request.fromWallet.currencyInfo.pluginId - const toMainnetCode = request.toWallet.currencyInfo.pluginId + // Supported chains must be whitelisted + if ( + MAINNET_CODE_TRANSCRIPTION[request.fromWallet.currencyInfo.pluginId] == + null || + MAINNET_CODE_TRANSCRIPTION[request.toWallet.currencyInfo.pluginId] == null + ) { + throw new SwapCurrencyError(swapInfo, request) + } + + const { + fromCurrencyCode, + toCurrencyCode, + fromMainnetCode, + toMainnetCode + } = getCodesWithTranscription(request, MAINNET_CODE_TRANSCRIPTION) const quoteAmount = request.quoteFor === 'from'