Forkchoice Ethereum Mainnet

Address Contract Verified

Address 0x3Cf6a3B3eD708D829bC87F47e5175779e7ABC03F
Balance 0 ETH
Nonce 1
Code Size 13392 bytes
Indexed Transactions 0
External Etherscan · Sourcify

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