forked from shapeshift/lib
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add approval needed to lib (shapeshift#95)
* add approvalNeeded to ZrxSwapper * add rest of approvalNeeded and tests; extract web3 logic to a helper function * add throw error when passed wrong chain to approvalNeeded
- Loading branch information
Showing
11 changed files
with
264 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
packages/swapper/src/swappers/zrx/approvalNeeded/approvalNeeded.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import Web3 from 'web3' | ||
import { HDWallet } from '@shapeshiftoss/hdwallet-core' | ||
import { ChainTypes } from '@shapeshiftoss/types' | ||
import { ChainAdapterManager } from '@shapeshiftoss/chain-adapters' | ||
import { approvalNeeded } from './approvalNeeded' | ||
import { setupQuote } from '../utils/test-data/setupSwapQuote' | ||
import { zrxService } from '../utils/zrxService' | ||
import { APPROVAL_GAS_LIMIT } from '../utils/constants' | ||
|
||
jest.mock('web3') | ||
jest.mock('axios', () => ({ | ||
create: jest.fn(() => ({ | ||
get: jest.fn() | ||
})) | ||
})) | ||
|
||
// @ts-ignore | ||
Web3.mockImplementation(() => ({ | ||
eth: { | ||
Contract: jest.fn(() => ({ | ||
methods: { | ||
allowance: jest.fn(() => ({ | ||
call: jest.fn() | ||
})) | ||
} | ||
})) | ||
} | ||
})) | ||
|
||
const setup = () => { | ||
const unchainedUrls = { | ||
[ChainTypes.Ethereum]: 'http://localhost:31300/api/v1' | ||
} | ||
const ethNodeUrl = 'http://localhost:1000' | ||
const adapterManager = new ChainAdapterManager(unchainedUrls) | ||
const web3Provider = new Web3.providers.HttpProvider(ethNodeUrl) | ||
const web3 = new Web3(web3Provider) | ||
|
||
return { web3, adapterManager } | ||
} | ||
|
||
describe('approvalNeeded', () => { | ||
const { web3, adapterManager } = setup() | ||
const args = { web3, adapterManager } | ||
const walletAddress = '0xc770eefad204b5180df6a14ee197d99d808ee52d' | ||
const wallet = ({ | ||
ethGetAddress: jest.fn(() => Promise.resolve(walletAddress)) | ||
} as unknown) as HDWallet | ||
|
||
const { quoteInput, sellAsset } = setupQuote() | ||
|
||
it('returns false if sellAsset symbol is ETH', async () => { | ||
const input = { | ||
quote: { ...quoteInput, sellAsset: { ...sellAsset, symbol: 'ETH' } }, | ||
wallet | ||
} | ||
|
||
expect(await approvalNeeded(args, input)).toEqual({ approvalNeeded: false }) | ||
}) | ||
|
||
it('throws an error if sellAsset chain is not ETH', async () => { | ||
const input = { | ||
quote: { ...quoteInput, sellAsset: { ...sellAsset, chain: ChainTypes.Bitcoin } }, | ||
wallet | ||
} | ||
|
||
await expect(approvalNeeded(args, input)).rejects.toThrow( | ||
'ZrxSwapper:approvalNeeded only Ethereum chain type is supported' | ||
) | ||
}) | ||
|
||
it('returns false if allowanceOnChain is greater than quote.sellAmount', async () => { | ||
const allowanceOnChain = '50' | ||
const data = { gasPrice: '1000', allowanceTarget: '10' } | ||
const input = { | ||
quote: { ...quoteInput, sellAmount: '10' }, | ||
wallet | ||
} | ||
;(web3.eth.Contract as jest.Mock<unknown>).mockImplementation(() => ({ | ||
methods: { | ||
allowance: jest.fn(() => ({ | ||
call: jest.fn(() => allowanceOnChain) | ||
})) | ||
} | ||
})) | ||
;(zrxService.get as jest.Mock<unknown>).mockReturnValue(Promise.resolve({ data })) | ||
|
||
expect(await approvalNeeded(args, input)).toEqual({ | ||
approvalNeeded: false, | ||
gas: APPROVAL_GAS_LIMIT, | ||
gasPrice: data.gasPrice | ||
}) | ||
}) | ||
|
||
it('returns true if allowanceOnChain is less than quote.sellAmount', async () => { | ||
const allowanceOnChain = '5' | ||
const data = { gasPrice: '1000', allowanceTarget: '10' } | ||
const input = { | ||
quote: { ...quoteInput, sellAmount: '10' }, | ||
wallet | ||
} | ||
;(web3.eth.Contract as jest.Mock<unknown>).mockImplementation(() => ({ | ||
methods: { | ||
allowance: jest.fn(() => ({ | ||
call: jest.fn(() => allowanceOnChain) | ||
})) | ||
} | ||
})) | ||
;(zrxService.get as jest.Mock<unknown>).mockReturnValue(Promise.resolve({ data })) | ||
|
||
expect(await approvalNeeded(args, input)).toEqual({ | ||
approvalNeeded: true, | ||
gas: APPROVAL_GAS_LIMIT, | ||
gasPrice: data.gasPrice | ||
}) | ||
}) | ||
}) |
82 changes: 82 additions & 0 deletions
82
packages/swapper/src/swappers/zrx/approvalNeeded/approvalNeeded.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import { BigNumber } from 'bignumber.js' | ||
import { AxiosResponse } from 'axios' | ||
import { ChainAdapter } from '@shapeshiftoss/chain-adapters' | ||
import { | ||
ApprovalNeededInput, | ||
ApprovalNeededOutput, | ||
ChainTypes, | ||
QuoteResponse | ||
} from '@shapeshiftoss/types' | ||
import { | ||
AFFILIATE_ADDRESS, | ||
APPROVAL_BUY_AMOUNT, | ||
APPROVAL_GAS_LIMIT, | ||
DEFAULT_ETH_PATH, | ||
DEFAULT_SLIPPAGE | ||
} from '../utils/constants' | ||
|
||
import { SwapError } from '../../../api' | ||
import { ZrxSwapperDeps } from '../ZrxSwapper' | ||
import { zrxService } from '../utils/zrxService' | ||
import { getERC20Allowance } from '../utils/helpers/helpers' | ||
import { erc20AllowanceAbi } from '../utils/abi/erc20Allowance-abi' | ||
|
||
export async function approvalNeeded( | ||
{ adapterManager, web3 }: ZrxSwapperDeps, | ||
{ quote, wallet }: ApprovalNeededInput | ||
): Promise<ApprovalNeededOutput> { | ||
const { sellAsset } = quote | ||
|
||
if (sellAsset.symbol === 'ETH') { | ||
return { approvalNeeded: false } | ||
} | ||
|
||
if (sellAsset.chain !== ChainTypes.Ethereum) { | ||
throw new SwapError('ZrxSwapper:approvalNeeded only Ethereum chain type is supported') | ||
} | ||
|
||
const adapter: ChainAdapter = adapterManager.byChain(sellAsset.chain) | ||
const receiveAddress = await adapter.getAddress({ wallet, path: DEFAULT_ETH_PATH }) | ||
|
||
/** | ||
* /swap/v1/quote | ||
* params: { | ||
* sellToken: contract address (or symbol) of token to sell | ||
* buyToken: contractAddress (or symbol) of token to buy | ||
* sellAmount?: integer string value of the smallest increment of the sell token | ||
* buyAmount?: integer string value of the smallest incremtent of the buy token | ||
* } | ||
*/ | ||
const quoteResponse: AxiosResponse<QuoteResponse> = await zrxService.get<QuoteResponse>( | ||
'/swap/v1/quote', | ||
{ | ||
params: { | ||
buyToken: 'ETH', | ||
sellToken: quote.sellAsset.tokenId || quote.sellAsset.symbol || quote.sellAsset.chain, | ||
buyAmount: APPROVAL_BUY_AMOUNT, | ||
takerAddress: receiveAddress, | ||
slippagePercentage: DEFAULT_SLIPPAGE, | ||
skipValidation: true, | ||
affiliateAddress: AFFILIATE_ADDRESS | ||
} | ||
} | ||
) | ||
const { data } = quoteResponse | ||
|
||
const allowanceResult = getERC20Allowance( | ||
{ web3, erc20AllowanceAbi }, | ||
{ | ||
tokenId: quote.sellAsset.tokenId as string, | ||
spenderAddress: data.allowanceTarget as string, | ||
ownerAddress: receiveAddress | ||
} | ||
) | ||
|
||
const allowanceOnChain = new BigNumber(allowanceResult || '0') | ||
|
||
return { | ||
approvalNeeded: allowanceOnChain.lt(new BigNumber(quote.sellAmount || 1)), | ||
gas: APPROVAL_GAS_LIMIT, | ||
gasPrice: data.gasPrice | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 2 additions & 1 deletion
3
packages/swapper/src/swappers/zrx/utils/helpers/helpers.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.