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

Sam/Totle Error Handling Fix #137

Merged
merged 2 commits into from
Jun 30, 2021
Merged
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
202 changes: 124 additions & 78 deletions src/swap/totle.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
// @flow

import { div, mul } from 'biggystring'
import {
asArray,
asBoolean,
asEither,
asNumber,
asObject,
asOptional,
asString,
asUnknown,
asValue
} from 'cleaners'
import {
type EdgeCorePluginOptions,
type EdgeLog,
Expand Down Expand Up @@ -29,76 +40,112 @@ const swapUri = 'https://api.totle.com/swap'
const tokenUri = 'https://api.totle.com/tokens'
const expirationMs = 1000 * 60 * 20

type QuoteInfo = {
id: string,
summary: {
baseAsset: {
address: string,
symbol: string,
decimals: string
},
sourceAsset: {
address: string,
symbol: string,
decimals: string
},
sourceAmount: string,
destinationAsset: {
address: string,
symbol: string,
decimals: string
},
destinationAmount: string,
notes: [],
rate: string,
guaranteedRate: string,
market: {
rate: string,
slippage: string
}
},
transactions: [
{
type: 'swap' | 'approve',
id: string,
tx: {
to: string,
from: string,
value: string,
data: string,
gasPrice: string,
gas: string,
nonce?: string
}
}
]
}
const asTotleErrorResponse = asObject({
success: asValue(false),
response: asObject({
id: asString,
code: asNumber,
message: asString,
info: asString,
name: asOptional(asString),
link: asOptional(asString)
})
})

type Token = {
name: string,
symbol: string,
decimals: number,
address: string,
tradable: boolean,
iconUrl: string
}
const asTotleSwapResponse = asObject({
success: asValue(true),
response: asObject({
id: asString,
summary: asObject({
baseAsset: asObject({
address: asString,
symbol: asString,
decimals: asString
}),
sourceAsset: asObject({
address: asString,
symbol: asString,
decimals: asString
}),
sourceAmount: asString,
destinationAsset: asObject({
address: asString,
symbol: asString,
decimals: asString
}),
destinationAmount: asString,
notes: asOptional(asArray(asUnknown)),
rate: asString,
guaranteedRate: asString,
market: asObject({
rate: asString,
slippage: asString
})
}),
transactions: asArray(
asObject({
type: asEither(asValue('swap'), asValue('approve')),
id: asString,
tx: asObject({
to: asString,
from: asString,
value: asString,
data: asString,
gasPrice: asString,
gas: asString,
nonce: asOptional(asString)
})
})
)
})
})

type TotleSwapResponse = $Call<typeof asTotleSwapResponse>

function checkReply(reply: Object, request: EdgeSwapRequest) {
// /swap
const asTotleSwapReply = asEither(asTotleSwapResponse, asTotleErrorResponse)
type TotleSwapReply = $Call<typeof asTotleSwapReply>

// /tokens
const asToken = asObject({
name: asString,
symbol: asString,
decimals: asNumber,
address: asString,
tradable: asBoolean,
iconUrl: asString
})
type Token = $Call<typeof asToken>

const asTotleTokensResponse = asObject({
tokens: asArray(asToken)
})

function checkSwapReply(
reply: TotleSwapReply,
request: EdgeSwapRequest
): TotleSwapResponse {
// Handle error response
if (reply.success === false) {
const code = reply.response.code
// unsupported tokens
if (code === 1203) {
throw new SwapCurrencyError(
swapInfo,
request.fromCurrencyCode,
request.toCurrencyCode
)
} else if (code === 3100) {
throw new InsufficientFundsError()
} else if (code === 1201) {
throw new NoAmountSpecifiedError()
switch (code) {
case 1203: // TokenNotFoundError
throw new SwapCurrencyError(
swapInfo,
request.fromCurrencyCode,
request.toCurrencyCode
)
case 3100: // InsufficientFundsError
throw new InsufficientFundsError()
case 1201: // AmountError
throw new NoAmountSpecifiedError()
default:
throw new Error(`Totle API Error: ${reply.response.message}`)
}
}

// Return swap response
return reply
}

export function makeTotlePlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin {
Expand All @@ -107,35 +154,35 @@ export function makeTotlePlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin {

const { partnerContract, apiKey } = initOptions

async function call(json: any) {
async function fetchSwapReply(json: any): Promise<TotleSwapReply> {
const body = JSON.stringify({
...json,
partnerContract,
apiKey
})

log('call:', json)
log('fetchSwap:', json)
const headers = {
'Content-Type': 'application/json'
}
const response = await fetchCors(swapUri, { method: 'POST', body, headers })
if (!response.ok) {
throw new Error(`Totle returned error code ${response.status}`)
}
const out = await response.json()
log('swap reply:', out)
return out
const reply = asTotleSwapReply(await response.json())
log('swap reply:', reply)
return reply
}

async function fetchTokens() {
async function fetchTokens(): Promise<Token[]> {
const response = await fetchCors(tokenUri, { method: 'GET' })
if (!response.ok) {
throw new Error(`Totle returned error code ${response.status}`)
}
const json = await response.json()
const out = json.tokens
log('token reply:', out)
return out
const reply = asTotleTokensResponse(await response.json())
const { tokens } = reply
log('token reply:', tokens)
return tokens
}

const out: EdgeSwapPlugin = {
Expand Down Expand Up @@ -166,7 +213,7 @@ export function makeTotlePlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin {
} = await request.toWallet.getReceiveAddress({}) // currencyCode ?

// Get the estimate from the server:
const reply = await call({
const reply = await fetchSwapReply({
address: userFromAddress,
config: {
transactions: true
Expand All @@ -181,9 +228,8 @@ export function makeTotlePlugin(opts: EdgeCorePluginOptions): EdgeSwapPlugin {
destinationAddress: userToAddress
}
})
checkReply(reply, request)

const { summary, transactions }: QuoteInfo = reply.response
const swapResponse = checkSwapReply(reply, request)
const { summary, transactions } = swapResponse.response

let fromNativeAmount: string = summary[0].sourceAmount // string with many zeroes
let toNativeAmount: string = summary[0].destinationAmount // string with many zeroes
Expand Down