├── .env.example ├── .gitignore ├── .prettierrc ├── .solhint.json ├── README.md ├── api └── ZapperAPI.js ├── contracts └── ZapConsumer.sol ├── oz ├── GSN │ └── Context.sol ├── math │ └── SafeMath.sol ├── ownership │ └── Ownable.sol ├── token │ └── ERC20 │ │ ├── IERC20.sol │ │ └── SafeERC20.sol └── utils │ └── Address.sol ├── package.json ├── test-environment.config.js ├── test └── ZapConsumer.test.js ├── truffle-config.js └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | NODE_URL=https://mainnet_node_url_infura_quiknode_alchemy 2 | ZAPPER_API_KEY=SEE https://docs.zapper.fi/ to get an API key -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bin/ 3 | build/ 4 | .env -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "singleQuote": false 4 | } 5 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:default", 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "prettier/prettier": "error", 6 | "avoid-throw": "off", 7 | "avoid-suicide": "error", 8 | "avoid-sha3": "warn", 9 | "two-lines-top-level-separator": "off" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zapper-API-Stack 2 | This repo demonstrates usage of the Zapper API using Web3 and Smart Contracts 3 | 4 | See https://docs.zapper.fi/zapper-api/api-guides for additional context 5 | 6 | ## Setup 7 | 1. Clone this repository 8 | 2. cd into the cloned folder 9 | 3. Run the command `yarn` to install dependencies 10 | 11 | ## Testing 12 | 1. Run the command `yarn test` 13 | 14 | ## Explanation 15 | This repo includes a contract called `ZapConsumer.sol` which demonstrates how to consume Zapper Transaction API data in a smart contract. 16 | 17 | The `test` folder contains tests demonstrating the usage of the `ZapConsumer.sol` contract as well as examples of how to consume transaction data with vanilla Web3. 18 | 19 | The `api` folder contains example utility functions for calling the Zapper API with Axios via GET requests. 20 | 21 | NOTE: An example of `.env` is provided as `.env.example`. You must create a `.env` after cloning this repository. 22 | -------------------------------------------------------------------------------- /api/ZapperAPI.js: -------------------------------------------------------------------------------- 1 | const constants = require("@openzeppelin/test-helpers/src/constants"); 2 | const axios = require("axios"); 3 | require("dotenv").config(); 4 | 5 | 6 | const userZapIn = async ({ 7 | toWhomToIssue, 8 | sellToken, 9 | sellAmount, 10 | poolAddress, 11 | protocol, 12 | }) => { 13 | return await getZapInData({ 14 | ownerAddress: toWhomToIssue, 15 | sellToken, 16 | sellAmount, 17 | poolAddress, 18 | protocol, 19 | }).then((data) => { 20 | return { 21 | ...data, 22 | gas: "6000000", 23 | }; 24 | }); 25 | }; 26 | 27 | const getZapInData = async ({ 28 | ownerAddress, 29 | sellToken, 30 | sellAmount, 31 | poolAddress, 32 | protocol, 33 | affiliateAddress = constants.ZERO_ADDRESS, 34 | }) => { 35 | const params = { 36 | api_key: process.env.ZAPPER_API_KEY, 37 | ownerAddress, 38 | sellAmount: sellAmount.toString(), 39 | sellTokenAddress: sellToken, 40 | poolAddress: poolAddress.toLowerCase(), 41 | affiliateAddress, 42 | gasPrice: "250000000000", 43 | slippagePercentage: "0.05", 44 | skipGasEstimate: true, 45 | }; 46 | 47 | const data = await axios 48 | .get(`http://api.zapper.fi/v1/zap-in/${protocol}/transaction`, { params }) 49 | .then((r) => { 50 | return r.data; 51 | }) 52 | .catch((error) => { 53 | console.log(error.response.data); 54 | }); 55 | 56 | return data; 57 | }; 58 | 59 | module.exports = { 60 | getZapInData, 61 | userZapIn, 62 | }; 63 | -------------------------------------------------------------------------------- /contracts/ZapConsumer.sol: -------------------------------------------------------------------------------- 1 | ///@notice This contract executes Zapper Transaction API calldata 2 | 3 | pragma solidity ^0.5.7; 4 | import "../oz/token/ERC20/IERC20.sol"; 5 | import "../oz/math/SafeMath.sol"; 6 | import "../oz/token/ERC20/SafeERC20.sol"; 7 | 8 | contract Zap_Consumer_V1 { 9 | using SafeMath for uint256; 10 | using SafeERC20 for IERC20; 11 | 12 | /** 13 | @dev Must attach ETH equal to the `value` field from the API response. 14 | @param sellTokenAddress The 'sellTokenAddress' from the API response 15 | @param buyTokenAddress The 'buyTokenAddress' field from the API response 16 | @param sellTokenQuantity The 'sellTokenAmount' field from the API response 17 | @param zapContract The 'to' field from the API response 18 | @param data The 'data' field from the API response 19 | */ 20 | function Zap( 21 | address sellTokenAddress, 22 | uint256 sellTokenQuantity, 23 | address buyTokenAddress, 24 | address zapContract, 25 | bytes calldata data 26 | ) external payable returns (uint256 tokensRec) { 27 | if (sellTokenAddress != address(0)) { 28 | IERC20(sellTokenAddress).safeTransferFrom( 29 | msg.sender, 30 | address(this), 31 | sellTokenQuantity 32 | ); 33 | } 34 | 35 | tokensRec = _fillQuote( 36 | sellTokenAddress, 37 | buyTokenAddress, 38 | sellTokenQuantity, 39 | zapContract, 40 | data 41 | ); 42 | 43 | if (buyTokenAddress != address(0)) { 44 | IERC20(buyTokenAddress).safeTransfer(msg.sender, tokensRec); 45 | } else { 46 | Address.sendValue(Address.toPayable(msg.sender), tokensRec); 47 | } 48 | } 49 | 50 | function _fillQuote( 51 | address fromToken, 52 | address toToken, 53 | uint256 amount, 54 | address swapTarget, 55 | bytes memory swapData 56 | ) internal returns (uint256 finalBalance) { 57 | uint256 valueToSend; 58 | if (fromToken == address(0)) valueToSend = amount; 59 | else _approveToken(fromToken, swapTarget); 60 | 61 | uint256 initialBalance = _getBalance(toToken); 62 | 63 | (bool success, ) = swapTarget.call.value(valueToSend)(swapData); 64 | require(success, "Zap Failed"); 65 | 66 | finalBalance = _getBalance(toToken).sub(initialBalance); 67 | 68 | require(finalBalance > 0, "Swapped to Invalid Token"); 69 | } 70 | 71 | function _approveToken(address token, address spender) internal { 72 | if (token == address(0)) return; 73 | IERC20 _token = IERC20(token); 74 | if (_token.allowance(address(this), spender) > 0) return; 75 | else { 76 | _token.safeApprove(spender, uint256(-1)); 77 | } 78 | } 79 | 80 | function _getBalance(address token) 81 | internal 82 | view 83 | returns (uint256 balance) 84 | { 85 | if (token == address(0)) { 86 | balance = address(this).balance; 87 | } else { 88 | balance = IERC20(token).balanceOf(address(this)); 89 | } 90 | } 91 | 92 | function() external payable { 93 | require(msg.sender != tx.origin, "Do not send ETH directly"); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /oz/GSN/Context.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | /* 4 | * @dev Provides information about the current execution context, including the 5 | * sender of the transaction and its data. While these are generally available 6 | * via msg.sender and msg.data, they should not be accessed in such a direct 7 | * manner, since when dealing with GSN meta-transactions the account sending and 8 | * paying for execution may not be the actual sender (as far as an application 9 | * is concerned). 10 | * 11 | * This contract is only required for intermediate, library-like contracts. 12 | */ 13 | contract Context { 14 | // Empty internal constructor, to prevent people from mistakenly deploying 15 | // an instance of this contract, which should be used via inheritance. 16 | constructor() internal {} 17 | 18 | // solhint-disable-previous-line no-empty-blocks 19 | 20 | function _msgSender() internal view returns (address payable) { 21 | return msg.sender; 22 | } 23 | 24 | function _msgData() internal view returns (bytes memory) { 25 | this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 26 | return msg.data; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /oz/math/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | /** 4 | * @dev Wrappers over Solidity's arithmetic operations with added overflow 5 | * checks. 6 | * 7 | * Arithmetic operations in Solidity wrap on overflow. This can easily result 8 | * in bugs, because programmers usually assume that an overflow raises an 9 | * error, which is the standard behavior in high level programming languages. 10 | * `SafeMath` restores this intuition by reverting the transaction when an 11 | * operation overflows. 12 | * 13 | * Using this library instead of the unchecked operations eliminates an entire 14 | * class of bugs, so it's recommended to use it always. 15 | */ 16 | library SafeMath { 17 | /** 18 | * @dev Returns the addition of two unsigned integers, reverting on 19 | * overflow. 20 | * 21 | * Counterpart to Solidity's `+` operator. 22 | * 23 | * Requirements: 24 | * - Addition cannot overflow. 25 | */ 26 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 27 | uint256 c = a + b; 28 | require(c >= a, "SafeMath: addition overflow"); 29 | 30 | return c; 31 | } 32 | 33 | /** 34 | * @dev Returns the subtraction of two unsigned integers, reverting on 35 | * overflow (when the result is negative). 36 | * 37 | * Counterpart to Solidity's `-` operator. 38 | * 39 | * Requirements: 40 | * - Subtraction cannot overflow. 41 | */ 42 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 43 | return sub(a, b, "SafeMath: subtraction overflow"); 44 | } 45 | 46 | /** 47 | * @dev Returns the subtraction of two unsigned integers, reverting with custom message on 48 | * overflow (when the result is negative). 49 | * 50 | * Counterpart to Solidity's `-` operator. 51 | * 52 | * Requirements: 53 | * - Subtraction cannot overflow. 54 | * 55 | * _Available since v2.4.0._ 56 | */ 57 | function sub( 58 | uint256 a, 59 | uint256 b, 60 | string memory errorMessage 61 | ) internal pure returns (uint256) { 62 | require(b <= a, errorMessage); 63 | uint256 c = a - b; 64 | 65 | return c; 66 | } 67 | 68 | /** 69 | * @dev Returns the multiplication of two unsigned integers, reverting on 70 | * overflow. 71 | * 72 | * Counterpart to Solidity's `*` operator. 73 | * 74 | * Requirements: 75 | * - Multiplication cannot overflow. 76 | */ 77 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 78 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 79 | // benefit is lost if 'b' is also tested. 80 | // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 81 | if (a == 0) { 82 | return 0; 83 | } 84 | 85 | uint256 c = a * b; 86 | require(c / a == b, "SafeMath: multiplication overflow"); 87 | 88 | return c; 89 | } 90 | 91 | /** 92 | * @dev Returns the integer division of two unsigned integers. Reverts on 93 | * division by zero. The result is rounded towards zero. 94 | * 95 | * Counterpart to Solidity's `/` operator. Note: this function uses a 96 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 97 | * uses an invalid opcode to revert (consuming all remaining gas). 98 | * 99 | * Requirements: 100 | * - The divisor cannot be zero. 101 | */ 102 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 103 | return div(a, b, "SafeMath: division by zero"); 104 | } 105 | 106 | /** 107 | * @dev Returns the integer division of two unsigned integers. Reverts with custom message on 108 | * division by zero. The result is rounded towards zero. 109 | * 110 | * Counterpart to Solidity's `/` operator. Note: this function uses a 111 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 112 | * uses an invalid opcode to revert (consuming all remaining gas). 113 | * 114 | * Requirements: 115 | * - The divisor cannot be zero. 116 | * 117 | * _Available since v2.4.0._ 118 | */ 119 | function div( 120 | uint256 a, 121 | uint256 b, 122 | string memory errorMessage 123 | ) internal pure returns (uint256) { 124 | // Solidity only automatically asserts when dividing by 0 125 | require(b > 0, errorMessage); 126 | uint256 c = a / b; 127 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 128 | 129 | return c; 130 | } 131 | 132 | /** 133 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 134 | * Reverts when dividing by zero. 135 | * 136 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 137 | * opcode (which leaves remaining gas untouched) while Solidity uses an 138 | * invalid opcode to revert (consuming all remaining gas). 139 | * 140 | * Requirements: 141 | * - The divisor cannot be zero. 142 | */ 143 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 144 | return mod(a, b, "SafeMath: modulo by zero"); 145 | } 146 | 147 | /** 148 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 149 | * Reverts with custom message when dividing by zero. 150 | * 151 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 152 | * opcode (which leaves remaining gas untouched) while Solidity uses an 153 | * invalid opcode to revert (consuming all remaining gas). 154 | * 155 | * Requirements: 156 | * - The divisor cannot be zero. 157 | * 158 | * _Available since v2.4.0._ 159 | */ 160 | function mod( 161 | uint256 a, 162 | uint256 b, 163 | string memory errorMessage 164 | ) internal pure returns (uint256) { 165 | require(b != 0, errorMessage); 166 | return a % b; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /oz/ownership/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "../GSN/Context.sol"; 4 | 5 | /** 6 | * @dev Contract module which provides a basic access control mechanism, where 7 | * there is an account (an owner) that can be granted exclusive access to 8 | * specific functions. 9 | * 10 | * This module is used through inheritance. It will make available the modifier 11 | * `onlyOwner`, which can be applied to your functions to restrict their use to 12 | * the owner. 13 | */ 14 | contract Ownable is Context { 15 | address private _owner; 16 | 17 | event OwnershipTransferred( 18 | address indexed previousOwner, 19 | address indexed newOwner 20 | ); 21 | 22 | /** 23 | * @dev Initializes the contract setting the deployer as the initial owner. 24 | */ 25 | constructor() internal { 26 | address msgSender = _msgSender(); 27 | _owner = msgSender; 28 | emit OwnershipTransferred(address(0), msgSender); 29 | } 30 | 31 | /** 32 | * @dev Returns the address of the current owner. 33 | */ 34 | function owner() public view returns (address) { 35 | return _owner; 36 | } 37 | 38 | /** 39 | * @dev Throws if called by any account other than the owner. 40 | */ 41 | modifier onlyOwner() { 42 | require(isOwner(), "Ownable: caller is not the owner"); 43 | _; 44 | } 45 | 46 | /** 47 | * @dev Returns true if the caller is the current owner. 48 | */ 49 | function isOwner() public view returns (bool) { 50 | return _msgSender() == _owner; 51 | } 52 | 53 | /** 54 | * @dev Leaves the contract without owner. It will not be possible to call 55 | * `onlyOwner` functions anymore. Can only be called by the current owner. 56 | * 57 | * NOTE: Renouncing ownership will leave the contract without an owner, 58 | * thereby removing any functionality that is only available to the owner. 59 | */ 60 | function renounceOwnership() public onlyOwner { 61 | emit OwnershipTransferred(_owner, address(0)); 62 | _owner = address(0); 63 | } 64 | 65 | /** 66 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 67 | * Can only be called by the current owner. 68 | */ 69 | function transferOwnership(address newOwner) public onlyOwner { 70 | _transferOwnership(newOwner); 71 | } 72 | 73 | /** 74 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 75 | */ 76 | function _transferOwnership(address newOwner) internal { 77 | require( 78 | newOwner != address(0), 79 | "Ownable: new owner is the zero address" 80 | ); 81 | emit OwnershipTransferred(_owner, newOwner); 82 | _owner = newOwner; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /oz/token/ERC20/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | /** 4 | * @dev Interface of the ERC20 standard as defined in the EIP. Does not include 5 | * the optional functions; to access them see {ERC20Detailed}. 6 | */ 7 | interface IERC20 { 8 | function decimals() external view returns (uint8); 9 | 10 | /** 11 | * @dev Returns the amount of tokens in existence. 12 | */ 13 | function totalSupply() external view returns (uint256); 14 | 15 | /** 16 | * @dev Returns the amount of tokens owned by `account`. 17 | */ 18 | function balanceOf(address account) external view returns (uint256); 19 | 20 | /** 21 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 22 | * 23 | * Returns a boolean value indicating whether the operation succeeded. 24 | * 25 | * Emits a {Transfer} event. 26 | */ 27 | function transfer(address recipient, uint256 amount) 28 | external 29 | returns (bool); 30 | 31 | /** 32 | * @dev Returns the remaining number of tokens that `spender` will be 33 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 34 | * zero by default. 35 | * 36 | * This value changes when {approve} or {transferFrom} are called. 37 | */ 38 | function allowance(address owner, address spender) 39 | external 40 | view 41 | returns (uint256); 42 | 43 | /** 44 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 45 | * 46 | * Returns a boolean value indicating whether the operation succeeded. 47 | * 48 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 49 | * that someone may use both the old and the new allowance by unfortunate 50 | * transaction ordering. One possible solution to mitigate this race 51 | * condition is to first reduce the spender's allowance to 0 and set the 52 | * desired value afterwards: 53 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 54 | * 55 | * Emits an {Approval} event. 56 | */ 57 | function approve(address spender, uint256 amount) external returns (bool); 58 | 59 | /** 60 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 61 | * allowance mechanism. `amount` is then deducted from the caller's 62 | * allowance. 63 | * 64 | * Returns a boolean value indicating whether the operation succeeded. 65 | * 66 | * Emits a {Transfer} event. 67 | */ 68 | function transferFrom( 69 | address sender, 70 | address recipient, 71 | uint256 amount 72 | ) external returns (bool); 73 | 74 | /** 75 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 76 | * another (`to`). 77 | * 78 | * Note that `value` may be zero. 79 | */ 80 | event Transfer(address indexed from, address indexed to, uint256 value); 81 | 82 | /** 83 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 84 | * a call to {approve}. `value` is the new allowance. 85 | */ 86 | event Approval( 87 | address indexed owner, 88 | address indexed spender, 89 | uint256 value 90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /oz/token/ERC20/SafeERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./IERC20.sol"; 4 | import "../../math/SafeMath.sol"; 5 | import "../../utils/Address.sol"; 6 | 7 | /** 8 | * @title SafeERC20 9 | * @dev Wrappers around ERC20 operations that throw on failure (when the token 10 | * contract returns false). Tokens that return no value (and instead revert or 11 | * throw on failure) are also supported, non-reverting calls are assumed to be 12 | * successful. 13 | * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract, 14 | * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. 15 | */ 16 | library SafeERC20 { 17 | using SafeMath for uint256; 18 | using Address for address; 19 | 20 | function safeTransfer( 21 | IERC20 token, 22 | address to, 23 | uint256 value 24 | ) internal { 25 | callOptionalReturn( 26 | token, 27 | abi.encodeWithSelector(token.transfer.selector, to, value) 28 | ); 29 | } 30 | 31 | function safeTransferFrom( 32 | IERC20 token, 33 | address from, 34 | address to, 35 | uint256 value 36 | ) internal { 37 | callOptionalReturn( 38 | token, 39 | abi.encodeWithSelector(token.transferFrom.selector, from, to, value) 40 | ); 41 | } 42 | 43 | function safeApprove( 44 | IERC20 token, 45 | address spender, 46 | uint256 value 47 | ) internal { 48 | // safeApprove should only be called when setting an initial allowance, 49 | // or when resetting it to zero. To increase and decrease it, use 50 | // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' 51 | // solhint-disable-next-line max-line-length 52 | require( 53 | (value == 0) || (token.allowance(address(this), spender) == 0), 54 | "SafeERC20: approve from non-zero to non-zero allowance" 55 | ); 56 | callOptionalReturn( 57 | token, 58 | abi.encodeWithSelector(token.approve.selector, spender, value) 59 | ); 60 | } 61 | 62 | function safeIncreaseAllowance( 63 | IERC20 token, 64 | address spender, 65 | uint256 value 66 | ) internal { 67 | uint256 newAllowance = 68 | token.allowance(address(this), spender).add(value); 69 | callOptionalReturn( 70 | token, 71 | abi.encodeWithSelector( 72 | token.approve.selector, 73 | spender, 74 | newAllowance 75 | ) 76 | ); 77 | } 78 | 79 | function safeDecreaseAllowance( 80 | IERC20 token, 81 | address spender, 82 | uint256 value 83 | ) internal { 84 | uint256 newAllowance = 85 | token.allowance(address(this), spender).sub( 86 | value, 87 | "SafeERC20: decreased allowance below zero" 88 | ); 89 | callOptionalReturn( 90 | token, 91 | abi.encodeWithSelector( 92 | token.approve.selector, 93 | spender, 94 | newAllowance 95 | ) 96 | ); 97 | } 98 | 99 | /** 100 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement 101 | * on the return value: the return value is optional (but if data is returned, it must not be false). 102 | * @param token The token targeted by the call. 103 | * @param data The call data (encoded using abi.encode or one of its variants). 104 | */ 105 | function callOptionalReturn(IERC20 token, bytes memory data) private { 106 | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since 107 | // we're implementing it ourselves. 108 | 109 | // A Solidity high level call has three parts: 110 | // 1. The target address is checked to verify it contains contract code 111 | // 2. The call itself is made, and success asserted 112 | // 3. The return value is decoded, which in turn checks the size of the returned data. 113 | // solhint-disable-next-line max-line-length 114 | require(address(token).isContract(), "SafeERC20: call to non-contract"); 115 | 116 | // solhint-disable-next-line avoid-low-level-calls 117 | (bool success, bytes memory returndata) = address(token).call(data); 118 | require(success, "SafeERC20: low-level call failed"); 119 | 120 | if (returndata.length > 0) { 121 | // Return data is optional 122 | // solhint-disable-next-line max-line-length 123 | require( 124 | abi.decode(returndata, (bool)), 125 | "SafeERC20: ERC20 operation did not succeed" 126 | ); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /oz/utils/Address.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.5; 2 | 3 | /** 4 | * @dev Collection of functions related to the address type 5 | */ 6 | library Address { 7 | /** 8 | * @dev Returns true if `account` is a contract. 9 | * 10 | * [IMPORTANT] 11 | * ==== 12 | * It is unsafe to assume that an address for which this function returns 13 | * false is an externally-owned account (EOA) and not a contract. 14 | * 15 | * Among others, `isContract` will return false for the following 16 | * types of addresses: 17 | * 18 | * - an externally-owned account 19 | * - a contract in construction 20 | * - an address where a contract will be created 21 | * - an address where a contract lived, but was destroyed 22 | * ==== 23 | */ 24 | function isContract(address account) internal view returns (bool) { 25 | // According to EIP-1052, 0x0 is the value returned for not-yet created accounts 26 | // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned 27 | // for accounts without code, i.e. `keccak256('')` 28 | bytes32 codehash; 29 | 30 | bytes32 accountHash = 31 | 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; 32 | // solhint-disable-next-line no-inline-assembly 33 | assembly { 34 | codehash := extcodehash(account) 35 | } 36 | return (codehash != accountHash && codehash != 0x0); 37 | } 38 | 39 | /** 40 | * @dev Converts an `address` into `address payable`. Note that this is 41 | * simply a type cast: the actual underlying value is not changed. 42 | * 43 | * _Available since v2.4.0._ 44 | */ 45 | function toPayable(address account) 46 | internal 47 | pure 48 | returns (address payable) 49 | { 50 | return address(uint160(account)); 51 | } 52 | 53 | /** 54 | * @dev Replacement for Solidity's `transfer`: sends `amount` wei to 55 | * `recipient`, forwarding all available gas and reverting on errors. 56 | * 57 | * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost 58 | * of certain opcodes, possibly making contracts go over the 2300 gas limit 59 | * imposed by `transfer`, making them unable to receive funds via 60 | * `transfer`. {sendValue} removes this limitation. 61 | * 62 | * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. 63 | * 64 | * IMPORTANT: because control is transferred to `recipient`, care must be 65 | * taken to not create reentrancy vulnerabilities. Consider using 66 | * {ReentrancyGuard} or the 67 | * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. 68 | * 69 | * _Available since v2.4.0._ 70 | */ 71 | function sendValue(address payable recipient, uint256 amount) internal { 72 | require( 73 | address(this).balance >= amount, 74 | "Address: insufficient balance" 75 | ); 76 | 77 | // solhint-disable-next-line avoid-call-value 78 | (bool success, ) = recipient.call.value(amount)(""); 79 | require( 80 | success, 81 | "Address: unable to send value, recipient may have reverted" 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Zapper-API-Stack", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "SuhailG ", 6 | "license": "MIT", 7 | "scripts": { 8 | "format": "npx prettier contracts/**/*.sol", 9 | "format:fix": "npx prettier --write contracts/**/*.sol", 10 | "just-test": "npx mocha --exit --recursive --timeout 200000 ./test/ZapConsumer.test.js", 11 | "test": "truffle compile && npm run just-test" 12 | }, 13 | "devDependencies": { 14 | "@openzeppelin/test-environment": "^0.1.9", 15 | "@openzeppelin/test-helpers": "^0.5.10", 16 | "axios": "^0.21.1", 17 | "chai": "^4.3.4", 18 | "dotenv": "^8.2.0", 19 | "husky": "^6.0.0", 20 | "mocha": "^8.3.2", 21 | "prettier": "^2.2.1", 22 | "prettier-plugin-solidity": "^1.0.0-beta.8", 23 | "solhint": "^3.3.4", 24 | "solhint-plugin-prettier": "^0.0.5", 25 | "truffle": "^5.3.1", 26 | "truffle-token-test-utils": "^1.0.0" 27 | }, 28 | "husky": { 29 | "hooks": { 30 | "pre-commit": "npm run format:fix", 31 | "pre-push": "npm run format:fix" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test-environment.config.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | 3 | module.exports = { 4 | accounts: { 5 | amount: 20, // Number of unlocked accounts 6 | ether: 1e6, 7 | }, 8 | 9 | node: { 10 | fork: process.env.NODE_URL, 11 | 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /test/ZapConsumer.test.js: -------------------------------------------------------------------------------- 1 | const { accounts, contract, web3 } = require("@openzeppelin/test-environment"); 2 | const { expect } = require("chai"); 3 | const { BN, ether, constants } = require("@openzeppelin/test-helpers"); 4 | 5 | const tokenTransfers = require("truffle-token-test-utils"); 6 | tokenTransfers.setWeb3(web3); 7 | 8 | const { getZapInData, userZapIn, toDecimal } = require("../api/ZapperAPI"); 9 | 10 | const IERC20 = contract.fromArtifact("IERC20"); 11 | const ZapConsumer = contract.fromArtifact("Zap_Consumer_V1"); 12 | 13 | describe("Zap Consumer", () => { 14 | const admin = accounts[0]; 15 | const toWhomToIssue = accounts[1]; 16 | 17 | const Sushiswap_DAI_WETH = "0xC3D03e4F041Fd4cD388c549Ee2A29a9E5075882f"; //DAI & WETH 18 | 19 | const Curve_alUSD = "0x43b4fdfd4ff969587185cdb6f0bd875c5fc83f8c"; 20 | 21 | before(async () => { 22 | this.zapConsumer = await ZapConsumer.new({ from: admin }); 23 | }); 24 | context.only("With Web3", () => { 25 | it("Should add liquidity to Sushiswap DAI/WETH pool with ETH", async () => { 26 | const data = await userZapIn({ 27 | toWhomToIssue, 28 | sellToken: constants.ZERO_ADDRESS, 29 | sellAmount: ether("1"), 30 | poolAddress: Sushiswap_DAI_WETH, 31 | protocol: "sushiswap", 32 | }); 33 | 34 | console.log({ ZapperApiData: data }); 35 | 36 | const tokenInstance = await IERC20.at(Sushiswap_DAI_WETH); 37 | 38 | const initialBalance = await tokenInstance.balanceOf(toWhomToIssue); 39 | console.log({ initialBalance: initialBalance.toString() / 10 ** 18 }); 40 | 41 | await web3.eth.sendTransaction(data); 42 | 43 | const finalBalance = await tokenInstance.balanceOf(toWhomToIssue); 44 | console.log({ finalBalance: finalBalance.toString() / 10 ** 18 }); 45 | 46 | expect(finalBalance).to.be.bignumber.gt(initialBalance); 47 | }); 48 | }); 49 | context.only("With a Smart Contract", () => { 50 | it("Should add liquidity Curve alUSD ", async () => { 51 | // Assemble sETH Curve pool Zap In with USDC 52 | const data = await getZapInData({ 53 | ownerAddress: this.zapConsumer.address, 54 | sellToken: constants.ZERO_ADDRESS, 55 | sellAmount: ether("1"), 56 | poolAddress: Curve_alUSD, 57 | protocol: "curve", 58 | }); 59 | 60 | console.log({ ZapperApiData: data }); 61 | 62 | tokenInstance = await IERC20.at(Curve_alUSD); 63 | 64 | const initialBalance = await tokenInstance.balanceOf(toWhomToIssue); 65 | console.log({ initialBalance: initialBalance.toString() / 10 ** 18 }); 66 | 67 | const tx = await this.zapConsumer.Zap( 68 | data.sellTokenAddress, 69 | data.sellTokenAmount, 70 | data.buyTokenAddress, 71 | data.to, 72 | data.data, 73 | { from: toWhomToIssue, value: data.value } 74 | ); 75 | 76 | await tokenTransfers.print(tx); 77 | 78 | const finalBalance = await tokenInstance.balanceOf(toWhomToIssue); 79 | console.log({ finalBalance: finalBalance.toString() / 10 ** 18 }); 80 | 81 | expect(finalBalance).to.be.bignumber.gt(initialBalance); 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Configure your compilers 3 | compilers: { 4 | solc: { 5 | version: "0.5.17", // Fetch exact version from solc-bin (default: truffle's version) 6 | settings: { 7 | // See the solidity docs for advice about optimization and evmVersion 8 | optimizer: { 9 | enabled: true, 10 | runs: 200, 11 | }, 12 | }, 13 | }, 14 | }, 15 | }; 16 | --------------------------------------------------------------------------------