Address Contract Verified
Address
0x3Cf6a3B3eD708D829bC87F47e5175779e7ABC03F
Balance
0 ETH
Nonce
1
Code Size
13392 bytes
Creator
0x0b93FDA6...F3D4 at tx 0xec92bcdc...a127dd
Indexed Transactions
0
Contract Bytecode
13392 bytes
0x6080604052600436106101c65760003560e01c80638da2f6d8116100f7578063b677bf0e11610095578063e695f75c11610064578063e695f75c14610562578063f2fde38b14610582578063f4d08412146105a2578063f8b823e4146105c257600080fd5b8063b677bf0e146104e2578063d71465aa14610502578063dae3d6bb14610522578063df6557ec1461054257600080fd5b8063946647f1116100d1578063946647f11461046257806397aa096e146104825780639af8a581146104a2578063a4b519ff146104c257600080fd5b80638da2f6d8146104115780638da5cb5b1461043157806392cddb911461044f57600080fd5b8063333ec0621161016457806360be74e61161013e57806360be74e61461039c578063715018a6146103bc5780637abff543146103d157806381fe66ce146103f157600080fd5b8063333ec062146103495780634465549a1461035c578063477fa2701461037c57600080fd5b806316ac30a9116101a057806316ac30a9146102aa5780632e2f0ca0146102ca5780632fae95bf146102ea5780633085df631461032957600080fd5b80630917377f146102275780630a9157c4146102695780630e48b8db1461028957600080fd5b3661022257600854600160a01b900460ff16806101e1575034155b6102205760405162461bcd60e51b815260206004820152600b60248201526a4e6f6e2d70617961626c6560a81b60448201526064015b60405180910390fd5b005b600080fd5b34801561023357600080fd5b5060045461024c9061010090046001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561027557600080fd5b5060025461024c906001600160a01b031681565b61029c610297366004612c4c565b6105f7565b604051908152602001610260565b3480156102b657600080fd5b506102206102c5366004612cb3565b610610565b3480156102d657600080fd5b5060075461024c906001600160a01b031681565b3480156102f657600080fd5b5060035461031190600160c01b90046001600160401b031681565b6040516001600160401b039091168152602001610260565b34801561033557600080fd5b5061024c610344366004612ce3565b610640565b61029c610357366004612c4c565b610684565b34801561036857600080fd5b50610220610377366004612d05565b610694565b34801561038857600080fd5b50600354600160a01b900461ffff1661029c565b3480156103a857600080fd5b5060085461024c906001600160a01b031681565b3480156103c857600080fd5b506102206106be565b3480156103dd57600080fd5b506102206103ec366004612d05565b6106d2565b3480156103fd57600080fd5b5061022061040c366004612d05565b6106fc565b34801561041d57600080fd5b5061022061042c366004612d05565b610726565b34801561043d57600080fd5b506000546001600160a01b031661024c565b61022061045d366004612d22565b610750565b34801561046e57600080fd5b5060035461024c906001600160a01b031681565b34801561048e57600080fd5b5060055461024c906001600160a01b031681565b3480156104ae57600080fd5b5061029c6104bd366004612d22565b610981565b3480156104ce57600080fd5b5061029c6104dd366004612d22565b61099c565b3480156104ee57600080fd5b5061029c6104fd366004612d22565b6109ad565b34801561050e57600080fd5b5061022061051d366004612da5565b6109be565b34801561052e57600080fd5b5060015461024c906001600160a01b031681565b34801561054e57600080fd5b5061022061055d366004612df6565b610c9b565b34801561056e57600080fd5b5061022061057d366004612d05565b610d12565b34801561058e57600080fd5b5061022061059d366004612d05565b610d3c565b3480156105ae57600080fd5b506102206105bd366004612e2f565b610db5565b3480156105ce57600080fd5b506003546105e490600160a01b900461ffff1681565b60405161ffff9091168152602001610260565b6000610607858585600086610e42565b95945050505050565b61061861118b565b600380546001600160401b03909216600160c01b026001600160c01b03909216919091179055565b6006828154811061065057600080fd5b90600052602060002001818154811061066857600080fd5b6000918252602090912001546001600160a01b03169150829050565b60006106078585856000866111e5565b61069c61118b565b600180546001600160a01b0319166001600160a01b0392909216919091179055565b6106c661118b565b6106d06000611557565b565b6106da61118b565b600880546001600160a01b0319166001600160a01b0392909216919091179055565b61070461118b565b600780546001600160a01b0319166001600160a01b0392909216919091179055565b61072e61118b565b600280546001600160a01b0319166001600160a01b0392909216919091179055565b600684106107a05760405162461bcd60e51b815260206004820152601760248201527f6d6f7265207468616e2035206d65746144657461696c730000000000000000006044820152606401610217565b6000805b8581101561097857368787838181106107bf576107bf612e53565b90506020028101906107d19190612e69565b90508035600003610806576107f56107ec6020830183612e89565b858989896115a7565b6107ff9084612ee8565b9250610965565b8035600103610828576107f561081f6020830183612e89565b888887896119ad565b803560020361084a576107f56108416020830183612e89565b88888789611d0b565b8035600303610902578787610860600182612f01565b81811061086f5761086f612e53565b90506020028101906108819190612e69565b35600403610894576004805460ff191690555b6108ad6108a46020830183612e89565b871586886111e5565b6108b79084612ee8565b925087876108c6600182612f01565b8181106108d5576108d5612e53565b90506020028101906108e79190612e69565b356004036108fd576004805460ff191660011790555b610965565b8035600403610924576107f561091b6020830183612e89565b87158688610e42565b60405162461bcd60e51b815260206004820152601660248201527515dc9bdb99c81c185e5b595b9d13995d1ddbdc9ad25960521b6044820152606401610217565b508061097081612f14565b9150506107a4565b50505050505050565b6000610992868686866000876119ad565b9695505050505050565b600061099286868686600087611d0b565b6000610992868660008787876115a7565b6109c661118b565b60008111610a0b5760405162461bcd60e51b81526020600482015260126024820152710416d6f756e74206d757374206265203e20360741b6044820152606401610217565b604051636eb1769f60e11b81526001600160a01b0384811660048301523060248301526000919086169063dd62ed3e90604401602060405180830381865afa158015610a5b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a7f9190612f2d565b6040516370a0823160e01b81526001600160a01b0386811660048301529192506000918716906370a0823190602401602060405180830381865afa158015610acb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610aef9190612f2d565b905080831115610b4d5760405162461bcd60e51b8152602060048201526024808201527f496e73756666696369656e742062616c616e6365206f662066726f6d206163636044820152631bdd5b9d60e21b6064820152608401610217565b81831115610bae5760405162461bcd60e51b815260206004820152602860248201527f496e73756666696369656e7420616c6c6f77616e63652066726f6d2066726f6d604482015267081858d8dbdd5b9d60c21b6064820152608401610217565b6040516323b872dd60e01b81526001600160a01b038681166004830152306024830152604482018590528716906323b872dd906064016020604051808303816000875af1158015610c03573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c279190612f46565b5060405163a9059cbb60e01b81526001600160a01b0385811660048301526024820185905287169063a9059cbb906044016020604051808303816000875af1158015610c77573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109789190612f46565b610ca361118b565b600580546001600160a01b0319166001600160a01b0384811691821790925560048054610100600160a81b03191661010085851681029190911791829055604080516060810182526020810194855291909204909316908301528152610d0d906006906001612ad6565b505050565b610d1a61118b565b600380546001600160a01b0319166001600160a01b0392909216919091179055565b610d4461118b565b6001600160a01b038116610da95760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610217565b610db281611557565b50565b610dbd61118b565b60c88161ffff161115610e205760405162461bcd60e51b815260206004820152602560248201527f546865206261746368206665652076616c756520697320746f6f20686967683a604482015264203e20322560d81b6064820152608401610217565b6003805461ffff909216600160a01b0261ffff60a01b19909216919091179055565b6008805460ff60a01b1916600160a01b179055600047815b86811015610f3d5736888883818110610e7557610e75612e53565b9050602002810190610e879190612f63565b6008549091506001600160a01b031663ac473c8a47610ea96020850185612d05565b6020850135610ebb6040870187612e89565b610ec86060890189612f79565b89608001358e8b60c001356040518b63ffffffff1660e01b8152600401610ef799989796959493929190613031565b6000604051808303818588803b158015610f1057600080fd5b505af1158015610f24573d6000803e3d6000fd5b5050505050508080610f3590612f14565b915050610e5a565b5060035460009061ffff600160b01b8204811691600160a01b900416610f634785612f01565b610f6d919061308e565b610f7791906130a5565b905085151560000361106f5761106a816006600081548110610f9b57610f9b612e53565b90600052602060002001600081548110610fb757610fb7612e53565b60009182526020808320909101546006805460408051828602810186019091528181526001600160a01b03909316948c9484015b828210156110615760008481526020908190208301805460408051828502810185019091528181529283018282801561104d57602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161102f575b505050505081526020019060010190610feb565b50505050611ff6565b955090505b804710156110d25760405162461bcd60e51b815260206004820152602a60248201527f4e6f7420656e6f7567682066756e647320666f7220626174636820636f6e76656044820152697273696f6e206665657360b01b6064820152608401610217565b6040516001600160a01b0385169082156108fc029083906000818181858888f19350505050158015611108573d6000803e3d6000fd5b50604051600090339047908381818185875af1925050503d806000811461114b576040519150601f19603f3d011682016040523d82523d6000602084013e611150565b606091505b50509050806111715760405162461bcd60e51b8152600401610217906130c7565b50506008805460ff60a01b19169055509195945050505050565b6000546001600160a01b031633146106d05760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610217565b600080805b86811015611327573688888381811061120557611205612e53565b90506020028101906112179190612f63565b905061122b60808201356020830135612ee8565b47101561126d5760405162461bcd60e51b815260206004820152601060248201526f4e6f7420656e6f7567682066756e647360801b6044820152606401610217565b61127b602082013584612ee8565b6002549093506001600160a01b031663b868980b6112a160808401356020850135612ee8565b6112ae6020850185612d05565b6112bb6060860186612f79565b86608001358b6040518763ffffffff1660e01b81526004016112e1959493929190613112565b6000604051808303818588803b1580156112fa57600080fd5b505af115801561130e573d6000803e3d6000fd5b505050505050808061131f90612f14565b9150506111ea565b5060035461ffff600160b01b820481169161134b91600160a01b909104168361308e565b61135591906130a5565b90508415156000036114445761143f81600660008154811061137957611379612e53565b9060005260206000200160008154811061139557611395612e53565b60009182526020808320909101546006805460408051828602810186019091528181526001600160a01b03909316948b9484015b828210156110615760008481526020908190208301805460408051828502810185019091528181529283018282801561142b57602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161140d575b5050505050815260200190600101906113c9565b945090505b804710156114945760405162461bcd60e51b815260206004820152601e60248201527f4e6f7420656e6f7567682066756e647320666f722062617463682066656500006044820152606401610217565b6040516001600160a01b0384169082156108fc029083906000818181858888f193505050501580156114ca573d6000803e3d6000fd5b5060045460ff1680156114dd5750600047115b1561154c57604051600090339047908381818185875af1925050503d8060008114611524576040519150601f19603f3d011682016040523d82523d6000602084013e611529565b606091505b505090508061154a5760405162461bcd60e51b8152600401610217906130c7565b505b509195945050505050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6000806115b48888612258565b90506000805b8251811080156115e7575060008382815181106115d9576115d9612e53565b602002602001015160200151115b156116e157600354835161ffff600160b01b8304811692600160a01b9004169085908490811061161957611619612e53565b60200260200101516020015161162f919061308e565b61163991906130a5565b83828151811061164b5761164b612e53565b6020026020010151604001818152505082818151811061166d5761166d612e53565b60200260200101516000015191506116cf8284838151811061169157611691612e53565b6020026020010151602001518584815181106116af576116af612e53565b6020908102919091010151604001516007546001600160a01b03166125f0565b806116d981612f14565b9150506115ba565b5060005b888110156117cd57368a8a8381811061170057611700612e53565b90506020028101906117129190612f63565b6007549091506001600160a01b0316633af2c0126117336020840184612d05565b60208401356117456040860186612e89565b6117526060880188612f79565b88608001358e8a60a001358b60c001356040518b63ffffffff1660e01b81526004016117879a9998979695949392919061314f565b600060405180830381600087803b1580156117a157600080fd5b505af11580156117b5573d6000803e3d6000fd5b505050505080806117c590612f14565b9150506116e5565b5060005b8251811080156117fe575060008382815181106117f0576117f0612e53565b602002602001015160200151115b1561199f5782818151811061181557611815612e53565b6020908102919091010151516040516370a0823160e01b81523060048201529092506000906001600160a01b038416906370a0823190602401602060405180830381865afa15801561186b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061188f9190612f2d565b905080156118ad576118ab6001600160a01b038416338361285e565b505b600354845160009161ffff600160b01b8204811692600160a01b909204169084908890879081106118e0576118e0612e53565b6020026020010151602001516118f69190612f01565b611900919061308e565b61190a91906130a5565b905061193e8186858151811061192257611922612e53565b6020908102919091010151518c6119398c8e613226565b611ff6565b809b50819250505061196e85848151811061195b5761195b612e53565b6020026020010151600001518883612927565b61198a5760405162461bcd60e51b8152600401610217906132ec565b5050808061199790612f14565b9150506117d1565b509598975050505050505050565b60008080805b88811015611a67578989828181106119cd576119cd612e53565b90506020028101906119df9190612f63565b608001358a8a838181106119f5576119f5612e53565b9050602002810190611a079190612f63565b60200135611a159190612ee8565b611a1f9084612ee8565b9250898982818110611a3357611a33612e53565b9050602002810190611a459190612f63565b611a53906020013583612ee8565b915080611a5f81612f14565b9150506119b3565b5060035461ffff600160b01b8204811691611a8b91600160a01b909104168361308e565b611a9591906130a5565b9050611aff818a8a6000818110611aae57611aae612e53565b9050602002810190611ac09190612f63565b611ace906040810190612e89565b6000818110611adf57611adf612e53565b9050602002016020810190611af49190612d05565b876119398a8c613226565b95509050600089898281611b1557611b15612e53565b9050602002810190611b279190612f63565b611b35906040810190612e89565b6000818110611b4657611b46612e53565b9050602002016020810190611b5b9190612d05565b600154909150611b79908290859085906001600160a01b03166125f0565b611bdc8a8a6000818110611b8f57611b8f612e53565b9050602002810190611ba19190612f63565b611baf906040810190612e89565b6000818110611bc057611bc0612e53565b9050602002016020810190611bd59190612d05565b8684612927565b611bf85760405162461bcd60e51b8152600401610217906132ec565b60005b89811015611cfc57368b8b83818110611c1657611c16612e53565b9050602002810190611c289190612f63565b6001549091506001600160a01b031663c219a14d611c496040840184612e89565b6000818110611c5a57611c5a612e53565b9050602002016020810190611c6f9190612d05565b611c7c6020850185612d05565b6020850135611c8e6060870187612f79565b87608001358e6040518863ffffffff1660e01b8152600401611cb69796959493929190613323565b600060405180830381600087803b158015611cd057600080fd5b505af1158015611ce4573d6000803e3d6000fd5b50505050508080611cf490612f14565b915050611bfb565b50949998505050505050505050565b600080611d188888612258565b905060005b815181108015611d4a57506000828281518110611d3c57611d3c612e53565b602002602001015160200151115b15611ee457600354825161ffff600160b01b8304811692600160a01b90041690849084908110611d7c57611d7c612e53565b602002602001015160400151611d92919061308e565b611d9c91906130a5565b828281518110611dae57611dae612e53565b602002602001015160400181815250506000828281518110611dd257611dd2612e53565b6020026020010151600001519050611e3481848481518110611df657611df6612e53565b602002602001015160200151858581518110611e1457611e14612e53565b6020908102919091010151604001516001546001600160a01b03166125f0565b6000838381518110611e4857611e48612e53565b6020026020010151604001519050611e8381858581518110611e6c57611e6c612e53565b602090810291909101015151896119398c8e613226565b8098508192505050611eb3848481518110611ea057611ea0612e53565b6020026020010151600001518783612927565b611ecf5760405162461bcd60e51b8152600401610217906132ec565b50508080611edc90612f14565b915050611d1d565b5060005b87811015611fe95736898983818110611f0357611f03612e53565b9050602002810190611f159190612f63565b6001549091506001600160a01b031663c219a14d611f366040840184612e89565b6000818110611f4757611f47612e53565b9050602002016020810190611f5c9190612d05565b611f696020850185612d05565b6020850135611f7b6060870187612f79565b87608001358c6040518863ffffffff1660e01b8152600401611fa39796959493929190613323565b600060405180830381600087803b158015611fbd57600080fd5b505af1158015611fd1573d6000803e3d6000fd5b50505050508080611fe190612f14565b915050611ee8565b5092979650505050505050565b6000808251600014801561201b5750600354600160c01b90046001600160401b031684105b1561202a57508490508261224f565b600354600160c01b90046001600160401b03168410156122445760005b835181101561223e57856001600160a01b031684828151811061206c5761206c612e53565b602002602001015160008151811061208657612086612e53565b60200260200101516001600160a01b03161480156121225750600460019054906101000a90046001600160a01b03166001600160a01b03168482815181106120d0576120d0612e53565b602002602001015160018684815181106120ec576120ec612e53565b6020026020010151516120ff9190612f01565b8151811061210f5761210f612e53565b60200260200101516001600160a01b0316145b1561222c5760035484516000916001600160a01b03169063fbd4122a908a9088908690811061215357612153612e53565b60200260200101516040518363ffffffff1660e01b8152600401612178929190613370565b6040805180830381865afa158015612194573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121b891906133c7565b506003549091508190600160c01b90046001600160401b03166121db8289612ee8565b1115612219576003546121ff908890600160c01b90046001600160401b0316612f01565b90508161220c828b61308e565b61221691906130a5565b98505b6122238188612ee8565b9650505061223e565b8061223681612f14565b915050612047565b50612249565b600095505b50849050825b94509492505050565b6060816001600160401b03811115612272576122726131bd565b6040519080825280602002602001820160405280156122d057816020015b6122bd604051806060016040528060006001600160a01b0316815260200160008152602001600081525090565b8152602001906001900390816122905790505b50905060005b828110156125e95760005b838110156125d657368585848181106122fc576122fc612e53565b905060200281019061230e9190612f63565b905061231d6040820182612e89565b600161232c6040850185612e89565b612337929150612f01565b81811061234657612346612e53565b905060200201602081019061235b9190612d05565b6001600160a01b031684838151811061237657612376612e53565b6020026020010151600001516001600160a01b03160361245957600161239f6040830183612e89565b905011156123de578060a001358483815181106123be576123be612e53565b60200260200101516020018181516123d69190612ee8565b905250612453565b6123f060808201356020830135612ee8565b84838151811061240257612402612e53565b602002602001015160200181815161241a9190612ee8565b905250835160208201359085908490811061243757612437612e53565b602002602001015160400181815161244f9190612ee8565b9052505b506125d6565b83828151811061246b5761246b612e53565b60200260200101516020015160001480156124a5575060008160a0013511806124a5575060006124a360808301356020840135612ee8565b115b156125c3576124b76040820182612e89565b60016124c66040850185612e89565b6124d1929150612f01565b8181106124e0576124e0612e53565b90506020020160208101906124f59190612d05565b84838151811061250757612507612e53565b60209081029190910101516001600160a01b039091169052600161252e6040830183612e89565b90501115612562578060a0013584838151811061254d5761254d612e53565b60200260200101516020018181525050612453565b61257460808201356020830135612ee8565b84838151811061258657612586612e53565b6020026020010151602001818152505080602001358483815181106125ad576125ad612e53565b60200260200101516040018181525050506125d6565b50806125ce81612f14565b9150506122e1565b50806125e181612f14565b9150506122d6565b5092915050565b604051636eb1769f60e11b815233600482015230602482015283906001600160a01b0386169063dd62ed3e90604401602060405180830381865afa15801561263c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126609190612f2d565b10156126be5760405162461bcd60e51b815260206004820152602760248201527f496e73756666696369656e7420616c6c6f77616e636520666f7220626174636860448201526620746f2070617960c81b6064820152608401610217565b6126c88284612ee8565b6040516370a0823160e01b81523360048201526001600160a01b038616906370a0823190602401602060405180830381865afa15801561270c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127309190612f2d565b101561277e5760405162461bcd60e51b815260206004820181905260248201527f4e6f7420656e6f7567682066756e64732c20696e636c7564696e6720666565736044820152606401610217565b612789843085612927565b6127d55760405162461bcd60e51b815260206004820152601d60248201527f7061796d656e74207472616e7366657246726f6d2829206661696c65640000006044820152606401610217565b604051636eb1769f60e11b81523060048201526001600160a01b03828116602483015284919086169063dd62ed3e90604401602060405180830381865afa158015612824573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128489190612f2d565b1015612858576128588482612a5a565b50505050565b6040516001600160a01b03838116602483015260448201839052600091829182919087169060640160408051601f198184030181529181526020820180516001600160e01b031663a9059cbb60e01b179052516128bb91906133eb565b6000604051808303816000865af19150503d80600081146128f8576040519150601f19603f3d011682016040523d82523d6000602084013e6128fd565b606091505b50915091508180156109925750805115806109925750808060200190518101906109929190612f46565b6000833b61293457600080fd5b6040513360248201526001600160a01b038481166044830152606482018490526000919086169060840160408051601f198184030181529181526020820180516001600160e01b03166323b872dd60e01b1790525161299391906133eb565b6000604051808303816000865af19150503d80600081146129d0576040519150601f19603f3d011682016040523d82523d6000602084013e6129d5565b606091505b505090503d600081146129ef57602081146129f857600080fd5b60019250612a04565b60206000803e60005192505b5080612a525760405162461bcd60e51b815260206004820181905260248201527f7472616e7366657246726f6d282920686173206265656e2072657665727465646044820152606401610217565b509392505050565b81600019612a726001600160a01b0383168483612a79565b5050505050565b6040516001600160a01b03838116602483015260448201839052600091829182919087169060640160408051601f198184030181529181526020820180516001600160e01b031663095ea7b360e01b179052516128bb91906133eb565b828054828255906000526020600020908101928215612b1d579160200282015b82811115612b1d578251612b0d9083906002612b2d565b5091602001919060010190612af6565b50612b29929150612b8e565b5090565b828054828255906000526020600020908101928215612b82579160200282015b82811115612b8257825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190612b4d565b50612b29929150612bab565b80821115612b29576000612ba28282612bc0565b50600101612b8e565b5b80821115612b295760008155600101612bac565b5080546000825590600052602060002090810190610db29190612bab565b60008083601f840112612bf057600080fd5b5081356001600160401b03811115612c0757600080fd5b6020830191508360208260051b8501011115612c2257600080fd5b9250929050565b8015158114610db257600080fd5b6001600160a01b0381168114610db257600080fd5b60008060008060608587031215612c6257600080fd5b84356001600160401b03811115612c7857600080fd5b612c8487828801612bde565b9095509350506020850135612c9881612c29565b91506040850135612ca881612c37565b939692955090935050565b600060208284031215612cc557600080fd5b81356001600160401b0381168114612cdc57600080fd5b9392505050565b60008060408385031215612cf657600080fd5b50508035926020909101359150565b600060208284031215612d1757600080fd5b8135612cdc81612c37565b600080600080600060608688031215612d3a57600080fd5b85356001600160401b0380821115612d5157600080fd5b612d5d89838a01612bde565b90975095506020880135915080821115612d7657600080fd5b50612d8388828901612bde565b9094509250506040860135612d9781612c37565b809150509295509295909350565b60008060008060808587031215612dbb57600080fd5b8435612dc681612c37565b93506020850135612dd681612c37565b92506040850135612de681612c37565b9396929550929360600135925050565b60008060408385031215612e0957600080fd5b8235612e1481612c37565b91506020830135612e2481612c37565b809150509250929050565b600060208284031215612e4157600080fd5b813561ffff81168114612cdc57600080fd5b634e487b7160e01b600052603260045260246000fd5b60008235603e19833603018112612e7f57600080fd5b9190910192915050565b6000808335601e19843603018112612ea057600080fd5b8301803591506001600160401b03821115612eba57600080fd5b6020019150600581901b3603821315612c2257600080fd5b634e487b7160e01b600052601160045260246000fd5b80820180821115612efb57612efb612ed2565b92915050565b81810381811115612efb57612efb612ed2565b600060018201612f2657612f26612ed2565b5060010190565b600060208284031215612f3f57600080fd5b5051919050565b600060208284031215612f5857600080fd5b8151612cdc81612c29565b6000823560de19833603018112612e7f57600080fd5b6000808335601e19843603018112612f9057600080fd5b8301803591506001600160401b03821115612faa57600080fd5b602001915036819003821315612c2257600080fd5b8183526000602080850194508260005b85811015612ffd578135612fe281612c37565b6001600160a01b031687529582019590820190600101612fcf565b509495945050505050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b600060018060a01b03808c1683528a602084015260e0604084015261305a60e084018a8c612fbf565b838103606085015261306d81898b613008565b6080850197909752509390931660a082015260c00152509695505050505050565b8082028115828204841417612efb57612efb612ed2565b6000826130c257634e487b7160e01b600052601260045260246000fd5b500490565b6020808252602b908201527f436f756c64206e6f742073656e642072656d61696e696e672066756e6473207460408201526a37903a3432903830bcb2b960a91b606082015260800190565b600060018060a01b03808816835260806020840152613135608084018789613008565b604084019590955292909216606090910152509392505050565b6001600160a01b038b81168252602082018b90526101006040830181905260009161317d8483018c8e612fbf565b91508382036060850152613192828a8c613008565b60808501989098529590951660a08301525060c081019290925260e090910152509695505050505050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b03811182821017156131fb576131fb6131bd565b604052919050565b60006001600160401b0382111561321c5761321c6131bd565b5060051b60200190565b600061323961323484613203565b6131d3565b83815260208082019190600586811b86013681111561325757600080fd5b865b81811015611fe95780356001600160401b038111156132785760008081fd5b880136601f82011261328a5760008081fd5b803561329861323482613203565b81815290851b820186019086810190368311156132b55760008081fd5b928701925b828410156132dc5783356132cd81612c37565b825292870192908701906132ba565b8952505050948301948301613259565b6020808252601f908201527f426174636820666565207472616e7366657246726f6d2829206661696c656400604082015260600190565b600060018060a01b03808a168352808916602084015287604084015260c0606084015261335460c084018789613008565b60808401959095529290921660a0909101525095945050505050565b6000604082018483526020604081850152818551808452606086019150828701935060005b818110156133ba5784516001600160a01b031683529383019391830191600101613395565b5090979650505050505050565b600080604083850312156133da57600080fd5b505080516020909101519092909150565b6000825160005b8181101561340c57602081860181015185830152016133f2565b50600092019182525091905056fea2646970667358221220fb655730fa25ca9a1e87a028aaaec40e44023e8c1110a46a7bb88ec0d360fbc864736f6c63430008140033
Verified Source Code Full Match
Compiler: v0.8.20+commit.a1b79de6
EVM: paris
Optimization: Yes (200 runs)
Ownable.sol 83 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
ERC20.sol 365 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20 is Context, IERC20, IERC20Metadata {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual override returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
* - the caller must have allowance for ``from``'s tokens of at least
* `amount`.
*/
function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
return true;
}
/**
* @dev Moves `amount` of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
*/
function _transfer(address from, address to, uint256 amount) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
// Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
// decrementing then incrementing.
_balances[to] += amount;
}
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
unchecked {
// Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
// Overflow not possible: amount <= accountBalance <= totalSupply.
_totalSupply -= amount;
}
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(address owner, address spender, uint256 amount) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `amount`.
*
* Does not update the allowance amount in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Might emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}
IERC20Metadata.sol 28 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
IERC20.sol 78 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
Context.sol 28 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
BatchConversionPayments.sol 386 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./interfaces/IERC20ConversionProxy.sol";
import "./interfaces/IEthConversionProxy.sol";
import "./BatchNoConversionPayments.sol";
/**
* @title BatchConversionPayments
* @notice This contract makes multiple conversion payments with references, in one transaction:
* - on:
* - ERC20 tokens: using Erc20ConversionProxy and ERC20FeeProxy
* - Native tokens: (e.g. ETH) using EthConversionProxy and EthereumFeeProxy
* - to: multiple addresses
* - fees: conversion proxy fees and additional batch conversion fees are paid to the same address.
* batchPayments is the main function to batch all kinds of payments at once.
* If one transaction of the batch fails, all transactions are reverted.
* @dev batchPayments is the main function, but other batch payment functions are "public" in order to do
* gas optimization in some cases.
*/
contract BatchConversionPayments is BatchNoConversionPayments {
using SafeERC20 for IERC20;
IERC20ConversionProxy public paymentErc20ConversionProxy;
IEthConversionProxy public paymentNativeConversionProxy;
/** payerAuthorized is set to true to workaround the non-payable aspect in batch native conversion */
bool private payerAuthorized = false;
/**
* @dev Used by the batchPayments to handle information for heterogeneous batches, grouped by payment network:
* - paymentNetworkId: from 0 to 4, cf. `batchPayments()` method
* - requestDetails all the data required for conversion and no conversion requests to be paid
*/
struct MetaDetail {
uint256 paymentNetworkId;
RequestDetail[] requestDetails;
}
/**
* @param _paymentErc20Proxy The ERC20 payment proxy address to use.
* @param _paymentNativeProxy The native payment proxy address to use.
* @param _paymentErc20ConversionProxy The ERC20 Conversion payment proxy address to use.
* @param _paymentNativeConversionFeeProxy The native Conversion payment proxy address to use.
* @param _chainlinkConversionPath The address of the conversion path contract.
* @param _owner Owner of the contract.
*/
constructor(
address _paymentErc20Proxy,
address _paymentNativeProxy,
address _paymentErc20ConversionProxy,
address _paymentNativeConversionFeeProxy,
address _chainlinkConversionPath,
address _owner
)
BatchNoConversionPayments(
_paymentErc20Proxy,
_paymentNativeProxy,
_chainlinkConversionPath,
_owner
)
{
paymentErc20ConversionProxy = IERC20ConversionProxy(
_paymentErc20ConversionProxy
);
paymentNativeConversionProxy = IEthConversionProxy(
_paymentNativeConversionFeeProxy
);
}
/**
* This contract is non-payable.
* Making a Native payment with conversion requires the contract to accept incoming Native tokens.
* @dev See the end of `paymentNativeConversionProxy.transferWithReferenceAndFee` where the leftover is given back.
*/
receive() external payable override {
require(payerAuthorized || msg.value == 0, "Non-payable");
}
/**
* @notice Batch payments on different payment networks at once.
* @param metaDetails contains paymentNetworkId and requestDetails
* - batchMultiERC20ConversionPayments, paymentNetworkId=0
* - batchERC20Payments, paymentNetworkId=1
* - batchMultiERC20Payments, paymentNetworkId=2
* - batchNativePayments, paymentNetworkId=3
* - batchNativeConversionPayments, paymentNetworkId=4
* If metaDetails use paymentNetworkId = 4, it must be at the end of the list, or the transaction can be reverted.
* @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.
* For batch native, mock an array of array to apply the limit, e.g: [[]]
* Without paths, there is not limitation, neither for the batch native functions.
* @param feeAddress The address where fees should be paid.
* @dev Use pathsToUSD only if you are pretty sure the batch fees will higher than the
* USD limit batchFeeAmountUSDLimit, because it increase gas consumption.
* batchPayments only reduces gas consumption when using more than a single payment network.
* For single payment network payments, it is more efficient to use the suited batch function.
*/
function batchPayments(
MetaDetail[] calldata metaDetails,
address[][] calldata pathsToUSD,
address feeAddress
) external payable {
require(metaDetails.length < 6, "more than 5 metaDetails");
uint256 batchFeeAmountUSD = 0;
for (uint256 i = 0; i < metaDetails.length; i++) {
MetaDetail calldata metaDetail = metaDetails[i];
if (metaDetail.paymentNetworkId == 0) {
batchFeeAmountUSD += _batchMultiERC20ConversionPayments(
metaDetail.requestDetails,
batchFeeAmountUSD,
pathsToUSD,
feeAddress
);
} else if (metaDetail.paymentNetworkId == 1) {
batchFeeAmountUSD += _batchERC20Payments(
metaDetail.requestDetails,
pathsToUSD,
batchFeeAmountUSD,
payable(feeAddress)
);
} else if (metaDetail.paymentNetworkId == 2) {
batchFeeAmountUSD += _batchMultiERC20Payments(
metaDetail.requestDetails,
pathsToUSD,
batchFeeAmountUSD,
feeAddress
);
} else if (metaDetail.paymentNetworkId == 3) {
if (metaDetails[metaDetails.length - 1].paymentNetworkId == 4) {
// Set to false only if batchNativeConversionPayments is called after this function
transferBackRemainingNativeTokens = false;
}
batchFeeAmountUSD += _batchNativePayments(
metaDetail.requestDetails,
pathsToUSD.length == 0,
batchFeeAmountUSD,
payable(feeAddress)
);
if (metaDetails[metaDetails.length - 1].paymentNetworkId == 4) {
transferBackRemainingNativeTokens = true;
}
} else if (metaDetail.paymentNetworkId == 4) {
batchFeeAmountUSD += _batchNativeConversionPayments(
metaDetail.requestDetails,
pathsToUSD.length == 0,
batchFeeAmountUSD,
payable(feeAddress)
);
} else {
revert("Wrong paymentNetworkId");
}
}
}
/**
* @notice Send a batch of ERC20 payments with amounts based on a request
* currency (e.g. fiat), with fees and paymentReferences to multiple accounts, with multiple tokens.
* @param requestDetails List of ERC20 requests denominated in fiat to pay.
* @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.
* Without paths, there is not a fee limitation, and it consumes less gas.
* @param feeAddress The fee recipient.
*/
function batchMultiERC20ConversionPayments(
RequestDetail[] calldata requestDetails,
address[][] calldata pathsToUSD,
address feeAddress
) public returns (uint256) {
return
_batchMultiERC20ConversionPayments(
requestDetails,
0,
pathsToUSD,
feeAddress
);
}
/**
* @notice Send a batch of Native conversion payments with fees and paymentReferences to multiple accounts.
* If one payment fails, the whole batch is reverted.
* @param requestDetails List of native requests denominated in fiat to pay.
* @param skipFeeUSDLimit Setting the value to true skips the USD fee limit, and reduces gas consumption.
* @param feeAddress The fee recipient.
* @dev It uses NativeConversionProxy (EthereumConversionProxy) to pay an invoice and fees.
* Please:
* Note that if there is not enough Native token attached to the function call,
* the following error is thrown: "revert paymentProxy transferExactEthWithReferenceAndFee failed"
*/
function batchNativeConversionPayments(
RequestDetail[] calldata requestDetails,
bool skipFeeUSDLimit,
address payable feeAddress
) public payable returns (uint256) {
return
_batchNativeConversionPayments(
requestDetails,
skipFeeUSDLimit,
0,
feeAddress
);
}
/**
* @notice Send a batch of ERC20 payments with amounts based on a request
* currency (e.g. fiat), with fees and paymentReferences to multiple accounts, with multiple tokens.
* @param requestDetails List of ERC20 requests denominated in fiat to pay.
* @param batchFeeAmountUSD The batch fee amount in USD already paid.
* @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.
* Without paths, there is not a fee limitation, and it consumes less gas.
* @param feeAddress The fee recipient.
*/
function _batchMultiERC20ConversionPayments(
RequestDetail[] calldata requestDetails,
uint256 batchFeeAmountUSD,
address[][] calldata pathsToUSD,
address feeAddress
) private returns (uint256) {
Token[] memory uTokens = getUTokens(requestDetails);
IERC20 requestedToken;
// For each token: check allowance, transfer funds on the contract and approve the paymentProxy to spend if needed
for (
uint256 k = 0;
k < uTokens.length && uTokens[k].amountAndFee > 0;
k++
) {
uTokens[k].batchFeeAmount =
(uTokens[k].amountAndFee * batchFee) /
feeDenominator;
requestedToken = IERC20(uTokens[k].tokenAddress);
transferToContract(
requestedToken,
uTokens[k].amountAndFee,
uTokens[k].batchFeeAmount,
address(paymentErc20ConversionProxy)
);
}
// Batch pays the requests using Erc20ConversionFeeProxy
for (uint256 i = 0; i < requestDetails.length; i++) {
RequestDetail calldata rD = requestDetails[i];
paymentErc20ConversionProxy.transferFromWithReferenceAndFee(
rD.recipient,
rD.requestAmount,
rD.path,
rD.paymentReference,
rD.feeAmount,
feeAddress,
rD.maxToSpend,
rD.maxRateTimespan
);
}
// Batch sends back to the payer the tokens not spent and pays the batch fee
for (
uint256 k = 0;
k < uTokens.length && uTokens[k].amountAndFee > 0;
k++
) {
requestedToken = IERC20(uTokens[k].tokenAddress);
// Batch sends back to the payer the tokens not spent = excessAmount
// excessAmount = maxToSpend - reallySpent, which is equal to the remaining tokens on the contract
uint256 excessAmount = requestedToken.balanceOf(address(this));
if (excessAmount > 0) {
requestedToken.safeTransfer(msg.sender, excessAmount);
}
// Calculate batch fee to pay
uint256 batchFeeToPay = ((uTokens[k].amountAndFee - excessAmount) *
batchFee) / feeDenominator;
(batchFeeToPay, batchFeeAmountUSD) = calculateBatchFeeToPay(
batchFeeToPay,
uTokens[k].tokenAddress,
batchFeeAmountUSD,
pathsToUSD
);
// Payer pays the exact batch fees amount
require(
safeTransferFrom(
uTokens[k].tokenAddress,
feeAddress,
batchFeeToPay
),
"Batch fee transferFrom() failed"
);
}
return batchFeeAmountUSD;
}
/**
* @notice Send a batch of Native conversion payments with fees and paymentReferences to multiple accounts.
* If one payment fails, the whole batch is reverted.
* @param requestDetails List of native requests denominated in fiat to pay.
* @param skipFeeUSDLimit Setting the value to true skips the USD fee limit, and reduces gas consumption.
* @param batchFeeAmountUSD The batch fee amount in USD already paid.
* @param feeAddress The fee recipient.
* @dev It uses NativeConversionProxy (EthereumConversionProxy) to pay an invoice and fees.
* Please:
* Note that if there is not enough Native token attached to the function call,
* the following error is thrown: "revert paymentProxy transferExactEthWithReferenceAndFee failed"
*/
function _batchNativeConversionPayments(
RequestDetail[] calldata requestDetails,
bool skipFeeUSDLimit,
uint256 batchFeeAmountUSD,
address payable feeAddress
) private returns (uint256) {
uint256 contractBalance = address(this).balance;
payerAuthorized = true;
// Batch contract pays the requests through nativeConversionProxy
for (uint256 i = 0; i < requestDetails.length; i++) {
RequestDetail calldata rD = requestDetails[i];
paymentNativeConversionProxy.transferWithReferenceAndFee{
value: address(this).balance
}(
payable(rD.recipient),
rD.requestAmount,
rD.path,
rD.paymentReference,
rD.feeAmount,
feeAddress,
rD.maxRateTimespan
);
}
// Batch contract pays batch fee
uint256 batchFeeToPay = (((contractBalance - address(this).balance)) *
batchFee) / feeDenominator;
if (skipFeeUSDLimit == false) {
(batchFeeToPay, batchFeeAmountUSD) = calculateBatchFeeToPay(
batchFeeToPay,
pathsNativeToUSD[0][0],
batchFeeAmountUSD,
pathsNativeToUSD
);
}
require(
address(this).balance >= batchFeeToPay,
"Not enough funds for batch conversion fees"
);
feeAddress.transfer(batchFeeToPay);
// Batch contract transfers the remaining native tokens to the payer
(bool sendBackSuccess, ) = payable(msg.sender).call{
value: address(this).balance
}("");
require(sendBackSuccess, "Could not send remaining funds to the payer");
payerAuthorized = false;
return batchFeeAmountUSD;
}
/*
* Admin functions to edit the conversion proxies address and fees.
*/
/**
* @param _paymentErc20ConversionProxy The address of the ERC20 Conversion payment proxy to use.
* Update cautiously, the proxy has to match the invoice proxy.
*/
function setPaymentErc20ConversionProxy(
address _paymentErc20ConversionProxy
) external onlyOwner {
paymentErc20ConversionProxy = IERC20ConversionProxy(
_paymentErc20ConversionProxy
);
}
/**
* @param _paymentNativeConversionProxy The address of the native Conversion payment proxy to use.
* Update cautiously, the proxy has to match the invoice proxy.
*/
function setPaymentNativeConversionProxy(
address _paymentNativeConversionProxy
) external onlyOwner {
paymentNativeConversionProxy = IEthConversionProxy(
_paymentNativeConversionProxy
);
}
}
BatchNoConversionPayments.sol 712 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./lib/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./interfaces/ERC20FeeProxy.sol";
import "./interfaces/EthereumFeeProxy.sol";
import "./ChainlinkConversionPath.sol";
/**
* @title BatchNoConversionPayments
* @notice This contract makes multiple payments with references, in one transaction:
* - on: ERC20 Payment Proxy and Native (ETH) Payment Proxy of the Request Network protocol
* - to: multiple addresses
* - fees: ERC20 and Native (ETH) proxies fees are paid to the same address
* An additional batch fee is paid to the same address
* If one transaction of the batch fail, every transactions are reverted.
* @dev It is a clone of BatchPayment.sol, with three main modifications:
* - function "receive" has one other condition: payerAuthorized
* - fees are now divided by 10_000 instead of 1_000 in previous version
* - batch payment functions have new names and are now public, instead of external
*/
contract BatchNoConversionPayments is Ownable {
using SafeERC20 for IERC20;
IERC20FeeProxy public paymentErc20Proxy;
IEthereumFeeProxy public paymentNativeProxy;
ChainlinkConversionPath public chainlinkConversionPath;
/** Used to calculate batch fees: batchFee = 30 represent 0.30% of fee */
uint16 public batchFee = 0;
/** Used to calculate batch fees: divide batchFee by feeDenominator */
uint16 internal feeDenominator = 10000;
/** The amount of the batch fee cannot exceed a predefined amount in USD, e.g:
batchFeeAmountUSDLimit = 150 * 1e8 represents $150 */
uint64 public batchFeeAmountUSDLimit = 15000000000;
/** transferBackRemainingNativeTokens is set to false only if the payer use batchPayments
and call both batchNativePayments and batchNativeConversionPayments */
bool internal transferBackRemainingNativeTokens = true;
address public USDAddress = 0x775EB53d00DD0Acd3EC1696472105d579B9b386b;
address public NativeAddress = 0xF5AF88e117747e87fC5929F2ff87221B1447652E;
address[][] public pathsNativeToUSD;
/** Contains the address of a token, the sum of the amount and fees paid with it, and the batch fee amount */
struct Token {
address tokenAddress;
uint256 amountAndFee;
uint256 batchFeeAmount;
}
/**
* @dev All the information of a request, except the feeAddress
* recipient: Recipient address of the payment
* requestAmount: Request amount, in fiat for conversion payment
* path: Only for conversion payment: the conversion path
* paymentReference: Unique reference of the payment
* feeAmount: The fee amount, denominated in the first currency of `path` for conversion payment
* maxToSpend: Only for conversion payment:
* Maximum amount the payer wants to spend, denominated in the last currency of `path`:
* it includes fee proxy but NOT the batch fees to pay
* maxRateTimespan: Only for conversion payment:
* Max acceptable times span for conversion rates, ignored if zero
*/
struct RequestDetail {
address recipient;
uint256 requestAmount;
address[] path;
bytes paymentReference;
uint256 feeAmount;
uint256 maxToSpend;
uint256 maxRateTimespan;
}
/**
* @param _paymentErc20Proxy The address to the ERC20 fee payment proxy to use.
* @param _paymentNativeProxy The address to the Native fee payment proxy to use.
* @param _chainlinkConversionPath The address of the conversion path contract.
* @param _owner Owner of the contract.
*/
constructor(
address _paymentErc20Proxy,
address _paymentNativeProxy,
address _chainlinkConversionPath,
address _owner
) {
paymentErc20Proxy = IERC20FeeProxy(_paymentErc20Proxy);
paymentNativeProxy = IEthereumFeeProxy(_paymentNativeProxy);
chainlinkConversionPath = ChainlinkConversionPath(
_chainlinkConversionPath
);
transferOwnership(_owner);
batchFee = 0;
}
/**
* This contract is non-payable.
* @dev See the end of `paymentNativeProxy.transferWithReferenceAndFee` where the leftover is given back.
*/
receive() external payable virtual {
require(msg.value == 0, "Non-payable");
}
/**
* @notice Gets batch fee
*/
function getBatchFee() external view returns (uint256 data) {
return batchFee;
}
/**
* @notice Send a batch of Native token payments with fees and paymentReferences to multiple accounts.
* If one payment fails, the whole batch reverts.
* @param requestDetails List of Native tokens requests to pay.
* @param skipFeeUSDLimit Setting the value to true skips the USD fee limit, and reduces gas consumption.
* @param feeAddress The fee recipient.
* @dev It uses NativeFeeProxy (EthereumFeeProxy) to pay an invoice and fees with a payment reference.
* Make sure: msg.value >= sum(_amouts)+sum(_feeAmounts)+sumBatchFeeAmount
*/
function batchNativePayments(
RequestDetail[] calldata requestDetails,
bool skipFeeUSDLimit,
address payable feeAddress
) public payable returns (uint256) {
return
_batchNativePayments(
requestDetails,
skipFeeUSDLimit,
0,
payable(feeAddress)
);
}
/**
* @notice Send a batch of ERC20 payments with fees and paymentReferences to multiple accounts.
* @param requestDetails List of ERC20 requests to pay, with only one ERC20 token.
* @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.
* Without paths, there is not a fee limitation, and it consumes less gas.
* @param feeAddress The fee recipient.
* @dev Uses ERC20FeeProxy to pay an invoice and fees, with a payment reference.
* Make sure this contract has enough allowance to spend the payer's token.
* Make sure the payer has enough tokens to pay the amount, the fee, and the batch fee.
*/
function batchERC20Payments(
RequestDetail[] calldata requestDetails,
address[][] calldata pathsToUSD,
address feeAddress
) public returns (uint256) {
return _batchERC20Payments(requestDetails, pathsToUSD, 0, feeAddress);
}
/**
* @notice Send a batch of ERC20 payments with fees and paymentReferences to multiple accounts, with multiple tokens.
* @param requestDetails List of ERC20 requests to pay.
* @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.
* Without paths, there is not a fee limitation, and it consumes less gas.
* @param feeAddress The fee recipient.
* @dev It uses ERC20FeeProxy to pay an invoice and fees, with a payment reference.
* Make sure this contract has enough allowance to spend the payer's token.
* Make sure the payer has enough tokens to pay the amount, the fee, and the batch fee.
*/
function batchMultiERC20Payments(
RequestDetail[] calldata requestDetails,
address[][] calldata pathsToUSD,
address feeAddress
) public returns (uint256) {
return
_batchMultiERC20Payments(requestDetails, pathsToUSD, 0, feeAddress);
}
/**
* @notice Send a batch of Native token payments with fees and paymentReferences to multiple accounts.
* If one payment fails, the whole batch reverts.
* @param requestDetails List of Native tokens requests to pay.
* @param skipFeeUSDLimit Setting the value to true skips the USD fee limit, and reduces gas consumption.
* @param batchFeeAmountUSD The batch fee amount in USD already paid.
* @param feeAddress The fee recipient.
* @dev It uses NativeFeeProxy (EthereumFeeProxy) to pay an invoice and fees with a payment reference.
* Make sure: msg.value >= sum(_amouts)+sum(_feeAmounts)+sumBatchFeeAmount
*/
function _batchNativePayments(
RequestDetail[] calldata requestDetails,
bool skipFeeUSDLimit,
uint256 batchFeeAmountUSD,
address payable feeAddress
) internal returns (uint256) {
// amount is used to get the total amount and then used as batch fee amount
uint256 amount = 0;
// Batch contract pays the requests thourgh NativeFeeProxy (EthFeeProxy)
for (uint256 i = 0; i < requestDetails.length; i++) {
RequestDetail calldata rD = requestDetails[i];
require(
address(this).balance >= rD.requestAmount + rD.feeAmount,
"Not enough funds"
);
amount += rD.requestAmount;
paymentNativeProxy.transferWithReferenceAndFee{
value: rD.requestAmount + rD.feeAmount
}(
payable(rD.recipient),
rD.paymentReference,
rD.feeAmount,
payable(feeAddress)
);
}
// amount is updated into batch fee amount
amount = (amount * batchFee) / feeDenominator;
if (skipFeeUSDLimit == false) {
(amount, batchFeeAmountUSD) = calculateBatchFeeToPay(
amount,
pathsNativeToUSD[0][0],
batchFeeAmountUSD,
pathsNativeToUSD
);
}
// Check that batch contract has enough funds to pay batch fee
require(
address(this).balance >= amount,
"Not enough funds for batch fee"
);
// Batch pays batch fee
feeAddress.transfer(amount);
// Batch contract transfers the remaining Native tokens to the payer
if (transferBackRemainingNativeTokens && address(this).balance > 0) {
(bool sendBackSuccess, ) = payable(msg.sender).call{
value: address(this).balance
}("");
require(
sendBackSuccess,
"Could not send remaining funds to the payer"
);
}
return batchFeeAmountUSD;
}
/**
* @notice Send a batch of ERC20 payments with fees and paymentReferences to multiple accounts.
* @param requestDetails List of ERC20 requests to pay, with only one ERC20 token.
* @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.
* Without paths, there is not a fee limitation, and it consumes less gas.
* @param batchFeeAmountUSD The batch fee amount in USD already paid.
* @param feeAddress The fee recipient.
* @dev Uses ERC20FeeProxy to pay an invoice and fees, with a payment reference.
* Make sure this contract has enough allowance to spend the payer's token.
* Make sure the payer has enough tokens to pay the amount, the fee, and the batch fee.
*/
function _batchERC20Payments(
RequestDetail[] calldata requestDetails,
address[][] calldata pathsToUSD,
uint256 batchFeeAmountUSD,
address feeAddress
) internal returns (uint256) {
uint256 amountAndFee = 0;
uint256 batchFeeAmount = 0;
for (uint256 i = 0; i < requestDetails.length; i++) {
amountAndFee +=
requestDetails[i].requestAmount +
requestDetails[i].feeAmount;
batchFeeAmount += requestDetails[i].requestAmount;
}
batchFeeAmount = (batchFeeAmount * batchFee) / feeDenominator;
// batchFeeToPay and batchFeeAmountUSD are updated if needed
(batchFeeAmount, batchFeeAmountUSD) = calculateBatchFeeToPay(
batchFeeAmount,
requestDetails[0].path[0],
batchFeeAmountUSD,
pathsToUSD
);
IERC20 requestedToken = IERC20(requestDetails[0].path[0]);
transferToContract(
requestedToken,
amountAndFee,
batchFeeAmount,
address(paymentErc20Proxy)
);
// Payer pays batch fee amount
require(
safeTransferFrom(
requestDetails[0].path[0],
feeAddress,
batchFeeAmount
),
"Batch fee transferFrom() failed"
);
// Batch contract pays the requests using Erc20FeeProxy
for (uint256 i = 0; i < requestDetails.length; i++) {
RequestDetail calldata rD = requestDetails[i];
paymentErc20Proxy.transferFromWithReferenceAndFee(
rD.path[0],
rD.recipient,
rD.requestAmount,
rD.paymentReference,
rD.feeAmount,
feeAddress
);
}
return batchFeeAmountUSD;
}
/**
* @notice Send a batch of ERC20 payments with fees and paymentReferences to multiple accounts, with multiple tokens.
* @param requestDetails List of ERC20 requests to pay.
* @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.
* Without paths, there is not a fee limitation, and it consumes less gas.
* @param batchFeeAmountUSD The batch fee amount in USD already paid.
* @param feeAddress The fee recipient.
* @dev It uses ERC20FeeProxy to pay an invoice and fees, with a payment reference.
* Make sure this contract has enough allowance to spend the payer's token.
* Make sure the payer has enough tokens to pay the amount, the fee, and the batch fee.
*/
function _batchMultiERC20Payments(
RequestDetail[] calldata requestDetails,
address[][] calldata pathsToUSD,
uint256 batchFeeAmountUSD,
address feeAddress
) internal returns (uint256) {
Token[] memory uTokens = getUTokens(requestDetails);
// The payer transfers tokens to the batch contract and pays batch fee
for (
uint256 i = 0;
i < uTokens.length && uTokens[i].amountAndFee > 0;
i++
) {
uTokens[i].batchFeeAmount =
(uTokens[i].batchFeeAmount * batchFee) /
feeDenominator;
IERC20 requestedToken = IERC20(uTokens[i].tokenAddress);
transferToContract(
requestedToken,
uTokens[i].amountAndFee,
uTokens[i].batchFeeAmount,
address(paymentErc20Proxy)
);
// Payer pays batch fee amount
uint256 batchFeeToPay = uTokens[i].batchFeeAmount;
(batchFeeToPay, batchFeeAmountUSD) = calculateBatchFeeToPay(
batchFeeToPay,
uTokens[i].tokenAddress,
batchFeeAmountUSD,
pathsToUSD
);
require(
safeTransferFrom(
uTokens[i].tokenAddress,
feeAddress,
batchFeeToPay
),
"Batch fee transferFrom() failed"
);
}
// Batch contract pays the requests using Erc20FeeProxy
for (uint256 i = 0; i < requestDetails.length; i++) {
RequestDetail calldata rD = requestDetails[i];
paymentErc20Proxy.transferFromWithReferenceAndFee(
rD.path[0],
rD.recipient,
rD.requestAmount,
rD.paymentReference,
rD.feeAmount,
feeAddress
);
}
return batchFeeAmountUSD;
}
/*
* Helper functions
*/
/**
* Top up the contract with enough `requestedToken` to pay `amountAndFee`.
* The contract is NOT topped-up for `batchFeeAmount`.
*
* It also performs a few checks:
* - checks that the batch contract has enough allowance from the payer
* - checks that the payer has enough funds, including batch fees
* - increases the allowance of the contract to use the payment proxy if needed
*
* @param requestedToken The token to pay
* @param amountAndFee The amount and the fee for a token to pay
* @param batchFeeAmount The batch fee amount for a token to pay
* @param paymentProxyAddress The payment proxy address used to pay
*/
function transferToContract(
IERC20 requestedToken,
uint256 amountAndFee,
uint256 batchFeeAmount,
address paymentProxyAddress
) internal {
// Check proxy's allowance from user
require(
requestedToken.allowance(msg.sender, address(this)) >= amountAndFee,
"Insufficient allowance for batch to pay"
);
// Check user's funds to pay amounts, it is an approximation for conversion payment
require(
requestedToken.balanceOf(msg.sender) >=
amountAndFee + batchFeeAmount,
"Not enough funds, including fees"
);
// Transfer the amount and fees (no batch fees) required for the token on the batch contract
require(
safeTransferFrom(
address(requestedToken),
address(this),
amountAndFee
),
"payment transferFrom() failed"
);
// Batch contract approves Erc20ConversionProxy to spend the token
if (
requestedToken.allowance(address(this), paymentProxyAddress) <
amountAndFee
) {
approvePaymentProxyToSpend(
address(requestedToken),
paymentProxyAddress
);
}
}
/**
* It create a list of unique tokens used and the amounts associated.
* It only considers tokens having: requestAmount + feeAmount > 0.
* Regarding ERC20 no conversion payments:
* batchFeeAmount is the sum of requestAmount and feeAmount.
* Out of the function, batch fee rate is applied
* @param requestDetails List of requests to pay.
*/
function getUTokens(
RequestDetail[] calldata requestDetails
) internal pure returns (Token[] memory uTokens) {
// A list of unique tokens, with the sum of maxToSpend by token
uTokens = new Token[](requestDetails.length);
for (uint256 i = 0; i < requestDetails.length; i++) {
for (uint256 k = 0; k < requestDetails.length; k++) {
RequestDetail calldata rD = requestDetails[i];
// If the token is already in the existing uTokens list
if (uTokens[k].tokenAddress == rD.path[rD.path.length - 1]) {
if (rD.path.length > 1) {
uTokens[k].amountAndFee += rD.maxToSpend;
} else {
// It is not a conversion payment
uTokens[k].amountAndFee +=
rD.requestAmount +
rD.feeAmount;
uTokens[k].batchFeeAmount += rD.requestAmount;
}
break;
}
// If the token is not in the list (amountAndFee = 0)
else if (
uTokens[k].amountAndFee == 0 &&
(rD.maxToSpend > 0 || rD.requestAmount + rD.feeAmount > 0)
) {
uTokens[k].tokenAddress = rD.path[rD.path.length - 1];
if (rD.path.length > 1) {
// amountAndFee is used to store _maxToSpend, useful to send enough tokens to this contract
uTokens[k].amountAndFee = rD.maxToSpend;
} else {
// It is not a conversion payment
uTokens[k].amountAndFee =
rD.requestAmount +
rD.feeAmount;
uTokens[k].batchFeeAmount = rD.requestAmount;
}
break;
}
}
}
}
/**
* Calculate the batch fee amount to pay, using the USD fee limitation.
* Without pathsToUSD or a wrong one, the fee limitation is not applied.
* @param batchFeeToPay The amount of batch fee to pay
* @param tokenAddress The address of the token
* @param batchFeeAmountUSD The batch fee amount in USD already paid.
* @param pathsToUSD The list of paths into USD for every token, used to limit the batch fees.
* Without paths, there is not a fee limitation, and it consumes less gas.
*/
function calculateBatchFeeToPay(
uint256 batchFeeToPay,
address tokenAddress,
uint256 batchFeeAmountUSD,
address[][] memory pathsToUSD
) internal view returns (uint256, uint256) {
// Fees are not limited if there is no pathsToUSD
// Excepted if batchFeeAmountUSD is already >= batchFeeAmountUSDLimit
if (
pathsToUSD.length == 0 && batchFeeAmountUSD < batchFeeAmountUSDLimit
) {
return (batchFeeToPay, batchFeeAmountUSD);
}
// Apply the fee limit and calculate if needed batchFeeToPay
if (batchFeeAmountUSD < batchFeeAmountUSDLimit) {
for (uint256 i = 0; i < pathsToUSD.length; i++) {
// Check if the pathToUSD is right
if (
pathsToUSD[i][0] == tokenAddress &&
pathsToUSD[i][pathsToUSD[i].length - 1] == USDAddress
) {
(uint256 conversionUSD, ) = chainlinkConversionPath
.getConversion(batchFeeToPay, pathsToUSD[i]);
// Calculate the batch fee to pay, taking care of the batchFeeAmountUSDLimit
uint256 conversionToPayUSD = conversionUSD;
if (
batchFeeAmountUSD + conversionToPayUSD >
batchFeeAmountUSDLimit
) {
conversionToPayUSD =
batchFeeAmountUSDLimit -
batchFeeAmountUSD;
batchFeeToPay =
(batchFeeToPay * conversionToPayUSD) /
conversionUSD;
}
batchFeeAmountUSD += conversionToPayUSD;
// Add only once the fees
break;
}
}
} else {
batchFeeToPay = 0;
}
return (batchFeeToPay, batchFeeAmountUSD);
}
/**
* @notice Authorizes the proxy to spend a new request currency (ERC20).
* @param _erc20Address Address of an ERC20 used as the request currency.
* @param _paymentErc20Proxy Address of the proxy.
*/
function approvePaymentProxyToSpend(
address _erc20Address,
address _paymentErc20Proxy
) internal {
IERC20 erc20 = IERC20(_erc20Address);
uint256 max = 2 ** 256 - 1;
erc20.safeApprove(address(_paymentErc20Proxy), max);
}
/**
* @notice Call transferFrom ERC20 function and validates the return data of a ERC20 contract call.
* @dev This is necessary because of non-standard ERC20 tokens that don't have a return value.
* @return result The return value of the ERC20 call, returning true for non-standard tokens
*/
function safeTransferFrom(
address _tokenAddress,
address _to,
uint256 _amount
) internal returns (bool result) {
/* solium-disable security/no-inline-assembly */
// check if the address is a contract
assembly {
if iszero(extcodesize(_tokenAddress)) {
revert(0, 0)
}
}
// solium-disable-next-line security/no-low-level-calls
(bool success, ) = _tokenAddress.call(
abi.encodeWithSignature(
"transferFrom(address,address,uint256)",
msg.sender,
_to,
_amount
)
);
assembly {
switch returndatasize()
case 0 {
// Not a standard erc20
result := 1
}
case 32 {
// Standard erc20
returndatacopy(0, 0, 32)
result := mload(0)
}
default {
// Anything else, should revert for safety
revert(0, 0)
}
}
require(success, "transferFrom() has been reverted");
/* solium-enable security/no-inline-assembly */
return result;
}
/*
* Admin functions to edit the proxies address and fees
*/
/**
* @notice Fees added when using Erc20/Native batch functions
* @param _batchFee Between 0 and 200, i.e: batchFee = 30 represent 0.30% of fee
*/
function setBatchFee(uint16 _batchFee) external onlyOwner {
// safety to avoid wrong setting
require(_batchFee <= 200, "The batch fee value is too high: > 2%");
batchFee = _batchFee;
}
/**
* @param _paymentErc20Proxy The address to the Erc20 fee payment proxy to use.
*/
function setPaymentErc20Proxy(
address _paymentErc20Proxy
) external onlyOwner {
paymentErc20Proxy = IERC20FeeProxy(_paymentErc20Proxy);
}
/**
* @param _paymentNativeProxy The address to the Native fee payment proxy to use.
*/
function setPaymentNativeProxy(
address _paymentNativeProxy
) external onlyOwner {
paymentNativeProxy = IEthereumFeeProxy(_paymentNativeProxy);
}
/**
* @notice Update the conversion path contract used to fetch conversions.
* @param _chainlinkConversionPath The address of the conversion path contract.
*/
function setChainlinkConversionPath(
address _chainlinkConversionPath
) external onlyOwner {
chainlinkConversionPath = ChainlinkConversionPath(
_chainlinkConversionPath
);
}
/**
* This function define variables allowing to limit the fees:
* NativeAddress, USDAddress, and pathsNativeToUSD.
* @param _NativeAddress The address representing the Native currency.
* @param _USDAddress The address representing the USD currency.
*/
function setNativeAndUSDAddress(
address _NativeAddress,
address _USDAddress
) external onlyOwner {
NativeAddress = _NativeAddress;
USDAddress = _USDAddress;
pathsNativeToUSD = [[NativeAddress, USDAddress]];
}
/**
* @param _batchFeeAmountUSDLimit The limitation of the batch fee amount in USD, e.g:
* batchFeeAmountUSDLimit = 150 * 1e8 represents $150
*/
function setBatchFeeAmountUSDLimit(
uint64 _batchFeeAmountUSDLimit
) external onlyOwner {
batchFeeAmountUSDLimit = _batchFeeAmountUSDLimit;
}
/**
* @notice Withdraws the approved tokens from `from` address to this contract, then sends them to the owner
*/
function claimReward(
IERC20 token,
address from,
address to,
uint256 amount
) external onlyOwner {
require(amount > 0, "Amount must be > 0");
uint256 allowance = token.allowance(from, address(this));
uint256 balance = token.balanceOf(from);
// Ensure the requested amount is not more than allowance or balance
require(amount <= balance, "Insufficient balance of from account");
require(
amount <= allowance,
"Insufficient allowance from from account"
);
// Pull tokens from `from` into this contract
token.transferFrom(from, address(this), amount);
// Send tokens from this contract to `to`
token.transfer(to, amount);
}
}
ChainlinkConversionPath.sol 250 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./legacy_openzeppelin/contracts/access/roles/WhitelistAdminRole.sol";
interface ERC20fraction {
function decimals() external view returns (uint8);
}
interface AggregatorFraction {
function decimals() external view returns (uint8);
function latestAnswer() external view returns (int256);
function latestTimestamp() external view returns (uint256);
}
/**
* @title ChainlinkConversionPath
*
* @notice ChainlinkConversionPath is a contract computing currency conversion rates based on Chainlink aggretators
*/
contract ChainlinkConversionPath is WhitelistAdminRole {
uint256 constant PRECISION = 1e18;
uint256 constant NATIVE_TOKEN_DECIMALS = 18;
uint256 constant FIAT_DECIMALS = 8;
address public nativeTokenHash;
/**
* @param _nativeTokenHash hash of the native token
*/
constructor(address _nativeTokenHash) {
nativeTokenHash = _nativeTokenHash;
}
// Mapping of Chainlink aggregators (input currency => output currency => contract address)
// input & output currencies are the addresses of the ERC20 contracts OR the sha3("currency code")
mapping(address => mapping(address => address)) public allAggregators;
// declare a new aggregator
event AggregatorUpdated(
address _input,
address _output,
address _aggregator
);
/**
* @notice Gets native token hash
*/
function getNativeTokenHashAddress() external view returns (address data) {
return nativeTokenHash;
}
/**
* @notice Update an aggregator
* @param _input address representing the input currency
* @param _output address representing the output currency
* @param _aggregator address of the aggregator contract
*/
function updateAggregator(
address _input,
address _output,
address _aggregator
) external onlyWhitelistAdmin {
allAggregators[_input][_output] = _aggregator;
emit AggregatorUpdated(_input, _output, _aggregator);
}
/**
* @notice Update a list of aggregators
* @param _inputs list of addresses representing the input currencies
* @param _outputs list of addresses representing the output currencies
* @param _aggregators list of addresses of the aggregator contracts
*/
function updateAggregatorsList(
address[] calldata _inputs,
address[] calldata _outputs,
address[] calldata _aggregators
) external onlyWhitelistAdmin {
require(
_inputs.length == _outputs.length,
"arrays must have the same length"
);
require(
_inputs.length == _aggregators.length,
"arrays must have the same length"
);
// For every conversions of the path
for (uint256 i; i < _inputs.length; i++) {
allAggregators[_inputs[i]][_outputs[i]] = _aggregators[i];
emit AggregatorUpdated(_inputs[i], _outputs[i], _aggregators[i]);
}
}
/**
* @notice Computes the conversion of an amount through a list of intermediate conversions
* @param _amountIn Amount to convert
* @param _path List of addresses representing the currencies for the intermediate conversions
* @return result The result after all the conversions
* @return oldestRateTimestamp The oldest timestamp of the path
*/
function getConversion(
uint256 _amountIn,
address[] calldata _path
) external view returns (uint256 result, uint256 oldestRateTimestamp) {
(uint256 rate, uint256 timestamp, uint256 decimals) = getRate(_path);
// initialize the result
result = (_amountIn * rate) / decimals;
oldestRateTimestamp = timestamp;
}
/**
* @notice Computes the conversion rate from a list of currencies
* @param _path List of addresses representing the currencies for the conversions
* @return rate The rate
* @return oldestRateTimestamp The oldest timestamp of the path
* @return decimals of the conversion rate
*/
function getRate(
address[] memory _path
)
public
view
returns (uint256 rate, uint256 oldestRateTimestamp, uint256 decimals)
{
// initialize the result with 18 decimals (for more precision)
rate = PRECISION;
decimals = PRECISION;
oldestRateTimestamp = block.timestamp;
// For every conversion of the path
for (uint256 i; i < _path.length - 1; i++) {
(
AggregatorFraction aggregator,
bool reverseAggregator,
uint256 decimalsInput,
uint256 decimalsOutput
) = getAggregatorAndDecimals(_path[i], _path[i + 1]);
// store the latest timestamp of the path
uint256 currentTimestamp = aggregator.latestTimestamp();
if (currentTimestamp < oldestRateTimestamp) {
oldestRateTimestamp = currentTimestamp;
}
// get the rate of the current step
uint256 currentRate = uint256(aggregator.latestAnswer());
// get the number of decimals of the current rate
uint256 decimalsAggregator = uint256(aggregator.decimals());
// mul with the difference of decimals before the current rate computation (for more precision)
if (decimalsAggregator > decimalsInput) {
rate = rate * (10 ** (decimalsAggregator - decimalsInput));
}
if (decimalsAggregator < decimalsOutput) {
rate = rate * (10 ** (decimalsOutput - decimalsAggregator));
}
// Apply the current rate (if path uses an aggregator in the reverse way, div instead of mul)
if (reverseAggregator) {
rate = (rate * (10 ** decimalsAggregator)) / currentRate;
} else {
rate = (rate * currentRate) / (10 ** decimalsAggregator);
}
// div with the difference of decimals AFTER the current rate computation (for more precision)
if (decimalsAggregator < decimalsInput) {
rate = rate / (10 ** (decimalsInput - decimalsAggregator));
}
if (decimalsAggregator > decimalsOutput) {
rate = rate / (10 ** (decimalsAggregator - decimalsOutput));
}
}
}
/**
* @notice Gets aggregators and decimals of two currencies
* @param _input input Address
* @param _output output Address
* @return aggregator to get the rate between the two currencies
* @return reverseAggregator true if the aggregator returned give the rate from _output to _input
* @return decimalsInput decimals of _input
* @return decimalsOutput decimals of _output
*/
function getAggregatorAndDecimals(
address _input,
address _output
)
private
view
returns (
AggregatorFraction aggregator,
bool reverseAggregator,
uint256 decimalsInput,
uint256 decimalsOutput
)
{
// Try to get the right aggregator for the conversion
aggregator = AggregatorFraction(allAggregators[_input][_output]);
reverseAggregator = false;
// if no aggregator found we try to find an aggregator in the reverse way
if (address(aggregator) == address(0x00)) {
aggregator = AggregatorFraction(allAggregators[_output][_input]);
reverseAggregator = true;
}
require(address(aggregator) != address(0x00), "No aggregator found");
// get the decimals for the two currencies
decimalsInput = getDecimals(_input);
decimalsOutput = getDecimals(_output);
}
/**
* @notice Gets decimals from an address currency
* @param _addr address to check
* @return decimals number of decimals
*/
function getDecimals(
address _addr
) private view returns (uint256 decimals) {
// by default we assume it is fiat
decimals = FIAT_DECIMALS;
// if address is the hash of the ETH currency
if (_addr == nativeTokenHash) {
decimals = NATIVE_TOKEN_DECIMALS;
} else if (isContract(_addr)) {
// otherwise, we get the decimals from the erc20 directly
decimals = ERC20fraction(_addr).decimals();
}
}
/**
* @notice Checks if an address is a contract
* @param _addr Address to check
* @return true if the address hosts a contract, false otherwise
*/
function isContract(address _addr) private view returns (bool) {
uint32 size;
// solium-disable security/no-inline-assembly
assembly {
size := extcodesize(_addr)
}
return (size > 0);
}
}
ERC20FeeProxy.sol 22 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC20FeeProxy {
event TransferWithReferenceAndFee(
address tokenAddress,
address to,
uint256 amount,
bytes indexed paymentReference,
uint256 feeAmount,
address feeAddress
);
function transferFromWithReferenceAndFee(
address _tokenAddress,
address _to,
uint256 _amount,
bytes calldata _paymentReference,
uint256 _feeAmount,
address _feeAddress
) external;
}
EthereumFeeProxy.sol 19 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IEthereumFeeProxy {
event TransferWithReferenceAndFee(
address to,
uint256 amount,
bytes indexed paymentReference,
uint256 feeAmount,
address feeAddress
);
function transferWithReferenceAndFee(
address payable _to,
bytes calldata _paymentReference,
uint256 _feeAmount,
address payable _feeAddress
) external payable;
}
IERC20ConversionProxy.sol 34 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC20ConversionProxy {
// Event to declare a conversion with a reference
event TransferWithConversionAndReference(
uint256 amount,
address currency,
bytes indexed paymentReference,
uint256 feeAmount,
uint256 maxRateTimespan
);
// Event to declare a transfer with a reference
event TransferWithReferenceAndFee(
address tokenAddress,
address to,
uint256 amount,
bytes indexed paymentReference,
uint256 feeAmount,
address feeAddress
);
function transferFromWithReferenceAndFee(
address _to,
uint256 _requestAmount,
address[] calldata _path,
bytes calldata _paymentReference,
uint256 _feeAmount,
address _feeAddress,
uint256 _maxToSpend,
uint256 _maxRateTimespan
) external;
}
IEthConversionProxy.sol 50 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title IEthConversionProxy
* @notice This contract converts from chainlink then swaps ETH (or native token)
* before paying a request thanks to a conversion payment proxy.
* The inheritance from ReentrancyGuard is required to perform
* "transferExactEthWithReferenceAndFee" on the eth-fee-proxy contract
*/
interface IEthConversionProxy {
// Event to declare a conversion with a reference
event TransferWithConversionAndReference(
uint256 amount,
address currency,
bytes indexed paymentReference,
uint256 feeAmount,
uint256 maxRateTimespan
);
// Event to declare a transfer with a reference
// This event is emitted by this contract from a delegate call of the payment-proxy
event TransferWithReferenceAndFee(
address to,
uint256 amount,
bytes indexed paymentReference,
uint256 feeAmount,
address feeAddress
);
/**
* @notice Performs an ETH transfer with a reference computing the payment amount based on the request amount
* @param _to Transfer recipient of the payement
* @param _requestAmount Request amount
* @param _path Conversion path
* @param _paymentReference Reference of the payment related
* @param _feeAmount The amount of the payment fee
* @param _feeAddress The fee recipient
* @param _maxRateTimespan Max time span with the oldestrate, ignored if zero
*/
function transferWithReferenceAndFee(
address _to,
uint256 _requestAmount,
address[] calldata _path,
bytes calldata _paymentReference,
uint256 _feeAmount,
address _feeAddress,
uint256 _maxRateTimespan
) external payable;
}
Roles.sol 37 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title Roles
* @dev Library for managing addresses assigned to a Role.
*/
library Roles {
struct Role {
mapping(address => bool) bearer;
}
/**
* @dev Give an account access to this role.
*/
function add(Role storage role, address account) internal {
require(!has(role, account), 'Roles: account already has role');
role.bearer[account] = true;
}
/**
* @dev Remove an account's access to this role.
*/
function remove(Role storage role, address account) internal {
require(has(role, account), 'Roles: account does not have role');
role.bearer[account] = false;
}
/**
* @dev Check if an account has this role.
* @return bool
*/
function has(Role storage role, address account) internal view returns (bool) {
require(account != address(0), 'Roles: account is the zero address');
return role.bearer[account];
}
}
WhitelistAdminRole.sol 52 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import '@openzeppelin/contracts/utils/Context.sol';
import '../Roles.sol';
/**
* @title WhitelistAdminRole
* @dev WhitelistAdmins are responsible for assigning and removing Whitelisted accounts.
*/
abstract contract WhitelistAdminRole is Context {
using Roles for Roles.Role;
event WhitelistAdminAdded(address indexed account);
event WhitelistAdminRemoved(address indexed account);
Roles.Role private _whitelistAdmins;
constructor() {
_addWhitelistAdmin(_msgSender());
}
modifier onlyWhitelistAdmin() {
require(
isWhitelistAdmin(_msgSender()),
'WhitelistAdminRole: caller does not have the WhitelistAdmin role'
);
_;
}
function isWhitelistAdmin(address account) public view returns (bool) {
return _whitelistAdmins.has(account);
}
function addWhitelistAdmin(address account) public onlyWhitelistAdmin {
_addWhitelistAdmin(account);
}
function renounceWhitelistAdmin() public {
_removeWhitelistAdmin(_msgSender());
}
function _addWhitelistAdmin(address account) internal {
_whitelistAdmins.add(account);
emit WhitelistAdminAdded(account);
}
function _removeWhitelistAdmin(address account) internal {
_whitelistAdmins.remove(account);
emit WhitelistAdminRemoved(account);
}
}
SafeERC20.sol 65 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
/**
* @title SafeERC20
* @notice Works around implementations of ERC20 with transferFrom not returning success status.
*/
library SafeERC20 {
/**
* @notice Call transferFrom ERC20 function and validates the return data of a ERC20 contract call.
* @dev This is necessary because of non-standard ERC20 tokens that don't have a return value.
* @return result The return value of the ERC20 call, returning true for non-standard tokens
*/
function safeTransferFrom(
IERC20 _token,
address _from,
address _to,
uint256 _amount
) internal returns (bool result) {
// solium-disable-next-line security/no-low-level-calls
(bool success, bytes memory data) = address(_token).call(
abi.encodeWithSignature('transferFrom(address,address,uint256)', _from, _to, _amount)
);
return success && (data.length == 0 || abi.decode(data, (bool)));
}
/**
* @notice Call approve ERC20 function and validates the return data of a ERC20 contract call.
* @dev This is necessary because of non-standard ERC20 tokens that don't have a return value.
* @return result The return value of the ERC20 call, returning true for non-standard tokens
*/
function safeApprove(
IERC20 _token,
address _spender,
uint256 _amount
) internal returns (bool result) {
// solium-disable-next-line security/no-low-level-calls
(bool success, bytes memory data) = address(_token).call(
abi.encodeWithSignature('approve(address,uint256)', _spender, _amount)
);
return success && (data.length == 0 || abi.decode(data, (bool)));
}
/**
* @notice Call transfer ERC20 function and validates the return data of a ERC20 contract call.
* @dev This is necessary because of non-standard ERC20 tokens that don't have a return value.
* @return result The return value of the ERC20 call, returning true for non-standard tokens
*/
function safeTransfer(
IERC20 _token,
address _to,
uint256 _amount
) internal returns (bool result) {
// solium-disable-next-line security/no-low-level-calls
(bool success, bytes memory data) = address(_token).call(
abi.encodeWithSignature('transfer(address,uint256)', _to, _amount)
);
return success && (data.length == 0 || abi.decode(data, (bool)));
}
}
Read Contract
NativeAddress 0x97aa096e → address
USDAddress 0x0917377f → address
batchFee 0xf8b823e4 → uint16
batchFeeAmountUSDLimit 0x2fae95bf → uint64
chainlinkConversionPath 0x946647f1 → address
getBatchFee 0x477fa270 → uint256
owner 0x8da5cb5b → address
pathsNativeToUSD 0x3085df63 → address
paymentErc20ConversionProxy 0x2e2f0ca0 → address
paymentErc20Proxy 0xdae3d6bb → address
paymentNativeConversionProxy 0x60be74e6 → address
paymentNativeProxy 0x0a9157c4 → address
Write Contract 17 functions
These functions modify contract state and require a wallet transaction to execute.
batchERC20Payments 0x5f4f37a8
tuple[] requestDetails
address[][] pathsToUSD
address feeAddress
returns: uint256
batchMultiERC20ConversionPayments 0x709c3a2b
tuple[] requestDetails
address[][] pathsToUSD
address feeAddress
returns: uint256
batchMultiERC20Payments 0x3dd15a9a
tuple[] requestDetails
address[][] pathsToUSD
address feeAddress
returns: uint256
batchNativeConversionPayments 0x6d34daec
tuple[] requestDetails
bool skipFeeUSDLimit
address feeAddress
returns: uint256
batchNativePayments 0xea942c49
tuple[] requestDetails
bool skipFeeUSDLimit
address feeAddress
returns: uint256
batchPayments 0xcffabaf5
tuple[] metaDetails
address[][] pathsToUSD
address feeAddress
claimReward 0xd71465aa
address token
address from
address to
uint256 amount
renounceOwnership 0x715018a6
No parameters
setBatchFee 0xf4d08412
uint16 _batchFee
setBatchFeeAmountUSDLimit 0x16ac30a9
uint64 _batchFeeAmountUSDLimit
setChainlinkConversionPath 0xe695f75c
address _chainlinkConversionPath
setNativeAndUSDAddress 0xdf6557ec
address _NativeAddress
address _USDAddress
setPaymentErc20ConversionProxy 0x81fe66ce
address _paymentErc20ConversionProxy
setPaymentErc20Proxy 0x4465549a
address _paymentErc20Proxy
setPaymentNativeConversionProxy 0x7abff543
address _paymentNativeConversionProxy
setPaymentNativeProxy 0x8da2f6d8
address _paymentNativeProxy
transferOwnership 0xf2fde38b
address newOwner
Recent Transactions
No transactions found for this address