Address Contract Verified
Address
0x49960769Aa50b533aa628AcDAa054bD5263d0d42
Balance
0 ETH
Nonce
1
Code Size
11915 bytes
Creator
0x8aCb306D...63ea at tx 0xf1563a2c...f7c673
Indexed Transactions
0
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