├── .gitignore ├── foundry.toml ├── .gitmodules ├── .github └── workflows │ └── test.yml ├── test ├── OthersideClaimViaFlashloan.t..sol └── OthersideClaimViaSingleSwaps.t.sol └── src ├── OthersideClaimViaFlashloan.sol └── OthersideClaimViaSingleSwaps.sol /.gitignore: -------------------------------------------------------------------------------- 1 | cache/ 2 | out/ 3 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | 6 | # See more config options https://github.com/gakonst/foundry/tree/master/config -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/ds-test"] 2 | path = lib/ds-test 3 | url = https://github.com/dapphub/ds-test 4 | [submodule "lib/forge-std"] 5 | path = lib/forge-std 6 | url = https://github.com/foundry-rs/forge-std 7 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /test/OthersideClaimViaFlashloan.t..sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "ds-test/test.sol"; 5 | import "forge-std/console.sol"; 6 | import {OthersideClaimViaFlashloan} from "../src/OthersideClaimViaFlashloan.sol"; 7 | 8 | interface IERC721 { 9 | function balanceOf(address owner) external view returns (uint256); 10 | 11 | function setApprovalForAll(address operator, bool _approved) external; 12 | 13 | function tokenOfOwnerByIndex(address owner, uint256 index) 14 | external 15 | view 16 | returns (uint256 tokenId); 17 | 18 | function transferFrom( 19 | address from, 20 | address to, 21 | uint256 tokenId 22 | ) external; 23 | } 24 | 25 | contract OthersideClaimViaFlashloanTest is DSTest { 26 | OthersideClaimViaFlashloan bot; 27 | 28 | function setUp() public { 29 | bot = new OthersideClaimViaFlashloan(); 30 | } 31 | 32 | function testExample() public { 33 | bot.flash(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/OthersideClaimViaSingleSwaps.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "ds-test/test.sol"; 5 | import "forge-std/console.sol"; 6 | import {OthersideClaimViaSingleSwaps} from "../src/OthersideClaimViaSingleSwaps.sol"; 7 | 8 | interface Vm { 9 | function startPrank(address) external; 10 | } 11 | 12 | interface IERC721 { 13 | function setApprovalForAll(address operator, bool _approved) external; 14 | } 15 | 16 | contract OthersideClaimViaSingleSwapsTest is DSTest { 17 | OthersideClaimViaSingleSwaps bot; 18 | Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); 19 | address baycNFT = 0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D; 20 | 21 | function setUp() public { 22 | bot = new OthersideClaimViaSingleSwaps(); 23 | } 24 | 25 | function testExample() public { 26 | vm.startPrank(address(0xA045dfB9b742b06160fac011A27F06d854e80f64)); 27 | IERC721(baycNFT).setApprovalForAll(address(bot), true); 28 | 29 | bot.flash(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/OthersideClaimViaFlashloan.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity ^0.8.13; 4 | import "forge-std/console.sol"; 5 | 6 | interface IERC20 { 7 | function approve(address spender, uint256 amount) external returns (bool); 8 | 9 | function balanceOf(address owner) external view returns (uint256); 10 | } 11 | 12 | interface IERC721 { 13 | function balanceOf(address owner) external view returns (uint256); 14 | 15 | function setApprovalForAll(address operator, bool _approved) external; 16 | 17 | function tokenOfOwnerByIndex(address owner, uint256 index) 18 | external 19 | view 20 | returns (uint256 tokenId); 21 | } 22 | 23 | interface IERC3156FlashBorrowerUpgradeable { 24 | function onFlashLoan( 25 | address initiator, 26 | address token, 27 | uint256 amount, 28 | uint256 fee, 29 | bytes calldata data 30 | ) external returns (bytes32); 31 | } 32 | 33 | interface NftxVault { 34 | function flashLoan( 35 | IERC3156FlashBorrowerUpgradeable receiver, 36 | address token, 37 | uint256 amount, 38 | bytes calldata data 39 | ) external returns (bool); 40 | 41 | function redeem(uint256 amount, uint256[] calldata specificIds) 42 | external 43 | returns (uint256[] calldata); 44 | 45 | function mint( 46 | uint256[] calldata tokenIds, 47 | uint256[] calldata amounts /* ignored for ERC721 vaults */ 48 | ) external returns (uint256); 49 | } 50 | 51 | interface Otherside { 52 | function nftOwnerClaimLand( 53 | uint256[] calldata alphaTokenIds, 54 | uint256[] calldata betaTokenIds 55 | ) external; 56 | } 57 | 58 | // This is a little gross but it gets the job done 59 | contract OthersideClaimViaFlashloan is IERC3156FlashBorrowerUpgradeable { 60 | address baycNFT = 0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D; 61 | address baycNftx = 0xEA47B64e1BFCCb773A0420247C0aa0a3C1D2E5C5; 62 | address otherside; // fill address 63 | 64 | function flash() public { 65 | IERC20(baycNftx).approve(baycNftx, type(uint256).max); 66 | 67 | NftxVault(baycNftx).flashLoan( 68 | IERC3156FlashBorrowerUpgradeable(address(this)), 69 | baycNftx, 70 | 10 ether, // loan enough tokens to claim all 5 apes in the vault 71 | "" 72 | ); 73 | } 74 | 75 | function onFlashLoan( 76 | address initiator, 77 | address token, 78 | uint256 amount, 79 | uint256 fee, 80 | bytes calldata data 81 | ) external returns (bytes32) { 82 | console.log("Recieved FL: Amount, Fees", amount, fee); 83 | console.log("\n"); 84 | 85 | // redeem all 5 apes 86 | NftxVault(baycNftx).redeem(5, new uint256[](0)); 87 | console.log("Redeeming 5 Apes \n"); 88 | 89 | uint256 BaycNftBalance = IERC721(baycNFT).balanceOf(address(this)); 90 | uint256 BaycERC20Balance = IERC20(baycNftx).balanceOf(address(this)); 91 | 92 | console.log("Cost to redeem 5 apes", amount - BaycERC20Balance); 93 | console.log("initial balance BAYC NFTs", BaycNftBalance); 94 | console.log("initial balance BAYC ERC20", BaycERC20Balance); 95 | console.log("\n"); 96 | 97 | // get tokenIds of apes in array, we can probably hard code this 98 | // since we know what apes are in the vault already 99 | uint256[] memory tokenIds = new uint256[](BaycNftBalance); 100 | 101 | for (uint256 i; i < IERC721(baycNFT).balanceOf(address(this)); ++i) { 102 | uint256 tokenId = IERC721(baycNFT).tokenOfOwnerByIndex( 103 | address(this), 104 | i 105 | ); 106 | 107 | tokenIds[i] = tokenId; 108 | } 109 | 110 | // redeem lands for all apes like: 111 | // Otherside(otherside).nftOwnerClaimLand(tokenIds, new uint256[](0)); 112 | 113 | // send apes back to the vault 114 | IERC721(baycNFT).setApprovalForAll(baycNftx, true); 115 | // mint new tokens 116 | NftxVault(baycNftx).mint(tokenIds, new uint256[](0)); 117 | 118 | BaycNftBalance = IERC721(baycNFT).balanceOf(address(this)); 119 | BaycERC20Balance = IERC20(baycNftx).balanceOf(address(this)); 120 | 121 | console.log( 122 | "current balance BAYC NFTs after sending to vault", 123 | BaycNftBalance 124 | ); 125 | console.log( 126 | "current balance BAYC ERC20s after minting", 127 | BaycERC20Balance 128 | ); 129 | console.log("Fee to pay NFTX", amount - BaycERC20Balance); 130 | 131 | // Buy Sushiswap tokens to repay the fee here or send your own ape to vault to help pay fee 132 | 133 | return keccak256("ERC3156FlashBorrower.onFlashLoan"); 134 | } 135 | 136 | function onERC721Received( 137 | address, 138 | address, 139 | uint256, 140 | bytes memory 141 | ) public returns (bytes4) { 142 | return this.onERC721Received.selector; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/OthersideClaimViaSingleSwaps.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | import "forge-std/console.sol"; 4 | 5 | interface IERC20 { 6 | function approve(address spender, uint256 amount) external returns (bool); 7 | 8 | function balanceOf(address owner) external view returns (uint256); 9 | } 10 | 11 | interface IERC721 { 12 | function balanceOf(address owner) external view returns (uint256); 13 | 14 | function setApprovalForAll(address operator, bool _approved) external; 15 | 16 | function tokenOfOwnerByIndex(address owner, uint256 index) 17 | external 18 | view 19 | returns (uint256 tokenId); 20 | 21 | function transferFrom( 22 | address from, 23 | address to, 24 | uint256 tokenId 25 | ) external; 26 | } 27 | 28 | interface IERC3156FlashBorrowerUpgradeable { 29 | function onFlashLoan( 30 | address initiator, 31 | address token, 32 | uint256 amount, 33 | uint256 fee, 34 | bytes calldata data 35 | ) external returns (bytes32); 36 | } 37 | 38 | interface NftxVault { 39 | function flashLoan( 40 | IERC3156FlashBorrowerUpgradeable receiver, 41 | address token, 42 | uint256 amount, 43 | bytes calldata data 44 | ) external returns (bool); 45 | 46 | function redeem(uint256 amount, uint256[] calldata specificIds) 47 | external 48 | returns (uint256[] calldata); 49 | 50 | function mint( 51 | uint256[] calldata tokenIds, 52 | uint256[] calldata amounts /* ignored for ERC721 vaults */ 53 | ) external returns (uint256); 54 | 55 | function swap( 56 | uint256[] calldata tokenIds, 57 | uint256[] calldata amounts, /* ignored for ERC721 vaults */ 58 | uint256[] calldata specificIds 59 | ) external returns (uint256[] calldata); 60 | } 61 | 62 | interface Otherside { 63 | function nftOwnerClaimLand( 64 | uint256[] calldata alphaTokenIds, 65 | uint256[] calldata betaTokenIds 66 | ) external; 67 | } 68 | 69 | contract OthersideClaimViaSingleSwaps is IERC3156FlashBorrowerUpgradeable { 70 | address baycNFT = 0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D; 71 | address baycNftx = 0xEA47B64e1BFCCb773A0420247C0aa0a3C1D2E5C5; 72 | address otherside; // fill address 73 | 74 | function flash() public { 75 | // transfer my own ape to this contract 76 | IERC721(baycNFT).transferFrom(msg.sender, address(this), 8558); 77 | IERC20(baycNftx).approve(baycNftx, type(uint256).max); 78 | 79 | // flashloan to pay for swaps 80 | NftxVault(baycNftx).flashLoan( 81 | IERC3156FlashBorrowerUpgradeable(address(this)), 82 | baycNftx, 83 | 10 ether, // arbitrary number, just loan enough for the NFTX swaps 84 | "" 85 | ); 86 | } 87 | 88 | function onFlashLoan( 89 | address initiator, 90 | address token, 91 | uint256 amount, 92 | uint256 fee, 93 | bytes calldata data 94 | ) external returns (bytes32) { 95 | console.log("Recieved FL: Amount, Fees", amount, fee); 96 | console.log("\n"); 97 | 98 | IERC721(baycNFT).setApprovalForAll(baycNftx, true); 99 | 100 | uint256[] memory tokenIdtoSwap = new uint256[](1); 101 | uint256[] memory tokenIdRedeem = new uint256[](1); 102 | 103 | // redeem land here for ape 8558 here 104 | // Otherside(otherside).nftOwnerClaimLand(tokenIdtoSwap, new uint256[](0)); 105 | 106 | tokenIdtoSwap[0] = 8558; 107 | tokenIdRedeem[0] = 3391; 108 | NftxVault(baycNftx).swap( 109 | tokenIdtoSwap, 110 | new uint256[](0), 111 | tokenIdRedeem 112 | ); 113 | 114 | // redeem land here for ape 3391 here 115 | // Otherside(otherside).nftOwnerClaimLand(tokenIdtoSwap, new uint256[](0)); 116 | 117 | tokenIdtoSwap[0] = 3391; 118 | tokenIdRedeem[0] = 2787; 119 | NftxVault(baycNftx).swap( 120 | tokenIdtoSwap, 121 | new uint256[](0), 122 | tokenIdRedeem 123 | ); 124 | 125 | // redeem land here for ape 2787 here 126 | // Otherside(otherside).nftOwnerClaimLand(tokenIdtoSwap, new uint256[](0)); 127 | 128 | tokenIdtoSwap[0] = 2787; 129 | tokenIdRedeem[0] = 4755; 130 | NftxVault(baycNftx).swap( 131 | tokenIdtoSwap, 132 | new uint256[](0), 133 | tokenIdRedeem 134 | ); 135 | 136 | // redeem land here for ape 4755 here 137 | // Otherside(otherside).nftOwnerClaimLand(tokenIdtoSwap, new uint256[](0)); 138 | 139 | tokenIdtoSwap[0] = 4755; 140 | tokenIdRedeem[0] = 8167; 141 | NftxVault(baycNftx).swap( 142 | tokenIdtoSwap, 143 | new uint256[](0), 144 | tokenIdRedeem 145 | ); 146 | // redeem land here for ape 8167 here 147 | // Otherside(otherside).nftOwnerClaimLand(tokenIdtoSwap, new uint256[](0)); 148 | 149 | tokenIdtoSwap[0] = 8167; 150 | tokenIdRedeem[0] = 8214; 151 | NftxVault(baycNftx).swap( 152 | tokenIdtoSwap, 153 | new uint256[](0), 154 | tokenIdRedeem 155 | ); 156 | 157 | // redeem land here for ape 8214 here 158 | // Otherside(otherside).nftOwnerClaimLand(tokenIdtoSwap, new uint256[](0)); 159 | 160 | uint256 BaycERC20Balance = IERC20(baycNftx).balanceOf(address(this)); 161 | 162 | // can send the ape back to the vault to help pay fees 163 | // NftxVault(baycNftx).mint(tokenIdtoSwap, new uint256[](0)); 164 | 165 | console.log("Fee to pay NFTX", amount - BaycERC20Balance); 166 | 167 | // pay fee here 168 | 169 | return keccak256("ERC3156FlashBorrower.onFlashLoan"); 170 | } 171 | 172 | function onERC721Received( 173 | address, 174 | address, 175 | uint256, 176 | bytes memory 177 | ) public returns (bytes4) { 178 | return this.onERC721Received.selector; 179 | } 180 | } 181 | --------------------------------------------------------------------------------