/* * Aegora Sales Offer * * Author: Craig Everett * Copyright: Tsuriai Corporation (2022) * License: GPLv3 * Version: 1 * * Sale offers at Aegora (aegora.psychobitch.party) are clones of this contract. * (The name might be a little weird, but who looks at domain names anymore anyway?) * When a sale offer is created on the site, the seller signs a contract call to * * * Calls to clones of this contract can come from five sources: * 1. The base contract (that cloned it in the first place) * - init(tsuriai : address, // Service fee destination * aegora : AegoraBase, // Get born * agent : address, // Deployment agent address * portion : int, // Deploying agent's fee portion * seller : address, // Address of the seller * id : int, * price : int) * - do_a_backflip() // Get the opposite of born * 2. Sellers who posted clones of this contract * - adjust(price : int) // update price * - accept() // Sale is DONE * - refuse() // Cancel a negotation * - revoke() // Invalidate and disable this offer * 3. Buyers who are interested in buying through this contract * - bid(price : int) // Update the bid amout in negotiation * - hold() // Set HOLD for price negotiation * - cancel() // Back out of a purchase * 4. Aegora (the base contract) * - reassign(tsuriai : address) // Reset Tsuriai's payable address * 5. The market's network service (or the public) * - price() // Check current contract price * - status() // Check contract status * - agent() // Retrieve the agent's public key (usually the same as the seller) * - seller() // Retrieve the seller's public key * - buyer() // Retrieve the buyer's public key */ @compiler == 7.0.1 include "Option.aes" contract interface AegoraBase = stateful entrypoint close : (int) => bool payable contract SalesOffer = record state = {id : int, aegora : address, agent : address, portion : int, price : int, seller : address, buyer : option(address), status : status, tsuriai : address} datatype status = OPEN | NEGO | HOLD | DONE // Seller Interface stateful entrypoint init(tsuriai : address, aegora : address, agent : address, portion : int, seller : address, id : int, price : int) : state = {id = id, aegora = aegora, agent = Call.origin, portion = portion, price = price, seller = seller, buyer = None, status = OPEN, tsuriai = tsuriai} public stateful entrypoint adjust(price : int) : unit = require(state.status != DONE, "HiLlArY wUz HeRe.") require(Call.caller == state.seller, "Nacho shop!") require(price > 0, "You can't pay people to buy things, Mr. Keynes.") switch(state.buyer) Some(buyer) => if(Contract.balance > price) Chain.spend(buyer, Contract.balance - price) true else false None => dead_claim() put(state{price = price}) public stateful entrypoint accept() : bool = require(state.status == NEGO, "Sale is not under negotiation.") require(Call.caller == state.seller, "Nacho shop!") require(Contract.balance >= state.price, "Insufficient funds!") let buyer = Option.force(state.buyer) if(Contract.balance > state.price) Chain.spend(buyer, Contract.balance - state.price) Chain.spend(state.tsuriai, calc_fee()) if(state.portion > 0) Chain.spend(state.agent, (Contract.balance / state.portion)) Chain.spend(state.seller, Contract.balance) put(state{status = DONE}) Address.to_contract(state.aegora).close(state.id) public stateful entrypoint refuse() : unit = require(state.status == NEGO || state.status == HOLD, "Sale is not under negotiation.") require(Call.caller == state.seller, "Nacho shop!") refund() put(state{buyer = None}) put(state{status = OPEN}) public stateful entrypoint revoke() : bool = require(state.status != DONE, "HiLlArY wUz HeRe.") require(Call.caller == state.seller, "Nacho shop!") switch(state.status) OPEN => dead_claim() NEGO => refund() HOLD => refund() put(state{status = DONE}) Address.to_contract(state.aegora).close(state.id) // Buyer Interface public stateful payable entrypoint bid(amount: int) : unit = require(state.status != DONE, "HiLlArY wUz HeRe.") require(amount >= state.price, "Stop being poor.") switch(state.status) OPEN => require(Call.value >= state.price, "Stop being poor.") put(state{status = NEGO}) put(state{buyer = Some(Call.caller)}) NEGO => switch(state.buyer) Some(buyer) => require(Call.caller == buyer, "Nacho bid.") require(Contract.balance >= state.price, "Stop being poor.") if(Contract.balance > amount) Chain.spend(buyer, Contract.balance - amount) HOLD => switch(state.buyer) Some(buyer) => require(Call.caller == buyer, "Nacho bid.") require(Contract.balance >= state.price, "Stop being poor.") if(Contract.balance > amount) Chain.spend(buyer, Contract.balance - amount) put(state{status = NEGO}) public stateful entrypoint hold() : unit = require(state.status == NEGO, "Sale is not in negotiation") switch(state.buyer) Some(buyer) => require(Call.caller == buyer, "Nacho bid!") put(state{status = HOLD}) None => abort("Nacho bid!") public stateful entrypoint cancel() : unit = require(state.status == NEGO || state.status == HOLD, "Wrong status") switch(state.buyer) Some(buyer) => require(Call.caller == buyer, "Nacho bid!") Chain.spend(state.tsuriai, calc_fee()) put(state{buyer = None}) put(state{status = OPEN}) Chain.spend(buyer, Contract.balance) None => abort("Nacho bid!") // Aegora Interface public stateful entrypoint reassign(tsuriai : address) : unit = require(Call.caller == state.aegora || Call.origin == state.tsuriai, "Knock it off, stinky") put(state{tsuriai = tsuriai}) public stateful entrypoint sweep_the_table() : bool = require(state.status == DONE, "Hillary has not yet arrived.") require(Call.caller == state.tsuriai, "Nope.") dead_claim() public stateful entrypoint do_a_backflip() : bool = require(Call.caller == state.aegora, "Too late, Nathan.") put(state{status = DONE}) dead_claim() // Service/Public Network Interface public entrypoint price() : int = state.price public entrypoint status() : string = switch(state.status) OPEN => "open" NEGO => "nego" HOLD => "hold" DONE => "done" public entrypoint agent() : address = state.agent public entrypoint seller() : address = state.seller public entrypoint buyer() : address = require(state.status != OPEN, "No buyer!") Option.force(state.buyer) // Utilities function calc_fee() : int = Contract.balance / 50 private stateful function refund() : bool = if(Contract.balance > 0) switch(state.buyer) Some(buyer) => Chain.spend(buyer, Contract.balance) true None => false else false private stateful function dead_claim() : bool = if(Contract.balance > 0) Chain.spend(state.tsuriai, Contract.balance) true else false