├── ganache ├── .gitignore ├── .solcover.js ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── .travis.yml ├── truffle.js ├── contracts ├── Migrations.sol ├── ERC20.sol ├── AtomicSwapEtherToERC20.sol ├── Test2ERC20.sol ├── TestERC20.sol ├── AtomicSwapEther.sol ├── AtomicSwapERC20ToERC20.sol └── AtomicSwapERC20.sol ├── package.json ├── LICENSE ├── test ├── AtomicSwapEther.js ├── AtomicSwapEtherToERC20.js ├── AtomicSwapERC20.js ├── AtomicSwapERC20ToERC20.js ├── Test2ERC20.js └── TestERC20.js ├── README.md └── coverage.json /ganache: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | $(npm bin)/ganache-cli -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | build 4 | coverage 5 | coverageEnv 6 | scTopics 7 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ['TestERC20.sol', 'Test2ERC20.sol'] 3 | }; -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | group: beta 4 | language: node_js 5 | node_js: 6 | - "8" 7 | cache: 8 | directories: 9 | - node_modules 10 | before_script: 11 | - npm install --global truffle 12 | - npm install --global ganache-cli 13 | - truffle version 14 | script: 15 | - npm run coverage 16 | - cat coverage/lcov.info | ./node_modules/.bin/coveralls 17 | 18 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "localhost", 5 | port: 8545, 6 | network_id: "*" // Match any network id 7 | }, 8 | coverage: { 9 | host: "localhost", 10 | network_id: "*", 11 | port: 8555, // <-- If you change this, also set the port option in .solcover.js. 12 | gas: 0xfffffffffff, // <-- Use this high gas value 13 | gasPrice: 0x01 // <-- Use this low gas price 14 | }, 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) { 9 | _; 10 | } 11 | } 12 | 13 | function Migrations() public { 14 | owner = msg.sender; 15 | } 16 | 17 | function setCompleted(uint completed) public restricted { 18 | last_completed_migration = completed; 19 | } 20 | 21 | function upgrade(address newAddress) public restricted { 22 | Migrations upgraded = Migrations(newAddress); 23 | upgraded.setCompleted(last_completed_migration); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var Ether2ERC20 = artifacts.require("./AtomicSwapEtherToERC20.sol"); 2 | var Ether = artifacts.require("./AtomicSwapEther.sol"); 3 | var ERC202ERC20 = artifacts.require("./AtomicSwapERC20ToERC20.sol"); 4 | var ERC20 = artifacts.require("./AtomicSwapERC20.sol"); 5 | var TestERC20 = artifacts.require("./TestERC20.sol"); 6 | var Test2ERC20 = artifacts.require("./Test2ERC20.sol"); 7 | 8 | module.exports = function(deployer) { 9 | deployer.deploy(Ether2ERC20); 10 | deployer.deploy(Ether); 11 | deployer.deploy(ERC202ERC20); 12 | deployer.deploy(ERC20); 13 | deployer.deploy(TestERC20); 14 | deployer.deploy(Test2ERC20); 15 | }; 16 | -------------------------------------------------------------------------------- /contracts/ERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | contract ERC20 { 4 | uint public totalSupply; 5 | 6 | event Transfer(address indexed from, address indexed to, uint value); 7 | event Approval(address indexed owner, address indexed spender, uint value); 8 | 9 | function balanceOf(address who) public constant returns (uint); 10 | function allowance(address owner, address spender) public constant returns (uint); 11 | 12 | function transfer(address to, uint value) public returns (bool ok); 13 | function transferFrom(address from, address to, uint value) public returns (bool ok); 14 | function approve(address spender, uint value) public returns (bool ok); 15 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atomic-swap", 3 | "version": "0.0.0", 4 | "description": "The Ethereum Atomic Swap library is an official reference implementation of atomic swaps on Ethereum for the Republic Protocol, written in Solidity. This library supports atomic swaps between Ether and ERC20 pairs, as well as the Ether and ERC20 side of cross-chain atomic swaps.", 5 | "main": "truffle.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "truffle test", 11 | "coverage": "./node_modules/.bin/solidity-coverage" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/republicprotocol/atomic-swap.git" 16 | }, 17 | "author": "Nadimpalli Susruth", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/republicprotocol/atomic-swap/issues" 21 | }, 22 | "homepage": "https://github.com/republicprotocol/atomic-swap#readme", 23 | "devDependencies": { 24 | "solidity-coverage": "^0.4.8" 25 | }, 26 | "dependencies": { 27 | "coveralls": "^3.0.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Republic Protocol 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/AtomicSwapEther.js: -------------------------------------------------------------------------------- 1 | const atomicSwap = artifacts.require("./AtomicSwapEther.sol"); 2 | 3 | contract('Cross Chain Atomic Swap with Ether', (accounts) => { 4 | 5 | const lock = "0x261c74f7dd1ed6a069e18375ab2bee9afcb1095613f53b07de11829ac66cdfcc"; 6 | const key = "0x42a990655bffe188c9823a2f914641a32dcbb1b28e8586bd29af291db7dcd4e8"; 7 | const swapID_swap = "0x0505915948dcd6756a8f5169e9c539b69d87d9a4b8f57cbb40867d9f91790211"; 8 | const swapID_expiry = "0xc3b89738306a66a399755e8535300c42b1423cac321938e7fe30b252abf8fe74"; 9 | 10 | it("Deposit ether into the contract", async () => { 11 | const swap = await atomicSwap.deployed(); 12 | const timeout = 100; // seconds 13 | await swap.open(swapID_swap, accounts[0], lock, timeout, {from: accounts[0], value: 50000}) 14 | }); 15 | 16 | it("Check the ether in the lock box", async () => { 17 | const swap = await atomicSwap.deployed(); 18 | const result = await swap.check(swapID_swap); 19 | 20 | assert.equal(result[1].toNumber(),50000); 21 | assert.equal(result[2].toString(),accounts[0]); 22 | assert.equal(result[3].toString(),lock); 23 | }) 24 | 25 | it("Withdraw the ether from the lockbox", async () => { 26 | const swap = await atomicSwap.deployed(); 27 | await swap.close(swapID_swap, key); 28 | }) 29 | 30 | it("Get secret key from the contract", async () => { 31 | const swap = await atomicSwap.deployed(); 32 | const secretkey = await swap.checkSecretKey(swapID_swap); 33 | assert.equal(secretkey.toString(), key); 34 | }) 35 | 36 | it("Deposit ether into the contract", async () => { 37 | const swap = await atomicSwap.deployed(); 38 | const timeout = 2; // seconds 39 | await swap.open(swapID_expiry, accounts[0], lock, timeout, {from: accounts[0], value: 50000}) 40 | }); 41 | 42 | it("Withdraw after expiry", async () => { 43 | await new Promise((resolve, reject) => setTimeout(async () => { 44 | try { 45 | const swap = await atomicSwap.deployed(); 46 | await swap.expire(swapID_expiry, {from: accounts[0]}); 47 | resolve(); 48 | } catch (err) { 49 | reject(err); 50 | } 51 | }, 2 * 1000)); 52 | }); 53 | 54 | }); 55 | -------------------------------------------------------------------------------- /test/AtomicSwapEtherToERC20.js: -------------------------------------------------------------------------------- 1 | const atomicSwap = artifacts.require("./AtomicSwapEtherToERC20.sol"); 2 | const testERC20 = artifacts.require("./TestERC20.sol"); 3 | 4 | contract('Atomic swap between ether and erc20', (accounts) => { 5 | const alice = accounts[3]; 6 | const bob = accounts[4]; 7 | const swapID_swap = "0x261c74f7dd1ed6a069e18375ab2bee9afcb1095613f53b07de11829ac66cdfcc"; 8 | const swapID_expiry = "0xc3b89738306a66a399755e8535300c42b1423cac321938e7fe30b252abf8fe74"; 9 | // Expected Trade: 10 | 11 | const etherValue = 50000; 12 | const erc20Value = 100000; 13 | 14 | it("Alice deposits ether into the contract", async () => { 15 | const swap = await atomicSwap.deployed(); 16 | const token = await testERC20.deployed(); 17 | await swap.open(swapID_swap, erc20Value, bob, token.address, {from:alice, value: etherValue}); 18 | }); 19 | 20 | it("Bob checks the ether in the lock box", async () => { 21 | const swap = await atomicSwap.deployed(); 22 | const token = await testERC20.deployed(); 23 | const result = await swap.check(swapID_swap); 24 | 25 | assert.equal(result[0].toNumber(),etherValue); 26 | assert.equal(result[1].toNumber(),erc20Value); 27 | assert.equal(result[2].toString(),bob); 28 | assert.equal(result[3].toString(),token.address); 29 | }) 30 | 31 | it("Bob closes the swap", async() => { 32 | const swap = await atomicSwap.deployed(); 33 | const token = await testERC20.deployed(); 34 | await token.transfer(bob, 100000, {from: accounts[0]}) 35 | await token.approve(swap.address, 100000, {from: bob}); 36 | const allowance = await token.allowance(bob,swap.address); 37 | assert.equal(100000,allowance) 38 | await swap.close(swapID_swap); 39 | }) 40 | 41 | it("Alice deposits ether into the contract", async () => { 42 | const swap = await atomicSwap.deployed(); 43 | const token = await testERC20.deployed(); 44 | await swap.open(swapID_expiry, bob, erc20Value, token.address, {from:alice, value: etherValue}); 45 | }); 46 | 47 | 48 | it("Alice withdraws after expiry", async () => { 49 | const swap = await atomicSwap.deployed(); 50 | await swap.expire(swapID_expiry); 51 | }) 52 | }); -------------------------------------------------------------------------------- /test/AtomicSwapERC20.js: -------------------------------------------------------------------------------- 1 | const atomicSwap = artifacts.require("./AtomicSwapERC20.sol"); 2 | const testERC20 = artifacts.require("./TestERC20.sol"); 3 | 4 | 5 | contract('Cross Chain Atomic Swap with ERC20', (accounts) => { 6 | 7 | const lock = "0x261c74f7dd1ed6a069e18375ab2bee9afcb1095613f53b07de11829ac66cdfcc"; 8 | const key = "0x42a990655bffe188c9823a2f914641a32dcbb1b28e8586bd29af291db7dcd4e8"; 9 | 10 | const swapID_swap = "0x0505915948dcd6756a8f5169e9c539b69d87d9a4b8f57cbb40867d9f91790211"; 11 | const swapID_expiry = "0xc3b89738306a66a399755e8535300c42b1423cac321938e7fe30b252abf8fe74"; 12 | 13 | 14 | it("Deposit erc20 tokens into the contract", async () => { 15 | const swap = await atomicSwap.deployed(); 16 | const token = await testERC20.deployed(); 17 | const timeout = 100; // seconds 18 | await token.approve(swap.address, 10000); 19 | await swap.open(swapID_swap, 10000, token.address, accounts[0], lock, timeout, {from: accounts[0]}) 20 | }) 21 | 22 | it("Check the erc20 tokens in the lock box", async () => { 23 | const swap = await atomicSwap.deployed(); 24 | const token = await testERC20.deployed(); 25 | const result = await swap.check(swapID_swap); 26 | 27 | assert.equal(result[1].toNumber(),10000); 28 | assert.equal(result[2].toString(),token.address); 29 | assert.equal(result[3].toString(),accounts[0]); 30 | assert.equal(result[4].toString(),lock); 31 | }) 32 | 33 | it("Withdraw the erc20 tokens from the lockbox", async () => { 34 | const swap = await atomicSwap.deployed(); 35 | await swap.close(swapID_swap, key); 36 | }) 37 | 38 | it("Get secret key from the contract", async () => { 39 | const swap = await atomicSwap.deployed(); 40 | const secretkey = await swap.checkSecretKey(swapID_swap, {from: accounts[0]}); 41 | assert.equal(secretkey.toString(), key); 42 | }) 43 | 44 | it("Deposit erc20 tokens into the contract", async () => { 45 | const swap = await atomicSwap.deployed(); 46 | const token = await testERC20.deployed(); 47 | const timeout = 2; // seconds 48 | await token.approve(swap.address, 10000); 49 | await swap.open(swapID_expiry, 10000, token.address, accounts[0], lock, timeout, {from: accounts[0]}) 50 | }) 51 | 52 | it("Withdraw after expiry", async () => { 53 | await new Promise((resolve, reject) => setTimeout(async () => { 54 | try { 55 | const swap = await atomicSwap.deployed(); 56 | await swap.expire(swapID_expiry, {from: accounts[0]}); 57 | resolve(); 58 | } catch (err) { 59 | reject(err); 60 | } 61 | }, 2 * 1000)); 62 | }) 63 | }); 64 | 65 | -------------------------------------------------------------------------------- /test/AtomicSwapERC20ToERC20.js: -------------------------------------------------------------------------------- 1 | const atomicSwap = artifacts.require("./AtomicSwapERC20ToERC20.sol"); 2 | const openTestERC20 = artifacts.require("./TestERC20.sol"); 3 | const closeTestERC20 = artifacts.require("./Test2ERC20.sol"); 4 | 5 | contract('Atomic swap between ether and erc20', (accounts) => { 6 | const alice = accounts[0]; 7 | const bob = accounts[1]; 8 | const swapID_swap = "0x261c74f7dd1ed6a069e18375ab2bee9afcb1095613f53b07de11829ac66cdfcc"; 9 | const swapID_expiry = "0xc3b89738306a66a399755e8535300c42b1423cac321938e7fe30b252abf8fe74"; 10 | // Expected Trade: 11 | 12 | const openValue = 50000; 13 | const closeValue = 100000; 14 | 15 | it("Alice deposits ether into the contract", async () => { 16 | const swap = await atomicSwap.deployed(); 17 | const openToken = await openTestERC20.deployed(); 18 | const closeToken = await closeTestERC20.deployed(); 19 | await openToken.approve(swap.address, openValue); 20 | await swap.open(swapID_swap, openValue, openToken.address, closeValue, bob, closeToken.address, {from:alice}); 21 | }); 22 | 23 | it("Bob checks the ether in the lock box", async () => { 24 | const swap = await atomicSwap.deployed(); 25 | const openToken = await openTestERC20.deployed(); 26 | const closeToken = await closeTestERC20.deployed(); 27 | const result = await swap.check(swapID_swap); 28 | 29 | assert.equal(result[0].toNumber(),openValue); 30 | assert.equal(result[1].toString(),openToken.address); 31 | assert.equal(result[2].toNumber(),closeValue); 32 | assert.equal(result[3].toString(),bob); 33 | assert.equal(result[4].toString(),closeToken.address); 34 | }) 35 | 36 | it("Bob closes the swap", async() => { 37 | const swap = await atomicSwap.deployed(); 38 | const openToken = await openTestERC20.deployed(); 39 | const closeToken = await closeTestERC20.deployed(); 40 | await closeToken.transfer(bob, 100000, {from: alice}) 41 | await closeToken.approve(swap.address, 100000, {from: bob}); 42 | const allowance = await closeToken.allowance(bob,swap.address); 43 | assert.equal(100000,allowance) 44 | await swap.close(swapID_swap); 45 | }) 46 | 47 | it("Alice deposits ether into the contract", async () => { 48 | const swap = await atomicSwap.deployed(); 49 | const openToken = await openTestERC20.deployed(); 50 | const closeToken = await closeTestERC20.deployed(); 51 | await openToken.approve(swap.address, openValue); 52 | await swap.open(swapID_expiry, openValue, openToken.address, closeValue, bob, closeToken.address, {from:alice}); 53 | }); 54 | 55 | it("Alice withdraws after expiry", async () => { 56 | const swap = await atomicSwap.deployed(); 57 | await swap.expire(swapID_expiry); 58 | }) 59 | }); -------------------------------------------------------------------------------- /contracts/AtomicSwapEtherToERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./ERC20.sol"; 4 | 5 | contract AtomicSwapEtherToERC20 { 6 | 7 | struct Swap { 8 | uint256 value; 9 | address ethTrader; 10 | uint256 erc20Value; 11 | address erc20Trader; 12 | address erc20ContractAddress; 13 | } 14 | 15 | enum States { 16 | INVALID, 17 | OPEN, 18 | CLOSED, 19 | EXPIRED 20 | } 21 | 22 | mapping (bytes32 => Swap) private swaps; 23 | mapping (bytes32 => States) private swapStates; 24 | 25 | event Open(bytes32 _swapID, address _closeTrader); 26 | event Expire(bytes32 _swapID); 27 | event Close(bytes32 _swapID); 28 | 29 | modifier onlyInvalidSwaps(bytes32 _swapID) { 30 | require (swapStates[_swapID] == States.INVALID); 31 | _; 32 | } 33 | 34 | modifier onlyOpenSwaps(bytes32 _swapID) { 35 | require (swapStates[_swapID] == States.OPEN); 36 | _; 37 | } 38 | 39 | function open(bytes32 _swapID, uint256 _erc20Value, address _erc20Trader, address _erc20ContractAddress) public onlyInvalidSwaps(_swapID) payable { 40 | // Store the details of the swap. 41 | Swap memory swap = Swap({ 42 | value: msg.value, 43 | ethTrader: msg.sender, 44 | erc20Value: _erc20Value, 45 | erc20Trader: _erc20Trader, 46 | erc20ContractAddress: _erc20ContractAddress 47 | }); 48 | swaps[_swapID] = swap; 49 | swapStates[_swapID] = States.OPEN; 50 | 51 | Open(_swapID, _erc20Trader); 52 | } 53 | 54 | function close(bytes32 _swapID) public onlyOpenSwaps(_swapID) { 55 | // Close the swap. 56 | Swap memory swap = swaps[_swapID]; 57 | swapStates[_swapID] = States.CLOSED; 58 | 59 | // Transfer the ERC20 funds from the ERC20 trader to the ETH trader. 60 | ERC20 erc20Contract = ERC20(swap.erc20ContractAddress); 61 | require(swap.erc20Value <= erc20Contract.allowance(swap.erc20Trader, address(this))); 62 | require(erc20Contract.transferFrom(swap.erc20Trader, swap.ethTrader, swap.erc20Value)); 63 | 64 | // Transfer the ETH funds from this contract to the ERC20 trader. 65 | swap.erc20Trader.transfer(swap.value); 66 | 67 | Close(_swapID); 68 | } 69 | 70 | function expire(bytes32 _swapID) public onlyOpenSwaps(_swapID) { 71 | // Expire the swap. 72 | Swap memory swap = swaps[_swapID]; 73 | swapStates[_swapID] = States.EXPIRED; 74 | 75 | // Transfer the ETH value from this contract back to the ETH trader. 76 | swap.ethTrader.transfer(swap.value); 77 | Expire(_swapID); 78 | } 79 | 80 | function check(bytes32 _swapID) public view returns (uint256 value, uint256 erc20Value, address erc20Trader, address erc20ContractAddress) { 81 | Swap memory swap = swaps[_swapID]; 82 | return (swap.value, swap.erc20Value, swap.erc20Trader, swap.erc20ContractAddress); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /contracts/Test2ERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | contract TokenInterface { 4 | function _transfer(address _from, address _to, uint256 _value) internal returns (bool); 5 | function transfer(address _to, uint256 _value) public returns (bool); 6 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool); 7 | function _burn(address _from, uint256 _value) internal returns (bool); 8 | function burn(uint256 _value) public returns (bool); 9 | function burnFrom(address _from, uint256 _value) public returns (bool); 10 | function approve(address _spender, uint256 _value) public returns (bool); 11 | function balanceOf(address _owner) public constant returns (uint256); 12 | function allowance(address _owner, address _spender) public constant returns (uint256); 13 | 14 | event Transfer(address _from, address _to, uint256 _value); 15 | event Burn(address indexed _from, uint256 _value); 16 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 17 | } 18 | 19 | contract Test2ERC20 is TokenInterface { 20 | string public name = "Republic Token"; 21 | string public symbol = "REN"; 22 | uint8 public decimals = 18; 23 | uint256 public totalSupply = 100000 * 10 ** uint256(decimals); 24 | 25 | mapping (address => uint256) public balances; 26 | mapping (address => mapping (address => uint256)) public allowed; 27 | 28 | function Test2ERC20() public { 29 | balances[msg.sender] = totalSupply; 30 | } 31 | 32 | // Transfer amount from one account to another (may require approval) 33 | function _transfer(address _from, address _to, uint256 _value) internal returns (bool) { 34 | require(_to != 0x0 && balances[_from] >= _value && _value > 0); 35 | balances[_from] -= _value; 36 | balances[_to] += _value; 37 | Transfer(_from, _to, _value); 38 | return true; 39 | } 40 | 41 | function transfer(address _to, uint256 _value) public returns (bool) { 42 | return _transfer(msg.sender, _to, _value); 43 | } 44 | 45 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { 46 | require(_value <= allowed[_from][msg.sender]); 47 | allowed[_from][msg.sender] -= _value; 48 | return _transfer(_from, _to, _value); 49 | } 50 | 51 | // Burn amount from account (may require approval) 52 | function _burn(address _from, uint256 _value) internal returns (bool) { 53 | require(balances[_from] >= _value && _value > 0); 54 | balances[_from] -= _value; 55 | totalSupply -= _value; 56 | Burn(_from, _value); 57 | return true; 58 | } 59 | 60 | function burn(uint256 _value) public returns (bool) { 61 | return _burn(msg.sender, _value); 62 | } 63 | 64 | function burnFrom(address _from, uint256 _value) public returns (bool) { 65 | require(_value <= allowed[_from][msg.sender]); 66 | allowed[_from][msg.sender] -= _value; 67 | return _burn(_from, _value); 68 | } 69 | 70 | // Approve spender from owner's account 71 | function approve(address _spender, uint256 _value) public returns (bool) { 72 | allowed[msg.sender][_spender] = _value; 73 | Approval(msg.sender, _spender, _value); 74 | return true; 75 | } 76 | 77 | // Return balance 78 | function balanceOf(address _owner) public constant returns (uint256) { 79 | return balances[_owner]; 80 | } 81 | 82 | // Return allowance 83 | function allowance(address _owner, address _spender) public constant returns (uint256) { 84 | return allowed[_owner][_spender]; 85 | } 86 | } -------------------------------------------------------------------------------- /contracts/TestERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | contract TokenInterface { 4 | function _transfer(address _from, address _to, uint256 _value) internal returns (bool); 5 | function transfer(address _to, uint256 _value) public returns (bool); 6 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool); 7 | function _burn(address _from, uint256 _value) internal returns (bool); 8 | function burn(uint256 _value) public returns (bool); 9 | function burnFrom(address _from, uint256 _value) public returns (bool); 10 | function approve(address _spender, uint256 _value) public returns (bool); 11 | function balanceOf(address _owner) public constant returns (uint256); 12 | function allowance(address _owner, address _spender) public constant returns (uint256); 13 | 14 | event Transfer(address _from, address _to, uint256 _value); 15 | event Burn(address indexed _from, uint256 _value); 16 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 17 | } 18 | 19 | contract TestERC20 is TokenInterface { 20 | string public name = "Republic Token"; 21 | string public symbol = "REN"; 22 | uint8 public decimals = 18; 23 | uint256 public totalSupply = 100000 * 10 ** uint256(decimals); 24 | 25 | mapping (address => uint256) public balances; 26 | mapping (address => mapping (address => uint256)) public allowed; 27 | 28 | function TestERC20() public { 29 | balances[msg.sender] = totalSupply; 30 | } 31 | 32 | // Transfer amount from one account to another (may require approval) 33 | function _transfer(address _from, address _to, uint256 _value) internal returns (bool) { 34 | require(_to != 0x0 && balances[_from] >= _value && _value > 0); 35 | balances[_from] -= _value; 36 | balances[_to] += _value; 37 | Transfer(_from, _to, _value); 38 | return true; 39 | } 40 | 41 | function transfer(address _to, uint256 _value) public returns (bool) { 42 | return _transfer(msg.sender, _to, _value); 43 | } 44 | 45 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { 46 | require(_value <= allowed[_from][msg.sender]); 47 | allowed[_from][msg.sender] -= _value; 48 | return _transfer(_from, _to, _value); 49 | } 50 | 51 | // Burn amount from account (may require approval) 52 | function _burn(address _from, uint256 _value) internal returns (bool) { 53 | require(balances[_from] >= _value && _value > 0); 54 | balances[_from] -= _value; 55 | totalSupply -= _value; 56 | Burn(_from, _value); 57 | return true; 58 | } 59 | 60 | function burn(uint256 _value) public returns (bool) { 61 | return _burn(msg.sender, _value); 62 | } 63 | 64 | function burnFrom(address _from, uint256 _value) public returns (bool) { 65 | require(_value <= allowed[_from][msg.sender]); 66 | allowed[_from][msg.sender] -= _value; 67 | return _burn(_from, _value); 68 | } 69 | 70 | // Approve spender from owner's account 71 | function approve(address _spender, uint256 _value) public returns (bool) { 72 | allowed[msg.sender][_spender] = _value; 73 | Approval(msg.sender, _spender, _value); 74 | return true; 75 | } 76 | 77 | // Return balance 78 | function balanceOf(address _owner) public constant returns (uint256) { 79 | return balances[_owner]; 80 | } 81 | 82 | // Return allowance 83 | function allowance(address _owner, address _spender) public constant returns (uint256) { 84 | return allowed[_owner][_spender]; 85 | } 86 | } -------------------------------------------------------------------------------- /contracts/AtomicSwapEther.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | contract AtomicSwapEther { 4 | 5 | struct Swap { 6 | uint256 timelock; 7 | uint256 value; 8 | address ethTrader; 9 | address withdrawTrader; 10 | bytes32 secretLock; 11 | bytes secretKey; 12 | } 13 | 14 | enum States { 15 | INVALID, 16 | OPEN, 17 | CLOSED, 18 | EXPIRED 19 | } 20 | 21 | mapping (bytes32 => Swap) private swaps; 22 | mapping (bytes32 => States) private swapStates; 23 | 24 | event Open(bytes32 _swapID, address _withdrawTrader,bytes32 _secretLock); 25 | event Expire(bytes32 _swapID); 26 | event Close(bytes32 _swapID, bytes _secretKey); 27 | 28 | modifier onlyInvalidSwaps(bytes32 _swapID) { 29 | require (swapStates[_swapID] == States.INVALID); 30 | _; 31 | } 32 | 33 | modifier onlyOpenSwaps(bytes32 _swapID) { 34 | require (swapStates[_swapID] == States.OPEN); 35 | _; 36 | } 37 | 38 | modifier onlyClosedSwaps(bytes32 _swapID) { 39 | require (swapStates[_swapID] == States.CLOSED); 40 | _; 41 | } 42 | 43 | modifier onlyExpirableSwaps(bytes32 _swapID) { 44 | require (now >= swaps[_swapID].timelock); 45 | _; 46 | } 47 | 48 | modifier onlyWithSecretKey(bytes32 _swapID, bytes _secretKey) { 49 | // TODO: Require _secretKey length to conform to the spec 50 | require (swaps[_swapID].secretLock == sha256(_secretKey)); 51 | _; 52 | } 53 | 54 | function open(bytes32 _swapID, address _withdrawTrader, bytes32 _secretLock, uint256 _timelock) public onlyInvalidSwaps(_swapID) payable { 55 | // Store the details of the swap. 56 | Swap memory swap = Swap({ 57 | timelock: _timelock, 58 | value: msg.value, 59 | ethTrader: msg.sender, 60 | withdrawTrader: _withdrawTrader, 61 | secretLock: _secretLock, 62 | secretKey: new bytes(0) 63 | }); 64 | swaps[_swapID] = swap; 65 | swapStates[_swapID] = States.OPEN; 66 | 67 | // Trigger open event. 68 | Open(_swapID, _withdrawTrader, _secretLock); 69 | } 70 | 71 | function close(bytes32 _swapID, bytes _secretKey) public onlyOpenSwaps(_swapID) onlyWithSecretKey(_swapID, _secretKey) { 72 | // Close the swap. 73 | Swap memory swap = swaps[_swapID]; 74 | swaps[_swapID].secretKey = _secretKey; 75 | swapStates[_swapID] = States.CLOSED; 76 | 77 | // Transfer the ETH funds from this contract to the withdrawing trader. 78 | swap.withdrawTrader.transfer(swap.value); 79 | 80 | // Trigger close event. 81 | Close(_swapID, _secretKey); 82 | } 83 | 84 | function expire(bytes32 _swapID) public onlyOpenSwaps(_swapID) onlyExpirableSwaps(_swapID) { 85 | // Expire the swap. 86 | Swap memory swap = swaps[_swapID]; 87 | swapStates[_swapID] = States.EXPIRED; 88 | 89 | // Transfer the ETH value from this contract back to the ETH trader. 90 | swap.ethTrader.transfer(swap.value); 91 | 92 | // Trigger expire event. 93 | Expire(_swapID); 94 | } 95 | 96 | function check(bytes32 _swapID) public view returns (uint256 timelock, uint256 value, address withdrawTrader, bytes32 secretLock) { 97 | Swap memory swap = swaps[_swapID]; 98 | return (swap.timelock, swap.value, swap.withdrawTrader, swap.secretLock); 99 | } 100 | 101 | function checkSecretKey(bytes32 _swapID) public view onlyClosedSwaps(_swapID) returns (bytes secretKey) { 102 | Swap memory swap = swaps[_swapID]; 103 | return swap.secretKey; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /contracts/AtomicSwapERC20ToERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./ERC20.sol"; 4 | 5 | contract AtomicSwapERC20ToERC20 { 6 | 7 | struct Swap { 8 | uint256 openValue; 9 | address openTrader; 10 | address openContractAddress; 11 | uint256 closeValue; 12 | address closeTrader; 13 | address closeContractAddress; 14 | } 15 | 16 | enum States { 17 | INVALID, 18 | OPEN, 19 | CLOSED, 20 | EXPIRED 21 | } 22 | 23 | mapping (bytes32 => Swap) private swaps; 24 | mapping (bytes32 => States) private swapStates; 25 | 26 | event Open(bytes32 _swapID, address _closeTrader); 27 | event Expire(bytes32 _swapID); 28 | event Close(bytes32 _swapID); 29 | 30 | modifier onlyInvalidSwaps(bytes32 _swapID) { 31 | require (swapStates[_swapID] == States.INVALID); 32 | _; 33 | } 34 | 35 | modifier onlyOpenSwaps(bytes32 _swapID) { 36 | require (swapStates[_swapID] == States.OPEN); 37 | _; 38 | } 39 | 40 | function open(bytes32 _swapID, uint256 _openValue, address _openContractAddress, uint256 _closeValue, address _closeTrader, address _closeContractAddress) public onlyInvalidSwaps(_swapID) { 41 | // Transfer value from the opening trader to this contract. 42 | ERC20 openERC20Contract = ERC20(_openContractAddress); 43 | require(_openValue <= openERC20Contract.allowance(msg.sender, address(this))); 44 | require(openERC20Contract.transferFrom(msg.sender, address(this), _openValue)); 45 | 46 | // Store the details of the swap. 47 | Swap memory swap = Swap({ 48 | openValue: _openValue, 49 | openTrader: msg.sender, 50 | openContractAddress: _openContractAddress, 51 | closeValue: _closeValue, 52 | closeTrader: _closeTrader, 53 | closeContractAddress: _closeContractAddress 54 | }); 55 | swaps[_swapID] = swap; 56 | swapStates[_swapID] = States.OPEN; 57 | 58 | Open(_swapID, _closeTrader); 59 | } 60 | 61 | function close(bytes32 _swapID) public onlyOpenSwaps(_swapID) { 62 | // Close the swap. 63 | Swap memory swap = swaps[_swapID]; 64 | swapStates[_swapID] = States.CLOSED; 65 | 66 | // Transfer the closing funds from the closing trader to the opening trader. 67 | ERC20 closeERC20Contract = ERC20(swap.closeContractAddress); 68 | require(swap.closeValue <= closeERC20Contract.allowance(swap.closeTrader, address(this))); 69 | require(closeERC20Contract.transferFrom(swap.closeTrader, swap.openTrader, swap.closeValue)); 70 | 71 | // Transfer the opening funds from this contract to the closing trader. 72 | ERC20 openERC20Contract = ERC20(swap.openContractAddress); 73 | require(openERC20Contract.transfer(swap.closeTrader, swap.openValue)); 74 | 75 | Close(_swapID); 76 | } 77 | 78 | function expire(bytes32 _swapID) public onlyOpenSwaps(_swapID) { 79 | // Expire the swap. 80 | Swap memory swap = swaps[_swapID]; 81 | swapStates[_swapID] = States.EXPIRED; 82 | 83 | // Transfer opening value from this contract back to the opening trader. 84 | ERC20 openERC20Contract = ERC20(swap.openContractAddress); 85 | require(openERC20Contract.transfer(swap.openTrader, swap.openValue)); 86 | 87 | Expire(_swapID); 88 | } 89 | 90 | function check(bytes32 _swapID) public view returns (uint256 openValue, address openContractAddress, uint256 closeValue, address closeTrader, address closeContractAddress) { 91 | Swap memory swap = swaps[_swapID]; 92 | return (swap.openValue, swap.openContractAddress, swap.closeValue, swap.closeTrader, swap.closeContractAddress); 93 | } 94 | } -------------------------------------------------------------------------------- /contracts/AtomicSwapERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "./ERC20.sol"; 4 | 5 | contract AtomicSwapERC20 { 6 | 7 | struct Swap { 8 | uint256 timelock; 9 | uint256 erc20Value; 10 | address erc20Trader; 11 | address erc20ContractAddress; 12 | address withdrawTrader; 13 | bytes32 secretLock; 14 | bytes secretKey; 15 | } 16 | 17 | enum States { 18 | INVALID, 19 | OPEN, 20 | CLOSED, 21 | EXPIRED 22 | } 23 | 24 | mapping (bytes32 => Swap) private swaps; 25 | mapping (bytes32 => States) private swapStates; 26 | 27 | event Open(bytes32 _swapID, address _withdrawTrader,bytes32 _secretLock); 28 | event Expire(bytes32 _swapID); 29 | event Close(bytes32 _swapID, bytes _secretKey); 30 | 31 | modifier onlyInvalidSwaps(bytes32 _swapID) { 32 | require (swapStates[_swapID] == States.INVALID); 33 | _; 34 | } 35 | 36 | modifier onlyOpenSwaps(bytes32 _swapID) { 37 | require (swapStates[_swapID] == States.OPEN); 38 | _; 39 | } 40 | 41 | modifier onlyClosedSwaps(bytes32 _swapID) { 42 | require (swapStates[_swapID] == States.CLOSED); 43 | _; 44 | } 45 | 46 | modifier onlyExpirableSwaps(bytes32 _swapID) { 47 | require (swaps[_swapID].timelock <= now); 48 | _; 49 | } 50 | 51 | modifier onlyWithSecretKey(bytes32 _swapID, bytes _secretKey) { 52 | // TODO: Require _secretKey length to conform to the spec 53 | require (swaps[_swapID].secretLock == sha256(_secretKey)); 54 | _; 55 | } 56 | 57 | function open(bytes32 _swapID, uint256 _erc20Value, address _erc20ContractAddress, address _withdrawTrader, bytes32 _secretLock, uint256 _timelock) public onlyInvalidSwaps(_swapID) { 58 | require(swapStates[_swapID] == States.INVALID); 59 | // Transfer value from the ERC20 trader to this contract. 60 | ERC20 erc20Contract = ERC20(_erc20ContractAddress); 61 | require(_erc20Value <= erc20Contract.allowance(msg.sender, address(this))); 62 | require(erc20Contract.transferFrom(msg.sender, address(this), _erc20Value)); 63 | 64 | // Store the details of the swap. 65 | Swap memory swap = Swap({ 66 | timelock: _timelock, 67 | erc20Value: _erc20Value, 68 | erc20Trader: msg.sender, 69 | erc20ContractAddress: _erc20ContractAddress, 70 | withdrawTrader: _withdrawTrader, 71 | secretLock: _secretLock, 72 | secretKey: new bytes(0) 73 | }); 74 | swaps[_swapID] = swap; 75 | swapStates[_swapID] = States.OPEN; 76 | Open(_swapID, _withdrawTrader, _secretLock); 77 | } 78 | 79 | function close(bytes32 _swapID, bytes _secretKey) public onlyOpenSwaps(_swapID) onlyWithSecretKey(_swapID, _secretKey) { 80 | // Close the swap. 81 | Swap memory swap = swaps[_swapID]; 82 | swaps[_swapID].secretKey = _secretKey; 83 | swapStates[_swapID] = States.CLOSED; 84 | 85 | // Transfer the ERC20 funds from this contract to the withdrawing trader. 86 | ERC20 erc20Contract = ERC20(swap.erc20ContractAddress); 87 | require(erc20Contract.transfer(swap.withdrawTrader, swap.erc20Value)); 88 | 89 | Close(_swapID, _secretKey); 90 | } 91 | 92 | function expire(bytes32 _swapID) public onlyOpenSwaps(_swapID) onlyExpirableSwaps(_swapID) { 93 | // Expire the swap. 94 | Swap memory swap = swaps[_swapID]; 95 | swapStates[_swapID] = States.EXPIRED; 96 | 97 | // Transfer the ERC20 value from this contract back to the ERC20 trader. 98 | ERC20 erc20Contract = ERC20(swap.erc20ContractAddress); 99 | require(erc20Contract.transfer(swap.erc20Trader, swap.erc20Value)); 100 | 101 | Expire(_swapID); 102 | } 103 | 104 | function check(bytes32 _swapID) public view returns (uint256 timelock, uint256 erc20Value, address erc20ContractAddress, address withdrawTrader, bytes32 secretLock) { 105 | Swap memory swap = swaps[_swapID]; 106 | return (swap.timelock, swap.erc20Value, swap.erc20ContractAddress, swap.withdrawTrader, swap.secretLock); 107 | } 108 | 109 | function checkSecretKey(bytes32 _swapID) public view onlyClosedSwaps(_swapID) returns (bytes secretKey) { 110 | Swap memory swap = swaps[_swapID]; 111 | return swap.secretKey; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /test/Test2ERC20.js: -------------------------------------------------------------------------------- 1 | const Token = artifacts.require("./Test2ERC20.sol"); 2 | 3 | contract("Token", function(accounts) { 4 | it("should return the initial balances", async function() { 5 | const token = await Token.deployed(); 6 | const initial = 100000 * 10 ** 18; 7 | const owner_balance = (await token.balanceOf.call(accounts[0])).toNumber(); 8 | const rest_balance = (await token.balanceOf.call(accounts[1])).toNumber(); 9 | 10 | // Check for initial balances 11 | assert.equal(owner_balance, initial, "accounts[0] balance is incorrect"); 12 | assert.equal(rest_balance, 0, "accounts[1] balance is incorrect"); 13 | }); 14 | 15 | it("should return the initial allowances", async function() { 16 | const token = await Token.deployed(); 17 | const initial = (await token.allowance.call(accounts[0], accounts[1])).toNumber(); 18 | assert.equal(initial, 0, "allowance is incorrect"); 19 | }); 20 | 21 | it("should transfer from one account to another", async function() { 22 | const token = await Token.deployed(); 23 | const initial = (await token.balanceOf.call(accounts[0])).toNumber(); 24 | const amount = 500 * 10 ** 18; 25 | // Transfer from accounts[0] to accounts[1] 26 | await token.transfer(accounts[1], amount, {from: accounts[0]}); 27 | const sender_balance = (await token.balanceOf.call(accounts[0])).toNumber(); 28 | const receiver_balance = (await token.balanceOf.call(accounts[1])).toNumber(); 29 | assert.equal(sender_balance, initial - amount, "amount was not deducted from sender account"); 30 | assert.equal(receiver_balance, amount, "amount was not added to receiver account"); 31 | }); 32 | 33 | it("should not allow transfer with insufficient funds", async function() { 34 | const token = await Token.deployed(); 35 | const sender_initial = (await token.balanceOf.call(accounts[0])).toNumber(); 36 | const receiver_initial = (await token.balanceOf.call(accounts[1])).toNumber(); 37 | const amount = sender_initial * 2; // Transfer value greater than intiial amount 38 | 39 | // Try transferring with insufficient funds 40 | try { 41 | await token.transfer(accounts[1], amount, {from: accounts[0]}); 42 | } catch(error) { 43 | const sender_final = (await token.balanceOf.call(accounts[0])).toNumber(); 44 | const receiver_final = (await token.balanceOf.call(accounts[1])).toNumber(); 45 | assert.equal(sender_final, sender_initial, "transfer was sent"); 46 | assert.equal(receiver_final, receiver_final, "transfer was received"); 47 | } 48 | }); 49 | 50 | it("should transfer with allowance", async function() { 51 | const token = await Token.deployed(); 52 | const sender_initial = (await token.balanceOf.call(accounts[0])).toNumber(); 53 | const receiver_initial = (await token.balanceOf.call("0x8bc790a583789367f72c9c59678ff85a00a5e5d0")).toNumber(); 54 | const amount = 500 * 10 ** 18; 55 | 56 | // First approve accounts[1] from accounts[0] 57 | await token.approve("0x8bc790a583789367f72c9c59678ff85a00a5e5d0", amount, {from: accounts[0]}); 58 | const approval = (await token.allowance.call(accounts[0], "0x8bc790a583789367f72c9c59678ff85a00a5e5d0")).toNumber(); 59 | assert.equal(approval, amount, "amount was not approved"); 60 | }); 61 | 62 | it("should not allow transfer without allowance", async function() { 63 | const token = await Token.deployed(); 64 | const sender_initial = (await token.balanceOf.call(accounts[1])).toNumber(); 65 | const receiver_initial = (await token.balanceOf.call(accounts[2])).toNumber(); 66 | const amount = 500 * 10 ** 18; 67 | 68 | // Try transferring from accounts[1] to accounts[2] without allowance 69 | try { 70 | await token.transferFrom(accounts[1], accounts[2], amount, {from: accounts[0]}); 71 | } catch(error) { 72 | const sender_final = (await token.balanceOf.call(accounts[1])).toNumber(); 73 | const receiver_final = (await token.balanceOf.call(accounts[2])).toNumber(); 74 | assert.equal(sender_final, sender_initial, "transfer was sent"); 75 | assert.equal(receiver_final, receiver_final, "transfer was received"); 76 | } 77 | }); 78 | 79 | it("should not allow transfer of negative value", async function() { 80 | const token = await Token.deployed(); 81 | const sender_initial = (await token.balanceOf.call(accounts[0])).toNumber(); 82 | const receiver_initial = (await token.balanceOf.call(accounts[1])).toNumber(); 83 | const amount = 500 * 10 ** 18; 84 | 85 | // Try transferring negative amount 86 | try { 87 | await token.transfer(accounts[1], amount); 88 | } catch(error) { 89 | // Check balance has not changed 90 | const sender_final = (await token.balanceOf.call(accounts[0])).toNumber(); 91 | const receiver_final = (await token.balanceOf.call(accounts[1])).toNumber(); 92 | assert.equal(sender_final, sender_initial, "transfer was sent"); 93 | assert.equal(receiver_final, receiver_final, "transfer was received"); 94 | } 95 | }); 96 | 97 | it("should burn valid amount", async function() { 98 | const token = await Token.deployed(); 99 | const initial = (await token.balanceOf.call(accounts[0])).toNumber(); 100 | 101 | // Burn from accounts[0]'s balance 102 | await token.burn(initial, {from: accounts[0]}); 103 | const balance = (await token.balanceOf.call(accounts[0])).toNumber(); 104 | assert.equal(balance, 0, "balance was not burned"); 105 | }); 106 | 107 | it("should not allow burn without allowance", async function() { 108 | const token = await Token.deployed(); 109 | const initial = (await token.balanceOf.call(accounts[1])).toNumber(); 110 | const amount = 500 * 10 ** 18; 111 | 112 | // Try burning accounts[1]'s balance without allowance 113 | try { 114 | await token.burnFrom(accounts[1], amount); 115 | } catch(error) { 116 | // Check accounts[1]'s balance has not changed 117 | const balance = (await token.balanceOf.call(accounts[1])).toNumber(); 118 | assert.equal(balance, initial, "balance was burned"); 119 | } 120 | }); 121 | 122 | it("should not burn negative amount", async function() { 123 | const token = await Token.deployed(); 124 | const initial = (await token.balanceOf.call(accounts[0])).toNumber(); 125 | 126 | // Try burning negative amount 127 | try { 128 | await token.burn(initial * -1); 129 | } catch(error) { 130 | // Check accounts[0]'s balance has not changed 131 | const balance = (await token.balanceOf.call(accounts[0])).toNumber(); 132 | assert.equal(balance, initial, "negative amount was burned"); 133 | } 134 | }); 135 | }); -------------------------------------------------------------------------------- /test/TestERC20.js: -------------------------------------------------------------------------------- 1 | const Token = artifacts.require("./TestERC20.sol"); 2 | 3 | contract("Token", function(accounts) { 4 | it("should return the initial balances", async function() { 5 | const token = await Token.deployed(); 6 | const initial = 100000 * 10 ** 18; 7 | const owner_balance = (await token.balanceOf.call(accounts[0])).toNumber(); 8 | const rest_balance = (await token.balanceOf.call(accounts[1])).toNumber(); 9 | 10 | // Check for initial balances 11 | assert.equal(owner_balance, initial, "accounts[0] balance is incorrect"); 12 | assert.equal(rest_balance, 0, "accounts[1] balance is incorrect"); 13 | }); 14 | 15 | it("should return the initial allowances", async function() { 16 | const token = await Token.deployed(); 17 | const initial = (await token.allowance.call(accounts[0], accounts[1])).toNumber(); 18 | assert.equal(initial, 0, "allowance is incorrect"); 19 | }); 20 | 21 | it("should transfer from one account to another", async function() { 22 | const token = await Token.deployed(); 23 | const initial = (await token.balanceOf.call(accounts[0])).toNumber(); 24 | const amount = 500 * 10 ** 18; 25 | // Transfer from accounts[0] to accounts[1] 26 | await token.transfer(accounts[1], amount, {from: accounts[0]}); 27 | const sender_balance = (await token.balanceOf.call(accounts[0])).toNumber(); 28 | const receiver_balance = (await token.balanceOf.call(accounts[1])).toNumber(); 29 | assert.equal(sender_balance, initial - amount, "amount was not deducted from sender account"); 30 | assert.equal(receiver_balance, amount, "amount was not added to receiver account"); 31 | }); 32 | 33 | it("should not allow transfer with insufficient funds", async function() { 34 | const token = await Token.deployed(); 35 | const sender_initial = (await token.balanceOf.call(accounts[0])).toNumber(); 36 | const receiver_initial = (await token.balanceOf.call(accounts[1])).toNumber(); 37 | const amount = sender_initial * 2; // Transfer value greater than intiial amount 38 | 39 | // Try transferring with insufficient funds 40 | try { 41 | await token.transfer(accounts[1], amount, {from: accounts[0]}); 42 | } catch(error) { 43 | const sender_final = (await token.balanceOf.call(accounts[0])).toNumber(); 44 | const receiver_final = (await token.balanceOf.call(accounts[1])).toNumber(); 45 | assert.equal(sender_final, sender_initial, "transfer was sent"); 46 | assert.equal(receiver_final, receiver_final, "transfer was received"); 47 | } 48 | }); 49 | 50 | it("should transfer with allowance", async function() { 51 | const token = await Token.deployed(); 52 | const sender_initial = (await token.balanceOf.call(accounts[0])).toNumber(); 53 | const receiver_initial = (await token.balanceOf.call("0x8bc790a583789367f72c9c59678ff85a00a5e5d0")).toNumber(); 54 | const amount = 500 * 10 ** 18; 55 | 56 | // First approve accounts[1] from accounts[0] 57 | await token.approve("0x8bc790a583789367f72c9c59678ff85a00a5e5d0", amount, {from: accounts[0]}); 58 | const approval = (await token.allowance.call(accounts[0], "0x8bc790a583789367f72c9c59678ff85a00a5e5d0")).toNumber(); 59 | assert.equal(approval, amount, "amount was not approved"); 60 | }); 61 | 62 | it("should not allow transfer without allowance", async function() { 63 | const token = await Token.deployed(); 64 | const sender_initial = (await token.balanceOf.call(accounts[1])).toNumber(); 65 | const receiver_initial = (await token.balanceOf.call(accounts[2])).toNumber(); 66 | const amount = 500 * 10 ** 18; 67 | 68 | // Try transferring from accounts[1] to accounts[2] without allowance 69 | try { 70 | await token.transferFrom(accounts[1], accounts[2], amount, {from: accounts[0]}); 71 | } catch(error) { 72 | const sender_final = (await token.balanceOf.call(accounts[1])).toNumber(); 73 | const receiver_final = (await token.balanceOf.call(accounts[2])).toNumber(); 74 | assert.equal(sender_final, sender_initial, "transfer was sent"); 75 | assert.equal(receiver_final, receiver_final, "transfer was received"); 76 | } 77 | }); 78 | 79 | it("should not allow transfer of negative value", async function() { 80 | const token = await Token.deployed(); 81 | const sender_initial = (await token.balanceOf.call(accounts[0])).toNumber(); 82 | const receiver_initial = (await token.balanceOf.call(accounts[1])).toNumber(); 83 | const amount = 500 * 10 ** 18; 84 | 85 | // Try transferring negative amount 86 | try { 87 | await token.transfer(accounts[1], amount); 88 | } catch(error) { 89 | // Check balance has not changed 90 | const sender_final = (await token.balanceOf.call(accounts[0])).toNumber(); 91 | const receiver_final = (await token.balanceOf.call(accounts[1])).toNumber(); 92 | assert.equal(sender_final, sender_initial, "transfer was sent"); 93 | assert.equal(receiver_final, receiver_final, "transfer was received"); 94 | } 95 | }); 96 | 97 | it("should burn valid amount", async function() { 98 | const token = await Token.deployed(); 99 | const initial = (await token.balanceOf.call(accounts[0])).toNumber(); 100 | 101 | // Burn from accounts[0]'s balance 102 | await token.burn(initial, {from: accounts[0]}); 103 | const balance = (await token.balanceOf.call(accounts[0])).toNumber(); 104 | assert.equal(balance, 0, "balance was not burned"); 105 | }); 106 | 107 | it("should not allow burn without allowance", async function() { 108 | const token = await Token.deployed(); 109 | const initial = (await token.balanceOf.call(accounts[1])).toNumber(); 110 | const amount = 500 * 10 ** 18; 111 | 112 | // Try burning accounts[1]'s balance without allowance 113 | try { 114 | await token.burnFrom(accounts[1], amount); 115 | } catch(error) { 116 | // Check accounts[1]'s balance has not changed 117 | const balance = (await token.balanceOf.call(accounts[1])).toNumber(); 118 | assert.equal(balance, initial, "balance was burned"); 119 | } 120 | }); 121 | 122 | it("should not burn negative amount", async function() { 123 | const token = await Token.deployed(); 124 | const initial = (await token.balanceOf.call(accounts[0])).toNumber(); 125 | 126 | // Try burning negative amount 127 | try { 128 | await token.burn(initial * -1); 129 | } catch(error) { 130 | // Check accounts[0]'s balance has not changed 131 | const balance = (await token.balanceOf.call(accounts[0])).toNumber(); 132 | assert.equal(balance, initial, "negative amount was burned"); 133 | } 134 | }); 135 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ethereum Atomic Swap 2 | 3 | [![Build Status](https://travis-ci.org/republicprotocol/eth-atomic-swap.svg?branch=master)](https://travis-ci.org/republicprotocol/eth-atomic-swap) 4 | [![Coverage Status](https://coveralls.io/repos/github/republicprotocol/eth-atomic-swap/badge.svg?branch=master)](https://coveralls.io/github/republicprotocol/eth-atomic-swap?branch=master) 5 | 6 | The Ethereum Atomic Swap library is an official reference implementation of atomic swaps on Ethereum for the Republic Protocol, written in Solidity. This library supports Ether and ERC20 atomic swaps, cross-chain Ether atomic swaps, and cross-chain ERC20 atomic swaps. Currently, the Republic Protocol only provides support for cross-chain trading with Bitcoin. 7 | 8 | ## Smart contracts 9 | 10 | The Ethereum Atomic Swap library is made up of several different smart contracts that work together to implement atomic swaps. These smart contracts are used by traders after the Republic Protocol has successfully matched their orders. However, traders are not required to use the Republic Protocol to use the Ethereum Atomic Swap contracts. 11 | 12 | 1. The EtherToERC20 contract implements atomic swaps between Ether and an ERC20 token. 13 | 2. The ERC20ToERC20 contract implements atomic swaps between two ERC20 tokens. 14 | 3. The Ether contract implements cross-chain atomic swaps where Ether is being used. 15 | 4. The ERC20 contract implements cross-chain atomic swaps where an ERC20 token is being used. 16 | 17 | None of the contract expose orders. Orders are never passed to the Republic network under any circumstances, and order fragments are never passed to the blockchain. The `_swapID` used by the contracts are only for identifying the swap, and is negotiated between traders. This maintains privacy between traders that have matched on the order book, and atomic swaps that have been executed. 18 | 19 | ## How it works 20 | 21 | These contracts are used by the Republic Protocol, but can also be used by any traders that are looking to perform a cross-chain atomic swaps. Traders are not required to use the Republic Protocol to use the Ethereum Atomic Swap contracts. 22 | 23 | ### Ether to ERC20 24 | 25 | When performing an atomic swap between Ether and ERC20 tokens, the `AtomicSwapEtherToERC20` contract should be used. For this example, Alice holds Ether and Bob holds ERC20 tokens. Alice is looking to give Ether to Bob in exchange for his ERC20 tokens. 26 | 27 | 1. Alice calls `open` using a unique `_swapID` that has been negotiated between both traders. This is a payable call and Alice must send her Ether when she makes this call. 28 | 2. Bob calls `check` to verify the details of the trade. If he does not agree then he does not need to do anything. At any point, Alice can call `expire` and get a refund of her Ether. Doing this cancels the swap. 29 | 3. Bob provides an allowance to the `AtomicSwapEtherToERC20` contract, using his ERC20 contract to do so. 30 | 4. Bob calls `close`, which will check the allowance and use it to transfer his ERC20 tokens to Alice. At the same time, it will transfer Alice's Ether to Bob. Alice can no longer expire the swap. 31 | 32 | ### ERC20 to ERC20 33 | 34 | When performing an atomic swap between ERC20 and another ERC20 token, the `AtomicSwapERC20ToERC20` contract should be used. For this example, Alice holds ERC20 tokens and Bob also holds ERC20 tokens. Alice is looking to give her ERC20 tokens to Bob in exchange for his ERC20 tokens, and Alice has agreed to initiate the atomic swap. 35 | 36 | 1. Alice provides an allowance to the `AtomicSwapERC20ToERC20` contract, using her ERC20 contract to do so. 37 | 1. Alice calls `open` using a unique `_swapID` that has been negotiated between both traders. The allowance will be checked and used to transfer Alice's ERC20 tokens to the `AtomicSwapERC20ToERC20` contract. 38 | 2. Bob calls `check` to verify the details of the trade. If he does not agree then he does not need to do anything. At any point, Alice can call `expire` and get a refund of her ERC20 tokens. Doing this cancels the swap. 39 | 3. Bob provides an allowance to the `AtomicSwapERC20ToERC20` contract, using his ERC20 contract to do so. 40 | 4. Bob calls `close`, which will check the allowance and use it to transfer his ERC20 tokens to Alice. At the same time, it will transfer Alice's ERC20 tokens to Bob. Alice can no longer expire the swap. 41 | 42 | ### Ether to Bitcoin 43 | 44 | > This example will use Bitcoin as the non-Ethereum cryptocurrency being traded. However, it works with any cryptocurrency that supports the same level of scripting as Bitcoin; including, but not limited to, Bitcoin Cash, Bitcoin Gold, and LiteCoin. The Republic Protocol team will provide an official implementation of the Bitcoin Script required to perform atomic swaps with these contracts, which be used without even if the Republic Protocol was not used to match the traders. Third party scripts will not be supported by the Republic Protocol, but users are free to use whichever scripts they want. 45 | 46 | When performing an atomic swap between Ether and Bitcoin, the `AtomicSwapEther` contract should be used. For this example, Alice holds Ether and Bob also holds Bitcoin. Alice is looking to give her Ether to Bob in exchange for his Bitcoins. 47 | 48 | 1. Bob generates a random secret key and hashes it using SHA256 to generate a secret lock. 49 | 2. Bob uses the secret lock, and a Bitcoin Script, to setup a transaction to Alice on the condition that she produces the secret key. If she does not do so within 48 hours then Bob can withdraw the funds. 50 | 3. Bob sends the secret lock to Alice along with the address of his transaction on the Bitcoin blockchain. 51 | 4. Alice checks Bob's transaction, verifying the details of the trade. If she does not agree then she does not need to do anything. After 48 hours, Bob can withdraw his funds. 52 | 5. Alice calls `open` using a unique `_swapID` that has been negotiated between both traders. She also uses the secret lock that was provided by Bob. This is a payable call and Alice must send her Ether when she makes this call. 53 | 6. Bob calls `check` to verify the details of the trade. If he does not agree, then he does not need to do anything. After 24 hours, Alice can call `expire`, getting a refund of her Ether. 54 | 7. Bob calls `close`, which requires that he submits the secret key associated with the secret lock. If he has provided the correct secret key, it will transfer Alice's Ether to Bob and store the secret key. 55 | 8. Alice calls `checkSecretKey`, acquiring the secret key. 56 | 9. Alice provides the secret key to Bob's Bitcoin Script, and receives his Bitcoin. 57 | 58 | ### ERC20 to Bitcoin 59 | 60 | > This example will use Bitcoin as the non-Ethereum cryptocurrency being traded. However, it works with any cryptocurrency that supports the same level of scripting as Bitcoin; including, but not limited to, Bitcoin Cash, Bitcoin Gold, and LiteCoin. The Republic Protocol team will provide an official implementation of the Bitcoin Script required to perform atomic swaps with these contracts, which be used without even if the Republic Protocol was not used to match the traders. Third party scripts will not be supported by the Republic Protocol, but users are free to use whichever scripts they want. 61 | 62 | When performing an atomic swap between ERC20 and Bitcoin, the `AtomicSwapERC20` contract should be used. For this example, Alice holds ERC20 tokens and Bob also holds Bitcoin. Alice is looking to give her ERC20 tokens to Bob in exchange for his Bitcoins. 63 | 64 | 1. Bob generates a random secret key and hashes it using SHA256 to generate a secret lock. 65 | 2. Bob uses the secret lock, and a Bitcoin Script, to setup a transaction to Alice on the condition that she produces the secret key. If she does not do so within 48 hours then Bob can withdraw the funds. 66 | 3. Bob sends the secret lock to Alice along with the address of his transaction on the Bitcoin blockchain. 67 | 4. Alice checks Bob's transaction, verifying the details of the trade. If she does not agree then she does not need to do anything. After 48 hours, Bob can withdraw his funds. 68 | 5. Alice provides an allowance to the `AtomicSwapERC20` contract, using her ERC20 contract to do so. 69 | 6. Alice calls `open` using a unique `_swapID` that has been negotiated between both traders. She also uses the secret lock that was provided by Bob. The allowance will be checked and used to transfer Alice's ERC20 tokens to the `AtomicSwapERC20` contract. 70 | 7. Bob calls `check` to verify the details of the trade. If he does not agree, then he does not need to do anything. After 24 hours, Alice can call `expire`, getting a refund of her ERC20 tokens. 71 | 8. Bob calls `close`, which requires that he submits the secret key associated with the secret lock. If he has provided the correct secret key, it will transfer Alice's ERC20 tokens to Bob and store the secret key. 72 | 9. Alice calls `checkSecretKey`, acquiring the secret key. 73 | 10. Alice provides the secret key to Bob's Bitcoin Script, and receives his Bitcoin. 74 | 75 | ### Limitations 76 | 77 | During a cross-chain atomic swap, funds are locked in contracts and scripts. If both traders participate faithfully in the trader then this will have no effect on either trader. The only latency in accessing funds is the latency of network communications, and blockchain confirmations. 78 | 79 | However, if one trader is malicious then they are able to inconvenience the other trader by never agreeing to the trade. In this case, the funds will be locked for up to 48 hours. This number can be reduced to any value agreed upon by the traders but it should always be long enough that both traders have time to execute the atomic swap. 80 | 81 | These cross-chain atomic swap contracts, and scripts, can be used by any traders but are ultimated designed to work with the Republic Protocol. The Republic Protocol provides economic incentivizes to discourage traders from not executing on matched orders. Orders on the Republic Protocol can not be staged prior to the atomic swap (this would reveal the order) and are most commonly large volumes that are traded once (a trader is unlikely to be continuously matched with a specific trader). 82 | 83 | ## Tests 84 | 85 | Install all NPM modules and Truffle as a global command. 86 | 87 | ``` 88 | npm install --global truffle 89 | npm install 90 | ``` 91 | 92 | Run the `ganache` script. This script needs to continue running in the background; either run it in a separate terminal, or append the `&` symbol. 93 | 94 | ```sh 95 | ./ganache 96 | ``` 97 | 98 | Run the Truffle test suite. 99 | 100 | ```sh 101 | truffle test 102 | ``` 103 | 104 | ## License 105 | 106 | The Ethereum Atomic Swap library was developed by the Republic Protocol team and is available under the MIT license. For more information, see our website https://republicprotocol.com. 107 | -------------------------------------------------------------------------------- /coverage.json: -------------------------------------------------------------------------------- 1 | {"contracts/AtomicSwapERC20.sol":{"l":{"32":2,"33":2,"38":2,"39":2,"44":1,"45":1,"50":1,"51":1,"57":1,"58":1,"63":2,"65":2,"66":2,"67":2,"70":2,"79":2,"80":2,"81":2,"86":1,"87":1,"88":1,"91":1,"92":1,"94":1,"99":1,"100":1,"103":1,"104":1,"106":1,"110":1,"111":1,"115":1,"116":1},"path":"/home/noah/github/republicprotocol/eth-atomic-swap/contracts/AtomicSwapERC20.sol","s":{"1":2,"2":2,"3":1,"4":1,"5":1,"6":2,"7":2,"8":2,"9":2,"10":2,"11":2,"12":2,"13":2,"14":1,"15":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"25":1,"26":1,"27":1,"28":1},"b":{"1":[2,0],"2":[2,0],"3":[1,0],"4":[1,0],"5":[1,0],"6":[2,0],"7":[2,0],"8":[2,0],"9":[1,0],"10":[1,0]},"f":{"1":2,"2":2,"3":1,"4":1,"5":1,"6":2,"7":1,"8":1,"9":1,"10":1},"fnMap":{"1":{"name":"onlyInvalidSwaps","line":31,"loc":{"start":{"line":31,"column":2},"end":{"line":31,"column":44}}},"2":{"name":"onlyOpenSwaps","line":37,"loc":{"start":{"line":37,"column":2},"end":{"line":37,"column":41}}},"3":{"name":"onlyClosedSwaps","line":43,"loc":{"start":{"line":43,"column":2},"end":{"line":43,"column":43}}},"4":{"name":"onlyExpirableSwaps","line":49,"loc":{"start":{"line":49,"column":2},"end":{"line":49,"column":46}}},"5":{"name":"onlyWithSecretKey","line":55,"loc":{"start":{"line":55,"column":2},"end":{"line":55,"column":63}}},"6":{"name":"open","line":62,"loc":{"start":{"line":62,"column":2},"end":{"line":62,"column":182}}},"7":{"name":"close","line":84,"loc":{"start":{"line":84,"column":2},"end":{"line":84,"column":120}}},"8":{"name":"expire","line":97,"loc":{"start":{"line":97,"column":2},"end":{"line":97,"column":92}}},"9":{"name":"check","line":109,"loc":{"start":{"line":109,"column":2},"end":{"line":109,"column":166}}},"10":{"name":"checkSecretKey","line":114,"loc":{"start":{"line":114,"column":2},"end":{"line":114,"column":105}}}},"statementMap":{"1":{"start":{"line":32,"column":4},"end":{"line":32,"column":654}},"2":{"start":{"line":38,"column":4},"end":{"line":38,"column":767}},"3":{"start":{"line":44,"column":4},"end":{"line":44,"column":879}},"4":{"start":{"line":50,"column":4},"end":{"line":50,"column":996}},"5":{"start":{"line":57,"column":4},"end":{"line":57,"column":1186}},"6":{"start":{"line":63,"column":4},"end":{"line":63,"column":49}},"7":{"start":{"line":65,"column":4},"end":{"line":65,"column":53}},"8":{"start":{"line":66,"column":4},"end":{"line":66,"column":77}},"9":{"start":{"line":67,"column":4},"end":{"line":67,"column":78}},"10":{"start":{"line":70,"column":4},"end":{"line":70,"column":1821}},"11":{"start":{"line":79,"column":4},"end":{"line":79,"column":24}},"12":{"start":{"line":80,"column":4},"end":{"line":80,"column":36}},"13":{"start":{"line":81,"column":4},"end":{"line":81,"column":46}},"14":{"start":{"line":86,"column":4},"end":{"line":86,"column":36}},"15":{"start":{"line":87,"column":4},"end":{"line":87,"column":40}},"16":{"start":{"line":88,"column":4},"end":{"line":88,"column":38}},"17":{"start":{"line":91,"column":4},"end":{"line":91,"column":57}},"18":{"start":{"line":92,"column":4},"end":{"line":92,"column":72}},"19":{"start":{"line":94,"column":4},"end":{"line":94,"column":29}},"20":{"start":{"line":99,"column":4},"end":{"line":99,"column":36}},"21":{"start":{"line":100,"column":4},"end":{"line":100,"column":39}},"22":{"start":{"line":103,"column":4},"end":{"line":103,"column":57}},"23":{"start":{"line":104,"column":4},"end":{"line":104,"column":69}},"24":{"start":{"line":106,"column":4},"end":{"line":106,"column":18}},"25":{"start":{"line":110,"column":4},"end":{"line":110,"column":36}},"26":{"start":{"line":111,"column":4},"end":{"line":111,"column":108}},"27":{"start":{"line":115,"column":4},"end":{"line":115,"column":36}},"28":{"start":{"line":116,"column":4},"end":{"line":116,"column":25}}},"branchMap":{"1":{"line":32,"type":"if","locations":[{"start":{"line":32,"column":4},"end":{"line":32,"column":4}},{"start":{"line":32,"column":4},"end":{"line":32,"column":4}}]},"2":{"line":38,"type":"if","locations":[{"start":{"line":38,"column":4},"end":{"line":38,"column":4}},{"start":{"line":38,"column":4},"end":{"line":38,"column":4}}]},"3":{"line":44,"type":"if","locations":[{"start":{"line":44,"column":4},"end":{"line":44,"column":4}},{"start":{"line":44,"column":4},"end":{"line":44,"column":4}}]},"4":{"line":50,"type":"if","locations":[{"start":{"line":50,"column":4},"end":{"line":50,"column":4}},{"start":{"line":50,"column":4},"end":{"line":50,"column":4}}]},"5":{"line":57,"type":"if","locations":[{"start":{"line":57,"column":4},"end":{"line":57,"column":4}},{"start":{"line":57,"column":4},"end":{"line":57,"column":4}}]},"6":{"line":63,"type":"if","locations":[{"start":{"line":63,"column":4},"end":{"line":63,"column":4}},{"start":{"line":63,"column":4},"end":{"line":63,"column":4}}]},"7":{"line":66,"type":"if","locations":[{"start":{"line":66,"column":4},"end":{"line":66,"column":4}},{"start":{"line":66,"column":4},"end":{"line":66,"column":4}}]},"8":{"line":67,"type":"if","locations":[{"start":{"line":67,"column":4},"end":{"line":67,"column":4}},{"start":{"line":67,"column":4},"end":{"line":67,"column":4}}]},"9":{"line":92,"type":"if","locations":[{"start":{"line":92,"column":4},"end":{"line":92,"column":4}},{"start":{"line":92,"column":4},"end":{"line":92,"column":4}}]},"10":{"line":104,"type":"if","locations":[{"start":{"line":104,"column":4},"end":{"line":104,"column":4}},{"start":{"line":104,"column":4},"end":{"line":104,"column":4}}]}}},"contracts/AtomicSwapERC20ToERC20.sol":{"l":{"31":2,"32":2,"37":2,"38":2,"44":2,"45":2,"46":2,"49":2,"57":2,"58":2,"60":2,"65":1,"66":1,"69":1,"70":1,"71":1,"74":1,"75":1,"77":1,"82":1,"83":1,"86":1,"87":1,"89":1,"93":1,"94":1},"path":"/home/noah/github/republicprotocol/eth-atomic-swap/contracts/AtomicSwapERC20ToERC20.sol","s":{"1":2,"2":2,"3":2,"4":2,"5":2,"6":2,"7":2,"8":2,"9":2,"10":1,"11":1,"12":1,"13":1,"14":1,"15":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1},"b":{"1":[2,0],"2":[2,0],"3":[2,0],"4":[2,0],"5":[1,0],"6":[1,0],"7":[1,0],"8":[1,0]},"f":{"1":2,"2":2,"3":2,"4":1,"5":1,"6":1},"fnMap":{"1":{"name":"onlyInvalidSwaps","line":30,"loc":{"start":{"line":30,"column":2},"end":{"line":30,"column":44}}},"2":{"name":"onlyOpenSwaps","line":36,"loc":{"start":{"line":36,"column":2},"end":{"line":36,"column":41}}},"3":{"name":"open","line":42,"loc":{"start":{"line":42,"column":2},"end":{"line":42,"column":189}}},"4":{"name":"close","line":63,"loc":{"start":{"line":63,"column":2},"end":{"line":63,"column":63}}},"5":{"name":"expire","line":80,"loc":{"start":{"line":80,"column":2},"end":{"line":80,"column":64}}},"6":{"name":"check","line":92,"loc":{"start":{"line":92,"column":2},"end":{"line":92,"column":173}}}},"statementMap":{"1":{"start":{"line":31,"column":4},"end":{"line":31,"column":605}},"2":{"start":{"line":37,"column":4},"end":{"line":37,"column":718}},"3":{"start":{"line":44,"column":4},"end":{"line":44,"column":56}},"4":{"start":{"line":45,"column":4},"end":{"line":45,"column":80}},"5":{"start":{"line":46,"column":4},"end":{"line":46,"column":81}},"6":{"start":{"line":49,"column":4},"end":{"line":49,"column":1306}},"7":{"start":{"line":57,"column":4},"end":{"line":57,"column":24}},"8":{"start":{"line":58,"column":4},"end":{"line":58,"column":36}},"9":{"start":{"line":60,"column":4},"end":{"line":60,"column":30}},"10":{"start":{"line":65,"column":4},"end":{"line":65,"column":36}},"11":{"start":{"line":66,"column":4},"end":{"line":66,"column":38}},"12":{"start":{"line":69,"column":4},"end":{"line":69,"column":62}},"13":{"start":{"line":70,"column":4},"end":{"line":70,"column":92}},"14":{"start":{"line":71,"column":4},"end":{"line":71,"column":95}},"15":{"start":{"line":74,"column":4},"end":{"line":74,"column":60}},"16":{"start":{"line":75,"column":4},"end":{"line":75,"column":72}},"17":{"start":{"line":77,"column":4},"end":{"line":77,"column":17}},"18":{"start":{"line":82,"column":4},"end":{"line":82,"column":36}},"19":{"start":{"line":83,"column":4},"end":{"line":83,"column":39}},"20":{"start":{"line":86,"column":4},"end":{"line":86,"column":60}},"21":{"start":{"line":87,"column":4},"end":{"line":87,"column":71}},"22":{"start":{"line":89,"column":4},"end":{"line":89,"column":18}},"23":{"start":{"line":93,"column":4},"end":{"line":93,"column":36}},"24":{"start":{"line":94,"column":4},"end":{"line":94,"column":115}}},"branchMap":{"1":{"line":31,"type":"if","locations":[{"start":{"line":31,"column":4},"end":{"line":31,"column":4}},{"start":{"line":31,"column":4},"end":{"line":31,"column":4}}]},"2":{"line":37,"type":"if","locations":[{"start":{"line":37,"column":4},"end":{"line":37,"column":4}},{"start":{"line":37,"column":4},"end":{"line":37,"column":4}}]},"3":{"line":45,"type":"if","locations":[{"start":{"line":45,"column":4},"end":{"line":45,"column":4}},{"start":{"line":45,"column":4},"end":{"line":45,"column":4}}]},"4":{"line":46,"type":"if","locations":[{"start":{"line":46,"column":4},"end":{"line":46,"column":4}},{"start":{"line":46,"column":4},"end":{"line":46,"column":4}}]},"5":{"line":70,"type":"if","locations":[{"start":{"line":70,"column":4},"end":{"line":70,"column":4}},{"start":{"line":70,"column":4},"end":{"line":70,"column":4}}]},"6":{"line":71,"type":"if","locations":[{"start":{"line":71,"column":4},"end":{"line":71,"column":4}},{"start":{"line":71,"column":4},"end":{"line":71,"column":4}}]},"7":{"line":75,"type":"if","locations":[{"start":{"line":75,"column":4},"end":{"line":75,"column":4}},{"start":{"line":75,"column":4},"end":{"line":75,"column":4}}]},"8":{"line":87,"type":"if","locations":[{"start":{"line":87,"column":4},"end":{"line":87,"column":4}},{"start":{"line":87,"column":4},"end":{"line":87,"column":4}}]}}},"contracts/AtomicSwapEther.sol":{"l":{"29":2,"30":2,"35":2,"36":2,"41":1,"42":1,"47":1,"48":1,"54":1,"55":1,"61":2,"69":2,"70":2,"73":2,"78":1,"79":1,"80":1,"83":1,"86":1,"91":1,"92":1,"95":1,"98":1,"102":1,"103":1,"107":1,"108":1},"path":"/home/noah/github/republicprotocol/eth-atomic-swap/contracts/AtomicSwapEther.sol","s":{"1":2,"2":2,"3":1,"4":1,"5":1,"6":2,"7":2,"8":2,"9":2,"10":1,"11":1,"12":1,"13":1,"14":1,"15":1,"16":1,"17":1,"18":1,"19":1,"20":1},"b":{"1":[2,0],"2":[2,0],"3":[1,0],"4":[1,0],"5":[1,0]},"f":{"1":2,"2":2,"3":1,"4":1,"5":1,"6":2,"7":1,"8":1,"9":1,"10":1},"fnMap":{"1":{"name":"onlyInvalidSwaps","line":28,"loc":{"start":{"line":28,"column":2},"end":{"line":28,"column":44}}},"2":{"name":"onlyOpenSwaps","line":34,"loc":{"start":{"line":34,"column":2},"end":{"line":34,"column":41}}},"3":{"name":"onlyClosedSwaps","line":40,"loc":{"start":{"line":40,"column":2},"end":{"line":40,"column":43}}},"4":{"name":"onlyExpirableSwaps","line":46,"loc":{"start":{"line":46,"column":2},"end":{"line":46,"column":46}}},"5":{"name":"onlyWithSecretKey","line":52,"loc":{"start":{"line":52,"column":2},"end":{"line":52,"column":63}}},"6":{"name":"open","line":59,"loc":{"start":{"line":59,"column":2},"end":{"line":59,"column":138}}},"7":{"name":"close","line":76,"loc":{"start":{"line":76,"column":2},"end":{"line":76,"column":120}}},"8":{"name":"expire","line":89,"loc":{"start":{"line":89,"column":2},"end":{"line":89,"column":92}}},"9":{"name":"check","line":101,"loc":{"start":{"line":101,"column":2},"end":{"line":101,"column":131}}},"10":{"name":"checkSecretKey","line":106,"loc":{"start":{"line":106,"column":2},"end":{"line":106,"column":105}}}},"statementMap":{"1":{"start":{"line":29,"column":4},"end":{"line":29,"column":590}},"2":{"start":{"line":35,"column":4},"end":{"line":35,"column":703}},"3":{"start":{"line":41,"column":4},"end":{"line":41,"column":815}},"4":{"start":{"line":47,"column":4},"end":{"line":47,"column":932}},"5":{"start":{"line":54,"column":4},"end":{"line":54,"column":1122}},"6":{"start":{"line":61,"column":4},"end":{"line":61,"column":1381}},"7":{"start":{"line":69,"column":4},"end":{"line":69,"column":24}},"8":{"start":{"line":70,"column":4},"end":{"line":70,"column":36}},"9":{"start":{"line":73,"column":4},"end":{"line":73,"column":46}},"10":{"start":{"line":78,"column":4},"end":{"line":78,"column":36}},"11":{"start":{"line":79,"column":4},"end":{"line":79,"column":40}},"12":{"start":{"line":80,"column":4},"end":{"line":80,"column":38}},"13":{"start":{"line":86,"column":4},"end":{"line":86,"column":29}},"14":{"start":{"line":91,"column":4},"end":{"line":91,"column":36}},"15":{"start":{"line":92,"column":4},"end":{"line":92,"column":39}},"16":{"start":{"line":98,"column":4},"end":{"line":98,"column":18}},"17":{"start":{"line":102,"column":4},"end":{"line":102,"column":36}},"18":{"start":{"line":103,"column":4},"end":{"line":103,"column":76}},"19":{"start":{"line":107,"column":4},"end":{"line":107,"column":36}},"20":{"start":{"line":108,"column":4},"end":{"line":108,"column":25}}},"branchMap":{"1":{"line":29,"type":"if","locations":[{"start":{"line":29,"column":4},"end":{"line":29,"column":4}},{"start":{"line":29,"column":4},"end":{"line":29,"column":4}}]},"2":{"line":35,"type":"if","locations":[{"start":{"line":35,"column":4},"end":{"line":35,"column":4}},{"start":{"line":35,"column":4},"end":{"line":35,"column":4}}]},"3":{"line":41,"type":"if","locations":[{"start":{"line":41,"column":4},"end":{"line":41,"column":4}},{"start":{"line":41,"column":4},"end":{"line":41,"column":4}}]},"4":{"line":47,"type":"if","locations":[{"start":{"line":47,"column":4},"end":{"line":47,"column":4}},{"start":{"line":47,"column":4},"end":{"line":47,"column":4}}]},"5":{"line":54,"type":"if","locations":[{"start":{"line":54,"column":4},"end":{"line":54,"column":4}},{"start":{"line":54,"column":4},"end":{"line":54,"column":4}}]}}},"contracts/AtomicSwapEtherToERC20.sol":{"l":{"30":2,"31":2,"36":2,"37":2,"43":2,"50":2,"51":2,"53":2,"58":1,"59":1,"62":1,"63":1,"64":1,"67":1,"69":1,"74":1,"75":1,"78":1,"79":1,"83":1,"84":1},"path":"/home/noah/github/republicprotocol/eth-atomic-swap/contracts/AtomicSwapEtherToERC20.sol","s":{"1":2,"2":2,"3":2,"4":2,"5":2,"6":2,"7":1,"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"14":1,"15":1,"16":1,"17":1},"b":{"1":[2,0],"2":[2,0],"3":[1,0],"4":[1,0]},"f":{"1":2,"2":2,"3":2,"4":1,"5":1,"6":1},"fnMap":{"1":{"name":"onlyInvalidSwaps","line":29,"loc":{"start":{"line":29,"column":2},"end":{"line":29,"column":44}}},"2":{"name":"onlyOpenSwaps","line":35,"loc":{"start":{"line":35,"column":2},"end":{"line":35,"column":41}}},"3":{"name":"open","line":41,"loc":{"start":{"line":41,"column":2},"end":{"line":41,"column":147}}},"4":{"name":"close","line":56,"loc":{"start":{"line":56,"column":2},"end":{"line":56,"column":63}}},"5":{"name":"expire","line":72,"loc":{"start":{"line":72,"column":2},"end":{"line":72,"column":64}}},"6":{"name":"check","line":82,"loc":{"start":{"line":82,"column":2},"end":{"line":82,"column":140}}}},"statementMap":{"1":{"start":{"line":30,"column":4},"end":{"line":30,"column":567}},"2":{"start":{"line":36,"column":4},"end":{"line":36,"column":680}},"3":{"start":{"line":43,"column":4},"end":{"line":43,"column":935}},"4":{"start":{"line":50,"column":4},"end":{"line":50,"column":24}},"5":{"start":{"line":51,"column":4},"end":{"line":51,"column":36}},"6":{"start":{"line":53,"column":4},"end":{"line":53,"column":30}},"7":{"start":{"line":58,"column":4},"end":{"line":58,"column":36}},"8":{"start":{"line":59,"column":4},"end":{"line":59,"column":38}},"9":{"start":{"line":62,"column":4},"end":{"line":62,"column":57}},"10":{"start":{"line":63,"column":4},"end":{"line":63,"column":87}},"11":{"start":{"line":64,"column":4},"end":{"line":64,"column":89}},"12":{"start":{"line":69,"column":4},"end":{"line":69,"column":17}},"13":{"start":{"line":74,"column":4},"end":{"line":74,"column":36}},"14":{"start":{"line":75,"column":4},"end":{"line":75,"column":39}},"15":{"start":{"line":79,"column":4},"end":{"line":79,"column":18}},"16":{"start":{"line":83,"column":4},"end":{"line":83,"column":36}},"17":{"start":{"line":84,"column":4},"end":{"line":84,"column":86}}},"branchMap":{"1":{"line":30,"type":"if","locations":[{"start":{"line":30,"column":4},"end":{"line":30,"column":4}},{"start":{"line":30,"column":4},"end":{"line":30,"column":4}}]},"2":{"line":36,"type":"if","locations":[{"start":{"line":36,"column":4},"end":{"line":36,"column":4}},{"start":{"line":36,"column":4},"end":{"line":36,"column":4}}]},"3":{"line":63,"type":"if","locations":[{"start":{"line":63,"column":4},"end":{"line":63,"column":4}},{"start":{"line":63,"column":4},"end":{"line":63,"column":4}}]},"4":{"line":64,"type":"if","locations":[{"start":{"line":64,"column":4},"end":{"line":64,"column":4}},{"start":{"line":64,"column":4},"end":{"line":64,"column":4}}]}}},"contracts/ERC20.sol":{"l":{},"path":"/home/noah/github/republicprotocol/eth-atomic-swap/contracts/ERC20.sol","s":{},"b":{},"f":{},"fnMap":{},"statementMap":{},"branchMap":{}}} --------------------------------------------------------------------------------