Cryo Explorer Ethereum Mainnet

Address Contract Verified

Address 0x4fA0C488F321A1D089f7E5f951fe8C43F2064709
Balance 0.000000002 ETH
Nonce 1
Code Size 10337 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

10337 bytes
0x6080604052600436106101695760003560e01c806359c57ee2116100d157806395fe863c1161008a578063d492714811610064578063d492714814610437578063d64d4ba514610457578063fb4daeec14610477578063ffa1ad741461048a57600080fd5b806395fe863c146103d2578063c1d8e08a146103e7578063d3b3ca771461041757600080fd5b806359c57ee2146103075780635daf206a146103275780636b55e99114610347578063753f94471461035a5780637da0a8771461037a57806388e3b76a146103b257600080fd5b80631da5f315116101235780631da5f3151461023f5780631ec82cb81461025f578063415a21871461027f578063435fd56d1461029f578063522f6815146102c7578063568f3254146102e757600080fd5b8062124e3d14610175578063078aa9641461019d57806311e8701c146101bd57806317f565d2146101df5780631945b784146101ff5780631c9af2e31461021f57600080fd5b3661017057005b600080fd5b34801561018157600080fd5b5061018a61049f565b6040519081526020015b60405180910390f35b3480156101a957600080fd5b5061018a6101b8366004611ca5565b61052f565b3480156101c957600080fd5b506101dd6101d8366004611d03565b610546565b005b3480156101eb57600080fd5b5061018a6101fa366004611d68565b6105de565b34801561020b57600080fd5b5061018a61021a366004611dc3565b610659565b34801561022b57600080fd5b506101dd61023a366004611e37565b6106e9565b34801561024b57600080fd5b5061018a61025a366004611ed1565b610762565b34801561026b57600080fd5b506101dd61027a366004611f7a565b610949565b34801561028b57600080fd5b506101dd61029a366004611d03565b61098f565b6102b26102ad366004611fbb565b610a07565b60408051928352602083019190915201610194565b3480156102d357600080fd5b506101dd6102e2366004612040565b610d34565b3480156102f357600080fd5b506101dd61030236600461206c565b610dd5565b34801561031357600080fd5b5061018a610322366004612118565b610e3e565b34801561033357600080fd5b506102b261034236600461215b565b610f11565b6101dd610355366004611d03565b611115565b34801561036657600080fd5b5061018a610375366004612282565b611189565b34801561038657600080fd5b5060005461039a906001600160a01b031681565b6040516001600160a01b039091168152602001610194565b3480156103be57600080fd5b5061018a6103cd366004612357565b6111fe565b3480156103de57600080fd5b5061018a6112f3565b3480156103f357600080fd5b50610407610402366004611d03565b611354565b6040519015158152602001610194565b34801561042357600080fd5b5061018a6104323660046123b1565b6113c3565b34801561044357600080fd5b5061018a610452366004612412565b61145b565b34801561046357600080fd5b5061018a6104723660046124be565b611487565b61018a610485366004612535565b61158b565b34801561049657600080fd5b5061018a600981565b6000807f000000000000000000000000449d117117838ffa61263b61da6301aa2a88b13a6001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015610500573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061052491906125a7565b509195945050505050565b600061053d838360016111fe565b90505b92915050565b60405163095ea7b360e01b81526001600160a01b037f000000000000000000000000e592427a0aece92de3edee1f18e0157c0586156481166004830152600019602483015282169063095ea7b3906044016020604051808303816000875af11580156105b6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105da91906125f7565b5050565b600061064e856001600160a01b0316635001f3b56040518163ffffffff1660e01b8152600401602060405180830381865afa158015610621573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106459190612614565b868686866113c3565b90505b949350505050565b60006001600160a01b03821673b4272071ecadd69d933adcd19ca99fe80664fc0814806106a257506001600160a01b03821673b58e61c3098d85632df34eecfb899a1ed80921cb145b156106d3576106af6112f3565b836106b861049f565b6106c29190612647565b6106cc919061265e565b9050610540565b6106db6112f3565b6106c26305f5e10085612647565b60005b8381101561075a576107523386868481811061070a5761070a612680565b905060200201602081019061071f9190611d03565b85858581811061073157610731612680565b90506020020135896001600160a01b0316611724909392919063ffffffff16565b6001016106ec565b505050505050565b6000805487906001600160a01b031633148015906107895750336001600160a01b03821614155b156107ae576040516340b3b69360e11b81523360048201526024015b60405180910390fd5b6001600160a01b03891663d505accf89308960208801356107d560608a0160408b01612696565b6040516001600160e01b031960e088901b1681526001600160a01b0395861660048201529490931660248501526044840191909152606483015260ff166084820152606086013560a4820152608086013560c482015260e401600060405180830381600087803b15801561084857600080fd5b505af115801561085c573d6000803e3d6000fd5b505050833515905061092a5760006108798b8b8b308b8b8b611791565b905060008b6001600160a01b0316635001f3b56040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108bb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108df9190612614565b90506108f66001600160a01b03821633873561181f565b610916896109058735856126b9565b6001600160a01b038416919061181f565b6109218535836126b9565b9350505061093c565b6109398a8a8a8a8a8a8a611791565b91505b5098975050505050505050565b6000546001600160a01b03163314610976576040516340b3b69360e11b81523360048201526024016107a5565b61098a6001600160a01b038416838361181f565b505050565b6000546001600160a01b031633146109bc576040516340b3b69360e11b81523360048201526024016107a5565b600080546001600160a01b0319166001600160a01b0383169081178255604051909133917f6b1afd5bf12dcac0f44e8afd235c6681297d3124624a7a7a0d34abd84b7986259190a350565b6000806000876001600160a01b0316635001f3b56040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a4a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a6e9190612614565b9050610a7988611354565b15610b8357610a888782610659565b9250876001600160a01b03166369365c528483338b8b8b6040518763ffffffff1660e01b8152600401610abf9594939291906126cc565b60206040518083038185885af1158015610add573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190610b029190612720565b915034831015610b7e57600033610b1985346126b9565b604051600081818185875af1925050503d8060008114610b55576040519150601f19603f3d011682016040523d82523d6000602084013e610b5a565b606091505b5050905080610b7c5760405163f82a0ab760e01b815260040160405180910390fd5b505b610d29565b6040516370a0823160e01b81526001600160a01b038981166004830152600091908316906370a0823190602401602060405180830381865afa158015610bcd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bf19190612720565b9050610bfe89898761158b565b6040516370a0823160e01b81526001600160a01b038b811660048301529195506000918416906370a0823190602401602060405180830381865afa158015610c4a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c6e9190612720565b9050610c7a82826126b9565b8914610cad5788610c8b83836126b9565b604051630a720dfb60e01b8152600481019290925260248201526044016107a5565b60405163349b2e2960e11b81526001600160a01b038b16906369365c5290610ce190869033908e908e908e906004016126cc565b6020604051808303816000875af1158015610d00573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d249190612720565b935050505b509550959350505050565b6000546001600160a01b03163314610d61576040516340b3b69360e11b81523360048201526024016107a5565b6000826001600160a01b03168260405160006040518083038185875af1925050503d8060008114610dae576040519150601f19603f3d011682016040523d82523d6000602084013e610db3565b606091505b505090508061098a5760405163f82a0ab760e01b815260040160405180910390fd5b60005b85811015610e3457610e2b88888884818110610df657610df6612680565b9050602002016020810190610e0b9190611d03565b878785818110610e1d57610e1d612680565b9050602002013586866113c3565b50600101610dd8565b5050505050505050565b60006001600160a01b03841615801590610e5c5750610e5c84611354565b15610ece57610ec785856001600160a01b0316635001f3b56040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ea3573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061021a9190612614565b9050610651565b610ec78584848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250600192506111fe915050565b6000806000896001600160a01b0316635001f3b56040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f54573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f789190612614565b6040516370a0823160e01b81526001600160a01b038c811660048301529192506000918316906370a0823190602401602060405180830381865afa158015610fc4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fe89190612720565b9050610ff78a898b8a8f611487565b6040516370a0823160e01b81526001600160a01b038d811660048301529195506000918416906370a0823190602401602060405180830381865afa158015611043573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110679190612720565b905061107382826126b9565b8b14611084578a610c8b83836126b9565b6001600160a01b038c166369365c52843361109f86866126b9565b8b8b6040518663ffffffff1660e01b81526004016110c19594939291906126cc565b6020604051808303816000875af11580156110e0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111049190612720565b935050505097509795505050505050565b6000816001600160a01b03163460405160006040518083038185875af1925050503d8060008114611162576040519150601f19603f3d011682016040523d82523d6000602084013e611167565b606091505b50509050806105da5760405163f82a0ab760e01b815260040160405180910390fd5b6000805488906001600160a01b031633148015906111b05750336001600160a01b03821614155b156111d0576040516340b3b69360e11b81523360048201526024016107a5565b6111e08b8b8b308c8c8c8c610762565b60608501526111ef8484611850565b9b9a5050505050505050505050565b6000811561129e57604051632f80bb1d60e01b81526001600160a01b037f000000000000000000000000b27308f9f90d607463bb33ea1bebb41c27ce5ab61690632f80bb1d906112549086908890600401612789565b6020604051808303816000875af1158015611273573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112979190612720565b90506112ec565b60405163cdca175360e01b81526001600160a01b037f000000000000000000000000b27308f9f90d607463bb33ea1bebb41c27ce5ab6169063cdca1753906112549086908890600401612789565b9392505050565b6000807f0000000000000000000000005f4ec3df9cbd43714fe2740f5e3616155c5b84196001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015610500573d6000803e3d6000fd5b6000600480836001600160a01b031663e06174e46040518163ffffffff1660e01b8152600401602060405180830381865afa158015611397573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113bb9190612720565b161492915050565b60006113da6001600160a01b038716338787611724565b60405163349b2e2960e11b81526001600160a01b038616906369365c529061140e90899033908990899089906004016126cc565b6020604051808303816000875af115801561142d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114519190612720565b9695505050505050565b600061146c888833308a8a8a611791565b606084015261147b8383611850565b98975050505050505050565b60007f000000000000000000000000e592427a0aece92de3edee1f18e0157c058615646114bf6001600160a01b038616333089611724565b6040805160a0810182528581526001600160a01b0380861660208301524282840152606082018a9052608082018990529151631e51809360e31b8152909183169063f28c0498906115149084906004016127fc565b6020604051808303816000875af1158015611533573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115579190612720565b925086831015611580576115803361156f858a6126b9565b6001600160a01b038916919061181f565b505095945050505050565b6040805160a0810182528281526001600160a01b03808616602083015242828401526060820185905234608083018190529251631e51809360e31b81526000937f000000000000000000000000e592427a0aece92de3edee1f18e0157c05861564939284169163f28c049891906116069085906004016127fc565b60206040518083038185885af1158015611624573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906116499190612720565b92503483101561171b57816001600160a01b03166312210e8a6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561168e57600080fd5b505af11580156116a2573d6000803e3d6000fd5b50600092503391506116b6905085346126b9565b604051600081818185875af1925050503d80600081146116f2576040519150601f19603f3d011682016040523d82523d6000602084013e6116f7565b606091505b50509050806117195760405163f82a0ab760e01b815260040160405180910390fd5b505b50509392505050565b6040516001600160a01b03848116602483015283811660448301526064820183905261178b9186918216906323b872dd906084015b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050506119d2565b50505050565b60006117a86001600160a01b038816878a87611724565b60405163349b2e2960e11b81526001600160a01b038916906369365c52906117dc908a9089908990899089906004016126cc565b6020604051808303816000875af11580156117fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061147b9190612720565b6040516001600160a01b0383811660248301526044820183905261098a91859182169063a9059cbb90606401611759565b600081156119c9576020830180513090915261186b84611a35565b91507f000000000000000000000000b27308f9f90d607463bb33ea1bebb41c27ce5ab66001600160a01b0316634aa4a4fc6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156118cb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118ef9190612614565b6001600160a01b0316632e1a7d4d836040518263ffffffff1660e01b815260040161191c91815260200190565b600060405180830381600087803b15801561193657600080fd5b505af115801561194a573d6000803e3d6000fd5b505050506000816001600160a01b03168360405160006040518083038185875af1925050503d806000811461199b576040519150601f19603f3d011682016040523d82523d6000602084013e6119a0565b606091505b50509050806119c25760405163f82a0ab760e01b815260040160405180910390fd5b5050610540565b61053d83611a35565b60006119e76001600160a01b03841683611b03565b90508051600014158015611a0c575080806020019051810190611a0a91906125f7565b155b1561098a57604051635274afe760e01b81526001600160a01b03841660048201526024016107a5565b60405163c04b8d5960e01b81526000906001600160a01b037f000000000000000000000000e592427a0aece92de3edee1f18e0157c05861564169063c04b8d5990611a849085906004016127fc565b6020604051808303816000875af1158015611aa3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ac79190612720565b90508160800151811015611afe576080820151604051630a720dfb60e01b81526004810191909152602481018290526044016107a5565b919050565b606061053d8383600060608251600014158015611b2857506001600160a01b0384163b155b15611b5157604051639eb1341360e01b81526001600160a01b03851660048201526024016107a5565b600080856001600160a01b03168486604051611b6d919061280f565b60006040518083038185875af1925050503d8060008114611baa576040519150601f19603f3d011682016040523d82523d6000602084013e611baf565b606091505b50915091508115611bc35791506112ec9050565b805115611bd257805160208201fd5b60405162461bcd60e51b815260206004820152600660248201526519985a5b195960d21b60448201526064016107a5565b634e487b7160e01b600052604160045260246000fd5b600082601f830112611c2a57600080fd5b81356001600160401b0380821115611c4457611c44611c03565b604051601f8301601f19908116603f01168101908282118183101715611c6c57611c6c611c03565b81604052838152866020858801011115611c8557600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060408385031215611cb857600080fd5b8235915060208301356001600160401b03811115611cd557600080fd5b611ce185828601611c19565b9150509250929050565b6001600160a01b0381168114611d0057600080fd5b50565b600060208284031215611d1557600080fd5b81356112ec81611ceb565b60008083601f840112611d3257600080fd5b5081356001600160401b03811115611d4957600080fd5b602083019150836020828501011115611d6157600080fd5b9250929050565b60008060008060608587031215611d7e57600080fd5b8435611d8981611ceb565b93506020850135925060408501356001600160401b03811115611dab57600080fd5b611db787828801611d20565b95989497509550505050565b60008060408385031215611dd657600080fd5b823591506020830135611de881611ceb565b809150509250929050565b60008083601f840112611e0557600080fd5b5081356001600160401b03811115611e1c57600080fd5b6020830191508360208260051b8501011115611d6157600080fd5b600080600080600060608688031215611e4f57600080fd5b8535611e5a81611ceb565b945060208601356001600160401b0380821115611e7657600080fd5b611e8289838a01611df3565b90965094506040880135915080821115611e9b57600080fd5b50611ea888828901611df3565b969995985093965092949392505050565b600060a08284031215611ecb57600080fd5b50919050565b600080600080600080600080610160898b031215611eee57600080fd5b8835611ef981611ceb565b97506020890135611f0981611ceb565b96506040890135611f1981611ceb565b95506060890135611f2981611ceb565b94506080890135935060a08901356001600160401b03811115611f4b57600080fd5b611f578b828c01611d20565b9094509250611f6b90508a60c08b01611eb9565b90509295985092959890939650565b600080600060608486031215611f8f57600080fd5b8335611f9a81611ceb565b92506020840135611faa81611ceb565b929592945050506040919091013590565b600080600080600060808688031215611fd357600080fd5b8535611fde81611ceb565b94506020860135935060408601356001600160401b038082111561200157600080fd5b61200d89838a01611d20565b9095509350606088013591508082111561202657600080fd5b5061203388828901611c19565b9150509295509295909350565b6000806040838503121561205357600080fd5b823561205e81611ceb565b946020939093013593505050565b60008060008060008060006080888a03121561208757600080fd5b873561209281611ceb565b965060208801356001600160401b03808211156120ae57600080fd5b6120ba8b838c01611df3565b909850965060408a01359150808211156120d357600080fd5b6120df8b838c01611df3565b909650945060608a01359150808211156120f857600080fd5b506121058a828b01611d20565b989b979a50959850939692959293505050565b6000806000806060858703121561212e57600080fd5b84359350602085013561214081611ceb565b925060408501356001600160401b03811115611dab57600080fd5b600080600080600080600060c0888a03121561217657600080fd5b873561218181611ceb565b965060208801359550604088013561219881611ceb565b94506060880135935060808801356001600160401b03808211156121bb57600080fd5b6121c78b838c01611c19565b945060a08a01359150808211156120f857600080fd5b600060a082840312156121ef57600080fd5b60405160a081016001600160401b03828210818311171561221257612212611c03565b81604052829350843591508082111561222a57600080fd5b5061223785828601611c19565b825250602083013561224881611ceb565b806020830152506040830135604082015260608301356060820152608083013560808201525092915050565b8015158114611d0057600080fd5b60008060008060008060008060006101808a8c0312156122a157600080fd5b89356122ac81611ceb565b985060208a01356122bc81611ceb565b975060408a01356122cc81611ceb565b965060608a0135955060808a01356001600160401b03808211156122ef57600080fd5b6122fb8d838e01611d20565b90975095508591506123108d60a08e01611eb9565b94506101408c013591508082111561232757600080fd5b506123348c828d016121dd565b9250506101608a013561234681612274565b809150509295985092959850929598565b60008060006060848603121561236c57600080fd5b8335925060208401356001600160401b0381111561238957600080fd5b61239586828701611c19565b92505060408401356123a681612274565b809150509250925092565b6000806000806000608086880312156123c957600080fd5b85356123d481611ceb565b945060208601356123e481611ceb565b93506040860135925060608601356001600160401b0381111561240657600080fd5b611ea888828901611d20565b600080600080600080600060c0888a03121561242d57600080fd5b873561243881611ceb565b9650602088013561244881611ceb565b95506040880135945060608801356001600160401b038082111561246b57600080fd5b6124778b838c01611d20565b909650945060808a013591508082111561249057600080fd5b5061249d8a828b016121dd565b92505060a08801356124ae81612274565b8091505092959891949750929550565b600080600080600060a086880312156124d657600080fd5b853594506020860135935060408601356124ef81611ceb565b925060608601356001600160401b0381111561250a57600080fd5b61251688828901611c19565b925050608086013561252781611ceb565b809150509295509295909350565b60008060006060848603121561254a57600080fd5b833561255581611ceb565b92506020840135915060408401356001600160401b0381111561257757600080fd5b61258386828701611c19565b9150509250925092565b805169ffffffffffffffffffff81168114611afe57600080fd5b600080600080600060a086880312156125bf57600080fd5b6125c88661258d565b94506020860151935060408601519250606086015191506125eb6080870161258d565b90509295509295909350565b60006020828403121561260957600080fd5b81516112ec81612274565b60006020828403121561262657600080fd5b81516112ec81611ceb565b634e487b7160e01b600052601160045260246000fd5b808202811582820484141761054057610540612631565b60008261267b57634e487b7160e01b600052601260045260246000fd5b500490565b634e487b7160e01b600052603260045260246000fd5b6000602082840312156126a857600080fd5b813560ff811681146112ec57600080fd5b8181038181111561054057610540612631565b6001600160a01b038681168252851660208201526040810184905260806060820181905281018290526000828460a0840137600060a0848401015260a0601f19601f85011683010190509695505050505050565b60006020828403121561273257600080fd5b5051919050565b60005b8381101561275457818101518382015260200161273c565b50506000910152565b60008151808452612775816020860160208601612739565b601f01601f19169290920160200192915050565b60408152600061279c604083018561275d565b90508260208301529392505050565b6000815160a084526127c060a085018261275d565b6020848101516001600160a01b031690860152604080850151908601526060808501519086015260809384015193909401929092525090919050565b60208152600061053d60208301846127ab565b60008251612821818460208701612739565b919091019291505056fea26469706673582212200ab5567e117c74bef75a597d9c93f4b94adc519b838070110a61fb2923d9a29764736f6c63430008190033

Verified Source Code Full Match

Compiler: v0.8.25+commit.b61c2a91 EVM: paris Optimization: Yes (200 runs)
IERC20.sol 92 lines
/**
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2016-2019 zOS Global Limited
*
*/
pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
 * the optional functions; to access them see `ERC20Detailed`.
 */

interface IERC20 {

    // Optional functions
    function name() external view returns (string memory);

    function symbol() external view returns (string memory);

    function decimals() external view returns (uint8);

    /**
     * @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 `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a `Transfer` event.
     */
    function transfer(address recipient, 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.
     *
     * > 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 `sender` to `recipient` 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 sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @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);

}
Address.sol 74 lines
// SPDX-License-Identifier: MIT
// Copied from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol
// and modified it.

pragma solidity ^0.8.0;

library Address {

    /// @param target Target address to call the function on.
    error Address_NotTransferNorContract(address target);

    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.
        return account.code.length > 0;
    }
    
    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    function functionCallWithValue(address target, bytes memory data, uint256 weiValue) internal returns (bytes memory) {
        if (data.length != 0 && !isContract(target)) {
            revert Address_NotTransferNorContract(target);
        }
        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.call{ value: weiValue }(data);
        if (success) {
            return returndata;
        } else if (returndata.length > 0) {
            assembly{
                revert (add (returndata, 0x20), mload (returndata))
            }
        } else {
           revert("failed");
        }
    }
}
Ownable.sol 59 lines
// SPDX-License-Identifier: MIT
//
// From https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol
//
// Modifications:
// - Replaced Context._msgSender() with msg.sender
// - Made leaner
// - Extracted interface

pragma solidity ^0.8.0;

/**
 * @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.
 */
contract Ownable {

    address public owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    error Ownable_NotOwner(address sender);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor (address initialOwner) {
        owner = initialOwner;
        emit OwnershipTransferred(address(0), owner);
    }

    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) external onlyOwner {
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }

    function _checkOwner() internal view {
        if (msg.sender != owner) {
            revert Ownable_NotOwner(msg.sender);
        }
    }
}
SafeERC20.sol 98 lines
// SPDX-License-Identifier: MIT
// coppied and adjusted from OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../ERC20/IERC20.sol";
import {IERC20Permit} from "../ERC20/IERC20Permit.sol";
import {Address} from "./Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev An operation with an ERC20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        if (nonceAfter != nonceBefore + 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}
IERC20Permit.sol 75 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
// Copied from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/draft-IERC20Permit.sol

pragma solidity ^0.8.0;

import "./IERC20.sol";

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit is IERC20 {

    /*//////////////////////////////////////////////////////////////
                            Custom errors
	//////////////////////////////////////////////////////////////*/
    /// Block timestamp must to be before deadline.
    /// @param deadline The deadline of the permit.
    /// @param blockTimestamp The timestamp of the execution block.
    error Permit_DeadlineExpired(uint256 deadline, uint256 blockTimestamp);
    /// Recovered address must be owner and not zero address.
    /// @param signerAddress The recovered signer address.
    error Permit_InvalidSigner(address signerAddress);

    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}
IBrokerbot.sol 40 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../ERC20/IERC20.sol";
import "../ERC20/IERC20Permit.sol";

interface IBrokerbot {

	/*//////////////////////////////////////////////////////////////
                            Custom errors
  //////////////////////////////////////////////////////////////*/
  error Brokerbot_BuyingDisabled();
  error Brokerbot_SellingDisabled();
  /// Sender(msg.sender) has to be incoming token or paymenthub.
  /// @param sender The msg.sender.
  error Brokerbot_InvalidSender(address sender);
  /// target.call() wasn't successful.
  /// @param target The receiver of the Eth.
  /// @param amount The withdraw amount.
  error Brokerbot_WithdrawFailed(address target, uint256 amount);
  /// Sender(msg.sender) needs to be owner or paymenthub.
  /// @param sender The msg.sender.
  error Brokerbot_NotAuthorized(address sender);

  function paymenthub() external view returns (address);

  function base() external view returns (IERC20);

  function token() external view returns (IERC20Permit);
  
  function settings() external view returns (uint256);

  // @return The amount of shares bought on buying or how much in the base currency is transfered on selling
  function processIncoming(IERC20 token_, address from, uint256 amount, bytes calldata ref) external payable returns (uint256);

  function getBuyPrice(uint256 shares) external view returns (uint256);

  function getSellPrice(uint256 shares) external view returns (uint256);

}
IUniswapV3.sol 123 lines
// SPDX-License-Identifier: MIT
// Copied from 
// https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/interfaces/IQuoter.sol
// https://github.com/Uniswap/v3-periphery/blob/main/contracts/interfaces/ISwapRouter.sol
pragma solidity ^0.8.0;

import "../ERC20/IERC20.sol";

interface IQuoter {

    /// @notice Returns the amount in required to receive the given exact output amount but for a swap of a single pool
    /// @param tokenIn The token being swapped in
    /// @param tokenOut The token being swapped out
    /// @param fee The fee of the token pool to consider for the pair
    /// @param amountOut The desired output amount
    /// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
    /// @return amountIn The amount required as the input for the swap in order to receive `amountOut`
    function quoteExactOutputSingle(
        address tokenIn,
        address tokenOut,
        uint24 fee,
        uint256 amountOut,
        uint160 sqrtPriceLimitX96
    ) external returns (uint256 amountIn);

    /// @notice Returns the amount in required for a given exact output swap without executing the swap
    /// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
    /// @param amountOut The amount of the last token to receive
    /// @return amountIn The amount of first token required to be paid
    function quoteExactOutput(bytes memory path, uint256 amountOut) external returns (uint256 amountIn);

    /// @notice Returns the amount out received for a given exact input swap without executing the swap
    /// @param path The path of the swap, i.e. each token pair and the pool fee
    /// @param amountIn The amount of the first token to swap
    /// @return amountOut The amount of the last token that would be received
    function quoteExactInput(bytes memory path, uint256 amountIn) external returns (uint256 amountOut);

    /// @notice Returns the amount out received for a given exact input but for a swap of a single pool
    /// @param tokenIn The token being swapped in
    /// @param tokenOut The token being swapped out
    /// @param fee The fee of the token pool to consider for the pair
    /// @param amountIn The desired input amount
    /// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
    /// @return amountOut The amount of `tokenOut` that would be received
    function quoteExactInputSingle(
        address tokenIn,
        address tokenOut,
        uint24 fee,
        uint256 amountIn,
        uint160 sqrtPriceLimitX96
    ) external returns (uint256 amountOut);
    
    // solhint-disable-next-line func-name-mixedcase
    function WETH9() external view returns (address);
}

interface ISwapRouter {
    struct ExactOutputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountOut;
        uint256 amountInMaximum;
        uint160 sqrtPriceLimitX96;
    }

    function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);

    struct ExactOutputParams {
        bytes path;
        address recipient;
        uint256 deadline;
        uint256 amountOut;
        uint256 amountInMaximum;
    }

    /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
    /// @return amountIn The amount of the input token
    function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn);

    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Swaps `amountIn` of one token for as much as possible of another token
    /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);

    struct ExactInputParams {
        bytes path;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
    }

    /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);
    
    function refundETH() external payable;
}

/// @title Interface for WETH9
interface IWETH9 is IERC20 {
    /// @notice Deposit ether to get wrapped ether
    function deposit() external payable;

    /// @notice Withdraw wrapped ether to get ether
    function withdraw(uint256) external;
}
PaymentHub.sol 506 lines
/**
* SPDX-License-Identifier: LicenseRef-Aktionariat
*
* MIT License with Automated License Fee Payments
*
* Copyright (c) 2022 Aktionariat AG (aktionariat.com)
*
* Permission is hereby granted to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* - The above copyright notice and this permission notice shall be included in
*   all copies or substantial portions of the Software.
* - All automated license fee payments integrated into this and related Software
*   are preserved.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity ^0.8.0;

import "../utils/Address.sol";
import "../ERC20/IERC20.sol";
import "../ERC20/IERC20Permit.sol";
import "./IUniswapV3.sol";
import "../utils/Ownable.sol";
import "./IBrokerbot.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "../utils/SafeERC20.sol";

/**
 * A hub for payments. This allows tokens that do not support ERC 677 to enjoy similar functionality,
 * namely interacting with a token-handling smart contract in one transaction, without having to set an allowance first.
 * Instead, an allowance needs to be set only once, namely for this contract.
 * Further, it supports automatic conversion from Ether to the payment currency through Uniswap or the reception of Ether
 * using the current exchange rate as found in the chainlink oracle.
 */
contract PaymentHub {

    using SafeERC20 for IERC20;

    // Version history
    // Version 4: added path to pay with any ecr20 via uniswap
    // Version 5: added sell via permit
    // Version 6: added transferEther function
    // Version 7: added sell against eth and erc20, version, add permitinfo/swapinfo struct
    // Version 8: use SafeERC20 for transfers
    // Version 9: change payFromEther to include a swap path
    uint256 public constant VERSION = 9;

    uint256 private constant KEEP_ETHER = 0x4; // copied from brokerbot

    uint256 private constant DENOMINATOR = 1e8;
    address private constant XCHF_TOKEN = 0xB4272071eCAdd69d933AdcD19cA99fe80664fc08;
    address private constant ZCHF_TOKEN = 0xB58E61C3098d85632Df34EecfB899A1Ed80921cB;

    IQuoter private immutable uniswapQuoter;
    ISwapRouter private immutable uniswapRouter;
    AggregatorV3Interface internal immutable priceFeedCHFUSD;
    AggregatorV3Interface internal immutable priceFeedETHUSD;

    address public trustedForwarder;

    struct PermitInfo {
        uint256 exFee;
        uint256 deadline;
        uint8 v;
        bytes32 r;
        bytes32 s;
    }

    // event to when new forwarder is set
    event ForwarderChanged(address indexed _oldForwarder, address indexed _newForwarder);

	/*//////////////////////////////////////////////////////////////
                            Custom errors
    //////////////////////////////////////////////////////////////*/
    /// Failguard when an erc20 transfer returns false. 
    error PaymentHub_TransferFailed();
    /// Sender not trusted.
    /// @param sender The msg.sender of this transaction.
    error PaymentHub_InvalidSender(address sender);
    /// swap with less base token as required.
    /// @param amountBase Required amount.
    /// @param swappedAmount Swapped amount.
    error PaymentHub_SwapError(uint256 amountBase, uint256 swappedAmount);

    constructor(address _trustedForwarder, IQuoter _quoter, ISwapRouter swapRouter, AggregatorV3Interface _aggregatorCHFUSD, AggregatorV3Interface _aggregatorETHUSD) {
        trustedForwarder = _trustedForwarder;
        uniswapQuoter = _quoter;
        uniswapRouter = swapRouter;
        priceFeedCHFUSD = _aggregatorCHFUSD;
        priceFeedETHUSD = _aggregatorETHUSD;
    }

    modifier onlySellerAndForwarder(address seller) {
        if (msg.sender != trustedForwarder && msg.sender != seller) {
            revert PaymentHub_InvalidSender(msg.sender);
        }
        _;
    }

    modifier onlyForwarder() {
        if (msg.sender != trustedForwarder) {
            revert PaymentHub_InvalidSender(msg.sender);
        }
        _;
    }

    /**
     * @notice Change the trusted forwarder.
     * @param newForwarder The new trusted forwarder.
     */
     function changeForwarder(address newForwarder) external onlyForwarder {
        trustedForwarder = newForwarder;
        emit ForwarderChanged(msg.sender, newForwarder);
    }

    /**  
     * Get price in ERC20
     * This is the method that the Brokerbot widget should use to quote the price to the user.
     * @param amountInBase The amount of the base currency for the exact output.
     * @param path The encoded path of the swap from erc20 to base.
     * @return amount quoted to pay
     */
    function getPriceInERC20(uint256 amountInBase, bytes memory path) public returns (uint256) {
        return getPriceERC20(amountInBase, path, true);
    }
    
    /**
     * @notice Get price for given amount and path swapped via uniswap. 
     * @param amount The exact amount which you want get out (exactOutput) or you put in (exactInput).
     * @param path The path of the swap (inreverse order for exactOutput).
     * @param exactOutput True if exactOutput should be used or false if exactInput should be used.
     */
    function getPriceERC20(uint256 amount, bytes memory path, bool exactOutput) public returns (uint256) {
        if (exactOutput) {
            return uniswapQuoter.quoteExactOutput(
                path,
                amount
            );
        } else {
            return uniswapQuoter.quoteExactInput(
                path,
                amount
            );
        }
    }

    /**
     * Get price in Ether depding on brokerbot setting.
     * If keep ETH is set price is from oracle.
     * This is the method that the Brokerbot widget should use to quote the price to the user.
     * @return The price in wei.
     */
    function getPriceInEther(uint256 amountInBase, IBrokerbot brokerBot, bytes calldata path) public returns (uint256) {
        if ((address(brokerBot) != address(0)) && hasSettingKeepEther(brokerBot)) {
            return getPriceInEtherFromOracle(amountInBase, IBrokerbot(brokerBot).base());
        } else {
            return getPriceERC20(amountInBase, path, true);
        }
    }

    /**
     * Price in ETH with 18 decimals
     */
    function getPriceInEtherFromOracle(uint256 amountInBase, IERC20 base) public view returns (uint256) {
        if(address(base) == XCHF_TOKEN || address(base) == ZCHF_TOKEN) {
            return getLatestPriceCHFUSD() * amountInBase / getLatestPriceETHUSD();
        } else {
            return amountInBase * DENOMINATOR / getLatestPriceETHUSD();
        }
    }

    /**
     * Returns the latest price of eth/usd pair from chainlink with 8 decimals
     */
    function getLatestPriceETHUSD() public view returns (uint256) {
        (, int256 price, , , ) = priceFeedETHUSD.latestRoundData();
        return uint256(price);
    }

    /**
     * Returns the latest price of chf/usd pair from chainlink with 8 decimals
     */
    function getLatestPriceCHFUSD() public view returns (uint256) {
        (, int256 price, , , ) = priceFeedCHFUSD.latestRoundData();
        return uint256(price);
    }

    /**
     * Convenience method to swap ether into base and pay a target address
     */
    function payFromEther(address recipient, uint256 amountInBase, bytes memory path) public payable returns (uint256 amountIn) {
        ISwapRouter swapRouter = uniswapRouter;
        // The parameter path is encoded as (tokenOut, fee, tokenIn/tokenOut, fee, tokenIn)
        ISwapRouter.ExactOutputParams memory params =
            ISwapRouter.ExactOutputParams({
                path: path,
                recipient: recipient,
                // solhint-disable-next-line not-rely-on-time
                deadline: block.timestamp,
                amountOut: amountInBase,
                amountInMaximum: msg.value
            });

        // Executes the swap, returning the amountIn actually spent.
        amountIn = swapRouter.exactOutput{value: msg.value}(params);

        // For exact output swaps, the amountInMaximum may not have all been spent.
        // If the actual amount spent (amountIn) is less than the specified maximum amount, we must refund the msg.sender and approve the swapRouter to spend 0.
        if (amountIn < msg.value) {
            swapRouter.refundETH();
            (bool success, ) = msg.sender.call{value:msg.value - amountIn}(""); // return change
            if (!success) {
                revert PaymentHub_TransferFailed();
            }
        }
    }

    /// @dev The calling address must approve this contract to spend its ERC20 for this function to succeed. As the amount of input ERC20 is variable,
    /// the calling address will need to approve for a slightly higher or infinit amount, anticipating some variance.
    /// @param amountOut The desired amount of baseCurrency.
    /// @param amountInMaximum The maximum amount of ERC20 willing to be swapped for the specified amountOut of baseCurrency.
    /// @param erc20In The address of the erc20 token to pay with.
    /// @param path The encoded path of the swap from erc20 to base.
    /// @param recipient The reciving address - brokerbot.
    /// @return amountIn The amountIn of ERC20 actually spent to receive the desired amountOut.
    function payFromERC20(uint256 amountOut, uint256 amountInMaximum, address erc20In, bytes memory path, address recipient) public returns (uint256 amountIn) {
        ISwapRouter swapRouter = uniswapRouter;
        // Transfer the specified `amountInMaximum` to this contract.
        IERC20(erc20In).safeTransferFrom(msg.sender, address(this), amountInMaximum);

        // The parameter path is encoded as (tokenOut, fee, tokenIn/tokenOut, fee, tokenIn)
        ISwapRouter.ExactOutputParams memory params =
            ISwapRouter.ExactOutputParams({
                path: path,
                recipient: recipient,
                // solhint-disable-next-line not-rely-on-time
                deadline: block.timestamp,
                amountOut: amountOut,
                amountInMaximum: amountInMaximum
            });

        // Executes the swap, returning the amountIn actually spent.
        amountIn = swapRouter.exactOutput(params);

        // If the swap did not require the full amountInMaximum to achieve the exact amountOut then we refund msg.sender and approve the router to spend 0.
        if (amountIn < amountInMaximum) {
            IERC20(erc20In).safeTransfer(msg.sender, amountInMaximum - amountIn);
        }
    }

    ///This function appoves infinite allowance for Uniswap, this is safe as the paymenthub should never hold any token (see also recover() ).
    ///@dev This function needs to be called before using the PaymentHub the first time with a new ERC20 token.
    ///@param erc20In The erc20 addresse to approve.
    function approveERC20(address erc20In) external {
        IERC20(erc20In).approve(address(uniswapRouter), type(uint256).max);
    }

    function multiPay(IERC20 token, address[] calldata recipients, uint256[] calldata amounts) public {
        for (uint i=0; i<recipients.length; i++) {
            IERC20(token).safeTransferFrom(msg.sender, recipients[i], amounts[i]);
        }
    }

    /**
     * Can (at least in theory) save some gas as the sender balance only is touched in one transaction.
     */
    function multiPayAndNotify(IERC20 token, IBrokerbot[] calldata brokerbots, uint256[] calldata amounts, bytes calldata ref) external {
        for (uint i=0; i<brokerbots.length; i++) {
            payAndNotify(token, brokerbots[i], amounts[i], ref);
        }
    }

    /**
     * @notice Allows to make a base currency payment from the sender to the brokerbot, given an allowance to this contract.
     * @dev Equivalent to xchf.transferAndCall(brokerbot, amountInBase)
     * @param brokerbot The brokerbot to pay and receive the shares from.
     * @param amountInBase The amount of base currency used to buy shares.
     * @param ref The reference data blob.
     * @return The amount of shares bought
     */
    function payAndNotify(IBrokerbot brokerbot, uint256 amountInBase, bytes calldata ref) external returns (uint256) {
        return payAndNotify(brokerbot.base(), brokerbot, amountInBase, ref);
    }

    function payAndNotify(IERC20 token, IBrokerbot brokerbot, uint256 amount, bytes calldata ref) public returns (uint256) {
        token.safeTransferFrom(msg.sender, address(brokerbot), amount);
        return brokerbot.processIncoming(token, msg.sender, amount, ref);
    }

    /**
     * @notice Pay with Ether to buy shares.
     * @param brokerbot The brokerbot to pay and receive the shares from.
     * @param amountBase The amount of base currency used to buy shares.
     * @param ref The reference data blob.
     * @param path The Uniswap path from ETH to base currency (uses exactOuput => reverse order)
     * @return priceInEther The amount of Ether spent.
     * @return sharesOut The amount of shares bought.
     */
    function payFromEtherAndNotify(IBrokerbot brokerbot, uint256 amountBase, bytes calldata ref, bytes memory path) external payable returns (uint256 priceInEther, uint256 sharesOut) {
        IERC20 base = brokerbot.base();
        // Check if the brokerbot has setting to keep ETH
        if (hasSettingKeepEther(brokerbot)) {
            priceInEther = getPriceInEtherFromOracle(amountBase, base);
            sharesOut = brokerbot.processIncoming{value: priceInEther}(base, msg.sender, amountBase, ref);

            // Pay back ETH that was overpaid
            if (priceInEther < msg.value) {
                (bool success, ) = msg.sender.call{value:msg.value - priceInEther}(""); // return change
                if (!success) {
                    revert PaymentHub_TransferFailed();
                }
            }

        } else {
            uint256 balanceBefore = IERC20(base).balanceOf(address(brokerbot));
            priceInEther = payFromEther(address(brokerbot), amountBase, path);
            uint256 balanceAfter = IERC20(base).balanceOf(address(brokerbot));
            if (amountBase != (balanceAfter - balanceBefore)) { // check that the swap was successful with correct currency
                revert PaymentHub_SwapError(amountBase, balanceAfter - balanceBefore);
            }    
            sharesOut = brokerbot.processIncoming(base, msg.sender, amountBase, ref); // not sending msg.value as this is already done in payFromEther function
        }
    }

    /***
     * @notice Pay from any ERC20 token (which has Uniswapv3 ERC20-ETH pool) and send swapped base currency to brokerbot.
     * @notice The needed amount needs to be approved at the ERC20 contract beforehand
     * @param brokerbot The brokerbot to pay and receive the shares from.
     * @param amountBase The amount of base currency used to buy shares.
     * @param erc20 The address of the ERC20 token to pay.
     * @param amountInMaximum The maximum amount of the ERC20 to pay (should include some slippage).
     * @param path The encoded path of the swap from erc20 to base currency.
     * @param ref Reference data blob.
     * @return amountIn The amount erc20 spent to buy shares.
     * @return amountOut The amount of shares received by the brokerbot.
     */
    function payFromERC20AndNotify(IBrokerbot brokerbot, uint256 amountBase, address erc20, uint256 amountInMaximum, bytes memory path, bytes calldata ref) external returns (uint256 amountIn, uint256 amountOut) {
        IERC20 base = brokerbot.base();
        uint256 balanceBefore = IERC20(base).balanceOf(address(brokerbot));
        amountIn = payFromERC20(amountBase, amountInMaximum, erc20, path, address(brokerbot));
        uint256 balanceAfter = IERC20(base).balanceOf(address(brokerbot));
        if (amountBase != (balanceAfter - balanceBefore)) {
            revert PaymentHub_SwapError(amountBase, balanceAfter - balanceBefore);
        }        
        amountOut = brokerbot.processIncoming(base, msg.sender, balanceAfter - balanceBefore, ref);
    }

    /**
     * @notice Sell shares with permit
     * @param brokerbot The brokerbot to recive the shares.
     * @param seller The address of the seller.
     * @param recipient The address of the recipient of the sell preceeds.
     * @param amountToSell The amount the seller wants to sell.
     * @param ref Reference e.g. insider declaration and the type of sell.
     * @param permitInfo Information about the permit.
     * @return The base currency amount for the selling of the shares.
     */
    function sellSharesWithPermit(IBrokerbot brokerbot, IERC20Permit shares, address seller, address recipient, uint256 amountToSell, bytes calldata ref, PermitInfo calldata permitInfo) public onlySellerAndForwarder(seller) returns (uint256) {
        // Call permit to set allowance
        shares.permit(seller, address(this), amountToSell, permitInfo.deadline, permitInfo.v, permitInfo.r,permitInfo.s);
        // process sell
        if (permitInfo.exFee > 0){
            uint256 proceeds = _sellShares(brokerbot, shares, seller, address(this), amountToSell, ref);
            IERC20 currency = brokerbot.base();
            currency.safeTransfer(msg.sender, permitInfo.exFee);
            currency.safeTransfer(recipient, proceeds - permitInfo.exFee);
            return proceeds - permitInfo.exFee;
        } else {
            return _sellShares(brokerbot, shares, seller, recipient, amountToSell, ref);
        }
    }

    /**
     * @notice With this function a user can sell shares with permit and swap them to a desired token.
     * @param brokerbot The brokerbot of the shares to sell.
     * @param shares The (draggable)shares address.
     * @param seller The seller address.
     * @param amountToSell The amount of shares to sell.
     * @param ref Reference e.g. insider declaration and the type of sell.
     * @param permitInfo Information about the permit.
     * @param params Information about the swap.
     * @return The output amount of the swap to the desired token.
     */
    function sellSharesWithPermitAndSwap(IBrokerbot brokerbot, IERC20Permit shares, address seller,  uint256 amountToSell, bytes calldata ref, PermitInfo calldata permitInfo, ISwapRouter.ExactInputParams memory params, bool unwrapWeth) external onlySellerAndForwarder(seller) returns (uint256) {
        params.amountIn = sellSharesWithPermit(brokerbot, shares, seller, address(this), amountToSell, ref, permitInfo);
        return _swap(params, unwrapWeth);
    }

    /**
     * @notice With this function a user can sell shares and swap them to a desired token. The user has to approve the paymenthub before on the shares contract.
     * @param brokerbot The brokerbot of the shares to sell.
     * @param shares The (draggable)shares address.
     * @param amountToSell The amount of shares to sell.
     * @param ref Reference e.g. insider declaration and the type of sell.
     * @param params Information about the swap.
     * @return The output amount of the swap to the desired token.
     */
    function sellSharesAndSwap(IBrokerbot brokerbot, IERC20 shares, uint256 amountToSell,  bytes calldata ref, ISwapRouter.ExactInputParams memory params, bool unwrapWeth) external returns (uint256) {
        params.amountIn = _sellShares(brokerbot, shares, msg.sender, address(this), amountToSell, ref);
        return _swap(params, unwrapWeth);
    }

    /**
     * @notice Transfers shares to brokerbot and executes the selling.
     * @param brokerbot The brokerbot of the shares to sell.
     * @param shares The (draggable)shares address.
     * @param seller The seller address.
     * @param recipient The recipient of the base currency tokens. (this can be a 3rd party to off-ramp or the paymenthub itself if a swap will be done direct after)
     * @param amountToSell The amount of shares to sell.
     * @param ref Reference e.g. insider declaration and the type of sell.
     * @return The base currency amount for the selling of the shares.
     */ 
    function _sellShares(IBrokerbot brokerbot, IERC20 shares, address seller, address recipient, uint256 amountToSell, bytes calldata ref ) internal returns (uint256) {
        // send shares token to brokerbot
        shares.safeTransferFrom(seller, address(brokerbot), amountToSell);
        // process sell on brokerbot
        return brokerbot.processIncoming(shares, recipient, amountToSell, ref);
    }

    /**
     * @notice Swap (base currency) token according to given path and unwrap weth if needed.
     * @param params Information about the swap (includes path).
     * @return amountOut The output amount of the swap to the desired token.
     */
    function _swap(ISwapRouter.ExactInputParams memory params, bool unwrapWeth) internal returns(uint256 amountOut) {
        // if weth should be unwrapped, swap recipient is this contract and eth is send to seller
        if (unwrapWeth){
            address seller = params.recipient;
            params.recipient = address(this);
            amountOut = _swapToERC20(params);
            IWETH9(uniswapQuoter.WETH9()).withdraw(amountOut);
            (bool success, ) = payable(seller).call{value:amountOut}("");
            if (!success) revert PaymentHub_TransferFailed();
        } else {
            amountOut = _swapToERC20(params);
        }
    }

    /**
     * @notice Calls the Uniswap router to swap tokens according to given path.
     * @param params Information about the swap (includes path).
     * @return amountOut The output amount of the swap to the desired token.
     */
    function _swapToERC20(ISwapRouter.ExactInputParams memory params) internal returns(uint256 amountOut) {
        amountOut = uniswapRouter.exactInput(params);
        if (amountOut < params.amountOutMinimum){
            revert PaymentHub_SwapError(params.amountOutMinimum, amountOut);
        }
    }

    /**
     * Checks if the brokerbot has setting enabled to keep ether
     */
    function hasSettingKeepEther(IBrokerbot brokerbot) public view returns (bool) {
        return brokerbot.settings() & KEEP_ETHER == KEEP_ETHER;
    }

    /**
     * @notice In case tokens have been accidentally sent directly to this contract. Only Forwarder can withdraw, else a MEV bot will intercept it.
     * @param ercAddress The erc20 address.
     * @param to The address to transfer tokens to.
     * @param amount The amount of tokens to transfer.
     */
    function recover(IERC20 ercAddress, address to, uint256 amount) external onlyForwarder {
        ercAddress.safeTransfer(to, amount);
    }

    /**
     * @notice Transfer ether to a given address. Only Forwarder can withdraw, else a MEV bot will intercept it.
     * @param to The address to transfer ether to.
     */
    function withdrawEther(address to, uint256 amount) external onlyForwarder {
        (bool success, ) = payable(to).call{value:amount}("");
        if (!success) {
            revert PaymentHub_TransferFailed();
        }
    }

    /**
     * @notice Transfer ether to a given address.
     * @dev Used with the mutlisigwallet.
     * @param to The address to transfer ether to.
     */
    function transferEther(address to) external payable {
        (bool success, ) = payable(to).call{value:msg.value}("");
        if (!success) {
            revert PaymentHub_TransferFailed();
        }
    }

    // solhint-disable-next-line no-empty-blocks
    receive() external payable {
        // Important to receive ETH refund from Uniswap
    }
}
AggregatorV3Interface.sol 35 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface AggregatorV3Interface {
  function decimals() external view returns (uint8);

  function description() external view returns (string memory);

  function version() external view returns (uint256);

  // getRoundData and latestRoundData should both raise "No data present"
  // if they do not have data to report, instead of returning unset values
  // which could be misinterpreted as actual reported values.
  function getRoundData(uint80 _roundId)
    external
    view
    returns (
      uint80 roundId,
      int256 answer,
      uint256 startedAt,
      uint256 updatedAt,
      uint80 answeredInRound
    );

  function latestRoundData()
    external
    view
    returns (
      uint80 roundId,
      int256 answer,
      uint256 startedAt,
      uint256 updatedAt,
      uint80 answeredInRound
    );
}

Read Contract

VERSION 0xffa1ad74 → uint256
getLatestPriceCHFUSD 0x00124e3d → uint256
getLatestPriceETHUSD 0x95fe863c → uint256
getPriceInEtherFromOracle 0x1945b784 → uint256
hasSettingKeepEther 0xc1d8e08a → bool
trustedForwarder 0x7da0a877 → address

Write Contract 19 functions

These functions modify contract state and require a wallet transaction to execute.

approveERC20 0x11e8701c
address erc20In
changeForwarder 0x415a2187
address newForwarder
getPriceERC20 0x88e3b76a
uint256 amount
bytes path
bool exactOutput
returns: uint256
getPriceInERC20 0x078aa964
uint256 amountInBase
bytes path
returns: uint256
getPriceInEther 0x59c57ee2
uint256 amountInBase
address brokerBot
bytes path
returns: uint256
multiPay 0x1c9af2e3
address token
address[] recipients
uint256[] amounts
multiPayAndNotify 0x568f3254
address token
address[] brokerbots
uint256[] amounts
bytes ref
payAndNotify 0x17f565d2
address brokerbot
uint256 amountInBase
bytes ref
returns: uint256
payAndNotify 0xd3b3ca77
address token
address brokerbot
uint256 amount
bytes ref
returns: uint256
payFromERC20 0xd64d4ba5
uint256 amountOut
uint256 amountInMaximum
address erc20In
bytes path
address recipient
returns: uint256
payFromERC20AndNotify 0x5daf206a
address brokerbot
uint256 amountBase
address erc20
uint256 amountInMaximum
bytes path
bytes ref
returns: uint256, uint256
payFromEther 0xfb4daeec
address recipient
uint256 amountInBase
bytes path
returns: uint256
payFromEtherAndNotify 0x435fd56d
address brokerbot
uint256 amountBase
bytes ref
bytes path
returns: uint256, uint256
recover 0x1ec82cb8
address ercAddress
address to
uint256 amount
sellSharesAndSwap 0x991f7849
address brokerbot
address shares
uint256 amountToSell
bytes ref
tuple params
bool unwrapWeth
returns: uint256
sellSharesWithPermit 0xbe8325fe
address brokerbot
address shares
address seller
address recipient
uint256 amountToSell
bytes ref
tuple permitInfo
returns: uint256
sellSharesWithPermitAndSwap 0x17370200
address brokerbot
address shares
address seller
uint256 amountToSell
bytes ref
tuple permitInfo
tuple params
bool unwrapWeth
returns: uint256
transferEther 0x6b55e991
address to
withdrawEther 0x522f6815
address to
uint256 amount

Recent Transactions

No transactions found for this address