[
"autonomous agent",
{
"bounce_fees": {
"base": 10000
},
"init": "{
$HOUSE_CUT = var["FEE"]; //We will take this cut on every sale/auction.
$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 = "
2NEH2TEG725U3JGJOIE3FCAEGY5HGVFU";
$CURRENCY_REGISTRY = "
R4MRGA3MRU33EAWQFX4TBKT6INKWCTHH";
$MAX_ROYALTY = var[$USER_REGISTRY_ADDRESS]["maxRoyalty"];
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[$USER_REGISTRY_ADDRESS]["owner"];
if (NOT $owner)
bounce("The smart contract has not been initialized yet");
}",
"getters": "{
$saleInfo = ($NFT, $seller)=>{
$unit = unit[$NFT];
$info = var["FREE_" || $NFT || "_" || $seller] OTHERWISE var["NFT_" || $NFT || "_" || $seller];
if (NOT $info)
bounce("That NFT is not for sale or does not exist");
return $info;
};
$fetchNFT = $asset => {
$unitInfo = unit[$asset];
$parentUnit = unit[$unitInfo.parent_units[0]];
if (NOT $unitInfo.messages)
bounce("Bad unit");
$schema = $unitInfo.authors[0].authentifiers
? false
: $unitInfo.authors[0].address;
if (NOT $schema)
bounce("We can only accept NFTs defined with a schema");
$definedBy = $unitInfo.authors[0].authentifiers
? $unitInfo.authors[0].address //Is a person
: $parentUnit.authors[0].address; //Is a person or an AA (they can also define NFTs)
$metadata = $parentUnit.messages[[.app='data']].payload;
$isAccepted = var["SCHEMA_" || $schema];
if (NOT $isAccepted)
bounce("We do not accept NFTs with that schema");
$assetMetadata = $unitInfo.messages[[.app='asset']].payload;
return {asset: $assetMetadata, meta: $metadata, author: $definedBy};
};
}",
"messages": {
"cases": [
{
"if": "{NOT $HOUSE_CUT}",
"messages": [
{
"app": "state",
"state": "{
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 == "setSchema")
}",
"messages": {
"cases": [
{
"if": "{$method == "payout"}",
"messages": [
{
"app": "payment",
"payload": {
"asset": "base",
"outputs": [
{
"address": "{trigger.address}",
"amount": "{$spendableFunds}"
}
]
}
}
]
},
{
"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": "{$method == "setSchema"}",
"init": "{
if (NOT trigger.data["schema"])
bounce("schema field is mandatory");
if (NOT is_aa(trigger.data["schema"]))
bounce("schema is not the address of an autonomous agent");
}",
"messages": [
{
"app": "state",
"state": "{
var["SCHEMA_" || trigger.data["schema"]] = true;
}"
}
]
}
]
}
},
{
"if": "{$method == "BUY"}",
"init": "{
if (NOT trigger.data["address"])
bounce("address field is mandatory");
if (NOT is_valid_address(trigger.data["address"]))
bounce("address field is an invalid address");
if (NOT trigger.data["NFT"])
bounce("NFT field is mandatory");
if (trigger.address == trigger.data["address"])
bounce("You are buying your own NFT. Claim it instead in order to avoid fees");
$sale = $saleInfo(trigger.data["NFT"], trigger.data["address"]);
$nft = $fetchNFT(trigger.data["NFT"]);
$artist = $USER_REGISTRY_ADDRESS.$artistInfoOf($nft.author);
$amount = $sale.isAuction
? 1 //1 lot not 1 unit
: trigger.data["amount"] OTHERWISE 1;
$pricePerUnit = $sale.currency
? $CURRENCY_REGISTRY.$convert($sale.price, $sale.currency)
: $sale.price;
$fullPrice = $amount * $pricePerUnit;
$cut = round($fullPrice * $HOUSE_CUT, 0);
$toAuthor = round($fullPrice * (min($nft.meta["royalty"] OTHERWISE 0, $MAX_ROYALTY))/100, 0);
response["meta"] = $nft.meta;
response["toAuthor"] = $toAuthor;
$toSeller = $fullPrice - $cut - $toAuthor - 10000;
}",
"messages": [
{
"if": "{NOT $sale.auction}",
"init": "{
if (exists(trigger.data["amount"])){
if (NOT is_integer(trigger.data["amount"]))
bounce("amount field must be an integer");
if (trigger.data["amount"] < 1)
bounce("amount field must be at least 1");
if (trigger.data["amount"] > $sale.amount)
bounce("That user is selling only " || $sale.amount || " units");
}
if ($sale.currency)
response["rate"] = $CURRENCY_REGISTRY.$getExchangeRate($sale.currency);
response["realPrice"] = $pricePerUnit;
response["total"] = $fullPrice;
if (trigger.output[[asset="base"]].amount < $fullPrice)
bounce("Your payment is lower than the NFT price. You have to send " || $fullPrice || " bytes");
}",
"app": "payment",
"payload": {
"asset": "base",
"outputs": [
{
"address": "{$sale.soldBy}",
"amount": "{$toSeller}"
},
{
"if": "{$toAuthor > 5000}",
"address": "{$nft.author}",
"amount": "{$toAuthor}"
}
]
}
},
{
"if": "{NOT $sale.auction}",
"app": "payment",
"payload": {
"asset": "{trigger.data['NFT']}",
"outputs": [
{
"address": "{trigger.address}",
"amount": "{$amount}"
}
]
}
},
{
"app": "state",
"state": "{
if ($sale.auction) {
if (trigger.output[[asset="base"]].amount < 20000)
bounce("The minimum bid is 20000 bytes");
if (trigger.output[[asset="base"]].amount <= $sale.price * (1 + $MIN_BID_INCREMENT))
bounce("Your bid must be at least " || ceil($sale.price * (1 + $MIN_BID_INCREMENT), 0) || " (0.01% higher than the current)");
if ($sale.soldBy == trigger.address)
bounce("You cannot bid on your own auction");
if (timestamp > $sale.endTime AND $sale.bids) //You can still bid on an expired auction if it had no bids. This will reawake the bid for 30 mins if the NFT was created by an attested user. Otherwise you will be able to claim the auction right away after bidding.
bounce("The auction is over");
var["locked"] += trigger.output[[asset="base"]].amount - ($sale.by ? $sale.price : 0); //Increase non-withdrawable funds by the difference between previous and last bid
$saleState = $sale || {
price: trigger.output[[asset="base"]].amount,
at: timestamp,
bids: $sale.bids + 1,
by: trigger.address,
endTime: $sale.endTime + $HALF_HOUR
};
}
else {
$newAmount = $sale.amount - $amount;
$saleState = $newAmount == 0 ? false : $sale || {amount: $newAmount, at: timestamp};
}
$key = var["FREE_" || trigger.data["NFT"] || "_" || trigger.data["address"]]
? "FREE_"
: "NFT_";
$fullKey = $key || trigger.data["NFT"] || "_" || trigger.data["address"];
var[$fullKey] = $saleState;
}"
}
]
},
{
"if": "{$method == "CLAIM"}",
"init": "{
$address = trigger.data["address"] OTHERWISE trigger.address;
$sale = $saleInfo(trigger.data["NFT"], $address);
if (NOT trigger.data["NFT"])
bounce("NFT field is mandatory");
$nft = $fetchNFT(trigger.data["NFT"]);
$artist = $USER_REGISTRY_ADDRESS.$artistInfoOf($nft.author);
if (NOT $sale.auction)
bounce("That sale is not an auction");
if ($sale.auction AND timestamp <= $sale.endTime)
bounce("The auction is not over yet");
$fullKey = var["FREE_" || trigger.data["NFT"] || "_" || $sale.soldBy]
? "FREE_" || trigger.data["NFT"] || "_" || $sale.soldBy
: "NFT_" || trigger.data["NFT"] || "_" || $sale.soldBy;
}",
"messages": {
"cases": [
{
"if": "{$sale.auction}",
"messages": [
{
"if": "{$sale.bids > 0}",
"app": "payment",
"init": "{
$share = $sale.price * (1 - $HOUSE_CUT);
if ($nft.meta["royalty"]){
$royalty = $sale.price * min($nft.meta["royalty"], $MAX_ROYALTY) / 100;
$outputs = [
{//Royalty
address: $nft.author,
amount: floor($royalty, 0) //Percentage of the whole bid, no fees are charged
},
{//Seller payment
address: $sale.soldBy,
amount: floor($share - $royalty, 0) //We take the house cut then royalty cut
}
];
}
else{
$outputs = [
{//Seller payment
address: $sale.soldBy,
amount: floor($share, 0)
}
];
}
$payload = {outputs: $outputs};
}",
"payload": "{$payload}"
},
{
"app": "payment",
"payload": {
"asset": "{trigger.data['NFT']}",
"outputs": [
{
"address": "{$sale.by OTHERWISE $sale.soldBy}",
"amount": "{$sale.amount}"
}
]
}
},
{
"app": "state",
"state": "{
if ($sale.bids > 0) //If it had no bids you cannot reduce locked amount
var["locked"] -= $sale.price;
var[$fullKey] = false; //No longer listed for sale
}"
}
]
},
{
"if": "{NOT $sale.auction}",
"init": "{
if (trigger.address != $sale.soldBy)
bounce("You cannot claim other people's sales");
}",
"messages": [
{
"app": "payment",
"payload": {
"asset": "{trigger.data['NFT']}",
"outputs": [
{
"address": "{$sale.soldBy}",
"amount": "{$sale.amount}"
}
]
}
},
{
"app": "state",
"state": "{
var[$fullKey] = false;
}"
}
]
}
]
}
},
{
"if": "{$method == "CHANGE_PRICE"}",
"init": "{
if (NOT trigger.data["NFT"])
bounce("NFT field is mandatory");
$sale = $saleInfo(trigger.data["NFT"], trigger.address);
if (NOT $sale)
bounce("Sale 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 ($sale.auction)
bounce("You cannot change an auction price!");
}",
"messages": [
{
"app": "state",
"state": "{
if (var["NFT_" || trigger.data["NFT"] || "_" || trigger.address])
var["NFT_" || trigger.data["NFT"] || "_" || trigger.address] ||= {price: trigger.data["price"], soldBy: trigger.address, currency: trigger.data["currency"] OTHERWISE false};
else
var["FREE_" || trigger.data["NFT"] || "_" || trigger.address] ||= {price: trigger.data["price"], soldBy: trigger.address, currency: trigger.data["currency"] OTHERWISE false};
}"
}
]
},
{
"if": "{$method == "SELL"}",
"init": "{
if (trigger.output[[asset!="base"]].asset == "ambigous")
bounce("You can only send a single NFT at a time");
$NFT = $fetchNFT(trigger.output[[asset!="base"]].asset);
$prof = $USER_REGISTRY_ADDRESS.$profileOf($NFT.author);
if (2NEH2TEG725U3JGJOIE3FCAEGY5HGVFU.$isRevoked(trigger.output[[asset!="base"]].asset))
bounce("We revoked the trading of that token probably due to copyright reasons");
if (NOT trigger.data["price"])
bounce("price field is mandatory");
if (trigger.data["currency"]){
if (NOT $CURRENCY_REGISTRY.$getCurrency(trigger.data["currency"]))
bounce("We do not support that currency");
}
else {
if (NOT is_integer(trigger.data["price"]))
bounce("Price must be an integer");
if (NOT is_valid_amount(trigger.data["price"]))
bounce("Price is not a valid amount");
if (NOT trigger.data["auction"]){
if (trigger.data["price"] * trigger.output[[asset!="base"]].amount < 20000)
bounce("The minimum sale price is 20.000 bytes");
}
}
if (trigger.data["auction"]){
if (NOT exists(trigger.data["endTime"]))
bounce("If you want to hold an auction you have to specify the endTime");
if (trigger.data["price"] < 20000)
bounce("Minimum auction price is 20.000 bytes");
if (trigger.data["currency"])
bounce("You cannot set the auction price in other currency than bytes");
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 (var["FREE_" || trigger.output[[asset!="base"]].asset || "_" || trigger.address] OR var["NFT_" || trigger.output[[asset!="base"]].asset || "_" || trigger.address])
bounce("You already have an open sale or auction for that NFT. Please claim it before starting another one");
}",
"messages": [
{
"app": "state",
"state": "{
$key = ($prof.verified ? "NFT_" : "FREE_") || trigger.output[[asset!="base"]].asset || "_" || trigger.address;
var[$key] = {
price: trigger.data["price"],
amount: trigger.output[[asset!="base"]].amount,
endTime: trigger.data["auction"]
? trigger.data["endTime"]
: false,
soldBy: trigger.address,
bids: trigger.data["auction"]
? 0
: false,
auction: trigger.data["auction"]
? true
: false,
currency: trigger.data["auction"]
? ''
: trigger.data["currency"] OTHERWISE false
};
}"
}
]
}
]
}
}
]