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

Patch/quicknode #188

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
36 changes: 12 additions & 24 deletions src/swap/spookySwap/spookyContracts.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,19 @@ import WRAPPED_FTM_ABI from '../../abi/WRAPPED_FTM_ABI'

export { UNISWAP_V2_PAIR_ABI }

// Providers
//

export const hardCodedUrls = ['https://rpc.ftm.tools']

// TOOD: Use FallbackProvider when it's patched https://github.com/ethers-io/ethers.js/issues/2837
export const provider = new ethers.providers.JsonRpcProvider(hardCodedUrls[0])

//
// Contracts
//

const SPOOKYSWAP_ROUTER_ADDRESS = '0xF491e7B69E4244ad4002BC14e878a34207E38c29'
export const spookySwapRouter = new ethers.Contract(
SPOOKYSWAP_ROUTER_ADDRESS,
UNISWAP_V2_ROUTER_ABI,
provider
)
export const makeSpookySwapRouter = (provider: ethers.Provider) =>
new ethers.Contract(
SPOOKYSWAP_ROUTER_ADDRESS,
UNISWAP_V2_ROUTER_ABI,
provider
)

const WFTM_TOKEN_ADDRESS = '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83'
export const wrappedFtmToken = new ethers.Contract(
WFTM_TOKEN_ADDRESS,
WRAPPED_FTM_ABI,
provider
)
export const makeWrappedFtmToken = (provider: ethers.Provider) =>
new ethers.Contract(WFTM_TOKEN_ADDRESS, WRAPPED_FTM_ABI, provider)

export const makeErc20Contract = (tokenAddress: string) =>
new ethers.Contract(tokenAddress, UNISWAP_V2_ERC20_ABI, provider)
export const makeErc20Contract = (
tokenAddress: string,
provider: ethers.Provider
) => new ethers.Contract(tokenAddress, UNISWAP_V2_ERC20_ABI, provider)
310 changes: 165 additions & 145 deletions src/swap/spookySwap/spookySwap.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ import { type PopulatedTransaction, ethers } from 'ethers'
import { round } from '../../util/biggystringplus.js'
import {
makeErc20Contract,
provider,
spookySwapRouter,
wrappedFtmToken
makeSpookySwapRouter,
makeWrappedFtmToken
} from './spookyContracts.js'

const swapInfo: EdgeSwapInfo = {
Expand All @@ -32,159 +31,180 @@ const expirationMs = 1000 * 20 * 60
const SLIPPAGE = '0.05' // 5%
const SLIPPAGE_MULTIPLIER = sub('1', SLIPPAGE)

export const getMetaTokenAddress = (
metaTokens: EdgeMetaToken[],
tokenCurrencyCode: string
): string => {
const metaToken = metaTokens.find(mt => mt.currencyCode === tokenCurrencyCode)

if (metaToken == null || metaToken?.contractAddress === undefined)
throw new Error('Could not find contract address for ' + tokenCurrencyCode)

return metaToken.contractAddress ?? ''
}

export const getSwapTransactions = async (
swapRequest: EdgeSwapRequest,
amountToSwap: string,
expectedAmountOut: string,
toAddress: string,
deadline: number
): Promise<PopulatedTransaction[]> => {
const { fromWallet, toWallet, fromCurrencyCode, toCurrencyCode } = swapRequest
const currencyInfo = fromWallet.currencyInfo
const fromAddress = (await fromWallet.getReceiveAddress()).publicAddress

// Sanity check: Both wallets should be of the same chain.
if (
fromWallet.currencyInfo.currencyCode !== toWallet.currencyInfo.currencyCode
)
throw new Error('SpookySwap: Mismatched wallet chain')

// TODO: Use our new denom implementation to get native amounts
const nativeCurrencyCode = currencyInfo.currencyCode
const wrappedCurrencyCode = `W${nativeCurrencyCode}`
const isFromNativeCurrency = fromCurrencyCode === nativeCurrencyCode
const isToNativeCurrency = toCurrencyCode === nativeCurrencyCode
const isFromWrappedCurrency = fromCurrencyCode === wrappedCurrencyCode
const isToWrappedCurrency = toCurrencyCode === wrappedCurrencyCode

// TODO: Do different wallets share the same custom metaTokens?
const metaTokens: EdgeMetaToken[] = currencyInfo.metaTokens

const fromTokenAddress = getMetaTokenAddress(
metaTokens,
isFromNativeCurrency ? wrappedCurrencyCode : fromCurrencyCode
)
const toTokenAddress = getMetaTokenAddress(
metaTokens,
isToNativeCurrency ? wrappedCurrencyCode : toCurrencyCode
export function makeSpookySwapPlugin(
opts: EdgeCorePluginOptions
): EdgeSwapPlugin {
const { log, initOptions } = opts
const { quiknodeApiKey } = initOptions

// TOOD: Use FallbackProvider when it's patched https://github.com/ethers-io/ethers.js/issues/2837
const provider = new ethers.providers.JsonRpcProvider(
quiknodeApiKey
? `https://polished-empty-cloud.fantom.quiknode.pro/${quiknodeApiKey}`
: 'https://rpc.ftm.tools'
)

// Determine router method name and params
if (isFromNativeCurrency && isToNativeCurrency)
throw new Error('Invalid swap: Cannot swap to the same native currency')
const path = [fromTokenAddress, toTokenAddress]

const gasPrice = await provider.getGasPrice()

const addressToApproveTxs = async (
tokenAddress: string,
contractAddress: string
): PopulatedTransaction | void => {
const tokenContract = makeErc20Contract(tokenAddress)
const allowence = await tokenContract.allowance(
fromAddress,
contractAddress
const spookySwapRouter = makeSpookySwapRouter(provider)
const wrappedFtmToken = makeWrappedFtmToken(provider)

const getMetaTokenAddress = (
metaTokens: EdgeMetaToken[],
tokenCurrencyCode: string
): string => {
const metaToken = metaTokens.find(
mt => mt.currencyCode === tokenCurrencyCode
)
if (allowence.sub(amountToSwap).lt(0)) {
return tokenContract.populateTransaction.approve(
contractAddress,
ethers.constants.MaxUint256,
{ gasLimit: '60000', gasPrice }

if (metaToken == null || metaToken?.contractAddress === undefined)
throw new Error(
'Could not find contract address for ' + tokenCurrencyCode
)
}

return metaToken.contractAddress ?? ''
}

const txs = await (async (): Promise<
Array<Promise<PopulatedTransaction> | void>
> => {
// Deposit native currency for wrapped token
if (isFromNativeCurrency && isToWrappedCurrency) {
return [
wrappedFtmToken.populateTransaction.deposit({
gasLimit: '51000',
gasPrice,
value: amountToSwap
})
]
}
// Withdraw wrapped token for native currency
if (isFromWrappedCurrency && isToNativeCurrency) {
return [
// Deposit Tx
wrappedFtmToken.populateTransaction.withdraw(amountToSwap, {
gasLimit: '51000',
gasPrice
})
]
}
// Swap native currency for token
if (isFromNativeCurrency && !isToNativeCurrency) {
return [
// Swap Tx
spookySwapRouter.populateTransaction.swapExactETHForTokens(
round(mul(expectedAmountOut, SLIPPAGE_MULTIPLIER)),
path,
toAddress,
deadline,
{ gasLimit: '250000', gasPrice, value: amountToSwap }
)
]
}
// Swap token for native currency
if (!isFromNativeCurrency && isToNativeCurrency) {
return [
// Approve TX
await addressToApproveTxs(path[0], spookySwapRouter.address),
// Swap Tx
spookySwapRouter.populateTransaction.swapExactTokensForETH(
amountToSwap,
round(mul(expectedAmountOut, SLIPPAGE_MULTIPLIER)),
path,
toAddress,
deadline,
{ gasLimit: '250000', gasPrice }
)
]
}
// Swap token for token
if (!isFromNativeCurrency && !isToNativeCurrency) {
return [
// Approve TX
await addressToApproveTxs(path[0], spookySwapRouter.address),
// Swap Tx
spookySwapRouter.populateTransaction.swapExactTokensForTokens(
amountToSwap,
round(mul(expectedAmountOut, SLIPPAGE_MULTIPLIER)),
path,
toAddress,
deadline,
{ gasLimit: '600000', gasPrice }
const getSwapTransactions = async (
swapRequest: EdgeSwapRequest,
amountToSwap: string,
expectedAmountOut: string,
toAddress: string,
deadline: number
): Promise<PopulatedTransaction[]> => {
const {
fromWallet,
toWallet,
fromCurrencyCode,
toCurrencyCode
} = swapRequest
const currencyInfo = fromWallet.currencyInfo
const fromAddress = (await fromWallet.getReceiveAddress()).publicAddress

// Sanity check: Both wallets should be of the same chain.
if (
fromWallet.currencyInfo.currencyCode !==
toWallet.currencyInfo.currencyCode
)
throw new Error('SpookySwap: Mismatched wallet chain')

// TODO: Use our new denom implementation to get native amounts
const nativeCurrencyCode = currencyInfo.currencyCode
const wrappedCurrencyCode = `W${nativeCurrencyCode}`
const isFromNativeCurrency = fromCurrencyCode === nativeCurrencyCode
const isToNativeCurrency = toCurrencyCode === nativeCurrencyCode
const isFromWrappedCurrency = fromCurrencyCode === wrappedCurrencyCode
const isToWrappedCurrency = toCurrencyCode === wrappedCurrencyCode

// TODO: Do different wallets share the same custom metaTokens?
const metaTokens: EdgeMetaToken[] = currencyInfo.metaTokens

const fromTokenAddress = getMetaTokenAddress(
metaTokens,
isFromNativeCurrency ? wrappedCurrencyCode : fromCurrencyCode
)
const toTokenAddress = getMetaTokenAddress(
metaTokens,
isToNativeCurrency ? wrappedCurrencyCode : toCurrencyCode
)

// Determine router method name and params
if (isFromNativeCurrency && isToNativeCurrency)
throw new Error('Invalid swap: Cannot swap to the same native currency')
const path = [fromTokenAddress, toTokenAddress]

const gasPrice = await provider.getGasPrice()

const addressToApproveTxs = async (
tokenAddress: string,
contractAddress: string
): PopulatedTransaction | void => {
const tokenContract = makeErc20Contract(tokenAddress, provider)
const allowence = await tokenContract.allowance(
fromAddress,
contractAddress
)
if (allowence.sub(amountToSwap).lt(0)) {
return tokenContract.populateTransaction.approve(
contractAddress,
ethers.constants.MaxUint256,
{ gasLimit: '60000', gasPrice }
)
]
}
}

throw new Error('Unhandled swap type')
})()
const txs = await (async (): Promise<
Array<Promise<PopulatedTransaction> | void>
> => {
// Deposit native currency for wrapped token
if (isFromNativeCurrency && isToWrappedCurrency) {
return [
wrappedFtmToken.populateTransaction.deposit({
gasLimit: '51000',
gasPrice,
value: amountToSwap
})
]
}
// Withdraw wrapped token for native currency
if (isFromWrappedCurrency && isToNativeCurrency) {
return [
// Deposit Tx
wrappedFtmToken.populateTransaction.withdraw(amountToSwap, {
gasLimit: '51000',
gasPrice
})
]
}
// Swap native currency for token
if (isFromNativeCurrency && !isToNativeCurrency) {
return [
// Swap Tx
spookySwapRouter.populateTransaction.swapExactETHForTokens(
round(mul(expectedAmountOut, SLIPPAGE_MULTIPLIER)),
path,
toAddress,
deadline,
{ gasLimit: '250000', gasPrice, value: amountToSwap }
)
]
}
// Swap token for native currency
if (!isFromNativeCurrency && isToNativeCurrency) {
return [
// Approve TX
await addressToApproveTxs(path[0], spookySwapRouter.address),
// Swap Tx
spookySwapRouter.populateTransaction.swapExactTokensForETH(
amountToSwap,
round(mul(expectedAmountOut, SLIPPAGE_MULTIPLIER)),
path,
toAddress,
deadline,
{ gasLimit: '250000', gasPrice }
)
]
}
// Swap token for token
if (!isFromNativeCurrency && !isToNativeCurrency) {
return [
// Approve TX
await addressToApproveTxs(path[0], spookySwapRouter.address),
// Swap Tx
spookySwapRouter.populateTransaction.swapExactTokensForTokens(
amountToSwap,
round(mul(expectedAmountOut, SLIPPAGE_MULTIPLIER)),
path,
toAddress,
deadline,
{ gasLimit: '600000', gasPrice }
)
]
}

return await Promise.all(txs.filter(tx => tx != null))
}
throw new Error('Unhandled swap type')
})()

export function makeSpookySwapPlugin(
opts: EdgeCorePluginOptions
): EdgeSwapPlugin {
const { log } = opts
return await Promise.all(txs.filter(tx => tx != null))
}

const out: EdgeSwapPlugin = {
swapInfo,
Expand Down