Cryo Explorer Ethereum Mainnet

Address Contract Verified

Address 0x49960769Aa50b533aa628AcDAa054bD5263d0d42
Balance 0 ETH
Nonce 1
Code Size 11915 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

11915 bytes
0x60a0806040526004361015610012575f80fd5b5f3560e01c9081622872941461258f5750806317442b701461256e57806326cc8a3c146125335780632d83549c146124b55780633400288b1461244c5780633472bd42146124265780633cc7a1de146123305780633f4ba83a1461229d578063458f5815146122805780635110c27414611c80578063526a654d14611b295780635392fd1c14611b045780635a6da1e914611ab95780635c975abb14611a975780635e280f1114611a5357806361d027b314611a2b5780636f7013a8146119e7578063704b6c0214611964578063715018a61461190d5780637bd3347b146118c95780637dbc1df01461184a5780638131842f146114005780638456cb59146113a75780638da5cb5b14611380578063995ded4d146113485780639f04586c146112dc5780639f6dea8e14611193578063accbe6fe14611112578063bb0b6a53146110dd578063c87a5105146110b8578063ca5eb5e11461102d578063ce26745014610fa1578063cfdbf25414610f86578063d0a1026014610c20578063d38bf4b11461097d578063d3c511801461094d578063d8550b3a146108d9578063dc0dd404146108b1578063e16c8bb2146107aa578063e1b515341461057a578063e404d9541461036f578063f0f44260146102ec578063f2fde38b1461022b5763f851a440146101ff575f80fd5b34610227575f36600319011261022757600c546040516001600160a01b039091168152602090f35b5f80fd5b34610227576020366003190112610227576102446126b6565b61024c612a14565b6001600160a01b03908116908115610298575f54826001600160601b0360a01b8216175f55167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3005b60405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608490fd5b34610227576020366003190112610227576103056126b6565b61030d612a14565b6001600160a01b0316801561035d576020817f7dae230f18360d76a040c81f050aa14eb9d6dc7901b20fc5d855e2a20fe814d1926001600160601b0360a01b600b541617600b55604051908152a1005b6040516302979eb960e31b8152600490fd5b34610227576060366003190112610227576001600160401b03600435818111610227576103a090369060040161260f565b9091604435908111610227576103ba90369060040161263f565b6004545f94906001600160a01b031680156105685760243515610556578415610544576032851161053257928592855f95600c54915b80881061040257602087604051908152f35b9091929394956104138883896127ab565b35600381101561022757604061042b6104ab92612b45565b8151906104378261272d565b63ffffffff8860a01c16825260243560208301528083830152606082015261046036898b61282e565b6080820152815161047081612763565b5f815260a0820152815161048381612763565b5f815260c0820152815180938192633b6f743b60e01b835284600484015260448301906128ca565b5f60248301520381875afa908115610527575f916104e1575b506001916104d39151906127cf565b9701969594939291906103f0565b90506040913d60401161051f575b6104f9838361277e565b604082848101031261022757610517826001946104d3940190612864565b9150916104c4565b3d92506104ef565b6040513d5f823e3d90fd5b6040516305beb17160e11b8152600490fd5b60405163c2e5347d60e01b8152600490fd5b604051636f86172f60e11b8152600490fd5b6040516312c4692760e31b8152600490fd5b3461022757610588366126cc565b90610591612aab565b610599612aef565b6105a38183612a6b565b5f5260206005815260405f2060028101805460ff8160081c16156107985791546001600160a01b03929083163303610786576105ea6105e187612b45565b600a54906127cf565b6004858582541660405192838092637e062a3560e11b82525afa80156105275785915f91610759575b5016926040516370a0823160e01b81523360048201528681602481885afa80156105275783915f91610728575b5010610716576106599261ff0019169055303384612c71565b600a5480610700575b505061066d84612b89565b16803b15610227576040516323b872dd60e01b815230600482015233602482015260448101849052905f908290606490829084905af18015610527576106f1575b5060038310156106dd575f906040519283528201525f80516020612e3683398151915260403392a36001600355005b634e487b7160e01b5f52602160045260245ffd5b6106fa9061271a565b836106ae565b61070f9183600b541690612c27565b8480610662565b604051637222ae5760e11b8152600490fd5b809250888092503d8311610752575b610741818361277e565b81010312610227578290518a610640565b503d610737565b6107799150873d891161077f575b610771818361277e565b8101906127dc565b89610613565b503d610767565b6040516323a7681d60e01b8152600490fd5b604051631831fdb360e31b8152600490fd5b34610227576107b8366126cc565b600c546001600160a01b0392908316330361089f576107d78183612a6b565b5f52600560205260405f20926002840180549460ff8660081c161561079857829054169461ff001916905561080b83612b89565b16803b15610227576040516323b872dd60e01b81523060048201526001600160a01b038516602482015260448101839052905f908290606490829084905af1801561052757610890575b5061086360405180936125d0565b60208201527f4cd34bfb21755b66fa5f4a977305912669546edc6fc8c21a2181b3395e07fc0060403392a3005b6108999061271a565b83610855565b604051637bfa4b9f60e01b8152600490fd5b34610227575f366003190112610227576004546040516001600160a01b039091168152602090f35b34610227576020366003190112610227577fbd6dc46ee1195d1c1210f8b8e9751a4de46ce4d19b86778a012586847f16f09160206109156125dd565b61091d612a14565b600c805463ffffffff60a01b191660a083901b63ffffffff60a01b1617905560405163ffffffff919091168152a1005b3461022757602036600319011261022757600435600381101561022757610975602091612b45565b604051908152f35b346102275761098b3661266c565b909291610996612aab565b61099e612aef565b818103610c0e5780156105445760328111610532576004546001600160a01b03949085168015610568575f945f5b848110610b935750600a549584870296808804861490151715610b7f57866109f3916127cf565b94604051637e062a3560e11b815260208160048183975afa90811561052757610a2a918a915f91610b60575b501696303389612c71565b5f5b858110610a595788888881610a43575b6001600355005b610a5192600b541690612c27565b808080610a3c565b610a648183876127ab565b35906003918281101561022757610a8790610a80838a886127ab565b3590612a6b565b5f5260058552600260405f200161ff00198154169055610aa88184886127ab565b358281101561022757610abb8b91612b89565b16610ac78289876127ab565b3590803b15610227576040516323b872dd60e01b815230600482015233602482015260448101929092525f908290606490829084905af1801561052757610b51575b50610b158184886127ab565b359182101561022757600191610b2c8289876127ab565b356040519081525f878201525f80516020612e3683398151915260403392a301610a2c565b610b5a9061271a565b8a610b09565b610b79915060203d60201161077f57610771818361277e565b8b610a1f565b634e487b7160e01b5f52601160045260245ffd5b95610b9f8787866127ab565b356003908181101561022757610bba90610a808a89876127ab565b5f52600560205260405f2060ff600282015460081c161561079857548916330361078657610be98888876127ab565b359081101561022757600191610c01610c0792612b45565b906127cf565b96016109cc565b60405163512509d360e11b8152600490fd5b60a036600319011261022757610c346126b6565b6001600160401b039060443582811161022757610c5590369060040161263f565b6001600160a01b039391929091606435858116036102275760843590811161022757610c8590369060040161263f565b5050610c8f612aab565b610c97612aef565b837f0000000000000000000000001a44076050125825900e736c501f859c50fe728c163303610f7457838060045416911603610f625780600c116102275763ffffffff600c5460a01c16600883013560e01c03610f505780602c1161022757600c8201359080604c1161022757610d17903690604b1901604c850161282e565b928351916040856020948101031261022757828501519460ff861680960361022757604001519160028611610f3e5760038610156106dd57610d598387612a6b565b5f526005845260405f2091600283019081549660ff8860081c161561079857602c600386015491013503610f2c57610d936105e189612b45565b90818110610f1a578391610da6916129d8565b9354169561ff0019169055600a5480610ebd575b5080610dc587612b89565b16803b15610227576040516323b872dd60e01b81523060048201526001600160a01b038716602482015260448101859052905f908290606490829084905af1801561052757610eae575b5081610e3c575b505060409060015f80516020612e36833981519152938351928352820152a36001600355005b8360049392948285541660405195868092637e062a3560e11b82525afa8015610527575f80516020612e368339815191529587600194610e87936040985f91610e91575b5016612c27565b9350819250610e16565b610ea89150863d881161077f57610771818361277e565b8c610e80565b610eb79061271a565b86610e0f565b600490858383541660405193848092637e062a3560e11b82525afa801561052757610ef7925f91610efd575b508380600b54169116612c27565b86610dba565b610f149150873d891161077f57610771818361277e565b89610ee9565b60405163057ab1a160e51b8152600490fd5b604051632c1cc7ab60e11b8152600490fd5b60405163517172a160e11b8152600490fd5b604051639284b19760e01b8152600490fd5b60405163fb22343b60e01b8152600490fd5b604051630fd72cd960e31b8152600490fd5b34610227575f36600319011261022757602060405160328152f35b3461022757602036600319011261022757610fba6126b6565b610fc2612a14565b600454906001600160a01b039081831661101b5716908115610568576001600160a01b03191681176004556040519081527fe3ed834126ef71c08cd3fc068a40194faa243ba67dcea2311e8a0877746a5c9990602090a1005b604051636627af6b60e11b8152600490fd5b34610227576020366003190112610227576110466126b6565b61104e612a14565b6001600160a01b037f0000000000000000000000001a44076050125825900e736c501f859c50fe728c81169190823b156102275760245f9283604051958694859363ca5eb5e160e01b85521660048401525af18015610527576110ad57005b6110b69061271a565b005b34610227575f36600319011261022757602060ff600c5460c81c166040519015158152f35b346102275760203660031901126102275763ffffffff6110fb6125dd565b165f526001602052602060405f2054604051908152f35b3461022757606036600319011261022757604435602435600435611134612a14565b60ff60095416611182577f023010bc68e7f4c0be9887f513c570c7a0f5f511b9716abccd42bf3b8943532b9260609282600655806007558160085560405192835260208301526040820152a1005b604051622f0cf160e01b8152600490fd5b34610227576060366003190112610227576004356003811015610227576044356001600160401b038111610227576111cf90369060040161263f565b6004546001600160a01b03168015610568576112286040936111f361127496612b45565b93600c549463ffffffff8751966112098861272d565b60a01c168652602435602087015280878701526060860152369161282e565b6080830152825161123881612763565b5f815260a0830152825161124b81612763565b5f815260c083015282518080958194633b6f743b60e01b835286600484015260448301906128ca565b5f602483015203915afa8015610527575f906112a1575b8060206040925191015182519182526020820152f35b5060403d6040116112d5575b6112b7818361277e565b810190604081830312610227576040916112d091612864565b61128b565b503d6112ad565b34610227576020366003190112610227576004358015158091036102275760207f3e60a7054babfee1c64189c1b1493c8972e387009d11e7b1619f10db74420aef91611326612a14565b600c805460ff60c01b191660c083901b60ff60c01b16179055604051908152a1005b346102275761135f611359366125f0565b90612a6b565b5f526005602052602060ff600260405f20015460081c166040519015158152f35b34610227575f366003190112610227575f546040516001600160a01b039091168152602090f35b34610227575f366003190112610227576113bf612a14565b6113c7612aab565b600160ff1960025416176002557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a1005b6080366003190112610227576024356004356003821015610227576001600160401b036044356064358281116102275761143e90369060040161263f565b919092611449612aab565b611451612aef565b60ff600c5460c01c1615611838576004546001600160a01b0390811693908415610568578315610556576114858789612a6b565b90815f526020916005835260ff600260405f20015460081c16611826576004836114ae8c612b45565b9860405192838092637e062a3560e11b82525afa80156105275783915f91611809575b5016946040516370a0823160e01b815230600482015284816024818a5afa80156105275789915f916117d8575b50106107165760405191611511836126eb565b3383528b858401928c845261152a60408601928361279f565b60608501936001855260808601914216825260a08601938b85525f52600588528660405f209651166001600160601b0360a01b87541617865551600186015560028501915160038110156106dd578254945161ffff1990951660ff9091161793151560081b61ff00169390931781556003925169ffffffffffffffff000082549160101b169069ffffffffffffffff0000191617905551910155806115ce8a612b89565b16803b15610227576040516323b872dd60e01b8152336004820152306024820152604481018a9052905f908290606490829084905af180156105275761164f95849289926117c9575b5083600454165f60405180998195829463095ea7b360e01b84526004840160209093929193604081019460018060a01b031681520152565b03925af18015610527576117019760c09561169e9261179c575b5063ffffffff600c5460a01c1694604051956116848761272d565b86528785870152886040870152886060870152369161282e565b60808401526040516116af81612763565b5f815260a08401526040516116c381612763565b5f8152848401525f604051926116d884612748565b348452830152600454169060405180978194829363c7c7f5b360e01b84523391600485016129a3565b039134905af1928315610527577f1178cde67db78bc437458c38ef65b12c375c4099998599e5b096f1cea5a0bb0093611762915f9161176c575b50516040519384933397859094939260609260808301968352602083015260408201520152565b0390a36001600355005b61178e915060c03d60c011611795575b611786818361277e565b81019061293a565b508761173b565b503d61177c565b6117bb90853d87116117c2575b6117b3818361277e565b8101906127fb565b508b611669565b503d6117a9565b6117d29061271a565b8c611617565b809250868092503d8311611802575b6117f1818361277e565b81010312610227578890518d6114fe565b503d6117e7565b6118209150853d871161077f57610771818361277e565b8c6114d1565b604051637dcf331360e01b8152600490fd5b604051630e2f42c960e31b8152600490fd5b3461022757602036600319011261022757600435611866612a14565b600c5460ff8160c81c166118b75760ff60c81b1916600160c81b17600c55600a8190556040519081527f8c4d35e54a3f2ef1134138fd8ea3daee6a3c89e10d2665996babdf70261e2c7690602090a1005b60405163b2fb16f360e01b8152600490fd5b34610227575f366003190112610227576040517f000000000000000000000000c0ffee8ff7e5497c2d6f7684859709225fcc5be86001600160a01b03168152602090f35b34610227575f36600319011261022757611925612a14565b5f80546001600160a01b0319811682556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b346102275760203660031901126102275761197d6126b6565b611985612a14565b6001600160a01b031680156119d5576020817f54e4612788f90384e6843298d7854436f3a585b2c3831ab66abf1de63bfa6c2d926001600160601b0360a01b600c541617600c55604051908152a1005b604051630b5eba9f60e41b8152600490fd5b34610227575f366003190112610227576040517f00000000000000000000000023581767a106ae21c074b2276d25e5c3e136a68b6001600160a01b03168152602090f35b34610227575f36600319011261022757600b546040516001600160a01b039091168152602090f35b34610227575f366003190112610227576040517f0000000000000000000000001a44076050125825900e736c501f859c50fe728c6001600160a01b03168152602090f35b34610227575f36600319011261022757602060ff600254166040519015158152f35b34610227575f36600319011261022757611ad1612a14565b600160ff1960095416176009557f35c26fbaac5e1f88f6bad0f1c623f324bb53468ef9655c494549d56612163f875f80a1005b34610227575f36600319011261022757602060ff600c5460c01c166040519015158152f35b3461022757611b373661266c565b600c54919390926001600160a01b03929091908316330361089f57838203610c0e5781156105445760328211610532575f5b828110611b7257005b611b7d8184846127ab565b3590611b8a8187896127ab565b3591600383101561022757611b9f8184612a6b565b5f526020926005845260405f2091600283018881549460ff8660081c1615611c715754169361ff001916905587611bd583612b89565b1694853b15610227576040516323b872dd60e01b81523060048201526001600160a01b038516602482015260448101839052955f908790606490829084905af195861561052757600196611c62575b50611c3260405180946125d0565b8201527f4cd34bfb21755b66fa5f4a977305912669546edc6fc8c21a2181b3395e07fc0060403392a35b01611b69565b611c6b9061271a565b8b611c24565b50505050505060019150611c5c565b6080366003190112610227576004356001600160401b03811161022757611cab90369060040161260f565b906024356001600160401b03811161022757611ccb90369060040161260f565b6064929192356001600160401b03811161022757611ced90369060040161263f565b939091611cf8612aab565b611d00612aef565b808603610c0e57851561054457603286116105325760ff600c5460c01c1615611838576004546001600160a01b031680156105685760443515610556575f905f5b8381106122545750604051637e062a3560e11b815291602083600481855afa928315610527575f93612233575b506040516370a0823160e01b81523060048201526020816024816001600160a01b0388165afa80156105275782915f916121fe575b50106107165760405163095ea7b360e01b81526001600160a01b0390921660048301526024820152906020908290815f816044810103926001600160a01b03165af18015610527576121df575b505f955f5b818110611e425787803411611e0b576001600355005b5f8080611e198194346129d8565b335af1611e246129e5565b5015611e305780610a3c565b604051633c31275160e21b8152600490fd5b611e4d8183886127ab565b35611e598285876127ab565b3590600382101561022757611e6e8183612a6b565b805f52600560205260ff600260405f20015460081c166118265760405190611e95826126eb565b338252826020830152611eab846040840161279f565b600160608301526001600160401b034216608083015260443560a08301525f52600560205260405f209060018060a01b038151166001600160601b0360a01b83541617825560208101516001830155604081015160038110156106dd576002830180546060840151608085015169ffffffffffffffff000060109190911b1669ffffffffffffffffffff1990921660ff949094169390931792151560081b61ff00169290921791909117905560a001516003909101556001600160a01b03611f7283612b89565b16998a3b15610227576040516323b872dd60e01b8152336004820152306024820152604481018390529a5f908c90606490829084905af19a8b156105275760409b6121d0575b50611fc283612b45565b9063ffffffff600c5460a01c168c5190611fdb8261272d565b81526044356020820152828d820152826060820152611ffb368d8c61282e565b60808201528c5161200b81612763565b5f815260a08201528c5161201e81612763565b5f815260c082015260018060a01b03600454168d519d8e633b6f743b60e01b81528160048201528061205360448201866128ca565b5f60248301520381845afa9d8e15610527575f9e61218f575b50908d60c0928151916040519261208284612748565b83525f602084015251604051608081815263c7c7f5b360e01b9091525193909284916120b3913391600485016129a3565b0391608051915af19b8c15610527575f9c612124575b916120fc7f1178cde67db78bc437458c38ef65b12c375c4099998599e5b096f1cea5a0bb009260019796959451906127cf565b9c5160408051948552604435602086015284019190915260608301523391608090a301611df5565b9b506001949392916120fc7f1178cde67db78bc437458c38ef65b12c375c4099998599e5b096f1cea5a0bb009260c03d60c011612188575b8061216c6121789260805161277e565b6080510160805161293a565b509e9294959697509250506120c9565b503d61215c565b91909d5060403d6040116121c9575b6121a8818461277e565b8201916040818403126102275760c0926121c191612864565b9d909161206c565b503d61219e565b6121d99061271a565b8b611fb8565b6121f79060203d6020116117c2576117b3818361277e565b5086611df0565b9150506020813d60201161222b575b8161221a6020938361277e565b81010312610227578190518b611da3565b3d915061220d565b61224d91935060203d60201161077f57610771818361277e565b9189611d6e565b916122608385876127ab565b35600381101561022757600191610c0161227992612b45565b9201611d41565b34610227575f366003190112610227576020600a54604051908152f35b34610227575f366003190112610227576122b5612a14565b60025460ff8116156122f45760ff19166002557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a1005b60405162461bcd60e51b815260206004820152601460248201527314185d5cd8589b194e881b9bdd081c185d5cd95960621b6044820152606490fd5b3461022757612373612341366125f0565b905f60a0604051612351816126eb565b8281528260208201528260408201528260608201528260808201520152612a6b565b5f52600560205260c060405f206040519061238d826126eb565b80546001600160a01b03908116835260018201546020840190815260028301549192919061240f604086016123c560ff85168261279f565b606087019260ff8560081c161515845260036001600160401b03968760808b019760101c16875201549660a0890197885260405198511688525160208801525160408701906125d0565b5115156060850152511660808301525160a0820152f35b34610227575f36600319011261022757602063ffffffff600c5460a01c16604051908152f35b34610227576040366003190112610227577f238399d427b947898edb290f5ff0f9109849b1c3ba196a42e35f00c50a54b98b60406124886125dd565b63ffffffff60243591612499612a14565b1690815f52600160205280835f205582519182526020820152a1005b34610227576020366003190112610227576004355f52600560205260c060405f2060018060a01b03815416906001600160401b03600182015491600360028201549101549260405194855260208501526125156040850160ff83166125d0565b60ff8160081c161515606085015260101c16608083015260a0820152f35b34610227575f36600319011261022757608060065460075460085460ff60095416916040519384526020840152604083015215156060820152f35b34610227575f36600319011261022757604080516001815260016020820152f35b34610227575f366003190112610227577f0000000000000000000000001792a96e5668ad7c167ab804a100ce42395ce54d6001600160a01b03168152602090f35b9060038210156106dd5752565b6004359063ffffffff8216820361022757565b6040906003190112610227576004356003811015610227579060243590565b9181601f84011215610227578235916001600160401b038311610227576020808501948460051b01011161022757565b9181601f84011215610227578235916001600160401b038311610227576020838186019501011161022757565b6040600319820112610227576001600160401b039160043583811161022757826126989160040161260f565b93909392602435918211610227576126b29160040161260f565b9091565b600435906001600160a01b038216820361022757565b6040906003190112610227576004359060243560038110156102275790565b60c081019081106001600160401b0382111761270657604052565b634e487b7160e01b5f52604160045260245ffd5b6001600160401b03811161270657604052565b60e081019081106001600160401b0382111761270657604052565b604081019081106001600160401b0382111761270657604052565b602081019081106001600160401b0382111761270657604052565b90601f801991011681019081106001600160401b0382111761270657604052565b60038210156106dd5752565b91908110156127bb5760051b0190565b634e487b7160e01b5f52603260045260245ffd5b91908201809211610b7f57565b9081602091031261022757516001600160a01b03811681036102275790565b90816020910312610227575180151581036102275790565b6001600160401b03811161270657601f01601f191660200190565b92919261283a82612813565b91612848604051938461277e565b829481845281830111610227578281602093845f960137010152565b91908260409103126102275760405161287c81612748565b6020808294805184520151910152565b91908251928382525f5b8481106128b6575050825f602080949584010152601f8019910116010190565b602081830181015184830182015201612896565b6129379163ffffffff825116815260208201516020820152604082015160408201526060820151606082015260c0612926612914608085015160e0608086015260e085019061288c565b60a085015184820360a086015261288c565b9201519060c081840391015261288c565b90565b919082810360c081126102275760801361022757604051906001600160401b036060830181811184821017612706576040528451835260208501519081168103610227578260809160206129379501526129978360408801612864565b60408201529401612864565b91939260206129bc6060936080865260808601906128ca565b86518583015295015160408401526001600160a01b0316910152565b91908203918211610b7f57565b3d15612a0f573d906129f682612813565b91612a04604051938461277e565b82523d5f602084013e565b606090565b5f546001600160a01b03163303612a2757565b606460405162461bcd60e51b815260206004820152602060248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152fd5b9060405190602082019260038110156106dd5760f81b8352602182015260218152606081018181106001600160401b038211176127065760405251902090565b60ff60025416612ab757565b60405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606490fd5b600260035414612b00576002600355565b60405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606490fd5b60038110156106dd578015612b825760018114612b7b57600214612b755760405163517172a160e11b8152600490fd5b60085490565b5060075490565b5060065490565b60038110156106dd578015612c025760018114612bdd57600214612bb95760405163517172a160e11b8152600490fd5b7f0000000000000000000000001792a96e5668ad7c167ab804a100ce42395ce54d90565b507f000000000000000000000000c0ffee8ff7e5497c2d6f7684859709225fcc5be890565b507f00000000000000000000000023581767a106ae21c074b2276d25e5c3e136a68b90565b60405163a9059cbb60e01b60208201526001600160a01b0390921660248301526044820192909252612c6f91612c6a82606481015b03601f19810184528361277e565b612cb3565b565b6040516323b872dd60e01b60208201526001600160a01b0392831660248201529290911660448301526064820192909252612c6f91612c6a8260848101612c5c565b604051612d10916001600160a01b0316612ccc82612748565b5f806020958685527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c656487860152868151910182855af1612d0a6129e5565b91612d98565b805190828215928315612d80575b50505015612d295750565b6084906040519062461bcd60e51b82526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152fd5b612d9093508201810191016127fb565b5f8281612d1e565b91929015612dfa5750815115612dac575090565b3b15612db55790565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b825190915015612e0d5750805190602001fd5b60405162461bcd60e51b815260206004820152908190612e3190602483019061288c565b0390fdfea5955cb007b105cd3ff3bf43a1ec37247fe7c45ba2ba85489a20d7f7e5dac551a2646970667358221220dca4776dbf0c2f5f85394dcde8f756f93ee377cbfffd5a355ac3804734365a8064736f6c63430008180033

Verified Source Code Full Match

Compiler: v0.8.24+commit.e11b9ed9 EVM: cancun Optimization: Yes (200 runs)
MoonbirdsEscrowAdapter.sol 699 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {OAppCore} from "@layerzerolabs/oapp-evm/contracts/oapp/OAppCore.sol";
import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ILayerZeroComposer} from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroComposer.sol";
import {ILayerZeroEndpointV2} from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
import {
    IOFT,
    SendParam,
    MessagingFee,
    MessagingReceipt
} from "@layerzerolabs/oft-evm/contracts/oft/interfaces/IOFT.sol";
import {OFTComposeMsgCodec} from "@layerzerolabs/oft-evm/contracts/oft/libs/OFTComposeMsgCodec.sol";

/// @title MoonbirdsEscrowAdapter
/// @notice Escrow adapter for locking Moonbirds/Mythics/Oddities NFTs and sending BIRB tokens to Solana
/// @dev Uses LayerZero OFT to send tokens cross-chain when users nest NFTs.
contract MoonbirdsEscrowAdapter is OAppCore, ILayerZeroComposer, Pausable, ReentrancyGuard {
    using SafeERC20 for IERC20;

    // ============ Enums ============

    enum Collection {
        MOONBIRDS,
        MYTHICS,
        ODDITIES
    }

    // ============ Structs ============

    struct EscrowRecord {
        address owner; // Original depositor (receives NFT on redeem)
        uint256 tokenId;
        Collection collection;
        bool active;
        uint64 nestedAt; // Timestamp of nesting
        bytes32 solanaAddress; // Solana address for validation in lzCompose
    }

    struct TokenRateConfig {
        uint256 moonbirdsRate; // Default: 10,000 * 10**6 (6 decimals)
        uint256 mythicsRate; // Default: 3,000 * 10**6
        uint256 odditiesRate; // Default: 3,000 * 10**6
        bool locked; // Rates locked by default after configuration
    }

    // ============ State Variables ============

    // Collection addresses
    IERC721 public immutable moonbirds;
    IERC721 public immutable mythics;
    IERC721 public immutable oddities;

    // $BIRB OFT for cross-chain transfers
    IOFT public birbOft;

    // Escrow state: keccak256(collection, tokenId) => EscrowRecord
    mapping(bytes32 => EscrowRecord) public escrows;

    // Token rates configuration
    TokenRateConfig public tokenRates;

    // Fee configuration
    uint256 public redemptionFee; // Fee in $BIRB (e.g., 100 * 10**6)
    address public treasury;

    // Admin role for emergency actions
    address public admin;

    // Solana endpoint ID (LayerZero)
    uint32 public solanaEid;

    // Deposit control (allows pausing nesting only, while still allowing unnesting)
    // Default false: deposits should only be enabled after lockRates() is called
    bool public depositsEnabled = false;

    // One-time configuration flags
    bool public redemptionFeeSet;

    // ============ Events ============

    event Nested(
        address indexed owner,
        Collection indexed collection,
        uint256 tokenId,
        bytes32 solanaAddress,
        uint256 tokenAmount,
        bytes32 guid
    );

    event Released(address indexed recipient, Collection indexed collection, uint256 tokenId, bool viaCompose);

    event AdminUnlock(address indexed admin, address indexed recipient, Collection collection, uint256 tokenId);

    event RatesUpdated(uint256 moonbirds, uint256 mythics, uint256 oddities);
    event RatesLocked();
    event FeeUpdated(uint256 newFee);
    event TreasuryUpdated(address newTreasury);
    event AdminUpdated(address newAdmin);
    event BirbOftUpdated(address newBirbOft);
    event SolanaEidUpdated(uint32 newEid);
    event DepositsEnabledUpdated(bool enabled);

    // ============ Errors ============

    error InvalidSolanaAddress();
    error AlreadyNested();
    error NotNested();
    error NotOriginalOwner();
    error NotAdmin();
    error RatesAreLocked();
    error RedemptionFeeAlreadySet();
    error BirbOftAlreadySet();
    error InvalidTreasury();
    error InvalidAdmin();
    error InsufficientTokenBalance();
    error InvalidCollection();
    error InvalidComposeSender();
    error InvalidComposeFrom();
    error InsufficientTokensReceived();
    error OnlyEndpoint();
    error DepositsDisabled();
    error BirbOftNotConfigured();
    error BatchTooLarge();
    error ArrayLengthMismatch();
    error EmptyBatch();
    error RefundFailed();
    error InvalidSourceChain();

    // ============ Constants ============

    uint256 public constant MAX_BATCH_SIZE = 50;

    // ============ Constructor ============

    constructor(
        address _endpoint,
        address _owner,
        address _moonbirds,
        address _mythics,
        address _oddities,
        address _treasury,
        uint32 _solanaEid
    ) OAppCore(_endpoint, _owner) {
        if (_moonbirds == address(0) || _mythics == address(0) || _oddities == address(0)) {
            revert InvalidCollection();
        }
        if (_treasury == address(0)) revert InvalidTreasury();

        moonbirds = IERC721(_moonbirds);
        mythics = IERC721(_mythics);
        oddities = IERC721(_oddities);
        treasury = _treasury;
        admin = _owner;
        solanaEid = _solanaEid;

        // Initialize default rates (6 decimals)
        tokenRates = TokenRateConfig({
            moonbirdsRate: 10_000 * 10 ** 6, mythicsRate: 3_000 * 10 ** 6, odditiesRate: 3_000 * 10 ** 6, locked: false
        });

        // Transfer ownership from deployer to intended owner
        _transferOwnership(_owner);
    }

    // ============ Core Functions ============

    /// @notice Lock NFT and send BIRB tokens to Solana via OFT
    /// @dev Tokens are sent from this contract's balance via BirbOFT.send()
    /// @param tokenId The NFT token ID to nest
    /// @param collection Which collection (MOONBIRDS, MYTHICS, ODDITIES)
    /// @param solanaAddress The recipient's Solana address (32 bytes)
    /// @param extraOptions LayerZero extra options for the message
    function nest(uint256 tokenId, Collection collection, bytes32 solanaAddress, bytes calldata extraOptions)
        external
        payable
        whenNotPaused
        nonReentrant
    {
        // CHECKS
        if (!depositsEnabled) revert DepositsDisabled();
        if (address(birbOft) == address(0)) revert BirbOftNotConfigured();
        if (solanaAddress == bytes32(0)) revert InvalidSolanaAddress();

        bytes32 escrowKey = _getEscrowKey(collection, tokenId);
        if (escrows[escrowKey].active) revert AlreadyNested();

        uint256 tokenAmount = _getTokenAmount(collection);
        address tokenAddr = birbOft.token();
        if (IERC20(tokenAddr).balanceOf(address(this)) < tokenAmount) {
            revert InsufficientTokenBalance();
        }

        // EFFECTS - Update state before external calls (CEI pattern)
        escrows[escrowKey] = EscrowRecord({
            owner: msg.sender,
            tokenId: tokenId,
            collection: collection,
            active: true,
            nestedAt: uint64(block.timestamp),
            solanaAddress: solanaAddress
        });

        // INTERACTIONS - External calls after state updates
        IERC721 nftContract = _getCollectionContract(collection);
        nftContract.transferFrom(msg.sender, address(this), tokenId);

        // Approve BirbOFT to spend tokens
        IERC20(tokenAddr).approve(address(birbOft), tokenAmount);

        // Build OFT send parameters
        SendParam memory sendParam = SendParam({
            dstEid: solanaEid,
            to: solanaAddress,
            amountLD: tokenAmount,
            minAmountLD: tokenAmount,
            extraOptions: extraOptions,
            composeMsg: "",
            oftCmd: ""
        });

        // Send tokens via OFT
        MessagingFee memory fee = MessagingFee({nativeFee: msg.value, lzTokenFee: 0});
        (MessagingReceipt memory receipt,) = birbOft.send{value: msg.value}(sendParam, fee, payable(msg.sender));

        emit Nested(msg.sender, collection, tokenId, solanaAddress, tokenAmount, receipt.guid);
    }

    /// @notice Handle incoming OFT compose message (redeem from Solana)
    /// @dev Called by LayerZero endpoint after OFT tokens are received
    /// @param _from The OApp that initiated the compose (should be BirbOFT)
    /// @param _guid The unique identifier for the LayerZero message
    /// @param _message The composed message containing redeem details
    /// @param _executor The address executing the compose
    /// @param _extraData Additional data (unused)
    function lzCompose(
        address _from,
        bytes32 _guid,
        bytes calldata _message,
        address _executor,
        bytes calldata _extraData
    ) external payable override whenNotPaused nonReentrant {
        // Only endpoint can call this
        if (msg.sender != address(endpoint)) revert OnlyEndpoint();

        // Verify the compose came from our BirbOFT
        if (_from != address(birbOft)) revert InvalidComposeSender();

        // Validate source chain is Solana
        uint32 srcEid = OFTComposeMsgCodec.srcEid(_message);
        if (srcEid != solanaEid) revert InvalidSourceChain();

        // Decode the OFT compose message header
        uint256 amountReceived = OFTComposeMsgCodec.amountLD(_message);
        bytes memory composeMsg = OFTComposeMsgCodec.composeMsg(_message);

        // Decode our custom compose message: (collection, tokenId)
        (uint8 collectionId, uint256 tokenId) = abi.decode(composeMsg, (uint8, uint256));

        // Validate collection ID before casting to enum (prevents Panic on invalid value)
        if (collectionId > 2) revert InvalidCollection();
        Collection collection = Collection(collectionId);
        bytes32 escrowKey = _getEscrowKey(collection, tokenId);
        EscrowRecord storage record = escrows[escrowKey];

        // CHECKS
        if (!record.active) revert NotNested();

        // Validate that the compose came from the expected Solana address
        bytes32 composeFrom = OFTComposeMsgCodec.composeFrom(_message);
        if (composeFrom != record.solanaAddress) revert InvalidComposeFrom();

        uint256 requiredAmount = _getTokenAmount(collection) + redemptionFee;
        if (amountReceived < requiredAmount) revert InsufficientTokensReceived();
        uint256 excess = amountReceived - requiredAmount;

        // EFFECTS - Update state before external calls (CEI pattern)
        address recipient = record.owner;
        record.active = false;

        // INTERACTIONS - External calls after state updates
        // Transfer fee to treasury if applicable
        if (redemptionFee > 0) {
            address tokenAddr = birbOft.token();
            IERC20(tokenAddr).safeTransfer(treasury, redemptionFee);
        }

        // Tokens stay in this contract for evergreen re-nesting
        // Release NFT to original owner
        IERC721 nftContract = _getCollectionContract(collection);
        nftContract.transferFrom(address(this), recipient, tokenId);

        // Refund any excess tokens to the original owner
        if (excess > 0) {
            address tokenAddr = birbOft.token();
            IERC20(tokenAddr).safeTransfer(recipient, excess);
        }

        emit Released(recipient, collection, tokenId, true);
    }

    /// @notice Alternative: Release NFT directly on Ethereum with token repayment
    /// @dev User must have $BIRB on ETH and call this directly
    function releaseWithTokens(uint256 tokenId, Collection collection) external whenNotPaused nonReentrant {
        bytes32 escrowKey = _getEscrowKey(collection, tokenId);
        EscrowRecord storage record = escrows[escrowKey];

        // CHECKS
        if (!record.active) revert NotNested();
        if (msg.sender != record.owner) revert NotOriginalOwner();

        uint256 tokenAmount = _getTokenAmount(collection);
        uint256 totalRequired = tokenAmount + redemptionFee;

        address tokenAddr = birbOft.token();
        IERC20 birbToken = IERC20(tokenAddr);

        if (birbToken.balanceOf(msg.sender) < totalRequired) {
            revert InsufficientTokenBalance();
        }

        // EFFECTS - Update state before external calls (CEI pattern)
        record.active = false;

        // INTERACTIONS - External calls after state updates
        birbToken.safeTransferFrom(msg.sender, address(this), totalRequired);

        if (redemptionFee > 0) {
            birbToken.safeTransfer(treasury, redemptionFee);
        }

        // Base tokens stay in contract for evergreen re-nesting
        IERC721 nftContract = _getCollectionContract(collection);
        nftContract.transferFrom(address(this), msg.sender, tokenId);

        emit Released(msg.sender, collection, tokenId, false);
    }

    // ============ Admin Functions ============

    /// @notice Admin unlock - returns NFT to original depositor address
    /// @param tokenId The NFT token ID to unlock
    /// @param collection Which collection (MOONBIRDS, MYTHICS, ODDITIES)
    function adminUnlock(uint256 tokenId, Collection collection) external {
        if (msg.sender != admin) revert NotAdmin();

        bytes32 escrowKey = _getEscrowKey(collection, tokenId);
        EscrowRecord storage record = escrows[escrowKey];

        if (!record.active) revert NotNested();

        address recipient = record.owner; // Always original owner
        record.active = false;

        IERC721 nftContract = _getCollectionContract(collection);
        nftContract.transferFrom(address(this), recipient, tokenId);

        emit AdminUnlock(msg.sender, recipient, collection, tokenId);
    }

    /// @notice Batch admin unlock - returns multiple NFTs to their original depositors
    /// @param tokenIds Array of NFT token IDs to unlock
    /// @param collections Array of collections corresponding to each tokenId
    function adminUnlockBatch(uint256[] calldata tokenIds, Collection[] calldata collections) external {
        if (msg.sender != admin) revert NotAdmin();
        if (tokenIds.length != collections.length) revert ArrayLengthMismatch();
        if (tokenIds.length == 0) revert EmptyBatch();
        if (tokenIds.length > MAX_BATCH_SIZE) revert BatchTooLarge();

        for (uint256 i = 0; i < tokenIds.length; i++) {
            uint256 tokenId = tokenIds[i];
            Collection collection = collections[i];

            bytes32 escrowKey = _getEscrowKey(collection, tokenId);
            EscrowRecord storage record = escrows[escrowKey];

            if (!record.active) continue; // Skip inactive records

            address recipient = record.owner;
            record.active = false;

            IERC721 nftContract = _getCollectionContract(collection);
            nftContract.transferFrom(address(this), recipient, tokenId);

            emit AdminUnlock(msg.sender, recipient, collection, tokenId);
        }
    }

    // ============ Batch Functions ============

    /// @notice Batch nest multiple NFTs and send BIRB to Solana
    /// @param tokenIds Array of NFT token IDs to nest
    /// @param collections Array of collections corresponding to each tokenId
    /// @param solanaAddress The recipient's Solana address (32 bytes)
    /// @param extraOptions LayerZero extra options for the message
    function nestBatch(
        uint256[] calldata tokenIds,
        Collection[] calldata collections,
        bytes32 solanaAddress,
        bytes calldata extraOptions
    ) external payable whenNotPaused nonReentrant {
        // Validation
        if (tokenIds.length != collections.length) revert ArrayLengthMismatch();
        if (tokenIds.length == 0) revert EmptyBatch();
        if (tokenIds.length > MAX_BATCH_SIZE) revert BatchTooLarge();
        if (!depositsEnabled) revert DepositsDisabled();
        if (address(birbOft) == address(0)) revert BirbOftNotConfigured();
        if (solanaAddress == bytes32(0)) revert InvalidSolanaAddress();

        // Calculate total BIRB needed
        uint256 totalTokens = 0;
        for (uint256 i = 0; i < collections.length; i++) {
            totalTokens += _getTokenAmount(collections[i]);
        }

        address tokenAddr = birbOft.token();
        if (IERC20(tokenAddr).balanceOf(address(this)) < totalTokens) {
            revert InsufficientTokenBalance();
        }

        // Approve total BIRB for OFT
        IERC20(tokenAddr).approve(address(birbOft), totalTokens);

        uint256 feeUsed = 0;

        // Process each NFT
        for (uint256 i = 0; i < tokenIds.length; i++) {
            uint256 tokenId = tokenIds[i];
            Collection collection = collections[i];

            bytes32 escrowKey = _getEscrowKey(collection, tokenId);
            if (escrows[escrowKey].active) revert AlreadyNested();

            // Create escrow record
            escrows[escrowKey] = EscrowRecord({
                owner: msg.sender,
                tokenId: tokenId,
                collection: collection,
                active: true,
                nestedAt: uint64(block.timestamp),
                solanaAddress: solanaAddress
            });

            // Transfer NFT
            IERC721 nftContract = _getCollectionContract(collection);
            nftContract.transferFrom(msg.sender, address(this), tokenId);

            // Send BIRB via OFT
            uint256 tokenAmount = _getTokenAmount(collection);
            SendParam memory sendParam = SendParam({
                dstEid: solanaEid,
                to: solanaAddress,
                amountLD: tokenAmount,
                minAmountLD: tokenAmount,
                extraOptions: extraOptions,
                composeMsg: "",
                oftCmd: ""
            });

            MessagingFee memory quotedFee = birbOft.quoteSend(sendParam, false);
            MessagingFee memory fee = MessagingFee({nativeFee: quotedFee.nativeFee, lzTokenFee: 0});

            (MessagingReceipt memory receipt,) =
                birbOft.send{value: quotedFee.nativeFee}(sendParam, fee, payable(msg.sender));

            feeUsed += quotedFee.nativeFee;

            emit Nested(msg.sender, collection, tokenId, solanaAddress, tokenAmount, receipt.guid);
        }

        // Refund excess ETH
        if (msg.value > feeUsed) {
            (bool success,) = payable(msg.sender).call{value: msg.value - feeUsed}("");
            if (!success) revert RefundFailed();
        }
    }

    /// @notice Batch release multiple NFTs with token repayment
    /// @param tokenIds Array of NFT token IDs to release
    /// @param collections Array of collections corresponding to each tokenId
    function releaseWithTokensBatch(uint256[] calldata tokenIds, Collection[] calldata collections)
        external
        whenNotPaused
        nonReentrant
    {
        // Validation
        if (tokenIds.length != collections.length) revert ArrayLengthMismatch();
        if (tokenIds.length == 0) revert EmptyBatch();
        if (tokenIds.length > MAX_BATCH_SIZE) revert BatchTooLarge();
        if (address(birbOft) == address(0)) revert BirbOftNotConfigured();

        // CHECKS: Validate ALL records and calculate tokens in single loop
        uint256 totalTokens = 0;
        for (uint256 i = 0; i < tokenIds.length; i++) {
            bytes32 escrowKey = _getEscrowKey(collections[i], tokenIds[i]);
            EscrowRecord storage record = escrows[escrowKey];
            if (!record.active) revert NotNested();
            if (msg.sender != record.owner) revert NotOriginalOwner();
            totalTokens += _getTokenAmount(collections[i]);
        }
        uint256 totalFee = redemptionFee * tokenIds.length;
        uint256 totalRequired = totalTokens + totalFee;

        // Transfer all tokens from user at once
        address tokenAddr = birbOft.token();
        IERC20(tokenAddr).safeTransferFrom(msg.sender, address(this), totalRequired);

        // Release each NFT
        for (uint256 i = 0; i < tokenIds.length; i++) {
            bytes32 escrowKey = _getEscrowKey(collections[i], tokenIds[i]);
            escrows[escrowKey].active = false;

            IERC721 nftContract = _getCollectionContract(collections[i]);
            nftContract.transferFrom(address(this), msg.sender, tokenIds[i]);

            emit Released(msg.sender, collections[i], tokenIds[i], false);
        }

        // Transfer fees to treasury
        if (totalFee > 0) {
            IERC20(tokenAddr).safeTransfer(treasury, totalFee);
        }
    }

    /// @notice Quote LayerZero fees for batch nesting
    /// @param collections Array of collections to quote for
    /// @param solanaAddress The recipient's Solana address (32 bytes)
    /// @param extraOptions LayerZero extra options for the message
    /// @return totalNativeFee Total native fee required for all sends
    function quoteNestBatch(Collection[] calldata collections, bytes32 solanaAddress, bytes calldata extraOptions)
        external
        view
        returns (uint256 totalNativeFee)
    {
        if (address(birbOft) == address(0)) revert BirbOftNotConfigured();
        if (solanaAddress == bytes32(0)) revert InvalidSolanaAddress();
        if (collections.length == 0) revert EmptyBatch();
        if (collections.length > MAX_BATCH_SIZE) revert BatchTooLarge();

        for (uint256 i = 0; i < collections.length; i++) {
            uint256 tokenAmount = _getTokenAmount(collections[i]);
            SendParam memory sendParam = SendParam({
                dstEid: solanaEid,
                to: solanaAddress,
                amountLD: tokenAmount,
                minAmountLD: tokenAmount,
                extraOptions: extraOptions,
                composeMsg: "",
                oftCmd: ""
            });
            MessagingFee memory fee = birbOft.quoteSend(sendParam, false);
            totalNativeFee += fee.nativeFee;
        }
    }

    /// @notice Update token rates (only if not locked)
    function setTokenRates(uint256 _moonbirds, uint256 _mythics, uint256 _oddities) external onlyOwner {
        if (tokenRates.locked) revert RatesAreLocked();

        tokenRates.moonbirdsRate = _moonbirds;
        tokenRates.mythicsRate = _mythics;
        tokenRates.odditiesRate = _oddities;

        emit RatesUpdated(_moonbirds, _mythics, _oddities);
    }

    /// @notice Lock rates permanently
    function lockRates() external onlyOwner {
        tokenRates.locked = true;
        emit RatesLocked();
    }

    /// @notice Set redemption fee (one-time only)
    /// @dev Can only be set once. Use 0 for no fee.
    function setRedemptionFee(uint256 _fee) external onlyOwner {
        if (redemptionFeeSet) revert RedemptionFeeAlreadySet();
        redemptionFeeSet = true;
        redemptionFee = _fee;
        emit FeeUpdated(_fee);
    }

    /// @notice Update treasury address
    function setTreasury(address _treasury) external onlyOwner {
        if (_treasury == address(0)) revert InvalidTreasury();
        treasury = _treasury;
        emit TreasuryUpdated(_treasury);
    }

    /// @notice Update admin address
    function setAdmin(address _admin) external onlyOwner {
        if (_admin == address(0)) revert InvalidAdmin();
        admin = _admin;
        emit AdminUpdated(_admin);
    }

    /// @notice Set $BIRB OFT address (one-time only)
    /// @dev Can only be set once to prevent breaking pending cross-chain messages
    function setBirbOft(address _birbOft) external onlyOwner {
        if (address(birbOft) != address(0)) revert BirbOftAlreadySet();
        if (_birbOft == address(0)) revert BirbOftNotConfigured();
        birbOft = IOFT(_birbOft);
        emit BirbOftUpdated(_birbOft);
    }

    /// @notice Set Solana endpoint ID
    function setSolanaEid(uint32 _eid) external onlyOwner {
        solanaEid = _eid;
        emit SolanaEidUpdated(_eid);
    }

    /// @notice Pause nesting and redemptions
    function pause() external onlyOwner {
        _pause();
    }

    /// @notice Unpause
    function unpause() external onlyOwner {
        _unpause();
    }

    /// @notice Enable or disable deposits (nesting) - allows unnesting to continue
    function setDepositsEnabled(bool _enabled) external onlyOwner {
        depositsEnabled = _enabled;
        emit DepositsEnabledUpdated(_enabled);
    }

    // ============ View Functions ============

    /// @notice Returns the OApp version
    /// @dev Required by IOAppCore interface
    function oAppVersion() public pure virtual override returns (uint64 senderVersion, uint64 receiverVersion) {
        return (1, 1);
    }

    function getEscrowRecord(Collection collection, uint256 tokenId) external view returns (EscrowRecord memory) {
        return escrows[_getEscrowKey(collection, tokenId)];
    }

    function isNested(Collection collection, uint256 tokenId) external view returns (bool) {
        return escrows[_getEscrowKey(collection, tokenId)].active;
    }

    function getTokenAmount(Collection collection) external view returns (uint256) {
        return _getTokenAmount(collection);
    }

    /// @notice Quote the LayerZero fee for nesting
    /// @param collection Which collection to quote for (determines token amount)
    /// @param solanaAddress The recipient's Solana address (32 bytes)
    /// @param extraOptions LayerZero extra options for the message
    function quoteNest(Collection collection, bytes32 solanaAddress, bytes calldata extraOptions)
        external
        view
        returns (uint256 nativeFee, uint256 lzTokenFee)
    {
        if (address(birbOft) == address(0)) revert BirbOftNotConfigured();

        uint256 tokenAmount = _getTokenAmount(collection);

        // Build OFT send parameters for quote
        SendParam memory sendParam = SendParam({
            dstEid: solanaEid,
            to: solanaAddress,
            amountLD: tokenAmount,
            minAmountLD: tokenAmount,
            extraOptions: extraOptions,
            composeMsg: "",
            oftCmd: ""
        });

        MessagingFee memory fee = birbOft.quoteSend(sendParam, false);
        return (fee.nativeFee, fee.lzTokenFee);
    }

    // ============ Internal Functions ============

    function _getCollectionContract(Collection c) internal view returns (IERC721) {
        if (c == Collection.MOONBIRDS) return moonbirds;
        if (c == Collection.MYTHICS) return mythics;
        if (c == Collection.ODDITIES) return oddities;
        revert InvalidCollection();
    }

    function _getTokenAmount(Collection c) internal view returns (uint256) {
        if (c == Collection.MOONBIRDS) return tokenRates.moonbirdsRate;
        if (c == Collection.MYTHICS) return tokenRates.mythicsRate;
        if (c == Collection.ODDITIES) return tokenRates.odditiesRate;
        revert InvalidCollection();
    }

    function _getEscrowKey(Collection c, uint256 tokenId) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(c, tokenId));
    }
}
OAppCore.sol 83 lines
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IOAppCore, ILayerZeroEndpointV2 } from "./interfaces/IOAppCore.sol";

/**
 * @title OAppCore
 * @dev Abstract contract implementing the IOAppCore interface with basic OApp configurations.
 */
abstract contract OAppCore is IOAppCore, Ownable {
    // The LayerZero endpoint associated with the given OApp
    ILayerZeroEndpointV2 public immutable endpoint;

    // Mapping to store peers associated with corresponding endpoints
    mapping(uint32 eid => bytes32 peer) public peers;

    /**
     * @dev Constructor to initialize the OAppCore with the provided endpoint and delegate.
     * @param _endpoint The address of the LOCAL Layer Zero endpoint.
     * @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
     *
     * @dev The delegate typically should be set as the owner of the contract.
     */
    constructor(address _endpoint, address _delegate) {
        endpoint = ILayerZeroEndpointV2(_endpoint);

        if (_delegate == address(0)) revert InvalidDelegate();
        endpoint.setDelegate(_delegate);
    }

    /**
     * @notice Sets the peer address (OApp instance) for a corresponding endpoint.
     * @param _eid The endpoint ID.
     * @param _peer The address of the peer to be associated with the corresponding endpoint.
     *
     * @dev Only the owner/admin of the OApp can call this function.
     * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.
     * @dev Set this to bytes32(0) to remove the peer address.
     * @dev Peer is a bytes32 to accommodate non-evm chains.
     */
    function setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner {
        _setPeer(_eid, _peer);
    }

    /**
     * @notice Sets the peer address (OApp instance) for a corresponding endpoint.
     * @param _eid The endpoint ID.
     * @param _peer The address of the peer to be associated with the corresponding endpoint.
     *
     * @dev Indicates that the peer is trusted to send LayerZero messages to this OApp.
     * @dev Set this to bytes32(0) to remove the peer address.
     * @dev Peer is a bytes32 to accommodate non-evm chains.
     */
    function _setPeer(uint32 _eid, bytes32 _peer) internal virtual {
        peers[_eid] = _peer;
        emit PeerSet(_eid, _peer);
    }

    /**
     * @notice Internal function to get the peer address associated with a specific endpoint; reverts if NOT set.
     * ie. the peer is set to bytes32(0).
     * @param _eid The endpoint ID.
     * @return peer The address of the peer associated with the specified endpoint.
     */
    function _getPeerOrRevert(uint32 _eid) internal view virtual returns (bytes32) {
        bytes32 peer = peers[_eid];
        if (peer == bytes32(0)) revert NoPeer(_eid);
        return peer;
    }

    /**
     * @notice Sets the delegate address for the OApp.
     * @param _delegate The address of the delegate to be set.
     *
     * @dev Only the owner/admin of the OApp can call this function.
     * @dev Provides the ability for a delegate to set configs, on behalf of the OApp, directly on the Endpoint contract.
     */
    function setDelegate(address _delegate) public onlyOwner {
        endpoint.setDelegate(_delegate);
    }
}
Pausable.sol 105 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    bool private _paused;

    /**
     * @dev Initializes the contract in unpaused state.
     */
    constructor() {
        _paused = false;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        require(!paused(), "Pausable: paused");
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        require(paused(), "Pausable: not paused");
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
}
ReentrancyGuard.sol 77 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == _ENTERED;
    }
}
IERC721.sol 132 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}
IERC20.sol 78 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
SafeERC20.sol 143 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/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 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.encodeWithSelector(token.transfer.selector, 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.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 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.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
        }
    }

    /**
     * @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.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);

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

    /**
     * @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);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @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, "SafeERC20: low-level call failed");
        require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
    }

    /**
     * @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.isContract(address(token));
    }
}
ILayerZeroComposer.sol 24 lines
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

/**
 * @title ILayerZeroComposer
 */
interface ILayerZeroComposer {
    /**
     * @notice Composes a LayerZero message from an OApp.
     * @param _from The address initiating the composition, typically the OApp where the lzReceive was called.
     * @param _guid The unique identifier for the corresponding LayerZero src/dst tx.
     * @param _message The composed message payload in bytes. NOT necessarily the same payload passed via lzReceive.
     * @param _executor The address of the executor for the composed message.
     * @param _extraData Additional arbitrary data in bytes passed by the entity who executes the lzCompose.
     */
    function lzCompose(
        address _from,
        bytes32 _guid,
        bytes calldata _message,
        address _executor,
        bytes calldata _extraData
    ) external payable;
}
ILayerZeroEndpointV2.sol 89 lines
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

import { IMessageLibManager } from "./IMessageLibManager.sol";
import { IMessagingComposer } from "./IMessagingComposer.sol";
import { IMessagingChannel } from "./IMessagingChannel.sol";
import { IMessagingContext } from "./IMessagingContext.sol";

struct MessagingParams {
    uint32 dstEid;
    bytes32 receiver;
    bytes message;
    bytes options;
    bool payInLzToken;
}

struct MessagingReceipt {
    bytes32 guid;
    uint64 nonce;
    MessagingFee fee;
}

struct MessagingFee {
    uint256 nativeFee;
    uint256 lzTokenFee;
}

struct Origin {
    uint32 srcEid;
    bytes32 sender;
    uint64 nonce;
}

interface ILayerZeroEndpointV2 is IMessageLibManager, IMessagingComposer, IMessagingChannel, IMessagingContext {
    event PacketSent(bytes encodedPayload, bytes options, address sendLibrary);

    event PacketVerified(Origin origin, address receiver, bytes32 payloadHash);

    event PacketDelivered(Origin origin, address receiver);

    event LzReceiveAlert(
        address indexed receiver,
        address indexed executor,
        Origin origin,
        bytes32 guid,
        uint256 gas,
        uint256 value,
        bytes message,
        bytes extraData,
        bytes reason
    );

    event LzTokenSet(address token);

    event DelegateSet(address sender, address delegate);

    function quote(MessagingParams calldata _params, address _sender) external view returns (MessagingFee memory);

    function send(
        MessagingParams calldata _params,
        address _refundAddress
    ) external payable returns (MessagingReceipt memory);

    function verify(Origin calldata _origin, address _receiver, bytes32 _payloadHash) external;

    function verifiable(Origin calldata _origin, address _receiver) external view returns (bool);

    function initializable(Origin calldata _origin, address _receiver) external view returns (bool);

    function lzReceive(
        Origin calldata _origin,
        address _receiver,
        bytes32 _guid,
        bytes calldata _message,
        bytes calldata _extraData
    ) external payable;

    // oapp can burn messages partially by calling this function with its own business logic if messages are verified in order
    function clear(address _oapp, Origin calldata _origin, bytes32 _guid, bytes calldata _message) external;

    function setLzToken(address _lzToken) external;

    function lzToken() external view returns (address);

    function nativeToken() external view returns (address);

    function setDelegate(address _delegate) external;
}
IOFT.sol 149 lines
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { MessagingReceipt, MessagingFee } from "../../oapp/OAppSender.sol";

/**
 * @dev Struct representing token parameters for the OFT send() operation.
 */
struct SendParam {
    uint32 dstEid; // Destination endpoint ID.
    bytes32 to; // Recipient address.
    uint256 amountLD; // Amount to send in local decimals.
    uint256 minAmountLD; // Minimum amount to send in local decimals.
    bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message.
    bytes composeMsg; // The composed message for the send() operation.
    bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations.
}

/**
 * @dev Struct representing OFT limit information.
 * @dev These amounts can change dynamically and are up the the specific oft implementation.
 */
struct OFTLimit {
    uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient.
    uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient.
}

/**
 * @dev Struct representing OFT receipt information.
 */
struct OFTReceipt {
    uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals.
    // @dev In non-default implementations, the amountReceivedLD COULD differ from this value.
    uint256 amountReceivedLD; // Amount of tokens to be received on the remote side.
}

/**
 * @dev Struct representing OFT fee details.
 * @dev Future proof mechanism to provide a standardized way to communicate fees to things like a UI.
 */
struct OFTFeeDetail {
    int256 feeAmountLD; // Amount of the fee in local decimals.
    string description; // Description of the fee.
}

/**
 * @title IOFT
 * @dev Interface for the OftChain (OFT) token.
 * @dev Does not inherit ERC20 to accommodate usage by OFTAdapter as well.
 * @dev This specific interface ID is '0x02e49c2c'.
 */
interface IOFT {
    // Custom error messages
    error InvalidLocalDecimals();
    error SlippageExceeded(uint256 amountLD, uint256 minAmountLD);

    // Events
    event OFTSent(
        bytes32 indexed guid, // GUID of the OFT message.
        uint32 dstEid, // Destination Endpoint ID.
        address indexed fromAddress, // Address of the sender on the src chain.
        uint256 amountSentLD, // Amount of tokens sent in local decimals.
        uint256 amountReceivedLD // Amount of tokens received in local decimals.
    );
    event OFTReceived(
        bytes32 indexed guid, // GUID of the OFT message.
        uint32 srcEid, // Source Endpoint ID.
        address indexed toAddress, // Address of the recipient on the dst chain.
        uint256 amountReceivedLD // Amount of tokens received in local decimals.
    );

    /**
     * @notice Retrieves interfaceID and the version of the OFT.
     * @return interfaceId The interface ID.
     * @return version The version.
     *
     * @dev interfaceId: This specific interface ID is '0x02e49c2c'.
     * @dev version: Indicates a cross-chain compatible msg encoding with other OFTs.
     * @dev If a new feature is added to the OFT cross-chain msg encoding, the version will be incremented.
     * ie. localOFT version(x,1) CAN send messages to remoteOFT version(x,1)
     */
    function oftVersion() external view returns (bytes4 interfaceId, uint64 version);

    /**
     * @notice Retrieves the address of the token associated with the OFT.
     * @return token The address of the ERC20 token implementation.
     */
    function token() external view returns (address);

    /**
     * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.
     * @return requiresApproval Needs approval of the underlying token implementation.
     *
     * @dev Allows things like wallet implementers to determine integration requirements,
     * without understanding the underlying token implementation.
     */
    function approvalRequired() external view returns (bool);

    /**
     * @notice Retrieves the shared decimals of the OFT.
     * @return sharedDecimals The shared decimals of the OFT.
     */
    function sharedDecimals() external view returns (uint8);

    /**
     * @notice Provides a quote for OFT-related operations.
     * @param _sendParam The parameters for the send operation.
     * @return limit The OFT limit information.
     * @return oftFeeDetails The details of OFT fees.
     * @return receipt The OFT receipt information.
     */
    function quoteOFT(
        SendParam calldata _sendParam
    ) external view returns (OFTLimit memory, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory);

    /**
     * @notice Provides a quote for the send() operation.
     * @param _sendParam The parameters for the send() operation.
     * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.
     * @return fee The calculated LayerZero messaging fee from the send() operation.
     *
     * @dev MessagingFee: LayerZero msg fee
     *  - nativeFee: The native fee.
     *  - lzTokenFee: The lzToken fee.
     */
    function quoteSend(SendParam calldata _sendParam, bool _payInLzToken) external view returns (MessagingFee memory);

    /**
     * @notice Executes the send() operation.
     * @param _sendParam The parameters for the send operation.
     * @param _fee The fee information supplied by the caller.
     *      - nativeFee: The native fee.
     *      - lzTokenFee: The lzToken fee.
     * @param _refundAddress The address to receive any excess funds from fees etc. on the src.
     * @return receipt The LayerZero messaging receipt from the send() operation.
     * @return oftReceipt The OFT receipt information.
     *
     * @dev MessagingReceipt: LayerZero msg receipt
     *  - guid: The unique identifier for the sent message.
     *  - nonce: The nonce of the sent message.
     *  - fee: The LayerZero fee incurred for the message.
     */
    function send(
        SendParam calldata _sendParam,
        MessagingFee calldata _fee,
        address _refundAddress
    ) external payable returns (MessagingReceipt memory, OFTReceipt memory);
}
OFTComposeMsgCodec.sol 91 lines
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

library OFTComposeMsgCodec {
    // Offset constants for decoding composed messages
    uint8 private constant NONCE_OFFSET = 8;
    uint8 private constant SRC_EID_OFFSET = 12;
    uint8 private constant AMOUNT_LD_OFFSET = 44;
    uint8 private constant COMPOSE_FROM_OFFSET = 76;

    /**
     * @dev Encodes a OFT composed message.
     * @param _nonce The nonce value.
     * @param _srcEid The source endpoint ID.
     * @param _amountLD The amount in local decimals.
     * @param _composeMsg The composed message.
     * @return _msg The encoded Composed message.
     */
    function encode(
        uint64 _nonce,
        uint32 _srcEid,
        uint256 _amountLD,
        bytes memory _composeMsg // 0x[composeFrom][composeMsg]
    ) internal pure returns (bytes memory _msg) {
        _msg = abi.encodePacked(_nonce, _srcEid, _amountLD, _composeMsg);
    }

    /**
     * @dev Retrieves the nonce from the composed message.
     * @param _msg The message.
     * @return The nonce value.
     */
    function nonce(bytes calldata _msg) internal pure returns (uint64) {
        return uint64(bytes8(_msg[:NONCE_OFFSET]));
    }

    /**
     * @dev Retrieves the source endpoint ID from the composed message.
     * @param _msg The message.
     * @return The source endpoint ID.
     */
    function srcEid(bytes calldata _msg) internal pure returns (uint32) {
        return uint32(bytes4(_msg[NONCE_OFFSET:SRC_EID_OFFSET]));
    }

    /**
     * @dev Retrieves the amount in local decimals from the composed message.
     * @param _msg The message.
     * @return The amount in local decimals.
     */
    function amountLD(bytes calldata _msg) internal pure returns (uint256) {
        return uint256(bytes32(_msg[SRC_EID_OFFSET:AMOUNT_LD_OFFSET]));
    }

    /**
     * @dev Retrieves the composeFrom value from the composed message.
     * @param _msg The message.
     * @return The composeFrom value.
     */
    function composeFrom(bytes calldata _msg) internal pure returns (bytes32) {
        return bytes32(_msg[AMOUNT_LD_OFFSET:COMPOSE_FROM_OFFSET]);
    }

    /**
     * @dev Retrieves the composed message.
     * @param _msg The message.
     * @return The composed message.
     */
    function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) {
        return _msg[COMPOSE_FROM_OFFSET:];
    }

    /**
     * @dev Converts an address to bytes32.
     * @param _addr The address to convert.
     * @return The bytes32 representation of the address.
     */
    function addressToBytes32(address _addr) internal pure returns (bytes32) {
        return bytes32(uint256(uint160(_addr)));
    }

    /**
     * @dev Converts bytes32 to an address.
     * @param _b The bytes32 value to convert.
     * @return The address representation of bytes32.
     */
    function bytes32ToAddress(bytes32 _b) internal pure returns (address) {
        return address(uint160(uint256(_b)));
    }
}
Ownable.sol 83 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

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

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
IOAppCore.sol 52 lines
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { ILayerZeroEndpointV2 } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";

/**
 * @title IOAppCore
 */
interface IOAppCore {
    // Custom error messages
    error OnlyPeer(uint32 eid, bytes32 sender);
    error NoPeer(uint32 eid);
    error InvalidEndpointCall();
    error InvalidDelegate();

    // Event emitted when a peer (OApp) is set for a corresponding endpoint
    event PeerSet(uint32 eid, bytes32 peer);

    /**
     * @notice Retrieves the OApp version information.
     * @return senderVersion The version of the OAppSender.sol contract.
     * @return receiverVersion The version of the OAppReceiver.sol contract.
     */
    function oAppVersion() external view returns (uint64 senderVersion, uint64 receiverVersion);

    /**
     * @notice Retrieves the LayerZero endpoint associated with the OApp.
     * @return iEndpoint The LayerZero endpoint as an interface.
     */
    function endpoint() external view returns (ILayerZeroEndpointV2 iEndpoint);

    /**
     * @notice Retrieves the peer (OApp) associated with a corresponding endpoint.
     * @param _eid The endpoint ID.
     * @return peer The peer address (OApp instance) associated with the corresponding endpoint.
     */
    function peers(uint32 _eid) external view returns (bytes32 peer);

    /**
     * @notice Sets the peer address (OApp instance) for a corresponding endpoint.
     * @param _eid The endpoint ID.
     * @param _peer The address of the peer to be associated with the corresponding endpoint.
     */
    function setPeer(uint32 _eid, bytes32 _peer) external;

    /**
     * @notice Sets the delegate address for the OApp Core.
     * @param _delegate The address of the delegate to be set.
     */
    function setDelegate(address _delegate) external;
}
Context.sol 28 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
IERC165.sol 25 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * 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[EIP 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);
}
IERC20Permit.sol 90 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @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.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @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].
     *
     * CAUTION: See Security Considerations above.
     */
    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);
}
Address.sol 244 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @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
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    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 Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @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, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * 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.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}
IMessageLibManager.sol 70 lines
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

struct SetConfigParam {
    uint32 eid;
    uint32 configType;
    bytes config;
}

interface IMessageLibManager {
    struct Timeout {
        address lib;
        uint256 expiry;
    }

    event LibraryRegistered(address newLib);
    event DefaultSendLibrarySet(uint32 eid, address newLib);
    event DefaultReceiveLibrarySet(uint32 eid, address newLib);
    event DefaultReceiveLibraryTimeoutSet(uint32 eid, address oldLib, uint256 expiry);
    event SendLibrarySet(address sender, uint32 eid, address newLib);
    event ReceiveLibrarySet(address receiver, uint32 eid, address newLib);
    event ReceiveLibraryTimeoutSet(address receiver, uint32 eid, address oldLib, uint256 timeout);

    function registerLibrary(address _lib) external;

    function isRegisteredLibrary(address _lib) external view returns (bool);

    function getRegisteredLibraries() external view returns (address[] memory);

    function setDefaultSendLibrary(uint32 _eid, address _newLib) external;

    function defaultSendLibrary(uint32 _eid) external view returns (address);

    function setDefaultReceiveLibrary(uint32 _eid, address _newLib, uint256 _timeout) external;

    function defaultReceiveLibrary(uint32 _eid) external view returns (address);

    function setDefaultReceiveLibraryTimeout(uint32 _eid, address _lib, uint256 _expiry) external;

    function defaultReceiveLibraryTimeout(uint32 _eid) external view returns (address lib, uint256 expiry);

    function isSupportedEid(uint32 _eid) external view returns (bool);

    function isValidReceiveLibrary(address _receiver, uint32 _eid, address _lib) external view returns (bool);

    /// ------------------- OApp interfaces -------------------
    function setSendLibrary(address _oapp, uint32 _eid, address _newLib) external;

    function getSendLibrary(address _sender, uint32 _eid) external view returns (address lib);

    function isDefaultSendLibrary(address _sender, uint32 _eid) external view returns (bool);

    function setReceiveLibrary(address _oapp, uint32 _eid, address _newLib, uint256 _gracePeriod) external;

    function getReceiveLibrary(address _receiver, uint32 _eid) external view returns (address lib, bool isDefault);

    function setReceiveLibraryTimeout(address _oapp, uint32 _eid, address _lib, uint256 _gracePeriod) external;

    function receiveLibraryTimeout(address _receiver, uint32 _eid) external view returns (address lib, uint256 expiry);

    function setConfig(address _oapp, address _lib, SetConfigParam[] calldata _params) external;

    function getConfig(
        address _oapp,
        address _lib,
        uint32 _eid,
        uint32 _configType
    ) external view returns (bytes memory config);
}
IMessagingComposer.sol 38 lines
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

interface IMessagingComposer {
    event ComposeSent(address from, address to, bytes32 guid, uint16 index, bytes message);
    event ComposeDelivered(address from, address to, bytes32 guid, uint16 index);
    event LzComposeAlert(
        address indexed from,
        address indexed to,
        address indexed executor,
        bytes32 guid,
        uint16 index,
        uint256 gas,
        uint256 value,
        bytes message,
        bytes extraData,
        bytes reason
    );

    function composeQueue(
        address _from,
        address _to,
        bytes32 _guid,
        uint16 _index
    ) external view returns (bytes32 messageHash);

    function sendCompose(address _to, bytes32 _guid, uint16 _index, bytes calldata _message) external;

    function lzCompose(
        address _from,
        address _to,
        bytes32 _guid,
        uint16 _index,
        bytes calldata _message,
        bytes calldata _extraData
    ) external payable;
}
IMessagingChannel.sol 34 lines
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

interface IMessagingChannel {
    event InboundNonceSkipped(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce);
    event PacketNilified(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);
    event PacketBurnt(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce, bytes32 payloadHash);

    function eid() external view returns (uint32);

    // this is an emergency function if a message cannot be verified for some reasons
    // required to provide _nextNonce to avoid race condition
    function skip(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce) external;

    function nilify(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;

    function burn(address _oapp, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) external;

    function nextGuid(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (bytes32);

    function inboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);

    function outboundNonce(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (uint64);

    function inboundPayloadHash(
        address _receiver,
        uint32 _srcEid,
        bytes32 _sender,
        uint64 _nonce
    ) external view returns (bytes32);

    function lazyInboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);
}
IMessagingContext.sol 9 lines
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

interface IMessagingContext {
    function isSendingMessage() external view returns (bool);

    function getSendContext() external view returns (uint32 dstEid, address sender);
}
OAppSender.sol 124 lines
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { MessagingParams, MessagingFee, MessagingReceipt } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
import { OAppCore } from "./OAppCore.sol";

/**
 * @title OAppSender
 * @dev Abstract contract implementing the OAppSender functionality for sending messages to a LayerZero endpoint.
 */
abstract contract OAppSender is OAppCore {
    using SafeERC20 for IERC20;

    // Custom error messages
    error NotEnoughNative(uint256 msgValue);
    error LzTokenUnavailable();

    // @dev The version of the OAppSender implementation.
    // @dev Version is bumped when changes are made to this contract.
    uint64 internal constant SENDER_VERSION = 1;

    /**
     * @notice Retrieves the OApp version information.
     * @return senderVersion The version of the OAppSender.sol contract.
     * @return receiverVersion The version of the OAppReceiver.sol contract.
     *
     * @dev Providing 0 as the default for OAppReceiver version. Indicates that the OAppReceiver is not implemented.
     * ie. this is a SEND only OApp.
     * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions
     */
    function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {
        return (SENDER_VERSION, 0);
    }

    /**
     * @dev Internal function to interact with the LayerZero EndpointV2.quote() for fee calculation.
     * @param _dstEid The destination endpoint ID.
     * @param _message The message payload.
     * @param _options Additional options for the message.
     * @param _payInLzToken Flag indicating whether to pay the fee in LZ tokens.
     * @return fee The calculated MessagingFee for the message.
     *      - nativeFee: The native fee for the message.
     *      - lzTokenFee: The LZ token fee for the message.
     */
    function _quote(
        uint32 _dstEid,
        bytes memory _message,
        bytes memory _options,
        bool _payInLzToken
    ) internal view virtual returns (MessagingFee memory fee) {
        return
            endpoint.quote(
                MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _payInLzToken),
                address(this)
            );
    }

    /**
     * @dev Internal function to interact with the LayerZero EndpointV2.send() for sending a message.
     * @param _dstEid The destination endpoint ID.
     * @param _message The message payload.
     * @param _options Additional options for the message.
     * @param _fee The calculated LayerZero fee for the message.
     *      - nativeFee: The native fee.
     *      - lzTokenFee: The lzToken fee.
     * @param _refundAddress The address to receive any excess fee values sent to the endpoint.
     * @return receipt The receipt for the sent message.
     *      - guid: The unique identifier for the sent message.
     *      - nonce: The nonce of the sent message.
     *      - fee: The LayerZero fee incurred for the message.
     */
    function _lzSend(
        uint32 _dstEid,
        bytes memory _message,
        bytes memory _options,
        MessagingFee memory _fee,
        address _refundAddress
    ) internal virtual returns (MessagingReceipt memory receipt) {
        // @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint.
        uint256 messageValue = _payNative(_fee.nativeFee);
        if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee);

        return
            // solhint-disable-next-line check-send-result
            endpoint.send{ value: messageValue }(
                MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0),
                _refundAddress
            );
    }

    /**
     * @dev Internal function to pay the native fee associated with the message.
     * @param _nativeFee The native fee to be paid.
     * @return nativeFee The amount of native currency paid.
     *
     * @dev If the OApp needs to initiate MULTIPLE LayerZero messages in a single transaction,
     * this will need to be overridden because msg.value would contain multiple lzFees.
     * @dev Should be overridden in the event the LayerZero endpoint requires a different native currency.
     * @dev Some EVMs use an ERC20 as a method for paying transactions/gasFees.
     * @dev The endpoint is EITHER/OR, ie. it will NOT support both types of native payment at a time.
     */
    function _payNative(uint256 _nativeFee) internal virtual returns (uint256 nativeFee) {
        if (msg.value != _nativeFee) revert NotEnoughNative(msg.value);
        return _nativeFee;
    }

    /**
     * @dev Internal function to pay the LZ token fee associated with the message.
     * @param _lzTokenFee The LZ token fee to be paid.
     *
     * @dev If the caller is trying to pay in the specified lzToken, then the lzTokenFee is passed to the endpoint.
     * @dev Any excess sent, is passed back to the specified _refundAddress in the _lzSend().
     */
    function _payLzToken(uint256 _lzTokenFee) internal virtual {
        // @dev Cannot cache the token because it is not immutable in the endpoint.
        address lzToken = endpoint.lzToken();
        if (lzToken == address(0)) revert LzTokenUnavailable();

        // Pay LZ token fee by sending tokens to the endpoint.
        IERC20(lzToken).safeTransferFrom(msg.sender, address(endpoint), _lzTokenFee);
    }
}

Read Contract

MAX_BATCH_SIZE 0xcfdbf254 → uint256
admin 0xf851a440 → address
birbOft 0xdc0dd404 → address
depositsEnabled 0x5392fd1c → bool
endpoint 0x5e280f11 → address
escrows 0x2d83549c → address, uint256, uint8, bool, uint64, bytes32
getEscrowRecord 0x3cc7a1de → tuple
getTokenAmount 0xd3c51180 → uint256
isNested 0x995ded4d → bool
moonbirds 0x6f7013a8 → address
mythics 0x7bd3347b → address
oAppVersion 0x17442b70 → uint64, uint64
oddities 0x00287294 → address
owner 0x8da5cb5b → address
paused 0x5c975abb → bool
peers 0xbb0b6a53 → bytes32
quoteNest 0x9f6dea8e → uint256, uint256
quoteNestBatch 0xe404d954 → uint256
redemptionFee 0x458f5815 → uint256
redemptionFeeSet 0xc87a5105 → bool
solanaEid 0x3472bd42 → uint32
tokenRates 0x26cc8a3c → uint256, uint256, uint256, bool
treasury 0x61d027b3 → address

Write Contract 21 functions

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

adminUnlock 0xe16c8bb2
uint256 tokenId
uint8 collection
adminUnlockBatch 0x526a654d
uint256[] tokenIds
uint8[] collections
lockRates 0x5a6da1e9
No parameters
lzCompose 0xd0a10260
address _from
bytes32 _guid
bytes _message
address _executor
bytes _extraData
nest 0x8131842f
uint256 tokenId
uint8 collection
bytes32 solanaAddress
bytes extraOptions
nestBatch 0x5110c274
uint256[] tokenIds
uint8[] collections
bytes32 solanaAddress
bytes extraOptions
pause 0x8456cb59
No parameters
releaseWithTokens 0xe1b51534
uint256 tokenId
uint8 collection
releaseWithTokensBatch 0xd38bf4b1
uint256[] tokenIds
uint8[] collections
renounceOwnership 0x715018a6
No parameters
setAdmin 0x704b6c02
address _admin
setBirbOft 0xce267450
address _birbOft
setDelegate 0xca5eb5e1
address _delegate
setDepositsEnabled 0x9f04586c
bool _enabled
setPeer 0x3400288b
uint32 _eid
bytes32 _peer
setRedemptionFee 0x7dbc1df0
uint256 _fee
setSolanaEid 0xd8550b3a
uint32 _eid
setTokenRates 0xaccbe6fe
uint256 _moonbirds
uint256 _mythics
uint256 _oddities
setTreasury 0xf0f44260
address _treasury
transferOwnership 0xf2fde38b
address newOwner
unpause 0x3f4ba83a
No parameters

Recent Transactions

No transactions found for this address