├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── Subscription ├── 1337.sol ├── Subscription.compiled ├── Subscription.sol ├── arguments.js └── dependencies.js ├── TokenExampleSubscriptionToken ├── TokenExampleSubscriptionToken.compiled ├── TokenExampleSubscriptionToken.sol ├── arguments.js └── dependencies.js ├── WasteCoin ├── WasteCoin.compiled ├── WasteCoin.sol ├── arguments.js └── dependencies.js ├── attach.sh ├── backend ├── README.md ├── devDatabase.sh ├── follow.sh ├── metatxminer.js ├── modules │ ├── contractLoader.js │ ├── eventParser.js │ └── liveParser.js ├── redis.sh ├── run.sh └── tokensubminer.js ├── build.sh ├── clevis.json ├── contracts.clevis ├── demosite ├── index.html ├── site.css ├── trash-site-bottom-image-left.jpg ├── trash-site-bottom-image-right.jpg └── trash-site-top-image.jpg ├── deploy.sh ├── docker ├── Dockerfile ├── attach.sh ├── bootstrap.sh ├── build.sh └── run.sh ├── grants ├── .gitignore ├── README.md ├── build.sh ├── deploy.js ├── deploy.sh ├── invalidate.js ├── invalidate.sh ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── assets │ │ └── img │ │ │ ├── back-arrow.png │ │ │ ├── bkg.jpg │ │ │ ├── loader.gif │ │ │ ├── loader.png │ │ │ ├── logo-icon.png │ │ │ └── particles.png │ ├── coins.js │ ├── components │ │ ├── CreateGrants.js │ │ ├── GrantBox.js │ │ ├── GrantDetails.js │ │ ├── GrantsList.js │ │ ├── Home.js │ │ ├── Nav.js │ │ └── ProgressBar.js │ ├── index.css │ ├── index.js │ └── loader.gif ├── todo.md └── yarn.lock ├── invalidate.sh ├── package.json ├── public ├── coinsubscription.css ├── coinsubscription.js ├── favicon.ico ├── index.html ├── manifest.json └── particles.png ├── run.sh ├── src ├── App.css ├── App.js ├── App.test.js ├── back-arrow.png ├── bkg.jpg ├── coins.js ├── components │ ├── component-mockup.js │ ├── mainui.js │ ├── particles.js │ ├── publisher.js │ ├── publisherDeploy.js │ ├── subscriber.js │ └── subscriberApprove.js ├── index.css ├── index.js ├── loader.gif ├── loader.png ├── logo-icon.png └── particles.png ├── stop.sh └── tests ├── clevis.js ├── compile.js ├── deploy.js ├── example.js ├── fast.js ├── full.js ├── metamask.js ├── publish.js └── version.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | backend/db.creds 2 | grants/src/contracts/* 3 | api/db.creds 4 | backend/twilio.* 5 | api.key 6 | cloudfront.id 7 | src/contracts/* 8 | deploy.network 9 | aws.json 10 | backend/redisdata 11 | accounts.json 12 | **.DS_* 13 | openzeppelin-solidity 14 | zeppelin-solidity 15 | package-lock.json 16 | *.log 17 | *.ens 18 | */*/*.abi 19 | */*/*.bytecode 20 | */*/*.address 21 | */*/*.run.xml 22 | */*/*.log.* 23 | */*/*.blockNumber 24 | */*.abi 25 | */*.bytecode 26 | */*.bib 27 | */*.address 28 | */*.run.xml 29 | */*.log.* 30 | */*.blockNumber 31 | */.clevis 32 | */*/.clevis 33 | */node_modules 34 | /node_modules 35 | public/reload.txt 36 | # See https://help.github.com/ignore-files/ for more about ignoring files. 37 | 38 | # dependencies 39 | /node_modules 40 | 41 | # testing 42 | /coverage 43 | 44 | # production 45 | /build 46 | 47 | # misc 48 | .DS_Store 49 | .env.local 50 | .env.development.local 51 | .env.test.local 52 | .env.production.local 53 | 54 | npm-debug.log* 55 | yarn-debug.log* 56 | yarn-error.log* 57 | yarn-lock -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Austin Griffith 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⏰💰🤠 Token Subscriptions 🤠💰⏰ 2 | 3 | [![tokensubscriptionscreencast](https://user-images.githubusercontent.com/2653167/45360744-be265700-b58d-11e8-9ffc-10e1e57dafb0.jpg)](https://youtu.be/I16a_XKAVoY) 4 | 5 | [https://tokensubscription.com](https://tokensubscription.com) allows publishers (service providers) to deploy a smart contract on Ethereum with parameters for a subscription including destination address, token address, token amount, and period of recurrence. The publisher then supplies a link to the subscriber that is presented with the terms of the subscription to sign an off-chain meta transaction that is replayed on the defined period. The subscriber controls the flow of the tokens (starting, stopping, pausing, etc) using the ERC20 standard approve() function. 6 | 7 | Usually a nonce is used for replay protection, but the heart of the trick with [https://tokensubscription.com](https://tokensubscription.com) is a replay-able nonce that works on a defined schedule. In combination with the ERC20 allowances and meta transactions an extremely simple token subscription service can be created. 8 | 9 | # first place overall [#wyominghackathon](https://wyominghackathon.devpost.com/) 10 | 11 | ![winnerscircle](https://user-images.githubusercontent.com/2653167/45587047-52a9f580-b8bd-11e8-8969-8ebda26e78e7.jpg) 12 | 13 | [https://devpost.com/software/token-subscription](https://devpost.com/software/token-subscription) 14 | 15 | ![9i9a0053](https://user-images.githubusercontent.com/2653167/45361717-2c6c1900-b590-11e8-8d3f-3a32e9bc89b2.JPG) 16 | -------------------------------------------------------------------------------- /Subscription/1337.sol: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/Subscription/1337.sol -------------------------------------------------------------------------------- /Subscription/Subscription.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /* 4 | Super Simple Token Subscriptions - https://tokensubscription.com 5 | 6 | //// Breakin’ Through @ University of Wyoming //// 7 | 8 | Austin Thomas Griffith - https://austingriffith.com 9 | 10 | Building on previous works: 11 | https://github.com/austintgriffith/token-subscription 12 | https://gist.github.com/androolloyd/0a62ef48887be00a5eff5c17f2be849a 13 | https://media.consensys.net/subscription-services-on-the-blockchain-erc-948-6ef64b083a36 14 | https://medium.com/gitcoin/technical-deep-dive-architecture-choices-for-subscriptions-on-the-blockchain-erc948-5fae89cabc7a 15 | https://github.com/ethereum/EIPs/pull/1337 16 | https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1077.md 17 | https://github.com/gnosis/safe-contracts 18 | 19 | Earlier Meta Transaction Demo: 20 | https://github.com/austintgriffith/bouncer-proxy 21 | 22 | Huge thanks, as always, to OpenZeppelin for the rad contracts: 23 | */ 24 | 25 | import "openzeppelin-solidity/contracts/cryptography/ECDSA.sol"; 26 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 27 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 28 | 29 | 30 | contract Subscription { 31 | using ECDSA for bytes32; 32 | using SafeMath for uint256; 33 | 34 | //who deploys the contract 35 | address public author; 36 | uint8 public contractVersion; 37 | 38 | // the publisher may optionally deploy requirements for the subscription 39 | // so only meta transactions that match the requirements can be relayed 40 | address public requiredToAddress; 41 | address public requiredTokenAddress; 42 | uint256 public requiredTokenAmount; 43 | uint256 public requiredPeriodSeconds; 44 | uint256 public requiredGasPrice; 45 | 46 | 47 | // similar to a nonce that avoids replay attacks this allows a single execution 48 | // every x seconds for a given subscription 49 | // subscriptionHash => next valid block number 50 | mapping(bytes32 => uint256) public nextValidTimestamp; 51 | 52 | //we'll use a nonce for each from but because transactions can go through 53 | //multiple times, we allow anything but users can use this as a signal for 54 | //uniqueness 55 | mapping(address => uint256) public extraNonce; 56 | 57 | event ExecuteSubscription( 58 | address indexed from, //the subscriber 59 | address indexed to, //the publisher 60 | address tokenAddress, //the token address paid to the publisher 61 | uint256 tokenAmount, //the token amount paid to the publisher 62 | uint256 periodSeconds, //the period in seconds between payments 63 | uint256 gasPrice, //the amount of tokens to pay relayer (0 for free) 64 | uint256 nonce // to allow multiple subscriptions with the same parameters 65 | ); 66 | 67 | event CancelSubscription( 68 | address indexed from, //the subscriber 69 | address indexed to, //the publisher 70 | address tokenAddress, //the token address paid to the publisher 71 | uint256 tokenAmount, //the token amount paid to the publisher 72 | uint256 periodSeconds, //the period in seconds between payments 73 | uint256 gasPrice, //the amount of tokens to pay relayer (0 for free) 74 | uint256 nonce // to allow multiple subscriptions with the same parameters 75 | ); 76 | 77 | constructor( 78 | address _toAddress, 79 | address _tokenAddress, 80 | uint256 _tokenAmount, 81 | uint256 _periodSeconds, 82 | uint256 _gasPrice, 83 | uint8 _version 84 | ) public { 85 | requiredToAddress=_toAddress; 86 | requiredTokenAddress=_tokenAddress; 87 | requiredTokenAmount=_tokenAmount; 88 | requiredPeriodSeconds=_periodSeconds; 89 | requiredGasPrice=_gasPrice; 90 | author=msg.sender; 91 | contractVersion=_version; 92 | } 93 | 94 | // this is used by external smart contracts to verify on-chain that a 95 | // particular subscription is "paid" and "active" 96 | // there must be a small grace period added to allow the publisher 97 | // or desktop miner to execute 98 | function isSubscriptionActive( 99 | bytes32 subscriptionHash, 100 | uint256 gracePeriodSeconds 101 | ) 102 | external 103 | view 104 | returns (bool) 105 | { 106 | if(nextValidTimestamp[subscriptionHash]==uint256(-1)){ 107 | return false; 108 | } 109 | return (block.timestamp <= 110 | nextValidTimestamp[subscriptionHash].add(gracePeriodSeconds) 111 | ); 112 | } 113 | 114 | // given the subscription details, generate a hash and try to kind of follow 115 | // the eip-191 standard and eip-1077 standard from my dude @avsa 116 | function getSubscriptionHash( 117 | address from, //the subscriber 118 | address to, //the publisher 119 | address tokenAddress, //the token address paid to the publisher 120 | uint256 tokenAmount, //the token amount paid to the publisher 121 | uint256 periodSeconds, //the period in seconds between payments 122 | uint256 gasPrice, //the amount of tokens or eth to pay relayer (0 for free) 123 | uint256 nonce // to allow multiple subscriptions with the same parameters 124 | ) 125 | public 126 | view 127 | returns (bytes32) 128 | { 129 | // if there are requirements from the deployer, let's make sure 130 | // those are met exactly 131 | require( requiredToAddress == address(0) || to == requiredToAddress, "requiredToAddress Failure" ); 132 | require( requiredTokenAddress == address(0) || tokenAddress == requiredTokenAddress, "requiredTokenAddress Failure" ); 133 | require( requiredTokenAmount == 0 || tokenAmount == requiredTokenAmount, "requiredTokenAmount Failure" ); 134 | require( requiredPeriodSeconds == 0 || periodSeconds == requiredPeriodSeconds, "requiredPeriodSeconds Failure" ); 135 | require( requiredGasPrice == 0 || gasPrice == requiredGasPrice, "requiredGasPrice Failure" ); 136 | 137 | return keccak256( 138 | abi.encodePacked( 139 | byte(0x19), 140 | byte(0), 141 | address(this), 142 | from, 143 | to, 144 | tokenAddress, 145 | tokenAmount, 146 | periodSeconds, 147 | gasPrice, 148 | nonce 149 | )); 150 | } 151 | 152 | //ecrecover the signer from hash and the signature 153 | function getSubscriptionSigner( 154 | bytes32 subscriptionHash, //hash of subscription 155 | bytes signature //proof the subscriber signed the meta trasaction 156 | ) 157 | public 158 | pure 159 | returns (address) 160 | { 161 | return subscriptionHash.toEthSignedMessageHash().recover(signature); 162 | } 163 | 164 | //check if a subscription is signed correctly and the timestamp is ready for 165 | // the next execution to happen 166 | function isSubscriptionReady( 167 | address from, //the subscriber 168 | address to, //the publisher 169 | address tokenAddress, //the token address paid to the publisher 170 | uint256 tokenAmount, //the token amount paid to the publisher 171 | uint256 periodSeconds, //the period in seconds between payments 172 | uint256 gasPrice, //the amount of the token to incentivize the relay network 173 | uint256 nonce,// to allow multiple subscriptions with the same parameters 174 | bytes signature //proof the subscriber signed the meta trasaction 175 | ) 176 | public 177 | view 178 | returns (bool) 179 | { 180 | bytes32 subscriptionHash = getSubscriptionHash( 181 | from, to, tokenAddress, tokenAmount, periodSeconds, gasPrice, nonce 182 | ); 183 | address signer = getSubscriptionSigner(subscriptionHash, signature); 184 | uint256 allowance = ERC20(tokenAddress).allowance(from, address(this)); 185 | uint256 balance = ERC20(tokenAddress).balanceOf(from); 186 | 187 | return ( 188 | signer == from && 189 | from != to && 190 | block.timestamp >= nextValidTimestamp[subscriptionHash] && 191 | allowance >= tokenAmount.add(gasPrice) && 192 | balance >= tokenAmount.add(gasPrice) 193 | ); 194 | } 195 | 196 | // you don't really need this if you are using the approve/transferFrom method 197 | // because you control the flow of tokens by approving this contract address, 198 | // but to make the contract an extensible example for later user I'll add this 199 | function cancelSubscription( 200 | address from, //the subscriber 201 | address to, //the publisher 202 | address tokenAddress, //the token address paid to the publisher 203 | uint256 tokenAmount, //the token amount paid to the publisher 204 | uint256 periodSeconds, //the period in seconds between payments 205 | uint256 gasPrice, //the amount of tokens or eth to pay relayer (0 for free) 206 | uint256 nonce, //to allow multiple subscriptions with the same parameters 207 | bytes signature //proof the subscriber signed the meta trasaction 208 | ) 209 | external 210 | returns (bool success) 211 | { 212 | bytes32 subscriptionHash = getSubscriptionHash( 213 | from, to, tokenAddress, tokenAmount, periodSeconds, gasPrice, nonce 214 | ); 215 | address signer = getSubscriptionSigner(subscriptionHash, signature); 216 | 217 | //the signature must be valid 218 | require(signer == from, "Invalid Signature for subscription cancellation"); 219 | 220 | //make sure it's the subscriber 221 | require(from == msg.sender, 'msg.sender is not the subscriber'); 222 | 223 | //nextValidTimestamp should be a timestamp that will never 224 | //be reached during the brief window human existence 225 | nextValidTimestamp[subscriptionHash]=uint256(-1); 226 | 227 | emit CancelSubscription( 228 | from, to, tokenAddress, tokenAmount, periodSeconds, gasPrice, nonce 229 | ); 230 | 231 | return true; 232 | } 233 | 234 | // execute the transferFrom to pay the publisher from the subscriber 235 | // the subscriber has full control by approving this contract an allowance 236 | function executeSubscription( 237 | address from, //the subscriber 238 | address to, //the publisher 239 | address tokenAddress, //the token address paid to the publisher 240 | uint256 tokenAmount, //the token amount paid to the publisher 241 | uint256 periodSeconds, //the period in seconds between payments 242 | uint256 gasPrice, //the amount of tokens or eth to pay relayer (0 for free) 243 | uint256 nonce, // to allow multiple subscriptions with the same parameters 244 | bytes signature //proof the subscriber signed the meta trasaction 245 | ) 246 | public 247 | returns (bool success) 248 | { 249 | bytes32 subscriptionHash = getSubscriptionHash( 250 | from, to, tokenAddress, tokenAmount, periodSeconds, gasPrice, nonce 251 | ); 252 | 253 | // make sure the subscription is valid and ready 254 | require( isSubscriptionReady(from, to, tokenAddress, tokenAmount, periodSeconds, gasPrice, nonce, signature), "Subscription is not ready or conditions of transction are not met" ); 255 | 256 | //increment the timestamp by the period so it wont be valid until then 257 | nextValidTimestamp[subscriptionHash] = block.timestamp.add(periodSeconds); 258 | 259 | //check to see if this nonce is larger than the current count and we'll set that for this 'from' 260 | if(nonce > extraNonce[from]){ 261 | extraNonce[from] = nonce; 262 | } 263 | 264 | // now, let make the transfer from the subscriber to the publisher 265 | uint256 startingBalance = ERC20(tokenAddress).balanceOf(to); 266 | ERC20(tokenAddress).transferFrom(from,to,tokenAmount); 267 | require( 268 | (startingBalance+tokenAmount) == ERC20(tokenAddress).balanceOf(to), 269 | "ERC20 Balance did not change correctly" 270 | ); 271 | 272 | require( 273 | checkSuccess(), 274 | "Subscription::executeSubscription TransferFrom failed" 275 | ); 276 | 277 | 278 | emit ExecuteSubscription( 279 | from, to, tokenAddress, tokenAmount, periodSeconds, gasPrice, nonce 280 | ); 281 | 282 | // it is possible for the subscription execution to be run by a third party 283 | // incentivized in the terms of the subscription with a gasPrice of the tokens 284 | // - pay that out now... 285 | if (gasPrice > 0) { 286 | //the relayer is incentivized by a little of the same token from 287 | // the subscriber ... as far as the subscriber knows, they are 288 | // just sending X tokens to the publisher, but the publisher can 289 | // choose to send Y of those X to a relayer to run their transactions 290 | // the publisher will receive X - Y tokens 291 | // this must all be setup in the constructor 292 | // if not, the subscriber chooses all the params including what goes 293 | // to the publisher and what goes to the relayer 294 | ERC20(tokenAddress).transferFrom(from, msg.sender, gasPrice); 295 | require( 296 | checkSuccess(), 297 | "Subscription::executeSubscription Failed to pay gas as from account" 298 | ); 299 | } 300 | 301 | return true; 302 | } 303 | 304 | // because of issues with non-standard erc20s the transferFrom can always return false 305 | // to fix this we run it and then check the return of the previous function: 306 | // https://github.com/ethereum/solidity/issues/4116 307 | /** 308 | * Checks the return value of the previous function. Returns true if the previous function 309 | * function returned 32 non-zero bytes or returned zero bytes. 310 | */ 311 | function checkSuccess( 312 | ) 313 | private 314 | pure 315 | returns (bool) 316 | { 317 | uint256 returnValue = 0; 318 | 319 | /* solium-disable-next-line security/no-inline-assembly */ 320 | assembly { 321 | // check number of bytes returned from last function call 322 | switch returndatasize 323 | 324 | // no bytes returned: assume success 325 | case 0x0 { 326 | returnValue := 1 327 | } 328 | 329 | // 32 bytes returned: check if non-zero 330 | case 0x20 { 331 | // copy 32 bytes into scratch space 332 | returndatacopy(0x0, 0x0, 0x20) 333 | 334 | // load those bytes into returnValue 335 | returnValue := mload(0x0) 336 | } 337 | 338 | // not sure what was returned: dont mark as success 339 | default { } 340 | } 341 | 342 | return returnValue != 0; 343 | } 344 | 345 | //we would like a way for the author to completly destroy the subscription 346 | // contract to prevent further transfers 347 | function endContract() 348 | external 349 | { 350 | require(msg.sender==author); 351 | selfdestruct(author); 352 | } 353 | 354 | // let's go ahead and revert any ETH sent directly to the contract 355 | function () public payable { 356 | revert (); 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /Subscription/arguments.js: -------------------------------------------------------------------------------- 1 | /* 2 | Example of passing in a string to the constructor: 3 | module.exports = ["hello world"] 4 | */ 5 | 6 | module.exports = ["0x0000000000000000000000000000000000000000", 7 | "0x0000000000000000000000000000000000000000", 8 | 0, 9 | 0, 10 | 0] 11 | -------------------------------------------------------------------------------- /Subscription/dependencies.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | module.exports = { 3 | 'openzeppelin-solidity/contracts/cryptography/ECDSA.sol': fs.readFileSync('openzeppelin-solidity/contracts/cryptography/ECDSA.sol', 'utf8'), 4 | 'openzeppelin-solidity/contracts/math/SafeMath.sol': fs.readFileSync('openzeppelin-solidity/contracts/math/SafeMath.sol', 'utf8'), 5 | 'openzeppelin-solidity/contracts/token/ERC20/ERC20.sol': fs.readFileSync('openzeppelin-solidity/contracts/token/ERC20/ERC20.sol', 'utf8'), 6 | 'openzeppelin-solidity/contracts/token/ERC20/IERC20.sol': fs.readFileSync('openzeppelin-solidity/contracts/token/ERC20/IERC20.sol', 'utf8'), 7 | } 8 | -------------------------------------------------------------------------------- /TokenExampleSubscriptionToken/TokenExampleSubscriptionToken.compiled: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /* 4 | TEST token for tokensubscription.com 5 | */ 6 | 7 | 8 | 9 | 10 | 11 | /** 12 | * @title ERC20 interface 13 | * @dev see https://github.com/ethereum/EIPs/issues/20 14 | */ 15 | interface IERC20 { 16 | function totalSupply() external view returns (uint256); 17 | 18 | function balanceOf(address who) external view returns (uint256); 19 | 20 | function allowance(address owner, address spender) 21 | external view returns (uint256); 22 | 23 | function transfer(address to, uint256 value) external returns (bool); 24 | 25 | function approve(address spender, uint256 value) 26 | external returns (bool); 27 | 28 | function transferFrom(address from, address to, uint256 value) 29 | external returns (bool); 30 | 31 | event Transfer( 32 | address indexed from, 33 | address indexed to, 34 | uint256 value 35 | ); 36 | 37 | event Approval( 38 | address indexed owner, 39 | address indexed spender, 40 | uint256 value 41 | ); 42 | } 43 | 44 | 45 | 46 | /** 47 | * @title SafeMath 48 | * @dev Math operations with safety checks that revert on error 49 | */ 50 | library SafeMath { 51 | 52 | /** 53 | * @dev Multiplies two numbers, reverts on overflow. 54 | */ 55 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 56 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 57 | // benefit is lost if 'b' is also tested. 58 | // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 59 | if (a == 0) { 60 | return 0; 61 | } 62 | 63 | uint256 c = a * b; 64 | require(c / a == b); 65 | 66 | return c; 67 | } 68 | 69 | /** 70 | * @dev Integer division of two numbers truncating the quotient, reverts on division by zero. 71 | */ 72 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 73 | require(b > 0); // Solidity only automatically asserts when dividing by 0 74 | uint256 c = a / b; 75 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 76 | 77 | return c; 78 | } 79 | 80 | /** 81 | * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). 82 | */ 83 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 84 | require(b <= a); 85 | uint256 c = a - b; 86 | 87 | return c; 88 | } 89 | 90 | /** 91 | * @dev Adds two numbers, reverts on overflow. 92 | */ 93 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 94 | uint256 c = a + b; 95 | require(c >= a); 96 | 97 | return c; 98 | } 99 | 100 | /** 101 | * @dev Divides two numbers and returns the remainder (unsigned integer modulo), 102 | * reverts when dividing by zero. 103 | */ 104 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 105 | require(b != 0); 106 | return a % b; 107 | } 108 | } 109 | 110 | 111 | 112 | /** 113 | * @title Standard ERC20 token 114 | * 115 | * @dev Implementation of the basic standard token. 116 | * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md 117 | * Originally based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol 118 | */ 119 | contract ERC20 is IERC20 { 120 | using SafeMath for uint256; 121 | 122 | mapping (address => uint256) private _balances; 123 | 124 | mapping (address => mapping (address => uint256)) private _allowed; 125 | 126 | uint256 private _totalSupply; 127 | 128 | /** 129 | * @dev Total number of tokens in existence 130 | */ 131 | function totalSupply() public view returns (uint256) { 132 | return _totalSupply; 133 | } 134 | 135 | /** 136 | * @dev Gets the balance of the specified address. 137 | * @param owner The address to query the the balance of. 138 | * @return An uint256 representing the amount owned by the passed address. 139 | */ 140 | function balanceOf(address owner) public view returns (uint256) { 141 | return _balances[owner]; 142 | } 143 | 144 | /** 145 | * @dev Function to check the amount of tokens that an owner allowed to a spender. 146 | * @param owner address The address which owns the funds. 147 | * @param spender address The address which will spend the funds. 148 | * @return A uint256 specifying the amount of tokens still available for the spender. 149 | */ 150 | function allowance( 151 | address owner, 152 | address spender 153 | ) 154 | public 155 | view 156 | returns (uint256) 157 | { 158 | return _allowed[owner][spender]; 159 | } 160 | 161 | /** 162 | * @dev Transfer token for a specified address 163 | * @param to The address to transfer to. 164 | * @param value The amount to be transferred. 165 | */ 166 | function transfer(address to, uint256 value) public returns (bool) { 167 | require(value <= _balances[msg.sender]); 168 | require(to != address(0)); 169 | 170 | _balances[msg.sender] = _balances[msg.sender].sub(value); 171 | _balances[to] = _balances[to].add(value); 172 | emit Transfer(msg.sender, to, value); 173 | return true; 174 | } 175 | 176 | /** 177 | * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 178 | * Beware that changing an allowance with this method brings the risk that someone may use both the old 179 | * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 180 | * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 181 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 182 | * @param spender The address which will spend the funds. 183 | * @param value The amount of tokens to be spent. 184 | */ 185 | function approve(address spender, uint256 value) public returns (bool) { 186 | require(spender != address(0)); 187 | 188 | _allowed[msg.sender][spender] = value; 189 | emit Approval(msg.sender, spender, value); 190 | return true; 191 | } 192 | 193 | /** 194 | * @dev Transfer tokens from one address to another 195 | * @param from address The address which you want to send tokens from 196 | * @param to address The address which you want to transfer to 197 | * @param value uint256 the amount of tokens to be transferred 198 | */ 199 | function transferFrom( 200 | address from, 201 | address to, 202 | uint256 value 203 | ) 204 | public 205 | returns (bool) 206 | { 207 | require(value <= _balances[from]); 208 | require(value <= _allowed[from][msg.sender]); 209 | require(to != address(0)); 210 | 211 | _balances[from] = _balances[from].sub(value); 212 | _balances[to] = _balances[to].add(value); 213 | _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value); 214 | emit Transfer(from, to, value); 215 | return true; 216 | } 217 | 218 | /** 219 | * @dev Increase the amount of tokens that an owner allowed to a spender. 220 | * approve should be called when allowed_[_spender] == 0. To increment 221 | * allowed value is better to use this function to avoid 2 calls (and wait until 222 | * the first transaction is mined) 223 | * From MonolithDAO Token.sol 224 | * @param spender The address which will spend the funds. 225 | * @param addedValue The amount of tokens to increase the allowance by. 226 | */ 227 | function increaseAllowance( 228 | address spender, 229 | uint256 addedValue 230 | ) 231 | public 232 | returns (bool) 233 | { 234 | require(spender != address(0)); 235 | 236 | _allowed[msg.sender][spender] = ( 237 | _allowed[msg.sender][spender].add(addedValue)); 238 | emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); 239 | return true; 240 | } 241 | 242 | /** 243 | * @dev Decrease the amount of tokens that an owner allowed to a spender. 244 | * approve should be called when allowed_[_spender] == 0. To decrement 245 | * allowed value is better to use this function to avoid 2 calls (and wait until 246 | * the first transaction is mined) 247 | * From MonolithDAO Token.sol 248 | * @param spender The address which will spend the funds. 249 | * @param subtractedValue The amount of tokens to decrease the allowance by. 250 | */ 251 | function decreaseAllowance( 252 | address spender, 253 | uint256 subtractedValue 254 | ) 255 | public 256 | returns (bool) 257 | { 258 | require(spender != address(0)); 259 | 260 | _allowed[msg.sender][spender] = ( 261 | _allowed[msg.sender][spender].sub(subtractedValue)); 262 | emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); 263 | return true; 264 | } 265 | 266 | /** 267 | * @dev Internal function that mints an amount of the token and assigns it to 268 | * an account. This encapsulates the modification of balances such that the 269 | * proper events are emitted. 270 | * @param account The account that will receive the created tokens. 271 | * @param amount The amount that will be created. 272 | */ 273 | function _mint(address account, uint256 amount) internal { 274 | require(account != 0); 275 | _totalSupply = _totalSupply.add(amount); 276 | _balances[account] = _balances[account].add(amount); 277 | emit Transfer(address(0), account, amount); 278 | } 279 | 280 | /** 281 | * @dev Internal function that burns an amount of the token of a given 282 | * account. 283 | * @param account The account whose tokens will be burnt. 284 | * @param amount The amount that will be burnt. 285 | */ 286 | function _burn(address account, uint256 amount) internal { 287 | require(account != 0); 288 | require(amount <= _balances[account]); 289 | 290 | _totalSupply = _totalSupply.sub(amount); 291 | _balances[account] = _balances[account].sub(amount); 292 | emit Transfer(account, address(0), amount); 293 | } 294 | 295 | /** 296 | * @dev Internal function that burns an amount of the token of a given 297 | * account, deducting from the sender's allowance for said account. Uses the 298 | * internal burn function. 299 | * @param account The account whose tokens will be burnt. 300 | * @param amount The amount that will be burnt. 301 | */ 302 | function _burnFrom(address account, uint256 amount) internal { 303 | require(amount <= _allowed[account][msg.sender]); 304 | 305 | // Should https://github.com/OpenZeppelin/zeppelin-solidity/issues/707 be accepted, 306 | // this function needs to emit an event with the updated approval. 307 | _allowed[account][msg.sender] = _allowed[account][msg.sender].sub( 308 | amount); 309 | _burn(account, amount); 310 | } 311 | } 312 | 313 | 314 | 315 | 316 | /** 317 | * @title Roles 318 | * @dev Library for managing addresses assigned to a Role. 319 | */ 320 | library Roles { 321 | struct Role { 322 | mapping (address => bool) bearer; 323 | } 324 | 325 | /** 326 | * @dev give an account access to this role 327 | */ 328 | function add(Role storage role, address account) internal { 329 | require(account != address(0)); 330 | role.bearer[account] = true; 331 | } 332 | 333 | /** 334 | * @dev remove an account's access to this role 335 | */ 336 | function remove(Role storage role, address account) internal { 337 | require(account != address(0)); 338 | role.bearer[account] = false; 339 | } 340 | 341 | /** 342 | * @dev check if an account has this role 343 | * @return bool 344 | */ 345 | function has(Role storage role, address account) 346 | internal 347 | view 348 | returns (bool) 349 | { 350 | require(account != address(0)); 351 | return role.bearer[account]; 352 | } 353 | } 354 | 355 | 356 | 357 | contract MinterRole { 358 | using Roles for Roles.Role; 359 | 360 | event MinterAdded(address indexed account); 361 | event MinterRemoved(address indexed account); 362 | 363 | Roles.Role private minters; 364 | 365 | constructor() public { 366 | minters.add(msg.sender); 367 | } 368 | 369 | modifier onlyMinter() { 370 | require(isMinter(msg.sender)); 371 | _; 372 | } 373 | 374 | function isMinter(address account) public view returns (bool) { 375 | return minters.has(account); 376 | } 377 | 378 | function addMinter(address account) public onlyMinter { 379 | minters.add(account); 380 | emit MinterAdded(account); 381 | } 382 | 383 | function renounceMinter() public { 384 | minters.remove(msg.sender); 385 | } 386 | 387 | function _removeMinter(address account) internal { 388 | minters.remove(account); 389 | emit MinterRemoved(account); 390 | } 391 | } 392 | 393 | 394 | 395 | /** 396 | * @title ERC20Mintable 397 | * @dev ERC20 minting logic 398 | */ 399 | contract ERC20Mintable is ERC20, MinterRole { 400 | event MintingFinished(); 401 | 402 | bool private _mintingFinished = false; 403 | 404 | modifier onlyBeforeMintingFinished() { 405 | require(!_mintingFinished); 406 | _; 407 | } 408 | 409 | /** 410 | * @return true if the minting is finished. 411 | */ 412 | function mintingFinished() public view returns(bool) { 413 | return _mintingFinished; 414 | } 415 | 416 | /** 417 | * @dev Function to mint tokens 418 | * @param to The address that will receive the minted tokens. 419 | * @param amount The amount of tokens to mint. 420 | * @return A boolean that indicates if the operation was successful. 421 | */ 422 | function mint( 423 | address to, 424 | uint256 amount 425 | ) 426 | public 427 | onlyMinter 428 | onlyBeforeMintingFinished 429 | returns (bool) 430 | { 431 | _mint(to, amount); 432 | return true; 433 | } 434 | 435 | /** 436 | * @dev Function to stop minting new tokens. 437 | * @return True if the operation was successful. 438 | */ 439 | function finishMinting() 440 | public 441 | onlyMinter 442 | onlyBeforeMintingFinished 443 | returns (bool) 444 | { 445 | _mintingFinished = true; 446 | emit MintingFinished(); 447 | return true; 448 | } 449 | } 450 | 451 | 452 | contract TokenExampleSubscriptionToken is ERC20Mintable { 453 | 454 | string public name = "TokenExampleSubscriptionToken"; 455 | string public symbol = "TEST"; 456 | uint8 public decimals = 18; 457 | 458 | constructor() public { } 459 | 460 | } 461 | 462 | 463 | 464 | 465 | 466 | 467 | -------------------------------------------------------------------------------- /TokenExampleSubscriptionToken/TokenExampleSubscriptionToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /* 4 | TEST token for tokensubscription.com 5 | */ 6 | 7 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol"; 8 | 9 | contract TokenExampleSubscriptionToken is ERC20Mintable { 10 | 11 | string public name = "TokenExampleSubscriptionToken"; 12 | string public symbol = "TEST"; 13 | uint8 public decimals = 18; 14 | 15 | constructor() public { } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /TokenExampleSubscriptionToken/arguments.js: -------------------------------------------------------------------------------- 1 | /* 2 | Example of passing in a string to the constructor: 3 | module.exports = ["hello world"] 4 | */ 5 | 6 | module.exports = [] 7 | -------------------------------------------------------------------------------- /TokenExampleSubscriptionToken/dependencies.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | module.exports = { 3 | 'openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol': fs.readFileSync('openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol', 'utf8'), 4 | 'openzeppelin-solidity/contracts/math/SafeMath.sol': fs.readFileSync('openzeppelin-solidity/contracts/math/SafeMath.sol', 'utf8'), 5 | 'openzeppelin-solidity/contracts/token/ERC20/ERC20.sol': fs.readFileSync('openzeppelin-solidity/contracts/token/ERC20/ERC20.sol', 'utf8'), 6 | 'openzeppelin-solidity/contracts/token/ERC20/IERC20.sol': fs.readFileSync('openzeppelin-solidity/contracts/token/ERC20/IERC20.sol', 'utf8'), 7 | 'openzeppelin-solidity/contracts/access/roles/MinterRole.sol': fs.readFileSync('openzeppelin-solidity/contracts/access/roles/MinterRole.sol', 'utf8'), 8 | 'openzeppelin-solidity/contracts/access/Roles.sol': fs.readFileSync('openzeppelin-solidity/contracts/access/Roles.sol', 'utf8'), 9 | 'openzeppelin-solidity/contracts/ownership/Ownable.sol': fs.readFileSync('openzeppelin-solidity/contracts/ownership/Ownable.sol', 'utf8'), 10 | } 11 | -------------------------------------------------------------------------------- /WasteCoin/WasteCoin.compiled: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /* 4 | TEST token for tokensubscription.com 5 | */ 6 | 7 | 8 | 9 | 10 | 11 | /** 12 | * @title ERC20 interface 13 | * @dev see https://github.com/ethereum/EIPs/issues/20 14 | */ 15 | interface IERC20 { 16 | function totalSupply() external view returns (uint256); 17 | 18 | function balanceOf(address who) external view returns (uint256); 19 | 20 | function allowance(address owner, address spender) 21 | external view returns (uint256); 22 | 23 | function transfer(address to, uint256 value) external returns (bool); 24 | 25 | function approve(address spender, uint256 value) 26 | external returns (bool); 27 | 28 | function transferFrom(address from, address to, uint256 value) 29 | external returns (bool); 30 | 31 | event Transfer( 32 | address indexed from, 33 | address indexed to, 34 | uint256 value 35 | ); 36 | 37 | event Approval( 38 | address indexed owner, 39 | address indexed spender, 40 | uint256 value 41 | ); 42 | } 43 | 44 | 45 | 46 | /** 47 | * @title SafeMath 48 | * @dev Math operations with safety checks that revert on error 49 | */ 50 | library SafeMath { 51 | 52 | /** 53 | * @dev Multiplies two numbers, reverts on overflow. 54 | */ 55 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 56 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 57 | // benefit is lost if 'b' is also tested. 58 | // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 59 | if (a == 0) { 60 | return 0; 61 | } 62 | 63 | uint256 c = a * b; 64 | require(c / a == b); 65 | 66 | return c; 67 | } 68 | 69 | /** 70 | * @dev Integer division of two numbers truncating the quotient, reverts on division by zero. 71 | */ 72 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 73 | require(b > 0); // Solidity only automatically asserts when dividing by 0 74 | uint256 c = a / b; 75 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 76 | 77 | return c; 78 | } 79 | 80 | /** 81 | * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). 82 | */ 83 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 84 | require(b <= a); 85 | uint256 c = a - b; 86 | 87 | return c; 88 | } 89 | 90 | /** 91 | * @dev Adds two numbers, reverts on overflow. 92 | */ 93 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 94 | uint256 c = a + b; 95 | require(c >= a); 96 | 97 | return c; 98 | } 99 | 100 | /** 101 | * @dev Divides two numbers and returns the remainder (unsigned integer modulo), 102 | * reverts when dividing by zero. 103 | */ 104 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 105 | require(b != 0); 106 | return a % b; 107 | } 108 | } 109 | 110 | 111 | 112 | /** 113 | * @title Standard ERC20 token 114 | * 115 | * @dev Implementation of the basic standard token. 116 | * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md 117 | * Originally based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol 118 | */ 119 | contract ERC20 is IERC20 { 120 | using SafeMath for uint256; 121 | 122 | mapping (address => uint256) private _balances; 123 | 124 | mapping (address => mapping (address => uint256)) private _allowed; 125 | 126 | uint256 private _totalSupply; 127 | 128 | /** 129 | * @dev Total number of tokens in existence 130 | */ 131 | function totalSupply() public view returns (uint256) { 132 | return _totalSupply; 133 | } 134 | 135 | /** 136 | * @dev Gets the balance of the specified address. 137 | * @param owner The address to query the the balance of. 138 | * @return An uint256 representing the amount owned by the passed address. 139 | */ 140 | function balanceOf(address owner) public view returns (uint256) { 141 | return _balances[owner]; 142 | } 143 | 144 | /** 145 | * @dev Function to check the amount of tokens that an owner allowed to a spender. 146 | * @param owner address The address which owns the funds. 147 | * @param spender address The address which will spend the funds. 148 | * @return A uint256 specifying the amount of tokens still available for the spender. 149 | */ 150 | function allowance( 151 | address owner, 152 | address spender 153 | ) 154 | public 155 | view 156 | returns (uint256) 157 | { 158 | return _allowed[owner][spender]; 159 | } 160 | 161 | /** 162 | * @dev Transfer token for a specified address 163 | * @param to The address to transfer to. 164 | * @param value The amount to be transferred. 165 | */ 166 | function transfer(address to, uint256 value) public returns (bool) { 167 | require(value <= _balances[msg.sender]); 168 | require(to != address(0)); 169 | 170 | _balances[msg.sender] = _balances[msg.sender].sub(value); 171 | _balances[to] = _balances[to].add(value); 172 | emit Transfer(msg.sender, to, value); 173 | return true; 174 | } 175 | 176 | /** 177 | * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 178 | * Beware that changing an allowance with this method brings the risk that someone may use both the old 179 | * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 180 | * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 181 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 182 | * @param spender The address which will spend the funds. 183 | * @param value The amount of tokens to be spent. 184 | */ 185 | function approve(address spender, uint256 value) public returns (bool) { 186 | require(spender != address(0)); 187 | 188 | _allowed[msg.sender][spender] = value; 189 | emit Approval(msg.sender, spender, value); 190 | return true; 191 | } 192 | 193 | /** 194 | * @dev Transfer tokens from one address to another 195 | * @param from address The address which you want to send tokens from 196 | * @param to address The address which you want to transfer to 197 | * @param value uint256 the amount of tokens to be transferred 198 | */ 199 | function transferFrom( 200 | address from, 201 | address to, 202 | uint256 value 203 | ) 204 | public 205 | returns (bool) 206 | { 207 | <<<<<<< HEAD 208 | require(value <= _allowed[from][msg.sender]); 209 | 210 | _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value); 211 | _transfer(from, to, value); 212 | ======= 213 | require(value <= _balances[from]); 214 | require(value <= _allowed[from][msg.sender]); 215 | require(to != address(0)); 216 | 217 | _balances[from] = _balances[from].sub(value); 218 | _balances[to] = _balances[to].add(value); 219 | _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value); 220 | emit Transfer(from, to, value); 221 | >>>>>>> 409679cb641f5bcaa76d81d090b97dca4e00bddf 222 | return true; 223 | } 224 | 225 | /** 226 | * @dev Increase the amount of tokens that an owner allowed to a spender. 227 | * approve should be called when allowed_[_spender] == 0. To increment 228 | * allowed value is better to use this function to avoid 2 calls (and wait until 229 | * the first transaction is mined) 230 | * From MonolithDAO Token.sol 231 | * @param spender The address which will spend the funds. 232 | * @param addedValue The amount of tokens to increase the allowance by. 233 | */ 234 | function increaseAllowance( 235 | address spender, 236 | uint256 addedValue 237 | ) 238 | public 239 | returns (bool) 240 | { 241 | require(spender != address(0)); 242 | 243 | _allowed[msg.sender][spender] = ( 244 | _allowed[msg.sender][spender].add(addedValue)); 245 | emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); 246 | return true; 247 | } 248 | 249 | /** 250 | * @dev Decrease the amount of tokens that an owner allowed to a spender. 251 | * approve should be called when allowed_[_spender] == 0. To decrement 252 | * allowed value is better to use this function to avoid 2 calls (and wait until 253 | * the first transaction is mined) 254 | * From MonolithDAO Token.sol 255 | * @param spender The address which will spend the funds. 256 | * @param subtractedValue The amount of tokens to decrease the allowance by. 257 | */ 258 | function decreaseAllowance( 259 | address spender, 260 | uint256 subtractedValue 261 | ) 262 | public 263 | returns (bool) 264 | { 265 | require(spender != address(0)); 266 | 267 | _allowed[msg.sender][spender] = ( 268 | _allowed[msg.sender][spender].sub(subtractedValue)); 269 | emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); 270 | return true; 271 | } 272 | 273 | /** 274 | <<<<<<< HEAD 275 | * @dev Transfer token for a specified addresses 276 | * @param from The address to transfer from. 277 | * @param to The address to transfer to. 278 | * @param value The amount to be transferred. 279 | */ 280 | function _transfer(address from, address to, uint256 value) internal { 281 | require(value <= _balances[from]); 282 | require(to != address(0)); 283 | 284 | _balances[from] = _balances[from].sub(value); 285 | _balances[to] = _balances[to].add(value); 286 | emit Transfer(from, to, value); 287 | } 288 | 289 | /** 290 | ======= 291 | >>>>>>> 409679cb641f5bcaa76d81d090b97dca4e00bddf 292 | * @dev Internal function that mints an amount of the token and assigns it to 293 | * an account. This encapsulates the modification of balances such that the 294 | * proper events are emitted. 295 | * @param account The account that will receive the created tokens. 296 | * @param amount The amount that will be created. 297 | */ 298 | function _mint(address account, uint256 amount) internal { 299 | require(account != 0); 300 | _totalSupply = _totalSupply.add(amount); 301 | _balances[account] = _balances[account].add(amount); 302 | emit Transfer(address(0), account, amount); 303 | } 304 | 305 | /** 306 | * @dev Internal function that burns an amount of the token of a given 307 | * account. 308 | * @param account The account whose tokens will be burnt. 309 | * @param amount The amount that will be burnt. 310 | */ 311 | function _burn(address account, uint256 amount) internal { 312 | require(account != 0); 313 | require(amount <= _balances[account]); 314 | 315 | _totalSupply = _totalSupply.sub(amount); 316 | _balances[account] = _balances[account].sub(amount); 317 | emit Transfer(account, address(0), amount); 318 | } 319 | 320 | /** 321 | * @dev Internal function that burns an amount of the token of a given 322 | * account, deducting from the sender's allowance for said account. Uses the 323 | * internal burn function. 324 | * @param account The account whose tokens will be burnt. 325 | * @param amount The amount that will be burnt. 326 | */ 327 | function _burnFrom(address account, uint256 amount) internal { 328 | require(amount <= _allowed[account][msg.sender]); 329 | 330 | // Should https://github.com/OpenZeppelin/zeppelin-solidity/issues/707 be accepted, 331 | // this function needs to emit an event with the updated approval. 332 | _allowed[account][msg.sender] = _allowed[account][msg.sender].sub( 333 | amount); 334 | _burn(account, amount); 335 | } 336 | } 337 | 338 | 339 | 340 | 341 | /** 342 | * @title Roles 343 | * @dev Library for managing addresses assigned to a Role. 344 | */ 345 | library Roles { 346 | struct Role { 347 | mapping (address => bool) bearer; 348 | } 349 | 350 | /** 351 | * @dev give an account access to this role 352 | */ 353 | function add(Role storage role, address account) internal { 354 | require(account != address(0)); 355 | role.bearer[account] = true; 356 | } 357 | 358 | /** 359 | * @dev remove an account's access to this role 360 | */ 361 | function remove(Role storage role, address account) internal { 362 | require(account != address(0)); 363 | role.bearer[account] = false; 364 | } 365 | 366 | /** 367 | * @dev check if an account has this role 368 | * @return bool 369 | */ 370 | function has(Role storage role, address account) 371 | internal 372 | view 373 | returns (bool) 374 | { 375 | require(account != address(0)); 376 | return role.bearer[account]; 377 | } 378 | } 379 | 380 | 381 | 382 | contract MinterRole { 383 | using Roles for Roles.Role; 384 | 385 | event MinterAdded(address indexed account); 386 | event MinterRemoved(address indexed account); 387 | 388 | Roles.Role private minters; 389 | 390 | constructor() public { 391 | <<<<<<< HEAD 392 | _addMinter(msg.sender); 393 | ======= 394 | minters.add(msg.sender); 395 | >>>>>>> 409679cb641f5bcaa76d81d090b97dca4e00bddf 396 | } 397 | 398 | modifier onlyMinter() { 399 | require(isMinter(msg.sender)); 400 | _; 401 | } 402 | 403 | function isMinter(address account) public view returns (bool) { 404 | return minters.has(account); 405 | } 406 | 407 | function addMinter(address account) public onlyMinter { 408 | <<<<<<< HEAD 409 | _addMinter(account); 410 | } 411 | 412 | function renounceMinter() public { 413 | _removeMinter(msg.sender); 414 | } 415 | 416 | function _addMinter(address account) internal { 417 | minters.add(account); 418 | emit MinterAdded(account); 419 | ======= 420 | minters.add(account); 421 | emit MinterAdded(account); 422 | } 423 | 424 | function renounceMinter() public { 425 | minters.remove(msg.sender); 426 | >>>>>>> 409679cb641f5bcaa76d81d090b97dca4e00bddf 427 | } 428 | 429 | function _removeMinter(address account) internal { 430 | minters.remove(account); 431 | emit MinterRemoved(account); 432 | } 433 | } 434 | 435 | 436 | 437 | /** 438 | * @title ERC20Mintable 439 | * @dev ERC20 minting logic 440 | */ 441 | contract ERC20Mintable is ERC20, MinterRole { 442 | <<<<<<< HEAD 443 | ======= 444 | event MintingFinished(); 445 | 446 | bool private _mintingFinished = false; 447 | 448 | modifier onlyBeforeMintingFinished() { 449 | require(!_mintingFinished); 450 | _; 451 | } 452 | 453 | /** 454 | * @return true if the minting is finished. 455 | */ 456 | function mintingFinished() public view returns(bool) { 457 | return _mintingFinished; 458 | } 459 | 460 | >>>>>>> 409679cb641f5bcaa76d81d090b97dca4e00bddf 461 | /** 462 | * @dev Function to mint tokens 463 | * @param to The address that will receive the minted tokens. 464 | * @param amount The amount of tokens to mint. 465 | * @return A boolean that indicates if the operation was successful. 466 | */ 467 | function mint( 468 | address to, 469 | uint256 amount 470 | ) 471 | public 472 | onlyMinter 473 | <<<<<<< HEAD 474 | ======= 475 | onlyBeforeMintingFinished 476 | >>>>>>> 409679cb641f5bcaa76d81d090b97dca4e00bddf 477 | returns (bool) 478 | { 479 | _mint(to, amount); 480 | return true; 481 | } 482 | <<<<<<< HEAD 483 | ======= 484 | 485 | /** 486 | * @dev Function to stop minting new tokens. 487 | * @return True if the operation was successful. 488 | */ 489 | function finishMinting() 490 | public 491 | onlyMinter 492 | onlyBeforeMintingFinished 493 | returns (bool) 494 | { 495 | _mintingFinished = true; 496 | emit MintingFinished(); 497 | return true; 498 | } 499 | >>>>>>> 409679cb641f5bcaa76d81d090b97dca4e00bddf 500 | } 501 | 502 | 503 | contract WasteCoin is ERC20Mintable { 504 | 505 | string public name = "WasteCoin"; 506 | string public symbol = "WC"; 507 | uint8 public decimals = 18; 508 | 509 | constructor() public { } 510 | 511 | } 512 | -------------------------------------------------------------------------------- /WasteCoin/WasteCoin.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /* 4 | TEST token for tokensubscription.com 5 | */ 6 | 7 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol"; 8 | 9 | contract WasteCoin is ERC20Mintable { 10 | 11 | string public name = "WasteCoin"; 12 | string public symbol = "WC"; 13 | uint8 public decimals = 18; 14 | 15 | constructor() public { } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /WasteCoin/arguments.js: -------------------------------------------------------------------------------- 1 | /* 2 | Example of passing in a string to the constructor: 3 | module.exports = ["hello world"] 4 | */ 5 | 6 | module.exports = [] 7 | -------------------------------------------------------------------------------- /WasteCoin/dependencies.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | module.exports = { 3 | 'openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol': fs.readFileSync('openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol', 'utf8'), 4 | 'openzeppelin-solidity/contracts/math/SafeMath.sol': fs.readFileSync('openzeppelin-solidity/contracts/math/SafeMath.sol', 'utf8'), 5 | 'openzeppelin-solidity/contracts/token/ERC20/ERC20.sol': fs.readFileSync('openzeppelin-solidity/contracts/token/ERC20/ERC20.sol', 'utf8'), 6 | 'openzeppelin-solidity/contracts/token/ERC20/IERC20.sol': fs.readFileSync('openzeppelin-solidity/contracts/token/ERC20/IERC20.sol', 'utf8'), 7 | 'openzeppelin-solidity/contracts/access/roles/MinterRole.sol': fs.readFileSync('openzeppelin-solidity/contracts/access/roles/MinterRole.sol', 'utf8'), 8 | 'openzeppelin-solidity/contracts/access/Roles.sol': fs.readFileSync('openzeppelin-solidity/contracts/access/Roles.sol', 'utf8'), 9 | 'openzeppelin-solidity/contracts/ownership/Ownable.sol': fs.readFileSync('openzeppelin-solidity/contracts/ownership/Ownable.sol', 'utf8'), 10 | } 11 | -------------------------------------------------------------------------------- /attach.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker exec -ti clevis bash 3 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Grants API Endpoints 2 | 3 | GET /grants -> get all Grants 4 | - You can add limit=##&page=## for paging to the end of the url 5 | - You can add s=Searchterm and it will search the title and pitch fields 6 | 7 | GET /grants/:id -> get a Grant by ID 8 | 9 | POST: /grants/create -> create a new Grant or update and existing one 10 | -------------------------------------------------------------------------------- /backend/devDatabase.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker rm -f grants-dev-mysql 3 | docker run --name grants-dev-mysql -e MYSQL_ROOT_PASSWORD=test -p 3306:3306 -d mysql 4 | -------------------------------------------------------------------------------- /backend/follow.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | pm2 logs -f tokensubminer --lines 1000 3 | -------------------------------------------------------------------------------- /backend/modules/contractLoader.js: -------------------------------------------------------------------------------- 1 | module.exports = function(contractList,web3){ 2 | let contracts = [] 3 | for(let c in contractList){ 4 | try{ 5 | let abi = require("../../src/contracts/"+contractList[c]+".abi.js") 6 | let address = require("../../src/contracts/"+contractList[c]+".address.js") 7 | console.log(contractList[c],address,abi.length) 8 | contracts[contractList[c]] = new web3.eth.Contract(abi,address) 9 | console.log("contract") 10 | contracts[contractList[c]].blockNumber = require("../../src/contracts/"+contractList[c]+".blocknumber.js") 11 | console.log("@ Block",contracts[contractList[c]].blockNumber) 12 | }catch(e){console.log(e)} 13 | } 14 | return contracts 15 | } 16 | -------------------------------------------------------------------------------- /backend/modules/eventParser.js: -------------------------------------------------------------------------------- 1 | const EVENTLOADCHUNK = 250000; 2 | let LASTBLOCK; 3 | let ENDBLOCK; 4 | const DEBUG = false; 5 | module.exports = function(contract,eventName,endingBlock,startingBlock,updateFn,filter){ 6 | LASTBLOCK=parseInt(startingBlock) 7 | ENDBLOCK=parseInt(endingBlock) 8 | loadDownTheChain(contract,eventName,updateFn,filter) 9 | } 10 | let loadDownTheChain = async (contract,eventName,updateFn,filter)=>{ 11 | while(LASTBLOCK>=ENDBLOCK){ 12 | let nextLast = LASTBLOCK-EVENTLOADCHUNK 13 | if(nextLast{ 19 | from = Math.max(0,from) 20 | if(DEBUG) console.log("EVENT:",eventName,"FROM",from,"to",to,contract) 21 | let events 22 | try{ 23 | if(filter){ 24 | events = await contract.getPastEvents(eventName, { 25 | filter: filter, 26 | fromBlock: from, 27 | toBlock: to 28 | }); 29 | }else{ 30 | events = await contract.getPastEvents(eventName, { 31 | fromBlock: from, 32 | toBlock: to 33 | }); 34 | } 35 | for(let e in events){ 36 | let thisEvent = events[e].returnValues 37 | thisEvent.blockNumber = events[e].blockNumber 38 | updateFn(thisEvent); 39 | } 40 | }catch(e){console.log(e)} 41 | return true; 42 | } 43 | -------------------------------------------------------------------------------- /backend/modules/liveParser.js: -------------------------------------------------------------------------------- 1 | const LOOKBACK = 16; 2 | module.exports = async function(contract,eventName,currentBlock,updateFn,filter){ 3 | let from = parseInt(currentBlock)-LOOKBACK 4 | let to = 'latest' 5 | let events 6 | if(filter){ 7 | events = await contract.getPastEvents(eventName, { 8 | filter: filter, 9 | fromBlock: from, 10 | toBlock: to 11 | }); 12 | }else{ 13 | events = await contract.getPastEvents(eventName, { 14 | fromBlock: from, 15 | toBlock: to 16 | }); 17 | } 18 | for(let e in events){ 19 | let thisEvent = events[e].returnValues; 20 | thisEvent.blockNumber = events[e].blockNumber 21 | updateFn(thisEvent); 22 | } 23 | return true; 24 | } 25 | -------------------------------------------------------------------------------- /backend/redis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker rm -f token-subscriber-redis 3 | docker run --name token-subscriber-redis -v ${PWD}/redisdata:/data -p 57300:6379 -d redis redis-server --appendonly yes 4 | -------------------------------------------------------------------------------- /backend/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm -rf redisdata/ ; ./redis.sh ; nodemon tokensubminer.js 3 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | npm run build 3 | -------------------------------------------------------------------------------- /clevis.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "http://localhost:8545", 3 | "gasprice": 8.1, 4 | "ethprice": 204.7819708, 5 | "deploygas": 5500000, 6 | "xfergas": 1300000, 7 | "DEBUG": true, 8 | "USE_INFURA": false, 9 | "ROOT_FOLDER": "/Users/austingriffith/tokensubscription.com", 10 | "CRA_FOLDER": "./src", 11 | "TESTS_FOLDER": "tests", 12 | "CONTRACTS_FOLDER": "." 13 | } 14 | -------------------------------------------------------------------------------- /contracts.clevis: -------------------------------------------------------------------------------- 1 | Subscription 2 | WasteCoin 3 | -------------------------------------------------------------------------------- /demosite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Subscription Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 26 |
27 |
28 |

WyoWaste

29 |

First in the industry to offer waste disposal services for a Crypto Token.

30 |
31 |
32 |
33 |
34 | 35 |
36 |
37 |
38 |

Commercial

39 |

Waste Removal for your Business

40 |
41 |
42 |
43 |
44 |

Residentail

45 |

Waste Removal for your Home

46 |
47 |
48 |
49 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /demosite/site.css: -------------------------------------------------------------------------------- 1 | .container { 2 | max-width: 960px; 3 | } 4 | 5 | /* 6 | * Custom translucent site header 7 | */ 8 | 9 | .site-header { 10 | background-color: #FFC425; 11 | -webkit-backdrop-filter: saturate(180%) blur(20px); 12 | backdrop-filter: saturate(180%) blur(20px); 13 | } 14 | .site-header a { 15 | color: #492F24; 16 | transition: ease-in-out color .15s; 17 | } 18 | .site-header a:hover { 19 | color: #fff; 20 | text-decoration: none; 21 | } 22 | 23 | .p-3 { 24 | background-image:url('trash-site-top-image.jpg'); 25 | height: 477px; 26 | } 27 | 28 | .px-md-5 { 29 | min-height:320px; 30 | } 31 | 32 | .px-md-55 { 33 | background-image:url('trash-site-bottom-image-left.jpg'); 34 | color:#000!important; 35 | } 36 | 37 | .px-md-56 { 38 | background-image:url('trash-site-bottom-image-right.jpg'); 39 | color:#000!important; 40 | } 41 | 42 | .my-3 { 43 | background-color: #fff; 44 | opacity: .8; 45 | } 46 | 47 | /* 48 | * Extra utilities 49 | */ 50 | 51 | .flex-equal > * { 52 | -ms-flex: 1; 53 | flex: 1; 54 | } 55 | @media (min-width: 768px) { 56 | .flex-md-equal > * { 57 | -ms-flex: 1; 58 | flex: 1; 59 | } 60 | } 61 | 62 | .overflow-hidden { overflow: hidden; } 63 | 64 | .footer-back { 65 | background-color: #FFC425; 66 | } 67 | 68 | .footer-back h5, .footer-back a, .mb-3 { 69 | color: #492F24 !important; 70 | } 71 | -------------------------------------------------------------------------------- /demosite/trash-site-bottom-image-left.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/demosite/trash-site-bottom-image-left.jpg -------------------------------------------------------------------------------- /demosite/trash-site-bottom-image-right.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/demosite/trash-site-bottom-image-right.jpg -------------------------------------------------------------------------------- /demosite/trash-site-top-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/demosite/trash-site-top-image.jpg -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | clevis upload tokensubscription.com 3 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN apt-get update 4 | RUN apt-get dist-upgrade -y 5 | RUN apt-get upgrade -y 6 | RUN apt-get install build-essential python htop -y 7 | RUN apt-get install curl -y 8 | RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - 9 | RUN apt-get install -y nodejs 10 | RUN apt-get install software-properties-common -y 11 | RUN add-apt-repository -y ppa:ethereum/ethereum 12 | RUN apt-get update && apt-get install ethereum -y 13 | 14 | RUN apt-get update && apt-get install -y sudo && rm -rf /var/lib/apt/lists/* 15 | 16 | RUN apt-get update 17 | RUN npm i npm@latest -g 18 | RUN npm config set user 0 19 | RUN npm config set unsafe-perm true 20 | RUN npm install -g ganache-cli 21 | RUN npm install -g npx 22 | RUN apt-get install git-core -y 23 | 24 | ADD bootstrap.sh /bootstrap.sh 25 | RUN chmod +x /bootstrap.sh 26 | 27 | CMD ../bootstrap.sh 28 | -------------------------------------------------------------------------------- /docker/attach.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #THIS ONLY WORKS ON THE MAINNET NEED TO USE DIFFERENT FOLDER FOR TESTNETS 4 | docker exec -ti metatxrelay bash ic "sudo geth attach --datadir '/root/.ethereum'" 5 | -------------------------------------------------------------------------------- /docker/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ -z "$network" ]; then 3 | network="local" 4 | fi 5 | 6 | echo "Launching Meta Transaction ⛏️ miner with network [ $network ]..." 7 | 8 | if [ "$network" = "rinkeby" ]; then 9 | echo "Launching Rinkeby Geth..." 10 | /usr/bin/geth --rinkeby --syncmode "light" --cache 512 --maxpeers 25 --datadir ".geth-rinkeby" --rpc --rpcaddr 0.0.0.0 --rpcapi="db,eth,net,web3,personal" > geth.log 2>&1 & 11 | elif [ "$network" = "ropsten" ]; then 12 | echo "Launching Ropsten Geth..." 13 | /usr/bin/geth --testnet --syncmode "light" --cache 512 --maxpeers 25 --datadir ".geth-ropsten" --rpc --rpcaddr 0.0.0.0 --rpcapi="db,eth,net,web3,personal" > geth.log 2>&1 & 14 | else 15 | echo "Launching Mainnet Geth..." 16 | /usr/bin/geth --syncmode "light" --cache 512 --maxpeers 25 --datadir ".geth" --rpc --rpcaddr 0.0.0.0 --rpcapi="db,eth,net,web3,personal" > geth.log 2>&1 & 17 | 18 | fi 19 | 20 | #fire up the backend here 21 | 22 | bash 23 | -------------------------------------------------------------------------------- /docker/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker build -t metatxrelay . 3 | -------------------------------------------------------------------------------- /docker/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## ---- No Network means use local ganache-cli 3 | docker run -ti --rm --name metatxrelay -p 3000:3000 -p 8545:8545 -v ${PWD}/../backend:/backend metatxrelay 4 | -------------------------------------------------------------------------------- /grants/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /grants/README.md: -------------------------------------------------------------------------------- 1 | # Token Subscription Grants 2 | 3 | A super slimmed down version of what will become Gitcoin Dev Grants! 4 | -------------------------------------------------------------------------------- /grants/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | npm run build 3 | -------------------------------------------------------------------------------- /grants/deploy.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs") 2 | const awsCreds = JSON.parse(fs.readFileSync("aws.json").toString().trim()) 3 | //https://github.com/andrewrk/node-s3-client 4 | var s3 = require('s3'); 5 | 6 | var client = s3.createClient({ 7 | s3Options: awsCreds, 8 | }); 9 | 10 | var params = { 11 | localDir: "build", 12 | 13 | s3Params: { 14 | Bucket: "ethgrants.com", 15 | Prefix: "", 16 | ACL: "public-read" 17 | // other options supported by putObject, except Body and ContentLength. 18 | // See: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property 19 | }, 20 | }; 21 | 22 | var uploader = client.uploadDir(params); 23 | uploader.on('error', function(err) { 24 | console.error("unable to sync:", err.stack); 25 | }); 26 | uploader.on('progress', function() { 27 | console.log("progress", uploader.progressAmount, uploader.progressTotal); 28 | }); 29 | uploader.on('end', function() { 30 | console.log("done uploading"); 31 | }); 32 | -------------------------------------------------------------------------------- /grants/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | node deploy.js 3 | -------------------------------------------------------------------------------- /grants/invalidate.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs") 2 | const awsCreds = JSON.parse(fs.readFileSync("aws.json").toString().trim()) 3 | 4 | const AWS = require('aws-sdk') 5 | 6 | var cloudfront = new AWS.CloudFront(new AWS.Config(awsCreds)); 7 | 8 | var params = { 9 | DistributionId: 'E3GVCT009BFV27', /* required */ 10 | InvalidationBatch: { /* required */ 11 | CallerReference: ''+(new Date()), /* required */ 12 | Paths: { /* required */ 13 | Quantity: 1, /* required */ 14 | Items: ["/*"] 15 | } 16 | } 17 | }; 18 | cloudfront.createInvalidation(params, function(err, data) { 19 | if (err) console.log(err, err.stack); // an error occurred 20 | else console.log(data); // successful response 21 | }); 22 | -------------------------------------------------------------------------------- /grants/invalidate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | node invalidate.js 3 | -------------------------------------------------------------------------------- /grants/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grants", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.18.0", 7 | "dapparatus": "^1.0.21", 8 | "react": "^16.5.1", 9 | "react-dom": "^16.5.1", 10 | "react-markdown": "^3.6.0", 11 | "react-router-dom": "^4.3.1", 12 | "react-scripts": "1.1.5", 13 | "rlp": "^2.1.0", 14 | "styled-components": "^3.4.6", 15 | "web3": "^1.0.0-beta.36" 16 | }, 17 | "scripts": { 18 | "start": "PORT=8000 react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test --env=jsdom", 21 | "eject": "react-scripts eject" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /grants/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/grants/public/favicon.ico -------------------------------------------------------------------------------- /grants/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | ETH Grants 23 | 24 | 25 | 26 | 27 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /grants/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /grants/src/App.css: -------------------------------------------------------------------------------- 1 | html, body, #root, .wrapper { 2 | height: 100%; 3 | } 4 | body { 5 | font-family: 'Roboto', sans-serif; 6 | color:#fff; 7 | font-size:18px; 8 | background-color: #162129; 9 | background-image: -webkit-linear-gradient(120deg, #162129 50%, #0f171c 25%); 10 | min-height: 500px; 11 | background-image: url(assets/img/bkg.jpg); 12 | background-position: top center; 13 | background-repeat: no-repeat; 14 | background-attachment: fixed; 15 | background-size: cover; 16 | background-color: #999; 17 | } 18 | a { 19 | color: #5396fd; 20 | } 21 | a:hover, 22 | a:focus { 23 | color: #6255d8; 24 | text-decoration: none; 25 | } 26 | 27 | hr { 28 | margin-top: 2rem; 29 | margin-bottom: 2rem; 30 | border-top: 1px solid rgba(255,255,255,0.4) 31 | } 32 | 33 | .wrapper::after { 34 | content: ""; 35 | background: url('assets/img/particles.png') center 80% no-repeat; 36 | opacity: 0.6; 37 | top: 0; 38 | left: 0; 39 | bottom: 0; 40 | right: 0; 41 | position: absolute; 42 | z-index: -1; 43 | } 44 | 45 | .nav { 46 | clear: both; 47 | padding: 5px 15px; 48 | margin: 0 0 20px; 49 | list-style: none; 50 | text-align: center; 51 | font-size: 15px; 52 | background: rgba(0,0,0,0.3); 53 | } 54 | .nav li { 55 | display: inline-block; 56 | } 57 | .nav a { 58 | display: inline-block; 59 | padding: 5px 10px; 60 | color: #fff; 61 | } 62 | .nav a:hover, .nav a:focus { 63 | color: #ccc; 64 | } 65 | 66 | .is-home .nav { 67 | display: none; 68 | } 69 | 70 | button { 71 | display: inline-block; 72 | font-weight: 400; 73 | text-align: center; 74 | white-space: nowrap; 75 | vertical-align: middle; 76 | -webkit-user-select: none; 77 | -moz-user-select: none; 78 | -ms-user-select: none; 79 | user-select: none; 80 | border: 1px solid transparent; 81 | padding: .5rem 1.5rem; 82 | border-radius: 30px; 83 | font-size: 1rem; 84 | line-height: 1.5; 85 | -webkit-transition: color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,-webkit-box-shadow 0.15s ease-in-out; 86 | transition: color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,-webkit-box-shadow 0.15s ease-in-out; 87 | -o-transition: color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out; 88 | transition: color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out; 89 | transition: color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out,-webkit-box-shadow 0.15s ease-in-out; 90 | color: #5396fd; 91 | background-color: transparent; 92 | background-image: none; 93 | border-color: #5396fd; 94 | } 95 | button:hover, 96 | button:focus { 97 | color: #fff; 98 | background-color: #5396fd; 99 | border-color: #5396fd; 100 | cursor: pointer; 101 | } 102 | 103 | .btn { 104 | padding: .5rem 1.5rem; 105 | border-radius: 30px; 106 | } 107 | .btn-lg { 108 | padding: .5rem 1.5rem; 109 | } 110 | .btn-outline-primary { 111 | color: #5396fd; 112 | background-color: transparent; 113 | background-image: none; 114 | border-color: #5396fd; 115 | } 116 | .btn-outline-primary:hover, 117 | .btn-outline-primary:focus { 118 | color: #fff; 119 | background-color: #5396fd; 120 | border-color: #5396fd; 121 | } 122 | 123 | .btn-outline-secondary { 124 | color: #7973b3; 125 | background-color: transparent; 126 | background-image: none; 127 | border-color: #7973b3; 128 | } 129 | .btn-outline-secondary:hover, 130 | .btn-outline-secondary:focus { 131 | color: #fff; 132 | background-color: #7973b3; 133 | border-color: #7973b3; 134 | } 135 | 136 | /* Forms */ 137 | 138 | input[type="text"], select { 139 | display: block; 140 | width: 100%; 141 | padding: 0 0 1px; 142 | background: none; 143 | border: none; 144 | border-bottom: 2px solid #5396fd; 145 | outline: none; 146 | color: #fff; 147 | } 148 | 149 | label { 150 | margin-bottom: 5px; 151 | } 152 | 153 | .field { 154 | margin-bottom: 40px; 155 | } 156 | 157 | .form-control { 158 | display: block; 159 | width: 100%; 160 | padding: 0 0 1px; 161 | background: none; 162 | border: none; 163 | border-bottom: 2px solid #5396fd; 164 | outline: none; 165 | color: #fff; 166 | } 167 | textarea.form-control { 168 | padding: 5px 10px; 169 | background: rgba(0,0,0,0.2); 170 | border: 2px solid #5396fd; 171 | } 172 | 173 | .help { 174 | color: #bbb; 175 | font-size: 13px; 176 | margin-top: .3rem; 177 | padding: 0 5px; 178 | } 179 | 180 | .form-field { 181 | margin-bottom: 40px; 182 | } 183 | 184 | /* Semantic UI */ 185 | .ui.selection.dropdown { 186 | background: none; 187 | color:#fff; 188 | border-radius: 0px; 189 | -webkit-appearance: none; 190 | -webkit-border-radius: 0px; 191 | padding: 3px 0px; 192 | min-height:auto; 193 | border:none; 194 | border-bottom: 2px solid #5396fd; 195 | } 196 | .ui.selection.dropdown .menu>.item, .ui.selection.active.dropdown, .ui.selection.active.dropdown .menu { 197 | border: none !important; 198 | background: #0f171c !important; 199 | } 200 | .ui.selection.dropdown:hover { 201 | border-bottom: 2px solid #5396fd; 202 | } 203 | .ui.selection.dropdown .menu>.item { 204 | background: none; 205 | color:#fff; 206 | } 207 | .ui.selection.dropdown .menu>.item:hover { 208 | background-color:#5396fd !important; 209 | } 210 | .ui.selection.visible.dropdown>.text:not(.default) { 211 | color:#fff; 212 | } 213 | .ui.selection.dropdown>.dropdown.icon { 214 | top: 0px; 215 | } 216 | .ui.dropdown>.text { 217 | padding: 0 0 0 10px; 218 | } 219 | 220 | @media (min-width: 768px) { 221 | 222 | .nav { 223 | clear: none; 224 | padding: 20px 20px; 225 | margin-bottom: 40px; 226 | text-align: left; 227 | } 228 | .nav li { 229 | display: inline-block; 230 | } 231 | .nav a { 232 | display: inline-block; 233 | padding: 10px 20px; 234 | color: #fff; 235 | } 236 | 237 | .field.is-horizontal { 238 | display: flex; 239 | } 240 | .field-label { 241 | flex-basis: 0; 242 | flex-grow: 1; 243 | flex-shrink: 0; 244 | margin-right: 2rem; 245 | text-align: right; 246 | } 247 | .field-body { 248 | display: flex; 249 | flex-basis: 0; 250 | flex-grow: 6; 251 | flex-shrink: 1; 252 | flex-flow: column; 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /grants/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { BrowserRouter as Router, Route } from "react-router-dom"; 3 | import { Metamask, Gas, ContractLoader, Transactions, Button, Scaler } from "dapparatus" 4 | import RLP from 'rlp'; 5 | import axios from 'axios' 6 | import Web3 from 'web3'; 7 | import './App.css'; 8 | 9 | import Coins from './coins.js' 10 | import Nav from './components/Nav'; 11 | import Home from './components/Home'; 12 | import GrantsList from './components/GrantsList'; 13 | import CreateGrants from './components/CreateGrants'; 14 | import GrantDetails from './components/GrantDetails'; 15 | 16 | 17 | let backendUrl = "" 18 | let setBackendUrl = (network)=>{ 19 | backendUrl = "http://localhost:10003/" 20 | if(network == "Rinkeby"){ 21 | backendUrl = "https://rinkeby.tokensubscription.com/" 22 | } 23 | else if(window.location.href.indexOf("tokensubscription.com")>=0 || window.location.href.indexOf("ethgrants.com")>=0) 24 | { 25 | backendUrl = "https://relay.tokensubscription.com/" 26 | } 27 | 28 | } 29 | 30 | class App extends Component { 31 | constructor(props) { 32 | super(props); 33 | console.log("Firing up with backendUrl:"+backendUrl) 34 | this.state = { 35 | grantToAddress:"0x0000000000000000000000000000000000000000", 36 | toAddress:"0x0000000000000000000000000000000000000000", 37 | author:"0x0000000000000000000000000000000000000000", 38 | web3: false, 39 | account: false, 40 | gwei: 4, 41 | title: '', 42 | pitch: '', 43 | desc: '# This is a preview', 44 | deploying: false, 45 | contractAddress: false, 46 | deployingGrantContract: false, 47 | ///SUBSCRIBE DEFUALTS: 48 | tokenAmount: 10, 49 | timeAmount: 1, 50 | timeType:"months", 51 | tokenAddress:"0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359", 52 | gasPrice:0.25, 53 | tokenName:"DAI", 54 | } 55 | 56 | this.handleInput = this.handleInput.bind(this) 57 | this.deployGrantContract = this.deployGrantContract.bind(this) 58 | this.sendSubscription = this.sendSubscription.bind(this) 59 | this.submitGrant = this.submitGrant.bind(this) 60 | } 61 | componentDidMount(){ 62 | let priceQuery = "https://min-api.cryptocompare.com/data/pricemulti?fsyms=ETH,DAI,ZRX,BAT,REP,GNT,SNT,BNT,MANA&tsyms=USD&extraParams=ethgrantscom" 63 | console.log("Loading prices...",priceQuery) 64 | axios.get(priceQuery) 65 | .then((response) =>{ 66 | let data = response.data; 67 | data.WC = {USD: 1} //add our fake coin in to test 68 | this.setState({prices:data},()=>{ 69 | console.log("PRICES SET:",this.state.prices) 70 | }) 71 | console.log("priceQuery",response); 72 | }) 73 | .catch(function (error) { 74 | console.log("priceQuery",error); 75 | }) 76 | } 77 | 78 | async sendSubscription(){ 79 | let {toAddress,timeType,tokenAmount,tokenAddress,gasPrice,account,web3} = this.state 80 | 81 | let tokenContract = this.state.customContractLoader("WasteCoin",tokenAddress) 82 | let subscriptionContract = this.state.customContractLoader("Subscription",this.state.deployedAddress) 83 | 84 | let value = 0 85 | let txData = "0x02" //something like this to say, hardcoded VERSION 2, we're sending approved tokens 86 | let gasLimit = 120000 87 | 88 | //hardcode period seconds to monthly 89 | let periodSeconds=2592000 90 | if(!gasPrice) gasPrice = 0 91 | 92 | console.log("TOKEN CONTRACT ",tokenContract) 93 | let decimals = parseInt(await tokenContract.decimals().call()) 94 | console.log("decimals",decimals) 95 | 96 | let nonce = parseInt(await subscriptionContract.extraNonce(account).call())+1 97 | 98 | let realTokenAmount = tokenAmount*10**decimals 99 | let realGasPrice = gasPrice*10**decimals 100 | /* 101 | address from, //the subscriber 102 | address to, //the publisher 103 | address tokenAddress, //the token address paid to the publisher 104 | uint256 tokenAmount, //the token amount paid to the publisher 105 | uint256 periodSeconds, //the period in seconds between payments 106 | uint256 gasPrice, //the amount of tokens or eth to pay relayer (0 for free) 107 | */ 108 | 109 | const parts = [ 110 | account, 111 | toAddress, 112 | tokenAddress, 113 | web3.utils.toTwosComplement(realTokenAmount), 114 | web3.utils.toTwosComplement(periodSeconds), 115 | web3.utils.toTwosComplement(realGasPrice), 116 | web3.utils.toTwosComplement(nonce) 117 | ] 118 | /*web3.utils.padLeft("0x"+nonce,64),*/ 119 | console.log("PARTS",parts) 120 | 121 | const subscriptionHash = await subscriptionContract.getSubscriptionHash(...parts).call() 122 | console.log("subscriptionHash",subscriptionHash) 123 | 124 | 125 | let signature = await web3.eth.personal.sign(""+subscriptionHash,account) 126 | console.log("signature",signature) 127 | let postData = { 128 | subscriptionContract:this.state.deployedAddress, 129 | parts:parts, 130 | subscriptionHash: subscriptionHash, 131 | signature:signature, 132 | } 133 | 134 | console.log("postData",postData) 135 | axios.post(backendUrl+'saveSubscription', postData, { 136 | headers: { 137 | 'Content-Type': 'application/json', 138 | } 139 | }).then((response)=>{ 140 | console.log("TX RESULT",response.data.subscriptionHash) 141 | }) 142 | .catch((error)=>{ 143 | console.log(error); 144 | }); 145 | } 146 | 147 | 148 | async deployGrantContract(grantToAddress,tokenName,tokenAmount,timeType,timeAmount,gasPrice,email) { 149 | let {web3,tx,contracts} = this.state 150 | 151 | if(!web3){ 152 | alert("Please install and unlock web3. (MetaMask, Trust, etc)") 153 | }else{ 154 | 155 | //requiredToAddress,requiredTokenAddress,requiredTokenAmount,requiredPeriodSeconds,requiredGasPrice 156 | let requiredToAddress = "0x0000000000000000000000000000000000000000" 157 | if(grantToAddress){ 158 | requiredToAddress = grantToAddress 159 | } 160 | 161 | let foundToken 162 | let requiredTokenAddress = "0x0000000000000000000000000000000000000000" 163 | if(tokenName){ 164 | //translate tokenName to tokenAddress 165 | for(let i = 0; i < this.state.coins.length; i++){ 166 | if(tokenName == this.state.coins[i].address){ 167 | requiredTokenAddress = this.state.coins[i].address 168 | foundToken = this.state.coins[i] 169 | } 170 | } 171 | } 172 | 173 | let requiredPeriodSeconds=0 174 | if(timeAmount){ 175 | //translate timeAmount&timeType to requiredPeriodSeconds 176 | let periodSeconds = timeAmount; 177 | if(timeType=="minutes"){ 178 | periodSeconds*=60 179 | }else if(timeType=="hours"){ 180 | periodSeconds*=3600 181 | }else if(timeType=="days"){ 182 | periodSeconds*=86400 183 | }else if(timeType=="months"){ 184 | periodSeconds*=2592000 185 | } 186 | if(periodSeconds){ 187 | requiredPeriodSeconds=periodSeconds 188 | } 189 | } 190 | 191 | let requiredTokenAmount=0 192 | let requiredGasPrice=0 193 | if(tokenAmount && foundToken){ 194 | //don't forget decimals.. you do a number * (10**##DECIMALS##) 195 | requiredTokenAmount = tokenAmount * (10**foundToken.decimals) 196 | if(gasPrice && foundToken){ 197 | //don't forget decimals.. you do a number * (10**##DECIMALS##) 198 | requiredGasPrice = gasPrice * (10**foundToken.decimals) 199 | requiredTokenAmount -= requiredGasPrice 200 | } 201 | } 202 | 203 | 204 | console.log("we can guess what the contract address is going to be, this let's us get the UI going without it being deployed yet...") 205 | let txCount = await this.state.web3.eth.getTransactionCount(this.state.account,'pending') 206 | let deployingAddress = "0x"+this.state.web3.utils.keccak256(RLP.encode([this.state.account,txCount])).substr(26) 207 | this.setState({deployingAddress:deployingAddress}) 208 | 209 | console.log("Deploying Subscription Contract...") 210 | let code = require("./contracts/Subscription.bytecode.js") 211 | 212 | let args = [ 213 | requiredToAddress, 214 | requiredTokenAddress, 215 | web3.utils.toTwosComplement(requiredTokenAmount), 216 | web3.utils.toTwosComplement(requiredPeriodSeconds), 217 | web3.utils.toTwosComplement(requiredGasPrice) 218 | ] 219 | 220 | console.log("ARGS",args) 221 | 222 | this.setState({deployingGrantContract:true}) 223 | 224 | tx(contracts.Subscription._contract.deploy({data:code,arguments:args}),1500000,(receipt)=>{ 225 | console.log("~~~~~~ DEPLOY FROM DAPPARATUS:",receipt) 226 | if(receipt.contractAddress){ 227 | console.log("CONTRACT DEPLOYED:",receipt.contractAddress) 228 | this.setState({deployedAddress:receipt.contractAddress,deployingGrantContract:false}) 229 | } 230 | }) 231 | 232 | axios.post(backendUrl+'deploysub',{arguments:args,email:email,deployingAddress:deployingAddress}, { 233 | headers: { 234 | 'Content-Type': 'application/json', 235 | } 236 | }).then((response)=>{ 237 | console.log("SAVED INFO:",response.data) 238 | 239 | }) 240 | .catch((error)=>{ 241 | console.log(error); 242 | }); 243 | } 244 | } 245 | submitGrant(hash,sig){ 246 | let {title,pitch,deployedAddress,desc,monthlyGoal,grantDuration,contactName,contactEmail} = this.state 247 | axios.post(backendUrl+'grants/create',{hash,sig,title,pitch,deployedAddress,desc,monthlyGoal,grantDuration,contactName,contactEmail}, { 248 | headers: { 249 | 'Content-Type': 'application/json', 250 | } 251 | }).then((response)=>{ 252 | console.log("SAVED INFO:: ",response.data) 253 | if(response.data.updateId){ 254 | window.location = "/view/"+response.data.updateId 255 | }else{ 256 | window.location = "/view/"+response.data.insertId 257 | } 258 | 259 | }) 260 | .catch((error)=>{ 261 | console.log(error); 262 | }); 263 | } 264 | 265 | handleInput(e,data){ 266 | let update = {} 267 | if(data){ 268 | update[data.name] = data.value 269 | }else{ 270 | let value = e.target.value 271 | if(e.target.name=="title") value = value.substring(0,148) //limit title characters 272 | if(e.target.name=="pitch") value = value.substring(0,298) //limit pitch characters 273 | update[e.target.name] = value 274 | } 275 | this.setState(() => (update)); 276 | } 277 | 278 | save(state,cb){ 279 | this.setState(state,cb) 280 | } 281 | 282 | render() { 283 | //console.log(this.state.title) 284 | let {web3,account,contracts,tx,gwei,block,avgBlockTime,etherscan} = this.state 285 | //console.log(this.state) 286 | let extraRoutes = "" 287 | let connectedDisplay = [] 288 | if(web3){ 289 | extraRoutes = ( 290 |
291 | { 292 | return 301 | }} /> 302 | { 303 | return 311 | }} /> 312 |
313 | ) 314 | connectedDisplay.push( 315 | { 318 | console.log("Gas price update:",state) 319 | this.setState(state,()=>{ 320 | console.log("GWEI set:",this.state) 321 | }) 322 | }} 323 | /> 324 | ) 325 | connectedDisplay.push( 326 | {return require(`${__dirname}/${path}`)}} 331 | onReady={(contracts,customLoader)=>{ 332 | console.log("contracts loaded",contracts) 333 | Coins.push( 334 | { 335 | address:contracts.WasteCoin._address, 336 | name:"WasteCoin", 337 | symbol:"WC", 338 | decimals:18, 339 | imageUrl:"https://s3.amazonaws.com/wyowaste.com/wastecoin.png" 340 | } 341 | ) 342 | this.setState({contractLink:contracts.Subscription._address,contracts:contracts,customContractLoader:customLoader,coins:Coins},async ()=>{ 343 | console.log("Contracts Are Ready:",this.state.contracts) 344 | }) 345 | }} 346 | /> 347 | ) 348 | connectedDisplay.push( 349 | { 359 | console.log("Transactions component is ready:",state) 360 | this.setState(state) 361 | }} 362 | onReceipt={(transaction,receipt)=>{ 363 | // this is one way to get the deployed contract address, but instead I'll switch 364 | // to a more straight forward callback system above 365 | console.log("Transaction Receipt",transaction,receipt) 366 | }} 367 | /> 368 | ) 369 | }else{ 370 | extraRoutes = ( 371 |
372 | { 373 | return ( 374 |

Please Unlock MetaMask

375 | ) 376 | }} /> 377 | { 378 | return ( 379 |

Please Unlock MetaMask

380 | ) 381 | }} /> 382 |
383 | ) 384 | } 385 | return ( 386 | 387 |
388 | { 401 | console.log("metamask state update:",state) 402 | this.setState({grantToAddress:state.account},()=>{ 403 | console.log("grantToAddress:",this.state.grantToAddress) 404 | }) 405 | if(state.web3Provider) { 406 | state.web3 = new Web3(state.web3Provider) 407 | console.log("WEB3",state) 408 | setBackendUrl(state.network) 409 | console.log("backendUrl",backendUrl) 410 | this.setState(state) 411 | } 412 | }} 413 | /> 414 | {connectedDisplay} 415 | 416 |
422 |
423 | ) 424 | } 425 | } 426 | 427 | export default App; 428 | -------------------------------------------------------------------------------- /grants/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /grants/src/assets/img/back-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/grants/src/assets/img/back-arrow.png -------------------------------------------------------------------------------- /grants/src/assets/img/bkg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/grants/src/assets/img/bkg.jpg -------------------------------------------------------------------------------- /grants/src/assets/img/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/grants/src/assets/img/loader.gif -------------------------------------------------------------------------------- /grants/src/assets/img/loader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/grants/src/assets/img/loader.png -------------------------------------------------------------------------------- /grants/src/assets/img/logo-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/grants/src/assets/img/logo-icon.png -------------------------------------------------------------------------------- /grants/src/assets/img/particles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/grants/src/assets/img/particles.png -------------------------------------------------------------------------------- /grants/src/coins.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | "address":"0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359", 4 | "name":"DAI", 5 | "symbol":"DAI", 6 | "decimals":18, 7 | "imageUrl":"https://raw.githubusercontent.com/TrustWallet/tokens/master/images/0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359.png" 8 | }, 9 | { 10 | "address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", 11 | "name":"Wrapped Ether", 12 | "symbol":"WETH", 13 | "decimals":18, 14 | "imageUrl":"https://raw.githubusercontent.com/TrustWallet/tokens/master/images/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2.png" 15 | }, 16 | { 17 | "address":"0xe41d2489571d322189246dafa5ebde1f4699f498", 18 | "name":"0x", 19 | "symbol":"ZRX", 20 | "decimals":18, 21 | "imageUrl":"https://s2.coinmarketcap.com/static/img/coins/32x32/1896.png" 22 | }, 23 | { 24 | "address":"0x0d8775f648430679a709e98d2b0cb6250d2887ef", 25 | "name":"Basic Attention Token", 26 | "symbol":"BAT", 27 | "decimals":18, 28 | "imageUrl":"https://raw.githubusercontent.com/TrustWallet/tokens/master/images/0x0d8775f648430679a709e98d2b0cb6250d2887ef.png" 29 | }, 30 | { 31 | "address":"0xe94327d07fc17907b4db788e5adf2ed424addff6", 32 | "name":"Reputation", 33 | "symbol":"REP", 34 | "decimals":18, 35 | "imageUrl":"https://s2.coinmarketcap.com/static/img/coins/32x32/1104.png" 36 | }, 37 | { 38 | "address":"0xa74476443119A942dE498590Fe1f2454d7D4aC0d", 39 | "name":"Golem", 40 | "symbol":"GNT", 41 | "decimals":18, 42 | "imageUrl":"https://s2.coinmarketcap.com/static/img/coins/32x32/1455.png" 43 | }, 44 | { 45 | "address":"0x744d70fdbe2ba4cf95131626614a1763df805b9e", 46 | "name":"Status Network", 47 | "symbol":"SNT", 48 | "decimals":18, 49 | "imageUrl":"https://raw.githubusercontent.com/TrustWallet/tokens/master/images/0x744d70fdbe2ba4cf95131626614a1763df805b9e.png" 50 | }, 51 | { 52 | "address":"0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c", 53 | "name":"Bancor Network Token", 54 | "symbol":"BNT", 55 | "decimals":18, 56 | "imageUrl":"https://raw.githubusercontent.com/TrustWallet/tokens/master/images/0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c.png" 57 | }, 58 | { 59 | "address": "0x35a9b440da4410dd63df8c54672b728970560328", 60 | "name":"Decentraland", 61 | "symbol":"MANA", 62 | "decimals":18, 63 | "imageUrl":"https://raw.githubusercontent.com/TrustWallet/tokens/master/images/0x0f5d2fb29fb7d3cfee444a200298f468908cc942.png", 64 | } 65 | ] 66 | -------------------------------------------------------------------------------- /grants/src/components/CreateGrants.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import axios from 'axios'; 3 | import { Address, Blockie, Scaler } from "dapparatus"; 4 | import ReactMarkdown from 'react-markdown'; 5 | import styled from 'styled-components' 6 | import Loader from '../loader.gif'; 7 | 8 | const Tab = styled.button` 9 | border-radius: 6px; 10 | padding: 5px 12px; 11 | margin-right: 10px; 12 | &.is-active { 13 | color: #fff; 14 | background-color: #5396fd; 15 | border-color: #5396fd; 16 | } 17 | ` 18 | const PreviewBox = styled.div` 19 | padding: 10px 20px; 20 | min-height: 550px; 21 | background: rgba(0,0,0,0.2); 22 | ` 23 | 24 | class CreateGrants extends Component { 25 | constructor(props) { 26 | super(props); 27 | this.state = { 28 | descriptionPreview: false 29 | } 30 | } 31 | componentDidMount() { 32 | this.getDetails(); 33 | } 34 | getDetails = async () => { 35 | try { 36 | let id = this.props.match.params.id 37 | if(id){ 38 | const response = await axios.get(this.props.backendUrl+`grants/`+id); 39 | console.log("RESPONSE DATA:",response.data) 40 | if(response.data&&response.data[0]){ 41 | this.props.save(response.data[0]) 42 | if(this.props.web3){ 43 | let tokenContract = this.props.customContractLoader("Subscription",response.data[0].deployedAddress) 44 | this.props.save({author:await tokenContract.author().call(),contract:tokenContract,grantToAddress:await tokenContract.requiredToAddress().call()}) 45 | } 46 | } 47 | }else{ 48 | console.log("THIS IS A FRESH CREATE, CLEAR THE DATA") 49 | this.props.save({ 50 | title: "", 51 | pitch: "", 52 | grantToAddress: this.props.account, 53 | deployedAddress: "", 54 | desc: "", 55 | monthlyGoal: "", 56 | grantDuration: "", 57 | contactName: "", 58 | contactEmail: "" 59 | }) 60 | } 61 | } catch (error) { 62 | this.setState(() => ({ error })) 63 | } 64 | } 65 | 66 | togglePreview = (mode) => { 67 | if (mode && mode === 'preview') { 68 | this.setState(() => ({ descriptionPreview: true })); 69 | } else { 70 | this.setState(() => ({ descriptionPreview: false })); 71 | } 72 | } 73 | 74 | render() { 75 | const input = '# This is a header\n\nAnd this is a paragraph' 76 | let recipient 77 | let deployedContract 78 | if(this.props.deployedAddress){ 79 | deployedContract = ( 80 |
81 |
85 |
86 | ) 87 | recipient = ( 88 |
89 |
93 |
94 | ) 95 | }else{ 96 | let loader = "" 97 | if(this.props.deployingGrantContract){ 98 | loader = ( 99 | 100 | ) 101 | } 102 | deployedContract = ( 103 |
104 | {loader} 109 |
110 | ) 111 | recipient = ( 112 |
113 | 117 |
118 | 119 |

The address that will receive the funding tokens.

120 |
121 |
122 | ) 123 | } 124 | 125 | let descriptionContent; 126 | 127 | if (this.state.descriptionPreview === true) { 128 | descriptionContent = 129 | } else { 130 | descriptionContent =
131 | } 132 | 133 | return ( 134 |
135 |

Create A Grant

136 | 137 |
138 |
139 |
140 | 141 |
142 |
143 | 144 |
145 |
146 | 147 |
148 |
149 | 150 |
151 |
152 | 153 |

Your short elevator pitch.

154 |
155 |
156 | 157 |
158 |
159 | 160 |
161 |
162 | {recipient} 163 |
164 |
165 | 166 |
167 |
168 | 169 |
170 |
171 |
172 | {deployedContract} 173 |
174 |

Deploy the grant contract.

175 |
176 |
177 | 178 |
179 |
180 | 181 |

(Markdown)

182 |
183 |
184 |
185 | 188 | Edit Mode 189 | 190 | {this.togglePreview('preview')}}> 193 | Preview Mode 194 | 195 |
196 | 197 | {descriptionContent} 198 | 199 |

A longer, more detailed description can be written in Markdown.

200 |
201 |
202 | 203 |
204 |
205 | 206 |
207 |
208 | 209 |

Amount in (USD) you would like to receive each month.

210 |
211 |
212 | 213 |
214 |
215 | 216 |
217 |
218 | 219 |

Expected duration you would like to receive funding.

220 |
221 |
222 | 223 |
224 |
225 | 226 |
227 |
228 | 229 |

Your full name.

230 |
231 |
232 | 233 |
234 |
235 | 236 |
237 |
238 | 239 |

A valid email address.

240 |
241 |
242 | 243 |
244 |
245 | 246 |
247 |
248 |
249 | 285 |
286 |
287 |
288 | 289 |
290 | 291 |
292 | ); 293 | } 294 | } 295 | 296 | export default CreateGrants; 297 | -------------------------------------------------------------------------------- /grants/src/components/GrantBox.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from "react-router-dom"; 3 | import styled from 'styled-components'; 4 | 5 | const GrantBoxWrap = styled(Link)` 6 | display: block; 7 | padding: 1rem; 8 | margin-bottom: 2rem; 9 | background: rgba(0,0,0,0.5); 10 | color: #fff; 11 | text-align: center; 12 | &:hover { 13 | background: rgba(83, 150, 253,0.3); 14 | color: #fff; 15 | } 16 | @media (min-width: 768px) { 17 | display: flex; 18 | align-items: center; 19 | padding: 1.2rem 1.5rem; 20 | text-align: left; 21 | } 22 | ` 23 | /* 24 | 25 | View Contract 26 | 27 | View Grant 28 | */ 29 | 30 | const GrantBox = (props) => { 31 | return ( 32 | 33 |
34 |

{props.title}

35 |

{props.pitch}

36 |
37 |
38 |

39 | 40 |

41 |
42 |
43 | ) 44 | } 45 | 46 | export default GrantBox; 47 | -------------------------------------------------------------------------------- /grants/src/components/GrantsList.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import GrantBox from './GrantBox'; 3 | import axios from 'axios'; 4 | 5 | export default class GrantsList extends Component { 6 | 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | error: null, 11 | isLoaded: false, 12 | grants: [] 13 | } 14 | } 15 | 16 | componentDidMount() { 17 | this.getGrants(); 18 | } 19 | 20 | getGrants = async () => { 21 | try { 22 | const response = await axios.get(`${this.props.backendUrl}grants`); 23 | this.setState(() => ({ 24 | isLoaded: true, 25 | grants: response.data 26 | })); 27 | } catch (error) { 28 | this.setState(() => ({ error })) 29 | } 30 | } 31 | 32 | render() { 33 | const { error, isLoaded, grants } = this.state; 34 | if (error) { 35 | window.location = "/" 36 | return
{"Please unlock MetaMask: "+error.message}
; 37 | } else if (!isLoaded) { 38 | return
Loading Grants...
; 39 | } else { 40 | return ( 41 |
42 |
43 | {grants.map((grant) => { 44 | return 45 | })} 46 |
47 |
48 | ) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /grants/src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from "react-router-dom"; 3 | import styled from 'styled-components'; 4 | 5 | import logo from '../assets/img/logo-icon.png'; 6 | 7 | const HomeWrap = styled.div` 8 | padding: 130px 0 50px; 9 | ` 10 | 11 | const Disclaimer = styled.div` 12 | display: inline-block; 13 | padding: 1.5rem; 14 | background: rgba(0,0,0,0.6); 15 | border: 1px solid #111; 16 | opacity:0.6; 17 | font-size: 14px; 18 | @media (min-width: 768px) { 19 | margin-top: 8rem; 20 | font-size: 16px; 21 | } 22 | ` 23 | 24 | class Home extends Component { 25 | 26 | componentDidMount() { 27 | document.body.classList.add('is-home'); 28 | } 29 | 30 | componentWillUnmount() { 31 | document.body.classList.remove('is-home'); 32 | } 33 | 34 | render() { 35 | return ( 36 | 37 |
38 |

ETH Grants

39 |

ETH Grants

40 |

Recurring Ethereum funding via token subscriptions powered by meta transactions

41 |

Create A Grant Fund A Grant

42 | 43 |

Disclaimer: We built this in a weekend!

44 |

You should inspect our smart contract before using.

45 |

100% free and open source! Please contribute!

46 |

UPDATE! Contract Audited!

47 |
48 |
49 |
50 | ) 51 | } 52 | } 53 | 54 | export default Home; 55 | -------------------------------------------------------------------------------- /grants/src/components/Nav.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from "react-router-dom"; 3 | 4 | const Nav = () => ( 5 |
    6 |
  • Home
  • 7 |
  • Fund A Grant
  • 8 |
  • Create A Grant
  • 9 |
10 | ) 11 | 12 | export default Nav; -------------------------------------------------------------------------------- /grants/src/components/ProgressBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const ProgressBarDiv = styled.div` 5 | display: -ms-flexbox; 6 | display: flex; 7 | height: 1.4rem; 8 | overflow: hidden; 9 | font-size: .75rem; 10 | background-color: #333; 11 | border-radius: .25rem; 12 | .progress-bar { 13 | display: -ms-flexbox; 14 | display: flex; 15 | -ms-flex-direction: column; 16 | flex-direction: column; 17 | -ms-flex-pack: center; 18 | justify-content: center; 19 | color: #fff; 20 | text-align: center; 21 | white-space: nowrap; 22 | background-color: #7973b3; 23 | transition: width .6s ease; 24 | } 25 | ` 26 | 27 | const ProgressBar = (props) => ( 28 | 29 |
30 | {props.percentage}% Funded 31 |
32 |
33 | ) 34 | 35 | export default ProgressBar; -------------------------------------------------------------------------------- /grants/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /grants/src/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/grants/src/loader.gif -------------------------------------------------------------------------------- /grants/todo.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | * Have the backend check to make sure the contract is actually deployed and authored by the right person for sybil protection 4 | they should have to deploy a contract to make it into our db 5 | 6 | * we still need a way to hide entries that are spam 7 | 8 | * if someone clicks create grant we need to clear the current information so they get a blank form 9 | -------------------------------------------------------------------------------- /invalidate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | clevis invalidate E5BJLSWQB882H 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tokensubscription.com", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.18.0", 7 | "chai": "^4.1.2", 8 | "clevis": "0.0.94", 9 | "cors": "^2.8.4", 10 | "dapparatus": "^1.0.21", 11 | "express": "^4.16.3", 12 | "helmet": "^3.13.0", 13 | "ioredis": "^4.0.0", 14 | "mocha": "^5.2.0", 15 | "mysql": "^2.16.0", 16 | "qrcode.react": "^0.8.0", 17 | "react": "^16.5.0", 18 | "react-dom": "^16.5.0", 19 | "react-motion": "^0.5.2", 20 | "react-scripts": "1.1.5", 21 | "react-transition-group": "^2.4.0", 22 | "rlp": "^2.1.0", 23 | "s3": "^4.4.0", 24 | "semantic-ui-react": "^0.82.3", 25 | "twilio": "^3.20.0", 26 | "web3": "^1.0.0-beta.36" 27 | }, 28 | "scripts": { 29 | "start": "react-scripts start", 30 | "build": "react-scripts build", 31 | "test": "react-scripts test --env=jsdom", 32 | "eject": "react-scripts eject" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /public/coinsubscription.css: -------------------------------------------------------------------------------- 1 | #coinsubscriptionbtn { 2 | background-color: #162129; 3 | color: #fff; 4 | padding: 16px 20px; 5 | border: none; 6 | cursor: pointer; 7 | opacity: 0.8; 8 | position: fixed; 9 | bottom: -2px; 10 | right: 20px; 11 | width: 280px; 12 | border-radius: 20px 20px 0 0; 13 | border: 2px solid #5396fd; 14 | font-size: 20px; 15 | z-index:998; 16 | } 17 | #coinsubscriptionbtn #coinsubscriptionlogo { 18 | width: 35px; 19 | height: 35px; 20 | z-index:998; 21 | } 22 | #coinsubscriptioncont { 23 | max-width: 300px; 24 | padding: 10px; 25 | background-color: #162129; 26 | font-family: sans-serif; 27 | display: none; 28 | position: fixed; 29 | padding: 16px 20px; 30 | bottom: -2px; 31 | right: 20px; 32 | border: 2px solid #5396fd; 33 | z-index: 9; 34 | background-color: #162129; 35 | color:#fff; 36 | width: 236px; 37 | border-radius: 20px 20px 0 0; 38 | text-align:center; 39 | } 40 | #coinsubscriptionpopupp { 41 | font-size: 11px; 42 | margin-top: 20px; 43 | } 44 | #coinsubscriptioncont .btn:hover, #coinsubscriptionbtn:hover { 45 | opacity: 1; 46 | } 47 | #coinsubscriptionclosebtn { 48 | float:right; 49 | color: #fff; 50 | background: none; 51 | border-radius: 25px; 52 | } 53 | #subscribenowbtn { 54 | border: 1px solid #fff; 55 | text-decoration: none; 56 | color:#fff; 57 | padding:5px; 58 | padding: 6px 26px; 59 | border-radius: 20px; 60 | } 61 | #coinsubscriptionlogo { 62 | width: 40px; 63 | height: 40px; 64 | vertical-align: middle; 65 | } 66 | -------------------------------------------------------------------------------- /public/coinsubscription.js: -------------------------------------------------------------------------------- 1 | document.write(''); 2 | 3 | var logo = ''; 4 | 5 | var button = ''; 6 | 7 | // create container 8 | buttoncontainer = document.createElement('div'); 9 | buttoncontainer.id = 'coinsubscriptionbuttoncont'; 10 | document.body.insertAdjacentElement('afterbegin', buttoncontainer); 11 | document.getElementById("coinsubscriptionbuttoncont").innerHTML = button; 12 | 13 | function getParams() { 14 | var script = document.getElementById("coinsubscription"); 15 | var scriptsrc = script.src; 16 | var urlstring = scriptsrc.substring( scriptsrc.indexOf('?') + 1 ); 17 | return urlstring.replace('contract=', ''); 18 | } 19 | 20 | function openCoinSubscription() { 21 | window.location = 'https://tokensubscription.com/'+getParams(); 22 | } 23 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Token Subscription 23 | 24 | 25 | 26 | 27 | 28 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /public/particles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/public/particles.png -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker run -ti --rm --name clevis -p 3000:3000 -p 8545:8545 -v ${PWD}:/dapp austingriffith/clevis 3 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | font-family: 'Roboto', sans-serif; 4 | color:#fff; 5 | font-size:18px; 6 | font-weight:bold; 7 | background-color: #162129; 8 | background-image: -webkit-linear-gradient(120deg, #162129 50%, #0f171c 25%); 9 | min-height: 500px; 10 | background-image: url(bkg.jpg) ; 11 | background-position: top center; 12 | background-repeat: no-repeat; 13 | background-attachment: fixed; 14 | background-size: cover; 15 | background-color: #999; 16 | } 17 | h1 { 18 | font-size:40px; 19 | margin-bottom:5px; 20 | } 21 | h3 { 22 | font-weight:normal; 23 | margin-top:5px; 24 | margin-bottom:30px; 25 | } 26 | a { 27 | color:#444444; 28 | text-decoration: none; 29 | } 30 | a:visited { 31 | color:#555555; 32 | text-decoration: none; 33 | } 34 | button { 35 | border-radius: 25px; 36 | background:none; 37 | color:#fff; 38 | padding:10px 20px; 39 | margin-top:25px; 40 | } 41 | button:hover { 42 | background-color:#5396fd; 43 | opacity: 0.8; 44 | cursor: pointer; 45 | } 46 | input[type="text"], select { 47 | background: transparent; 48 | border: none; 49 | outline: none; 50 | border-bottom: 2px solid #5396fd; 51 | color:#fff; 52 | text-align: center; 53 | width: 70px; 54 | margin: 0px 40px; 55 | } 56 | select { 57 | -webkit-appearance: none; 58 | -webkit-border-radius: 0px; 59 | padding: 1px 0; 60 | } 61 | label { 62 | font-size: 18px; 63 | margin-right: 20px; 64 | font-weight:normal; 65 | } 66 | pre { 67 | white-space: pre-wrap; 68 | font-size: 13px; 69 | background-color: #162129; 70 | padding: 10px; 71 | border: 1px solid #666; 72 | } 73 | .container { 74 | margin: auto; 75 | width: 800px; 76 | padding: 10px; 77 | } 78 | .center { 79 | text-align:center; 80 | } 81 | .form-field { 82 | padding: 20px 0px; 83 | } 84 | .side-pad { 85 | padding: 0px 10px; 86 | } 87 | .ui.selection.dropdown { 88 | background: none; 89 | color:#fff; 90 | border-radius: 0px; 91 | -webkit-appearance: none; 92 | -webkit-border-radius: 0px; 93 | margin: 0 50px; 94 | padding: 2px 0px; 95 | min-height:auto; 96 | border:none; 97 | border-bottom: 2px solid #5396fd; 98 | } 99 | .ui.selection.dropdown .menu>.item, .ui.selection.active.dropdown, .ui.selection.active.dropdown .menu { 100 | border: none !important; 101 | background: #0f171c !important; 102 | } 103 | .ui.selection.dropdown:hover { 104 | border-bottom: 2px solid #5396fd; 105 | } 106 | .ui.selection.dropdown .menu>.item { 107 | background: none; 108 | color:#fff; 109 | } 110 | .ui.selection.dropdown .menu>.item:hover { 111 | background-color:#5396fd !important; 112 | } 113 | .ui.selection.visible.dropdown>.text:not(.default) { 114 | color:#fff; 115 | } 116 | .ui.selection.dropdown>.dropdown.icon { 117 | top: 0px; 118 | } 119 | .ui.dropdown>.text { 120 | padding: 0 0 0 10px; 121 | } 122 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './App.css'; 3 | import { Metamask, Gas, ContractLoader, Transactions, Button, Scaler } from "dapparatus" 4 | import Web3 from 'web3'; 5 | import MainUI from './components/mainui.js' 6 | import Subscriber from './components/subscriber.js' 7 | import Publisher from './components/publisher.js' 8 | import PublisherDeploy from './components/publisherDeploy.js' 9 | import SubscriberApprove from './components/subscriberApprove.js' 10 | import Coins from './coins.js' 11 | import Logo from './logo-icon.png'; 12 | import axios from 'axios' 13 | 14 | var RLP = require('rlp'); 15 | 16 | let backendUrl = "http://localhost:10003/" 17 | if(window.location.href.indexOf("tokensubscription.com")>=0) 18 | { 19 | backendUrl = "https://relay.tokensubscription.com/" 20 | } 21 | 22 | class App extends Component { 23 | constructor(props) { 24 | super(props); 25 | let contract 26 | let subscription 27 | let path = window.location.pathname.replace("/","") 28 | if(path.length==42){ 29 | contract = path 30 | }else if(path.length==66){ 31 | subscription = path 32 | }else{ 33 | console.log("PATH LENGTH UNKNWON",path,path.length) 34 | } 35 | let startMode = "" 36 | if(contract||subscription){ 37 | startMode = "subscriber" 38 | } 39 | 40 | this.state = { 41 | web3: false, 42 | account: false, 43 | gwei: 4, 44 | doingTransaction: false, 45 | contract: contract, 46 | subscription: subscription, 47 | mode: startMode, 48 | coins:false, 49 | contractLink:"" 50 | } 51 | } 52 | 53 | async deploySubscription(toAddress,tokenName,tokenAmount,timeType,timeAmount,gasPrice,email) { 54 | let {web3,tx,contracts} = this.state 55 | 56 | 57 | //requiredToAddress,requiredTokenAddress,requiredTokenAmount,requiredPeriodSeconds,requiredGasPrice 58 | let requiredToAddress = "0x0000000000000000000000000000000000000000" 59 | if(toAddress){ 60 | requiredToAddress = toAddress 61 | } 62 | 63 | let foundToken 64 | let requiredTokenAddress = "0x0000000000000000000000000000000000000000" 65 | if(tokenName){ 66 | //translate tokenName to tokenAddress 67 | for(let i = 0; i < this.state.coins.length; i++){ 68 | if(tokenName == this.state.coins[i].address){ 69 | requiredTokenAddress = this.state.coins[i].address 70 | foundToken = this.state.coins[i] 71 | } 72 | } 73 | } 74 | 75 | let requiredPeriodSeconds=0 76 | if(timeAmount){ 77 | //translate timeAmount&timeType to requiredPeriodSeconds 78 | let periodSeconds = timeAmount; 79 | if(timeType=="minutes"){ 80 | periodSeconds*=60 81 | }else if(timeType=="hours"){ 82 | periodSeconds*=3600 83 | }else if(timeType=="days"){ 84 | periodSeconds*=86400 85 | }else if(timeType=="months"){ 86 | periodSeconds*=2592000 87 | } 88 | if(periodSeconds){ 89 | requiredPeriodSeconds=periodSeconds 90 | } 91 | } 92 | 93 | let requiredTokenAmount=0 94 | let requiredGasPrice=0 95 | if(tokenAmount && foundToken){ 96 | //don't forget decimals.. you do a number * (10**##DECIMALS##) 97 | requiredTokenAmount = tokenAmount * (10**foundToken.decimals) 98 | if(gasPrice && foundToken){ 99 | //don't forget decimals.. you do a number * (10**##DECIMALS##) 100 | requiredGasPrice = gasPrice * (10**foundToken.decimals) 101 | requiredTokenAmount -= requiredGasPrice 102 | } 103 | } 104 | 105 | 106 | console.log("we can guess what the contract address is going to be, this let's us get the UI going without it being deployed yet...") 107 | let txCount = await this.state.web3.eth.getTransactionCount(this.state.account,'pending') 108 | let deployingAddress = "0x"+this.state.web3.utils.keccak256(RLP.encode([this.state.account,txCount])).substr(26) 109 | this.setState({deployingAddress:deployingAddress}) 110 | 111 | console.log("Deploying Subscription Contract...") 112 | let code = require("./contracts/Subscription.bytecode.js") 113 | 114 | let args = [ 115 | requiredToAddress, 116 | requiredTokenAddress, 117 | web3.utils.toTwosComplement(requiredTokenAmount), 118 | web3.utils.toTwosComplement(requiredPeriodSeconds), 119 | web3.utils.toTwosComplement(requiredGasPrice) 120 | ] 121 | 122 | console.log("ARGS",args) 123 | 124 | tx(contracts.Subscription._contract.deploy({data:code,arguments:args}),1500000,(receipt)=>{ 125 | console.log("~~~~~~ DEPLOY FROM DAPPARATUS:",receipt) 126 | if(receipt.contractAddress){ 127 | console.log("CONTRACT DEPLOYED:",receipt.contractAddress) 128 | this.setState({deployedAddress:receipt.contractAddress}) 129 | } 130 | }) 131 | 132 | axios.post(backendUrl+'deploysub',{arguments:args,email:email,deployingAddress:deployingAddress}, { 133 | headers: { 134 | 'Content-Type': 'application/json', 135 | } 136 | }).then((response)=>{ 137 | console.log("SAVED INFO",response.data) 138 | }) 139 | .catch((error)=>{ 140 | console.log(error); 141 | }); 142 | 143 | 144 | } 145 | setMode(mode){ 146 | this.setState({mode:mode}) 147 | } 148 | handleInput(e){ 149 | let update = {} 150 | update[e.target.name] = e.target.value 151 | this.setState(update) 152 | } 153 | render() { 154 | 155 | 156 | const { error, isLoaded, items } = this.state; 157 | let {web3,account,contracts,tx,gwei,block,avgBlockTime,etherscan,mode,deployingAddress,deployedAddress} = this.state 158 | let connectedDisplay = [] 159 | let contractsDisplay = [] 160 | let noWeb3Display = "" 161 | if(web3){ 162 | connectedDisplay.push( 163 | { 166 | console.log("Gas price update:",state) 167 | this.setState(state,()=>{ 168 | console.log("GWEI set:",this.state) 169 | }) 170 | }} 171 | /> 172 | ) 173 | connectedDisplay.push( 174 | {return require(`${__dirname}/${path}`)}} 179 | onReady={(contracts,customLoader)=>{ 180 | console.log("contracts loaded",contracts) 181 | 182 | this.setState({contractLink:contracts.Subscription._address,contracts:contracts,customContractLoader:customLoader},async ()=>{ 183 | console.log("Contracts Are Ready:",this.state.contracts) 184 | Coins.unshift( 185 | { 186 | address:"0x0000000000000000000000000000000000000000", 187 | name:"*ANY*", 188 | symbol:"*ANY*", 189 | decimals:18, 190 | imageUrl:"https://tokensubscription.com/logo.png" 191 | } 192 | ) 193 | Coins.push( 194 | { 195 | address:this.state.contracts.WasteCoin._address, 196 | name:"WasteCoin", 197 | symbol:"WC", 198 | decimals:18, 199 | imageUrl:"https://s3.amazonaws.com/wyowaste.com/wastecoin.png" 200 | } 201 | ) 202 | this.setState({coins:Coins}) 203 | }) 204 | }} 205 | /> 206 | ) 207 | connectedDisplay.push( 208 | { 218 | console.log("Transactions component is ready:",state) 219 | this.setState(state) 220 | }} 221 | onReceipt={(transaction,receipt)=>{ 222 | // this is one way to get the deployed contract address, but instead I'll switch 223 | // to a more straight forward callback system above 224 | console.log("Transaction Receipt",transaction,receipt) 225 | }} 226 | /> 227 | ) 228 | 229 | if(contracts&&mode){ 230 | 231 | let body 232 | if(mode=="subscriber"){ 233 | if(this.state.subscription){ 234 | body = ( 235 | 239 | ) 240 | }else if(deployingAddress||deployedAddress){ 241 | body = ( 242 |
243 | subscriber deploy page {deployingAddress} => {deployedAddress} 244 |
245 | ) 246 | }else{ 247 | body = ( 248 | 253 | ) 254 | } 255 | 256 | }else{ 257 | if(deployingAddress||deployedAddress){ 258 | body = ( 259 | 264 | ) 265 | }else{ 266 | body = ( 267 | 272 | ) 273 | } 274 | } 275 | 276 | contractsDisplay.push( 277 |
278 |
279 | {body} 280 |
281 |
282 | ) 283 | }else{ 284 | connectedDisplay.push( 285 | { 287 | this.setState({mode:"publisher"}) 288 | } 289 | }/> 290 | ) 291 | } 292 | }else{ 293 | noWeb3Display = ( 294 | { 296 | alert("Install and unlock web3. MetaMask, Trust, etc. ") 297 | } 298 | }/> 299 | ) 300 | } 301 | 302 | let forkBanner = "" 303 | if(!this.state.mode){ 304 | forkBanner = ( 305 | 306 | Fork me on GitHub 307 | 308 | ) 309 | } 310 | 311 | return ( 312 |
313 | 314 | {forkBanner} 315 | 316 | { 329 | console.log("metamask state update:",state) 330 | if(state.web3Provider) { 331 | state.web3 = new Web3(state.web3Provider) 332 | this.setState(state) 333 | } 334 | }} 335 | /> 336 |
337 | {connectedDisplay} 338 | {contractsDisplay} 339 | {noWeb3Display} 340 |
341 |
342 | ); 343 | } 344 | } 345 | 346 | 347 | 348 | export default App; 349 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/back-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/src/back-arrow.png -------------------------------------------------------------------------------- /src/bkg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/src/bkg.jpg -------------------------------------------------------------------------------- /src/coins.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | "address":"0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359", 4 | "name":"DAI", 5 | "symbol":"DAI", 6 | "decimals":18, 7 | "imageUrl":"https://raw.githubusercontent.com/TrustWallet/tokens/master/images/0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359.png" 8 | }, 9 | { 10 | "address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", 11 | "name":"Wrapped Ether", 12 | "symbol":"WETH", 13 | "decimals":18, 14 | "imageUrl":"https://raw.githubusercontent.com/TrustWallet/tokens/master/images/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2.png" 15 | }, 16 | { 17 | "address":"0xe41d2489571d322189246dafa5ebde1f4699f498", 18 | "name":"0x", 19 | "symbol":"ZRX", 20 | "decimals":18, 21 | "imageUrl":"https://s2.coinmarketcap.com/static/img/coins/32x32/1896.png" 22 | }, 23 | { 24 | "address":"0x0d8775f648430679a709e98d2b0cb6250d2887ef", 25 | "name":"Basic Attention Token", 26 | "symbol":"BAT", 27 | "decimals":18, 28 | "imageUrl":"https://raw.githubusercontent.com/TrustWallet/tokens/master/images/0x0d8775f648430679a709e98d2b0cb6250d2887ef.png" 29 | }, 30 | { 31 | "address":"0xe94327d07fc17907b4db788e5adf2ed424addff6", 32 | "name":"Reputation", 33 | "symbol":"REP", 34 | "decimals":18, 35 | "imageUrl":"https://s2.coinmarketcap.com/static/img/coins/32x32/1104.png" 36 | }, 37 | { 38 | "address":"0xa74476443119A942dE498590Fe1f2454d7D4aC0d", 39 | "name":"Golem", 40 | "symbol":"GNT", 41 | "decimals":18, 42 | "imageUrl":"https://s2.coinmarketcap.com/static/img/coins/32x32/1455.png" 43 | }, 44 | { 45 | "address":"0x744d70fdbe2ba4cf95131626614a1763df805b9e", 46 | "name":"Status Network", 47 | "symbol":"SNT", 48 | "decimals":18, 49 | "imageUrl":"https://raw.githubusercontent.com/TrustWallet/tokens/master/images/0x744d70fdbe2ba4cf95131626614a1763df805b9e.png" 50 | }, 51 | { 52 | "address":"0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c", 53 | "name":"Bancor Network Token", 54 | "symbol":"BNT", 55 | "decimals":18, 56 | "imageUrl":"https://raw.githubusercontent.com/TrustWallet/tokens/master/images/0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c.png" 57 | }, 58 | { 59 | "address": "0x35a9b440da4410dd63df8c54672b728970560328", 60 | "name":"Decentraland", 61 | "symbol":"MANA", 62 | "decimals":18, 63 | "imageUrl":"https://raw.githubusercontent.com/TrustWallet/tokens/master/images/0x0f5d2fb29fb7d3cfee444a200298f468908cc942.png", 64 | } 65 | ] 66 | -------------------------------------------------------------------------------- /src/components/component-mockup.js: -------------------------------------------------------------------------------- 1 | /*import React, { Component } from 'react'; 2 | import './App.css'; 3 | import { Dropdown } from 'semantic-ui-react'; 4 | 5 | class App extends Component { 6 | 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | addressValue: '', 11 | tokenAmount: '', 12 | recurringValue: '', 13 | periodValue: '', 14 | coin: '', 15 | error: null, 16 | isLoaded: false, 17 | items: [] 18 | }; 19 | 20 | this.handleAddressChange = this.handleAddressChange.bind(this) 21 | this.handleCoinChange = this.handleCoinChange.bind(this) 22 | this.handleTokenAmountChange = this.handleTokenAmountChange.bind(this) 23 | this.handleRecurringValueChange = this.handleRecurringValueChange.bind(this) 24 | this.handlePeriodChange = this.handlePeriodChange.bind(this) 25 | this.handleSubmit = this.handleSubmit.bind(this) 26 | } 27 | 28 | handleAddressChange(event) { 29 | this.setState({addressValue: event.target.value}); 30 | } 31 | 32 | handleCoinChange(event) { 33 | this.setState({coin: event.target.value}) 34 | } 35 | 36 | handleTokenAmountChange(event) { 37 | this.setState({tokenAmount: event.target.value}) 38 | } 39 | 40 | handleRecurringValueChange(event) { 41 | this.setState({recurringValue: event.target.value}) 42 | } 43 | 44 | handlePeriodChange(event) { 45 | this.setState({periodValue: event.target.value}) 46 | } 47 | 48 | handleSubmit(event) { 49 | alert('Token Address: ' + this.state.addressValue) 50 | alert('Coin: ' + this.state.coin) 51 | alert('Token Value: ' + this.state.tokenAmount) 52 | alert('Recurring: ' + this.state.recurringValue) 53 | alert('Term: ' + this.state.periodValue) 54 | event.preventDefault() 55 | } 56 | 57 | componentDidMount() { 58 | fetch('https://api.0xtracker.com/tokens') 59 | .then(res => res.json()) 60 | .then( 61 | (result) => { 62 | this.setState({ 63 | isLoaded: true, 64 | items: result 65 | }) 66 | }, 67 | // Note: it's important to handle errors here 68 | // instead of a catch() block so that we don't swallow 69 | // exceptions from actual bugs in components. 70 | (error) => { 71 | this.setState({ 72 | isLoaded: true, 73 | error 74 | }); 75 | } 76 | ) 77 | } 78 | 79 | render() { 80 | const { error, isLoaded, items } = this.state; 81 | 82 | 83 | 84 | if (error) { 85 | return
Error: {error.message}
; 86 | } else if (!isLoaded) { 87 | return
Loading...
; 88 | } else { 89 | let coins = [] 90 | for(let i in items){ 91 | console.log(items[i]) 92 | coins.push({ 93 | key: items[i].address, 94 | value: items[i].name, 95 | image:{ 96 | avatar : true, 97 | src : items[i].imageUrl, 98 | }, 99 | text: items[i].name 100 | }) 101 | } 102 | 103 | return ( 104 |
105 | 109 |
110 | 111 | 112 |
113 | 117 |
118 | 122 |
123 | 127 |
128 | 135 |
136 | 137 |
138 | ); 139 | } 140 | } 141 | } 142 | 143 | export default App;*/ 144 | -------------------------------------------------------------------------------- /src/components/mainui.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Address, Blockie, Scaler } from "dapparatus" 3 | import { Dropdown } from 'semantic-ui-react' 4 | import Particles from './particles.js'; 5 | import Logo from '../logo-icon.png'; 6 | import Backarrow from '../back-arrow.png' 7 | 8 | class MainUI extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | contractLink: "0x3847033426C5c9AdD7D95E60d32dFb7Cb7304837" 13 | }; 14 | } 15 | render() { 16 | return ( 17 | 18 |
19 | 20 | 21 |
22 | 23 |
24 | 25 |

Token Subscriptions

26 |

27 |
Recurring subscriptions on the Ethereum blockchain
28 |
set it and forget it token transfers
29 |

30 | 31 | 33 | 34 |
35 |
Disclaimer: We built this in a weekend!
36 |
You should inspect our smart contract before using.
37 |
100% free and open source! Please contribute!
38 | 39 |
40 |
41 |
42 | ); 43 | } 44 | } 45 | 46 | export default MainUI; 47 | -------------------------------------------------------------------------------- /src/components/particles.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Address, Blockie, Scaler } from "dapparatus" 3 | import { Dropdown } from 'semantic-ui-react' 4 | import Particles from '../particles.png'; 5 | 6 | class ParticlesRender extends Component { 7 | render() { 8 | 9 | let left = -700 10 | let opacity = 0.95 11 | if(this.props.left) left = this.props.left 12 | if(this.props.opacity) opacity = this.props.opacity 13 | 14 | return ( 15 | 16 | ) 17 | } 18 | } 19 | 20 | export default ParticlesRender; 21 | -------------------------------------------------------------------------------- /src/components/publisher.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Address, Blockie, Scaler } from "dapparatus" 3 | import { Dropdown } from 'semantic-ui-react' 4 | import Particles from './particles.js'; 5 | import Backarrow from '../back-arrow.png' 6 | 7 | 8 | class Publisher extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | toAddress: props.account, 13 | tokenAmount: 10, 14 | timeAmount: 1, 15 | timeType:"months", 16 | tokenAddress:"0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359", 17 | gasPrice:0.25 18 | }; 19 | } 20 | handleInput(e,data){ 21 | console.log("INPUT",e,data) 22 | let update = {} 23 | if(data){ 24 | update[data.name] = data.value 25 | if(data.name=="tokenAddress"&&data.value=="0x0000000000000000000000000000000000000000"){ 26 | update.tokenAmount="" 27 | update.gasPrice="" 28 | update.timeAmount="" 29 | }else{ 30 | if(this.state.tokenAmount==""){ 31 | update.tokenAmount=1 32 | } 33 | if(this.state.gasPrice==""){ 34 | update.gasPrice=0.25 35 | } 36 | if(this.state.timeAmount==""){ 37 | update.timeAmount=1 38 | } 39 | } 40 | }else{ 41 | update[e.target.name] = e.target.value 42 | } 43 | this.setState(update,()=>{ 44 | // this.updateUrl() 45 | }) 46 | } 47 | updateUrl(){ 48 | let url = window.location.origin+window.location.pathname+ 49 | "?timeAmount="+this.state.timeAmount+ 50 | "&timeType="+this.state.timeType 51 | if(this.state.toAddress) url+="&toAddress="+this.state.toAddress 52 | if(this.state.tokenAddress) url+="&tokenAddress="+this.state.tokenAddress 53 | if(this.state.tokenAmount) url+="&tokenAmount="+this.state.tokenAmount 54 | if(this.state.gasPrice) url+="&gasPrice="+this.state.gasPrice 55 | 56 | this.setState({url:url}) 57 | } 58 | componentDidMount() { 59 | let {contracts} = this.props 60 | console.log("contracts",contracts) 61 | this.setState({ 62 | isLoaded: true, 63 | items: [ { 64 | address: this.props.contracts.WasteCoin._address, 65 | decimals: 18, 66 | name: "WasteCoin", 67 | symbol: "WC" 68 | } ] 69 | }) 70 | } 71 | 72 | render() { 73 | 74 | let {contracts,coins} = this.props 75 | let {items,toAddress,tokenAddress,tokenAmount,timeType,timeAmount,gasPrice,email} = this.state 76 | 77 | let coinOptions = [] 78 | 79 | for(let i = 0; i < coins.length; i++){ 80 | coinOptions.push({ 81 | key: coins[i].address, 82 | value: coins[i].address, 83 | image:{ 84 | avatar : true, 85 | src : coins[i].imageUrl, 86 | }, 87 | text: coins[i].symbol 88 | }) 89 | } 90 | 91 | let monthOptions = [ 92 | {key: 'months', value: 'months', text: 'Month(s)'}, 93 | {key: 'days', value: 'days', text: 'Day(s)'}, 94 | {key: 'hours', value: 'hours', text: 'Hour(s)'}, 95 | {key: 'minutes', value: 'minutes', text: 'Minutes(s)'}, 96 | ] 97 | 98 | return ( 99 | 100 | 101 |

Subscriptions Parameters

102 |
103 | 104 | 108 | 109 |
110 |
111 | 112 | 121 | 122 | 123 | 124 |
125 |
126 | 127 | 128 | 137 |
138 |
139 | 140 | 143 |
144 |
145 | 146 | 149 |
150 | 155 | 156 |
{this.props.setMode("")}}> 157 | Previous 158 |
159 |
160 | ); 161 | } 162 | } 163 | 164 | export default Publisher; 165 | -------------------------------------------------------------------------------- /src/components/publisherDeploy.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Address, Blockie, Scaler } from "dapparatus" 3 | import Loader from '../loader.gif'; 4 | import Particles from './particles.js'; 5 | import Backarrow from '../back-arrow.png' 6 | var QRCode = require('qrcode.react'); 7 | 8 | 9 | class PublisherDeploy extends Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | }; 14 | } 15 | render() { 16 | 17 | 18 | 19 | 20 | let {deployingAddress,deployedAddress} = this.props 21 | 22 | let contractAddress = deployingAddress 23 | 24 | 25 | let url = window.location.origin+"/"+contractAddress 26 | 27 | 28 | let deployed = "" 29 | if(deployedAddress){ 30 | contractAddress=deployedAddress 31 | url = window.location.origin+"/"+contractAddress 32 | return ( 33 | 34 | 35 |

Congratulations, your contract is ready.

36 |

You can now accept subscriptions!

37 |

{contractAddress} {deployed}

38 |

Follow the instructions below to share your subscription

39 |
40 |

Add a link to your website:

41 |
{"Subscribe Now"}
42 |

Share Url:

43 |
{url}
44 |

QR Code:

45 | 46 |

Embed a script on your website:

47 |
{""}
48 |
49 |
50 | ); 51 | }else{ 52 | return ( 53 | 54 | 55 |

Your contract is being deployed

56 |

(Make sure you confirm the metamask dialog to deploy your contract!)

57 |

{contractAddress}

58 |

Follow the instructions below to share your subscription

59 |
60 |

Add a link to your website:

61 |
{"Subscribe Now"}
62 |

Share Url:

63 |
{url}
64 |

QR Code:

65 | 66 |

Embed a script on your website:

67 |
{"\n"}
68 |
69 |
{this.props.setMode("")}}> 70 | Previous 71 |
72 |
73 | ); 74 | } 75 | } 76 | } 77 | 78 | export default PublisherDeploy; 79 | -------------------------------------------------------------------------------- /src/components/subscriber.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Address, Blockie, Scaler } from "dapparatus" 3 | import axios from 'axios' 4 | import Loader from '../loader.gif'; 5 | import Particles from './particles.js'; 6 | import { Dropdown } from 'semantic-ui-react' 7 | 8 | let monthOptions = [ 9 | {key: 'months', value: 'months', text: 'Month(s)'}, 10 | {key: 'days', value: 'days', text: 'Day(s)'}, 11 | {key: 'hours', value: 'hours', text: 'Hour(s)'}, 12 | {key: 'minutes', value: 'minutes', text: 'Minutes(s)'}, 13 | ] 14 | 15 | class Subscriber extends Component { 16 | constructor(props) { 17 | super(props); 18 | this.state = { 19 | toAddress: "", 20 | 21 | prefilledParams:false, 22 | tokenAmount: 10, 23 | timeAmount: 1, 24 | timeType:"months", 25 | tokenAddress:"0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359", 26 | gasPrice:0.25, 27 | tokenName:"DAI", 28 | 29 | }; 30 | } 31 | handleInput(e,data){ 32 | let update = {} 33 | if(data){ 34 | update[data.name] = data.value 35 | if(data.name=="tokenAddress"&&data.value=="0x0000000000000000000000000000000000000000"){ 36 | update.tokenAmount="" 37 | update.gasPrice="" 38 | update.timeAmount="" 39 | }else{ 40 | if(this.state.tokenAmount==""){ 41 | update.tokenAmount=1 42 | } 43 | if(this.state.gasPrice==""){ 44 | update.gasPrice=0.25 45 | } 46 | if(this.state.timeAmount==""){ 47 | update.timeAmount=1 48 | } 49 | } 50 | }else{ 51 | update[e.target.name] = e.target.value 52 | } 53 | // console.log("==== UPDATE",update) 54 | this.setState(update,()=>{ 55 | this.updateUrl() 56 | }) 57 | } 58 | updateUrl(){ 59 | let url = window.location.origin+window.location.pathname+ 60 | "?timeAmount="+this.state.timeAmount+ 61 | "&timeType="+this.state.timeType 62 | if(this.state.toAddress) url+="&toAddress="+this.state.toAddress 63 | if(this.state.tokenAddress) url+="&tokenAddress="+this.state.tokenAddress 64 | if(this.state.tokenAmount) url+="&tokenAmount="+this.state.tokenAmount 65 | if(this.state.gasPrice) url+="&gasPrice="+this.state.gasPrice 66 | 67 | this.setState({url:url}) 68 | } 69 | async sendSubscription(){ 70 | let {backendUrl,web3,account,contract} = this.props 71 | let {toAddress,timeType,tokenAmount,tokenAddress,gasPrice} = this.state 72 | 73 | let subscriptionContract = this.props.customContractLoader("Subscription",this.props.contract) 74 | 75 | let value = 0 76 | let txData = "0x02" //something like this to say, hardcoded VERSION 2, we're sending approved tokens 77 | let gasLimit = 120000 78 | 79 | let periodSeconds = this.state.timeAmount; 80 | if(timeType=="minutes"){ 81 | periodSeconds*=60 82 | }else if(timeType=="hours"){ 83 | periodSeconds*=3600 84 | }else if(timeType=="days"){ 85 | periodSeconds*=86400 86 | }else if(timeType=="months"){ 87 | periodSeconds*=2592000 88 | } 89 | 90 | if(!gasPrice) gasPrice = 0 91 | 92 | let nonce = parseInt(await subscriptionContract.extraNonce(account).call())+1 93 | 94 | //TODO know decimals and convert here 95 | let realTokenAmount = tokenAmount*10**18 96 | let realGasPrice = gasPrice*10**18 97 | /* 98 | address from, //the subscriber 99 | address to, //the publisher 100 | address tokenAddress, //the token address paid to the publisher 101 | uint256 tokenAmount, //the token amount paid to the publisher 102 | uint256 periodSeconds, //the period in seconds between payments 103 | uint256 gasPrice, //the amount of tokens or eth to pay relayer (0 for free) 104 | */ 105 | 106 | const parts = [ 107 | account, 108 | toAddress, 109 | tokenAddress, 110 | web3.utils.toTwosComplement(realTokenAmount), 111 | web3.utils.toTwosComplement(periodSeconds), 112 | web3.utils.toTwosComplement(realGasPrice), 113 | web3.utils.toTwosComplement(nonce) 114 | ] 115 | /*web3.utils.padLeft("0x"+nonce,64),*/ 116 | console.log("PARTS",parts) 117 | 118 | const subscriptionHash = await subscriptionContract.getSubscriptionHash(...parts).call() 119 | console.log("subscriptionHash",subscriptionHash) 120 | 121 | let signature = await web3.eth.personal.sign(""+subscriptionHash,account) 122 | console.log("signature",signature) 123 | let postData = { 124 | subscriptionContract:subscriptionContract._address, 125 | parts:parts, 126 | subscriptionHash: subscriptionHash, 127 | signature:signature, 128 | } 129 | 130 | console.log("postData",postData) 131 | axios.post(backendUrl+'saveSubscription', postData, { 132 | headers: { 133 | 'Content-Type': 'application/json', 134 | } 135 | }).then((response)=>{ 136 | console.log("TX RESULT",response.data.subscriptionHash) 137 | window.location = "/"+response.data.subscriptionHash 138 | }) 139 | .catch((error)=>{ 140 | console.log(error); 141 | }); 142 | } 143 | 144 | async componentDidMount() { 145 | let {contracts} = this.props 146 | console.log("contracts",contracts) 147 | this.setState({ 148 | isLoaded: true, 149 | items: [ { 150 | address: this.props.contracts.WasteCoin._address, 151 | decimals: 18, 152 | name: "WasteCoin", 153 | symbol: "WC" 154 | } ] 155 | }) 156 | if(this.props.contract){ 157 | console.log("poll contract for values...") 158 | let subscriptionsContract = this.props.customContractLoader("Subscription",this.props.contract) 159 | console.log("subscriptionsContract",subscriptionsContract) 160 | let requiredToAddress = await subscriptionsContract.requiredToAddress().call() 161 | let requiredTokenAddress = await subscriptionsContract.requiredTokenAddress().call() 162 | 163 | let requiredTokenName 164 | let tokenDecimals = 0 165 | 166 | if(requiredTokenAddress && requiredTokenAddress!="0x0000000000000000000000000000000000000000"){ 167 | console.log("using",requiredTokenAddress,"search through",this.props.coins) 168 | for(let c in this.props.coins){ 169 | console.log("CHECKING",this.props.coins[c]) 170 | if(this.props.coins[c] && this.props.coins[c].address && this.props.coins[c].address.toLowerCase()==requiredTokenAddress.toLowerCase()){ 171 | console.log("FOUND!!!!!!!!") 172 | requiredTokenName = this.props.coins[c].name 173 | tokenDecimals = this.props.coins[c].decimals 174 | } 175 | } 176 | let requiredTokenAmount = await subscriptionsContract.requiredTokenAmount().call() 177 | console.log("requiredTokenAmount",requiredTokenAmount) 178 | let requiredPeriodSeconds = await subscriptionsContract.requiredPeriodSeconds().call() 179 | let requiredTimeAmount = 0 180 | let requiredTimeType = "" 181 | if(requiredPeriodSeconds){ 182 | if(requiredPeriodSeconds>=2592000){ 183 | requiredTimeAmount = requiredPeriodSeconds/2592000 184 | requiredTimeType = "months" 185 | }else if(requiredPeriodSeconds>=86400){ 186 | requiredTimeAmount = requiredPeriodSeconds/86400 187 | requiredTimeType = "days" 188 | }else if(requiredPeriodSeconds>=3600){ 189 | requiredTimeAmount = requiredPeriodSeconds/3600 190 | requiredTimeType = "hours" 191 | }else{ 192 | requiredTimeAmount = requiredPeriodSeconds/60 193 | requiredTimeType = "minutes" 194 | } 195 | } 196 | let requiredGasPrice = await subscriptionsContract.requiredGasPrice().call() 197 | if(tokenDecimals){ 198 | requiredGasPrice=requiredGasPrice/(10**tokenDecimals) 199 | requiredTokenAmount=requiredTokenAmount/(10**tokenDecimals) 200 | } 201 | console.log("requiredTokenAmount",requiredTokenAmount) 202 | console.log(requiredGasPrice); 203 | this.setState({ 204 | requiredTokenAddress:requiredTokenAddress, 205 | prefilledParams:true, 206 | toAddress:requiredToAddress, 207 | tokenAddress:requiredTokenAddress, 208 | tokenAmount:requiredTokenAmount, 209 | tokenName:requiredTokenName, 210 | timeAmount:requiredTimeAmount, 211 | timeType:requiredTimeType, 212 | gasPrice:requiredGasPrice, 213 | }) 214 | }else{ 215 | 216 | console.log("=====---- This is an open ended subscription without parameters...") 217 | //this is an open ended model, set some defaults.... 218 | this.setState({ 219 | requiredTokenAddress:requiredTokenAddress, 220 | toAddress:requiredToAddress, 221 | prefilledParams: true, 222 | tokenAmount: 10, 223 | timeAmount: 1, 224 | timeType:"months", 225 | tokenAddress:"0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359", 226 | gasPrice:0.25 227 | },()=>{ 228 | console.log(this.state) 229 | }) 230 | 231 | 232 | } 233 | 234 | 235 | } 236 | } 237 | render() { 238 | let {contract,coins} = this.props 239 | let {items,toAddress,tokenName,tokenAmount,tokenAddress,timeType,timeAmount,gasPrice,prefilledParams,email,requiredTokenAddress} = this.state 240 | console.log("timeType:",timeType) 241 | let coinOptions = [] 242 | for(let i in items){ 243 | //console.log(items[i].name) 244 | coinOptions.push( 245 | 246 | ) 247 | } 248 | if(contract){ 249 | if(!prefilledParams&&requiredTokenAddress!="0x0000000000000000000000000000000000000000"){ 250 | return ( 251 |
252 | ); 253 | }else{ 254 | 255 | console.log("tokenAddresstokenAddresstokenAddress",tokenAddress,requiredTokenAddress) 256 | if(requiredTokenAddress && requiredTokenAddress=="0x0000000000000000000000000000000000000000"){ 257 | 258 | let coinOptions = [] 259 | 260 | for(let i = 0; i < coins.length; i++){ 261 | if(coins[i].symbol!="*ANY*"){ 262 | coinOptions.push({ 263 | key: coins[i].address, 264 | value: coins[i].address, 265 | image:{ 266 | avatar : true, 267 | src : coins[i].imageUrl, 268 | }, 269 | text: coins[i].symbol 270 | }) 271 | } 272 | } 273 | 274 | return ( 275 | 276 | 277 |
278 | 279 | 283 | 284 |
285 |
286 | 287 | 296 | 297 | 298 | 299 |
300 |
301 | 302 | 303 | 312 |
313 |
314 | 315 | 318 |
319 |
320 | 321 | 324 |
325 | 330 |
331 | ); 332 | }else{ 333 | 334 | if(timeAmount==1){ 335 | timeType = timeType.substring(0, timeType.length - 1) 336 | } 337 | 338 | return ( 339 | 340 | 341 |
342 | 343 | {toAddress.toLowerCase()} 347 |
348 |
349 | {tokenName} 350 |
351 |
352 | {parseFloat(tokenAmount) + parseFloat(gasPrice)} 353 |
354 |
355 | Recurring Every: {timeAmount} {timeType} 356 |
357 | 362 |
363 | ); 364 | } 365 | 366 | } 367 | }else{ 368 | return ( 369 |
370 |
371 |
Loading Contract...
372 |
373 | ); 374 | } 375 | 376 | } 377 | } 378 | 379 | export default Subscriber; 380 | -------------------------------------------------------------------------------- /src/components/subscriberApprove.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Address, Blockie, Scaler } from "dapparatus" 3 | import axios from 'axios' 4 | import Particles from '../particles.png'; 5 | import Loader from "../loader.gif" 6 | 7 | let pollInterval 8 | let pollTime = 1777 9 | 10 | class SubscriberApprove extends Component { 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | approved:0, 15 | approve:12, 16 | loading:false 17 | }; 18 | } 19 | componentDidMount(){ 20 | axios.get(this.props.backendUrl+"subscription/"+this.props.subscription, { crossdomain: true }) 21 | .catch((err)=>{ 22 | console.log("Error getting subscription",err) 23 | }) 24 | .then(async (response)=>{ 25 | console.log("subscription:",response.data) 26 | this.setState({subscription:response.data}) 27 | //let subscriptionContract = this.props.customContractLoader("Subscription",this.props.contract) 28 | let tokenContract = this.props.customContractLoader("WasteCoin",response.data.parts[2]) 29 | let decimals = await tokenContract.decimals().call() 30 | let foundToken 31 | for(let i = 0; i < this.props.coins.length; i++){ 32 | if(tokenContract._address.toLowerCase() == this.props.coins[i].address.toLowerCase()){ 33 | foundToken = this.props.coins[i] 34 | } 35 | } 36 | this.setState({token:foundToken,decimals:decimals,tokenContract:tokenContract}) 37 | }) 38 | pollInterval = setInterval(this.load.bind(this),pollTime) 39 | this.load() 40 | } 41 | componentWillUnmount(){ 42 | clearInterval(pollInterval) 43 | } 44 | async load(){ 45 | if(this.state.tokenContract){ 46 | this.setState({ 47 | balance:await this.state.tokenContract.balanceOf(this.props.account).call(), 48 | approved:await this.state.tokenContract.allowance(this.state.subscription.parts[0],this.state.subscription.subscriptionContract).call() 49 | }) 50 | } 51 | } 52 | handleInput(e){ 53 | let update = {} 54 | update[e.target.name] = e.target.value 55 | this.setState(update) 56 | } 57 | render() { 58 | let {web3,tx} = this.props 59 | if(!this.state.subscription){ 60 | return ( 61 |
Loading Subscription...
62 | ) 63 | } 64 | console.log(this.state.subscription) 65 | 66 | let contract = this.state.subscription.subscriptionContract 67 | let from = this.state.subscription.parts[0] 68 | let to = this.state.subscription.parts[1] 69 | let token = this.state.subscription.parts[2] 70 | 71 | console.log("token",token) 72 | 73 | if(!this.state.tokenContract){ 74 | return ( 75 |
Connecting to Subscription Contract...
76 | ) 77 | } 78 | 79 | 80 | let tokenAmount = parseInt(web3.utils.toBN(this.state.subscription.parts[3]).toString())/(10**this.state.decimals) 81 | let periodSeconds = web3.utils.toBN(this.state.subscription.parts[4]).toString() 82 | let gasPrice = parseInt(web3.utils.toBN(this.state.subscription.parts[5]).toString())/(10**this.state.decimals) 83 | 84 | //let from = this.state.subscription.parts[0] 85 | 86 | console.log("TOKEN",this.state.token) 87 | 88 | let loading = "" 89 | if(this.state.loading){ 90 | loading = ( 91 | 92 | ) 93 | } 94 | 95 | let approvedColor = "#fd9653" 96 | if(this.state.approved>0){ 97 | approvedColor = "#5396fd" 98 | } 99 | 100 | let particleRender = ( 101 | 102 | ) 103 | 104 | 105 | 106 | return ( 107 | 108 | {particleRender} 109 |

Approve Max Subscription Limit:

110 |
Subscription: {this.state.subscription.subscriptionHash}
111 |
112 | {tokenAmount+gasPrice} {this.state.token.name} 113 |
114 |
115 | From
to
122 |
123 |
124 | Recurring every {periodSeconds}s 125 |
126 |
127 | Token Balance: {this.state.balance/(10**this.state.decimals)} 128 |
129 |
130 | Approved Tokens: {this.state.approved/(10**this.state.decimals)} 131 |
132 |
133 | {loading} 136 | 150 |
151 |
152 | ); 153 | } 154 | } 155 | 156 | export default SubscriberApprove; 157 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /src/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/src/loader.gif -------------------------------------------------------------------------------- /src/loader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/src/loader.png -------------------------------------------------------------------------------- /src/logo-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/src/logo-icon.png -------------------------------------------------------------------------------- /src/particles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EthereumOpenSubscriptions/reference-client/469d2e20044403dc1b3fb6d43a09ac18df7ff564/src/particles.png -------------------------------------------------------------------------------- /stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker stop clevis 3 | -------------------------------------------------------------------------------- /tests/clevis.js: -------------------------------------------------------------------------------- 1 | const clevis = require("clevis") 2 | const colors = require('colors') 3 | const chai = require("chai") 4 | const assert = chai.assert 5 | const expect = chai.expect; 6 | const should = chai.should(); 7 | const fs = require('fs') 8 | const Web3 = require('web3') 9 | const clevisConfig = JSON.parse(fs.readFileSync("clevis.json").toString().trim()) 10 | web3 = new Web3(new Web3.providers.HttpProvider(clevisConfig.provider)) 11 | function localContractAddress(contract){ 12 | return fs.readFileSync(contract+"/"+contract+".address").toString().trim() 13 | } 14 | function localContractAbi(contract){ 15 | return JSON.parse(fs.readFileSync(contract+"/"+contract+".abi").toString().trim()) 16 | } 17 | function printTxResult(result){ 18 | if(!result||!result.transactionHash){ 19 | console.log("ERROR".red,"MISSING TX HASH".yellow) 20 | }else{ 21 | console.log(tab,result.transactionHash.gray,(""+result.gasUsed).yellow) 22 | } 23 | } 24 | function bigHeader(str){ 25 | return "########### "+str+" "+Array(128-str.length).join("#") 26 | } 27 | function rand(min, max) { 28 | return Math.floor( Math.random() * (max - min) + min ); 29 | } 30 | function getPaddedHexFromNumber(num,digits){ 31 | let hexIs = web3.utils.numberToHex(num).replace("0x",""); 32 | while(hexIs.length{ 45 | describe('#reload() ', function() { 46 | it('should force browser to reload', async function() { 47 | fs.writeFileSync("public/reload.txt",Date.now()); 48 | }); 49 | }); 50 | }, 51 | version:()=>{ 52 | describe('#version() ', function() { 53 | it('should get version', async function() { 54 | this.timeout(90000) 55 | const result = await clevis("version") 56 | console.log(result) 57 | }); 58 | }); 59 | }, 60 | blockNumber:()=>{ 61 | describe('#blockNumber() ', function() { 62 | it('should get blockNumber', async function() { 63 | this.timeout(90000) 64 | const result = await clevis("blockNumber") 65 | console.log(result) 66 | }); 67 | }); 68 | }, 69 | compile:(contract)=>{ 70 | describe('#compile() '+contract.magenta, function() { 71 | it('should compile '+contract.magenta+' contract to bytecode', async function() { 72 | this.timeout(90000) 73 | const result = await clevis("compile",contract) 74 | console.log(result) 75 | assert(Object.keys(result.contracts).length>0, "No compiled contacts found.") 76 | let count = 0 77 | for(let c in result.contracts){ 78 | console.log("\t\t"+"contract "+c.blue+": ",result.contracts[c].bytecode.length) 79 | if(count++==0){ 80 | assert(result.contracts[c].bytecode.length > 1, "No bytecode for contract "+c) 81 | } 82 | } 83 | }); 84 | }); 85 | }, 86 | deploy:(contract,accountindex)=>{ 87 | describe('#deploy() '+contract.magenta, function() { 88 | it('should deploy '+contract.magenta+' as account '+accountindex, async function() { 89 | this.timeout(360000) 90 | const result = await clevis("deploy",contract,accountindex) 91 | printTxResult(result) 92 | console.log(tab+"Address: "+result.contractAddress.blue) 93 | assert(result.contractAddress) 94 | }); 95 | }); 96 | }, 97 | 98 | publish:()=>{ 99 | describe('#publish() ', function() { 100 | it('should inject contract address and abi into web app', async function() { 101 | this.timeout(120000) 102 | const fs = require("fs") 103 | if(!fs.existsSync("src")){ 104 | fs.mkdirSync("src"); 105 | } 106 | if(!fs.existsSync("src/contracts")){ 107 | fs.mkdirSync("src/contracts"); 108 | } 109 | if(!fs.existsSync("grants/src")){ 110 | fs.mkdirSync("grants/src"); 111 | } 112 | if(!fs.existsSync("grants/src/contracts")){ 113 | fs.mkdirSync("grants/src/contracts"); 114 | } 115 | for(let c in module.exports.contracts){ 116 | let thisContract = module.exports.contracts[c] 117 | console.log(tab,thisContract.magenta) 118 | let address = fs.readFileSync(thisContract+"/"+thisContract+".address").toString().trim() 119 | console.log(tab,"ADDRESS:",address.blue) 120 | assert(address,"No Address!?") 121 | fs.writeFileSync("src/contracts/"+thisContract+".address.js","module.exports = \""+address+"\""); 122 | fs.writeFileSync("grants/src/contracts/"+thisContract+".address.js","module.exports = \""+address+"\""); 123 | let blockNumber = fs.readFileSync(thisContract+"/"+thisContract+".blockNumber").toString().trim() 124 | console.log(tab,"blockNumber:",blockNumber.blue) 125 | assert(blockNumber,"No blockNumber!?") 126 | fs.writeFileSync("src/contracts/"+thisContract+".blocknumber.js","module.exports = \""+blockNumber+"\""); 127 | fs.writeFileSync("grants/src/contracts/"+thisContract+".blocknumber.js","module.exports = \""+blockNumber+"\""); 128 | let abi = fs.readFileSync(thisContract+"/"+thisContract+".abi").toString().trim() 129 | fs.writeFileSync("src/contracts/"+thisContract+".abi.js","module.exports = "+abi); 130 | fs.writeFileSync("grants/src/contracts/"+thisContract+".abi.js","module.exports = "+abi); 131 | let bytecode = fs.readFileSync(thisContract+"/"+thisContract+".bytecode").toString().trim() 132 | fs.writeFileSync("src/contracts/"+thisContract+".bytecode.js","module.exports = \""+bytecode+"\""); 133 | fs.writeFileSync("grants/src/contracts/"+thisContract+".bytecode.js","module.exports = \""+bytecode+"\""); 134 | } 135 | fs.writeFileSync("src/contracts/contracts.js","module.exports = "+JSON.stringify(module.exports.contracts)); 136 | fs.writeFileSync("grants/src/contracts/contracts.js","module.exports = "+JSON.stringify(module.exports.contracts)); 137 | module.exports.reload() 138 | }); 139 | }); 140 | }, 141 | metamask:()=>{ 142 | describe('#transfer() ', function() { 143 | it('should give metamask account some ether or tokens to test', async function() { 144 | this.timeout(600000) 145 | let result = await clevis("sendTo","0.1","0","0x2a906694D15Df38F59e76ED3a5735f8AAbccE9cb")///<<<-------- change this to your metamask accounts 146 | printTxResult(result) 147 | result = await clevis("sendTo","0.1","0","0x9319bbb4e2652411be15bb74f339b7f6218b2508")///<<<-------- change this to your metamask accounts 148 | 149 | printTxResult(result) 150 | result = await clevis("sendTo","0.1","0","0x5f19cefc9c9d1bc63f9e4d4780493ff5577d238b")///<<<-------- change this to your metamask accounts 151 | 152 | printTxResult(result) 153 | result = await clevis("sendTo","0.1","0","0x34aa3f359a9d614239015126635ce7732c18fdf3")///<<<-------- change this to your metamask accounts 154 | 155 | printTxResult(result) 156 | result = await clevis("sendTo","0.1","0","0x55ffbcd5f80a7e22660a3b564447a0c1d5396a5c")///<<<-------- change this to your metamask accounts 157 | 158 | printTxResult(result) 159 | result = await clevis("sendTo","0.1","0","0x707912a400af1cb2d00ffad766d8a675b8dce504")///<<<-------- change this to your metamask accounts 160 | 161 | printTxResult(result) 162 | result = await clevis("sendTo","0.1","0","0xd402fc82c418923453377a431a168e21e1425a16")///<<<-------- change this to your metamask accounts 163 | 164 | 165 | 166 | 167 | printTxResult(result) 168 | result = await clevis("contract","mint","WasteCoin","3","0x2a906694D15Df38F59e76ED3a5735f8AAbccE9cb","100000000000000000000") 169 | 170 | printTxResult(result) 171 | result = await clevis("contract","mint","WasteCoin","3","0x5f19cefc9c9d1bc63f9e4d4780493ff5577d238b","100000000000000000000") 172 | 173 | printTxResult(result) 174 | result = await clevis("contract","mint","WasteCoin","3","0x34aa3f359a9d614239015126635ce7732c18fdf3","100000000000000000000") 175 | printTxResult(result) 176 | result = await clevis("contract","mint","WasteCoin","3","0xd402fc82c418923453377a431a168e21e1425a16","100000000000000000000") 177 | 178 | 179 | 180 | 181 | 182 | }); 183 | }); 184 | }, 185 | 186 | 187 | ////----------------------------------------------------------------------------/////////////////// 188 | 189 | 190 | //// ADD YOUR TESTS HERE <<<<<<<<-------------------------------- 191 | 192 | 193 | ////----------------------------------------------------------------------------/////////////////// 194 | 195 | 196 | full:()=>{ 197 | describe(bigHeader('COMPILE'), function() { 198 | it('should compile all contracts', async function() { 199 | this.timeout(6000000) 200 | const result = await clevis("test","compile") 201 | assert(result==0,"deploy ERRORS") 202 | }); 203 | }); 204 | describe(bigHeader('FAST'), function() { 205 | it('should run the fast test (everything after compile)', async function() { 206 | this.timeout(6000000) 207 | const result = await clevis("test","fast") 208 | assert(result==0,"fast ERRORS") 209 | }); 210 | }); 211 | }, 212 | 213 | fast:()=>{ 214 | describe(bigHeader('DEPLOY'), function() { 215 | it('should deploy all contracts', async function() { 216 | this.timeout(6000000) 217 | const result = await clevis("test","deploy") 218 | assert(result==0,"deploy ERRORS") 219 | }); 220 | }); 221 | describe(bigHeader('METAMASK'), function() { 222 | it('should deploy all contracts', async function() { 223 | this.timeout(6000000) 224 | const result = await clevis("test","metamask") 225 | assert(result==0,"metamask ERRORS") 226 | }); 227 | }); 228 | describe(bigHeader('PUBLISH'), function() { 229 | it('should publish all contracts', async function() { 230 | this.timeout(6000000) 231 | const result = await clevis("test","publish") 232 | assert(result==0,"publish ERRORS") 233 | }); 234 | }); 235 | 236 | }, 237 | 238 | } 239 | 240 | checkContractDeployment = async (contract)=>{ 241 | const localAddress = localContractAddress(contract) 242 | const address = await clevis("contract","getContract","Example",web3.utils.fromAscii(contract)) 243 | console.log(tab,contract.blue+" contract address is "+(localAddress+"").magenta+" deployed as: "+(address+"").magenta) 244 | assert(localAddress==address,contract.red+" isn't deployed correctly!?") 245 | return address 246 | } 247 | 248 | 249 | 250 | //example helper function 251 | /* 252 | makeSureContractHasTokens = async (contract,contractAddress,token)=>{ 253 | const TokenBalance = await clevis("contract","balanceOf",token,contractAddress) 254 | console.log(tab,contract.magenta+" has "+TokenBalance+" "+token) 255 | assert(TokenBalance>0,contract.red+" doesn't have any "+token.red) 256 | } 257 | 258 | view more examples here: https://github.com/austintgriffith/galleass/blob/master/tests/galleass.js 259 | 260 | */ 261 | -------------------------------------------------------------------------------- /tests/compile.js: -------------------------------------------------------------------------------- 1 | const clevis = require("./clevis.js") 2 | for(let c in clevis.contracts){ 3 | clevis.compile(clevis.contracts[c]) 4 | } 5 | -------------------------------------------------------------------------------- /tests/deploy.js: -------------------------------------------------------------------------------- 1 | const clevis = require("./clevis.js") 2 | for(let c in clevis.contracts){ 3 | clevis.deploy(clevis.contracts[c],3) 4 | } 5 | -------------------------------------------------------------------------------- /tests/example.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | run from parent directory: 4 | 5 | mocha tests/account.js 6 | 7 | */ 8 | const clevis = require("clevis") 9 | const colors = require('colors') 10 | const chai = require("chai") 11 | const assert = chai.assert 12 | const expect = chai.expect; 13 | const should = chai.should(); 14 | 15 | 16 | //--------------------------------------------------------// 17 | 18 | testAccounts() 19 | //testContractCompile("Contract") 20 | //testContractDeploy("Contract",0) 21 | 22 | //--------------------------------------------------------// 23 | 24 | function testAccounts(){ 25 | describe('#accounts()', function() { 26 | it('should have at least one accounts to work with', async function() { 27 | const accounts = await clevis("accounts") 28 | console.log(accounts) 29 | assert(accounts.length > 0) 30 | }); 31 | }); 32 | } 33 | 34 | function testContractCompile(contract){ 35 | const tab = "\t\t"; 36 | describe('#compile() '+contract.magenta, function() { 37 | it('should compile '+contract.magenta+' contract to bytecode', async function() { 38 | this.timeout(10000) 39 | const result = await clevis("compile",contract) 40 | for(let c in result.contracts){ 41 | console.log("\t\t"+"contract "+c.blue+": ",result.contracts[c].bytecode.length) 42 | assert(result.contracts[c].bytecode.length > 1) 43 | } 44 | }); 45 | }); 46 | } 47 | 48 | function testContractDeploy(contract,accountindex){ 49 | const tab = "\t\t"; 50 | describe('#deploy() '+contract.magenta, function() { 51 | it('should deploy '+contract.magenta+' as account '+accountindex, async function() { 52 | this.timeout(60000) 53 | const result = await clevis("deploy",contract,accountindex) 54 | console.log(tab+"Address: "+result.contractAddress.blue) 55 | assert(result.contractAddress) 56 | }); 57 | }); 58 | } 59 | 60 | // -------------------- example contract logic tests ---------------------------------------- // 61 | 62 | function accountCanSetName(contract,account,name){ 63 | const tab = "\t\t"; 64 | describe('#testCanSetName() '+contract.magenta, function() { 65 | it('should set the name of '+contract.magenta+' as account '+account, async function() { 66 | this.timeout(10000) 67 | 68 | let setResult = await clevis("contract","setName",contract,account,name) 69 | //console.log(setResult) 70 | assert(setResult.status == 1) 71 | console.log(tab+"Status: "+setResult.status.toString().blue) 72 | 73 | let gotName = await clevis("contract","name",contract) 74 | assert(gotName == name) 75 | console.log(tab+"Name: "+gotName.blue) 76 | 77 | }); 78 | }); 79 | } 80 | 81 | function accountCanNOTSetName(contract,account,name){ 82 | const tab = "\t\t"; 83 | describe('#testCanNOTSetName() '+contract.magenta, function() { 84 | it('should fail to set the name of '+contract.magenta+' as account '+account+" with a 'revert' error", async function() { 85 | this.timeout(10000) 86 | //let revertError = new Error("Error: Returned error: VM Exception while processing transaction: revert") 87 | //expect( await clevis("contract","setName",contract,account,name) ).to.be.rejectedWith('revert'); 88 | let error 89 | try{ 90 | await clevis("contract","setName",contract,account,name) 91 | console.log(tab,"WARNING".red,"WAS ABLE TO SET!".yellow) 92 | }catch(e){ 93 | error = e.toString() 94 | } 95 | assert(error.indexOf("VM Exception while processing transaction: revert")>0) 96 | }); 97 | }); 98 | } 99 | -------------------------------------------------------------------------------- /tests/fast.js: -------------------------------------------------------------------------------- 1 | const clevis = require("./clevis.js") 2 | clevis.fast() 3 | -------------------------------------------------------------------------------- /tests/full.js: -------------------------------------------------------------------------------- 1 | const clevis = require("./clevis.js") 2 | clevis.full() 3 | -------------------------------------------------------------------------------- /tests/metamask.js: -------------------------------------------------------------------------------- 1 | const clevis = require("./clevis.js") 2 | clevis.metamask() 3 | -------------------------------------------------------------------------------- /tests/publish.js: -------------------------------------------------------------------------------- 1 | const clevis = require("./clevis.js") 2 | clevis.publish() 3 | -------------------------------------------------------------------------------- /tests/version.js: -------------------------------------------------------------------------------- 1 | const clevis = require("./clevis.js") 2 | clevis.version() 3 | --------------------------------------------------------------------------------