Address Contract Partially Verified
Address
0x660C56a3f42D9527324af8A51CBe78E8f2Db17aE
Balance
0.000000000 ETH
Nonce
1
Code Size
17583 bytes
Creator
0x0F92D394...b068 at tx 0xd052de93...5250de
Indexed Transactions
0
Contract Bytecode
17583 bytes
0x6080604052600436101561001257600080fd5b60003560e01c806301ffc9a71461018257806311a003271461017d578063150b7a021461017857806317a288df146101735780631efeff841461016e5780632b1708b0146101695780632ede65df1461016457806332689eb81461015f5780633596188d1461015a5780635333a0a4146101555780635e9e78d014610150578063715018a61461014b57806374268ff2146101465780637fea43b814610141578063854cff2f1461013c5780638a72ea6a146101375780638b4c042a146101325780638da5cb5b1461012d578063a85c38ef14610128578063c402b96514610123578063e74b981b1461011e578063f23a6e61146101195763f2fde38b0361018757610cce565b610caf565b610bab565b610b94565b610b21565b6109b0565b610997565b610966565b61083c565b6107f9565b6106f5565b6106dd565b6106b9565b61069d565b610621565b61060d565b6105cd565b6105b9565b610570565b610534565b610318565b610235565b6101dd565b600080fd5b7fffffffff0000000000000000000000000000000000000000000000000000000081165b0361018757565b905035906101c48261018c565b565b90602082820312610187576101da916101b7565b90565b346101875761020a6101f86101f33660046101c6565b613146565b60405191829182901515815260200190565b0390f35b806101b0565b905035906101c48261020e565b90602082820312610187576101da91610214565b346101875761024d610248366004610221565b6111b8565b604051005b73ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff81166101b0565b905035906101c48261026b565b909182601f830112156101875781359167ffffffffffffffff831161018757602001926001830284011161018757565b90608082820312610187576102d98183610287565b926102e78260208501610287565b926102f58360408301610214565b92606082013567ffffffffffffffff8111610187576103149201610294565b9091565b346101875761020a61033761032e3660046102c4565b93929092612825565b604051918291827fffffffff00000000000000000000000000000000000000000000000000000000909116815260200190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b90601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810190811067ffffffffffffffff8211176103d957604052565b61036a565b906101c46103eb60405190565b9283610399565b67ffffffffffffffff81116103d95760208091020190565b9092919261041f61041a826103f2565b6103de565b938185526020808601920283019281841161018757915b8383106104435750505050565b602080916104518486610214565b815201920191610436565b9080601f83011215610187578160206101da9335910161040a565b9092919261048761041a826103f2565b938185526020808601920283019281841161018757915b8383106104ab5750505050565b602080916104b98486610287565b81520192019161049e565b9080601f83011215610187578160206101da93359101610477565b91606083830312610187576104f48284610214565b92602081013567ffffffffffffffff8111610187578361051591830161045c565b92604082013567ffffffffffffffff8111610187576101da92016104c4565b346101875761024d6105473660046104df565b91611634565b9190604083820312610187576101da906105678185610214565b93602001610214565b346101875761024d61058336600461054d565b906113f6565b9091606082840312610187576101da6105a28484610214565b936105b08160208601610214565b93604001610214565b61024d6105c7366004610589565b91611baf565b346101875761024d6105e0366004610589565b91610d2f565b9091606082840312610187576101da6105ff8484610214565b936105b08160208601610287565b61024d61061b3660046105e6565b91611fa5565b346101875761024d610634366004610589565b916130df565b906080828203126101875761064f8183610214565b9261065d8260208501610214565b92604081013567ffffffffffffffff8111610187578361067e91830161045c565b92606082013567ffffffffffffffff8111610187576101da92016104c4565b346101875761024d6106b036600461063a565b92919091611dc6565b346101875761024d6106cc36600461054d565b906112a7565b600091031261018757565b34610187576106ed3660046106d2565b61024d613250565b346101875761024d610708366004610221565b611abf565b9091606082840312610187576101da6105a28484610287565b0190565b9061074a610743610739845190565b8084529260200190565b9260200190565b9060005b81811061075b5750505090565b90919261078e610787600192865173ffffffffffffffffffffffffffffffffffffffff16815260200190565b9460200190565b92910161074e565b906107a5610743610739845190565b9060005b8181106107b65750505090565b9091926107cc6107876001928651815260200190565b9291016107a9565b60408082526101da9391926107eb9184019061072a565b916020818403910152610796565b346101875761081261080c36600461070d565b916138e4565b9061020a61081f60405190565b928392836107d4565b90602082820312610187576101da91610287565b346101875761024d61084f366004610828565b61308d565b6101da6101da6101da9290565b9061086b90610854565b600052602052604060002090565b6101da9081565b6101da9054610879565b61089590600b610861565b61089e81610880565b916108ab60018301610880565b916108b860028201610880565b916101da60046108df600385015473ffffffffffffffffffffffffffffffffffffffff1690565b93015473ffffffffffffffffffffffffffffffffffffffff1690565b909594926101c49461092d61094a9261092660809661091f60a088019c6000890152565b6020870152565b6040850152565b73ffffffffffffffffffffffffffffffffffffffff166060830152565b019073ffffffffffffffffffffffffffffffffffffffff169052565b346101875761020a61098161097c366004610221565b61088a565b9161098e95939560405190565b958695866108fb565b346101875761024d6109aa366004610589565b91611d04565b34610187576109c03660046106d2565b61020a6109e260005473ffffffffffffffffffffffffffffffffffffffff1690565b6040519182918273ffffffffffffffffffffffffffffffffffffffff909116815260200190565b90610a18610743610739845190565b9060005b818110610a295750505090565b909192610a55610787600192865173ffffffffffffffffffffffffffffffffffffffff16815260200190565b929101610a1c565b805182526101da9160e0610aff6101008301610a7e60208601516020860152565b610a8d60408601516040860152565b60608581015173ffffffffffffffffffffffffffffffffffffffff169085015260808581015173ffffffffffffffffffffffffffffffffffffffff169085015260a08581015173ffffffffffffffffffffffffffffffffffffffff169085015260c085015184820360c0860152610796565b9201519060e0818403910152610a09565b60208082526101da92910190610a5d565b346101875761020a610b3c610b37366004610221565b61312f565b60405191829182610b10565b919060a08382031261018757610b5e8184610287565b92610b6c8260208301610214565b926101da610b7d8460408501610214565b93610b8b8160608601610287565b93608001610214565b61024d610ba2366004610b48565b939290926119e7565b346101875761024d610bbe366004610828565b613053565b67ffffffffffffffff81116103d957602090601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160190565b90826000939282370152565b90929192610c1a61041a82610bc3565b93818552602085019082840111610187576101c492610bfe565b9080601f83011215610187578160206101da93359101610c0a565b91909160a08184031261018757610c668382610287565b92610c748160208401610287565b92610c828260408501610214565b92610c908360608301610214565b92608082013567ffffffffffffffff8111610187576101da9201610c34565b346101875761020a610337610cc5366004610c4f565b9392909261227e565b346101875761024d610ce1366004610828565b6132b0565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff905b9181191691161790565b90610d246101da610d2b92610854565b8254610ce6565b9055565b610d496101c493610d42600193826113f6565b600a610861565b01610d14565b90610d6a610d5e610739845490565b92600052602060002090565b9060005b818110610d7b5750505090565b909192610d9f610d98600192610d9087610880565b815260200190565b9460010190565b929101610d6e565b906101da91610d4f565b906101c4610dcb92610dc260405190565b93848092610da7565b0383610399565b90610de1610d5e610739845490565b9060005b818110610df25750505090565b909192610e3a610d98600192610e1c875473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff16815260200190565b929101610de5565b906101da91610dd2565b906101c4610dcb92610e5d60405190565b93848092610e42565b6101da6101006103de565b906101c4610f9e6007610e82610e66565b94610e93610e8f82610880565b8752565b610ea9610ea260018301610880565b6020880152565b610ebf610eb860028301610880565b6040880152565b610f00610ee3600383015473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff166060880152565b610f41610f24600483015473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff166080880152565b610f82610f65600583015473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff1660a0880152565b610f98610f9160068301610db1565b60c0880152565b01610e4c565b60e0840152565b6101da90610e71565b1561018757565b91906008610d0a910291610fe87fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff841b90565b921b90565b9190610ffe6101da610d2b93610854565b908354610fb5565b6101c491600091610fed565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b919061104c565b9290565b9180830292818404149015171561105f57565b611012565b81811061106f575050565b8061107d6000600193611006565b01611064565b909182811061109157505050565b6101c4929061109f90610d5e565b9081019101611064565b906801000000000000000081116103d957816110c66101c4935490565b90828155611083565b60006101c4916110a9565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b9060000361111a576101c4906110cf565b6110da565b600060076101c49261113383808301611006565b6111408360018301611006565b61114d8360028301611006565b82600382015582600482015582600582015561116c8360068301611109565b01611109565b9060000361111a576101c49061111f565b6102526101da6101da9273ffffffffffffffffffffffffffffffffffffffff1690565b6101da90611183565b6101da906111a6565b611267600a6111cf6111ca8483610861565b610fa5565b611229600061122486339561121f61120161025260a089015173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff89165b14610fae565b610861565b611172565b606081015173ffffffffffffffffffffffffffffffffffffffff169161124e306111af565b611261604061125b855190565b94015190565b936129ee565b6112a27f91635156bba825be2f9a200b140420e4bee0d251f7d43bf956e63f74960030e09161129560405190565b9182918290815260200190565b0390a1565b906113019060016112bc6101da85600a610861565b610d49336112196112e7610252600586015473ffffffffffffffffffffffffffffffffffffffff1690565b9173ffffffffffffffffffffffffffffffffffffffff1690565b6112a27fb8b459bc0688c37baf5f735d17f1711684bc14ab7db116f88bc18bf409b9309a9161129560405190565b9190820391821161105f57565b61134f6113496101da9290565b60e01b90565b7fffffffff000000000000000000000000000000000000000000000000000000001690565b73ffffffffffffffffffffffffffffffffffffffff9182168152911660208201526101da9260a08201926113b391906113ac90610926565b6060830152565b60808183039101526000815260200190565b906113d261041a83610bc3565b918252565b3d156113f1576113e63d6113c5565b903d6000602084013e565b606090565b8091611503600a916114fe6000806114116101da8888610861565b60046114e6339261145b61143f610252600584015473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff8616611219565b6114d7600282019861147e611478826114738d610880565b61132f565b8b610d14565b600383015473ffffffffffffffffffffffffffffffffffffffff169563f242432a906114bd6114b78a6114b0306111af565b9701610880565b9261133c565b946114c760405190565b9788966020880190815201611374565b60208201810382520382610399565b82602082019151925af16114f86113d7565b50610fae565b610880565b9060009161151761151384610854565b9190565b03611529576112679261122491610861565b5050506112a27fb8b459bc0688c37baf5f735d17f1711684bc14ab7db116f88bc18bf409b9309a9161129560405190565b81519167ffffffffffffffff83116103d957611585610d5e60019261157f86866110a9565b60200190565b92049160005b8381106115985750505050565b60019060206115a86101da865190565b940193818401550161158b565b906101c49161155a565b81519167ffffffffffffffff83116103d9576115e4610d5e60019261157f86866110a9565b92049160005b8381106115f75750505050565b600190602061161d6101da865173ffffffffffffffffffffffffffffffffffffffff1690565b94019381840155016115ea565b906101c4916115bf565b91600761168c846113019461121f339161167a600a936112196112e7610252600561165f8a8a610861565b015473ffffffffffffffffffffffffffffffffffffffff1690565b60066116868585610861565b016115b5565b0161162a565b8015156101b0565b905051906101c482611692565b90602082820312610187576101da9161169a565b939291906000806116e160045473ffffffffffffffffffffffffffffffffffffffff1690565b600461172b6116f363f6a3d24e61133c565b6114d78b61170060405190565b948593602085019081520173ffffffffffffffffffffffffffffffffffffffff909116815260200190565b602081019051915afa506117556117406113d7565b602061174a825190565b8183010191016116a7565b600090611763825b91151590565b14611773575b506101c4946118e5565b808061179460055473ffffffffffffffffffffffffffffffffffffffff1690565b60046117b36117a6633af32abf61133c565b6114d78c61170060405190565b602081019051915afa506117d461175d6117ce6117406113d7565b92151590565b146117df5738611769565b6040517f82b42900000000000000000000000000000000000000000000000000000000008152600490fd5b0390fd5b6101da60a06103de565b9073ffffffffffffffffffffffffffffffffffffffff90610d0a565b906118446101da610d2b926111af565b8254611818565b6101c491906118b89060809060049061186c81611866875190565b90610d14565b61187e60018201611866602088015190565b61189060028201611866604088015190565b6118be600382016118b8606088015173ffffffffffffffffffffffffffffffffffffffff1690565b90611834565b0192015173ffffffffffffffffffffffffffffffffffffffff1690565b906101c49161184b565b6119a094936119656119829261194861199b96611941611903600290565b9961190d8b613356565b61193a60009961191c8b610854565b85036119ce5761192c8334611219565b61193461180e565b9a8b0152565b6020890152565b6040870152565b73ffffffffffffffffffffffffffffffffffffffff166060850152565b73ffffffffffffffffffffffffffffffffffffffff166080830152565b611996600b61199085613341565b90610861565b6118db565b613341565b6112a27f682fd9923da5632e7c7702dabcfa626195d5f444833bc25f94e418e258e791869161129560405190565b6119e2346112196115136101da8988611041565b61192c565b906101c4949392916116bb565b906101c4611a546004611a0561180e565b94611a12610e8f82610880565b611a21610ea260018301610880565b611a30610eb860028301610880565b61165f610ee3600383015473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff166080840152565b6101da906119f4565b600090600490611a8a8382611006565b611a978360018301611006565b611aa48360028301611006565b8260038201550155565b9060000361111a576101c490611a7a565b611b5b600b611b56611ad9611ad48584610861565b611a71565b611b3c60408201611ae8815190565b9087600092611af961151385610854565b03611b89576020850151611b379250965b61121f6080339701966112196112e76102528a5173ffffffffffffffffffffffffffffffffffffffff1690565b611aae565b5173ffffffffffffffffffffffffffffffffffffffff1690565b612fe8565b6112a27f88686b85d6f2c3ab9a04e4f15a22fcfa025ffd97226dcf0a67cdf682def556769161129560405190565b611ba9611b3792611ba3611b9e602089015190565b915190565b90611041565b96611b0a565b9190611c3791611c32611bc66101da86600b610861565b91611bf2336112196112e7610252600488015473ffffffffffffffffffffffffffffffffffffffff1690565b60016002840193611c0285610880565b611c0f6115136000610854565b03611c6557610d49346112196115136101da611c2c878701610880565b8861132f565b610d14565b6112a27f7eff4a127d11f41398f54211d23467816d498336249c9103adb206d679f0e4829161129560405190565b611c99346112196115136101da611c7c8b89611041565b611c93611c8a898901610880565b611ba38d610880565b9061132f565b610d49565b15611ca557565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600360248201527f68777900000000000000000000000000000000000000000000000000000000006044820152606490fd5b91611c3791611d176101da85600b610861565b913391611d5e611d41610252600487015473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff851614611c9e565b611d686000610854565b8103611d8b5750806001611d84611b5693611473838801610880565b9401610d14565b9290600182611c99611da183611b569601610880565b96611dc0611db66002850199611ba38b610880565b611c938388611041565b97610d14565b91929092600a611dd96111ca8583610861565b90600b611de9611ad48883610861565b96611e0b60a085015173ffffffffffffffffffffffffffffffffffffffff1690565b3393908890611e19866112e7565b1480611f8a575b80611f36575b611e2f90610fae565b611e3891610861565b6000611e4391611172565b611e4c91610861565b6000611e5791611aae565b815195611e65602082015190565b60609093015160009073ffffffffffffffffffffffffffffffffffffffff1660809092015173ffffffffffffffffffffffffffffffffffffffff1693611ea9610e66565b98611eb490838b0152565b6020890152611ec290610854565b604088015273ffffffffffffffffffffffffffffffffffffffff16606087015273ffffffffffffffffffffffffffffffffffffffff16608086015273ffffffffffffffffffffffffffffffffffffffff1660a085015260c084015260e0830152611f2c6007610880565b906101c492612b64565b50611e2f8a611f826112e76102526060611f66818d015173ffffffffffffffffffffffffffffffffffffffff1690565b94015173ffffffffffffffffffffffffffffffffffffffff1690565b149050611e26565b5089611f9f6115136101da600061125b8b5190565b14611e20565b90916101da92600a90611fbb6111ca8584610861565b91843491611fca602086015190565b90611ff5611fef60009a8b94611fdf86610854565b8b0361211f57611ba36001610854565b85611219565b61200e8761147360026120088786610861565b01610880565b61201a61151384610854565b036120fb5761202c9261122491610861565b8583015193612052606085015173ffffffffffffffffffffffffffffffffffffffff1690565b90612074608086015173ffffffffffffffffffffffffffffffffffffffff1690565b9260c08601519560e0015196612088610e66565b998a01526020890152604088015273ffffffffffffffffffffffffffffffffffffffff16606087015273ffffffffffffffffffffffffffffffffffffffff16608086015273ffffffffffffffffffffffffffffffffffffffff1660a085015260c084015260e0830152611f2c6006610880565b6002915061211a9261210c91610861565b016118668661147383610880565b61202c565b8a90611041565b905051906101c48261020e565b9092919261214361041a826103f2565b938185526020808601920283019281841161018757915b8383106121675750505050565b602080916121758486612126565b81520192019161215a565b9080601f830112156101875781516101da92602001612133565b905051906101c48261026b565b909291926121b761041a826103f2565b938185526020808601920283019281841161018757915b8383106121db5750505050565b602080916121e9848661219a565b8152019201916121ce565b9080601f830112156101875781516101da926020016121a7565b90608082820312610187576122238183612126565b926122318260208501612126565b92604081015167ffffffffffffffff81116101875783612252918301612180565b92606082015167ffffffffffffffff8111610187576101da92016121f4565b9190820180921161105f57565b9390929061229b906020612290825190565b81830101910161220e565b949295916000949194976122ae89610854565b8203612436575050600b906122c6611ad48884610861565b87896122e9606084015173ffffffffffffffffffffffffffffffffffffffff1690565b3395906122f5876112e7565b148061241f575b61230590610fae565b6123188561147360026120088786610861565b61232461151384610854565b036123fb5761233692611b3791610861565b81612342602083015190565b9061234c91611041565b60809091015173ffffffffffffffffffffffffffffffffffffffff1694612371610e66565b998a01526020890152604088015273ffffffffffffffffffffffffffffffffffffffff16606087015273ffffffffffffffffffffffffffffffffffffffff16608086015273ffffffffffffffffffffffffffffffffffffffff1660a085015260c084015260e08301526123e46007610880565b906123ee92612b64565b6101da63f23a6e6161133c565b6002915061241a9261240c91610861565b016118668461147383610880565b612336565b5061230561242d8386015190565b891490506122fc565b90949596976124488195949395610854565b890361245e57506124599750612816565b6123ee565b6124ee975060019596506124d891949250610d49936124816101da8b600a610861565b956124c36112e76124a960058a015473ffffffffffffffffffffffffffffffffffffffff1690565b9273ffffffffffffffffffffffffffffffffffffffff1690565b149182612540575b82612524575b5050610fae565b61186660028401916124e983610880565b612271565b61251c7fb8b459bc0688c37baf5f735d17f1711684bc14ab7db116f88bc18bf409b9309a9161129560405190565b0390a16123ee565b612538919250611048611513918801610880565b1438806124d1565b9150612563600387015473ffffffffffffffffffffffffffffffffffffffff1690565b61256c336112e7565b14916124cb565b9594939291903360008061259c60045473ffffffffffffffffffffffffffffffffffffffff1690565b60046125bb6125ae63f6a3d24e61133c565b6114d78761170060405190565b602081019051915afa506125d06117406113d7565b600091906125dd8361175d565b146125ee575b50506101c49661273d565b818091600461263061261560055473ffffffffffffffffffffffffffffffffffffffff1690565b926114d7612626633af32abf61133c565b9161170060405190565b602081019051915afa5061264b61175d6117ce6117406113d7565b146117df5738806125e3565b6101c4919061272d9060e09060079061267281611866875190565b61268460018201611866602088015190565b61269660028201611866604088015190565b6126be600382016118b8606088015173ffffffffffffffffffffffffffffffffffffffff1690565b6126e6600482016118b8608088015173ffffffffffffffffffffffffffffffffffffffff1690565b61270e600582016118b860a088015173ffffffffffffffffffffffffffffffffffffffff1690565b6127266006820161272060c088015190565b906115b5565b0192015190565b9061162a565b906101c491612657565b9194929593600161274d81613356565b61275690613341565b963391612761610e66565b9788526020880152604087015273ffffffffffffffffffffffffffffffffffffffff16606086015273ffffffffffffffffffffffffffffffffffffffff16608085015273ffffffffffffffffffffffffffffffffffffffff1660a084015260c083015260e08201526127d482600a610861565b906127de91612733565b7f7e82078c35b6665b9d320ebeaa6c266960fad5b802c5558cf7df60c4769af95b9061280960405190565b90815280602081016112a2565b906101c4969594939291612573565b9261283d919294612834600090565b5081019061063a565b94919260009491949061284f82610854565b810361287757509061286461286a9792610854565b92612816565b6101da63150b7a0261133c565b959493509150600b61288c611ad48783610861565b92806128af606086015173ffffffffffffffffffffffffffffffffffffffff1690565b33939089906128bd866112e7565b148061299c575b6128cd90610fae565b6128d691610861565b906128e091611aae565b602084015160809094015173ffffffffffffffffffffffffffffffffffffffff169361290a610e66565b9861291590838b0152565b602089015261292390610854565b604088015273ffffffffffffffffffffffffffffffffffffffff16606087015273ffffffffffffffffffffffffffffffffffffffff16608086015273ffffffffffffffffffffffffffffffffffffffff1660a085015260c084015260e083015261298d6007610880565b9061299792612b64565b61286a565b506128cd8b6129b1611513611048878c015190565b1490506128c4565b73ffffffffffffffffffffffffffffffffffffffff9182168152911660208201526060810192916101c49160400152565b0152565b906101c4949293600491612a00600090565b506000958695612a0f87610854565b8103612a4557506114e6926114d791612a2b6342842e0e61133c565b93612a3560405190565b96879560208701908152016129b9565b6114e6936114d7926114bd63f242432a61133c565b73ffffffffffffffffffffffffffffffffffffffff90911681526040810192916101c49160200152565b6101da612710610854565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b8115612ac8570490565b612a8f565b801561105f577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b90612b30825190565b811015612b41576020809102010190565b612af8565b9081526060810193926101c49290916040916129ea905b6020830152565b90916020820191612b73835190565b908192612b7e606090565b8060019182936060860194612ba7865173ffffffffffffffffffffffffffffffffffffffff1690565b3f612bb86115136101da6009610880565b14612f4e575b612f19575b8251968791600098612bd76115138b610854565b1115612ecb575050612be96008610880565b612bf561151389610854565b11612e81575b83805b612df5575b505050505b60e0830190612c1682515190565b9081612c2461151388610854565b11612d40575b505050611b9e612cc8612ccd612cc8612ce894612c636080880199611b568b5173ffffffffffffffffffffffffffffffffffffffff1690565b611b3c612c84825173ffffffffffffffffffffffffffffffffffffffff1690565b612c8d306111af565b98612caf60a082015173ffffffffffffffffffffffffffffffffffffffff1690565b9a81019a6040612cbd8d5190565b92019a6112618c5190565b955190565b955173ffffffffffffffffffffffffffffffffffffffff1690565b94612d3b612d28612d22612d1c7f09c7d1ab159dbdc16054d51213cf8ab76b507ff896aca64379b18e54ce7959ef966111af565b96610854565b966111af565b96612d3260405190565b93849384612b46565b0390a4565b9081805b612d4f575b50612c2a565b15612de0575b612d5f8691612acd565b91828960c08801612d78612d74848351612b27565b5190565b612d846115138c610854565b11612d95575b505050919082612d44565b611b3c612dd0612dc9612dbb869e95611ba3611b9e612d74612dd89a611b569951612b27565b612dc3612a84565b90612abe565b809461132f565b9b8851612b27565b388981612d8a565b612de986610854565b8111612d555780612d49565b15612e6c575b612e058791612acd565b9081612e14612d748286612b27565b612e206115138b610854565b11612e2d575b5084612bfe565b98612e67612e438792611c93612d748e89612b27565b9a611b56612e61612d74612e5a611b3c858c612b27565b9389612b27565b916111af565b612e26565b612e7587610854565b8111612dfb5780612c03565b612ec6612ea6612e9f612dbb612e958d5190565b611ba36008610880565b809a61132f565b98611b5660035473ffffffffffffffffffffffffffffffffffffffff1690565b612bfb565b612dbb919950612f149450612ef49350612eed9250612ee88b5190565b611041565b809761132f565b95611b5660035473ffffffffffffffffffffffffffffffffffffffff1690565b612c08565b915050612f47612f3d845173ffffffffffffffffffffffffffffffffffffffff1690565b8551895191613463565b9091612bc3565b506000807f000000000000000000000000167a296135c0067903d4c099806405f6c316442e6004612fcd8a6114d78b612fb3612fad88612fa7637abab711945173ffffffffffffffffffffffffffffffffffffffff1690565b95015190565b9161133c565b92612fbd60405190565b9586946020860190815201612a5a565b82602082019151925af150612fe36117406113d7565b612bbe565b60006101c492613000612ffb83946111af565b6111af565b9061300a60405190565b90818003925af16114f86113d7565b6101c49061304861303f60005473ffffffffffffffffffffffffffffffffffffffff1690565b611219336112e7565b6101c4906003611834565b6101c490613019565b6101c49061308261303f60005473ffffffffffffffffffffffffffffffffffffffff1690565b6101c4906005611834565b6101c49061305c565b906101c492916130be61303f60005473ffffffffffffffffffffffffffffffffffffffff1690565b6101c492916130d16130d8926006610d14565b6007610d14565b6008610d14565b906101c49291613096565b6130f2610e66565b906000825260208080808080808089016000815201600081520160008152016000815201600081520160608152016060905250565b6101da6130ea565b6111ca6101da9161313e613127565b50600a610861565b6131536301ffc9a761133c565b7fffffffff000000000000000000000000000000000000000000000000000000008216149081156131c5575b8115613189575090565b90506131c161319b63150b7a0261133c565b917fffffffff000000000000000000000000000000000000000000000000000000001690565b1490565b90506131d463f23a6e6161133c565b7fffffffff000000000000000000000000000000000000000000000000000000008216149061317f565b61322061303f60005473ffffffffffffffffffffffffffffffffffffffff1690565b6101c461323e565b6102526101da6101da9290565b6101da90613228565b6101c461324b6000613235565b6132e5565b6101c46131fe565b6101c49061327e61303f60005473ffffffffffffffffffffffffffffffffffffffff1690565b6101c49061324b6132926102526000613235565b73ffffffffffffffffffffffffffffffffffffffff83161415610fae565b6101c490613258565b73ffffffffffffffffffffffffffffffffffffffff90911681526040810192916101c49160209061094a565b60005473ffffffffffffffffffffffffffffffffffffffff169061330a816000611834565b7f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0916112a261333860405190565b928392836132b9565b60006101da9161334f600090565b5001610880565b6101c49061186660006133696001610854565b92019161072683610880565b9092919261338561041a826103f2565b938185526020808601920283019281841161018757915b8383106133a95750505050565b602080916133b7848661219a565b81520192019161339c565b9080601f830112156101875781516101da92602001613375565b91909160408184031261018757805167ffffffffffffffff811161018757836134069183016133c2565b92602082015167ffffffffffffffff8111610187576101da9201612180565b73ffffffffffffffffffffffffffffffffffffffff90911681526060810193926101c49290916040916129ea90612b5d565b6040513d6000823e3d90fd5b6000919392613470606090565b506134a98361347e306111af565b637fea43b86134b4613492620f4240610854565b9561349c60405190565b9a8b988997889560e01b90565b855260048501613425565b0393f191826000918294613527575b50611513576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f526f79616c7479206c6f6f6b7570206661696c656400000000000000000000006044820152606490fd5b909350613545913d8091833e61353d8183610399565b8101906133dc565b92386134c3565b90602082820312610187576101da9161219a565b9081526040810192916101c49160200152565b9190604083820312610187576101da9061358d818561219a565b93602001612126565b6135a36101da6101da9290565b63ffffffff1690565b61ffff81166101b0565b905051906101c4826135ac565b919091604081840312610187576135f86135dd60406103de565b9360006135ea828561219a565b9086015260208093016135b6565b90830152565b9092919261360e61041a826103f2565b93818552604060208601920283019281841161018757915b8383106136335750505050565b602060409161364284866135c3565b815201920191613626565b9080601f830112156101875781516101da926020016135fe565b9060208282031261018757815167ffffffffffffffff8111610187576101da920161364d565b906113d261041a836103f2565b369037565b906101c46136b56136af8461368d565b936103f2565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0016020840161369a565b6101da6101da6101da9261ffff1690565b156136f857565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600d60248201527f496e76616c69642073706c6974000000000000000000000000000000000000006044820152606490fd5b90602082820312610187576101da91612126565b6bffffffffffffffffffffffff81166101b0565b905051906101c48261376b565b919091604081840312610187576135f86137a660406103de565b9360006137b3828561219a565b90860152602080930161377f565b909291926137d161041a826103f2565b93818552604060208601920283019281841161018757915b8383106137f65750505050565b6020604091613805848661378c565b8152019201916137e9565b9080601f830112156101875781516101da926020016137c1565b9060208282031261018757815167ffffffffffffffff8111610187576101da9201613810565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461105f5760010190565b6101da6101da6101da926bffffffffffffffffffffffff1690565b9060208282031261018757815167ffffffffffffffff8111610187576101da92016133c2565b9060208282031261018757815167ffffffffffffffff8111610187576101da9201612180565b6060939284929190613935602061391d612ffb7f000000000000000000000000ad2184fb5dbcfc05d8f056542fb25b04fa32a95d6111af565b63de5488af9061392c60405190565b93849260e01b90565b825273ffffffffffffffffffffffffffffffffffffffff8616600483015260249082905afa8015613da25761397291600091614406575b506111af565b9061397c826111af565b9163d6a1919a61399561398e60405190565b9160e01b90565b8152600081806139a9868a60048401613560565b038183885af18060009283926143e8575b506140db575050632a55205a6139d261398e60405190565b8152604081806139e6868a60048401613560565b0381875afa8060009283926143b5575b5061412457505063bb3bafd6613a0e61398e60405190565b815260048101869052600081602481875afa806000928392614106575b506140db575050613a506102527341a322b28d0ff354040e2cbc676f0320d8c8850d90565b73ffffffffffffffffffffffffffffffffffffffff85161480156140a8575b613f51575b63cad96cca613a8561398e60405190565b815260048101869052600081602481875afa60009181613f2e575b50613dc2575063b9c4d9fb613ab460405190565b613abe8260e01b90565b815260048101879052600081602481885afa60009181613da7575b50613ce9575b505063d5a06d4c613af261398e60405190565b815260048101869052600081602481875afa806000928392613ccb575b50613c5557505063f6622074613b2761398e60405190565b815260008180613b3b898960048401612a5a565b0381875afa806000928392613cad575b50613c55575050639ca7dc7a613b6361398e60405190565b815260008180613b77898960048401612a5a565b0381875afa806000928392613c8f575b50613c5557505060009392916134a99150613bb763fbda03ab91613baa60405190565b9788968795869560e01b90565b03915afa90816000918293613c38575b50613bd25750509190565b80519194509150613be76115136101da865190565b03613bf0579190565b613bfc611b9e84925190565b9061180a613c0960405190565b9283927fab8b67c600000000000000000000000000000000000000000000000000000000845260048401613560565b909250613c4e913d8091833e61353d8183610399565b9138613bc7565b965097505092505050613c66845190565b613c746115136101da855190565b03613c8357906101da91614424565b50613bfc611b9e845190565b9092613ca592503d8091833e61353d8183610399565b909138613b87565b9092613cc392503d8091833e61353d8183610399565b909138613b4b565b9092613ce192503d8091833e61353d8183610399565b909138613b0f565b50613cf661398e60405190565b815260048101869052600081602481875afa908115613da257600091613d81575b50630ebd4c7f613d2961398e60405190565b815260048101879052600081602481885afa60009181613d5e575b5015600003613adf57965097505092505050613c66845190565b613d7a91923d8091833e613d728183610399565b8101906138be565b9038613d44565b613d9c913d8091833e613d948183610399565b810190613898565b38613d17565b613457565b613dbb91923d8091833e613d948183610399565b9038613ad9565b9750509350505050613dda613dd5845190565b61369f565b90613de6613dd5855190565b936000926000613df581610854565b945b613e026101da855190565b861015613ed657613eca613ed091613eb7613eaa613e9f613e99613e946020613e808e613e7a8d8f838f613e5990613e3d83613e5f95612b27565b51015173ffffffffffffffffffffffffffffffffffffffff1690565b92612b27565b9073ffffffffffffffffffffffffffffffffffffffff169052565b8d612b27565b5101516bffffffffffffffffffffffff1690565b61387d565b8a611041565b612dc3612710610854565b613eb48a8d612b27565b52565b613ec4612d74898c612b27565b90612271565b95613850565b94613df7565b9391945050613ee3915090565b8111613eee57509190565b61180a90613efb60405190565b9182917f3728b83d0000000000000000000000000000000000000000000000000000000083526004830190815260200190565b613f4a91923d8091833e613f428183610399565b81019061382a565b9038613aa0565b613f6f612ffb7317b0c8564e53f22364a6c8de6f7ca5ce9bea4e5d81565b63b85ed7e490613f88613f8160405190565b9260e01b90565b825260208280613f9c8a8a60048401612a5a565b0381845afa60009281614078575b50613fb7575b5050613a74565b6020613fcc9163860110f59061392c60405190565b82528180613fdf898d8d60048501613425565b03915afa60009181614048575b5015600003613fb05796509750505050505061151361400b6001610854565b91613eb461404261402461401e8661369f565b9561369f565b9661403d600091613e5f61403784610854565b89612b27565b610854565b86612b27565b61406a91925060203d8111614071575b6140628183610399565b810190613757565b9038613fec565b503d614058565b61409a91935060203d81116140a1575b6140928183610399565b81019061354c565b9138613faa565b503d614088565b5073b932a70a57673d89f4acffbe830e8ed7f75fb9e073ffffffffffffffffffffffffffffffffffffffff851614613a6f565b95509750509150506101da92506141016140f3865190565b6112196115136101da865190565b614424565b909261411c92503d8091833e61353d8183610399565b909138613a2b565b959396509750925083116143a85760009384918291883b61415161414785613596565b9163ffffffff1690565b116142c5575b614188945061417a63fd90e89761416d60405190565b9687948593849360e01b90565b835260048301526024820190565b03915afa8492816142a1575b506141da57505061151390613eb46140426141af6001610854565b9461403d6141ce6141c86141c28961369f565b9861369f565b996111af565b613e5f61403784610854565b92909394506141e7845190565b936141f18561369f565b906141fb8661369f565b96855b88885b8810156142855761427688614201936124e961427d94613eb461426e613e9f8e612ee88f8f8f91614260602092613e5f8e613e596142428261426999612b27565b519586015173ffffffffffffffffffffffffffffffffffffffff1690565b015161ffff1690565b6136e0565b948592612b27565b9760010190565b9690506141fe565b509350955050925061429a6115136115139390565b11156136f1565b6142be9193503d8087833e6142b68183610399565b810190613667565b9138614194565b916142e9906142da612ffb8b979496976111af565b63d78d610b9061392c60405190565b825260049082905afa87918161438c575b5015871461430c575092918591614157565b9597505050505061431e613dd5835190565b9361432a613dd5845190565b9461433482610854565b9361433d815190565b95855b88885b8810156142855761427688614343936124e961438494613eb461426e613e9f8e612ee88f8f8f91614260602092613e5f8e613e596142428261426999612b27565b969050614340565b6143a19192503d808a833e6142b68183610399565b90386142fa565b61180a83613efb60405190565b9092506143d9915060403d81116143e1575b6143d18183610399565b810190613573565b9091386139f6565b503d6143c7565b90926143fe92503d8091833e61353d8183610399565b9091386139ba565b61441e915060203d81116140a1576140928183610399565b3861396c565b9190614431613dd5825190565b9260009161443f6000610854565b925b61444c6101da835190565b8410156144995761448d61449391614480614476613e9f614470612d748a89612b27565b88611041565b613eb4888b612b27565b613ec4612d74878a612b27565b93613850565b92614441565b93949392506144a59050565b8111613eee57509056
Verified Source Code Partial Match
Compiler: v0.8.20+commit.a1b79de6
EVM: paris
Ownable.sol 73 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/**
* @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 {
address private _owner;
event OwnershipTransferred(address previousOwner, address newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(msg.sender);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == msg.sender);
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() external 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) external onlyOwner {
require(newOwner != address(0));
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) private {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
Counters.sol 53 lines
/*----------------------------------------------------------*| |* ███ ██ ██ ███ ██ ███████ █████ *| |* ████ ██ ██ ████ ██ ██ ██ ██ *| |* ██ ██ ██ ██ ██ ██ ██ █████ ███████ *| |* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ *| |* ██ ████ ██ ██ ████ ██ ██ ██ *| |*----------------------------------------------------------*/ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; /** * * @title Counters * * * * @dev Stripped down version of OpenZeppelin Contracts * * v4.4.1 (utils/Counters.sol), identical to * * CountersUpgradeable.sol being a library. Provides * * counters that can only be incremented. * * Used to track the total supply of ERC721 ids. * * @dev Include with `using Counters for Counters.Counter;` * * * * @custom:security-contact [email protected] * * */ /** * @title Counters * @dev Stripped down version of OpenZeppelin Contracts v4.4.1 * (utils/Counters.sol), identical to * CountersUpgradeable.sol being a library. Provides counters that can only be * incremented. Used to track the total * supply of ERC721 ids. * @dev Include with `using Counters for Counters.Counter;` */ library Counters { struct Counter { uint256 _value; // default: 0 } function current(Counter storage counter) internal view returns (uint256) { return counter._value; } /// @dev if implementing ERC721A there could be an overflow risk by removing /// overflow protection with `unchecked`, /// unless we limit the amount of tokens that can be minted, or require that /// totalsupply be less than 2^256 - 1 function increment(Counter storage counter) internal { unchecked { counter._value += 1; } } }
NinfaMarketplace.sol 1010 lines
/*----------------------------------------------------------*| |* ███ ██ ██ ███ ██ ███████ █████ *| |* ████ ██ ██ ████ ██ ██ ██ ██ *| |* ██ ██ ██ ██ ██ ██ ██ █████ ███████ *| |* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ *| |* ██ ████ ██ ██ ████ ██ ██ ██ *| |*----------------------------------------------------------*/ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; import "./utils/Counters.sol"; import "./utils/RoyaltyEngineV1.sol"; import "./access/Ownable.sol"; /** * * @title NinfaMarketplace * * * * @notice On-chain NFT marketplace * * * * @custom:security-contact [email protected] * * */ contract NinfaMarketplace is Ownable, RoyaltyEngineV1 { /*----------------------------------------------------------*| |* # VARIABLES *| |*----------------------------------------------------------*/ using Counters for Counters.Counter; /// @notice Orders counter Counters.Counter private _orderCount; /// @notice Offers counter Counters.Counter private _offerCount; /// @notice address of admin multisig contract for receiving fees generated /// by the marketplace address private _feeRecipient; /// @notice factory contract for deploying self-sovereign collections. address private _factory; /// @notice Ninfa's whitelist contract, used for checking if a collection is whitelisted address private _whitelist; /// @notice Ninfa's external registry, mapping Ninfa's ERC721 sovreign /// collection's tokenIds to a boolean value /// indicating if the token has been sold on any of Ninfa's contracts, such /// as this marketplace or an auction /// contract. address private immutable _PRIMARY_MARKET_REGISTRY; ///@notice constant 10,000 BPS = 100% shares sale price uint256 private constant _BPS_DENOMINATOR = 10_000; /// @notice Ninfa Marketplace fee percentage on primary sales from orders, /// expressed in basis points uint256 private _primaryOrdersFee; /// @notice Ninfa Marketplace fee percentage on primary sales from offers, /// expressed in basis points uint256 private _primaryOffersFee; /// @notice Ninfa Marketplace fee percentage on all secondary sales, /// expressed in basis points uint256 private _secondaryMarketFee; /// @notice codehash of Ninfa's ERC721 sovreign collection bytes32 private _ERC721SovreignV1CodeHash; /// @notice `_orderCount` counter to `_Order` struct mapping mapping(uint256 => _Order) private _orders; /// @notice `_offerCount` counter to `_Offer` struct mapping mapping(uint256 => _Offer) public offers; /*----------------------------------------------------------*| |* # STRUCTS *| |*----------------------------------------------------------*/ /** * @dev the `Order` struct is used both for storing order information, as * well as trade information when passed as a * function parameter to the private `_trade` function * @param tokenId the NFT id, for now we only allow trading NINFA NFT's so * no erc721 address is needed * @param unitPrice ERC-1155 unit price in ETH, or total price if ERC-721 * since there is only 1 unit of each token. * @dev when the `Order` struct is passed as a function parameter to * `_trade`, `unitPrice` always refers to the * total price of ERC-1155 tokens, i.e. token value * unit price * @param collection address of the ERC721 or ERC1155 contract. Doesn't * require any access control besides the * collection being whitelisted, i.e. msg.sender may be any address. * @param erc1155Value the NFT amount, _amount == 0 for ERC721 and _amount > * 0 for ERC1155 * @param commissionBps commission amounts, expressed in basis points 0 - * 10000 * @param commissionReceivers receivers of commission on sales (primary AND * secondary) * @param collection address of the collection being sold * @param from always refers to the seller, who either creater an order or * is accepting an offer. * @param operator address of an authorized operator, such as a gallery * managing an artist; the operator is allowed * to change or cancel an order they created on the Marketplace. * @dev operator address usually also corresponds to the commission receiver * in order to receive sale commissions, * unless `commissionReceiver` is set to yet another address, e.g. a payment * splitter. */ struct _Order { uint256 tokenId; uint256 unitPrice; uint256 erc1155Value; address collection; address from; address operator; uint256[] commissionBps; address[] commissionReceivers; } struct _Offer { uint256 tokenId; uint256 unitPrice; uint256 erc1155Value; address collection; address from; // buyer } /*----------------------------------------------------------*| |* # EVENTS *| |*----------------------------------------------------------*/ event OrderCreated(uint256 orderId); event OrderUpdated(uint256 orderId); event OrderDeleted(uint256 orderId); event OfferCreated(uint256 offerId); event OfferUpdated(uint256 offerId); event OfferDeleted(uint256 offerId); // we have order/offer id and all the related data stored in db. event Trade( // seller address indexed collection, uint256 indexed tokenId, address indexed from, uint256 id, uint256 price, uint256 erc1155Value ); /*----------------------------------------------------------*| |* # MODIFIERS *| |*----------------------------------------------------------*/ /** * @notice ERC721 and ERC1155 collections must be whitelisted. * @dev if the collection has not been whitelisted, check if it is one of * Ninfa's factory clones (Ninfa's * self-sovereign collections) * @dev by checking the whitelist first with nested `if`s avoids having to * make an external call unnecessarily */ modifier isWhitelisted(address _collection) { bytes memory authorized; (, authorized) = _factory.staticcall( abi.encodeWithSelector(0xf6a3d24e, _collection) ); if (abi.decode(authorized, (bool)) == false) { (, authorized) = _whitelist.staticcall( abi.encodeWithSelector(0x3af32abf, _collection) ); if (abi.decode(authorized, (bool)) == false) revert Unauthorized(); } _; } /*----------------------------------------------------------*| |* # ORDERS *| |*----------------------------------------------------------*/ /** * @notice create a new order on the marketplace by transfering an NFT to * it. * @dev Will create a new order by sending tokens with data bytes containing * function parameters * * Require: * * - can only be called by an NFT smart contract transfering an NFT to the * marketplace * - collection must be whitelisted * */ function _createOrder( address _operator, // either the previous owner or operator, i.e. // whichever address called safeTransferFrom on // the ERC1155 contract address _from, // previous owner, i.e. seller uint256 _id, uint256 _value, uint256 _unitPrice, uint256[] memory _commissionBps, address[] memory _commissionReceivers ) private isWhitelisted(msg.sender) { // `_orderCount` starts at 1 _orderCount.increment(); uint256 _orderId = _orderCount.current(); // create order with new `_orderId` in orders mapping _orders[_orderId] = _Order( _id, _unitPrice, _value, msg.sender, // collection _from, _operator, _commissionBps, _commissionReceivers ); emit OrderCreated(_orderId); } /// @dev only for 1155 for updating the price and LOWER the amount function updateOrder(uint256 _erc1155RedeemAmount, uint256 _unitPrice, uint256 _orderId) external { lowerOrderErc1155Amount(_orderId, _erc1155RedeemAmount); _orders[_orderId].unitPrice = _unitPrice; } /** * @notice cancels order and transfers NFT back to owner * @param _orderId the Id of the order * @dev delete `_orders[_orderId]` from storage BEFORE making external calls * for transfering the NFT back to the * seller (check-effects pattern) * * * SHOULD: * * This function does not check whether the order exists or not * */ function deleteOrder(uint256 _orderId) external { _Order memory order = _orders[_orderId]; require(msg.sender == order.operator); delete _orders[_orderId]; _transferNFT(order.collection, address(this), msg.sender, order.tokenId, order.erc1155Value); emit OrderDeleted(_orderId); } /** * @param _unitPrice will override the old price, note that it may be 0 * although this could only mean that a mistake * was made * @dev this function doesn't enforce a positive value for `_unitPrice` in * order to save a little gas for the users. * @dev if `_unitPrice` is set to 0, the order will be deleted from the * database (not from the smart contract), see * {NinfaMarketplace-OrderUpdated} modifier */ function updateOrderPrice(uint256 _orderId, uint256 _unitPrice) external { _Order storage order = _orders[_orderId]; require(msg.sender == order.operator); order.unitPrice = _unitPrice; emit OrderUpdated(_orderId); } /** * @notice function to lower an order's amount of the ERC-1155 tokenId on * sale, doesn't apply to ERC-721 because it * is non-fungible * @param _erc1155RedeemAmount is the (negative) difference of tokens to be * withdrawn by the seller. Should be * different from 0, although not strictly required. */ function lowerOrderErc1155Amount(uint256 _orderId, uint256 _erc1155RedeemAmount) public { _Order storage order = _orders[_orderId]; require(msg.sender == order.operator); /// @dev warning, make changes to storage BEFORE making external calls /// for transfering the NFT back to the /// seller (check-effects-interactions pattern) order.erc1155Value -= _erc1155RedeemAmount; (bool success,) = order.collection.call( abi.encodeWithSelector( 0xf242432a, // bytes4(keccak256('safeTransferFrom(address,address,uint256,uint256,bytes)')) address(this), msg.sender, order.tokenId, _erc1155RedeemAmount, "" ) ); require(success); if (order.erc1155Value == 0) { delete _orders[_orderId]; // it is not possible to delete using // storage pointers // https://docs.soliditylang.org/en/develop/types.html#data-location emit OrderDeleted(_orderId); } else { emit OrderUpdated(_orderId); } } function setOrderCommission( uint256 _orderId, uint256[] memory _commissionBps, address[] memory _commissionReceivers) external { // should we check if the total commission is less than 10000? // should revert in trade function anyway require(msg.sender == _orders[_orderId].operator); _orders[_orderId].commissionBps = _commissionBps; _orders[_orderId].commissionReceivers = _commissionReceivers; emit OrderUpdated(_orderId); } /*----------------------------------------------------------*| |* # OFFERS *| |*----------------------------------------------------------*/ /** * @dev offers can be made independently of whether the token is on sale or * not, the msg.value is used to determine * the offer amount, so no function parameter is needed for that * @dev there is no require to check that an offer or offer doesn't already * exist and if so, that the offer amount * is not greater than the order itself, this was omitted in order to save * gas; the frontend should check this in * order to prevent mistakes from the user * @param _collection address of the erc721 implementation contract or proxy * contract * @param _tokenId the token Id to make an offer to * @param _amount the NFT amount, _amount == 0 for ERC721 and _amount > 0 * for ERC1155 * @param _from needed in order to integrate Wert payment solution, because * in every txn Wert is the `msg.sender`. * using _msgSender does not represent a security risk, on the other * hand, it is possible for the buyer to use * this parameter simply in order to transfer the NFT to an address other * than their own, this can be useful for * external contract buying NFTs. */ function createOffer( address _collection, uint256 _tokenId, uint256 _amount, address _from, uint256 _unitPrice ) external payable isWhitelisted(_collection) { _offerCount.increment(); // start count at 1 if (_amount == 0) require(msg.value == _unitPrice); else require(msg.value == _unitPrice * _amount); offers[_offerCount.current()] = _Offer( _tokenId, // uint256 tokenId; _unitPrice, // uint256 unitPrice; _amount, // uint256 amount; _collection, // address collection; _from // address from; ); emit OfferCreated(_offerCount.current()); } /** * @dev cancels offer and refunds ETH back to bidder. When an order gets * filled, the offer isn't marked as * cancelled, in order to allow users to claim back their money. * @param _offerId the Id of the offer. */ function deleteOffer(uint256 _offerId) external { // in memory copy needed so that it is possible to delete the struct // inside the storage offers mapping, while // keeping check effects interact pattern intact _Offer memory offer = offers[_offerId]; uint256 refund; if (offer.erc1155Value == 0) { refund = offer.unitPrice; } else { refund = offer.unitPrice * offer.erc1155Value; } require(msg.sender == offer.from); // mark offer as cancelled forever, updating offer price before external // call, Checks Effects Interactions // pattern delete offers[_offerId]; // transfer the offer amount back to bidder _sendValue(offer.from, refund); emit OfferDeleted(_offerId); } /** * @dev this is one of two functions called by a buyer in order to modify * their offer, there are two functions, * `raiseOffer()` and `lowerOffer()`, because they expect different * parameters depending on whether the offer is * being raised or lowerd. * A `msg.value` is required, this function will add the amount sent to * the old offer amount. The frontend * needs to calculate the difference between the old and new offer. * E.g. A buyer calls createOffer() and pays 0.1 ETH. The same buyer * later wants to raise the offer to 0.3 ETH, * therefore they now need to send 0.2 ETH, because 0.1 was was sent before. * @dev anyone can call this function, i.e. requiring that caller is offer * creator is not needed * @param _offerId the id of the offer * call this function only if the new total price is greater than the old * total price */ function raiseOfferPrice(uint256 _offerId, uint256 _erc1155Value, uint256 _unitPrice) external payable { _Offer storage _offer = offers[_offerId]; require(msg.sender == _offer.from); if (_offer.erc1155Value == 0) { require(msg.value == _unitPrice - _offer.unitPrice); } else { require(msg.value == (_unitPrice * _erc1155Value) - (_offer.unitPrice * _offer.erc1155Value)); } _offer.unitPrice = _unitPrice; // transfer extra amount needed on top of // older offer _offer.erc1155Value = _erc1155Value; emit OfferUpdated(_offerId); } /** * @dev this is one of two functions called by a buyer in order to modify * their offer, there are two functions, * `raiseOffer()` and `lowerOffer()`, because they expect different * parameters depending on whether the offer is * being raised or lowerd. * In contrast with `raiseOffer()`, instead of `msg.value` this * function expects a uint parameter representing * the new (lower) offer; the buyer will get refunded the difference. * E.g. A buyer calls createOffer() and pays 0.3 ETH. The same buyer * later wants to lower the offer to 0.1 ETH, * therefore they will get refunded 0.2 ETH. I.e. The amount expected by the * `_newAmount` paramer is 0.1 ETH (1^17). * @param _offerId the id of the offer */ function lowerOfferPrice(uint256 _offerId, uint256 _erc1155Amount, uint256 _unitPrice) external { _Offer storage _offer = offers[_offerId]; require(msg.sender == _offer.from, "hwy"); uint256 refund; if (_erc1155Amount == 0) { refund = _offer.unitPrice - _unitPrice; } else { refund = (_offer.unitPrice * _offer.erc1155Value) - (_unitPrice * _erc1155Amount); _offer.erc1155Value = _erc1155Amount; } _offer.unitPrice = _unitPrice; // needed to store result before offer // price is updated // transfer the difference between old and new lower offer to the user _sendValue(msg.sender, refund); emit OfferUpdated(_offerId); } function acceptListedTokenOffer( uint256 _orderId, uint256 _offerId, uint256[] memory _commissionBps, address[] memory _commissionReceivers ) external { _Order memory order = _orders[_orderId]; _Offer memory offer = offers[_offerId]; require(order.operator == msg.sender && order.tokenId == offer.tokenId && order.collection == offer.collection); delete _orders[_orderId]; delete offers[_offerId]; _trade( _Order( order.tokenId, // uint256 tokenId offer.unitPrice, // offer price, not order price 0, order.collection, // address collection msg.sender, // seller offer.from, // buyer / nft recipient _commissionBps, // uint256 commissionBps _commissionReceivers ), _orderId, _primaryOffersFee ); } /*----------------------------------------------------------*| |* # TRADING *| |*----------------------------------------------------------*/ /** * @notice the collector calls this function to buy an NFT at the ask price, * only if an order exists * @notice if someone has an open offer but calls fillOrder, the offer will * remain open, meaning they will need to * call cancelOffer() to get a refund. This is unlikely, as users will * likely be aware of this and use the refund in * order to pay for part of the order. * @param _id avoids having to store a mapping to order id like the * deprecated `mapping(address => mapping(uint256 * => uint256)) private _tokenToOrderId` which would have not worked for * erc1155 as each token has a supply. * _orderId does not constitute a vulnerability as it is user provided, * since A) a regular user will go through the * frontend which gets orderId from events * @param _buyer needed in order to integrate Wert payment solution, because * in every txn Wert is the msg.sender, * although using msg.sender would cost less gas. * using _msgSender does not represent a security risk, on the other hand, * it is possible for the buyer to use this * parameter simply in order to transfer the NFT to an address other than * their own, this can be useful for external * contract buying NFTs. * + @param _erc1155Value market order amount (total or partial fill). * `_erc1155Value == 0` corresponds to one erc721 tokenId, `_erc1155Value > * 0` for erc1155 tokenIds * * MUST: * * - `msg.value` must be equal to `_orders[_orderId]der.unitPrice * * buyAmount` * - `_orders[_orderId].sellAmount >= buyAmount` * */ function fillOrder(uint256 _id, address _buyer, uint256 _erc1155Value) external payable { _Order memory order = _orders[_id]; require(msg.value == order.unitPrice * (_erc1155Value == 0 ? 1 : _erc1155Value)); // subtracting user-suplied `_erc1155Value` from order amount, // transaction will revert if underflow, implicitly // requiring `_orders[_id]._erc1155Value >= _erc1155Value` if (_orders[_id].erc1155Value - _erc1155Value == 0) delete _orders[_id]; else _orders[_id].erc1155Value -= _erc1155Value; _trade( _Order( order.tokenId, // uint256 tokenId msg.value, // price _erc1155Value, order.collection, // address collection order.from, // seller or from _buyer, // address buyer used for nft transfer order.commissionBps, // uint256 commissionBps order.commissionReceivers // address commissionReceiver ), _id, _primaryOrdersFee // uint256 primaryFee ); } /** * * @dev Handles the receipt of a single ERC1155 token type. This function is * called at the end of a `safeTransferFrom` after the balance has been * updated. * * NOTE: To accept the transfer, this must return * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` * (i.e. 0xf23a6e61, or its own function selector). * * @param _operator The address which initiated the transfer (i.e. * msg.sender) * @param _from The address which previously owned the token * @param _tokenId The ID of the token being transferred * @param _value The amount of tokens being transferred * @param _data Additional data with no specified format * @param _data `uint256 id` corresponding to an order to be updated or * an offer to be accepted, if the `id` * parameter is 0 create a new order. * @param _data `uint256 unitPrice` is only required for updating an * order, i.e. if `unitPrice == 0` `id` an * offer id, if `unitPrice > 0` `id` is an order id * @return * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` * if transfer is allowed */ function onERC1155Received( address _operator, // either the previous owner or operator, whoever // called safeTransferFrom on the ERC1155 // contract address _from, // previous owner uint256 _tokenId, uint256 _value, bytes memory _data ) external returns (bytes4) { (uint256 id, uint256 unitPrice, uint256[] memory commissionBps, address[] memory commissionReceivers) = abi.decode(_data, (uint256, uint256, uint256[], address[])); if (unitPrice == 0) { /*----------------------------------------------------------*| |* # ACCEPT OFFER *| |*----------------------------------------------------------*/ // if `_value` is more than the amount the following if check will // revert due to underflow, // intended to stop someone from sending more erc1155 tokens then // there are available in the order _Offer memory offer = offers[id]; require(offer.collection == msg.sender && offer.tokenId == _tokenId); // subtracting `_value` from offer amount, transaction will revert // if underflow, implicitly requiring // `offers[id]._erc1155Value >= _value` if (offers[id].erc1155Value - _value == 0) delete offers[id]; else offers[id].erc1155Value -= _value; _trade( _Order( _tokenId, // uint256 tokenId offer.unitPrice * _value, // uint256 price (unitPrice * // value) _value, msg.sender, // address collection _from, // seller offer.from, // buyer or operator commissionBps, // uint256 commissionBps commissionReceivers // address commissionReceivers ), id, _primaryOffersFee ); } else if (id == 0) { /*----------------------------------------------------------*| |* # CREATE ORDER *| |*----------------------------------------------------------*/ // if the order/offer id parameter is 0, create a new order _createOrder(_operator, _from, _tokenId, _value, unitPrice, commissionBps, commissionReceivers); } else { /*----------------------------------------------------------*| |* # UPDATE ORDER *| |*----------------------------------------------------------*/ // if the user supplied a non-zero value for `unitPrice` then the // `id` parameter must correspond to an order // that needs to be updated // the operator, collection and tokenId of the NFT received by the // marketplace must match the ones stored at // the id provided by the operator // in order to avoid operators increasing allowance for orders with // different (more valuable) NFTs _Order storage order = _orders[id]; require(order.operator == _operator && order.collection == msg.sender && order.tokenId == _tokenId); order.erc1155Value += _value; order.unitPrice = unitPrice; emit OrderUpdated(id); } return 0xf23a6e61; } /** * @dev Whenever an {IERC721} `tokenId` token is transferred to this * contract via {IERC721-safeTransferFrom} * by `operator` from `from`, this function is called. * * It must return its Solidity selector to confirm the token transfer. * If any other value is returned or the interface is not implemented by the * recipient, the transfer will be * reverted. * * The selector can be obtained in Solidity with * `IERC721.onERC721Received.selector`. * @param _operator The address which called `safeTransferFrom` function * @param _from The address which previously owned the token * @param _tokenId The NFT identifier which is being transferred * @param _data Additional data with no specified format */ function onERC721Received( address _operator, address _from, uint256 _tokenId, bytes calldata _data ) external returns (bytes4) { (uint256 id, uint256 unitPrice, uint256[] memory commissionBps, address[] memory commissionReceivers) = abi.decode(_data, (uint256, uint256, uint256[], address[])); if (id == 0) { /*----------------------------------------------------------*| |* # CREATE ORDER *| |*----------------------------------------------------------*/ // if the order/offer id parameter is 0, create a new order _createOrder(_operator, _from, _tokenId, 0, unitPrice, commissionBps, commissionReceivers); } else { /*----------------------------------------------------------*| |* # ACCEPT OFFER *| |*----------------------------------------------------------*/ // if `_value` is more than the amount the following if check will // revert due to underflow, // intended to stop someone from sending more erc1155 tokens then // there are available in the order _Offer memory offer = offers[id]; require(offer.collection == msg.sender && offer.tokenId == _tokenId); delete offers[id]; // ERC-721 doesn't have any supply therefore the // offer may be deleted after accepting the // offer _trade( _Order( _tokenId, // uint256 tokenId offer.unitPrice, // uint256 price 0, msg.sender, // address collection _from, // address buyer or from offer.from, // seller commissionBps, // uint256 commissionBps commissionReceivers ), id, _primaryOffersFee //uint256 primaryFee ); } return 0x150b7a02; } /** * @dev ERC-721 tokens are transferred to the buyer via `transferFrom` * rather than `safeTransferFrom` * i.e. the caller is responsible to confirm that the recipient is * capable of receiving ERC721 */ function _transferNFT( address _collection, address _from, address _to, uint256 _tokenId, uint256 _erc1155Value ) private { bool success; if (_erc1155Value == 0) { // bytes4(keccak256('safeTransferFrom(address,address,uint256)')) == 0x42842e0e (success,) = _collection.call(abi.encodeWithSelector(0x42842e0e, _from, _to, _tokenId)); } else { // bytes4(keccak256('safeTransferFrom(address,address,uint256,uint256,bytes)')) == 0xf242432a (success,) = _collection.call(abi.encodeWithSelector(0xf242432a, _from, _to, _tokenId, _erc1155Value, "")); } require(success); } function _trade(_Order memory _order, uint256 _id, uint256 _primaryMarketFee) private { uint256 marketplaceAmount; // declare `marketplaceAmount`, its value // will be calculated based on whether it is a // primary or secondary sale uint256 sellerAmount = _order.unitPrice; // sellerAmount is set equal to // price and reduced at each step by // subtracting fees, royalties and commissions, if any. address payable[] memory royaltyRecipients; // declare // `royaltyRecipients`, its value will be calculated based // on whether it is a primary or secondary sale uint256[] memory royaltyAmounts; // declare `royaltyAmounts`, its value // will be calculated based on whether it // is a primary or secondary sale bool checkSecondaryMarket = true; if (_order.collection.codehash == _ERC721SovreignV1CodeHash) { // it's a v1 721 token, check market registry (, bytes memory secondaryMarket) = _PRIMARY_MARKET_REGISTRY.call( abi.encodeWithSelector( 0x7abab711, _order.collection, _order.tokenId ) // bytes4(keccak256("secondaryMarketInfo(address,uint256)")) == 0x7abab711 ); checkSecondaryMarket = abi.decode(secondaryMarket, (bool)); } /*----------------------------------------------------------*| |* # PAY ROYALTIES *| |*----------------------------------------------------------*/ // > "Marketplaces that support this standard MUST pay royalties no // matter where the sale occurred or in what // currency" - https://eips.ethereum.org/EIPS/eip-2981. /*----------------------------------------------------------*| |* # IF ROYALTIES SUPPORTED *| |*----------------------------------------------------------*/ // The collection implements some royalty standard, otherwise the length // of the arrays returned would be 0. if (checkSecondaryMarket) { (royaltyRecipients, royaltyAmounts) = getRoyalty(_order.collection, _order.tokenId, _order.unitPrice); } uint256 royaltyRecipientsLength = royaltyRecipients.length; // assign to // memory variable to save gas if (royaltyRecipientsLength > 0) { if (_secondaryMarketFee > 0) { /*----------------------------------------------------------*| |* # PAY MARKETPLACE FEE *| |*----------------------------------------------------------*/ marketplaceAmount = (_order.unitPrice * _secondaryMarketFee) / _BPS_DENOMINATOR; // subtracting primary or secondary fee amount from seller // amount, this is a security check (will revert // on underflow) as well as a variable assignment. sellerAmount -= marketplaceAmount; // subtract before external // call _sendValue(_feeRecipient, marketplaceAmount); } do { royaltyRecipientsLength--; // subtracting royalty amount from seller amount, this is a // security check (will revert on // underflow) as well as a variable assignment. if(royaltyAmounts[royaltyRecipientsLength] > 0){ sellerAmount -= royaltyAmounts[royaltyRecipientsLength]; // subtract // before external call _sendValue(royaltyRecipients[royaltyRecipientsLength], royaltyAmounts[royaltyRecipientsLength]); } } while (royaltyRecipientsLength > 0); } else { //case primary marketplaceAmount = (_order.unitPrice * _primaryMarketFee) / _BPS_DENOMINATOR; sellerAmount -= marketplaceAmount; // subtract before external // call _sendValue(_feeRecipient, marketplaceAmount); } /*----------------------------------------------------------*| |* # PAY ORDER COMMISSIONS (if any) *| |*----------------------------------------------------------*/ uint256 commissionReceiversLength = _order.commissionReceivers.length; // assign if (commissionReceiversLength > 0) { do { commissionReceiversLength--; if(_order.commissionBps[commissionReceiversLength] > 0){ uint256 commissionAmount = (_order.commissionBps[commissionReceiversLength] * _order.unitPrice) / _BPS_DENOMINATOR; // calculate sellerAmount -= commissionAmount; // subtract before external _sendValue(_order.commissionReceivers[commissionReceiversLength], commissionAmount); } } while (commissionReceiversLength > 0); } /*----------------------------------------------------------*| |* # PAY SELLER *| |*----------------------------------------------------------*/ _sendValue(_order.from, sellerAmount); /*----------------------------------------------------------*| |* # TRANSFER NFT *| |*----------------------------------------------------------*/ _transferNFT( _order.collection, address(this), _order.operator, // buyer _order.tokenId, _order.erc1155Value ); emit Trade( _order.collection, _order.tokenId, _order.from, // seller _id, _order.unitPrice, _order.erc1155Value ); } /** * @dev Replacement for Solidity's `transfer`: sends `_amount` wei to * `_receiver`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] raises 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://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn * more]. * * IMPORTANT: because control is transferred to `_receiver`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions * pattern]. */ function _sendValue(address _receiver, uint256 _amount) private { // solhint-disable-next-line avoid-low-level-calls (bool success,) = payable(_receiver).call{ value: _amount }(""); require(success); } /*----------------------------------------------------------*| |* # ADMIN FUNCTIONS *| |*----------------------------------------------------------*/ /** * @param feeRecipient_ address (multisig) controlled by Ninfa that will * receive any market fees */ function setFeeRecipient(address feeRecipient_) external onlyOwner { _feeRecipient = feeRecipient_; } function setWhitelist(address whitelist_) external onlyOwner { _whitelist = whitelist_; } /** * @notice sets market sale fees for NINFA_ERC721_V2 communal collection. * @param primaryOrdersFee_ fee BPS for primary market orders, set to 500 * BPS (5% shares) at deployment. * @param primaryOffersFee_ fee BPS for primary market offers, set to 500 * BPS (5% shares) at deployment. */ function setMarketFees( uint256 primaryOrdersFee_, uint256 primaryOffersFee_, uint256 secondaryMarketFee_ ) external onlyOwner { _primaryOrdersFee = primaryOrdersFee_; _primaryOffersFee = primaryOffersFee_; _secondaryMarketFee = secondaryMarketFee_; } function orders(uint256 _orderId) external view returns (_Order memory) { return _orders[_orderId]; } /*----------------------------------------------------------*| |* # VIEW FUNCTIONS *| |*----------------------------------------------------------*/ /** * @dev See {IERC165-supportsInterface}. * Interface ID for IERC165 == 0x01ffc9a7 * Return value from `onERC1155Received` call if a contract accepts receipt * (i.e * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`). * In all other cases the ERC1155TokenReceiver rules MUST be followed as * appropriate for the implementation (i.e. * safe, custom and/or hybrid). * Interface ID for IERC721Receiver. A wallet/broker/auction application * MUST implement the wallet interface if it * will accept safe transfers. */ function supportsInterface(bytes4 interfaceId) external pure returns (bool) { return interfaceId == 0x01ffc9a7 || interfaceId == 0xf23a6e61 || interfaceId == 0x150b7a02; } /*----------------------------------------------------------*| |* # CONSTRUCTOR *| |*----------------------------------------------------------*/ /** * @dev Grants `DEFAULT_ADMIN_ROLE` * @dev after deployment admin needs to manually whitelist collections. * @param _royaltyRegistry see https://royaltyregistry.xyz/lookup for public * addresses */ constructor( address _royaltyRegistry, address _primaryMarketRegistry, address factory_, address whitelist_, bytes32 ERC721SovreignV1CodeHash ) RoyaltyEngineV1(_royaltyRegistry) { _PRIMARY_MARKET_REGISTRY = _primaryMarketRegistry; _factory = factory_; _whitelist = whitelist_; _ERC721SovreignV1CodeHash = ERC721SovreignV1CodeHash; } }
IEIP2981.sol 15 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/**
* EIP-2981
*/
interface IEIP2981 {
/**
* bytes4(keccak256("royaltyInfo(uint256,uint256)")) == 0x2a55205a
*
* => 0x2a55205a = 0x2a55205a
*/
function royaltyInfo(uint256 tokenId, uint256 value) external view returns (address, uint256);
}
IRarible.sol 27 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
interface IRaribleV1 {
/*
* bytes4(keccak256('getFeeBps(uint256)')) == 0x0ebd4c7f
* bytes4(keccak256('getFeeRecipients(uint256)')) == 0xb9c4d9fb
*
* => 0x0ebd4c7f ^ 0xb9c4d9fb == 0xb7799584
*/
function getFeeBps(uint256 id) external view returns (uint256[] memory);
function getFeeRecipients(uint256 id) external view returns (address payable[] memory);
}
interface IRaribleV2 {
/*
* bytes4(keccak256('getRaribleV2Royalties(uint256)')) == 0xcad96cca
*/
struct Part {
address payable account;
uint96 value;
}
function getRaribleV2Royalties(uint256 id) external view returns (Part[] memory);
}
RoyaltyEngineV1.sol 302 lines
/*----------------------------------------------------------*| |* ███ ██ ██ ███ ██ ███████ █████ *| |* ████ ██ ██ ████ ██ ██ ██ ██ *| |* ██ ██ ██ ██ ██ ██ ██ █████ ███████ *| |* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ *| |* ██ ████ ██ ██ ████ ██ ██ ██ *| |*----------------------------------------------------------*/ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; import "./specs/IRoyaltyRegistry.sol"; import "./specs/IRoyaltySplitter.sol"; import "./specs/IManifold.sol"; import "./specs/IRarible.sol"; import "./specs/IFoundation.sol"; import "./specs/ISuperRare.sol"; import "./specs/IEIP2981.sol"; import "./specs/INinfaRoyalty.sol"; import "./specs/IZoraOverride.sol"; import "./specs/IArtBlocksOverride.sol"; import "./specs/IKODAV2Override.sol"; /** * * @title RoyaltyEngineV1 * * * * @notice Custom implementation of Manifold RoyaltyEngineV1 * * * * @dev > "Marketplaces may choose to directly inherit the * * Royalty Engine to save a bit of gas". * * * * @dev ERC165 was removed because interface selector will * * be different from Manifold's (0xcb23f816) and this engine * * implementation is not meant for used by other contracts * * * * @author Fork of Manifold's RoyaltyRegistryV1 * * * * @custom:security-contact [email protected] * * */ contract RoyaltyEngineV1 { /** * @dev ROYALTY_REGISTRY could be a hardcoded constant, however using an * immutable variable * is useful for deploying the engine onto other (test) networks where its * address differs from Mainnet */ address internal immutable ROYALTY_REGISTRY; address internal constant SUPERRARE_REGISTRY = 0x17B0C8564E53f22364A6C8de6F7ca5CE9BEa4e5D; address internal constant SUPERRARE_V1 = 0x41A322b28D0fF354040e2CbC676F0320d8c8850d; address internal constant SUPERRARE_V2 = 0xb932a70A57673d89f4acfFBE830E8ed7f75Fb9e0; error Unauthorized(); error InvalidAmount(uint256 amount); error LengthMismatch(uint256 recipients, uint256 bps); // only used in // RoyaltyEngineV1 /** * Get the royalties for a given token and sale amount. * * @param tokenAddress - address of token * @param tokenId - id of token * @param value - sale value of token * Returns two arrays, first is the list of royalty recipients, second is * the amounts for each recipient. */ function getRoyalty( address tokenAddress, uint256 tokenId, uint256 value ) internal returns (address payable[] memory recipients, uint256[] memory amounts) { /** * @dev Control-flow hijack and gas griefing vulnerability within * Manifold's RoyaltyEngine, mitigated in * https://github.com/manifoldxyz/royalty-registry-solidity/commit/c5ba6db3e04e0b364f7afd7aae853a25542a7439. * "To mitigate the griefing vector and other potential * vulnerabilities, limit the gas by default that * _getRoyalty is given to at most 50,000 gas, but certainly no more * than 100,000 gas." * - * https://githubrecord.com/issue/manifoldxyz/royalty-registry-solidity/17/1067105243 * However, Ninfa's ERC-2981 implementation (ERC2981N) needs to write to * storage upon primary sales, this consumes * 800,000 at most gas, * while it only reads from storage upon secondary sales, see * {ERC2981N-rotaltyInfo} */ try this._getRoyalty{ gas: 1_000_000 }(tokenAddress, tokenId, value) returns ( address payable[] memory _recipients, uint256[] memory _amounts ) { return (_recipients, _amounts); } catch { revert("Royalty lookup failed"); } } /** * @dev Get the royalty for a given token * @dev the original RoyaltyEngineV1 has been modified by removing the * _specCache and the associated code, * using try catch statements is very cheap, no need to store `_specCache` * mapping, see * {RoyaltyEngineV1-_specCache}. * - https://www.reddit.com/r/ethdev/comments/szot8r/comment/hy5vsxb/ * @dev EIP-2981 standard lookup is performed first unlike Manifold's * implementation, as it is the most prevalent * royalty standard as well as the one being used by Ninfa's collections * @return recipients array and amounts array, if no royalty standard has * been found, the returned arrays will be * empty */ function _getRoyalty( address tokenAddress, uint256 tokenId, uint256 value ) external returns (address payable[] memory recipients, uint256[] memory amounts) { address royaltyAddress = IRoyaltyRegistry(ROYALTY_REGISTRY).getRoyaltyLookupAddress(tokenAddress); try INinfaRoyalty(royaltyAddress).ninfaRoyaltyInfo(tokenId, value) returns ( address payable[] memory recipients_, uint256[] memory bps_ ) { require(recipients_.length == bps_.length); return (recipients_, _computeAmounts(value, bps_)); } catch { } try IEIP2981(royaltyAddress).royaltyInfo(tokenId, value) returns (address recipient, uint256 amount) { if (amount > value) revert InvalidAmount(amount); uint32 recipientSize; assembly { recipientSize := extcodesize(recipient) } if (recipientSize > 0) { try IRoyaltySplitter(recipient).getRecipients() returns (Recipient[] memory splitRecipients) { recipients = new address payable[](splitRecipients.length); amounts = new uint256[](splitRecipients.length); uint256 sum = 0; uint256 splitRecipientsLength = splitRecipients.length; for (uint256 i = 0; i < splitRecipientsLength;) { Recipient memory splitRecipient = splitRecipients[i]; recipients[i] = payable(splitRecipient.recipient); uint256 splitAmount = splitRecipient.bps * amount / 10_000; amounts[i] = splitAmount; sum += splitAmount; unchecked { ++i; } } // sum can be less than amount, otherwise small-value listings can break require(sum <= amount, "Invalid split"); return (recipients, amounts); } catch {} } try IRoyaltySplitter(royaltyAddress).getRecipients(tokenId) returns (Recipient[] memory royaltyInfo) { uint256 splitRecipientsLength = royaltyInfo.length; recipients = new address payable[](splitRecipientsLength); amounts = new uint256[](splitRecipientsLength); uint256 sum; for (uint256 i; i < splitRecipientsLength; ) { Recipient memory splitRecipient = royaltyInfo[i]; recipients[i] = payable(splitRecipient.recipient); uint256 splitAmount = (splitRecipient.bps * amount) / 10000; amounts[i] = splitAmount; sum += splitAmount; unchecked { ++i; } } // sum can be less than amount, otherwise small-value listings can break require(sum <= amount, "Invalid split"); return (recipients, amounts); } catch { // Supports EIP2981. Return amounts recipients = new address payable[](1); amounts = new uint256[](1); recipients[0] = payable(recipient); amounts[0] = amount; return (recipients, amounts); } } catch { } try IManifold(royaltyAddress).getRoyalties(tokenId) returns ( address payable[] memory recipients_, uint256[] memory bps ) { // Supports manifold interface. Compute amounts require(recipients_.length == bps.length); return (recipients_, _computeAmounts(value, bps)); } catch { } // SuperRare handling if (tokenAddress == SUPERRARE_V1 || tokenAddress == SUPERRARE_V2) { try ISuperRareRegistry(SUPERRARE_REGISTRY).tokenCreator(tokenAddress, tokenId) returns ( address payable creator ) { try ISuperRareRegistry(SUPERRARE_REGISTRY).calculateRoyaltyFee(tokenAddress, tokenId, value) returns ( uint256 amount ) { recipients = new address payable[](1); amounts = new uint256[](1); recipients[0] = creator; amounts[0] = amount; return (recipients, amounts); } catch { } } catch { } } try IRaribleV2(royaltyAddress).getRaribleV2Royalties(tokenId) returns (IRaribleV2.Part[] memory royalties) { // Supports rarible v2 interface. Compute amounts recipients = new address payable[](royalties.length); amounts = new uint256[](royalties.length); uint256 totalAmount; for (uint256 i = 0; i < royalties.length; i++) { recipients[i] = royalties[i].account; amounts[i] = (value * royalties[i].value) / 10_000; totalAmount += amounts[i]; } if (totalAmount > value) revert InvalidAmount(totalAmount); return (recipients, amounts); } catch { } try IRaribleV1(royaltyAddress).getFeeRecipients(tokenId) returns (address payable[] memory recipients_) { // Supports rarible v1 interface. Compute amounts recipients_ = IRaribleV1(royaltyAddress).getFeeRecipients(tokenId); try IRaribleV1(royaltyAddress).getFeeBps(tokenId) returns (uint256[] memory bps) { if (recipients_.length != bps.length) { revert LengthMismatch(recipients_.length, bps.length); } return (recipients_, _computeAmounts(value, bps)); } catch { } } catch { } try IFoundation(royaltyAddress).getFees(tokenId) returns ( address payable[] memory recipients_, uint256[] memory bps ) { // Supports foundation interface. Compute amounts if (recipients_.length != bps.length) { revert LengthMismatch(recipients_.length, bps.length); } return (recipients_, _computeAmounts(value, bps)); } catch { } try IZoraOverride(royaltyAddress).convertBidShares(tokenAddress, tokenId) returns ( address payable[] memory recipients_, uint256[] memory bps ) { // Support Zora override if (recipients_.length != bps.length) { revert LengthMismatch(recipients_.length, bps.length); } return (recipients_, _computeAmounts(value, bps)); } catch { } try IArtBlocksOverride(royaltyAddress).getRoyalties(tokenAddress, tokenId) returns ( address payable[] memory recipients_, uint256[] memory bps ) { // Support Art Blocks override if (recipients_.length != bps.length) { revert LengthMismatch(recipients_.length, bps.length); } return (recipients_, _computeAmounts(value, bps)); } catch { } try IKODAV2Override(royaltyAddress).getKODAV2RoyaltyInfo(tokenAddress, tokenId, value) returns ( address payable[] memory _recipients, uint256[] memory _amounts ) { // Support KODA V2 override if (_recipients.length != _amounts.length) { revert LengthMismatch(_recipients.length, _amounts.length); } return (_recipients, _amounts); } catch { } // No supported royalties configured return (recipients, amounts); } /** * Compute royalty amounts */ function _computeAmounts(uint256 value, uint256[] memory bps) private pure returns (uint256[] memory amounts) { amounts = new uint256[](bps.length); uint256 totalAmount; for (uint256 i = 0; i < bps.length; i++) { amounts[i] = (value * bps[i]) / 10_000; totalAmount += amounts[i]; } if (totalAmount > value) revert InvalidAmount(totalAmount); return amounts; } constructor(address _royaltyRegistry) { ROYALTY_REGISTRY = _royaltyRegistry; } }
IManifold.sol 19 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/// @author: manifold.xyz
/**
* @dev Royalty interface for creator core classes
*/
interface IManifold {
/**
* @dev Get royalites of a token. Returns list of receivers and basisPoints
*
* bytes4(keccak256('getRoyalties(uint256)')) == 0xbb3bafd6
*
* => 0xbb3bafd6 = 0xbb3bafd6
*/
function getRoyalties(uint256 tokenId) external view returns (address payable[] memory, uint256[] memory);
}
ISuperRare.sol 43 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
interface ISuperRareRegistry {
/**
* @dev Get the royalty fee percentage for a specific ERC721 contract.
* @param _contractAddress address ERC721Contract address.
* @param _tokenId uint256 token ID.
* @return uint8 wei royalty fee.
*/
function getERC721TokenRoyaltyPercentage(
address _contractAddress,
uint256 _tokenId
)
external
view
returns (uint8);
/**
* @dev Utililty function to calculate the royalty fee for a token.
* @param _contractAddress address ERC721Contract address.
* @param _tokenId uint256 token ID.
* @param _amount uint256 wei amount.
* @return uint256 wei fee.
*/
function calculateRoyaltyFee(
address _contractAddress,
uint256 _tokenId,
uint256 _amount
)
external
view
returns (uint256);
/**
* @dev Get the token creator which will receive royalties of the given
* token
* @param _contractAddress address ERC721Contract address.
* @param _tokenId uint256 token ID.
*/
function tokenCreator(address _contractAddress, uint256 _tokenId) external view returns (address payable);
}
IFoundation.sol 20 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
interface IFoundation {
/*
* bytes4(keccak256('getFees(uint256)')) == 0xd5a06d4c
*
* => 0xd5a06d4c = 0xd5a06d4c
*/
function getFees(uint256 tokenId) external view returns (address payable[] memory, uint256[] memory);
}
interface IFoundationTreasuryNode {
function getFoundationTreasury() external view returns (address payable);
}
interface IFoundationTreasury {
function isAdmin(address account) external view returns (bool);
}
INinfaRoyalty.sol 20 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/**
* EIP-2981
*/
interface INinfaRoyalty {
/**
* bytes4(keccak256("royaltyInfo(uint256,uint256)")) == 0x2a55205a
*
* => 0x2a55205a = 0x2a55205a
*/
function ninfaRoyaltyInfo(
uint256 tokenId,
uint256 value
)
external
returns (address payable[] memory, uint256[] memory);
}
IZoraOverride.sol 64 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/**
* Paired down version of the Zora Market interface
*/
interface IZoraMarket {
struct ZoraDecimal {
uint256 value;
}
struct ZoraBidShares {
// % of sale value that goes to the _previous_ owner of the nft
ZoraDecimal prevOwner;
// % of sale value that goes to the original creator of the nft
ZoraDecimal creator;
// % of sale value that goes to the seller (current owner) of the nft
ZoraDecimal owner;
}
function bidSharesForToken(uint256 tokenId) external view returns (ZoraBidShares memory);
}
/**
* Paired down version of the Zora Media interface
*/
interface IZoraMedia {
/**
* Auto-generated accessors of public variables
*/
function marketContract() external view returns (address);
function previousTokenOwners(uint256 tokenId) external view returns (address);
function tokenCreators(uint256 tokenId) external view returns (address);
/**
* ERC721 function
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
}
/**
* Interface for a Zora media override
*/
interface IZoraOverride {
/**
* @dev Convert bid share configuration of a Zora Media token into an array
* of receivers and bps values
* Does not support prevOwner and sell-on amounts as that is specific
* to Zora marketplace implementation
* and requires updates on the Zora Media and Marketplace to update the
* sell-on amounts/previous owner values.
* An off-Zora marketplace sale will break the sell-on functionality.
*/
function convertBidShares(
address media,
uint256 tokenId
)
external
view
returns (address payable[] memory, uint256[] memory);
}
IERC165.sol 25 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/**
* @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);
}
IKODAV2Override.sol 38 lines
// SPDX-License-Identifier: MIT
/// @author: knownorigin.io
pragma solidity 0.8.20;
interface IKODAV2 {
function editionOfTokenId(uint256 _tokenId) external view returns (uint256 _editionNumber);
function artistCommission(uint256 _editionNumber)
external
view
returns (address _artistAccount, uint256 _artistCommission);
function editionOptionalCommission(uint256 _editionNumber)
external
view
returns (uint256 _rate, address _recipient);
}
interface IKODAV2Override {
/// @notice Emitted when the royalties fee changes
event CreatorRoyaltiesFeeUpdated(uint256 _oldCreatorRoyaltiesFee, uint256 _newCreatorRoyaltiesFee);
/// @notice For the given KO NFT and token ID, return the addresses and the
/// amounts to pay
function getKODAV2RoyaltyInfo(
address _tokenAddress,
uint256 _id,
uint256 _amount
)
external
view
returns (address payable[] memory, uint256[] memory);
/// @notice Allows the owner() to update the creator royalties
function updateCreatorRoyalties(uint256 _creatorRoyaltiesFee) external;
}
IRoyaltyRegistry.sol 54 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/// @author: manifold.xyz
import "../introspection/IERC165.sol";
/**
* @dev Royalty registry interface
*/
interface IRoyaltyRegistry is IERC165 {
event RoyaltyOverride(address owner, address tokenAddress, address royaltyAddress);
/**
* Override the location of where to look up royalty information for a given
* token contract.
* Allows for backwards compatibility and implementation of royalty logic
* for contracts that did not previously
* support them.
*
* @param tokenAddress - The token address you wish to override
* @param royaltyAddress - The royalty override address
*/
function setRoyaltyLookupAddress(address tokenAddress, address royaltyAddress) external returns (bool);
/**
* Returns royalty address location. Returns the tokenAddress by default,
* or the override if it exists
*
* @param tokenAddress - The token address you are looking up the royalty
* for
*/
function getRoyaltyLookupAddress(address tokenAddress) external view returns (address);
/**
* Returns the token address that an overrideAddress is set for.
* Note: will not be accurate if the override was created before this
* function was added.
*
* @param overrideAddress - The override address you are looking up the
* token for
*/
function getOverrideLookupTokenAddress(address overrideAddress) external view returns (address);
/**
* Whether or not the message sender can override the royalty address for
* the given token address
*
* @param tokenAddress - The token address you are looking up the royalty
* for
*/
function overrideAllowed(address tokenAddress) external view returns (bool);
}
IRoyaltySplitter.sol 20 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/// @author: manifold.xyz
struct Recipient {
address payable recipient;
uint16 bps;
}
interface IRoyaltySplitter {
/**
* @dev Get the splitter recipients;
*/
function getRecipients() external view returns (Recipient[] memory);
function getRecipients(
uint256 tokenId
) external view returns (Recipient[] memory);
}
IArtBlocksOverride.sol 24 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/**
* Interface for an Art Blocks override
*/
interface IArtBlocksOverride {
/**
* @dev Get royalites of a token at a given tokenAddress.
* Returns array of receivers and basisPoints.
*
* bytes4(keccak256('getRoyalties(address,uint256)')) == 0x9ca7dc7a
*
* => 0x9ca7dc7a = 0x9ca7dc7a
*/
function getRoyalties(
address tokenAddress,
uint256 tokenId
)
external
view
returns (address payable[] memory, uint256[] memory);
}
Read Contract
offers 0x8a72ea6a → uint256, uint256, uint256, address, address
orders 0xa85c38ef → tuple
owner 0x8da5cb5b → address
supportsInterface 0x01ffc9a7 → bool
Write Contract 19 functions
These functions modify contract state and require a wallet transaction to execute.
_getRoyalty 0x7fea43b8
address tokenAddress
uint256 tokenId
uint256 value
returns: address[], uint256[]
acceptListedTokenOffer 0x5333a0a4
uint256 _orderId
uint256 _offerId
uint256[] _commissionBps
address[] _commissionReceivers
createOffer 0xc402b965
address _collection
uint256 _tokenId
uint256 _amount
address _from
uint256 _unitPrice
deleteOffer 0x74268ff2
uint256 _offerId
deleteOrder 0x11a00327
uint256 _orderId
fillOrder 0x32689eb8
uint256 _id
address _buyer
uint256 _erc1155Value
lowerOfferPrice 0x8b4c042a
uint256 _offerId
uint256 _erc1155Amount
uint256 _unitPrice
lowerOrderErc1155Amount 0x1efeff84
uint256 _orderId
uint256 _erc1155RedeemAmount
onERC1155Received 0xf23a6e61
address _operator
address _from
uint256 _tokenId
uint256 _value
bytes _data
returns: bytes4
onERC721Received 0x150b7a02
address _operator
address _from
uint256 _tokenId
bytes _data
returns: bytes4
raiseOfferPrice 0x2b1708b0
uint256 _offerId
uint256 _erc1155Value
uint256 _unitPrice
renounceOwnership 0x715018a6
No parameters
setFeeRecipient 0xe74b981b
address feeRecipient_
setMarketFees 0x3596188d
uint256 primaryOrdersFee_
uint256 primaryOffersFee_
uint256 secondaryMarketFee_
setOrderCommission 0x17a288df
uint256 _orderId
uint256[] _commissionBps
address[] _commissionReceivers
setWhitelist 0x854cff2f
address whitelist_
transferOwnership 0xf2fde38b
address newOwner
updateOrder 0x2ede65df
uint256 _erc1155RedeemAmount
uint256 _unitPrice
uint256 _orderId
updateOrderPrice 0x5e9e78d0
uint256 _orderId
uint256 _unitPrice
Token Balances (1)
View Transfers →Recent Transactions
No transactions found for this address