├── .gitignore ├── .openzeppelin ├── .session ├── project.json └── rinkeby.json ├── README.md ├── abi.json ├── contracts ├── .gitkeep ├── Blacklistable.sol ├── LimitedUpgradesProxyAdmin.sol ├── Migrations.sol ├── NGNT.sol ├── Ownable.sol └── Pausable.sol ├── docs ├── deployment.md └── tokendesign.md ├── keystore └── UTC--2019-11-25T11-21-06.248251000Z--f192d9342576df0a3e5f7af3b9edf530f85a02a0 ├── migrations └── 1_initial_migration.js ├── package.json ├── script └── test.sh ├── test ├── limitedUpgradesProxyAdmin.test.js ├── ngntWithGSN.test.js └── utils.js └── truffle-config.js /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | package-lock.json 4 | .idea/ 5 | git .DS_Store 6 | contracts/.DS_Store 7 | .env 8 | db/ 9 | -------------------------------------------------------------------------------- /.openzeppelin/.session: -------------------------------------------------------------------------------- 1 | { 2 | "network": "development", 3 | "expires": "2019-11-19T15:56:09.412Z" 4 | } -------------------------------------------------------------------------------- /.openzeppelin/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifestVersion": "2.2", 3 | "contracts": { 4 | "NGNT": "NGNT", 5 | "LimitedUpgradesProxyAdmin": "LimitedUpgradesProxyAdmin", 6 | "V1": "V1" 7 | }, 8 | "dependencies": {}, 9 | "name": "ngnt", 10 | "version": "0.0.1", 11 | "compiler": { 12 | "manager": "openzeppelin", 13 | "compilerSettings": { 14 | "optimizer": { 15 | "enabled": true 16 | } 17 | }, 18 | "solcVersion": "0.5.12" 19 | } 20 | } -------------------------------------------------------------------------------- /.openzeppelin/rinkeby.json: -------------------------------------------------------------------------------- 1 | { 2 | "contracts": { 3 | "LimitedUpgradesProxyAdmin": { 4 | "address": "0x01020006F0de7F8C10c07b96257c2b8e7d7Ef8dc", 5 | "constructorCode": "60806040819052600080546001600160a01b03191633178082556001600160a01b0316917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a361075a806100576000396000f3fe", 6 | "bodyBytecodeHash": "14d2a0ff4973297487ee5a34aa46ff677f283ce9e1a0e46602a69f34466ee2dd", 7 | "localBytecodeHash": "56fd3fd3c72a60298478e1d87d9d252b9f0e3d8e241711675edc9f24a549fabb", 8 | "deployedBytecodeHash": "56fd3fd3c72a60298478e1d87d9d252b9f0e3d8e241711675edc9f24a549fabb", 9 | "types": { 10 | "t_address": { 11 | "id": "t_address", 12 | "kind": "elementary", 13 | "label": "address" 14 | }, 15 | "t_uint256": { 16 | "id": "t_uint256", 17 | "kind": "elementary", 18 | "label": "uint256" 19 | }, 20 | "t_mapping": { 21 | "id": "t_mapping", 22 | "valueType": "t_uint256", 23 | "label": "mapping(key => uint256)", 24 | "kind": "mapping" 25 | }, 26 | "t_bool": { 27 | "id": "t_bool", 28 | "kind": "elementary", 29 | "label": "bool" 30 | } 31 | }, 32 | "storage": [ 33 | { 34 | "contract": "OpenZeppelinUpgradesOwnable", 35 | "path": "@openzeppelin/upgrades/contracts/ownership/Ownable.sol", 36 | "label": "_owner", 37 | "astId": 1893, 38 | "type": "t_address", 39 | "src": "703:22:11" 40 | }, 41 | { 42 | "contract": "LimitedUpgradesProxyAdmin", 43 | "path": "contracts/LimitedUpgradesProxyAdmin.sol", 44 | "label": "allowedUpgradesPerProxy", 45 | "astId": 2487, 46 | "type": "t_uint256", 47 | "src": "914:38:19" 48 | }, 49 | { 50 | "contract": "LimitedUpgradesProxyAdmin", 51 | "path": "contracts/LimitedUpgradesProxyAdmin.sol", 52 | "label": "upgradeCounts", 53 | "astId": 2491, 54 | "type": "t_mapping", 55 | "src": "958:50:19" 56 | }, 57 | { 58 | "contract": "LimitedUpgradesProxyAdmin", 59 | "path": "contracts/LimitedUpgradesProxyAdmin.sol", 60 | "label": "initialized", 61 | "astId": 2493, 62 | "type": "t_bool", 63 | "src": "1014:25:19" 64 | } 65 | ], 66 | "warnings": { 67 | "hasConstructor": false, 68 | "hasSelfDestruct": false, 69 | "hasDelegateCall": false, 70 | "hasInitialValuesInDeclarations": false, 71 | "uninitializedBaseContracts": [] 72 | } 73 | }, 74 | "NGNT": { 75 | "address": "0xeF92Bc323a1CF9721C31677edf4629e681A07031", 76 | "constructorCode": "60806040526069805460ff60a01b191690556002607755600360785561002d336001600160e01b0361003216565b610054565b603380546001600160a01b0319166001600160a01b0392909216919091179055565b61279f806100636000396000f3fe", 77 | "bodyBytecodeHash": "4f66eebc58ac3d973e9bf535df8542ef0f32adc608361e281283718873d039c7", 78 | "localBytecodeHash": "00b948e286d331e23d377a17bfe5e683f489a05575236e148d769bee271ec69c", 79 | "deployedBytecodeHash": "bc97dbc16caa519a7c7e3ddd1d5188062a9475bf8e2a29cba6fc06307a28114c", 80 | "types": { 81 | "t_bool": { 82 | "id": "t_bool", 83 | "kind": "elementary", 84 | "label": "bool" 85 | }, 86 | "t_uint256": { 87 | "id": "t_uint256", 88 | "kind": "elementary", 89 | "label": "uint256" 90 | }, 91 | "t_array:50": { 92 | "id": "t_array:50", 93 | "valueType": "t_uint256", 94 | "length": "50", 95 | "kind": "array", 96 | "label": "uint256[50]" 97 | }, 98 | "t_address": { 99 | "id": "t_address", 100 | "kind": "elementary", 101 | "label": "address" 102 | }, 103 | "t_mapping": { 104 | "id": "t_mapping", 105 | "valueType": "t_uint256", 106 | "label": "mapping(key => uint256)", 107 | "kind": "mapping" 108 | }, 109 | "t_mapping": { 110 | "id": "t_mapping", 111 | "valueType": "t_bool", 112 | "label": "mapping(key => bool)", 113 | "kind": "mapping" 114 | }, 115 | "t_string": { 116 | "id": "t_string", 117 | "kind": "elementary", 118 | "label": "string" 119 | }, 120 | "t_uint8": { 121 | "id": "t_uint8", 122 | "kind": "elementary", 123 | "label": "uint8" 124 | } 125 | }, 126 | "storage": [ 127 | { 128 | "contract": "Initializable", 129 | "path": "@openzeppelin/upgrades/contracts/Initializable.sol", 130 | "label": "initialized", 131 | "astId": 1830, 132 | "type": "t_bool", 133 | "src": "757:24:10" 134 | }, 135 | { 136 | "contract": "Initializable", 137 | "path": "@openzeppelin/upgrades/contracts/Initializable.sol", 138 | "label": "initializing", 139 | "astId": 1832, 140 | "type": "t_bool", 141 | "src": "876:25:10" 142 | }, 143 | { 144 | "contract": "Initializable", 145 | "path": "@openzeppelin/upgrades/contracts/Initializable.sol", 146 | "label": "______gap", 147 | "astId": 1888, 148 | "type": "t_array:50", 149 | "src": "1951:29:10" 150 | }, 151 | { 152 | "contract": "Ownable", 153 | "path": "contracts/Ownable.sol", 154 | "label": "_owner", 155 | "astId": 3512, 156 | "type": "t_address", 157 | "src": "617:22:21" 158 | }, 159 | { 160 | "contract": "ERC20", 161 | "path": "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol", 162 | "label": "_balances", 163 | "astId": 1369, 164 | "type": "t_mapping", 165 | "src": "1414:46:8" 166 | }, 167 | { 168 | "contract": "ERC20", 169 | "path": "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol", 170 | "label": "_allowances", 171 | "astId": 1375, 172 | "type": "t_mapping", 173 | "src": "1467:69:8" 174 | }, 175 | { 176 | "contract": "ERC20", 177 | "path": "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol", 178 | "label": "_totalSupply", 179 | "astId": 1377, 180 | "type": "t_uint256", 181 | "src": "1543:28:8" 182 | }, 183 | { 184 | "contract": "ERC20", 185 | "path": "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol", 186 | "label": "______gap", 187 | "astId": 1756, 188 | "type": "t_array:50", 189 | "src": "7911:29:8" 190 | }, 191 | { 192 | "contract": "Pausable", 193 | "path": "contracts/Pausable.sol", 194 | "label": "pauser", 195 | "astId": 3599, 196 | "type": "t_address", 197 | "src": "796:21:22" 198 | }, 199 | { 200 | "contract": "Pausable", 201 | "path": "contracts/Pausable.sol", 202 | "label": "paused", 203 | "astId": 3602, 204 | "type": "t_bool", 205 | "src": "823:26:22" 206 | }, 207 | { 208 | "contract": "Blacklistable", 209 | "path": "contracts/Blacklistable.sol", 210 | "label": "blacklister", 211 | "astId": 2365, 212 | "type": "t_address", 213 | "src": "312:26:18" 214 | }, 215 | { 216 | "contract": "Blacklistable", 217 | "path": "contracts/Blacklistable.sol", 218 | "label": "blacklisted", 219 | "astId": 2369, 220 | "type": "t_mapping", 221 | "src": "344:45:18" 222 | }, 223 | { 224 | "contract": "V1", 225 | "path": "contracts/NGNT.sol", 226 | "label": "name", 227 | "astId": 2679, 228 | "type": "t_string", 229 | "src": "492:18:20" 230 | }, 231 | { 232 | "contract": "V1", 233 | "path": "contracts/NGNT.sol", 234 | "label": "symbol", 235 | "astId": 2681, 236 | "type": "t_string", 237 | "src": "516:20:20" 238 | }, 239 | { 240 | "contract": "V1", 241 | "path": "contracts/NGNT.sol", 242 | "label": "decimals", 243 | "astId": 2683, 244 | "type": "t_uint8", 245 | "src": "542:21:20" 246 | }, 247 | { 248 | "contract": "V1", 249 | "path": "contracts/NGNT.sol", 250 | "label": "currency", 251 | "astId": 2685, 252 | "type": "t_string", 253 | "src": "569:22:20" 254 | }, 255 | { 256 | "contract": "V1", 257 | "path": "contracts/NGNT.sol", 258 | "label": "masterMinter", 259 | "astId": 2687, 260 | "type": "t_address", 261 | "src": "597:27:20" 262 | }, 263 | { 264 | "contract": "V1", 265 | "path": "contracts/NGNT.sol", 266 | "label": "initialized", 267 | "astId": 2689, 268 | "type": "t_bool", 269 | "src": "630:25:20" 270 | }, 271 | { 272 | "contract": "V1", 273 | "path": "contracts/NGNT.sol", 274 | "label": "gsnFee", 275 | "astId": 2691, 276 | "type": "t_uint256", 277 | "src": "662:21:20" 278 | }, 279 | { 280 | "contract": "V1", 281 | "path": "contracts/NGNT.sol", 282 | "label": "balances", 283 | "astId": 2695, 284 | "type": "t_mapping", 285 | "src": "689:45:20" 286 | }, 287 | { 288 | "contract": "V1", 289 | "path": "contracts/NGNT.sol", 290 | "label": "allowed", 291 | "astId": 2701, 292 | "type": "t_mapping", 293 | "src": "740:64:20" 294 | }, 295 | { 296 | "contract": "V1", 297 | "path": "contracts/NGNT.sol", 298 | "label": "totalSupply_", 299 | "astId": 2703, 300 | "type": "t_uint256", 301 | "src": "810:29:20" 302 | }, 303 | { 304 | "contract": "V1", 305 | "path": "contracts/NGNT.sol", 306 | "label": "minters", 307 | "astId": 2707, 308 | "type": "t_mapping", 309 | "src": "845:41:20" 310 | }, 311 | { 312 | "contract": "V1", 313 | "path": "contracts/NGNT.sol", 314 | "label": "minterAllowed", 315 | "astId": 2711, 316 | "type": "t_mapping", 317 | "src": "892:50:20" 318 | }, 319 | { 320 | "contract": "NGNT", 321 | "path": "contracts/NGNT.sol", 322 | "label": "version", 323 | "astId": 3504, 324 | "type": "t_uint256", 325 | "src": "9468:19:20" 326 | }, 327 | { 328 | "contract": "NGNT", 329 | "path": "contracts/NGNT.sol", 330 | "label": "version3", 331 | "astId": 3507, 332 | "type": "t_uint256", 333 | "src": "9493:20:20" 334 | } 335 | ], 336 | "warnings": { 337 | "hasConstructor": false, 338 | "hasSelfDestruct": false, 339 | "hasDelegateCall": false, 340 | "hasInitialValuesInDeclarations": true, 341 | "uninitializedBaseContracts": [], 342 | "storageUncheckedVars": [], 343 | "storageDiff": [ 344 | { 345 | "action": "append", 346 | "updated": { 347 | "index": 25, 348 | "contract": "NGNT", 349 | "path": "contracts/NGNT.sol", 350 | "label": "version3", 351 | "astId": 3507, 352 | "type": "t_uint256", 353 | "src": "9493:20:20" 354 | } 355 | } 356 | ] 357 | } 358 | } 359 | }, 360 | "solidityLibs": { 361 | "RelayedCallHelper": { 362 | "address": "0x61032F5c4C537AE69CE2876Fe23e3aEB9FE9045F", 363 | "constructorCode": "610411610026600b82828239805160001a60731461001957fe5b30600052607381538281f3fe", 364 | "bodyBytecodeHash": "d1344af910bb675ac241a313d98d1c5f91f224e40db12c54d008b1ecd3d2a70e", 365 | "localBytecodeHash": "7c7a2272d1e707fedb851910f1a0c1a09cf22bb29cafed45d02efc8b85cd2ae8", 366 | "deployedBytecodeHash": "7c7a2272d1e707fedb851910f1a0c1a09cf22bb29cafed45d02efc8b85cd2ae8" 367 | } 368 | }, 369 | "proxies": { 370 | "ngnt/LimitedUpgradesProxyAdmin": [ 371 | { 372 | "address": "0xE1dF1B8e49e1594Efc67D30E3E376c02292D31EE", 373 | "version": "0.0.1", 374 | "implementation": "0x01020006f0de7f8c10c07b96257c2b8e7d7ef8dc", 375 | "admin": null, 376 | "kind": "Minimal" 377 | } 378 | ], 379 | "ngnt/NGNT": [ 380 | { 381 | "address": "0x62a939D0e00195A5B86D0eDE81fcbfCdF491436b", 382 | "version": "0.0.1", 383 | "implementation": "0xcD5d861a8634cbE5dB894501052fBD9c55Cd5535", 384 | "admin": "0xE1dF1B8e49e1594Efc67D30E3E376c02292D31EE", 385 | "kind": "Upgradeable" 386 | } 387 | ] 388 | }, 389 | "manifestVersion": "2.2", 390 | "version": "0.0.1", 391 | "proxyFactory": { 392 | "address": "0xc7A9cb7a4fb8AD2331042C42333C9076B424249d" 393 | }, 394 | "proxyAdmin": { 395 | "address": "0xE1dF1B8e49e1594Efc67D30E3E376c02292D31EE" 396 | } 397 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenZeppelin SDK 2 | This project makes use of the [openzeppelin-sdk](https://github.com/OpenZeppelin/openzeppelin-sdk) for compiling, 3 | upgrading, deploying and interacting with the contracts. 4 | 5 | ## Upgradeable Contracts 6 | 7 | OpenZeppelin SDK allows one to create and deploy **upgradeable** contracts. 8 | You can read more about how this works in the [OpenZeppelin docs](https://docs.openzeppelin.com/sdk/2.5/pattern.html). 9 | 10 | # Gas Station Network Intro 11 | The [Gas Station Network](https://docs.openzeppelin.com/openzeppelin/gsn/faq.html) (GSN) is a decentralized network of relayers which can be used to sign and send Ethereum transactions without 12 | the original sender (the end-users) paying for gas. 13 | 14 | This is particularly useful for NGNT's use-case because gas is usually paid in Ether. 15 | Having to acquire and store Ether before being able to transfer tokens (especially one like NGNT) is a difficult, generally bad user experience. 16 | 17 | The main contract `NGNT.sol` inherits from `GSNRecipient` and as such, can accept transactions from Relayers on the Gas Station Network. 18 | More on this below. 19 | 20 | # Contracts 21 | 22 | ## NGNT 23 | 24 | The main contract `NGNT.sol` implements the ERC20 interface. It also offers other abilities, which will be briefly explained below. 25 | There are more [detailed design docs](./docs/tokendesign.md) in the `docs` folder. 26 | 27 | Other things peculiar to the NGNT contract are as follows. 28 | 29 | ### GSN 30 | 31 | NGNT's implementation of GSN only accepts relayed calls for the `transfer`, `transferFrom` and `approve`. 32 | This simply means that it is possible to call those functions without paying Ether for gas. 33 | 34 | However, users will still get charged for these transactions in NGNT. 35 | The amount of NGNT that will be charged is determined by the `gsnFee`. The purpose of said fee is to balance out the cost of paying for the transfers in Ether on behalf of the users. 36 | The `gsnFee` amount is currently determined and can be updated manually by the contract `owner` because of Ether price and fiat exchange rate fluctuations. 37 | We're currently researching the possibility of an automatic and onchain mechanism for updating this fee. 38 | 39 | 40 | ### Upgradebility 41 | 42 | The NGNT token contract will be upgradeable **just once**. This is achieved by restrictions in the [`ProxyAdmin.sol`](contracts/LimitedUpgradesProxyAdmin.sol) contract that will be deployed and set as the Proxy contract's Admin. 43 | The motivation for this decision is to retain the advantages of the immutability of the smart contracts while still giving us the opportunity to effect any (or all) of the following changes: 44 | 45 | 1. GSN is still relatively new technology, we think it is a good idea to allow for the opportunity to fix any major vulnerability that may be found. 46 | 2. We would love to be able to implement an automatic and onchain mechanism for updating the `gsnFee`. 47 | 48 | Details about deployment and upgrading can be found [here](./docs/deployment.md) 49 | 50 | ## Other Capabilities 51 | 52 | The capabilites touched on below are a clone from Centre's [USDC](https://www.centre.io/usdc) token design 53 | as at [this commit](https://github.com/centrehq/centre-tokens/commit/3ba876b5e96eec6955733e7e008d85f419ec44a5) (from the master branch). 54 | 55 | ### ERC20 compatible 56 | The FiatToken implements the ERC20 interface. 57 | 58 | ### Pausable 59 | The entire contract can be frozen, in case a serious bug is found or there is a serious key compromise. No transfers can take place while the contract is paused. 60 | Access to the pause functionality is controlled by the `pauser` address. 61 | 62 | ### Blacklist 63 | The contract can blacklist certain addresses which will prevent those addresses from transferring or receiving tokens. 64 | Access to the blacklist functionality is controlled by the `blacklister` address. 65 | 66 | ### Minting/Burning 67 | Tokens can be minted or burned on demand. The contract supports having multiple minters simultaneously. There is a 68 | `masterMinter` address which controls the list of minters and how much each is allowed to mint. The mint allowance is 69 | similar to the ERC20 allowance - as each minter mints new tokens their allowance decreases. When it gets too low they will 70 | need the allowance increased again by the `masterMinter`. 71 | 72 | ### Ownable 73 | The contract has an Owner, who can change the `owner`, `pauser`, `blacklister`, or `masterMinter` addresses. The `owner` can not change 74 | the `proxyOwner` address. 75 | 76 | ## Running Tests 77 | To run the tests, in the project path on your terminal, run `npm test` -------------------------------------------------------------------------------- /abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "string" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": false, 18 | "inputs": [ 19 | { 20 | "name": "_spender", 21 | "type": "address" 22 | }, 23 | { 24 | "name": "_value", 25 | "type": "uint256" 26 | } 27 | ], 28 | "name": "approve", 29 | "outputs": [ 30 | { 31 | "name": "", 32 | "type": "bool" 33 | } 34 | ], 35 | "payable": false, 36 | "stateMutability": "nonpayable", 37 | "type": "function" 38 | }, 39 | { 40 | "constant": true, 41 | "inputs": [], 42 | "name": "totalSupply", 43 | "outputs": [ 44 | { 45 | "name": "", 46 | "type": "uint256" 47 | } 48 | ], 49 | "payable": false, 50 | "stateMutability": "view", 51 | "type": "function" 52 | }, 53 | { 54 | "constant": false, 55 | "inputs": [ 56 | { 57 | "name": "_account", 58 | "type": "address" 59 | } 60 | ], 61 | "name": "unBlacklist", 62 | "outputs": [], 63 | "payable": false, 64 | "stateMutability": "nonpayable", 65 | "type": "function" 66 | }, 67 | { 68 | "constant": false, 69 | "inputs": [ 70 | { 71 | "name": "_from", 72 | "type": "address" 73 | }, 74 | { 75 | "name": "_to", 76 | "type": "address" 77 | }, 78 | { 79 | "name": "_value", 80 | "type": "uint256" 81 | } 82 | ], 83 | "name": "transferFrom", 84 | "outputs": [ 85 | { 86 | "name": "", 87 | "type": "bool" 88 | } 89 | ], 90 | "payable": false, 91 | "stateMutability": "nonpayable", 92 | "type": "function" 93 | }, 94 | { 95 | "constant": false, 96 | "inputs": [ 97 | { 98 | "name": "_name", 99 | "type": "string" 100 | }, 101 | { 102 | "name": "_symbol", 103 | "type": "string" 104 | }, 105 | { 106 | "name": "_currency", 107 | "type": "string" 108 | }, 109 | { 110 | "name": "_decimals", 111 | "type": "uint8" 112 | }, 113 | { 114 | "name": "_masterMinter", 115 | "type": "address" 116 | }, 117 | { 118 | "name": "_pauser", 119 | "type": "address" 120 | }, 121 | { 122 | "name": "_blacklister", 123 | "type": "address" 124 | }, 125 | { 126 | "name": "_owner", 127 | "type": "address" 128 | }, 129 | { 130 | "name": "_gsnFee", 131 | "type": "uint256" 132 | } 133 | ], 134 | "name": "initialize", 135 | "outputs": [], 136 | "payable": false, 137 | "stateMutability": "nonpayable", 138 | "type": "function" 139 | }, 140 | { 141 | "constant": false, 142 | "inputs": [ 143 | { 144 | "name": "minter", 145 | "type": "address" 146 | } 147 | ], 148 | "name": "removeMinter", 149 | "outputs": [ 150 | { 151 | "name": "", 152 | "type": "bool" 153 | } 154 | ], 155 | "payable": false, 156 | "stateMutability": "nonpayable", 157 | "type": "function" 158 | }, 159 | { 160 | "constant": true, 161 | "inputs": [], 162 | "name": "decimals", 163 | "outputs": [ 164 | { 165 | "name": "", 166 | "type": "uint8" 167 | } 168 | ], 169 | "payable": false, 170 | "stateMutability": "view", 171 | "type": "function" 172 | }, 173 | { 174 | "constant": true, 175 | "inputs": [], 176 | "name": "masterMinter", 177 | "outputs": [ 178 | { 179 | "name": "", 180 | "type": "address" 181 | } 182 | ], 183 | "payable": false, 184 | "stateMutability": "view", 185 | "type": "function" 186 | }, 187 | { 188 | "constant": false, 189 | "inputs": [ 190 | { 191 | "name": "_spender", 192 | "type": "address" 193 | }, 194 | { 195 | "name": "_addedValue", 196 | "type": "uint256" 197 | } 198 | ], 199 | "name": "increaseAllowance", 200 | "outputs": [ 201 | { 202 | "name": "", 203 | "type": "bool" 204 | } 205 | ], 206 | "payable": false, 207 | "stateMutability": "nonpayable", 208 | "type": "function" 209 | }, 210 | { 211 | "constant": false, 212 | "inputs": [ 213 | { 214 | "name": "_newGsnFee", 215 | "type": "uint256" 216 | } 217 | ], 218 | "name": "updateGsnFee", 219 | "outputs": [], 220 | "payable": false, 221 | "stateMutability": "nonpayable", 222 | "type": "function" 223 | }, 224 | { 225 | "constant": false, 226 | "inputs": [], 227 | "name": "unpause", 228 | "outputs": [], 229 | "payable": false, 230 | "stateMutability": "nonpayable", 231 | "type": "function" 232 | }, 233 | { 234 | "constant": false, 235 | "inputs": [ 236 | { 237 | "name": "_to", 238 | "type": "address" 239 | }, 240 | { 241 | "name": "_amount", 242 | "type": "uint256" 243 | } 244 | ], 245 | "name": "mint", 246 | "outputs": [ 247 | { 248 | "name": "", 249 | "type": "bool" 250 | } 251 | ], 252 | "payable": false, 253 | "stateMutability": "nonpayable", 254 | "type": "function" 255 | }, 256 | { 257 | "constant": false, 258 | "inputs": [ 259 | { 260 | "name": "_amount", 261 | "type": "uint256" 262 | } 263 | ], 264 | "name": "burn", 265 | "outputs": [], 266 | "payable": false, 267 | "stateMutability": "nonpayable", 268 | "type": "function" 269 | }, 270 | { 271 | "constant": false, 272 | "inputs": [ 273 | { 274 | "name": "minter", 275 | "type": "address" 276 | }, 277 | { 278 | "name": "minterAllowedAmount", 279 | "type": "uint256" 280 | } 281 | ], 282 | "name": "configureMinter", 283 | "outputs": [ 284 | { 285 | "name": "", 286 | "type": "bool" 287 | } 288 | ], 289 | "payable": false, 290 | "stateMutability": "nonpayable", 291 | "type": "function" 292 | }, 293 | { 294 | "constant": false, 295 | "inputs": [ 296 | { 297 | "name": "_newPauser", 298 | "type": "address" 299 | } 300 | ], 301 | "name": "updatePauser", 302 | "outputs": [], 303 | "payable": false, 304 | "stateMutability": "nonpayable", 305 | "type": "function" 306 | }, 307 | { 308 | "constant": true, 309 | "inputs": [], 310 | "name": "paused", 311 | "outputs": [ 312 | { 313 | "name": "", 314 | "type": "bool" 315 | } 316 | ], 317 | "payable": false, 318 | "stateMutability": "view", 319 | "type": "function" 320 | }, 321 | { 322 | "constant": true, 323 | "inputs": [ 324 | { 325 | "name": "account", 326 | "type": "address" 327 | } 328 | ], 329 | "name": "balanceOf", 330 | "outputs": [ 331 | { 332 | "name": "", 333 | "type": "uint256" 334 | } 335 | ], 336 | "payable": false, 337 | "stateMutability": "view", 338 | "type": "function" 339 | }, 340 | { 341 | "constant": true, 342 | "inputs": [], 343 | "name": "getHubAddr", 344 | "outputs": [ 345 | { 346 | "name": "", 347 | "type": "address" 348 | } 349 | ], 350 | "payable": false, 351 | "stateMutability": "view", 352 | "type": "function" 353 | }, 354 | { 355 | "constant": false, 356 | "inputs": [ 357 | { 358 | "name": "context", 359 | "type": "bytes" 360 | } 361 | ], 362 | "name": "preRelayedCall", 363 | "outputs": [ 364 | { 365 | "name": "", 366 | "type": "bytes32" 367 | } 368 | ], 369 | "payable": false, 370 | "stateMutability": "nonpayable", 371 | "type": "function" 372 | }, 373 | { 374 | "constant": false, 375 | "inputs": [], 376 | "name": "initialize", 377 | "outputs": [], 378 | "payable": false, 379 | "stateMutability": "nonpayable", 380 | "type": "function" 381 | }, 382 | { 383 | "constant": true, 384 | "inputs": [ 385 | { 386 | "name": "relay", 387 | "type": "address" 388 | }, 389 | { 390 | "name": "from", 391 | "type": "address" 392 | }, 393 | { 394 | "name": "encodedFunction", 395 | "type": "bytes" 396 | }, 397 | { 398 | "name": "transactionFee", 399 | "type": "uint256" 400 | }, 401 | { 402 | "name": "gasPrice", 403 | "type": "uint256" 404 | }, 405 | { 406 | "name": "gasLimit", 407 | "type": "uint256" 408 | }, 409 | { 410 | "name": "nonce", 411 | "type": "uint256" 412 | }, 413 | { 414 | "name": "approvalData", 415 | "type": "bytes" 416 | }, 417 | { 418 | "name": "maxPossibleCharge", 419 | "type": "uint256" 420 | } 421 | ], 422 | "name": "acceptRelayedCall", 423 | "outputs": [ 424 | { 425 | "name": "", 426 | "type": "uint256" 427 | }, 428 | { 429 | "name": "", 430 | "type": "bytes" 431 | } 432 | ], 433 | "payable": false, 434 | "stateMutability": "view", 435 | "type": "function" 436 | }, 437 | { 438 | "constant": false, 439 | "inputs": [], 440 | "name": "pause", 441 | "outputs": [], 442 | "payable": false, 443 | "stateMutability": "nonpayable", 444 | "type": "function" 445 | }, 446 | { 447 | "constant": true, 448 | "inputs": [ 449 | { 450 | "name": "minter", 451 | "type": "address" 452 | } 453 | ], 454 | "name": "minterAllowance", 455 | "outputs": [ 456 | { 457 | "name": "", 458 | "type": "uint256" 459 | } 460 | ], 461 | "payable": false, 462 | "stateMutability": "view", 463 | "type": "function" 464 | }, 465 | { 466 | "constant": true, 467 | "inputs": [], 468 | "name": "owner", 469 | "outputs": [ 470 | { 471 | "name": "", 472 | "type": "address" 473 | } 474 | ], 475 | "payable": false, 476 | "stateMutability": "view", 477 | "type": "function" 478 | }, 479 | { 480 | "constant": true, 481 | "inputs": [], 482 | "name": "symbol", 483 | "outputs": [ 484 | { 485 | "name": "", 486 | "type": "string" 487 | } 488 | ], 489 | "payable": false, 490 | "stateMutability": "view", 491 | "type": "function" 492 | }, 493 | { 494 | "constant": true, 495 | "inputs": [], 496 | "name": "gsnFee", 497 | "outputs": [ 498 | { 499 | "name": "", 500 | "type": "uint256" 501 | } 502 | ], 503 | "payable": false, 504 | "stateMutability": "view", 505 | "type": "function" 506 | }, 507 | { 508 | "constant": true, 509 | "inputs": [], 510 | "name": "pauser", 511 | "outputs": [ 512 | { 513 | "name": "", 514 | "type": "address" 515 | } 516 | ], 517 | "payable": false, 518 | "stateMutability": "view", 519 | "type": "function" 520 | }, 521 | { 522 | "constant": false, 523 | "inputs": [ 524 | { 525 | "name": "_spender", 526 | "type": "address" 527 | }, 528 | { 529 | "name": "_subtractedValue", 530 | "type": "uint256" 531 | } 532 | ], 533 | "name": "decreaseAllowance", 534 | "outputs": [ 535 | { 536 | "name": "", 537 | "type": "bool" 538 | } 539 | ], 540 | "payable": false, 541 | "stateMutability": "nonpayable", 542 | "type": "function" 543 | }, 544 | { 545 | "constant": false, 546 | "inputs": [ 547 | { 548 | "name": "_to", 549 | "type": "address" 550 | }, 551 | { 552 | "name": "_value", 553 | "type": "uint256" 554 | } 555 | ], 556 | "name": "transfer", 557 | "outputs": [ 558 | { 559 | "name": "", 560 | "type": "bool" 561 | } 562 | ], 563 | "payable": false, 564 | "stateMutability": "nonpayable", 565 | "type": "function" 566 | }, 567 | { 568 | "constant": false, 569 | "inputs": [ 570 | { 571 | "name": "_newMasterMinter", 572 | "type": "address" 573 | } 574 | ], 575 | "name": "updateMasterMinter", 576 | "outputs": [], 577 | "payable": false, 578 | "stateMutability": "nonpayable", 579 | "type": "function" 580 | }, 581 | { 582 | "constant": true, 583 | "inputs": [ 584 | { 585 | "name": "account", 586 | "type": "address" 587 | } 588 | ], 589 | "name": "isMinter", 590 | "outputs": [ 591 | { 592 | "name": "", 593 | "type": "bool" 594 | } 595 | ], 596 | "payable": false, 597 | "stateMutability": "view", 598 | "type": "function" 599 | }, 600 | { 601 | "constant": false, 602 | "inputs": [ 603 | { 604 | "name": "_newBlacklister", 605 | "type": "address" 606 | } 607 | ], 608 | "name": "updateBlacklister", 609 | "outputs": [], 610 | "payable": false, 611 | "stateMutability": "nonpayable", 612 | "type": "function" 613 | }, 614 | { 615 | "constant": true, 616 | "inputs": [], 617 | "name": "relayHubVersion", 618 | "outputs": [ 619 | { 620 | "name": "", 621 | "type": "string" 622 | } 623 | ], 624 | "payable": false, 625 | "stateMutability": "view", 626 | "type": "function" 627 | }, 628 | { 629 | "constant": true, 630 | "inputs": [], 631 | "name": "blacklister", 632 | "outputs": [ 633 | { 634 | "name": "", 635 | "type": "address" 636 | } 637 | ], 638 | "payable": false, 639 | "stateMutability": "view", 640 | "type": "function" 641 | }, 642 | { 643 | "constant": true, 644 | "inputs": [ 645 | { 646 | "name": "owner", 647 | "type": "address" 648 | }, 649 | { 650 | "name": "spender", 651 | "type": "address" 652 | } 653 | ], 654 | "name": "allowance", 655 | "outputs": [ 656 | { 657 | "name": "", 658 | "type": "uint256" 659 | } 660 | ], 661 | "payable": false, 662 | "stateMutability": "view", 663 | "type": "function" 664 | }, 665 | { 666 | "constant": false, 667 | "inputs": [ 668 | { 669 | "name": "context", 670 | "type": "bytes" 671 | }, 672 | { 673 | "name": "success", 674 | "type": "bool" 675 | }, 676 | { 677 | "name": "actualCharge", 678 | "type": "uint256" 679 | }, 680 | { 681 | "name": "preRetVal", 682 | "type": "bytes32" 683 | } 684 | ], 685 | "name": "postRelayedCall", 686 | "outputs": [], 687 | "payable": false, 688 | "stateMutability": "nonpayable", 689 | "type": "function" 690 | }, 691 | { 692 | "constant": true, 693 | "inputs": [], 694 | "name": "currency", 695 | "outputs": [ 696 | { 697 | "name": "", 698 | "type": "string" 699 | } 700 | ], 701 | "payable": false, 702 | "stateMutability": "view", 703 | "type": "function" 704 | }, 705 | { 706 | "constant": false, 707 | "inputs": [ 708 | { 709 | "name": "newOwner", 710 | "type": "address" 711 | } 712 | ], 713 | "name": "transferOwnership", 714 | "outputs": [], 715 | "payable": false, 716 | "stateMutability": "nonpayable", 717 | "type": "function" 718 | }, 719 | { 720 | "constant": false, 721 | "inputs": [ 722 | { 723 | "name": "_account", 724 | "type": "address" 725 | } 726 | ], 727 | "name": "blacklist", 728 | "outputs": [], 729 | "payable": false, 730 | "stateMutability": "nonpayable", 731 | "type": "function" 732 | }, 733 | { 734 | "constant": true, 735 | "inputs": [ 736 | { 737 | "name": "_account", 738 | "type": "address" 739 | } 740 | ], 741 | "name": "isBlacklisted", 742 | "outputs": [ 743 | { 744 | "name": "", 745 | "type": "bool" 746 | } 747 | ], 748 | "payable": false, 749 | "stateMutability": "view", 750 | "type": "function" 751 | }, 752 | { 753 | "anonymous": false, 754 | "inputs": [ 755 | { 756 | "indexed": true, 757 | "name": "minter", 758 | "type": "address" 759 | }, 760 | { 761 | "indexed": true, 762 | "name": "to", 763 | "type": "address" 764 | }, 765 | { 766 | "indexed": false, 767 | "name": "amount", 768 | "type": "uint256" 769 | } 770 | ], 771 | "name": "Mint", 772 | "type": "event" 773 | }, 774 | { 775 | "anonymous": false, 776 | "inputs": [ 777 | { 778 | "indexed": true, 779 | "name": "burner", 780 | "type": "address" 781 | }, 782 | { 783 | "indexed": false, 784 | "name": "amount", 785 | "type": "uint256" 786 | } 787 | ], 788 | "name": "Burn", 789 | "type": "event" 790 | }, 791 | { 792 | "anonymous": false, 793 | "inputs": [ 794 | { 795 | "indexed": false, 796 | "name": "oldFee", 797 | "type": "uint256" 798 | }, 799 | { 800 | "indexed": false, 801 | "name": "newFee", 802 | "type": "uint256" 803 | } 804 | ], 805 | "name": "GSNFeeUpdated", 806 | "type": "event" 807 | }, 808 | { 809 | "anonymous": false, 810 | "inputs": [ 811 | { 812 | "indexed": false, 813 | "name": "fee", 814 | "type": "uint256" 815 | }, 816 | { 817 | "indexed": false, 818 | "name": "user", 819 | "type": "address" 820 | } 821 | ], 822 | "name": "GSNFeeCharged", 823 | "type": "event" 824 | }, 825 | { 826 | "anonymous": false, 827 | "inputs": [ 828 | { 829 | "indexed": true, 830 | "name": "minter", 831 | "type": "address" 832 | }, 833 | { 834 | "indexed": false, 835 | "name": "minterAllowedAmount", 836 | "type": "uint256" 837 | } 838 | ], 839 | "name": "MinterConfigured", 840 | "type": "event" 841 | }, 842 | { 843 | "anonymous": false, 844 | "inputs": [ 845 | { 846 | "indexed": true, 847 | "name": "oldMinter", 848 | "type": "address" 849 | } 850 | ], 851 | "name": "MinterRemoved", 852 | "type": "event" 853 | }, 854 | { 855 | "anonymous": false, 856 | "inputs": [ 857 | { 858 | "indexed": true, 859 | "name": "newMasterMinter", 860 | "type": "address" 861 | } 862 | ], 863 | "name": "MasterMinterChanged", 864 | "type": "event" 865 | }, 866 | { 867 | "anonymous": false, 868 | "inputs": [ 869 | { 870 | "indexed": true, 871 | "name": "_account", 872 | "type": "address" 873 | } 874 | ], 875 | "name": "Blacklisted", 876 | "type": "event" 877 | }, 878 | { 879 | "anonymous": false, 880 | "inputs": [ 881 | { 882 | "indexed": true, 883 | "name": "_account", 884 | "type": "address" 885 | } 886 | ], 887 | "name": "UnBlacklisted", 888 | "type": "event" 889 | }, 890 | { 891 | "anonymous": false, 892 | "inputs": [ 893 | { 894 | "indexed": true, 895 | "name": "newBlacklister", 896 | "type": "address" 897 | } 898 | ], 899 | "name": "BlacklisterChanged", 900 | "type": "event" 901 | }, 902 | { 903 | "anonymous": false, 904 | "inputs": [], 905 | "name": "Pause", 906 | "type": "event" 907 | }, 908 | { 909 | "anonymous": false, 910 | "inputs": [], 911 | "name": "Unpause", 912 | "type": "event" 913 | }, 914 | { 915 | "anonymous": false, 916 | "inputs": [ 917 | { 918 | "indexed": true, 919 | "name": "newAddress", 920 | "type": "address" 921 | } 922 | ], 923 | "name": "PauserChanged", 924 | "type": "event" 925 | }, 926 | { 927 | "anonymous": false, 928 | "inputs": [ 929 | { 930 | "indexed": false, 931 | "name": "previousOwner", 932 | "type": "address" 933 | }, 934 | { 935 | "indexed": false, 936 | "name": "newOwner", 937 | "type": "address" 938 | } 939 | ], 940 | "name": "OwnershipTransferred", 941 | "type": "event" 942 | }, 943 | { 944 | "anonymous": false, 945 | "inputs": [ 946 | { 947 | "indexed": true, 948 | "name": "from", 949 | "type": "address" 950 | }, 951 | { 952 | "indexed": true, 953 | "name": "to", 954 | "type": "address" 955 | }, 956 | { 957 | "indexed": false, 958 | "name": "value", 959 | "type": "uint256" 960 | } 961 | ], 962 | "name": "Transfer", 963 | "type": "event" 964 | }, 965 | { 966 | "anonymous": false, 967 | "inputs": [ 968 | { 969 | "indexed": true, 970 | "name": "owner", 971 | "type": "address" 972 | }, 973 | { 974 | "indexed": true, 975 | "name": "spender", 976 | "type": "address" 977 | }, 978 | { 979 | "indexed": false, 980 | "name": "value", 981 | "type": "uint256" 982 | } 983 | ], 984 | "name": "Approval", 985 | "type": "event" 986 | }, 987 | { 988 | "anonymous": false, 989 | "inputs": [ 990 | { 991 | "indexed": true, 992 | "name": "oldRelayHub", 993 | "type": "address" 994 | }, 995 | { 996 | "indexed": true, 997 | "name": "newRelayHub", 998 | "type": "address" 999 | } 1000 | ], 1001 | "name": "RelayHubChanged", 1002 | "type": "event" 1003 | } 1004 | ], 1005 | -------------------------------------------------------------------------------- /contracts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/token-mint/ngnt/258467ef8978a3f769c3c5179b8ed1531217e1b3/contracts/.gitkeep -------------------------------------------------------------------------------- /contracts/Blacklistable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.5; 2 | 3 | import "./Ownable.sol"; 4 | 5 | 6 | /** 7 | * @title Blacklistable Token 8 | * @dev Allows accounts to be blacklisted by a "blacklister" role 9 | * From https://github.com/centrehq/centre-tokens 10 | * branch: master commit: 3ba876b5e96eec6955733e7e008d85f419ec44a5 11 | */ 12 | contract Blacklistable is Ownable { 13 | 14 | address public blacklister; 15 | mapping(address => bool) internal blacklisted; 16 | 17 | event Blacklisted(address indexed _account); 18 | event UnBlacklisted(address indexed _account); 19 | event BlacklisterChanged(address indexed newBlacklister); 20 | 21 | /** 22 | * @dev Throws if called by any account other than the blacklister 23 | */ 24 | modifier onlyBlacklister() { 25 | require(msg.sender == blacklister); 26 | _; 27 | } 28 | 29 | /** 30 | * @dev Throws if argument account is blacklisted 31 | * @param _account The address to check 32 | */ 33 | modifier notBlacklisted(address _account) { 34 | require(blacklisted[_account] == false); 35 | _; 36 | } 37 | 38 | /** 39 | * @dev Checks if account is blacklisted 40 | * @param _account The address to check 41 | */ 42 | function isBlacklisted(address _account) public view returns (bool) { 43 | return blacklisted[_account]; 44 | } 45 | 46 | /** 47 | * @dev Adds account to blacklist 48 | * @param _account The address to blacklist 49 | */ 50 | function blacklist(address _account) public onlyBlacklister { 51 | blacklisted[_account] = true; 52 | emit Blacklisted(_account); 53 | } 54 | 55 | /** 56 | * @dev Removes account from blacklist 57 | * @param _account The address to remove from the blacklist 58 | */ 59 | function unBlacklist(address _account) public onlyBlacklister { 60 | blacklisted[_account] = false; 61 | emit UnBlacklisted(_account); 62 | } 63 | 64 | function updateBlacklister(address _newBlacklister) public onlyOwner { 65 | require(_newBlacklister != address(0)); 66 | blacklister = _newBlacklister; 67 | emit BlacklisterChanged(blacklister); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /contracts/LimitedUpgradesProxyAdmin.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.5; 2 | 3 | /** 4 | * @title ProxyAdmin 5 | * @dev This contract is the admin of a proxy, and is in charge 6 | * of upgrading it as well as transferring it to another admin. 7 | * The ProxyAdmin contract is from https://github.com/OpenZeppelin/openzeppelin-sdk/blob/master/packages/lib/contracts/upgradeability/ProxyAdmin.sol 8 | * branch: master commit: 71c9ad77e0326db079e6a643eca8568ab316d4a9 modified to: 9 | * 1) Inherit from our custom Ownable contract 10 | * 2) Adds initializer that lets us set number of allowed upgrades 11 | * 3) Prevents contract from being upgraded more than allowedUpgrade times 12 | * 4) Removes ability to change admin (to prevent contract from being upgraded further) 13 | */ 14 | 15 | import "@openzeppelin/upgrades/contracts/upgradeability/AdminUpgradeabilityProxy.sol"; 16 | import "@openzeppelin/upgrades/contracts/ownership/Ownable.sol"; 17 | 18 | contract LimitedUpgradesProxyAdmin is OpenZeppelinUpgradesOwnable { 19 | uint256 public allowedUpgradesPerProxy; 20 | mapping(address => uint256) internal upgradeCounts; 21 | bool internal initialized; 22 | 23 | function initialize(address _owner, uint256 _allowedUpgradesPerProxy) public { 24 | require(!initialized); 25 | 26 | _transferOwnership(_owner); 27 | allowedUpgradesPerProxy = _allowedUpgradesPerProxy; 28 | initialized = true; 29 | } 30 | 31 | /** 32 | * @dev Returns the current implementation of a proxy. 33 | * This is needed because only the proxy admin can query it. 34 | * @return The address of the current implementation of the proxy. 35 | */ 36 | function getProxyImplementation(AdminUpgradeabilityProxy proxy) public view returns (address) { 37 | // We need to manually run the static call since the getter cannot be flagged as view 38 | // bytes4(keccak256("implementation()")) == 0x5c60da1b 39 | (bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b"); 40 | require(success); 41 | return abi.decode(returndata, (address)); 42 | } 43 | 44 | /** 45 | * @dev Returns the admin of a proxy. Only the admin can query it. 46 | * @return The address of the current admin of the proxy. 47 | */ 48 | function getProxyAdmin(AdminUpgradeabilityProxy proxy) public view returns (address) { 49 | // We need to manually run the static call since the getter cannot be flagged as view 50 | // bytes4(keccak256("admin()")) == 0xf851a440 51 | (bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440"); 52 | require(success); 53 | return abi.decode(returndata, (address)); 54 | } 55 | 56 | /** 57 | * @dev Upgrades a proxy to the newest implementation of a contract. 58 | * @param proxy Proxy to be upgraded. 59 | * @param implementation the address of the Implementation. 60 | */ 61 | function upgrade(AdminUpgradeabilityProxy proxy, address implementation) public onlyOwner { 62 | require(upgradeCounts[address(proxy)] < allowedUpgradesPerProxy); 63 | upgradeCounts[address(proxy)] += 1; 64 | proxy.upgradeTo(implementation); 65 | } 66 | 67 | /** 68 | * @dev Upgrades a proxy to the newest implementation of a contract and forwards a function call to it. 69 | * This is useful to initialize the proxied contract. 70 | * @param proxy Proxy to be upgraded. 71 | * @param implementation Address of the Implementation. 72 | * @param data Data to send as msg.data in the low level call. 73 | * It should include the signature and the parameters of the function to be called, as described in 74 | * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding. 75 | */ 76 | function upgradeAndCall(AdminUpgradeabilityProxy proxy, address implementation, bytes memory data) payable public onlyOwner { 77 | require(upgradeCounts[address(proxy)] < allowedUpgradesPerProxy); 78 | upgradeCounts[address(proxy)] += 1; 79 | proxy.upgradeToAndCall.value(msg.value)(implementation, data); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.5; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/NGNT.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.5; 2 | 3 | import '@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol'; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/GSN/GSNRecipient.sol"; 5 | import '@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol'; 6 | import "@0x/contracts-utils/contracts/src/LibBytes.sol"; 7 | 8 | import './Ownable.sol'; 9 | import './Blacklistable.sol'; 10 | import './Pausable.sol'; 11 | 12 | contract V1 is GSNRecipient, Ownable, ERC20, Pausable, Blacklistable { 13 | using SafeMath for uint256; 14 | 15 | string public name; 16 | string public symbol; 17 | uint8 public decimals; 18 | string public currency; 19 | address public masterMinter; 20 | bool internal initialized; 21 | 22 | enum GSNErrorCodes { 23 | INSUFFICIENT_BALANCE, NOT_ALLOWED 24 | } 25 | 26 | uint256 public gsnFee; 27 | mapping(address => uint256) internal balances; 28 | mapping(address => mapping(address => uint256)) internal allowed; 29 | uint256 internal totalSupply_; 30 | mapping(address => bool) internal minters; 31 | mapping(address => uint256) internal minterAllowed; 32 | 33 | event Mint(address indexed minter, address indexed to, uint256 amount); 34 | event Burn(address indexed burner, uint256 amount); 35 | event GSNFeeUpdated(uint256 oldFee, uint256 newFee); 36 | event GSNFeeCharged(uint256 fee, address user); 37 | event MinterConfigured(address indexed minter, uint256 minterAllowedAmount); 38 | event MinterRemoved(address indexed oldMinter); 39 | event MasterMinterChanged(address indexed newMasterMinter); 40 | 41 | function initialize( 42 | string memory _name, 43 | string memory _symbol, 44 | string memory _currency, 45 | uint8 _decimals, 46 | address _masterMinter, 47 | address _pauser, 48 | address _blacklister, 49 | address _owner, 50 | uint256 _gsnFee 51 | ) public { 52 | require(!initialized); 53 | require(_masterMinter != address(0)); 54 | require(_pauser != address(0)); 55 | require(_blacklister != address(0)); 56 | require(_owner != address(0)); 57 | 58 | GSNRecipient.initialize(); 59 | name = _name; 60 | symbol = _symbol; 61 | currency = _currency; 62 | decimals = _decimals; 63 | masterMinter = _masterMinter; 64 | pauser = _pauser; 65 | blacklister = _blacklister; 66 | gsnFee = _gsnFee; 67 | setOwner(_owner); 68 | initialized = true; 69 | } 70 | 71 | /** 72 | * @dev Throws if called by any account other than a minter 73 | */ 74 | modifier onlyMinters() { 75 | require(minters[_msgSender()] == true); 76 | _; 77 | } 78 | 79 | /** 80 | * @dev Function to mint tokens 81 | * @param _to The address that will receive the minted tokens. 82 | * @param _amount The amount of tokens to mint. Must be less than or equal to the minterAllowance of the caller. 83 | * @return A boolean that indicates if the operation was successful. 84 | */ 85 | function mint(address _to, uint256 _amount) whenNotPaused onlyMinters notBlacklisted(_msgSender()) notBlacklisted(_to) public returns (bool) { 86 | require(_to != address(0)); 87 | require(_amount > 0); 88 | 89 | uint256 mintingAllowedAmount = minterAllowed[_msgSender()]; 90 | require(_amount <= mintingAllowedAmount); 91 | 92 | totalSupply_ = totalSupply_.add(_amount); 93 | balances[_to] = balances[_to].add(_amount); 94 | minterAllowed[_msgSender()] = mintingAllowedAmount.sub(_amount); 95 | emit Mint(_msgSender(), _to, _amount); 96 | emit Transfer(address(0), _to, _amount); 97 | return true; 98 | } 99 | 100 | /** 101 | * @dev Throws if called by any account other than the masterMinter 102 | */ 103 | modifier onlyMasterMinter() { 104 | require(_msgSender() == masterMinter); 105 | _; 106 | } 107 | 108 | /** 109 | * @dev Get minter allowance for an account 110 | * @param minter The address of the minter 111 | */ 112 | function minterAllowance(address minter) public view returns (uint256) { 113 | return minterAllowed[minter]; 114 | } 115 | 116 | /** 117 | * @dev Checks if account is a minter 118 | * @param account The address to check 119 | */ 120 | function isMinter(address account) public view returns (bool) { 121 | return minters[account]; 122 | } 123 | 124 | /** 125 | * @dev Get allowed amount for an account 126 | * @param owner address The account owner 127 | * @param spender address The account spender 128 | */ 129 | function allowance(address owner, address spender) public view returns (uint256) { 130 | return allowed[owner][spender]; 131 | } 132 | 133 | /** 134 | * @dev Get totalSupply of token 135 | */ 136 | function totalSupply() public view returns (uint256) { 137 | return totalSupply_; 138 | } 139 | 140 | /** 141 | * @dev Get token balance of an account 142 | * @param account address The account 143 | */ 144 | function balanceOf(address account) public view returns (uint256) { 145 | return balances[account]; 146 | } 147 | 148 | /** 149 | * @dev Adds blacklisted check to approve 150 | * @return True if the operation was successful. 151 | */ 152 | function approve(address _spender, uint256 _value) whenNotPaused notBlacklisted(_msgSender()) notBlacklisted(_spender) public returns (bool) { 153 | allowed[_msgSender()][_spender] = _value; 154 | emit Approval(_msgSender(), _spender, _value); 155 | return true; 156 | } 157 | 158 | /** 159 | * @dev Adds blacklisted & not paused check to increaseAllowance 160 | * @return True if the operation was successful. 161 | */ 162 | function increaseAllowance(address _spender, uint256 _addedValue) whenNotPaused notBlacklisted(_msgSender()) notBlacklisted(_spender) public returns (bool) { 163 | _approve(_msgSender(), _spender, allowed[_msgSender()][_spender].add(_addedValue)); 164 | return true; 165 | } 166 | 167 | /** 168 | * @dev Adds blacklisted & not paused check to decreaseAllowance 169 | * @return True if the operation was successful. 170 | */ 171 | function decreaseAllowance(address _spender, uint256 _subtractedValue) whenNotPaused notBlacklisted(_msgSender()) notBlacklisted(_spender) public returns (bool) { 172 | _approve(_msgSender(), _spender, allowed[_msgSender()][_spender].sub(_subtractedValue)); 173 | return true; 174 | } 175 | 176 | /** 177 | * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens. 178 | * 179 | * This is internal function is equivalent to `approve`, and can be used to 180 | * e.g. set automatic allowances for certain subsystems, etc. 181 | * 182 | * Emits an `Approval` event. 183 | */ 184 | function _approve(address owner, address spender, uint256 value) internal { 185 | allowed[owner][spender] = value; 186 | emit Approval(owner, spender, value); 187 | } 188 | 189 | /** 190 | * @dev Transfer tokens from one address to another. 191 | * @param _from address The address which you want to send tokens from 192 | * @param _to address The address which you want to transfer to 193 | * @param _value uint256 the amount of tokens to be transferred 194 | * @return bool success 195 | */ 196 | function transferFrom(address _from, address _to, uint256 _value) whenNotPaused notBlacklisted(_to) notBlacklisted(_msgSender()) notBlacklisted(_from) public returns (bool) { 197 | require(_to != address(0)); 198 | require(_value <= balances[_from]); 199 | require(_value <= allowed[_from][_msgSender()]); 200 | 201 | balances[_from] = balances[_from].sub(_value); 202 | balances[_to] = balances[_to].add(_value); 203 | allowed[_from][_msgSender()] = allowed[_from][_msgSender()].sub(_value); 204 | emit Transfer(_from, _to, _value); 205 | return true; 206 | } 207 | 208 | /** 209 | * @dev transfer token for a specified address 210 | * @param _to The address to transfer to. 211 | * @param _value The amount to be transferred. 212 | * @return bool success 213 | */ 214 | function transfer(address _to, uint256 _value) whenNotPaused notBlacklisted(_msgSender()) notBlacklisted(_to) public returns (bool) { 215 | require(_to != address(0)); 216 | require(_value <= balances[_msgSender()]); 217 | 218 | balances[_msgSender()] = balances[_msgSender()].sub(_value); 219 | balances[_to] = balances[_to].add(_value); 220 | emit Transfer(_msgSender(), _to, _value); 221 | return true; 222 | } 223 | 224 | /** 225 | * @dev Function to add/update a new minter 226 | * @param minter The address of the minter 227 | * @param minterAllowedAmount The minting amount allowed for the minter 228 | * @return True if the operation was successful. 229 | */ 230 | function configureMinter(address minter, uint256 minterAllowedAmount) whenNotPaused onlyMasterMinter public returns (bool) { 231 | minters[minter] = true; 232 | minterAllowed[minter] = minterAllowedAmount; 233 | emit MinterConfigured(minter, minterAllowedAmount); 234 | return true; 235 | } 236 | 237 | /** 238 | * @dev Function to remove a minter 239 | * @param minter The address of the minter to remove 240 | * @return True if the operation was successful. 241 | */ 242 | function removeMinter(address minter) onlyMasterMinter public returns (bool) { 243 | minters[minter] = false; 244 | minterAllowed[minter] = 0; 245 | emit MinterRemoved(minter); 246 | return true; 247 | } 248 | 249 | /** 250 | * @dev allows a minter to burn some of its own tokens 251 | * Validates that caller is a minter and that sender is not blacklisted 252 | * amount is less than or equal to the minter's account balance 253 | * @param _amount uint256 the amount of tokens to be burned 254 | */ 255 | function burn(uint256 _amount) whenNotPaused onlyMinters notBlacklisted(_msgSender()) public { 256 | uint256 balance = balances[_msgSender()]; 257 | require(_amount > 0); 258 | require(balance >= _amount); 259 | 260 | totalSupply_ = totalSupply_.sub(_amount); 261 | balances[_msgSender()] = balance.sub(_amount); 262 | emit Burn(_msgSender(), _amount); 263 | emit Transfer(_msgSender(), address(0), _amount); 264 | } 265 | 266 | /** 267 | * @dev allows the owner to update the master minter address 268 | * Validates that caller is an owner 269 | * @param _newMasterMinter the new master minter address 270 | */ 271 | function updateMasterMinter(address _newMasterMinter) onlyOwner public { 272 | require(_newMasterMinter != address(0)); 273 | masterMinter = _newMasterMinter; 274 | emit MasterMinterChanged(masterMinter); 275 | } 276 | 277 | /** 278 | * @dev allows the owner to update the gsnFee 279 | * Validates that caller is an owner 280 | * Validates _newGsnFee is not 0 and that its not more than two times old fee 281 | * @param _newGsnFee the new gnsFee 282 | */ 283 | function updateGsnFee(uint256 _newGsnFee) onlyOwner public { 284 | require(_newGsnFee != 0); 285 | require(_newGsnFee <= gsnFee.mul(2)); 286 | uint256 oldFee = gsnFee; 287 | gsnFee = _newGsnFee; 288 | emit GSNFeeUpdated(oldFee, gsnFee); 289 | } 290 | 291 | /** 292 | * @dev "callback" to determine if a GSN call should be accepted 293 | * Validates that call is transfer, approve or transferFrom 294 | * Validates that user NGNT balances is enough for the transaction + gsnFee 295 | */ 296 | function acceptRelayedCall( 297 | address relay, 298 | address from, 299 | bytes calldata encodedFunction, 300 | uint256 transactionFee, 301 | uint256 gasPrice, 302 | uint256 gasLimit, 303 | uint256 nonce, 304 | bytes calldata approvalData, 305 | uint256 maxPossibleCharge 306 | ) external view returns (uint256, bytes memory) { 307 | uint256 userBalance = balanceOf(from); 308 | 309 | bytes4 calldataSelector = LibBytes.readBytes4(encodedFunction, 0); 310 | 311 | if (calldataSelector == this.transfer.selector) { 312 | uint256 valuePlusGsnFee = gsnFee.add(uint(LibBytes.readBytes32(encodedFunction, 36))); 313 | 314 | if (userBalance >= valuePlusGsnFee) { 315 | return _approveRelayedCall(abi.encode(from)); 316 | } else { 317 | return _rejectRelayedCall(uint256(GSNErrorCodes.INSUFFICIENT_BALANCE)); 318 | } 319 | } else if (calldataSelector == this.transferFrom.selector || calldataSelector == this.approve.selector) { 320 | if (userBalance >= gsnFee) { 321 | return _approveRelayedCall(abi.encode(from)); 322 | } else { 323 | return _rejectRelayedCall(uint256(GSNErrorCodes.INSUFFICIENT_BALANCE)); 324 | } 325 | } else { 326 | return _rejectRelayedCall(uint256(GSNErrorCodes.NOT_ALLOWED)); 327 | } 328 | 329 | } 330 | 331 | function _preRelayedCall(bytes memory context) internal returns (bytes32) { 332 | 333 | } 334 | 335 | /** 336 | * @dev "callback" to charge user gsnFee units of NGNT after successful relayed call 337 | */ 338 | function _postRelayedCall(bytes memory context, bool, uint256 actualCharge, bytes32) internal { 339 | address from = abi.decode(context, (address)); 340 | 341 | balances[from] = balances[from].sub(gsnFee); 342 | balances[address(this)] = balances[address(this)].add(gsnFee); 343 | emit GSNFeeCharged(gsnFee, from); 344 | } 345 | 346 | } 347 | 348 | contract NGNT is V1 { 349 | 350 | } 351 | -------------------------------------------------------------------------------- /contracts/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.5; 2 | 3 | /** 4 | * @title Ownable 5 | * @dev The Ownable contract from https://github.com/zeppelinos/labs/blob/master/upgradeability_ownership/contracts/ownership/Ownable.sol 6 | * branch: master commit: 3887ab77b8adafba4a26ace002f3a684c1a3388b modified to: 7 | * 1) Add emit prefix to OwnershipTransferred event (7/13/18) 8 | * 2) Replace constructor with constructor syntax (7/13/18) 9 | * 3) consolidate OwnableStorage into this contract 10 | * 11 | * From https://github.com/centrehq/centre-tokens 12 | * branch: master commit: 3ba876b5e96eec6955733e7e008d85f419ec44a5 13 | */ 14 | 15 | contract Ownable { 16 | 17 | // Owner of the contract 18 | address private _owner; 19 | 20 | /** 21 | * @dev Event to show ownership has been transferred 22 | * @param previousOwner representing the address of the previous owner 23 | * @param newOwner representing the address of the new owner 24 | */ 25 | event OwnershipTransferred(address previousOwner, address newOwner); 26 | 27 | /** 28 | * @dev The constructor sets the original owner of the contract to the sender account. 29 | */ 30 | constructor() public { 31 | setOwner(msg.sender); 32 | } 33 | 34 | /** 35 | * @dev Tells the address of the owner 36 | * @return the address of the owner 37 | */ 38 | function owner() public view returns (address) { 39 | return _owner; 40 | } 41 | 42 | /** 43 | * @dev Sets a new owner address 44 | */ 45 | function setOwner(address newOwner) internal { 46 | _owner = newOwner; 47 | } 48 | 49 | /** 50 | * @dev Throws if called by any account other than the owner. 51 | */ 52 | modifier onlyOwner() { 53 | require(msg.sender == owner()); 54 | _; 55 | } 56 | 57 | /** 58 | * @dev Allows the current owner to transfer control of the contract to a newOwner. 59 | * @param newOwner The address to transfer ownership to. 60 | */ 61 | function transferOwnership(address newOwner) public onlyOwner { 62 | require(newOwner != address(0)); 63 | emit OwnershipTransferred(owner(), newOwner); 64 | setOwner(newOwner); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /contracts/Pausable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.5; 2 | 3 | 4 | import "./Ownable.sol"; 5 | 6 | /** 7 | * @title Pausable 8 | * @dev Base contract which allows children to implement an emergency stop mechanism. 9 | * Based on openzeppelin tag v1.10.0 commit: feb665136c0dae9912e08397c1a21c4af3651ef3 10 | * Modifications: 11 | * 1) Added pauser role, switched pause/unpause to be onlyPauser (6/14/2018) 12 | * 2) Removed whenNotPause/whenPaused from pause/unpause (6/14/2018) 13 | * 3) Removed whenPaused (6/14/2018) 14 | * 4) Switches ownable library to use zeppelinos (7/12/18) 15 | * 5) Remove constructor (7/13/18) 16 | * 17 | * From https://github.com/centrehq/centre-tokens 18 | * branch: master commit: 3ba876b5e96eec6955733e7e008d85f419ec44a5 19 | */ 20 | 21 | contract Pausable is Ownable { 22 | event Pause(); 23 | event Unpause(); 24 | event PauserChanged(address indexed newAddress); 25 | 26 | 27 | address public pauser; 28 | bool public paused = false; 29 | 30 | /** 31 | * @dev Modifier to make a function callable only when the contract is not paused. 32 | */ 33 | modifier whenNotPaused() { 34 | require(!paused); 35 | _; 36 | } 37 | 38 | /** 39 | * @dev throws if called by any account other than the pauser 40 | */ 41 | modifier onlyPauser() { 42 | require(msg.sender == pauser); 43 | _; 44 | } 45 | 46 | /** 47 | * @dev called by the owner to pause, triggers stopped state 48 | */ 49 | function pause() onlyPauser public { 50 | paused = true; 51 | emit Pause(); 52 | } 53 | 54 | /** 55 | * @dev called by the owner to unpause, returns to normal state 56 | */ 57 | function unpause() onlyPauser public { 58 | paused = false; 59 | emit Unpause(); 60 | } 61 | 62 | /** 63 | * @dev update the pauser role 64 | */ 65 | function updatePauser(address _newPauser) onlyOwner public { 66 | require(_newPauser != address(0)); 67 | pauser = _newPauser; 68 | emit PauserChanged(pauser); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /docs/deployment.md: -------------------------------------------------------------------------------- 1 | # Deployment Process 2 | 3 | This details the steps involved in deploying the NGNT contract using [openzeppelin-cli](https://github.com/OpenZeppelin/openzeppelin-sdk). 4 | You can read more about the design we're trying to achieve in the [Token Design](tokendesign.md) 5 | 6 | ### 1. Deploy Proxy Admin Contract 7 | 8 | The [Limited Upgrades Proxy Admin contract](../contracts/LimitedUpgradesProxyAdmin.sol) is one that allows us to limit the amount of times a contract can get upgraded. 9 | NGNT will only be upgradeable **once**. You can read more on that [here](../README.md#upgradebility). 10 | 11 | This contract will be deployed with the `--minimal` flag because we do not need it to be upgradeable. You can read more about this from the [openzepplin-cli docs](https://docs.openzeppelin.com/sdk/2.5/api/cli#create). 12 | Upon deployment, the `initalize(address _owner, uint256 allowedUpgrades)` function MUST be called and the arguments should be set as follows: 13 | 14 | 1. The `_owner` should be an address that will be the transaction sender in step 2 below. This owner address will be able to upgrade the contract. 15 | 2. `allowedUpgrades` should be set to 1. 16 | 17 | ### 2. Deploy NGNT Contract 18 | 19 | Deploy the NGNT contract using `openzeppelin create` as one would normally do. 20 | Use `--from` to specify the transaction sender (this should be the same address as the `_owner` in step 1) 21 | 22 | Immediately after, this function 23 | ``` 24 | initialize( 25 | string memory _name, 26 | string memory _symbol, 27 | uint8 _decimals, 28 | address _masterMinter, 29 | address _pauser, 30 | address _blacklister, 31 | address _owner, 32 | uint256 _gsnFee) 33 | ``` 34 | 35 | MUST be called and the arguments set as follows: 36 | 37 | 1. `_name`: Naira Token 38 | 2. `_symbol`: NGNT 39 | 3. `_decimals`: 2 40 | 4. `_masterMinter`, `_pauser`, `_blacklister`, `_owner` shall be (multisig contract) addresses obtained from Token Mint Center. 41 | 5. `gsnFee`: 5000 42 | 43 | ### 3. Set NGNT Contract Admin 44 | Using [`openzeppelin-cli`'s `set-admin` command](https://docs.openzeppelin.com/sdk/2.5/api/cli#set-admin) to change the admin of 45 | the NGNT Proxy contract to the address of your `LimitedUpgradesProxyAdmin` deployed in [step 2](#2-deploy-proxy-admin-contract). 46 | 47 | Finally, in the `.openzeppelin` folder, there's a `.json` file (if you're deploying to Rinkeby, it'll be `rinkeby.json`). 48 | Change the value of the `proxyAdmin`'s address (at the bottom of the file) to the address of your deployed `LimitedUpgradesProxyAdmin` contract. 49 | -------------------------------------------------------------------------------- /docs/tokendesign.md: -------------------------------------------------------------------------------- 1 | # NGNT 2 | The NGNT contract is an ERC-20 compatible token. 3 | It allows minting/burning of tokens by multiple entities, pausing all activity, freezing of individual addresses, 4 | and a way to upgrade the contract so that bugs can be fixed or features added. 5 | 6 | ## Roles 7 | `NGNT` has a number of roles (addresses) which control different functionality: 8 | - `masterMinter` - adds and removes minters and increases their minting allowance 9 | - `minters` - create and destroy tokens 10 | - `pauser` - pause the contract, which prevents all transfers, minting, and burning 11 | - `blacklister` - prevent all transfers to or from a particular address, and prevents that address from minting or burning 12 | - `owner` - re-assign any of the roles except for `admin` 13 | - `admin` - upgrade the contract. This can happen only **once**. 14 | 15 | Token Mint Center will control the addresses of all roles except for minters, which will be controlled by the entities that 16 | Token Mint Center elects to make minters. **All non-minter addresses** will have multisig contracts deployed on them (using the [Gnosis MultiSigWallet](https://github.com/gnosis/MultiSigWallet)). 17 | 18 | ## ERC-20 19 | `NGNT` implements the standard methods of the ERC-20 interface with some changes: 20 | - A blacklisted address will be unable to call `transfer`, `transferFrom`, or `approve`, and will be unable to receive tokens. 21 | - `transfer`, `transferFrom`, and `approve` will fail if the contract has been paused. 22 | 23 | 24 | ## Issuing and Destroying tokens 25 | NGNT allows multiple entities to create and destroy tokens. 26 | These entities will have to be members of Token Mint Center, and will be vetted by Token Mint Center before they are allowed to create new 27 | tokens. 28 | 29 | Each `minter` has a `mintingAllowance`, which Token Mint Center configures. The `mintingAllowance` is how many tokens that minter 30 | may issue, and as a `minter` issues tokens, its `mintingAllowance` declines. 31 | Token Mint Center will periodically reset the `mintingAllowance` as long as a `minter` remains in good standing with Token Mint Center and maintains 32 | adequate reserves for the tokens it has issued. The `mintingAllowance` is to limit the damage if any particular 33 | `minter` is compromised. 34 | 35 | ### Adding Minters 36 | Token Mint Center adds minters via the `configureMinter` method. When a minter is configured a `mintingAllowance` is specified, 37 | which is the number of tokens that address is allowed to mint. As a `minter` mints tokens, the `mintingAllowance` will decline. 38 | 39 | - Only the `masterMinter` role may call configureMinter. 40 | 41 | ### Resetting Minting Allowance 42 | The `minters` will need their allowance reset periodically to allow them to continue 43 | minting. When a `minter`'s allowance is low, Token Mint Center can make another call to `configureMinter` to reset the 44 | `mintingAllowance` to a higher value. 45 | 46 | ### Removing Minters 47 | Token Mint Center removes minters via the `removeMinter` method. This will remove the `minter` from the list of `minters` and set 48 | its `mintingAllowance` to 0. Once a `minter` is removed it will no longer be able to mint or burn tokens. 49 | 50 | - Only the `masterMinter` role may call `removeMinter`. 51 | 52 | ### Minting 53 | A `minter` mints tokens via the `mint` method. The `minter` specifies the `amount` of tokens to create, and a `_to` 54 | address which will own the newly created tokens. A `minter` may only mint an amount less than or equal to its `mintingAllowance`. 55 | The `mintingAllowance` will decrease by the amount of tokens minted, and the balance of the `_to` address and `totalSupply` 56 | will each increase by `amount`. 57 | 58 | - Only a `minter` may call `mint`. 59 | 60 | - Minting fails when the contract is `paused`. 61 | - Minting fails when the `minter` or `_to` address is blacklisted. 62 | - Minting emits a `Mint(minter, _to, amount)` event and a `Transfer(0x00, _to, amount)` event. 63 | ### Burning 64 | A `minter` burns tokens via the `burn` method. The `minter` specifies the `amount` of tokens to burn, and the `minter` 65 | must have a `balance` greater than or equal to the `amount`. Burning tokens is restricted to `minter` addresses to 66 | avoid accidental burning of tokens by end users. A `minter` with a `mintingAllowance` of 0 is allowed to burn tokens. 67 | A `minter` can only burn tokens which it owns. 68 | When a minter burns tokens, its balance and the totalSupply are reduced by `amount`. 69 | 70 | Burning tokens will not increase the mintingAllowance of the address doing the burning. 71 | 72 | - Only a minter may call burn. 73 | 74 | - Burning fails when the contract is paused. 75 | - Burning fails when the minter is blacklisted. 76 | 77 | - Burning emits a `Burn(minter, amount)` event, and a `Transfer(minter, 0x00, amount)` event. 78 | 79 | ## Blacklisting 80 | Addresses can be blacklisted. A blacklisted address will be unable to transfer tokens, approve, mint, or burn tokens. 81 | ### Adding a blacklisted address 82 | Token Mint Center blacklists an address via the `blacklist` method. The specified `account` will be added to the blacklist. 83 | 84 | - Only the `blacklister` role may call `blacklist`. 85 | - Blacklisting emits a `Blacklist(account)` event 86 | 87 | ### Removing a blacklisted address 88 | Token Mint Center removes an address from the blacklist via the `unblacklist` method. The specified `account` will be removed from the blacklist. 89 | 90 | - Only the `blacklister` role may call `unblacklist`. 91 | - Unblacklisting emits an `UnBlacklist(account)` event. 92 | 93 | ## Pausing 94 | The entire contract can be paused in case a serious bug is found or there is a serious key compromise. 95 | All transfers, minting, burning, and adding minters will be prevented while the contract is paused. Other functionality, such as modifying 96 | the blacklist, removing minters, changing roles, and upgrading will remain operational as those methods may be 97 | required to fix or mitigate the issue that caused Token Mint Center to pause the contract. 98 | 99 | ### Pause 100 | Token Mint Center will pause the contract via the `pause` method. This method will set the paused flag to true. 101 | 102 | - Only the `pauser` role may call pause. 103 | 104 | - Pausing emits a `Pause()` event 105 | 106 | ### Unpause 107 | Token Mint Center will unpause the contract via the `unpause` method. This method will set the `paused` flag to false. 108 | All functionality will be restored when the contract is unpaused. 109 | 110 | - Only the `pauser` role may call unpause. 111 | 112 | - Unpausing emits an `Unpause()` event 113 | 114 | ## Reassigning Roles 115 | The roles outlined above may be reassigned except for the `admin` role. 116 | 117 | The `owner` role has the ability to reassign all roles (including itself) except for the `admin` role. 118 | ### Admin 119 | After deployment (read more [here](./deployment.md)), 120 | the admin is immediately set to an address with [`ProxyAdmin.sol`](../contracts/LimitedUpgradesProxyAdmin.sol) deployed to it. This has no functions to change the admin and prevents the contract from being upgraded more than once. 121 | ### Master Minter 122 | - `updateMasterMinter` updates the `masterMinter` role to a new address. 123 | - `updateMasterMinter` may only be called by the `owner` role. 124 | ### Pauser 125 | - `updatePauser` updates the `pauser` role to a new address. 126 | - `updatePauser` may only be called by the `owner` role. 127 | ### Blacklister 128 | - `updateBlacklister` updates the `blacklister` role to a new address. 129 | - `updateBlacklister` may only be called by the `owner` role. 130 | ### Owner 131 | - `transferOwnership` updates the `owner` role to a new address. 132 | - `transferOwnership` may only be called by the `owner` role. 133 | 134 | The image below might be useful in visualizing the token design in terms of roles and how each contract interacts 135 | 136 | ![Token Design Schematic](https://res.cloudinary.com/buycoins/image/upload/v1573065220/Paper.Bitkoin.147_2.png) 137 | -------------------------------------------------------------------------------- /keystore/UTC--2019-11-25T11-21-06.248251000Z--f192d9342576df0a3e5f7af3b9edf530f85a02a0: -------------------------------------------------------------------------------- 1 | {"address":"f192d9342576df0a3e5f7af3b9edf530f85a02a0","crypto":{"cipher":"aes-128-ctr","ciphertext":"e0a54539c8d5506e983afbbfca97c837ba23ac9884ee3426682159b9361d5fe9","cipherparams":{"iv":"7c74ce0632f8fe71727ad40704566dc7"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":4096,"p":6,"r":8,"salt":"96e3260cb9b69fc541cba746b177b87f91b575445aab3844fdb8369b2f7ec10e"},"mac":"4853fa4cde1ee73cfbe14329a67485adc6560d318ef05e4fdcb9ffcf9bac5bae"},"id":"b2c06703-9b6f-4648-9835-724655cbe464","version":3} -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngnt", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "bash script/test.sh" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@0x/contracts-utils": "^3.2.4", 14 | "@openzeppelin/cli": "^2.5.3", 15 | "@openzeppelin/network": "^0.2.10", 16 | "chance": "^1.1.3", 17 | "ethers": "^4.0.39", 18 | "ganache-core": "^2.8.0", 19 | "truffle": "^5.0.43", 20 | "web3": "^1.2.4", 21 | "websocket": "^1.0.30" 22 | }, 23 | "devDependencies": { 24 | "@openzeppelin/contracts-ethereum-package": "^2.3.0", 25 | "@openzeppelin/gsn-helpers": "^0.2.1", 26 | "@openzeppelin/test-helpers": "^0.5.4", 27 | "@openzeppelin/upgrades": "^2.5.3", 28 | "@truffle/hdwallet-provider": "^1.0.23", 29 | "dotenv": "^8.2.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /script/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Exit script as soon as a command fails. 4 | set -eo pipefail 5 | 6 | # Executes cleanup function at script exit. 7 | trap cleanup EXIT 8 | 9 | cleanup() { 10 | # Kill the GSN relay server that we started (if we started one and if it's still running). 11 | if [ -n "$gsn_relay_server_pid" ] && ps -p $gsn_relay_server_pid > /dev/null; then 12 | kill $gsn_relay_server_pid 13 | fi 14 | 15 | # Kill the ganache instance that we started (if we started one and if it's still running). 16 | if [ -n "$ganache_pid" ] && ps -p $ganache_pid > /dev/null; then 17 | kill -9 $ganache_pid 18 | fi 19 | } 20 | 21 | 22 | ganache_port=8545 23 | ganache_url="http://localhost:$ganache_port" 24 | 25 | relayer_port=8090 26 | relayer_url="http://localhost:${relayer_port}" 27 | 28 | ganache_running() { 29 | nc -z localhost "$ganache_port" 30 | } 31 | 32 | relayer_running() { 33 | nc -z localhost "$relayer_port" 34 | } 35 | 36 | start_ganache() { 37 | npx ganache-cli --port "$ganache_port" -g 20000000000 -d --noVMErrorsOnRPCResponse &> /dev/null & 38 | ganache_pid=$! 39 | 40 | echo "Waiting for ganache to launch on port "$ganache_port"..." 41 | 42 | while ! ganache_running; do 43 | sleep 0.1 # wait for 1/10 of the second before check again 44 | done 45 | 46 | echo "Ganache launched!" 47 | } 48 | 49 | #--detach --quiet 50 | setup_gsn_relay() { 51 | echo "Launching GSN relay server" 52 | gsn_relay_server_pid=$(npx oz-gsn run-relayer --ethereumNodeURL $ganache_url --port $relayer_port --detach --quiet) 53 | 54 | echo "GSN relay server launched!" 55 | } 56 | 57 | # Main 58 | if ganache_running; then 59 | echo "Using existing ganache instance" 60 | else 61 | echo "Starting our own ganache instance" 62 | start_ganache 63 | fi 64 | 65 | setup_gsn_relay 66 | 67 | env PROVIDER_URL=$ganache_url RELAYER_URL=$relayer_url ./node_modules/.bin/truffle test $@ -------------------------------------------------------------------------------- /test/limitedUpgradesProxyAdmin.test.js: -------------------------------------------------------------------------------- 1 | const {TestHelper} = require('@openzeppelin/cli'); 2 | const {Contracts, ZWeb3} = require('@openzeppelin/upgrades'); 3 | 4 | ZWeb3.initialize(web3.currentProvider); 5 | const NGNT = Contracts.getFromLocal('NGNT'); 6 | const LimitedUpgradesProxyAdmin = Contracts.getFromLocal('LimitedUpgradesProxyAdmin'); 7 | 8 | const Chance = require('chance'); 9 | const chance = new Chance(); 10 | 11 | contract('LimitedUpgradesProxyAdmin', function (accounts) { 12 | let ngnt; 13 | let ngntProxy; 14 | let limitedUpgradesProxyAdmin; 15 | let project; 16 | let numberOfUpgrades; 17 | 18 | const tokenName = 'Naira Token'; 19 | const symbol = 'NGNT'; 20 | const currency = 'Naira'; 21 | const decimals = 18; 22 | const masterMinter = accounts[0]; 23 | const pauser = accounts[1]; 24 | const blacklister = accounts[1]; 25 | const owner = accounts[0]; 26 | const gsnFee = 10; 27 | 28 | beforeEach(async function () { 29 | project = await TestHelper(); 30 | numberOfUpgrades = chance.integer({min: 1, max: 10}); 31 | limitedUpgradesProxyAdmin = await project.createMinimalProxy(LimitedUpgradesProxyAdmin, { 32 | initMethod: 'initialize', 33 | initArgs: [owner, numberOfUpgrades] 34 | }); 35 | const adminAddress = limitedUpgradesProxyAdmin.options.address; 36 | ngntProxy = await project.createProxy(NGNT, { 37 | initMethod: 'initialize', 38 | initArgs: [tokenName, symbol, currency, decimals, masterMinter, pauser, blacklister, owner, gsnFee], 39 | admin: adminAddress 40 | }); 41 | }); 42 | 43 | describe('contract should be properly initialized', () => { 44 | it('contract should have correct initialization values', async function () { 45 | const _tokenName = await ngntProxy.methods.name().call(); 46 | const _symbol = await ngntProxy.methods.symbol().call(); 47 | const _currency = await ngntProxy.methods.currency().call(); 48 | const _decimals = await ngntProxy.methods.decimals().call(); 49 | const _masterMinter = await ngntProxy.methods.masterMinter().call(); 50 | const _pauser = await ngntProxy.methods.pauser().call(); 51 | const _blacklister = await ngntProxy.methods.blacklister().call(); 52 | const _owner = await ngntProxy.methods.owner().call(); 53 | const _gsnFee = await ngntProxy.methods.gsnFee().call(); 54 | 55 | assert.equal(_tokenName, tokenName); 56 | assert.equal(_symbol, symbol); 57 | assert.equal(_currency, currency); 58 | assert.equal(_decimals, decimals); 59 | assert.equal(_pauser, pauser); 60 | assert.equal(_masterMinter, masterMinter); 61 | assert.equal(_blacklister, blacklister); 62 | assert.equal(_owner, owner); 63 | assert.equal(_gsnFee, gsnFee); 64 | }); 65 | }); 66 | 67 | context('Test limited Upgrades Proxy Admin upgrade function', function () { 68 | let previousProxyAddress; 69 | let newImplementationAddress; 70 | 71 | it(`should upgrade proxy admin contract not more than ${numberOfUpgrades} times`, async function () { 72 | previousProxyAddress = ngntProxy.options.address; 73 | 74 | for (let upgrades = 1; upgrades <= numberOfUpgrades; upgrades++) { 75 | ngnt = await NGNT.new({from: accounts[1], gas: 4600000}); 76 | await ngnt.methods.initialize(tokenName, symbol, currency, decimals, masterMinter, pauser, blacklister, owner, gsnFee); 77 | newImplementationAddress = ngnt.options.address; 78 | 79 | await limitedUpgradesProxyAdmin.methods.upgrade(previousProxyAddress, newImplementationAddress).send({ 80 | from: owner, gas: 4600000, gasPrice: 1e6 81 | }); 82 | 83 | } 84 | 85 | ngnt = await NGNT.new({from: accounts[1], gas: 4600000}); 86 | await ngnt.methods.initialize(tokenName, symbol, currency, decimals, masterMinter, pauser, blacklister, owner, gsnFee); 87 | newImplementationAddress = ngnt.options.address; 88 | 89 | expect(async function () { 90 | await limitedUpgradesProxyAdmin.methods.upgrade(previousProxyAddress, newImplementationAddress).send({ 91 | from: owner, gas: 4600000, gasPrice: 1e6 92 | }).to.throw(); 93 | }) 94 | }); 95 | 96 | it('should only be upgradable by owner', async () => { 97 | const notOwner = accounts[3]; 98 | expect(async function () { 99 | await limitedUpgradesProxyAdmin.methods.upgrade(previousProxyAddress, newImplementationAddress).send({ 100 | from: notOwner, gas: 4600000, gasPrice: 1e6 101 | }).to.throw(); 102 | }) 103 | }); 104 | }); 105 | 106 | context('Test limited Upgrades Proxy Admin upgradeAndCall function', function () { 107 | let previousProxyAddress; 108 | let newImplementationAddress; 109 | let encodedTotalSupplyFunction = web3.eth.abi.encodeFunctionCall({ 110 | "name": "totalSupply", 111 | "type": "function", 112 | "inputs": [], 113 | "outputs": [ 114 | { 115 | "name": "", 116 | "type": "uint256" 117 | } 118 | ] 119 | }, []); 120 | 121 | it(`should upgrade proxy admin contract and call encodedFunction not more than ${numberOfUpgrades} times `, async function () { 122 | previousProxyAddress = ngntProxy.options.address; 123 | 124 | for (let upgrades = 1; upgrades <= numberOfUpgrades; upgrades++) { 125 | ngnt = await NGNT.new({from: accounts[1], gas: 4600000}); 126 | newImplementationAddress = ngnt.options.address; 127 | 128 | await limitedUpgradesProxyAdmin.methods.upgradeAndCall(previousProxyAddress, newImplementationAddress, encodedTotalSupplyFunction).send({ 129 | from: owner, gas: 4600000, gasPrice: 1e6 130 | }); 131 | } 132 | 133 | ngnt = await NGNT.new({from: accounts[1], gas: 4600000}); 134 | newImplementationAddress = ngnt.options.address; 135 | 136 | expect(async function () { 137 | await limitedUpgradesProxyAdmin.methods.upgrade(previousProxyAddress, newImplementationAddress, encodedTotalSupplyFunction).send({ 138 | from: owner, gas: 4600000, gasPrice: 1e6 139 | }).to.throw(); 140 | }) 141 | }); 142 | 143 | it('should not call upgradableAndCall by non contract owner', async () => { 144 | const notOwner = accounts[3]; 145 | expect(async function () { 146 | await limitedUpgradesProxyAdmin.methods.upgradeAndCall(previousProxyAddress, newImplementationAddress, encodedTotalSupplyFunction).send({ 147 | from: notOwner, gas: 4600000, gasPrice: 1e6 148 | }).to.throw(); 149 | }) 150 | }); 151 | }); 152 | }); 153 | -------------------------------------------------------------------------------- /test/ngntWithGSN.test.js: -------------------------------------------------------------------------------- 1 | const {registerRelay, deployRelayHub, fundRecipient, balance, runRelayer} = require('@openzeppelin/gsn-helpers'); 2 | const {BN, constants, ether, expectEvent, expectRevert} = require('@openzeppelin/test-helpers'); 3 | const gsn = require('@openzeppelin/gsn-helpers'); 4 | const IRelayHub = artifacts.require('IRelayHub'); 5 | const chai = require('chai'); 6 | const expect = chai.expect; 7 | 8 | const NGNT = artifacts.require('NGNT'); 9 | 10 | contract("NGNT with GSN", function (accounts) { 11 | let ngnt; 12 | const tokenName = 'Naira Token'; 13 | const symbol = 'NGNT'; 14 | const currency = 'Naira'; 15 | const decimals = 18; 16 | const masterMinter = accounts[0]; 17 | const pauser = accounts[1]; 18 | const blacklister = accounts[1]; 19 | const owner = accounts[0]; 20 | const minter = accounts[3]; 21 | const minterAllowedAmount = 50000; 22 | let mintedAmount = 5000; 23 | const gsnFee = 10; 24 | 25 | before(async function () { 26 | ngnt = await NGNT.new(); 27 | await ngnt.initialize(tokenName, symbol, currency, decimals, masterMinter, pauser, blacklister, owner, gsnFee); 28 | 29 | 30 | await deployRelayHub(web3, { 31 | from: accounts[0] 32 | }); 33 | 34 | await runRelayer({ 35 | relayUrl: 'http://localhost:8090', 36 | workdir: process.cwd(), 37 | devMode: true, 38 | ethereumNodeURL: 'http://localhost:8545', 39 | gasPricePercent: 0, 40 | port: 8090, 41 | quiet: true 42 | }); 43 | 44 | await registerRelay(web3, { 45 | relayUrl: 'http://localhost:8090', 46 | stake: ether('1'), 47 | unstakeDelay: 604800, 48 | funds: ether('5'), 49 | from: accounts[0] 50 | }); 51 | 52 | await fundRecipient(web3, { 53 | recipient: ngnt.address, 54 | amount: ether('2'), 55 | from: accounts[0] 56 | }); 57 | }); 58 | 59 | context('when transfer is called', function () { 60 | beforeEach(async function () { 61 | await gsn.fundRecipient(web3, {recipient: ngnt.address}); 62 | this.relayHub = await IRelayHub.at('0xD216153c06E857cD7f72665E0aF1d7D82172F494'); 63 | 64 | await ngnt.configureMinter(minter, minterAllowedAmount, {from: masterMinter}); 65 | await ngnt.mint(minter, mintedAmount, {from: minter}); 66 | }); 67 | 68 | it("should transfer the right amount of tokens", async () => { 69 | const transferAmount = mintedAmount - gsnFee; 70 | const recipient = accounts[2]; 71 | 72 | const recipientPreviousBalance = await ngnt.balanceOf(recipient); 73 | 74 | const {tx} = await ngnt.transfer(recipient, transferAmount, { 75 | from: minter, 76 | useGSN: true 77 | }); 78 | 79 | await expectEvent.inTransaction(tx, IRelayHub, 'TransactionRelayed', {status: '0'}); 80 | 81 | const senderNewBalance = await ngnt.balanceOf(minter); 82 | const recipientNewBalance = await ngnt.balanceOf(recipient); 83 | 84 | const expectedSenderNewBalance = new BN(mintedAmount - transferAmount - gsnFee); 85 | const expectedRecipientNewBalance = new BN(recipientPreviousBalance + transferAmount); 86 | 87 | 88 | expect(senderNewBalance).to.be.bignumber.equal(expectedSenderNewBalance); 89 | expect(recipientNewBalance).to.be.bignumber.equal(expectedRecipientNewBalance); 90 | }); 91 | 92 | context('when the sender does not have enough tokens', async () => { 93 | 94 | it("should reject the transaction", async () => { 95 | const transferAmount = mintedAmount; // will be less than required because of gsnFee 96 | const recipient = accounts[2]; 97 | 98 | expect(async function () { 99 | await ngnt.transfer(recipient, transferAmount, { 100 | from: minter, 101 | useGSN: true 102 | }).to.throw(); 103 | }) 104 | }); 105 | }); 106 | }); 107 | 108 | 109 | context('when approve is called', function () { 110 | beforeEach(async function () { 111 | mintedAmount = gsnFee; 112 | await gsn.fundRecipient(web3, {recipient: ngnt.address}); 113 | this.relayHub = await IRelayHub.at('0xD216153c06E857cD7f72665E0aF1d7D82172F494'); 114 | 115 | await ngnt.configureMinter(minter, minterAllowedAmount, {from: masterMinter}); 116 | await ngnt.mint(minter, mintedAmount, {from: minter}); 117 | }); 118 | 119 | context("should approve transactions without issues", async () => { 120 | let approverPreviousBalance; 121 | let approverNewBalance; 122 | 123 | it("should approve spender without any issues", async () => { 124 | const allowedAmount = 1000; 125 | const spender = accounts[4]; 126 | 127 | approverPreviousBalance = await ngnt.balanceOf(minter); 128 | 129 | const {tx} = await ngnt.approve(spender, allowedAmount, { 130 | from: minter, 131 | useGSN: true 132 | }); 133 | 134 | await expectEvent.inTransaction(tx, IRelayHub, 'TransactionRelayed', {status: '0'}); 135 | 136 | approverNewBalance = await ngnt.balanceOf(minter); 137 | const allowance = await ngnt.allowance(minter, spender); 138 | 139 | expect(allowance).to.be.bignumber.equal(allowance); 140 | }); 141 | 142 | it('should charge approver', async () => { 143 | const approvalCharge = approverPreviousBalance - approverNewBalance; 144 | expect(approvalCharge).to.equal(gsnFee); 145 | }) 146 | }); 147 | 148 | context('when the user does not have up to gsnFee', async () => { 149 | beforeEach(async function () { 150 | mintedAmount = gsnFee - 1; 151 | await gsn.fundRecipient(web3, {recipient: ngnt.address}); 152 | this.relayHub = await IRelayHub.at('0xD216153c06E857cD7f72665E0aF1d7D82172F494'); 153 | 154 | await ngnt.configureMinter(minter, minterAllowedAmount, {from: masterMinter}); 155 | await ngnt.mint(minter, mintedAmount, {from: minter}); 156 | }); 157 | 158 | it("should reject the transaction", async () => { 159 | const allowedAmount = 1000; 160 | const spender = accounts[4]; 161 | 162 | expect(async function () { 163 | await ngnt.approve(spender, allowedAmount, { 164 | from: minter, 165 | useGSN: true 166 | }).to.throw(); 167 | }) 168 | }); 169 | }); 170 | }); 171 | 172 | context('when a function that is not approve, transfer or transferFrom is called', function () { 173 | beforeEach(async function () { 174 | await gsn.fundRecipient(web3, {recipient: ngnt.address}); 175 | this.relayHub = await IRelayHub.at('0xD216153c06E857cD7f72665E0aF1d7D82172F494'); 176 | }); 177 | 178 | it("should work without GSN", async () => { 179 | expect(async function () { 180 | await ngnt.totalSupply({ 181 | from: minter 182 | }).to.not.throw(); 183 | }) 184 | }); 185 | 186 | it("should reject the transaction with GSN", async () => { 187 | expect(async function () { 188 | await ngnt.totalSupply({ 189 | from: minter, 190 | useGSN: true 191 | }).to.throw(); 192 | }) 193 | }); 194 | }); 195 | 196 | }); 197 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV !== 'production') { 2 | require('dotenv').config(); 3 | } 4 | 5 | const HDNode = require('ethers').utils.HDNode; 6 | const mnemonic = process.env.DEV_MNEMONIC; 7 | const masterNode = HDNode.fromMnemonic(mnemonic); 8 | 9 | 10 | const initializeFunctionParameters = [{ 11 | "constant": true, 12 | "inputs": [], 13 | "name": "totalSupply", 14 | "outputs": [ 15 | { 16 | "name": "", 17 | "type": "uint256" 18 | } 19 | ], 20 | "payable": false, 21 | "stateMutability": "view", 22 | "type": "function" 23 | } 24 | ]; 25 | 26 | 27 | function getSigningKey(index){ 28 | const derivationPath = "m/44'/60'/0'/0/" + index.toString(); 29 | const addressNode = masterNode.derivePath(derivationPath); 30 | return addressNode.privateKey; 31 | } 32 | 33 | module.exports = { 34 | initializeFunctionParameters, 35 | getSigningKey 36 | }; 37 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | const {GSNProvider} = require("@openzeppelin/gsn-provider"); 2 | 3 | 4 | module.exports = { 5 | networks: { 6 | development: { 7 | provider: function () { 8 | return new GSNProvider("http://localhost:8545", {useGSN: false}) 9 | }, 10 | network_id: "*", 11 | gas: 4600000 12 | }, 13 | }, 14 | 15 | compilers: { 16 | solc: { 17 | version: "0.5.5", 18 | docker: false, 19 | settings: { 20 | optimizer: { 21 | enabled: true, 22 | runs: 200 23 | }, 24 | evmVersion: "byzantium" 25 | } 26 | } 27 | } 28 | } 29 | --------------------------------------------------------------------------------