Skip to content

Commit

Permalink
Merge pull request #294 from EdgeApp/matthew/ftmChangehero
Browse files Browse the repository at this point in the history
Whitelist plugins for ChangeHero plugin
  • Loading branch information
peachbits authored Oct 9, 2023
2 parents 88a1e48 + 2ebd1bf commit ba0c480
Show file tree
Hide file tree
Showing 13 changed files with 126 additions and 118 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

- changed: Restrict ChangeHero trading to whitelisted plugins
- 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`
Expand Down
6 changes: 1 addition & 5 deletions src/swap-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
56 changes: 45 additions & 11 deletions src/swap/changehero.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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: {
Expand Down Expand Up @@ -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))
}
Expand Down Expand Up @@ -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'
Expand Down
4 changes: 2 additions & 2 deletions src/swap/changenow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down
8 changes: 4 additions & 4 deletions src/swap/defi/lifi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? '']
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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()
Expand Down
10 changes: 5 additions & 5 deletions src/swap/defi/thorchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
}

Expand Down
17 changes: 8 additions & 9 deletions src/swap/defi/thorchainDa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand All @@ -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'
Expand Down Expand Up @@ -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 ?? ''
Expand Down Expand Up @@ -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)
}
}

Expand Down
94 changes: 47 additions & 47 deletions src/swap/exolix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Object> {
const headers: { [header: string]: string } = {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `${apiKey}`
}

let response: Awaited<ReturnType<typeof fetchCors>>
const getFixedQuote = async (
request: EdgeSwapRequestPlugin,
_userSettings: Object | undefined
): Promise<SwapOrder> => {
async function call(
method: 'GET' | 'POST',
route: string,
params: any
): Promise<Object> {
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<ReturnType<typeof fetchCors>>

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<SwapOrder> => {
const [fromAddress, toAddress] = await Promise.all([
getAddress(request.fromWallet),
getAddress(request.toWallet)
Expand Down
Loading

0 comments on commit ba0c480

Please sign in to comment.