Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Whitelist plugins for ChangeHero plugin #294

Merged
merged 2 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading