| 1 | [ |
| 2 | "autonomous agent", |
| 3 | { |
| 4 | "bounce_fees": { |
| 5 | "base": 10000 |
| 6 | }, |
| 7 | "init": "{ |
| 8 | $HOUSE_CUT = 0.1; |
| 9 | $ARTIST_CUT = 0.01; |
| 10 | $INVESTMENT_CUT = 0.01; |
| 11 | $HALF_HOUR = 1800; |
| 12 | |
| 13 | $extendDeadLine = $NFT=>($NFT.soldAt <= timestamp + $HALF_HOUR) ? ($NFT || {soldAt: $NFT.soldAt + $HALF_HOUR}) : $NFT; |
| 14 | /* |
| 15 | ** Strips $NFT of computed fields to reduce AA's storage size |
| 16 | */ |
| 17 | $stripToken = $NFT=>{ |
| 18 | delete($NFT, "isBanned"); |
| 19 | delete($NFT, "issuer"); |
| 20 | delete($NFT, "cap"); |
| 21 | delete($NFT, "mintedAt"); |
| 22 | delete($NFT, "duration"); |
| 23 | return $NFT; |
| 24 | }; |
| 25 | |
| 26 | if (NOT exists(trigger.data["method"])) |
| 27 | bounce("method field is mandatory"); |
| 28 | |
| 29 | $_lambertW = $x=>{ |
| 30 | if ($x == 1) |
| 31 | return 0.5671432904097838729999686622103555497538157871865125081351310792; |
| 32 | if ($x == 2) |
| 33 | return 0.8526055020137254913464724146953174668984533001514035087721073946; |
| 34 | $ev = ln($x); |
| 35 | return $ev - ln($ev - ln($ev - ln($ev - ln($ev - ln($ev - ln($ev - ln($ev - ln($ev - ln($ev - ln($ev - ln($ev - ln($ev - ln($ev))))))))))))); |
| 36 | }; |
| 37 | }", |
| 38 | "getters": "{ |
| 39 | $_maecenasCurve = $x=>69069420 * $x^2; |
| 40 | |
| 41 | |
| 42 | /* |
| 43 | ** All the information about the given NFT |
| 44 | */ |
| 45 | $tokenInfo = $NFT=>{ |
| 46 | $unit = unit[$NFT]; |
| 47 | $tok = var[$NFT]; |
| 48 | if (typeof($tok) == "boolean") |
| 49 | bounce("NFT not found"); |
| 50 | $feedData = data_feed[[oracles=this_address, feed_name=$NFT, ifnone=false, ifseveral="last", type="string"]]; |
| 51 | return $tok || |
| 52 | { |
| 53 | isBanned: (typeof($feedData) == "string") AND ($feedData == "REVOKED"), |
| 54 | cap: $unit.messages[[.app="asset"]].payload.cap, |
| 55 | mintedAt: $unit.timestamp |
| 56 | }; |
| 57 | }; |
| 58 | $publicTokenInfo = $NFT=>{ |
| 59 | return $tokenInfo($NFT) || {issuer: asset[$NFT].definer_address}; |
| 60 | }; |
| 61 | $smallTokenInfo = $NFT=>{ |
| 62 | $baseInfo = var[$NFT]; |
| 63 | $unit = unit[$NFT]; |
| 64 | if (typeof($baseInfo) == "boolean") |
| 65 | bounce("NFT not found"); |
| 66 | return $baseInfo || { |
| 67 | cap: $unit.messages[[.app="asset"]].payload.cap, |
| 68 | mintedAt: $unit.timestamp |
| 69 | }; |
| 70 | }; |
| 71 | /* |
| 72 | ** How many copies of a given NFT were initially minted wether they were sold or not |
| 73 | */ |
| 74 | $mintedUnits = $NFT=>{ |
| 75 | if (typeof(var[$NFT]) == "boolean") |
| 76 | bounce("NFT not found"); |
| 77 | if (asset[$NFT].exists == false) |
| 78 | bounce("That NFT does not exist"); |
| 79 | return asset[$NFT].cap; |
| 80 | }; |
| 81 | /* |
| 82 | ** How many copies of the NFT were sold, bounces if we did not mint the NFT |
| 83 | */ |
| 84 | $circulatingSupply = $NFT=>{ |
| 85 | $info = $tokenInfo($NFT); |
| 86 | if ($info.isBanned) |
| 87 | bounce("That NFT was revoked"); |
| 88 | if ($info.issuer != this_address) |
| 89 | bounce("That NFT was not minted by us, thus we don't know how many of them are out in the wild"); |
| 90 | return $info.unitsSold OTHERWISE 1; |
| 91 | }; |
| 92 | |
| 93 | |
| 94 | |
| 95 | |
| 96 | /* |
| 97 | ** All the artist information |
| 98 | */ |
| 99 | |
| 100 | $artistInfo = $artist=>var["artist_" || $artist]; |
| 101 | $profile = $artist=>var["profile_" || $artist]; |
| 102 | /* |
| 103 | ** How many BYTES have maecenas invested in a given artist |
| 104 | */ |
| 105 | $totalArtistInvestment = $artist=>{ |
| 106 | if (NOT is_valid_address($artist)) |
| 107 | bounce("artist address is invalid"); |
| 108 | $artistInf = $artistInfo($artist); |
| 109 | if (typeof($artistInf) == "boolean") |
| 110 | bounce("That artist does not accept maecenas"); |
| 111 | return 11511570 * $artistInf.supply * (1 + $artistInf.supply) * (1 + 2 * $artistInf.supply); |
| 112 | }; |
| 113 | /* |
| 114 | ** How many BYTES does the next share of the given artist costs |
| 115 | */ |
| 116 | $artistSharePrice = $artist=>{ |
| 117 | $artistInf = var["artist_" || $artist]; |
| 118 | if (typeof($artistInf) == "boolean") |
| 119 | bounce("That artist does not accept investments"); |
| 120 | return $_maecenasCurve($artistInf.supply); |
| 121 | }; |
| 122 | /* |
| 123 | ** How many BYTES does it take to buy $shares amount of the given artist shares |
| 124 | */ |
| 125 | $artistSharesPrice = ($artist, $shares)=>{ |
| 126 | if (NOT is_integer($shares)) |
| 127 | bounce("shares must be an integer"); |
| 128 | if ($shares <= 0) |
| 129 | bounce("shares must be a positive integer"); |
| 130 | if (NOT is_valid_address($artist)) |
| 131 | bounce("artist address is invalid"); |
| 132 | |
| 133 | $artistInf = $artistInfo($artist); |
| 134 | |
| 135 | if (typeof($artistInf) == "boolean") |
| 136 | bounce("that artist does not accept maecenas"); |
| 137 | |
| 138 | $x0 = $artistInf.supply; |
| 139 | $x1 = $artistInf.supply + $shares; |
| 140 | $upToFirstToken = 11511570 * $x0 * (1 + $x0) * (1 + 2 * $x0); |
| 141 | $upToTargetPurchase = 11511570 * $x1 * (1 + $x1) * (1 + 2 * $x1); |
| 142 | |
| 143 | return round($upToTargetPurchase - ($upToFirstToken OTHERWISE 0), 0); |
| 144 | }; |
| 145 | /* |
| 146 | ** How many SHARES of the given artist has the given maecenas |
| 147 | */ |
| 148 | $maecenasArtistShares = ($maecenas, $artist)=>{ |
| 149 | if (NOT is_valid_address($artist)) |
| 150 | bounce("artist address is invalid"); |
| 151 | if (NOT is_valid_address($maecenas)) |
| 152 | bounce("maecenas address is invalid"); |
| 153 | |
| 154 | $shares = var["maecenas_" || $maecenas || "_" || $artist]; |
| 155 | return $shares OTHERWISE 0; |
| 156 | }; |
| 157 | /* |
| 158 | ** How many BYTES have an investor accrued from the given artist |
| 159 | */ |
| 160 | $maecenasPnL = ($maecenas, $artist)=>{ |
| 161 | if (NOT is_valid_address($artist)) |
| 162 | bounce("artist address is invalid"); |
| 163 | if (NOT is_valid_address($maecenas)) |
| 164 | bounce("maecenas address is invalid"); |
| 165 | $artistInf = $artistInfo($artist); |
| 166 | return floor($artistInf.share * $maecenasArtistShares($maecenas) / $artistInf.supply, 0); |
| 167 | }; |
| 168 | }", |
| 169 | "messages": { |
| 170 | "cases": [ |
| 171 | { |
| 172 | "if": "{ |
| 173 | NOT var["owner"] |
| 174 | }", |
| 175 | "messages": [ |
| 176 | { |
| 177 | "app": "state", |
| 178 | "state": "{ |
| 179 | var["owner"] = "IUU43O7TS2TBYKAPGKUARDZHOTAE275A"; |
| 180 | var["locked"] = 0; |
| 181 | }" |
| 182 | } |
| 183 | ] |
| 184 | }, |
| 185 | { |
| 186 | "if": "{ |
| 187 | trigger.address == var["owner"] |
| 188 | AND (trigger.data["method"] == "mint" |
| 189 | OR trigger.data["method"] == "payout" |
| 190 | OR trigger.data["method"] == "approve" |
| 191 | OR trigger.data["method"] == "revoke" |
| 192 | OR trigger.data["method"] == "transferOwnership" |
| 193 | OR trigger.data["method"] == "verifyProfile") |
| 194 | }", |
| 195 | "messages": { |
| 196 | "cases": [ |
| 197 | { |
| 198 | "if": "{ |
| 199 | trigger.data["method"] == "mint" |
| 200 | }", |
| 201 | "init": "{ |
| 202 | if (NOT exists(trigger.data["amount"])) |
| 203 | bounce("amount field is mandatory"); |
| 204 | if (NOT is_valid_amount(trigger.data["amount"])) |
| 205 | bounce("The amount of NFT copies to mint is not valid"); |
| 206 | if (NOT exists(trigger.data["ipfs"])) |
| 207 | bounce("ipfs field is mandatory"); |
| 208 | if (NOT exists(trigger.data["seller"])) |
| 209 | bounce("seller field is mandatory"); |
| 210 | if (NOT is_valid_address(trigger.data["seller"])) |
| 211 | bounce("The seller address is not valid"); |
| 212 | if (NOT exists(trigger.data["endTime"])) |
| 213 | bounce("endTime field is mandatory"); |
| 214 | if (NOT is_integer(trigger.data["endTime"])) |
| 215 | bounce("endTime field is not a timestamp"); |
| 216 | if (trigger.data["endTime"] <= timestamp) |
| 217 | bounce("The endTime cannot be set in the past"); |
| 218 | if (trigger.data["endTime"] - timestamp > 2628000) |
| 219 | bounce("You cannot set the end time in more than 30 days in the future"); |
| 220 | }", |
| 221 | "messages": { |
| 222 | "cases": [ |
| 223 | { |
| 224 | "if": "{ |
| 225 | trigger.data["amount"] == 1 |
| 226 | }", |
| 227 | "messages": [ |
| 228 | { |
| 229 | "app": "asset", |
| 230 | "payload": { |
| 231 | "cap": 1, |
| 232 | "is_private": false, |
| 233 | "is_transferrable": true, |
| 234 | "auto_destroy": false, |
| 235 | "fixed_denominations": false, |
| 236 | "issued_by_definer_only": true, |
| 237 | "cosigned_by_definer": false, |
| 238 | "spender_attested": false |
| 239 | } |
| 240 | }, |
| 241 | { |
| 242 | "app": "state", |
| 243 | "state": "{ |
| 244 | var[response_unit] = { |
| 245 | bid: 20000, |
| 246 | by: trigger.data["seller"], |
| 247 | at: timestamp, |
| 248 | ipfs: trigger.data["ipfs"], |
| 249 | author: trigger.data["seller"], |
| 250 | soldAt: trigger.data["endTime"], |
| 251 | soldBy: trigger.data["seller"] |
| 252 | }; |
| 253 | }" |
| 254 | } |
| 255 | ] |
| 256 | }, |
| 257 | { |
| 258 | "init": "{ |
| 259 | if (NOT exists(trigger.data["price"])) |
| 260 | bounce("price field is mandatory if you intend to sell more than one NFT copy"); |
| 261 | if (trigger.data["price"] < 20000) |
| 262 | bounce("The minium price is 20000 bytes"); |
| 263 | if (NOT is_valid_amount(trigger.data["price"])) |
| 264 | bounce("price is not valid"); |
| 265 | }", |
| 266 | "messages": [ |
| 267 | { |
| 268 | "app": "asset", |
| 269 | "payload": { |
| 270 | "cap": "{trigger.data["amount"]}", |
| 271 | "is_private": false, |
| 272 | "is_transferrable": true, |
| 273 | "auto_destroy": false, |
| 274 | "fixed_denominations": false, |
| 275 | "issued_by_definer_only": true, |
| 276 | "cosigned_by_definer": false, |
| 277 | "spender_attested": false |
| 278 | } |
| 279 | }, |
| 280 | { |
| 281 | "app": "state", |
| 282 | "state": "{ |
| 283 | var[response_unit] = { |
| 284 | price: trigger.data["price"], |
| 285 | at: timestamp, |
| 286 | ipfs: trigger.data["ipfs"], |
| 287 | unitsSold: 0, |
| 288 | author: trigger.data["seller"], |
| 289 | soldAt: trigger.data["endTime"], |
| 290 | soldBy: trigger.data["seller"] |
| 291 | }; |
| 292 | }" |
| 293 | } |
| 294 | ] |
| 295 | } |
| 296 | ] |
| 297 | } |
| 298 | }, |
| 299 | { |
| 300 | "if": "{ |
| 301 | trigger.data["method"] == "payout" |
| 302 | }", |
| 303 | "init": "{ |
| 304 | $payout = balance["base"] - var["locked"] - storage_size - 200000; |
| 305 | if ($payout <= storage_size + 200000) |
| 306 | bounce("There are not enought funds to withdraw"); |
| 307 | }", |
| 308 | "messages": [ |
| 309 | { |
| 310 | "app": "payment", |
| 311 | "payload": { |
| 312 | "asset": "base", |
| 313 | "outputs": [ |
| 314 | { |
| 315 | "address": "{trigger.address}", |
| 316 | "amount": "{$payout}" |
| 317 | } |
| 318 | ] |
| 319 | } |
| 320 | } |
| 321 | ] |
| 322 | }, |
| 323 | { |
| 324 | "if": "{ |
| 325 | trigger.data["method"] == "approve" |
| 326 | }", |
| 327 | "init": "{ |
| 328 | if (NOT exists(trigger.data["NFT"])) |
| 329 | bounce("NFT field is mandatory"); |
| 330 | |
| 331 | $NFT = var[trigger.data["NFT"]]; |
| 332 | |
| 333 | if (typeof($NFT) == "boolean") |
| 334 | bounce("That NFT is not known by this AA"); |
| 335 | if ($NFT.pendingAproval == false) |
| 336 | bounce("That NFT is not pendingAproval"); |
| 337 | }", |
| 338 | "messages": [ |
| 339 | { |
| 340 | "app": "state", |
| 341 | "state": "{ |
| 342 | var[trigger.data["NFT"]] ||= {pendingAproval: false, duration: '', foreign: true, bid: 20000, at: timestamp, soldAt: timestamp + $NFT.duration}; |
| 343 | }" |
| 344 | } |
| 345 | ] |
| 346 | }, |
| 347 | { |
| 348 | "if": "{ |
| 349 | trigger.data["method"] == "verifyProfile" |
| 350 | }", |
| 351 | "init": "{ |
| 352 | if (NOT exists(trigger.data["artist"])) |
| 353 | bounce("artist field is mandatory"); |
| 354 | }", |
| 355 | "messages": [ |
| 356 | { |
| 357 | "app": "state", |
| 358 | "state": "{ |
| 359 | var["profile_" || trigger.data["artist"]] ||= {verified: true}; |
| 360 | }" |
| 361 | } |
| 362 | ] |
| 363 | }, |
| 364 | { |
| 365 | "if": "{ |
| 366 | trigger.data["method"] == "revoke" |
| 367 | }", |
| 368 | "init": "{ |
| 369 | if (NOT exists(trigger.data["NFT"])) |
| 370 | bounce("NFT field is mandatory"); |
| 371 | }", |
| 372 | "messages": [ |
| 373 | { |
| 374 | "app": "data_feed", |
| 375 | "payload": { |
| 376 | "{trigger.data["NFT"]}": "REVOKED" |
| 377 | } |
| 378 | } |
| 379 | ] |
| 380 | }, |
| 381 | { |
| 382 | "if": "{ |
| 383 | trigger.data["method"] == "transferOwnership" |
| 384 | }", |
| 385 | "init": "{ |
| 386 | if (NOT exists(trigger.data["newOwner"])) |
| 387 | bounce("newOwner field is mandatory"); |
| 388 | if (NOT is_valid_address(trigger.data["newOwner"])) |
| 389 | bounce("newOwner field is not a valid address"); |
| 390 | }", |
| 391 | "messages": [ |
| 392 | { |
| 393 | "app": "state", |
| 394 | "state": "{ |
| 395 | var["owner"] = trigger.data["newOwner"]; |
| 396 | }" |
| 397 | } |
| 398 | ] |
| 399 | } |
| 400 | ] |
| 401 | } |
| 402 | }, |
| 403 | { |
| 404 | "if": "{ |
| 405 | trigger.data["method"] == "BUY" |
| 406 | OR trigger.data["method"] == "DIVEST" |
| 407 | OR trigger.data["method"] == "CLAIM" |
| 408 | }", |
| 409 | "init": "{ |
| 410 | $NFT = exists(trigger.data["NFT"]) |
| 411 | ? $tokenInfo(trigger.data["NFT"]) |
| 412 | : false; |
| 413 | |
| 414 | $artist = trigger.data["method"] != "BUY" |
| 415 | ? $artistInfo($NFT.author) |
| 416 | : false; |
| 417 | |
| 418 | if (trigger.data["method"] == "BUY"){ |
| 419 | if (NOT exists(trigger.data["NFT"])) |
| 420 | bounce("NFT field is mandatory"); |
| 421 | |
| 422 | $isAuction = typeof($NFT.bid) == "number"; |
| 423 | |
| 424 | if ($isAuction) |
| 425 | $hasBidders = $NFT.soldBy != $NFT.by; |
| 426 | |
| 427 | if (timestamp > $NFT.soldAt){ |
| 428 | if ($isAuction){ |
| 429 | if ($hasBidders) |
| 430 | bounce("The auction is over"); |
| 431 | } |
| 432 | else{ |
| 433 | bounce("The sale is already over"); |
| 434 | } |
| 435 | } |
| 436 | } |
| 437 | |
| 438 | if (trigger.data["method"] == "DIVEST"){ |
| 439 | if (NOT exists(trigger.data["artist"])) |
| 440 | bounce("artist field is mandatory"); |
| 441 | if (NOT is_valid_address(trigger.data["artist"])) |
| 442 | bounce("artist's address is invalid"); |
| 443 | |
| 444 | if (typeof($artist) == "boolean") |
| 445 | bounce("That artist never accepted maecenas"); |
| 446 | |
| 447 | if (NOT exists(trigger.data["amount"])) |
| 448 | bounce("amount field is mandatory"); |
| 449 | if (NOT is_integer(trigger.data["amount"]) OR trigger.data["amount"] <= 0) |
| 450 | bounce("amount must be an integer > 0"); |
| 451 | |
| 452 | $investedAmount = var["maecenas_" || trigger.address || "_" || trigger.data["artist"]]; |
| 453 | |
| 454 | if ($investedAmount == false) |
| 455 | bounce("You have no shares of that artist"); |
| 456 | |
| 457 | if (trigger.data["amount"] > ($investedAmount OTHERWISE 0)) |
| 458 | bounce("You only have " || $investedAmount || " shares of that artist"); |
| 459 | |
| 460 | $yourShare = floor($artist.share * trigger.data["amount"] / $artist.supply, 0); |
| 461 | |
| 462 | if ($yourShare < 20000) |
| 463 | bounce("It is not worth divesting your share"); |
| 464 | } |
| 465 | |
| 466 | if (trigger.data["method"] == "CLAIM"){ |
| 467 | if (NOT exists(trigger.data["NFT"])) |
| 468 | bounce("NFT field is mandatory"); |
| 469 | |
| 470 | if (timestamp <= $NFT.soldAt) |
| 471 | bounce("The auction is not over yet"); |
| 472 | |
| 473 | if ($NFT.bid == 0 AND typeof($NFT.bid) == "number") |
| 474 | bounce("That auction was already claimed"); |
| 475 | } |
| 476 | |
| 477 | |
| 478 | $allowsMaecenas = typeof($artist) == "object"; |
| 479 | $hasMaecenas = (typeof($artist) == "object" AND $artist.supply > 0); |
| 480 | |
| 481 | if ($allowsMaecenas){ |
| 482 | $maecenasShare = trigger.data["method"] != "DIVEST" |
| 483 | ? $artist.sharePercent * $NFT.price |
| 484 | : 0; |
| 485 | $sharesInThePool = (trigger.data["method"] != "DIVEST") |
| 486 | ? ($artist.supply) |
| 487 | : ($artist.supply - trigger.data["amount"]); |
| 488 | $p = (trigger.data["method"] != "DIVEST") |
| 489 | ? ($artist.share + $maecenasShare) |
| 490 | : ($artist.share - floor((trigger.data["amount"] / $artist.supply) * $artist.share, 0)); |
| 491 | $k = 1 / ($sharesInThePool + 1); |
| 492 | $price = $_maecenasCurve($sharesInThePool + 1); |
| 493 | |
| 494 | if ($k==1){ |
| 495 | $sharesToBuy = ($p > $price) ? 1: 0; |
| 496 | } |
| 497 | else{ |
| 498 | $ln1lessk = ln(1 - $k); |
| 499 | $logArgument = ($k * $p * $ln1lessk) / ($k - 1); |
| 500 | |
| 501 | $lambertW = $_lambertW($logArgument); |
| 502 | |
| 503 | if ($logArgument <= e) |
| 504 | $sharesToBuy = 0; |
| 505 | |
| 506 | else { |
| 507 | $s = (-1 * $lambertW) - $ln1lessk / ($ln1lessk); |
| 508 | $sharesToBuy = abs(floor($s, 0)); |
| 509 | } |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | $buyShares = ($allowsMaecenas AND $sharesToBuy > 0); |
| 514 | $myProfit = $buyShares ? ($sharesToBuy / ($artist.supply + $sharesToBuy))^$sharesToBuy * $p : 0; |
| 515 | }", |
| 516 | "messages": { |
| 517 | "cases": [ |
| 518 | { |
| 519 | "if": "{ |
| 520 | trigger.data["method"] == "BUY" |
| 521 | }", |
| 522 | "messages": { |
| 523 | "cases": [ |
| 524 | { |
| 525 | "if": "{ |
| 526 | $isAuction |
| 527 | }", |
| 528 | "init": "{ |
| 529 | if (trigger.output[[asset=base]].amount < 20000) |
| 530 | bounce("The minimum bid is 20000 bytes"); |
| 531 | if (trigger.output[[asset=base]].amount <= $NFT.bid) |
| 532 | bounce("Your bid is lower or equal to the last bid. You must increase it"); |
| 533 | if ($NFT.soldBy == trigger.address) |
| 534 | bounce("You cannot bid on your own auction"); |
| 535 | }", |
| 536 | "messages": [ |
| 537 | { |
| 538 | "app": "payment", |
| 539 | "if": "{ |
| 540 | $NFT.by != $NFT.soldBy |
| 541 | }", |
| 542 | "payload": { |
| 543 | "asset": "base", |
| 544 | "outputs": [ |
| 545 | { |
| 546 | "address": "{$NFT.by}", |
| 547 | "amount": "{$NFT.bid - 10000}" |
| 548 | } |
| 549 | ] |
| 550 | } |
| 551 | }, |
| 552 | { |
| 553 | "app": "state", |
| 554 | "state": "{ |
| 555 | var["locked"] += trigger.output[[asset=base]].amount - ($hasBidders ? $NFT.bid : 0); |
| 556 | $NFT.bid = trigger.output[[asset=base]].amount; |
| 557 | $NFT.by = trigger.address; |
| 558 | var[trigger.data["NFT"]] ||= $stripToken($extendDeadLine($NFT)); |
| 559 | }" |
| 560 | } |
| 561 | ] |
| 562 | }, |
| 563 | { |
| 564 | "init": "{ |
| 565 | if ($NFT.unitsSold == $NFT.cap) |
| 566 | bounce("All NFT copies have been already sold"); |
| 567 | if (trigger.output[[asset=base]].amount < $NFT.price) |
| 568 | bounce("Your payment is lower than the NFT price. You have to send " || $NFT.price || " bytes"); |
| 569 | |
| 570 | |
| 571 | |
| 572 | |
| 573 | |
| 574 | }", |
| 575 | "messages": [ |
| 576 | { |
| 577 | "app": "payment", |
| 578 | "if": "{ |
| 579 | floor($NFT.price * (1 - $HOUSE_CUT - ($hasMaecenas ? $artist.sharePercent : 0)) - 10000, 0) > 0 |
| 580 | }", |
| 581 | "payload": { |
| 582 | "asset": "base", |
| 583 | "outputs": [ |
| 584 | { |
| 585 | "address": "{$NFT.soldBy}", |
| 586 | "amount": "{floor($NFT.price * (1 - $HOUSE_CUT - ($hasMaecenas ? $artist.sharePercent : 0)) - 10000, 0)}" |
| 587 | } |
| 588 | ] |
| 589 | } |
| 590 | }, |
| 591 | { |
| 592 | "app": "payment", |
| 593 | "payload": { |
| 594 | "asset": "{trigger.data['NFT']}", |
| 595 | "outputs": [ |
| 596 | { |
| 597 | "address": "{trigger.address}", |
| 598 | "amount": 1 |
| 599 | } |
| 600 | ] |
| 601 | } |
| 602 | }, |
| 603 | { |
| 604 | "app": "state", |
| 605 | "state": "{ |
| 606 | var[trigger.data["NFT"]] ||= {unitsSold: $NFT.unitsSold + 1}; |
| 607 | if ($hasMaecenas){ |
| 608 | $increment = floor($NFT.price * (1 - $artist.sharePercent), 0); |
| 609 | var["locked"] += $increment; |
| 610 | var["artist_" || $NFT.author] ||= {share: $artist.share + $increment}; |
| 611 | } |
| 612 | }" |
| 613 | } |
| 614 | ] |
| 615 | } |
| 616 | ] |
| 617 | } |
| 618 | }, |
| 619 | { |
| 620 | "if": "{ |
| 621 | trigger.data["method"] == "CLAIM" |
| 622 | }", |
| 623 | "messages": { |
| 624 | "cases": [ |
| 625 | { |
| 626 | "if": "{ |
| 627 | $NFT.soldBy == $NFT.author |
| 628 | }", |
| 629 | "messages": [ |
| 630 | { |
| 631 | "app": "payment", |
| 632 | "if": "{ |
| 633 | floor($NFT.bid * (1 - $HOUSE_CUT - $hasMaecenas ? $artist.sharePercent : 0) - 10000 + ($buyShares ? $artistSharePrice($NFT.author) * $sharesToBuy : 0), 0) > 0 |
| 634 | AND $NFT.soldBy != $NFT.by |
| 635 | }", |
| 636 | "payload": { |
| 637 | "asset": "base", |
| 638 | "outputs": [ |
| 639 | { |
| 640 | "address": "{$NFT.soldBy}", |
| 641 | "amount": "{floor($NFT.bid * (1 - $HOUSE_CUT - ($allowsMaecenas ? (($hasMaecenas OR $buyShares) ? $artist.sharePercent : 0) : 0)) - 10000 + ($buyShares ? $artistSharePrice($NFT.author) * $sharesToBuy : 0), 0)}" |
| 642 | } |
| 643 | ] |
| 644 | } |
| 645 | }, |
| 646 | { |
| 647 | "app": "payment", |
| 648 | "payload": { |
| 649 | "asset": "{trigger.data['NFT']}", |
| 650 | "outputs": [ |
| 651 | { |
| 652 | "address": "{$NFT.by}", |
| 653 | "amount": 1 |
| 654 | } |
| 655 | ] |
| 656 | } |
| 657 | }, |
| 658 | { |
| 659 | "app": "state", |
| 660 | "state": "{ |
| 661 | $decrement = $hasMaecenas |
| 662 | ? $NFT.bid |
| 663 | : $NFT.bid - $maecenasShare; |
| 664 | |
| 665 | var["locked"] -= $decrement; |
| 666 | var[trigger.data["NFT"]] ||= {bid: 0, by: ''}; |
| 667 | |
| 668 | if ($hasMaecenas) |
| 669 | var["artist_" || $NFT.author] ||= {share: $artist.share + $maecenasShare}; |
| 670 | }" |
| 671 | } |
| 672 | ] |
| 673 | }, |
| 674 | { |
| 675 | "messages": [ |
| 676 | { |
| 677 | "app": "payment", |
| 678 | "if": "{ |
| 679 | $NFT.soldBy != $NFT.by |
| 680 | }", |
| 681 | "payload": { |
| 682 | "asset": "base", |
| 683 | "outputs": { |
| 684 | "cases": [ |
| 685 | { |
| 686 | "if": "{ |
| 687 | floor($NFT.bid * $ARTIST_CUT, 0) - 10000 > 0 |
| 688 | }", |
| 689 | "outputs": [ |
| 690 | { |
| 691 | "address": "{$NFT.soldBy}", |
| 692 | "amount": "{floor($NFT.bid * (1 - $ARTIST_CUT) - 10000, 0)}" |
| 693 | }, |
| 694 | { |
| 695 | "address": "{$NFT.author}", |
| 696 | "amount": "{floor($NFT.bid * $ARTIST_CUT, 0) - 10000}" |
| 697 | } |
| 698 | ] |
| 699 | }, |
| 700 | { |
| 701 | "outputs": [ |
| 702 | { |
| 703 | "address": "{$NFT.soldBy}", |
| 704 | "amount": "{floor($NFT.bid * (1 - $ARTIST_CUT) - 10000, 0)}" |
| 705 | } |
| 706 | ] |
| 707 | } |
| 708 | ] |
| 709 | } |
| 710 | } |
| 711 | }, |
| 712 | { |
| 713 | "app": "payment", |
| 714 | "payload": { |
| 715 | "asset": "{trigger.data['NFT']}", |
| 716 | "outputs": [ |
| 717 | { |
| 718 | "address": "{$NFT.by}", |
| 719 | "amount": 1 |
| 720 | } |
| 721 | ] |
| 722 | } |
| 723 | }, |
| 724 | { |
| 725 | "app": "state", |
| 726 | "state": "{ |
| 727 | var["locked"] -= $NFT.bid; |
| 728 | }" |
| 729 | } |
| 730 | ] |
| 731 | } |
| 732 | ] |
| 733 | } |
| 734 | }, |
| 735 | { |
| 736 | "if": "{ |
| 737 | trigger.data["method"] == "DIVEST" |
| 738 | }", |
| 739 | "messages": [ |
| 740 | { |
| 741 | "app": "payment", |
| 742 | "payload": { |
| 743 | "asset": "base", |
| 744 | "outputs": [ |
| 745 | { |
| 746 | "address": "{trigger.address}", |
| 747 | "amount": "{$yourShare - 10000}" |
| 748 | } |
| 749 | ] |
| 750 | } |
| 751 | }, |
| 752 | { |
| 753 | "app": "state", |
| 754 | "state": "{ |
| 755 | var["artist_" || trigger.data["artist"]] ||= { |
| 756 | share: $artist.share - $yourShare, |
| 757 | supply: $artist.supply - trigger.data["amount"] |
| 758 | }; |
| 759 | |
| 760 | var["maecenas_" || trigger.address || "_" || trigger.data["artist"]] -= trigger.data["amount"]; |
| 761 | var["locked"] -= ($yourShare + $myProfit); |
| 762 | }" |
| 763 | } |
| 764 | ] |
| 765 | } |
| 766 | ] |
| 767 | } |
| 768 | }, |
| 769 | { |
| 770 | "if": "{ |
| 771 | trigger.data["method"] == "SELL" |
| 772 | }", |
| 773 | "init": "{ |
| 774 | |
| 775 | if (NOT exists(trigger.data["endTime"])) |
| 776 | bounce("endTime field is mandatory"); |
| 777 | if (trigger.data["endTime"] < timestamp) |
| 778 | bounce("You cannot set the end time in the past"); |
| 779 | if (trigger.data["endTime"] - timestamp > 2628000) |
| 780 | bounce("You cannot set the end time in more than 30 days"); |
| 781 | |
| 782 | if (NOT exists(trigger.data["initialPrice"])) |
| 783 | bounce("initialPrice field is mandatory"); |
| 784 | if (NOT is_valid_amount(trigger.data["initialPrice"])) |
| 785 | bounce("initialPrice must be a valid amount"); |
| 786 | if (trigger.data["initialPrice"] < 20000) |
| 787 | bounce("The minimum initialPrice is 20000 bytes"); |
| 788 | |
| 789 | |
| 790 | if (trigger.output[[asset!="base"]].asset == "ambigous") |
| 791 | bounce("You cannot send more than one NFT type at a time"); |
| 792 | if (typeof(var[trigger.output[[asset!="base"]].asset]) == "boolean") |
| 793 | bounce("You need to send an actual NFT but you are sending only bytes"); |
| 794 | |
| 795 | $feedData = data_feed[[oracles=this_address, feed_name=trigger.output[[asset!="base"]].asset, ifnone=false, ifseveral="last", type="string"]]; |
| 796 | |
| 797 | $NFT = var[trigger.output[[asset!="base"]].asset] || {isBanned: (typeof($feedData) == "string") AND ($feedData == "REVOKED")}; |
| 798 | |
| 799 | if (trigger.output[[asset!="base"]].amount != 1) |
| 800 | bounce("You cannot auction more than 1 copy of an NFT at a time"); |
| 801 | if ($NFT.isBanned) |
| 802 | bounce("We revoked the trading of that token probably due to copyright reasons"); |
| 803 | |
| 804 | if (timestamp < $NFT.soldAt) |
| 805 | bounce("A copy of that NFT is already being auctioned. The auction is expected to end at " || timestamp_to_string($NFT.soldAt) || " UTC but may be delayed depending on the number of bids"); |
| 806 | else if ($NFT.bid AND typeof($NFT.bid) == "number" AND $NFT.bid != 0) |
| 807 | bounce("A copy of that NFT is already being auctioned. The auction has already ended but it has not been claimed. You can close it yourself even if you are not the max bidder"); |
| 808 | }", |
| 809 | "messages": [ |
| 810 | { |
| 811 | "app": "state", |
| 812 | "state": "{ |
| 813 | $NFT.bid = trigger.data["initialPrice"]; |
| 814 | $NFT.by = trigger.address; |
| 815 | $NFT.soldBy = trigger.address; |
| 816 | $NFT.at = timestamp; |
| 817 | $NFT.soldAt = trigger.data["endTime"]; |
| 818 | var[trigger.output[[asset!="base"]].asset] ||= $stripToken($NFT); |
| 819 | }" |
| 820 | } |
| 821 | ] |
| 822 | }, |
| 823 | { |
| 824 | "if": "{ |
| 825 | trigger.data["method"] == "PRELIST" |
| 826 | }", |
| 827 | "init": "{ |
| 828 | $ass = asset[trigger.data["NFT"]].exists; |
| 829 | if (NOT exists(trigger.data["NFT"])) |
| 830 | bounce("NFT field is mandatory"); |
| 831 | if (NOT $ass.exists) |
| 832 | bounce("That NFT does not exist within the Obyte network"); |
| 833 | if (NOT $ass.is_transferrable) |
| 834 | bounce("That NFT is not tradeable"); |
| 835 | if (NOT exists(trigger.data["ipfs"])) |
| 836 | bounce("ipfs field is mandatory"); |
| 837 | if (NOT exists(trigger.data["seller"])) |
| 838 | bounce("seller field is mandatory"); |
| 839 | if (NOT is_valid_address(trigger.data["seller"])) |
| 840 | bounce("The seller address is not valid"); |
| 841 | if (NOT exists(trigger.data["duration"])) |
| 842 | bounce("duration field is mandatory. Enter the auction duration in seconds"); |
| 843 | if (NOT is_integer(trigger.data["duration"])) |
| 844 | bounce("duration field is not a valid amount of seconds"); |
| 845 | if (trigger.data["duration"] > 2628000) |
| 846 | bounce("The auction cannot last more than 30 days"); |
| 847 | }", |
| 848 | "messages": [ |
| 849 | { |
| 850 | "app": "state", |
| 851 | "state": "{ |
| 852 | var[trigger.data["NFT"]] = { |
| 853 | ipfs: trigger.data["ipfs"], |
| 854 | author: 'foreign', |
| 855 | soldBy: trigger.data["seller"], |
| 856 | duration: trigger.data["duration"] |
| 857 | }; |
| 858 | }" |
| 859 | } |
| 860 | ] |
| 861 | }, |
| 862 | { |
| 863 | "if": "{ |
| 864 | trigger.data["method"] == "ENABLE_INVESTING" |
| 865 | }", |
| 866 | "init": "{ |
| 867 | if (var["artist_" || trigger.address]) |
| 868 | bounce("You are already accepting maecenas"); |
| 869 | if (NOT exists(trigger.data["share"])) |
| 870 | bounce("share field is mandatory"); |
| 871 | if (NOT is_integer(trigger.data["share"]) OR trigger.data["share"] < 1 OR trigger.data["share"] > 50) |
| 872 | bounce("share must be an integer between 1 - 50"); |
| 873 | }", |
| 874 | "messages": [ |
| 875 | { |
| 876 | "app": "state", |
| 877 | "state": "{ |
| 878 | var["artist_" || trigger.address] = { |
| 879 | supply: 0, |
| 880 | share: 0, |
| 881 | sharePercent: trigger.data["share"] / 100 |
| 882 | }; |
| 883 | }" |
| 884 | } |
| 885 | ] |
| 886 | }, |
| 887 | { |
| 888 | "if": "{ |
| 889 | trigger.data["method"] == "INVEST" |
| 890 | }", |
| 891 | "init": "{ |
| 892 | if (NOT exists(trigger.data["artist"])) |
| 893 | bounce("artist field is mandatory"); |
| 894 | if (NOT is_valid_address(trigger.data["artist"])) |
| 895 | bounce("artist field is not a valid address"); |
| 896 | if (NOT var["artist_" || trigger.data["artist"]]) |
| 897 | bounce("That artist is not accepting maecenas"); |
| 898 | if (NOT exists(trigger.data["amount"])) |
| 899 | bounce("amount field is mandatory"); |
| 900 | if (NOT is_integer(trigger.data["amount"]) OR trigger.data["amount"] <= 0) |
| 901 | bounce("amount must be a positive integer"); |
| 902 | |
| 903 | $artist = $artistInfo(trigger.data["artist"]); |
| 904 | |
| 905 | $x0 = $artist.supply; |
| 906 | $x1 = $artist.supply + trigger.data["amount"]; |
| 907 | |
| 908 | $upToFirstToken = 11511570 * $x0 * (1 + $x0) * (1 + 2 * $x0); |
| 909 | $upToTargetPurchase = 11511570 * $x1 * (1 + $x1) * (1 + 2 * $x1); |
| 910 | |
| 911 | $totalCost = round($upToTargetPurchase - ($upToFirstToken OTHERWISE 0), 0); |
| 912 | |
| 913 | if (trigger.output[[asset="base"]].amount < $totalCost) |
| 914 | bounce("Your payment is not enough for that purchase. You need to send " || $totalCost || " bytes" || (trigger.data["amount"]>1 ? " or reduce the amount of maecenas tokens you are buying" : "")); |
| 915 | }", |
| 916 | "messages": [ |
| 917 | { |
| 918 | "app": "payment", |
| 919 | "payload": { |
| 920 | "asset": "base", |
| 921 | "outputs": [ |
| 922 | { |
| 923 | "address": "{trigger.data["artist"]}", |
| 924 | "amount": "{floor($totalCost * (1 - $INVESTMENT_CUT) - 10000, 0)}" |
| 925 | } |
| 926 | ] |
| 927 | } |
| 928 | }, |
| 929 | { |
| 930 | "app": "state", |
| 931 | "state": "{ |
| 932 | var["artist_" || trigger.data["artist"]] ||= {supply: $artist.supply + trigger.data["amount"]}; |
| 933 | |
| 934 | if (var["maecenas_" || trigger.address || "_" || trigger.data["artist"]]) |
| 935 | var["maecenas_" || trigger.address || "_" || trigger.data["artist"]] += trigger.data["amount"]; |
| 936 | else |
| 937 | var["maecenas_" || trigger.address || "_" || trigger.data["artist"]] = trigger.data["amount"]; |
| 938 | }" |
| 939 | } |
| 940 | ] |
| 941 | }, |
| 942 | { |
| 943 | "if": "{ |
| 944 | trigger.data["method"] == "PROFILE" |
| 945 | }", |
| 946 | "init": "{ |
| 947 | $prof = $profile(trigger.address); |
| 948 | |
| 949 | if ($prof.verified) |
| 950 | bounce("You have to contact support to modify a verified profile. We do this to prevent already verified accounts from impersonating other artists"); |
| 951 | |
| 952 | $profileObj = $prof || trigger.data || {verified: false}; |
| 953 | delete($profileObj, "method"); |
| 954 | }", |
| 955 | "messages": [ |
| 956 | { |
| 957 | "app": "state", |
| 958 | "state": "{ |
| 959 | var["profile_" || trigger.address] = $profileObj; |
| 960 | }" |
| 961 | } |
| 962 | ] |
| 963 | }, |
| 964 | { |
| 965 | "if": "{ |
| 966 | trigger.data["method"] == "SET_META" |
| 967 | }", |
| 968 | "init": "{ |
| 969 | if (NOT exists(trigger.data["NFT"])) |
| 970 | bounce("NFT field is mandatory"); |
| 971 | $NFT = var[trigger.data["NFT"]]; |
| 972 | if ($NFT.author != trigger.address) |
| 973 | bounce("You can only set the metadata of NFTs created by yourself"); |
| 974 | }", |
| 975 | "messages": [ |
| 976 | { |
| 977 | "app": "state", |
| 978 | "state": "{ |
| 979 | var["meta_" || trigger.data["NFT"]] = trigger.data; |
| 980 | }" |
| 981 | } |
| 982 | ] |
| 983 | } |
| 984 | ] |
| 985 | } |
| 986 | } |
| 987 | ] |