Cryo Explorer Ethereum Mainnet

Address Contract Verified

Address 0xFB9167A8b5Cb585202953c6d5537A7D640c43a96
Balance 0 ETH
Nonce 20
Code Size 11386 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

11386 bytes
0x60a06040526004361015610011575f80fd5b5f3560e01c80638f9ff8d3146100305763be65ab8c036100565761021e565b61015c565b6001600160a01b031690565b90565b6001600160a01b0381165b0361005657565b5f80fd5b9050359061006782610044565b565b6001600160a01b03811661004f565b9050359061006782610069565b8061004f565b9050359061006782610085565b6101a081830312610056576100ad828261005a565b926100bb836020840161005a565b926100c98160408501610078565b926100d78260608301610078565b926100e5836080840161005a565b926100f38160a0850161005a565b926101018260c0830161005a565b9261010f8360e0840161008b565b9261011e81610100850161008b565b9261012d82610120830161008b565b9261004161013f84610140850161008b565b9361018061015182610160870161008b565b940161008b565b9052565b34610056576101a1610187610172366004610098565b9b9a909a9991999892989793979694966104c1565b604051918291826001600160a01b03909116815260200190565b0390f35b90602082820312610056576100419161005a565b61004190610035906001600160a01b031682565b610041906101b9565b610041906101cd565b906101e9906101d6565b5f5260205260405f2090565b610041916008021c81565b9061004191546101f5565b5f61021961004192826101df565b610200565b34610056576101a16102396102343660046101a5565b61020b565b6040519182918290815260200190565b634e487b7160e01b5f52604160045260245ffd5b90601f01601f1916810190811067ffffffffffffffff82111761027f57604052565b610249565b60ff811661004f565b9050519061006782610284565b90602082820312610056576100419161028d565b6040513d5f823e3d90fd5b610158906101d6565b60808190526101c081019f9e9d9c9b9a9998979695949392916102e591906102b9565b6080516020016102f4916102b9565b608051604001610303916102b9565b60805160600161031a916001600160a01b03169052565b608051608001610331916001600160a01b03169052565b60805160a001610348916001600160a01b03169052565b60805160c0016103599160ff169052565b60805160e00161036a9160ff169052565b608051610100015260805161012001526080516101400152608051610160015260805161018001526080516101a00152565b6100419081565b610041905461039c565b634e487b7160e01b5f52601160045260245ffd5b5f1981146103cf5760010190565b6103ad565b6100416100416100419290565b906100416100416103f1926103d4565b9055565b6001600160a01b0390911681526040810192916100679160200152565b9061006761041f60405190565b928361025d565b90825f9392825e0152565b61045261044992602092610443815190565b94859290565b93849101610426565b0190565b610464906100419392610431565b90610431565b61048b6104946020936104529361047f815190565b80835293849260200190565b95869101610426565b601f01601f191690565b6001600160a01b039091168152610041916040820191602081840391015261046a565b9199959b97939b9a96929a9894986104d65f90565b506001600160a01b0382166001600160a01b038416146106ec576105016104fc846101d6565b6101d6565b9263313ce567602061051260405190565b809661051e8460e01b90565b825260049082905afa9384156106bb57610555955f956106c0575b50906105496104fc6020936101d6565b60405196879260e01b90565b825260049082905afa9384156106bb575f94610686575b50610576906101d6565b9961058060405190565b9d8e9d60208f019d6105929d8f6102c2565b9081038252036105a2908261025d565b6105ac335f6101df565b6105b5816103a3565b906105bf826103c1565b6105c8916103e1565b6040518091602082019033906105de91836103f5565b9081038252036105ee908261025d565b805190602001208161248061060560208201610412565b908082526107c5602083013960405191829160208301916106269183610456565b908103825203610636908261025d565b61063f5f6103d4565b9161064992610728565b9061065360405190565b61065f8192848361049e565b037f4c7817f70b9a7e647a0a4702478086204d451af9bb29ac7686d7fb47da4ed71991a190565b6105769194506106ad9060203d6020116106b4575b6106a5818361025d565b81019061029a565b939061056c565b503d61069b565b6102ae565b6020929195506104fc6106e261054992853d87116106b4576106a5818361025d565b9692935050610539565b632159402760e21b5f90815260045b035ffd5b9081526040810192916100679160200152565b6100356100416100419290565b61004190610712565b909190610734306101d6565b818131610740565b9190565b106107ab5750815161075461073c5f6103d4565b1461079a5760208251920190f5903d15198215166102ae576107786100355f61071f565b6001600160a01b0383161461078957565b63b06ebf3d60e01b5f908152600490fd5b631328927760e21b5f908152600490fd5b63cf47918160e01b5f908152916106fb913160046106ff56fe610280604052346101ba5761002d610015610352565b9c9b909b9a919a9992999893989794979695966104f1565b604051611bf861088882396080518181816104f701528181610b1601528181610d5a01528181611052015261133c015260a0518181816108c70152818161155001526117c8015260c05181818161095e015281816115c4015261183b015260e0518181816106f801528181610bba01526113a801526101005181818161092501528181610a3e01528181610fb2015281816111730152818161130701526113ff0152610120518181816106450152610ce401526101405181818161043a015281816110bb015261199301526101605181818161040101526116970152610180518181816109d00152610ef301526101a0518181816102c1015261145101526101c0518181816105f301528181610f7f01526119fa01526101e051818181610473015281816111df015261128201526102005181818161079a0152818161173101526118f201526102205181818161099701528181611702015261191c01526102405181818161088e0152818161163b01526118b201526102605181818161080001526116d20152611bf890f35b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b90601f01601f191681019081106001600160401b038211176101f357604052565b6101be565b9061020c61020560405190565b92836101d2565b565b6001600160a01b031690565b90565b6001600160a01b0381165b036101ba57565b9050519061020c8261021d565b6001600160a01b038116610228565b9050519061020c8261023c565b60ff8116610228565b9050519061020c82610258565b80610228565b9050519061020c8261026e565b91906101c0838203126101ba57610298818461022f565b926102a6826020830161022f565b926102b4836040840161022f565b926102c2816060850161024b565b926102d0826080830161024b565b926102de8360a0840161024b565b926102ec8160c08501610261565b926102fa8260e08301610261565b92610309836101008401610274565b92610318816101208501610274565b92610327826101408301610274565b9261021a610339846101608501610274565b936101a061034b826101808701610274565b9401610274565b61037061248080380380610365816101f8565b928339810190610281565b909192939495969798999a9b9c9d565b61020e61021a61021a9290565b61021a90610380565b61021a9061020e906001600160a01b031682565b61021a90610396565b61021a906103aa565b61021a61021a61021a9290565b61021a6305f5e1006103bc565b61021a620151806103bc565b906020828203126101ba5761021a91610261565b6040513d5f823e3d90fd5b61040e61021a61021a9290565b60ff1690565b61021a6006610401565b6001600160501b038116610228565b9050519061020c8261041e565b919060a0838203126101ba57610450818461042d565b9261045e8260208301610274565b9261021a61046f8460408501610274565b93608061047f8260608701610274565b940161042d565b634e487b7160e01b5f52601160045260245ffd5b818102929181159184041417156104ad57565b610486565b60ff16604d81116104ad57600a0a90565b634e487b7160e01b5f52601260045260245ffd5b906104e1565b9190565b9081156104ec570490565b6104c3565b6104fa5f61038d565b6001600160a01b0381166001600160a01b03881614610876576001600160a01b0381166001600160a01b03871614610876576001600160a01b0381166001600160a01b038616146108765761056e610561610554846103b3565b926001600160a01b031690565b916001600160a01b031690565b146108765761057c5f6103bc565b89146108655761058d61021a6103c9565b8a116108545761059b6103d6565b808c1061084357808d1061084357808e10610843578e10610843578c8e11610832576105c6826103b3565b63313ce5679060206105d760405190565b80926105e38560e01b90565b825260049082905afa908115610771575f91610813575b5061024052610608846103b3565b9061061c61061560405190565b9160e01b90565b8152602081600481855afa908115610771575f916107e4575b5061026052610642610414565b60ff811660ff8a16109081156107d3575b81156107b1575b8115610787575b506107765760a061067160405190565b633fabe5a360e21b815291829060049082905afa908115610771575f9161073f575b5061069d5f6103bc565b81131561072f576106b96106b36106d4926103bc565b8b61049a565b6106ce6106c96102605160ff1690565b6104b2565b906104d7565b6106e36104dd61021a8b6104b2565b1061071e5760805260a05260c05260e052610100526101205261020052610220526101405261016052610180526101a0526101c0526101e052565b63cde8c83560e01b5f908152600490fd5b62bfc92160e01b5f908152600490fd5b610761915060a03d60a01161076a575b61075981836101d2565b81019061043a565b50505090610693565b503d61074f565b6103f6565b6370c5eb6160e11b5f908152600490fd5b90506107aa6107a361079c6102605160ff1690565b9260ff1690565b9160ff1690565b105f610661565b90506107c06102405160ff1690565b6107cc60ff83166107a3565b109061065a565b905060ff811660ff8b161090610653565b610806915060203d60201161080c575b6107fe81836101d2565b8101906103e2565b5f610635565b503d6107f4565b61082c915060203d60201161080c576107fe81836101d2565b5f6105fa565b6325c6702760e11b5f908152600490fd5b6325c3636760e01b5f908152600490fd5b63c189a4d360e01b5f908152600490fd5b631f2a200560e01b5f908152600490fd5b63d92e233d60e01b5f908152600490fdfe60806040526004361015610011575f80fd5b5f3560e01c80630bbfc8331461024057806314e853b51461023b57806324938e9f146102365780632ae4e9311461023157806333d263f21461022c5780633e032a3b146102275780633e52531a14610222578063414a62791461021d57806341f1d3c1146102185780634460d3cf146102135780634aca04a81461020e5780634e71d92d14610209578063607af3971461020457806364cbe5f9146101ff57806366d003ac146101fa5780636eb6a820146101f55780638129fc1c146101f05780639049a1e3146101eb578063943640c3146101e657806395098763146101e157806398018b8b146101dc5780639cfbce51146101d7578063aa5c9691146101d2578063ab579f45146101cd578063ad9161cc146101c8578063b001a482146101c3578063b6a6d177146101be578063b7d03c8f146101b9578063c797af7e146101b4578063e5945ad7146101af578063e6fd48bc146101aa578063ef4a57d1146101a5578063efb1ad5d146101a0578063f47083bc1461019b5763fdea36570361024f576109bb565b610982565b610949565b610910565b6108f5565b6108b2565b610879565b61085e565b610838565b6107eb565b6107d0565b610785565b61076a565b61074f565b610734565b61071c565b6106e3565b6106cb565b61068c565b610671565b610630565b6105de565b6105c3565b6105a0565b610585565b61055d565b6104e2565b61045e565b610425565b6103ec565b6103bd565b610371565b610307565b6102ac565b61027d565b5f91031261024f57565b5f80fd5b61025e916008021c81565b90565b9061025e9154610253565b61025e5f6002610261565b9052565b565b3461024f5761028d366004610245565b6102a861029861026c565b6040519182918290815260200190565b0390f35b3461024f576102bc366004610245565b6102a87f0000000000000000000000000000000000000000000000000000000000000000610298565b61025e61025e61025e9290565b61025e6305f5e1006102e5565b61025e6102f2565b3461024f57610317366004610245565b6102a86102986102ff565b634e487b7160e01b5f52602160045260245ffd5b6004111561034057565b610322565b9061027b82610336565b61025e90610345565b6102779061034f565b60208101929161027b9190610358565b3461024f57610381366004610245565b6102a861038c610a07565b60405191829182610361565b6103a561025e61025e9290565b60ff1690565b61025e6006610398565b61025e6103ab565b3461024f576103cd366004610245565b6102a86103d86103b5565b6040519182918260ff909116815260200190565b3461024f576103fc366004610245565b6102a87f0000000000000000000000000000000000000000000000000000000000000000610298565b3461024f57610435366004610245565b6102a87f0000000000000000000000000000000000000000000000000000000000000000610298565b3461024f5761046e366004610245565b6102a87f0000000000000000000000000000000000000000000000000000000000000000610298565b61025e906104ab906001600160a01b031682565b6001600160a01b031690565b61025e90610497565b61025e906104b7565b610277906104c0565b60208101929161027b91906104c9565b3461024f576104f2366004610245565b6102a87f00000000000000000000000000000000000000000000000000000000000000005b604051918291826104d2565b61025e906104ab565b61053581610523565b0361024f57565b9050359061027b8261052c565b9060208282031261024f5761025e9161053c565b3461024f57610575610570366004610549565b610c44565b604051005b61025e5f6004610261565b3461024f57610595366004610245565b6102a861029861057a565b3461024f576105b0366004610245565b610575610cc1565b61025e5f6001610261565b3461024f576105d3366004610245565b6102a86102986105b8565b3461024f576105ee366004610245565b6102a87f0000000000000000000000000000000000000000000000000000000000000000610298565b610277906104ab565b60208101929161027b9190610617565b3461024f57610640366004610245565b6102a87f00000000000000000000000000000000000000000000000000000000000000005b60405191829182610620565b3461024f57610681366004610245565b6102a8610298610f30565b3461024f5761069c366004610245565b610575611166565b80610535565b9050359061027b826106a4565b9060208282031261024f5761025e916106aa565b3461024f576105756106de3660046106b7565b6112de565b3461024f576106f3366004610245565b6102a87f0000000000000000000000000000000000000000000000000000000000000000610665565b3461024f5761072c366004610245565b6105756112e7565b3461024f576102a861029861074a3660046106b7565b61154b565b3461024f576102a86102986107653660046106b7565b6117c3565b3461024f5761077a366004610245565b6102a8610298611986565b3461024f57610795366004610245565b6102a87f00000000000000000000000000000000000000000000000000000000000000006103d8565b61025e6012610398565b61025e6107be565b3461024f576107e0366004610245565b6102a86103d86107c8565b3461024f576107fb366004610245565b6102a87f00000000000000000000000000000000000000000000000000000000000000006103d8565b61025e620151806102e5565b61025e610824565b3461024f57610848366004610245565b6102a8610298610830565b61025e5f6003610261565b3461024f5761086e366004610245565b6102a8610298610853565b3461024f57610889366004610245565b6102a87f00000000000000000000000000000000000000000000000000000000000000006103d8565b3461024f576108c2366004610245565b6102a87f0000000000000000000000000000000000000000000000000000000000000000610517565b61025e5f80610261565b3461024f57610905366004610245565b6102a86102986108eb565b3461024f57610920366004610245565b6102a87f0000000000000000000000000000000000000000000000000000000000000000610665565b3461024f57610959366004610245565b6102a87f0000000000000000000000000000000000000000000000000000000000000000610517565b3461024f57610992366004610245565b6102a87f00000000000000000000000000000000000000000000000000000000000000006103d8565b3461024f576109cb366004610245565b6102a87f0000000000000000000000000000000000000000000000000000000000000000610298565b61025e906103a5565b61025e90546109f4565b610a0f610f30565b610a185f6102e5565b8114610a3457421015610a2f5761025e60056109fd565b600390565b505f90565b610a627f00000000000000000000000000000000000000000000000000000000000000006104ab565b610a6b336104ab565b03610a795761027b90610b11565b639c867e2160e01b5f90815260045b035ffd5b634e487b7160e01b5f52604160045260245ffd5b90601f01601f1916810190811067ffffffffffffffff821117610ac257604052565b610a8c565b9050519061027b826106a4565b9060208282031261024f5761025e91610ac7565b6040513d5f823e3d90fd5b91602061027b929493610b0d60408201965f830190610617565b0152565b610b3a7f0000000000000000000000000000000000000000000000000000000000000000610523565b610b4382610523565b14610c335780610b55610b7d926104c0565b6020610b60306104c0565b6040519485918291906370a0823160e01b5b835260048301610620565b0381845afa918215610c2e577f8aec0ce3dadffacf4b7a963e0fed1ff2e6151b4c95d4a65acafa9d1299630402935f93610bf9575b5082610bdf917f000000000000000000000000000000000000000000000000000000000000000090611aa7565b610bf4610beb60405190565b92839283610af3565b0390a1565b610bdf919350610c209060203d602011610c27575b610c188183610aa0565b810190610ad4565b9290610bb2565b503d610c0e565b610ae8565b63bf27c04f60e01b5f908152600490fd5b61027b90610a39565b61025e9081565b61025e9054610c4d565b634e487b7160e01b5f52601160045260245ffd5b91908201809211610c7f57565b610c5e565b90815260408101929161027b9160200152565b905f19905b9181191691161790565b90610cb661025e610cbd926102e5565b8254610c97565b9055565b610ccb60056109fd565b610cdd610cd75f610345565b91610345565b14610f1f577f0000000000000000000000000000000000000000000000000000000000000000610d0c816104ab565b610d15336104ab565b141580610ee0575b610ecf57610d29611986565b80610d335f6102e5565b8114610ebe57610d429061154b565b918290610d4e5f6102e5565b8414610ebe57610dac937f0000000000000000000000000000000000000000000000000000000000000000906020610d85836104c0565b610d8e306104c0565b90610d9860405190565b988992839182916370a0823160e01b610b72565b03915afa908115610c2e577fc83b5086ce94ec8d5a88a9f5fea4b18a522bb238ed0d2d8abd959549a80c16b8965f92610e9d575b50808210610e4b575b5050610e36918391610dfc426001610ca6565b610e19610e1287610e0d6003610c54565b610c72565b6003610ca6565b610e31610e2a84610e0d6004610c54565b6004610ca6565b611aa7565b610bf4610e4260405190565b92839283610c84565b81610e369492939650610e9395507f521d4ec08ea54943095f5f84755de8b51e1222628c06daa159d618c8c5fda56a91610e87610e4260405190565b0390a1819384926117c3565b9491819350610de9565b610eb791925060203d602011610c2757610c188183610aa0565b905f610de0565b631f2a200560e01b5f908152600490fd5b635a06399f60e11b5f908152600490fd5b50610f1861025e610ef16001610c54565b7f000000000000000000000000000000000000000000000000000000000000000090610c72565b4210610d1d565b6321c4e35760e21b5f908152600490fd5b610f3a60056109fd565b610f46610cd75f610345565b14610fa457610f5560056109fd565b610f62610cd76002610345565b03610f715761025e6002610c54565b61025e610f7d5f610c54565b7f000000000000000000000000000000000000000000000000000000000000000090610c72565b61025e5f6102e5565b610fd67f00000000000000000000000000000000000000000000000000000000000000006104ab565b610fdf336104ab565b03610a795761027b61100c565b9060ff90610c9c565b9061100561025e610cbd9261034f565b8254610fec565b61101660056109fd565b611022610cd75f610345565b0361115657611031425f610ca6565b61103c426001610ca6565b61104860016005610ff5565b61109d60206110767f00000000000000000000000000000000000000000000000000000000000000006104c0565b61107f306104c0565b9061108960405190565b938492839182916370a0823160e01b610b72565b03915afa908115610c2e575f91611137575b506110b9816117c3565b7f0000000000000000000000000000000000000000000000000000000000000000919082906110e7565b9190565b1061111f5750507f5daa87a0e9463431830481fd4b6e3403442dfb9a12b9c07597e9f61d50b633c861111860405190565b8080610bf4565b634787a10360e11b5f90815291610a88916004610c84565b611150915060203d602011610c2757610c188183610aa0565b5f6110af565b62dc149f60e41b5f908152600490fd5b61027b610fad565b6111977f00000000000000000000000000000000000000000000000000000000000000006104ab565b6111a0336104ab565b03610a795761027b906111b360056109fd565b906002916111c3610cd784610345565b146112cd576111d15f6102e5565b810361127a5761120a6112047f000000000000000000000000000000000000000000000000000000000000000042610c72565b83610ca6565b61121382610c54565b6112256110e361025e610f7d5f610c54565b116112635750610bf46102988261125e7f9a9cad27a1bdce3d7cb0602aaf727dfaad03ba6524d9bca7b9615867fa6802b4946005610ff5565b610c54565b6351fe048360e11b5f908152600491909152602490fd5b6112a761025e7f000000000000000000000000000000000000000000000000000000000000000042610c72565b81106112bc576112b78183610ca6565b61120a565b6325c3636760e01b5f908152600490fd5b632aaa685560e11b5f908152600490fd5b61027b9061116e565b6112f160056109fd565b6112fd610cd75f610345565b036113f25761132b7f00000000000000000000000000000000000000000000000000000000000000006104ab565b611334336104ab565b03610a79575b7f00000000000000000000000000000000000000000000000000000000000000006113696020611076836104c0565b03915afa8015610c2e577f7f221332ee403570bf4d61630b58189ea566ff1635269001e9df6a890f413dd892610bf4925f926113cd575b5081610298917f000000000000000000000000000000000000000000000000000000000000000090611aa7565b6102989192506113eb9060203d602011610c2757610c188183610aa0565b91906113a0565b6113fa610f30565b6114237f00000000000000000000000000000000000000000000000000000000000000006104ab565b61142c336104ab565b0361144857421161133a5763a33e709960e01b5f908152600490fd5b61025e611476917f000000000000000000000000000000000000000000000000000000000000000090610c72565b421161133a5763042f54af60e01b5f908152600490fd5b69ffffffffffffffffffff8116610535565b9050519061027b8261148d565b919060a08382031261024f576114c2818461149f565b926114d08260208301610ac7565b9261025e6114e18460408501610ac7565b9360806114f18260608701610ac7565b940161149f565b61025e61025e61025e9260ff1690565b91908203918211610c7f57565b81810292918115918404141715610c7f57565b634e487b7160e01b5f52601260045260245ffd5b8115611546570490565b611528565b6115747f00000000000000000000000000000000000000000000000000000000000000006104c0565b63feaf968c9160a061158560405190565b80936115918660e01b90565b825260049082905afa918215610c2e575f9261179f575b506115b25f6102e5565b82131561175b5760a06115f4936115e87f00000000000000000000000000000000000000000000000000000000000000006104c0565b60405195869260e01b90565b825260049082905afa928315610c2e575f9361176b575b506116155f6102e5565b83131561175b5761025e926116c261172b92611726611636611686966102e5565b6116fc7f0000000000000000000000000000000000000000000000000000000000000000916116cd6116c761167b61167561166f6107be565b966114f8565b956114f8565b9a8b80968195611afd565b6116c26116916102f2565b916116bc7f000000000000000000000000000000000000000000000000000000000000000084611508565b90611515565b61153c565b976102e5565b6116f67f00000000000000000000000000000000000000000000000000000000000000006114f8565b90611afd565b926116f67f00000000000000000000000000000000000000000000000000000000000000006114f8565b611515565b906117557f00000000000000000000000000000000000000000000000000000000000000006114f8565b91611afd565b62bfc92160e01b5f908152600490fd5b61178e91935060a03d60a011611798575b6117868183610aa0565b8101906114ac565b505050929061160b565b503d61177c565b6117b991925060a03d60a011611798576117868183610aa0565b50505091906115a8565b6117ec7f00000000000000000000000000000000000000000000000000000000000000006104c0565b63feaf968c60a06117fc60405190565b80936118088460e01b90565b825260049082905afa918215610c2e575f92611962575b506118295f6102e5565b82131561175b5760a061186b9161185f7f00000000000000000000000000000000000000000000000000000000000000006104c0565b60405193849260e01b90565b825260049082905afa908115610c2e575f91611940575b5061188c5f6102e5565b81131561175b5761025e926116c2611916926117266118ad611686966102e5565b6118ec7f0000000000000000000000000000000000000000000000000000000000000000916116cd6118e661167b61167561166f6107be565b956102e5565b946116f67f00000000000000000000000000000000000000000000000000000000000000006114f8565b906117557f00000000000000000000000000000000000000000000000000000000000000006114f8565b611959915060a03d60a011611798576117868183610aa0565b50505090611882565b61197c91925060a03d60a011611798576117868183610aa0565b505050919061181f565b5f6119916003610c54565b7f000000000000000000000000000000000000000000000000000000000000000090811115611a76576119c2610f30565b6119cb5f6102e5565b8114611a6a5761025e925080421015611a2f57506119f8611a1f916116bc6119f25f610c54565b42611508565b7f00000000000000000000000000000000000000000000000000000000000000009061153c565b611a296003610c54565b90611508565b611a3960056109fd565b611a46610cd76002610345565b03611a6457611a5f916116bc6119f892611a295f610c54565b611a1f565b50611a1f565b50505061025e5f6102e5565b505061025e5f6102e5565b611a9a611a9461025e9263ffffffff1690565b60e01b90565b6001600160e01b03191690565b611aea600492611adb61027b95611ac163a9059cbb611a81565b92611acb60405190565b9687946020860190815201610af3565b60208201810382520383610aa0565b611b3e565b604d8111610c7f57600a0a90565b9091808314611b3857808311611b2257611b1d61025e936116bc92611508565b611aef565b611b1d611b329161025e94611508565b9061153c565b50905090565b905f602091611b4a5f90565b50828151910182855af115610ae8573d5f5190611b696110e35f6102e5565b03611bae5750611b78816104c0565b3b611b856110e35f6102e5565b145b611b8e5750565b610a88611b9b5f926104c0565b635274afe760e01b835260048301610620565b611bbb6110e360016102e5565b1415611b8756fea2646970667358221220b9bc72af3f9557a386e75ff3464e83a88f088fbc6cfe64110801fc9398f4e9ce64736f6c634300081d0033a2646970667358221220fa508bab55c3cca8f23dbf8d459bd927ee834e681c50c09b13ae230bb385e15664736f6c634300081d0033

Verified Source Code Full Match

Compiler: v0.8.29+commit.ab55807c EVM: prague
IERC1363.sol 86 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";

/**
 * @title IERC1363
 * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
 *
 * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
 * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
 */
interface IERC1363 is IERC20, IERC165 {
    /*
     * Note: the ERC-165 identifier for this interface is 0xb0202a11.
     * 0xb0202a11 ===
     *   bytes4(keccak256('transferAndCall(address,uint256)')) ^
     *   bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
     */

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @param data Additional data with no specified format, sent in call to `spender`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}
IERC165.sol 6 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../utils/introspection/IERC165.sol";
IERC20.sol 6 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../token/ERC20/IERC20.sol";
IERC20Metadata.sol 26 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC-20 standard.
 */
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 79 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
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 value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` 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 value) external returns (bool);
}
SafeERC20.sol 212 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 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 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @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 Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            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 {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 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).
     *
     * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}
Create2.sol 92 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol)

pragma solidity ^0.8.20;

import {Errors} from "./Errors.sol";

/**
 * @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
 * `CREATE2` can be used to compute in advance the address where a smart
 * contract will be deployed, which allows for interesting new mechanisms known
 * as 'counterfactual interactions'.
 *
 * See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
 * information.
 */
library Create2 {
    /**
     * @dev There's no code to deploy.
     */
    error Create2EmptyBytecode();

    /**
     * @dev Deploys a contract using `CREATE2`. The address where the contract
     * will be deployed can be known in advance via {computeAddress}.
     *
     * The bytecode for a contract can be obtained from Solidity with
     * `type(contractName).creationCode`.
     *
     * Requirements:
     *
     * - `bytecode` must not be empty.
     * - `salt` must have not been used for `bytecode` already.
     * - the factory must have a balance of at least `amount`.
     * - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
     */
    function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
        if (address(this).balance < amount) {
            revert Errors.InsufficientBalance(address(this).balance, amount);
        }
        if (bytecode.length == 0) {
            revert Create2EmptyBytecode();
        }
        assembly ("memory-safe") {
            addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
            // if no address was created, and returndata is not empty, bubble revert
            if and(iszero(addr), not(iszero(returndatasize()))) {
                let p := mload(0x40)
                returndatacopy(p, 0, returndatasize())
                revert(p, returndatasize())
            }
        }
        if (addr == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
     * `bytecodeHash` or `salt` will result in a new destination address.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
        return computeAddress(salt, bytecodeHash, address(this));
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
     * `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
        assembly ("memory-safe") {
            let ptr := mload(0x40) // Get free memory pointer

            // |                   | ↓ ptr ...  ↓ ptr + 0x0B (start) ...  ↓ ptr + 0x20 ...  ↓ ptr + 0x40 ...   |
            // |-------------------|---------------------------------------------------------------------------|
            // | bytecodeHash      |                                                        CCCCCCCCCCCCC...CC |
            // | salt              |                                      BBBBBBBBBBBBB...BB                   |
            // | deployer          | 000000...0000AAAAAAAAAAAAAAAAAAA...AA                                     |
            // | 0xFF              |            FF                                                             |
            // |-------------------|---------------------------------------------------------------------------|
            // | memory            | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
            // | keccak(start, 85) |            ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |

            mstore(add(ptr, 0x40), bytecodeHash)
            mstore(add(ptr, 0x20), salt)
            mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
            let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
            mstore8(start, 0xff)
            addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff)
        }
    }
}
Errors.sol 34 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of common custom errors used in multiple contracts
 *
 * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
 * It is recommended to avoid relying on the error API for critical functionality.
 *
 * _Available since v5.1._
 */
library Errors {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error InsufficientBalance(uint256 balance, uint256 needed);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedCall();

    /**
     * @dev The deployment failed.
     */
    error FailedDeployment();

    /**
     * @dev A necessary precompile is missing.
     */
    error MissingPrecompile(address);
}
IERC165.sol 25 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
AggregatorV3Interface.sol 25 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;

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);
}
IStreamer.sol 56 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

enum StreamState {
    NOT_INITIALIZED,
    STARTED,
    SHORTENED,
    FINISHED
}

interface IStreamer {
    event Initialized();
    event Claimed(uint256 streamingAssetAmount, uint256 nativeAssetAmount);
    event Terminated(uint256 terminationTimestamp);
    event Swept(uint256 amount);
    event Rescued(address token, uint256 balance);
    event InsufficientAssetBalance(uint256 balanceRequired, uint256 balance);

    error ZeroAmount();
    error NotReceiver();
    error NotStreamCreator();
    error CantRescueStreamingAsset();
    error ZeroAddress();
    error SlippageExceedsScaleFactor();
    error InvalidPrice();
    error NotInitialized();
    error NotEnoughBalance(uint256 balance, uint256 streamingAmount);
    error StreamNotFinished();
    error AlreadyInitialized();
    error DurationTooShort();
    error TerminationIsAfterStream(uint256 terminationTimestamp);
    error CreatorCannotSweepYet();
    error SweepCooldownNotPassed();
    error AlreadyTerminated();
    error NoticePeriodExceedsStreamDuration();
    error DecimalsNotInBounds();
    error StreamingAmountTooLow();

    function initialize() external;

    function claim() external;

    function sweepRemaining() external;

    function terminateStream(uint256 _terminationTimestamp) external;

    function rescueToken(IERC20 token) external;

    function getNativeAssetAmountOwed() external view returns (uint256);

    function calculateStreamingAssetAmount(uint256 nativeAssetAmount) external view returns (uint256);

    function calculateNativeAssetAmount(uint256 streamingAssetAmount) external view returns (uint256);
}
IStreamerFactory.sol 26 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;

import { AggregatorV3Interface } from "./AggregatorV3Interface.sol";

interface IStreamerFactory {
    event StreamerDeployed(address newContract, bytes constructorParams);

    error AssetsMatch();

    function deployStreamer(
        address _streamingAsset,
        address _nativeAsset,
        AggregatorV3Interface _streamingAssetOracle,
        AggregatorV3Interface _nativeAssetOracle,
        address _returnAddress,
        address _streamCreator,
        address _recipient,
        uint256 _nativeAssetStreamingAmount,
        uint256 _slippage,
        uint256 _sweepCooldown,
        uint256 _finishCooldown,
        uint256 _streamDuration,
        uint256 _minimumNoticePeriod
    ) external returns (address);
}
Streamer.sol 365 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;

// Developed with @openzeppelin/contracts v5.3.0
import { AggregatorV3Interface } from "./interfaces/AggregatorV3Interface.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { StreamState, IStreamer } from "./interfaces/IStreamer.sol";

/** @title Streamer
 * @author WOOF! Software
 * @custom:security-contact [email protected]
 * @notice This contract streams a certain amount of native asset in a form of streaming asset to the recipient over a specified streaming duration.
 * - The contract is designed to work with a pair of Chainlink oracles: Native Asset / USD and Streaming Asset / USD. However, can support any oracle which supports AggregatorV3Interface.
 * - Streaming asset is accrued linearly over a streaming duration, unlocking a portion of Streaming asset each second. Recipient can claim any time during and after the stream.
 * - Stream Creator is able to:
 *  1. rescue any ERC-20 token stuck in contract except of streaming asset.
 *  2. terminate the stream until the stream end. In this case, the distribution of streaming asset will continue till the termination timestamp.
 *  3. sweep remaining streaming asset tokens after stream end or termination timestamp (in case the stream is terminated).
 * - The streaming amount is specified in the native asset units. During the claiming, accrued native asset amount is calculated into streaming asset.
 * - All the tokens transferred via sweepRemaining or rescueToken are sent to the returnAddress.
 * - Anyone is able to call claim or sweepRemaining after a specified duration. Assets will still be transferred to the recipient and returnAddress accordingly.
 */
contract Streamer is IStreamer {
    using SafeERC20 for IERC20;

    /// @notice The denominator for slippage calculation. Equals 100%.
    uint256 public constant SLIPPAGE_SCALE = 1e8;
    /// @notice Minimal required duration for all duration parameters.
    uint256 public constant MIN_DURATION = 1 days;
    /// @notice Minimal number of decimals allowed for tokens and price feeds.
    uint8 public constant MIN_DECIMALS = 6;
    /// @notice Number of decimals used to scale prices.
    uint8 public constant SCALE_DECIMALS = 18;

    /// @notice The address of asset used for distribution.
    IERC20 public immutable streamingAsset;
    /// @notice The address of price feed oracle for Streaming asset. Must return the price in USD.
    AggregatorV3Interface public immutable streamingAssetOracle;
    /// @notice The address of price feed oracle for Native asset. Must return the price in USD.
    AggregatorV3Interface public immutable nativeAssetOracle;
    /// @notice The address which receives tokens during the execution of sweepRemaining and rescueToken functions.
    address public immutable returnAddress;
    /// @notice The owner of the stream.
    address public immutable streamCreator;
    /// @notice The recipient of streaming asset.
    address public immutable recipient;
    /// @notice Amount of asset to be distributed. Specified in the Native asset units.
    uint256 public immutable nativeAssetStreamingAmount;
    /// @notice A percentage used to reduce the price of streaming asset to account for price fluctuations.
    uint256 public immutable slippage;
    /// @notice A period of time since last claim timestamp after which anyone can call claim.
    uint256 public immutable claimCooldown;
    /// @notice A period of time since the end of stream after which anyone can call sweepRemaining.
    uint256 public immutable sweepCooldown;
    /// @notice A period of time since the initialization of the stream during which asset is streamed.
    uint256 public immutable streamDuration;
    /// @notice A minimal period of time during which Streaming asset must continue to accrue after termination is called.
    uint256 public immutable minimumNoticePeriod;
    /// @notice Decimals of Streaming asset.
    uint8 public immutable streamingAssetDecimals;
    /// @notice Decimals of Native asset.
    uint8 public immutable nativeAssetDecimals;
    /// @notice Decimals of the price returned by the Streaming Asset Oracle.
    uint8 public immutable streamingAssetOracleDecimals;
    /// @notice Decimals of the price returned by the Native Asset Oracle.
    uint8 public immutable nativeAssetOracleDecimals;
    /// @notice The start of the stream. Set during initialization of the stream.
    uint256 public startTimestamp;
    /// @notice The timestamp of the latest claim call.
    uint256 public lastClaimTimestamp;
    /// @notice The timestamp till which tokens continue to accrue. Set during the terminateStream call.
    uint256 public terminationTimestamp;
    /// @notice Amount of Native asset already distributed.
    uint256 public nativeAssetSuppliedAmount;
    /// @notice Total amount of claimed Streaming asset.
    uint256 public streamingAssetClaimedAmount;
    /// @notice The state which indicated if the stream is not initialized, ongoing or terminated.
    StreamState private state;

    modifier onlyStreamCreator() {
        if (msg.sender != streamCreator) revert NotStreamCreator();
        _;
    }

    /// @dev Decimals for tokens and price feeds should be between 6 and 18 to ensure proper calculations.
    /// @dev Streaming asset should not be a token with multiple addresses to ensure the correct flow of the stream.
    /// USD value of `_nativeAssetStreamingAmount` must be equal to at least $1.
    constructor(
        IERC20 _streamingAsset,
        AggregatorV3Interface _streamingAssetOracle,
        AggregatorV3Interface _nativeAssetOracle,
        address _returnAddress,
        address _streamCreator,
        address _recipient,
        uint8 _streamingAssetDecimals,
        uint8 _nativeAssetDecimals,
        uint256 _nativeAssetStreamingAmount,
        uint256 _slippage,
        uint256 _claimCooldown,
        uint256 _sweepCooldown,
        uint256 _streamDuration,
        uint256 _minimumNoticePeriod
    ) {
        if (_recipient == address(0)) revert ZeroAddress();
        if (_streamCreator == address(0)) revert ZeroAddress();
        if (_returnAddress == address(0)) revert ZeroAddress();
        if (address(_streamingAsset) == address(0)) revert ZeroAddress();
        if (_nativeAssetStreamingAmount == 0) revert ZeroAmount();
        if (_slippage > SLIPPAGE_SCALE) revert SlippageExceedsScaleFactor();
        if (_claimCooldown < MIN_DURATION) revert DurationTooShort();
        if (_sweepCooldown < MIN_DURATION) revert DurationTooShort();
        if (_streamDuration < MIN_DURATION) revert DurationTooShort();
        if (_minimumNoticePeriod < MIN_DURATION) revert DurationTooShort();
        if (_minimumNoticePeriod > _streamDuration) revert NoticePeriodExceedsStreamDuration();
        streamingAssetOracleDecimals = _streamingAssetOracle.decimals();
        nativeAssetOracleDecimals = _nativeAssetOracle.decimals();
        if (
            _streamingAssetDecimals < MIN_DECIMALS ||
            _nativeAssetDecimals < MIN_DECIMALS ||
            streamingAssetOracleDecimals < MIN_DECIMALS ||
            nativeAssetOracleDecimals < MIN_DECIMALS
        ) revert DecimalsNotInBounds();
        (, int256 nativeAssetPrice, , , ) = _nativeAssetOracle.latestRoundData();
        if (nativeAssetPrice <= 0) revert InvalidPrice();
        if (
            (_nativeAssetStreamingAmount * uint256(nativeAssetPrice)) / 10 ** nativeAssetOracleDecimals <
            10 ** _nativeAssetDecimals
        ) revert StreamingAmountTooLow();

        streamingAsset = _streamingAsset;
        streamingAssetOracle = _streamingAssetOracle;
        nativeAssetOracle = _nativeAssetOracle;
        returnAddress = _returnAddress;
        streamCreator = _streamCreator;
        recipient = _recipient;
        streamingAssetDecimals = _streamingAssetDecimals;
        nativeAssetDecimals = _nativeAssetDecimals;
        nativeAssetStreamingAmount = _nativeAssetStreamingAmount;
        slippage = _slippage;
        claimCooldown = _claimCooldown;
        sweepCooldown = _sweepCooldown;
        streamDuration = _streamDuration;
        minimumNoticePeriod = _minimumNoticePeriod;
    }

    /** @notice Initializes the stream by setting start timestamp and validating that the contract has enough Streaming asset.
     * @dev Streaming asset must be transferred to the contract's balance before function is called.
     * @dev It is recommended to send a sufficient amount of Streaming asset in order to ensure the correct work of the Streamer.
     * The extra amount depends on the volatility of assets. In general, we recommend sending extra 10% of the necessary Streaming asset amount.
     * @dev Use the function `calculateStreamingAssetAmount` to determine the amount of Streaming asset to transfer.
     */
    function initialize() external onlyStreamCreator {
        if (state != StreamState.NOT_INITIALIZED) revert AlreadyInitialized();
        startTimestamp = block.timestamp;
        lastClaimTimestamp = block.timestamp;
        state = StreamState.STARTED;

        uint256 balance = streamingAsset.balanceOf(address(this));
        if (calculateNativeAssetAmount(balance) < nativeAssetStreamingAmount)
            revert NotEnoughBalance(balance, nativeAssetStreamingAmount);

        emit Initialized();
    }

    /** @notice Claims the accrued amount of Streaming asset to the recipient's address.
     * @dev The stream must be initialized.
     * @dev Can be called by the recipient or anyone after claim cooldown has passed since the last claim timestamp.
     * @dev In case the contract doesn't have enough Streaming asset on its balance, the whole balance will be sent. The stream owner will have to replenish
     * the balance in order to resume the stream.
     */
    function claim() external {
        if (state == StreamState.NOT_INITIALIZED) revert NotInitialized();
        if (msg.sender != recipient && block.timestamp < lastClaimTimestamp + claimCooldown) revert NotReceiver();

        uint256 owed = getNativeAssetAmountOwed();
        if (owed == 0) revert ZeroAmount();

        uint256 streamingAssetAmount = calculateStreamingAssetAmount(owed);
        if (streamingAssetAmount == 0) revert ZeroAmount();

        uint256 balance = streamingAsset.balanceOf(address(this));
        if (balance < streamingAssetAmount) {
            emit InsufficientAssetBalance(streamingAssetAmount, balance);
            streamingAssetAmount = balance;
            owed = calculateNativeAssetAmount(balance);
        }

        lastClaimTimestamp = block.timestamp;
        nativeAssetSuppliedAmount += owed;
        streamingAssetClaimedAmount += streamingAssetAmount;

        streamingAsset.safeTransfer(recipient, streamingAssetAmount);
        emit Claimed(streamingAssetAmount, owed);
    }

    /// @notice Terminates the stream, stopping the distribution after the termination timestamp.
    /// @param _terminationTimestamp The timestamp after which the stream is stopped. Must be longer than `block.timestamp + minimumNoticePeriod` and less than the end of stream.
    /// If the parameter is equal to 0, the termination timestamp will be set as `block.timestamp + minimumNoticePeriod`.
    function terminateStream(uint256 _terminationTimestamp) external onlyStreamCreator {
        if (state == StreamState.SHORTENED) revert AlreadyTerminated();
        if (_terminationTimestamp == 0) {
            terminationTimestamp = block.timestamp + minimumNoticePeriod;
        } else {
            if (_terminationTimestamp < block.timestamp + minimumNoticePeriod) revert DurationTooShort();
            terminationTimestamp = _terminationTimestamp;
        }

        if (terminationTimestamp > startTimestamp + streamDuration)
            revert TerminationIsAfterStream(_terminationTimestamp);
        state = StreamState.SHORTENED;
        emit Terminated(terminationTimestamp);
    }

    /** @notice Allows to sweep all the Streaming asset tokens from the Streamer's balance.
     * @dev Can be called by Stream Creator before initialization without any additional conditions.
     * @dev After the end of stream (Either after stream duration or after termination timestamp if termination was called), can be called
     * by Stream Creator or anyone after sweep cooldown has passed.
     */
    function sweepRemaining() external {
        if (state == StreamState.NOT_INITIALIZED) {
            if (msg.sender != streamCreator) {
                revert NotStreamCreator();
            }
        } else {
            uint256 streamEnd = getStreamEnd();

            if (msg.sender == streamCreator) {
                if (block.timestamp <= streamEnd) {
                    revert CreatorCannotSweepYet();
                }
            } else if (block.timestamp <= streamEnd + sweepCooldown) {
                revert SweepCooldownNotPassed();
            }
        }
        uint256 remainingBalance = streamingAsset.balanceOf(address(this));

        streamingAsset.safeTransfer(returnAddress, remainingBalance);
        emit Swept(remainingBalance);
    }

    /** @notice Allows to transfer any ERC-20 token except the Streaming asset from the Streamer's balance.
     * @param token Address of ERC-20 token to transfer.
     * @dev Can only be called by Stream Creator.
     */
    function rescueToken(IERC20 token) external onlyStreamCreator {
        if (token == streamingAsset) revert CantRescueStreamingAsset();
        uint256 balance = token.balanceOf(address(this));
        token.safeTransfer(returnAddress, balance);
        emit Rescued(address(token), balance);
    }

    /// @notice Calculates the amount of asset accrued since the last claiming
    /// @return Amount of accrued asset in Native asset units.
    function getNativeAssetAmountOwed() public view returns (uint256) {
        if (nativeAssetSuppliedAmount >= nativeAssetStreamingAmount) {
            return 0;
        }
        uint256 streamEnd = getStreamEnd();
        // Validate if stream is properly initialized
        if (streamEnd == 0) return 0;
        uint256 totalOwed;

        if (block.timestamp < streamEnd) {
            uint256 elapsed = block.timestamp - startTimestamp;
            totalOwed = (nativeAssetStreamingAmount * elapsed) / streamDuration;
        } else {
            // If Stream is terminated, calculate amount accrued before termination timestamp
            if (state == StreamState.SHORTENED)
                totalOwed = (nativeAssetStreamingAmount * (streamEnd - startTimestamp)) / streamDuration;
            else totalOwed = nativeAssetStreamingAmount;
        }
        return totalOwed - nativeAssetSuppliedAmount;
    }

    /** @notice Calculates the amount of Streaming asset based on the specified Native asset amount.
     * @param nativeAssetAmount The amount of Native asset to be converted to Streaming asset.
     * @dev Used in `claim` to calculate the amount Native asset owed in Streaming asset.
     * @dev The price of streaming asset is reduced by the slippage to account for price fluctuations.
     * @return Amount of Streaming asset.
     */
    function calculateStreamingAssetAmount(uint256 nativeAssetAmount) public view returns (uint256) {
        (, int256 streamingAssetPrice, , , ) = streamingAssetOracle.latestRoundData();
        if (streamingAssetPrice <= 0) revert InvalidPrice();

        (, int256 nativeAssetPrice, , , ) = nativeAssetOracle.latestRoundData();
        if (nativeAssetPrice <= 0) revert InvalidPrice();

        uint256 streamingAssetPriceScaled = (scaleAmount(
            uint256(streamingAssetPrice),
            streamingAssetOracleDecimals,
            SCALE_DECIMALS
        ) * (SLIPPAGE_SCALE - slippage)) / SLIPPAGE_SCALE;
        // Scale native asset price to streaming asset decimals for calculations
        uint256 nativeAssetPriceScaled = scaleAmount(
            uint256(nativeAssetPrice),
            nativeAssetOracleDecimals,
            SCALE_DECIMALS
        );
        uint256 amountInStreamingAsset = (scaleAmount(nativeAssetAmount, nativeAssetDecimals, SCALE_DECIMALS) *
            nativeAssetPriceScaled) / streamingAssetPriceScaled;

        return scaleAmount(amountInStreamingAsset, SCALE_DECIMALS, streamingAssetDecimals);
    }

    /** @notice Calculates the amount of Native asset based on the specified Streaming asset amount.
     * @param streamingAssetAmount The amount of Streaming asset to be converted to Native asset.
     * @dev Used in `initialize` to validate if the Streamer has enough Streaming asset to begin stream.
     * @dev Used in `claim` to calculate how much the remaining balance of Streaming asset is equal to the Native Asset
     * (For cases where the Streamer doesn't have enough Streaming asset to distribute).
     * @return Amount of Native asset.
     */
    function calculateNativeAssetAmount(uint256 streamingAssetAmount) public view returns (uint256) {
        (, int256 streamingAssetPrice, , , ) = streamingAssetOracle.latestRoundData();
        if (streamingAssetPrice <= 0) revert InvalidPrice();

        (, int256 nativeAssetPrice, , , ) = nativeAssetOracle.latestRoundData();
        if (nativeAssetPrice <= 0) revert InvalidPrice();

        // Streaming asset price is reduced by slippage to account for price fluctuations
        uint256 streamingAssetPriceScaled = (scaleAmount(
            uint256(streamingAssetPrice),
            streamingAssetOracleDecimals,
            SCALE_DECIMALS
        ) * (SLIPPAGE_SCALE - slippage)) / SLIPPAGE_SCALE;
        // Scale native asset price to streaming asset decimals for calculations
        uint256 nativeAssetPriceScaled = scaleAmount(
            uint256(nativeAssetPrice),
            nativeAssetOracleDecimals,
            SCALE_DECIMALS
        );
        uint256 amountInNativeAsset = (scaleAmount(streamingAssetAmount, streamingAssetDecimals, SCALE_DECIMALS) *
            streamingAssetPriceScaled) / nativeAssetPriceScaled;

        return scaleAmount(amountInNativeAsset, SCALE_DECIMALS, nativeAssetDecimals);
    }

    /// @dev Returns a correct end of the stream once the stream is initialized.
    /// @return Timestamp representing the end of the stream.
    function getStreamEnd() public view returns (uint256) {
        if (state == StreamState.NOT_INITIALIZED) return 0;
        return (state == StreamState.SHORTENED) ? terminationTimestamp : startTimestamp + streamDuration;
    }

    /// @return Current state of the stream.
    function getStreamState() external view returns (StreamState) {
        uint256 streamEnd = getStreamEnd();
        if (streamEnd == 0) return StreamState.NOT_INITIALIZED;
        return block.timestamp < streamEnd ? state : StreamState.FINISHED;
    }

    /** @notice Scales an amount from one decimal representation to another.
     * @param amount The amount to be scaled.
     * @param fromDecimals The number of decimals of the original amount.
     * @param toDecimals The number of decimals of the target amount.
     * @return The scaled amount.
     */
    function scaleAmount(uint256 amount, uint256 fromDecimals, uint256 toDecimals) internal pure returns (uint256) {
        if (fromDecimals == toDecimals) return amount;
        if (fromDecimals > toDecimals) {
            return amount / (10 ** (fromDecimals - toDecimals));
        }
        return amount * (10 ** (toDecimals - fromDecimals));
    }
}
StreamerFactory.sol 66 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;

import { AggregatorV3Interface } from "./interfaces/AggregatorV3Interface.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { Create2 } from "@openzeppelin/contracts/utils/Create2.sol";
import { IStreamerFactory } from "./interfaces/IStreamerFactory.sol";
import { Streamer } from "./Streamer.sol";

/** @title Streamer Factory
 * @author WOOF! Software
 * @custom:security-contact [email protected]
 * @notice A Factory smart contract used for a safe deployment of new Streamer instances.
 * Anyone can use this Smart contract to deploy new streamers.
 */
contract StreamerFactory is IStreamerFactory {
    /// @notice A number per deployer used to generate a unique salt for Create2.
    mapping(address => uint256) public counters;

    /// @notice Deploys a new Streamer instance.
    /// @dev For details of each parameter, check documentation for Streamer.
    /// @dev Do not send tokens to Streamer address precomputed before actual deployment. Use the address returned from the function.
    /// @return The address of a new Streamer instance.
    function deployStreamer(
        address _streamingAsset,
        address _nativeAsset,
        AggregatorV3Interface _streamingAssetOracle,
        AggregatorV3Interface _nativeAssetOracle,
        address _returnAddress,
        address _streamCreator,
        address _recipient,
        uint256 _nativeAssetStreamingAmount,
        uint256 _slippage,
        uint256 _claimCooldown,
        uint256 _sweepCooldown,
        uint256 _streamDuration,
        uint256 _minimumNoticePeriod
    ) external returns (address) {
        if (_streamingAsset == _nativeAsset) revert AssetsMatch();
        uint8 streamingAssetDecimals = IERC20Metadata(_streamingAsset).decimals();
        uint8 nativeAssetDecimals = IERC20Metadata(_nativeAsset).decimals();
        bytes memory constructorParams = abi.encode(
            IERC20(_streamingAsset),
            _streamingAssetOracle,
            _nativeAssetOracle,
            _returnAddress,
            _streamCreator,
            _recipient,
            streamingAssetDecimals,
            nativeAssetDecimals,
            _nativeAssetStreamingAmount,
            _slippage,
            _claimCooldown,
            _sweepCooldown,
            _streamDuration,
            _minimumNoticePeriod
        );
        bytes32 uniqueSalt = keccak256(abi.encode(msg.sender, counters[msg.sender]++));
        bytes memory bytecodeWithParams = abi.encodePacked(type(Streamer).creationCode, constructorParams);
        address newContract = Create2.deploy(0, uniqueSalt, bytecodeWithParams);

        emit StreamerDeployed(newContract, constructorParams);
        return newContract;
    }
}

Read Contract

counters 0xbe65ab8c → uint256

Write Contract 1 functions

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

deployStreamer 0x8f9ff8d3
address _streamingAsset
address _nativeAsset
address _streamingAssetOracle
address _nativeAssetOracle
address _returnAddress
address _streamCreator
address _recipient
uint256 _nativeAssetStreamingAmount
uint256 _slippage
uint256 _claimCooldown
uint256 _sweepCooldown
uint256 _streamDuration
uint256 _minimumNoticePeriod
returns: address

Recent Transactions

No transactions found for this address