[
"autonomous agent",
{
"bounce_fees": {
"base": 10000
},
"init": "{
$HOUSE_CUT = var["FEE"]; //We will take this cut on the debut of an NFT in our platform. We charge 0.1% for foreign NFTs.
$FREEMARKET_CUT = 0.01; //We take 1% on every non-verified NFT trade
$ARTIST_CUT = 0.01; //The artist will get 1% from every resale of their NFT
$INVESTMENT_CUT = 0.01; //We take a 1% cut from all maecenas investments
$HALF_HOUR = 1800; //30m = 1800s. An auction is extended by 1800s every time there is a new bid near the deadline
$RESERVE_AMOUNT = 200000; //We will always keep > 200.000 in the AA
$MIN_BID_INCREMENT = 0.0001;//Bids must be at least 0.01% higher than the previous one
$ONE_MONTH = 2592000;
$USER_REGISTRY_ADDRESS = "
S6A3QQN7VMWKJF5R5QTKBEHAFNABHXTP";
$CURRENCY_REGISTRY = "
AO46ELAIDT2LX2RXVVABIABXNBKHRSCE";
$extendDeadLine = $NFT=>($NFT.soldAt <= timestamp + $HALF_HOUR) ? ($NFT || {soldAt: $NFT.soldAt + $HALF_HOUR}) : $NFT; //Extend the auction if there is a bid less than half an hour before the auction is closed
/*
** Strips $NFT of computed fields to reduce AA's storage size
*/
$stripToken = $NFT=>{
delete($NFT, "isBanned");
delete($NFT, "cap");
delete($NFT, "mintedAt");
return $NFT;
};
if (NOT trigger.data["method"])
bounce("method field is mandatory");
$method = trigger.data["method"];
$spendableFunds = balance["base"] - var["locked"] - storage_size - $RESERVE_AMOUNT;
$owner = var["owner"];
$calculateSharesToBuy = ($price, $pool, $myShare)=>ln($price / ($pool * $myShare)) / ln($myShare);
$_maecenasCurve = $x=>6969420 * $x^2;
}",
"getters": "{
$saleInfo = $NFT=>{
$unit = unit[$NFT];
$info = var["FREE_" || $NFT] OTHERWISE var["NFT_" || $NFT];
if (NOT $info)
bounce("That NFT is not for sale or does not exist");
return $info || {
cap: $unit.messages[[.app="asset"]]["payload"]["cap"],
mintedAt: $unit.timestamp
};
};
/*
** Example of a meta getter to enable platform interop
*/
$getNFTInfo = $NFT=>{
$unit = unit[$NFT];
/*
** Info should have at least 'author' field. 'ipfs' and 'type' field are also recommended.
*/
$info = var["FREE_" || $NFT] OTHERWISE var["NFT_" || $NFT];
$transferrable = $unit.messages[[.app="asset"]]["payload"]["transferrable"];
return $info || {
cap: $unit.messages[[.app="asset"]]["payload"]["cap"],
mintedAt: $unit.timestamp,
transferrable: $transferrable
};
};
}",
"messages": {
"cases": [
{
"if": "{
NOT $owner
}",
"messages": [
{
"app": "state",
"state": "{
var["owner"] = "
IUU43O7TS2TBYKAPGKUARDZHOTAE275A"; //Set the Owner to my address
var["helper"] = "
IUU43O7TS2TBYKAPGKUARDZHOTAE275A";
var["locked"] = 0; //Bytes locked in bids are not withdrawable by the Owner
var["FEE"] = 0.1; //10% first sale fee
}"
}
]
},
{
"if": "{
trigger.address == $owner
AND ($method == "payout"
OR $method == "transferOwnership"
OR $method == "reduceFee"
OR $method == "setHelper")
}",
"messages": {
"cases": [
{
"if": "{
$method == "payout"
}",
"messages": [
{
"app": "payment",
"payload": {
"asset": "base",
"outputs": [
{
"address": "{trigger.address}",
"amount": "{$spendableFunds}"
}
]
}
}
]
},
{
"if": "{
$method == "transferOwnership"
}",
"init": "{
if (NOT trigger.data["newOwner"])
bounce("newOwner field is mandatory");
if (NOT is_valid_address(trigger.data["newOwner"]))
bounce("newOwner field is not a valid address");
}",
"messages": [
{
"app": "payment",
"payload": {
"asset": "base",
"outputs": [
{
"address": "{trigger.address}",
"amount": "{$spendableFunds}"
}
]
}
},
{
"app": "state",
"state": "{
var["owner"] = trigger.data["newOwner"];//Ownership is transferred
}"
}
]
},
{
"if": "{
$method == "setHelper"
}",
"init": "{
if (NOT trigger.data["helper"])
bounce("helper field is mandatory");
}",
"messages": [
{
"app": "state",
"state": "{
var["helper"] = trigger.data["helper"];
}"
}
]
},
{
"if": "{
$method == "reduceFee"
}",
"init": "{
if (NOT exists(trigger.data["fee"]))
bounce("fee field is mandatory");
if (trigger.data["fee"] < 0)
bounce("Are you drunk?");
if (trigger.data["fee"] >= $HOUSE_CUT)
bounce("fee must be lower than the current one");
}",
"messages": [
{
"app": "state",
"state": "{
var["FEE"] = trigger.data["fee"];
}"
}
]
}
]
}
},
{
"if": "{
(trigger.address == $owner OR trigger.address == var["helper"])
AND $method == "revoke"
}",
"init": "{
if (NOT trigger.data["NFT"])
bounce("NFT field is mandatory");
}",
"messages": [
{
"app": "data_feed",
"payload": {
"{trigger.data["NFT"]}": "REVOKED"
}
}
]
},
{
"if": "{
$method == "MINT"
}",
"init": "{
if (NOT exists(trigger.data["amount"]))
bounce("amount field is mandatory");
if (NOT is_valid_amount(trigger.data["amount"]) AND trigger.data["amount"] != 0)
bounce("The amount of NFT copies to mint is not valid");
if (NOT exists(trigger.data["type"]))
bounce("type field is mandatory");
else if (trigger.data["type"] == "LIC")
bounce("You cannot issue licenses using the AA");
if (exists(trigger.data["endTime"])){
if (NOT is_integer(trigger.data["endTime"]))
bounce("endTime must be an integer");
if (trigger.data["endTime"] <= timestamp)
bounce("The endTime cannot be set in the past");
}
if ($owner != trigger.address){ //The owner reserves the right to post sales with longer deadlines
if (trigger.data["endTime"] - timestamp > 2628000) //A bit more than 30 days
bounce("You cannot set the end time in more than 30 days in the future");
}
if (NOT exists(trigger.data["title"]))
bounce("title field is mandatory");
if (NOT exists(trigger.data["ipfs"]))
bounce("ipfs field is mandatory");
if (exists(trigger.data["royalty"])){
if (NOT is_integer(trigger.data["royalty"]))
bounce("royalty must be an integer");
if (trigger.data["royalty"] < 0 OR trigger.data["royalty"] > 40)
bounce("royalty must be in the interval 0-40");
}
$seller = trigger.address == $owner
? trigger.data["seller"] OTHERWISE trigger.address
: trigger.address;
if (trigger.data["seller"] AND NOT is_valid_address(trigger.data["seller"])) //The owner has to specify who sells the NFT
bounce("The seller address is not valid");
$currency = $CURRENCY_REGISTRY.$getCurrency(trigger.data["currency"]);
if (exists(trigger.data["currency"])){
if (trigger.data["amount"] == 1)
bounce("You cannot price an auction in other than Bytes");
if (NOT $currency)
bounce("The provided currency is not supported");
}
$prof = $USER_REGISTRY_ADDRESS.$profileOf($seller);
$key = $prof.verified
? "NFT_"
: "FREE_";
}",
"messages": {
"cases": [
{
"if": "{
trigger.data["amount"] == 1
}",
"messages": [
{
"app": "asset",
"payload": {
"cap": 1,
"is_private": false,
"is_transferrable": true,
"auto_destroy": false,
"fixed_denominations": false,
"issued_by_definer_only": true,
"cosigned_by_definer": false,
"spender_attested": false
}
},
{
"app": "data",
"if": "{
var["recordToData"]
}",
"payload": {
"asset": "{var["recordToData"]}",
"name": "{trigger.data["title"] OTHERWISE "Untitled"}",
"author": "{$seller}",
"ipfs": "{trigger.data["ipfs"]}",
"type": "{trigger.data["type"]}",
"decimals": 0
}
},
{
"app": "state",
"state": "{
var[$key || response_unit] = {
bid: 20000, //Max current bid (a NFT can have this field because it was resold. Check for price field instead)
bids: 0,
type: trigger.data["type"],
by: $seller,
royalty: trigger.data["royalty"] OTHERWISE 0,
at: timestamp, //Max bid time
ipfs: trigger.data["ipfs"], //Hash of the content
author: $seller, //The original seller
soldAt: trigger.data["endTime"] OTHERWISE timestamp + $ONE_MONTH, //End of the auction
soldBy: $seller //Who is currently selling the NFT
};
var["recordToData"] = response_unit;
}"
}
]
},
{
"init": "{
$price = trigger.data["price"];
if (NOT $price)
bounce("price field is mandatory if you intend to sell more than one NFT copy");
if (NOT exists(trigger.data["currency"])){
if ($price < 20000)
bounce("The minium price is 20000 bytes");
if (NOT is_integer(trigger.data["price"]))
bounce("Price must be an integer");
if (NOT is_valid_amount($price)) //Must be integer for bytes
bounce("price is not valid");
}
}",
"messages": [
{
"app": "asset",
"if": "{
trigger.data["amount"] == 0 //Uncapped asset, no more units can be issued after endTime
}",
"payload": {
"is_private": false,
"is_transferrable": true,
"auto_destroy": false,
"fixed_denominations": false,
"issued_by_definer_only": true,
"cosigned_by_definer": false,
"spender_attested": false
}
},
{
"app": "asset",
"if": "{
trigger.data["amount"] > 0
}",
"payload": {
"cap": "{trigger.data["inflation"] ? trigger.data["cap"] OTHERWISE 9e15 : trigger.data["amount"]}",
"is_private": false,
"is_transferrable": true,
"auto_destroy": false,
"fixed_denominations": false,
"issued_by_definer_only": true,
"cosigned_by_definer": false,
"spender_attested": false
}
},
{
"app": "state",
"state": "{
var[$key || response_unit] = {
price: $price, //Sale price (this field is only present if the first sale was a fixed price one)
type: trigger.data["type"],
at: timestamp,
royalty: trigger.data["royalty"] OTHERWISE 0, //Max bid time
ipfs: trigger.data["ipfs"], //Hash of the content
unitsSold: 0,
author: $seller, //The original seller
soldAt: trigger.data["endTime"] OTHERWISE timestamp + $ONE_MONTH, //End of the auction
soldBy: $seller, //Who is currently selling the NFT
redeem: trigger.data["redeem"] ? true : false,
currency: trigger.data["currency"] OTHERWISE false
};
}"
}
]
}
]
}
},
{
"if": "{
$method == "BUY"
OR $method == "CLAIM"
}",
"init": "{
$NFT = $saleInfo(trigger.data["NFT"]);
$artist = $USER_REGISTRY_ADDRESS.$artistInfoOf($NFT.author);
if ($method == "BUY"){ //Validate buy params
if (NOT exists(trigger.data["NFT"]))
bounce("NFT field is mandatory");
$isAuction = $NFT.bid ? true : false; //It can only NOT be an auction if it is first sold and thus $NFT.bid does not exist
if ($isAuction)
$hasBidders = ($NFT.soldBy != $NFT.by) ? true : false; //Booleans cannot be compared
if ($isAuction AND NOT $NFT.by)
bounce("There are no copies of that NFT listed for sale at the moment");
if ($isAuction){
if (timestamp > $NFT.soldAt){
if ($isAuction){
if ($hasBidders) //You can still bid on an auction it if there were no bidders
bounce("The auction is over");
}
else{
bounce("The sale is already over");
}
}
}
}
else if ($method == "CLAIM"){ //Validate claim params
if (NOT trigger.data["NFT"])
bounce("NFT field is mandatory");
$isAuction = $NFT.bid ? true : false;
if (NOT $isAuction)
bounce("You cannot claim a non-auction sale");
$hasBidders = ($NFT.soldBy != $NFT.by) ? true : false;
if (timestamp <= $NFT.soldAt)
bounce("The auction is not over yet");
if ($NFT.claimed)
bounce("That auction was already claimed");
}
if ($artist AND (($isAuction AND $hasBidders) OR (NOT $isAuction))){
$allowsMaecenas = ($artist.sharePercent >= 0.01) ? true : false;
$hasMaecenas = $allowsMaecenas
? $artist.supply > 0
? true
: false
: false;
if ($allowsMaecenas){
if ($method == "BUY")
$maecenasShare = floor($artist.sharePercent * trigger.output[[asset="base"]].amount, 0);
else if ($method == "CLAIM" AND $hasBidders)
$maecenasShare = floor($artist.sharePercent * $NFT.bid, 0);
else
$maecenasShare = 0;
$sharesInThePool = ($method != "DIVEST")
? $artist.supply
: ($artist.supply - trigger.data["amount"]); //If divesting we need to substract divested shares
if ($method == "DIVEST")
$p = $artist.share - floor((trigger.data["amount"] / $artist.supply) * $artist.share, 0); //Current share minus what was just divested
else
$p = $artist.share + $maecenasShare; //Pool value after the sale
$k = 1 / ($sharesInThePool + 1); //The fraction of the pool 1 share represents
$price = $_maecenasCurve($sharesInThePool + 1); //Price of buying one share
if ($method == "BUY"){
if ($NFT.soldBy == $NFT.author){ //We do not need to check for auction as we do not invest in those until the claim phase
$totalSpendable = floor(
$spendableFunds
+ $price * $INVESTMENT_CUT //What I save from investment cut as it is paid to myself
+ floor(trigger.output[[asset="base"]].amount * $HOUSE_CUT, 0) //What I got as cut from the triggering tx
, 0);
}
else{
$totalSpendable = floor(
$spendableFunds
+ $price * $INVESTMENT_CUT //What I save from investment cut as it is paid to myself
- trigger.output[[asset="base"]].amount //Cannot spend the trigering tx as it will be locked
, 0);
}
}
else if ($method == "CLAIM"){
$totalSpendable = floor(
$spendableFunds //Initial amount
+ $price * $INVESTMENT_CUT //What I save from investment cut
+ $NFT.bid * (1 - $HOUSE_CUT) //What I get from house cut
, 0);
}
else{
$totalSpendable = floor($spendableFunds + $price * $INVESTMENT_CUT, 0);
}
$sharesToBuy = $sharesInThePool == 0 ? 0 : ceil($calculateSharesToBuy($price, $p, $k), 0);//Can be 0 and I would still afford 1 share if profitable
if ($k == 1){
$sharesICanAfford = $price < $totalSpendable ? 1 : 0;
}
else {
if ($totalSpendable > $price AND $sharesToBuy >= 1){
$sharesICanAfford = floor($totalSpendable / ($price * $sharesToBuy * (1 - $INVESTMENT_CUT)), 0); //Do not buy more shares than what I can afford
}
else{
$sharesICanAfford = 0;
}
}
}
if ($allowsMaecenas AND $sharesICanAfford > 0){
$buyShares = true;
}
else{
$buyShares = false;
}
$shareIWillfinallyBuy = min($sharesToBuy, $sharesICanAfford); //Maybe I can afford more shares than what is profitable to buy
$myProfit = floor($buyShares ? $p - $p * $k^$shareIWillfinallyBuy : 0, 0); //0 if no shares are bought
$poolFinalValue = $p - $myProfit; //Not actual profit but what I got out of the pool
}
else {
$allowsMaecenas = false;
$buyShares = false;
$hasMaecenas = false;
$maecenasShare = 0;
$myProfit = 0;
$poolFinalValue = 0;
}
}",
"messages": {
"cases": [
{
"if": "{
trigger.data["method"] == "BUY"
}",
"messages": {
"cases": [
{
"if": "{
$isAuction
}",
"init": "{
if (trigger.output[[asset="base"]].amount < 20000)
bounce("The minimum bid is 20000 bytes");
if (trigger.output[[asset="base"]].amount <= $NFT.bid * (1 + $MIN_BID_INCREMENT))
bounce("Your bid must be at least " || $NFT.bid * (1 + $MIN_BID_INCREMENT) || " (0.01% higher than the current)");
if ($NFT.soldBy == trigger.address)
bounce("You cannot bid on your own auction");
}",
"messages": [
{
"app": "payment",
"if": "{
$NFT.by != $NFT.soldBy //If we don't check for this the artists could steal bytes from the AA
}",
"payload": {
"asset": "base",
"outputs": [
{
"address": "{$NFT.by}",
"amount": "{$NFT.bid - 10000}"
}
]
}
},
{
"app": "state",
"state": "{
var["locked"] += trigger.output[[asset="base"]].amount - ($hasBidders ? $NFT.bid : 0); //Increase non-withdrawable funds by the difference between previous and last bid
$NFT.bid = trigger.output[[asset="base"]].amount;
$NFT.at = timestamp;
$NFT.bids = $NFT.bids + 1;
$NFT.by = trigger.address;
if (var["FREE_" || trigger.data["NFT"]])//We do not extend the deadline of freemarket NFTs
var["FREE_" || trigger.data["NFT"]] ||= $stripToken($NFT);
else
var["NFT_" || trigger.data["NFT"]] ||= $stripToken($extendDeadLine($NFT));
}"
}
]
},
{
"init": "{
if ($NFT.unitsSold == $NFT.cap AND $NFT.cap != 0) //We need to make an additional check for uncapped assets
bounce("All NFT copies have been already sold");
$realPrice = $NFT.currency ? $CURRENCY_REGISTRY.$convert($NFT.price, $NFT.currency) : $NFT.price;
response["realPrice"] = $realPrice;
if ($NFT.currency)
response["rate"] = $CURRENCY_REGISTRY.$getExchangeRate($NFT.currency);
if (trigger.output[[asset="base"]].amount < $realPrice)
bounce("Your payment is lower than the NFT price. You have to send " || $realPrice || " bytes");
}",
"messages": [
{
"app": "payment",
"if": "{
floor($realPrice * (1 - $HOUSE_CUT - ($allowsMaecenas ? (($hasMaecenas OR $buyShares) ? $artist.sharePercent : 0) : 0)) - 10000, 0) > 0
}",
"payload": {
"asset": "base",
"outputs": {
"cases": [
{
"if": "{
$hasMaecenas
}",
"outputs": [
{
"address": "{$NFT.soldBy}",
"amount": "{floor($realPrice * (1 - $HOUSE_CUT - ($allowsMaecenas ? (($hasMaecenas OR $buyShares) ? $artist.sharePercent : 0) : 0)) - 10000, 0)}"
},
{
"address": "{$USER_REGISTRY_ADDRESS}",
"amount": "{$poolFinalValue - $artist.share}"
}
]
},
{
"outputs": [
{
"address": "{$NFT.soldBy}",
"amount": "{floor($realPrice * (1 - $HOUSE_CUT - ($allowsMaecenas ? (($hasMaecenas OR $buyShares) ? $artist.sharePercent : 0) : 0)) - 10000, 0)}"
}
]
}
]
}
}
},
{
"app": "payment",
"payload": {
"asset": "{trigger.data['NFT']}",
"outputs": [
{
"address": "{trigger.address}",
"amount": 1
}
]
}
},
{
"if": "{
$hasMaecenas
}",
"app": "data",
"payload": {
"share": "{$poolFinalValue - $artist.share}"
}
},
{
"app": "state",
"state": "{
if ($NFT.soldBy == $NFT.author){ //Only increase the count if sold by the author
if (var["FREE_" || trigger.data["NFT"]])
var["FREE_" || trigger.data["NFT"]] ||= {unitsSold: $NFT.unitsSold + 1};
else
var["NFT_" || trigger.data["NFT"]] ||= {unitsSold: $NFT.unitsSold + 1};
//Check if all units were just sold
if ($NFT.unitsSold + 1 == $NFT.cap AND $NFT.cap != 0){
if (var["FREE_" || trigger.data["NFT"]])
var["FREE_" || trigger.data["NFT"]] ||= {bid: 0, price: false, soldAt: timestamp, currency: false}; //All units are sold, let's adjust soldAt so it can be already resold
else
var["NFT_" || trigger.data["NFT"]] ||= {bid: 0, price: false, soldAt: timestamp, currency: false}; //Prepare for auction and delist it from normal sale
}
}
//if ($hasMaecenas){
// var["locked"] += $maecenasShare - $myProfit;
// var["artist_" || $NFT.author] ||= {share: $poolFinalValue};
//}
}"
}
]
}
]
}
},
{
"if": "{
trigger.data["method"] == "CLAIM"
}",
"messages": {
"cases": [
{
"if": "{
$NFT.soldBy == $NFT.author
}",
"init": "{
if ($hasMaecenas OR $buyShares){
$paymentFromShares = $shareIWillfinallyBuy * round($price * (1 - $INVESTMENT_CUT), 0);
$sellerPayout = floor($NFT.bid * (1 - $HOUSE_CUT - $artist.sharePercent) - 10000 + $paymentFromShares, 0);
}
else {
$sellerPayout = floor($NFT.bid * (1 - $HOUSE_CUT) - 10000, 0);
}
}",
"messages": [
{
"app": "payment",
"if": "{
$sellerPayout > 0 //enough to be worth paying
AND $NFT.soldBy != $NFT.by //there is at least a bid
}",
"payload": {
"asset": "base",
"outputs": [
{
"address": "{$NFT.soldBy}",
"amount": "{$sellerPayout}"
}
]
}
},
{
"app": "payment",
"payload": {
"asset": "{trigger.data['NFT']}",
"outputs": [
{
"address": "{$NFT.by}",
"amount": 1
}
]
}
},
{
"if": "{
$hasMaecenas AND $NFT.bid > 20000 //If it does not have maecenas but accepts them pool is not increased
}",
"app": "data",
"payload": {
"share": "{$maecenasShare}"
}
},
{
"app": "state",
"state": "{
$decrement = $hasMaecenas //My share does not play a role here
? $NFT.bid - $maecenasShare //If it had maecenas the maecenas share remains locked
: $NFT.bid; //If it does not have maecenas all bytes are unlocked
if ($NFT.bid > 20000) //If it had no bids you don't need to reduce locked amount
var["locked"] -= $decrement; //These bytes are no longer locked as the artist is getting them in this same unit
if (var["FREE_" || trigger.data["NFT"]])
var["FREE_" || trigger.data["NFT"]] ||= {claimed: true};
else
var["NFT_" || trigger.data["NFT"]] ||= {claimed: true}; //No longer listed for sale
//if ($hasMaecenas AND $NFT.bid > 20000)
// var["artist_" || $NFT.author] ||= {share: $artist.share + $maecenasShare}; //Increase the artist pool value
}"
}
]
},
{
"messages": [
{
"app": "payment",
"if": "{
$NFT.soldBy != $NFT.by //If we don't do this check the seller could steal bytes from the AA if there were no bids
}",
"init": "{
$share = $NFT.bid * (1 - $HOUSE_CUT);
if ($NFT.royalty > 0 AND $NFT.soldBy != $NFT.by){
$royalty = $share * $NFT.royalty / 100;
$outputs = [
{//Royalty
address: $NFT.author,
amount: floor($royalty, 0) //Percentage of the whole bid, no fees are charged
},
{//Seller payment
address: $NFT.soldBy,
amount: floor($share - $royalty, 0) //We take the house cut then royalty cut
}
];
}
else{
$outputs = [
{//Seller payment
address: $NFT.soldBy,
amount: floor($share, 0)
}
];
}
$payload = {outputs: $outputs};
}",
"payload": "{$payload}"
},
{
"app": "payment",
"payload": {
"asset": "{trigger.data['NFT']}",
"outputs": [
{
"address": "{$NFT.by}",
"amount": 1
}
]
}
},
{
"app": "state",
"state": "{
var["locked"] -= $NFT.bid;
if (var["FREE_" || trigger.data["NFT"]])
var["FREE_" || trigger.data["NFT"]] ||= {claimed: true};
else
var["NFT_" || trigger.data["NFT"]] ||= {claimed: true}; //No longer listed for sale
}"
}
]
}
]
}
}
]
}
},
{
"if": "{
$method == "REDEEM"
}",
"init": "{
if (NOT exists(trigger.data["signed_message"]))
bounce("signed_message field is mandatory");
if (NOT exists(trigger.data["authors"]))
bounce('authors field is mandatory');
if (NOT exists(trigger.data["meta"]))
bounce("meta field is mandatory");
$spack = {
signed_message: trigger.data["signed_message"],
authors: trigger.data["authors"]
} || trigger.data["meta"];
if (NOT is_valid_signed_package($spack, $spack.authors[0]["address"]))
bounce("The signed message signature is invalid");
if (NOT $spack.signed_message["serial"])
bounce("The signed message does not contains a serial field");
if (NOT $spack.signed_message["NFT"])
bounce("The signer of this message did not include a NFT field");
//Check if the code has already been redeemed
if (var["CODE_" || $spack.signed_message["NFT"] || "_" || sha256($spack.signed_message["serial"])])
bounce("That code was already redeemed");
$NFT = $saleInfo($spack.signed_message["NFT"]);
if (NOT $NFT)
bounce("That NFT does not exist");
//Prevent author from stealing their NFTs from other people
if ($NFT.soldBy != $NFT.author)
bounce("You cannot claim an NFT that is not being sold by their author");
if ($NFT.author != $spack.authors[0]["address"])
bounce("The signer of that message is not the author of that NFT");
//NFTs have to be created with redeemable: true to be redeemable
if (NOT $NFT.redeem)
bounce("That NFT is not redeemable");
//Check if sale is over and expire was set in the signed message
if ($spack.signed_message["expireDate"])
if ($NFT.soldAt < timestamp OR $spack.signed_message["expireDate"] < timestamp)
bounce("That NFT is no longer claimable");
//Check if the NFT is address-locked
if ($spack.signed_message["claimer"])
if ($spack.signed_message["claimer"] != trigger.address)
bounce("You cannot claim that NFT from that address");
$signedAmount = $spack.signed_message["amount"] OTHERWISE 1;
//Uncapped asset, we can issue whatever amount was signed
if (NOT $NFT.cap){
$amount = $signedAmount;
}
//If it is going over the cap we need to reduce the claimed amount to match the asset cap
else {
if ($NFT.unitsSold + $signedAmount > $NFT.cap)
$amount = $NFT.cap - $NFT.unitsSold;
else
$amount = $signedAmount;
}
//All copies were issued
if ($amount == 0)
bounce("All copies of that NFT have been already minted");
}",
"messages": {
"cases": [
{
"if": "{
trigger.data["NFT"]
}",
"messages": [
{
"app": "state",
"state": "{
if (var["FREE_" || trigger.data["NFT"]])
var["FREE_" || trigger.data["NFT"]] ||= {lastBatchDate: timestamp};
else
var["NFT_" || trigger.data["NFT"]] ||= {lastBatchDate: timestamp};
}"
}
]
},
{
"messages": [
{
"app": "payment",
"payload": {
"asset": "{$spack.signed_message["NFT"]}",
"outputs": [
{
"address": "{trigger.address}",
"amount": "{$amount}"
}
]
}
},
{
"app": "state",
"state": "{
if (var["FREE_" || $spack.signed_message["NFT"]])
var["FREE_" || $spack.signed_message["NFT"]] ||= {unitsSold: $NFT.unitsSold + $amount};
else
var["NFT_" || $spack.signed_message["NFT"]] ||= {unitsSold: $NFT.unitsSold + $amount};
var["CODE_" || $spack.signed_message["NFT"] || "_" || sha256($spack.signed_message["serial"], 'base64')] = true; //Hash the serial to prevent the issuer from sending too big serials to bloat state size
}"
}
]
}
]
}
},
{
"if": "{
$method == "SELL"
}",
"init": "{
//Parameter validation
if (NOT exists(trigger.data["endTime"]))
bounce("endTime field is mandatory");
if (trigger.data["endTime"] < timestamp)
bounce("You cannot set the end time in the past");
if (trigger.data["endTime"] > timestamp + 2628000)
bounce("You cannot set the end time in more than 30 days");
if (NOT exists(trigger.data["initialPrice"]))
bounce("initialPrice field is mandatory");
if (NOT is_valid_amount(trigger.data["initialPrice"]))
bounce("initialPrice must be a valid amount");
if (trigger.data["initialPrice"] < 20000)
bounce("The minimum initialPrice is 20000 bytes");
//NFT validation
if (trigger.output[[asset!="base"]].asset == "ambigous")
bounce("You cannot send more than one NFT type at a time");
//Check that the NFT is not revoked
$feedData = data_feed[[oracles=this_address, feed_name=trigger.output[[asset!="base"]].asset, ifnone="OK", ifseveral="last", type="string"]];
if ($feedData == "REVOKED")
bounce("We revoked the trading of that token probably due to copyright reasons");
$NFT = $saleInfo(trigger.output[[asset!="base"]].asset) OTHERWISE {};
if (NOT $NFT)
bounce("NFT not found");
if (trigger.output[[asset!="base"]].amount != 1)
bounce("You cannot auction more than 1 copy of an NFT at a time");
if ($NFT.soldAt >= timestamp)//Booleans cannot be compared if NFT metadata is not present (user tried to sell an asset not issued by this AA)
bounce("A copy of that NFT is already being sold. The sale is expected to end at " || timestamp_to_string($NFT.soldAt) || " UTC but may be delayed depending on the number of bids");
if ($NFT.bid AND NOT $NFT.claimed)
bounce("That NFT was auctioned but the payment has not been claimed yet. You can force the seller to claim it.");
}",
"messages": [
{
"app": "state",
"state": "{
$NFT.bid = trigger.data["initialPrice"];
$NFT.bids = 0;
$NFT.by = trigger.address;
$NFT.soldBy = trigger.address;
$NFT.at = timestamp;
$NFT.claimed = false;
$NFT.soldAt = trigger.data["endTime"];
if (var["FREE_" || trigger.output[[asset!="base"]].asset])
var["FREE_" || trigger.output[[asset!="base"]].asset] = $stripToken($NFT) || {claimed: false};
else
var["NFT_" || trigger.output[[asset!="base"]].asset] = $stripToken($NFT) || {claimed: false};
}"
}
]
},
{
"if": "{
$method == "CHANGE_PRICE"
}",
"init": "{
if (NOT trigger.data["NFT"])
bounce("NFT field is mandatory");
$NFT = var["NFT_" || trigger.data["NFT"]];
if (NOT $NFT)
bounce("NFT not found");
if (NOT exists(trigger.data["price"]))
bounce("price field is mandatory");
if (NOT exists(trigger.data["currency"])){ //Priced in bytes, we cannot allow decimals
if (NOT is_integer(trigger.data["price"]))
bounce("price must be an integer");
if (NOT is_valid_amount(trigger.data["price"]))
bounce("price is invalid");
if (trigger.data["price"] < 20000)
bounce("The minimum price is 20000 bytes");
}
else{//User wants price to be foreign asset based, let's check if we support the asset
if (NOT $CURRENCY_REGISTRY.$getCurrency(trigger.data["currency"]))
bounce("You cannot set the price in that currency");
}
if ($NFT.bid)
bounce("You cannot change an auction price!");
}",
"messages": [
{
"app": "state",
"state": "{
var["NFT_" || trigger.data["NFT"]] ||= {price: trigger.data["price"], currency: trigger.data["currency"] OTHERWISE false};
}"
}
]
}
]
}
}
]