Tokenizing Real Estate With Fi

In light of the news of Securitize and Elevated Returns tokenizing $1B worth of real estate on the Tezos Network, let’s dive into what it takes to make a similar contract in Fi. We will create a contract that has the ability to tokenize a piece of property, that has a concept of a real estate wallet, and has a built in real estate escrow. Let’s get started!

Development

Describing A Property

To begin let’s start off by defining some structures that we can use to represent real estate. Firstly we need a property structure.

struct Property(
string addr,
nat supply,
set[address] owners
);

In the Property structure we are going to have the property address (string addr), the total token supply for the property (nat supply), and a set/list of owners who own the property (set[address] owners).

Note:

  • set: is like a list or array but has additional functions available to it like Fi’s in function.

Describing A Wallet

Next let’s define what a real estate wallet looks like. It’s important to note, these wallets will be tied to the address of the Tezos wallet that created it. More on that later.

struct Wallet(
map[nat=>Deed] deeds
);

You can see the Wallet structure is pretty simple. In our wallet we have a map of deeds which is mapped by a nat (map[nat=>Deed] deeds). The natural number used to map deeds, is going to act as a property ID. In other words, the wallet will contain deeds associated to the property ID in the map.

Describing A Deed

The next thing we need to implement is the Deed itself.

struct Deed(
nat free,
nat locked,
nat total
);

Remember a deed in a wallet is mapped by a property ID, later we will use this property ID to associate that connection. In the deed we have a total (nat total), which is the total tokens owned for the associate property ID. We then have a free field (nat free), which is the amount of tokens in the deed that are free to use. Finally we have a locked field, which is the amount of tokens held that are un spendable. The reason to have the free and locked fields is to add the ability to lock tokens in a deed that are used in an escrow contract (later).

Describing Escrow

The next structure we need to in this contract is the escrow structure.

struct Escrow(
address buyer,
address seller,
address auditor,
map[address=>Sig] signatures,
nat property,
nat tokens,
mutez bond,
string conditional
);

The Escrow structure is going to be used to create escrow contracts between a buyer and a seller. The first thing we need to do is represent the buyer (address buyer) and the seller (address seller). We next add another address to escorw called auditor (address auditor). The auditor in an escrow will act as an intermediary to release escrow funds if there is a discrepancy between the buyer and seller.

In order to release funds out of an escrow structure, we are going to require some signatures (map[address=>Sig] signatures), more on that later. Next we need to add the property this escrow is representing (nat property). After that we add the amount of tokens (real estate tokens) to be transferred to the buyer (nat tokens). Likewise, we add the amount of bond (mutez bond) the buyer is putting up to buy the property. The buyer will put XTZ into the smart contract as bond. Finally we add any conditionals for the escrow agreement (string conditional).

Describing Signatures

Finally the last structure we define will deal with the buyer’s, seller’s, and auditor’s signatures in an escrow.

struct Sig(
bool sig,
address recipient
);

First we need to represent whether the Sig structure has been signed (bool sig). And next we need to represent who the signature is in favor for, either the buyer or seller (address recipient).

Defining Storage

Now that we have all our structures defined, let’s define the storage needed for this smart contract.

storage map[address=>Wallet] wallets;
storage map[nat=>Property] properties;
storage map[nat=>Escrow] escrow;
storage set[address] managers;

The first thing we add to storage is all the wallets in the smart contract. Like mentioned before, wallets are mapped by addresses. The next thing we defined in storage is all the properties tokenized in the contract. Each property is mapped by a nat, which will represent the property ID. Next we define storage for all the escrows in the contract. Finally we define storage for managers. Managers will represent wallets authorized to add properties to the contract.

Adding A Property

Now that we have all the structures and necessary storage defined, let’s add the ability to add a property.

entry addProperty(Property property){
if (in(storage.managers, SENDER) == bool false) {
throw(string "you are not an authorized manager");
}
let nat len = length(storage.properties);
let nat id = add(len, nat 1);
storage.properties.push(id, input.property)
}

The addProperty function will take in the property to be added in as input. Because we are only allowing managers of the contract to add a property, we need to check if the SENDER invoking this function is listed in the managers storage, if they aren’t we throw an unauthorized error.

Next we need to get the length of the properties map in storage. We do this because property ID’s will be assigned incrementally. We then use Fi’s add function to add one to the length of the map and store it in a variable called id. Finally we push the input property onto the storage map with an id of the id variable.

Creating A Wallet

The next function we should add is the ability to create a real estate wallet.

entry createWallet(){
if (in(storage.wallets, SENDER) == bool true) {
throw(string "you already have a wallet!");
}
let Wallet wallet;
storage.wallets.push(SENDER, wallet);
}

We are going to allow anyone to create a wallet, so there is no reason to authenticate the SENDER. We do want to make sure the SENDER doesn’t already have a wallet though. We check this by using FI’s in function to see if any wallet exists at the SENDER’s address.

If no wallet already exists at the SENDER’s address we create a wallet variable, and push it to the wallet storage with SENDER as the address key.

Transferring Property

The next ability we will add, is a function to allow you to transfer property in a wallet to another wallet.

entry tranferProperty(nat propId, nat amnt, address to){
if (in(storage.wallets, SENDER) == bool false) {
throw(string "no wallet found")
}
if (in(storage.wallets, input.to) == bool false) {
throw(string "no wallet found for to")
}
let Wallet wallet = storage.wallets.get(SENDER);
if (in(wallet.deeds, input.propId) == bool false) {
throw(string "property not found in wallet")
}
let Deed deed = wallet.deeds.get(input.propId);
if (deed.free < input.amnt) {
throw(string "wallet does not hold the amount desired to transfer")
}
let int totalBalance = sub(deed.total, input.amnt);
let int freeBalance = sub(deed.free, input.amnt);
deed.total = abs(totalBalance);
deed.free = abs(freeBalance);

wallet.deeds.push(input.propId, deed);
storage.wallets.push(SENDER, wallet);
let Wallet toWallet = storage.wallets.get(input.to);
let Deed toDeed;
if (in(wallet.deeds, input.propId) == bool false) {
toDeed.free = input.amnt;
toDeed.total = input.amnt;
wallet.deeds.push(input.propId, toDeed);
} else {
toDeed = wallet.deeds.get(input.propId);
toDeed.free = add(toDeed.free, input.amnt);
toDeed.total = add(toDeed.total, input.amnt);
}
toWallet.deeds.push(input.propId, toDeed);
storage.wallets.push(input.to, toWallet);

let Property prop = storage.properties.get(input.propId);
if (in(prop.owners, input.to) == bool false) {
prop.owners.push(input.to);
}
storage.properties.push(input.propId, prop);
}

This one’s a long one, so hold tight. In the transferProperty function we pass the property ID of the property being transferred, the amount of tokens being transferred, and finally the address of the wallet to transfer to.

In the beginning of the function, we check if the SENDER has a wallet to even transfer property out of. We then check if the to address has a wallet as well. If neither have a wallet, an error is thrown back. The next thing we do is retrieve the Wallet for the SENDER and store it in a variable called wallet. After that, we check if the SENDER’s wallet has a deed to the property to be transfered. If a deed is found we store it in a local variable called deed.

We then check if the deed in the SENDER’s wallet has enough tokens free to transfer the requested amount. If the wallet does have enough tokens free, we subtract the amount of tokens to transfer from the total tokens and the free tokens of the deed. Note FI’s sub function, only returns an integer so we need to convert that back to a natural number by using FI’s abs function. One the deed’s balances are updated, we push it back into the SENDER’s wallet, and then push the SENDER’s wallet back into storage.

Now we need to add the tokens subtracted from the SENDER’s wallet to the to wallet. To do this we retrieve the wallet associated to the to input from storage and assign it to a variable called toWallet. We then create a variable called toDeed to represent the deed for the property. After that, we check whether the to wallet already owns some tokens for the property being transferred.

If no deed is found for the property being transferred in the to wallet, we use the deed variable to initialize a new deed containing the tokens subtracted from the SENDER. We assign the toDeed free, and total fields to the amount subtracted from the SENDER.

If a deed already exists for the property being transferred in the toWallet, we retrieve it and assign it to toDeed. We then add the tokens subtracted from the SENDER to the free and total fields of the deed retrieved. After updating the toWallet to the new owner of the property tokens, we push it back to the wallets storage.

Finally we need to update the Property itself to show all of its owners. We retrieve the property from storage and add the address of input.to to the owners field if it’s not already there. We then push the property back to the properties storage.

Proposing An Escrow

The next function we will write is proposeEscrow. This function should only be called by the buyer as it expects the XTZ to be sent into the contract equal to the escrow bond.

entry proposeEscrow(address buyer, address seller, address auditor, string cond, nat prop, nat tokens, mutez bond) {
if (in(storage.managers, input.auditor) == bool false){
throw(string "the auditor is not authorized for escrow");
}
let Escrow escrow;
escrow.buyer = input.buyer;
escrow.seller = input.seller;
escrow.auditor = input.auditor;
escrow.conditional = input.cond;
escrow.property = input.prop;
escrow.tokens = input.tokens;
escrow.bond = input.bond;

if (AMOUNT != input.bond) {
transfer(SENDER, AMOUNT);
throw(string "did not fill proposed escrow with the correct amount of xtz");
}

escrow.status = bool true;
let nat len = length(storage.escrow);
let nat id = add(len, nat 1);
storage.escrow.push(id, escrow);
}

In proposeEscrow we take in input for buyer, seller, and auditor’s addresses. As well as, any conditionals for the escrow, the property id the escrow is related to, the amount of tokens to be bought, and the bond to be placed in the contract by the buyer.

We then check whether the auditor proposed for the escrow is in the managers list of the contract. If not, the auditor is not trusted.

After that we create an escrow variable and assign all the input fields to their perspective values in the escrow struct. Then we check whether the buyer sent the required bond into the contract, and if not we send the AMOUNT sent in back.

Finally we set the escrow status to active by setting trie, and push the escrow to escrow storage with a new id.

Rejecting An Escrow

Now that we have the ability to propose an escrow, we need to allow the auditor or seller of the proposed escrow to reject it, transferring the funds back to the buyer.

entry rejectEscrow(nat id) {
if(in(storage.escrow, input.id)) {
throw(string "escrow not found");
}
let Escrow escrow = storage.escrow.get(input.id);
if (escrow.auditor != SENDER || escrow.seller != SENDER) {
throw(string "unauthorized to reject escrow");
}

escrow.status = bool false;
transfer(escrow.buyer, escrow.bond);
storage.escrow.push(input.id, escrow);
}

As input we take in the escrow id, and then check whether an escrow exists by that id, and finally we retrieve the escrow.

We set the escrow status to false to show that it’s rejected. Then we transfer the escrow bond back to the buyer who proposed it. Finally, we push the rejected escrow back into storage.

Accepting An Escrow

Now that we have proposeEscrow, and rejectEscrow we need an acceptEscrow, which can only be executed by the proposed seller of an escrow.

entry acceptEscrow(nat id){
if (in(storage.managers, SENDER) == bool false){
throw(string "not authorized to make escrow");
}
if (in(storage.escrow, input.id) == bool false){
throw(string "no escrow found at id");
}

let Escrow escrow = storage.escrow.get(input.id);
if (escrow.status == bool false) {
throw(string "escrow has been rejected");
}

let Sig bSig;
let Sig sSig;
let Sig aSig;
bSig.sig = bool false;
sSig.sig = bool false;
aSig.sig = bool false;
escrow.signatures.push(escrow.buyer, bSig);
escrow.signatures.push(escrow.seller, sSig);
escrow.signatures.push(escrow.auditor, aSig);
if (in(storage.wallets, escrow.seller) == bool false) {
throw(string "no wallet found for seller")
}
if (in(storage.wallets, escrow.buyer) == bool false) {
throw(string "no wallet found for buyer")
}
let Wallet wSeller = storage.wallets.get(escrow.seller);
if (in(wSeller.deeds, escrow.property) == bool false) {
throw(string "seller does not own the requested property")
}
let Deed sDeed = wSeller.deeds.get(escrow.property);
if (sDeed.free < escrow.tokens) {
throw(string "seller does not have enough tokens to fufill escrow");
}
sDeed.locked = escrow.tokens;
let int intFree = sub(sDeed.free, escrow.tokens);
sDeed.free = abs(intFree);

wSeller.deeds.push(escrow.property, sDeed);
storage.wallets.push(escrow.seller, wSeller);
storage.escrow.push(input.id, escrow);
}

In acceptEscrow we take in the escrow id as input. We then validate that escrow exists, and retrieve it if it does. We then check to see if seller of the escrow is the SENDER invoking this function, if not they are unauthorized. Finally we check if the escrow has previously been rejected, and throw an error if so.

After that we create the required signatures for the escrow, by creating a signature for the buyer, seller, and auditor. We then set each signature to unsigned (false). Finally we push all 3 signature structs to the escrow.

We then check if both the buyer and the seller have real estate wallets in the contract. After that we get the seller’s wallet, and check if they hold the deed to the property in escrow. If they do hold the deed, we lock the amount of tokens available in that deed, as they will be frozen in the escrow. We adjust the amount of free tokens for the deed as well.

We then push the deed back into the seller’s wallet. After adjusting everything needed in the seller’s wallet for escrow, we then check whether the AMOUNT sent into the contract is equal to the amount of the bond. If it isn’t the AMOUNT is set back the buyer. It is assumed the buyer invokes the makeEscrow function.

Finally we get the length of the escrow storage to assign an an incremental id to the new the escrow. We then push the escrow to the escrow storage at that id, and push the sellers wallet back into the wallets storage.

Signing An Escrow

Next we to add the ability for the buyer, seller, and auditor to sign an escrow, to show that it is free to execute.

entry signEscrow(nat id, address recipient){
if (in(storage.escrow, input.id)) {
throw(string "no escrow found at id");
}
let Escrow escrow = storage.escrow.get(input.id);
if (SENDER != escrow.seller || SENDER != escrow.buyer || SENDER != escrow.auditor) {
throw (string "you are unauthorized to sign in this escrow")
}
let Sig sig = escrow.signatures.get(SENDER);
if (input.recipient != escrow.seller || input.recipient != escrow.buyer) {
throw (string "invalid recipient resolution")
}
sig.sig = bool true;
sig.recipient = input.recipient;
escrow.signatures.push(SENDER, sig);
storage.escrow.push(input.id, escrow);
}

The first thing we do in signEscrow is take in the escrow id and the address that the SENDER is signing in favor for. We then check if there is an escrow that exists by that id in storage, and if there is we assign it to a local variable called escrow.

We then validate that the SENDER is the seller, buyer, or auditor for that escrow. If not they are unauthorized to sign.

We then get the signature structure associated with the SENDER from the escrow. We check whether the input recipient is either the buyer or the seller, otherwise the input is invlaid.

Finally we set the Sig object to signed (true) and add the recipient. We then push the Sig object back the escrow signatures, and likewise we push the escrow object back into storage.

Executing An Escrow

Finally we need to add a function to allow an escrow to execute.

entry execEscrow(nat id){
if (in(storage.escrow, input.id)) {
throw(string "no escrow found at id");
}
let Escrow escrow = storage.escrow.get(input.id);
if (SENDER != escrow.seller || SENDER != escrow.buyer || SENDER != escrow.auditor) {
throw (string "you are unauthorized to execute this escrow")
}

let Wallet sWallet = storage.wallets.get(escrow.seller);
let Deed sDeed = sWallet.deeds.get(escrow.property);
let Wallet bWallet = storage.wallets.get(escrow.buyer);
let Deed bDeed;
if (in(bWallet.deeds, escrow.property) == bool false) {
bWallet.deeds.push(escrow.property, bDeed);
}
bDeed = bWallet.deeds.get(escrow.property);



let Sig sSig = escrow.signatures.get(escrow.seller);
let Sig bSig = escrow.signatures.get(escrow.buyer);
let Sig aSig = escrow.signatures.get(escrow.auditor);
let int unlock;
let int total;
if (sSig.sig == bool true && bSig.sig == bool true) {
if (sSig.recipient == bSig.recipient) {
transfer(sSig.recipient, escrow.bond);
if (sSig.recipient == escrow.seller){
unlock = sub(sDeed.locked, escrow.tokens);
sDeed.locked = abs(unlock);
total = sub(sDeed.total, escrow.tokens);
sDeed.total = abs(total);
bDeed.total = add(bDeed.total, escrow.tokens);
bDeed.free = add(bDeed.free, escrow.tokens);
} else {
unlock = sub(sDeed.locked, escrow.tokens);
sDeed.locked = abs(unlock);
sDeed.free = add(sDeed.free, escrow.tokens);

}
}
}
if (sSig.sig == bool true && aSig.sig == bool true) {
if (sSig.recipient == aSig.recipient) {
transfer(sSig.recipient, escrow.bond);
if (sSig.recipient == escrow.seller){
unlock = sub(sDeed.locked, escrow.tokens);
sDeed.locked = abs(unlock);
total = sub(sDeed.total, escrow.tokens);
sDeed.total = abs(total);
bDeed.total = add(bDeed.total, escrow.tokens);
bDeed.free = add(bDeed.free, escrow.tokens);
} else {
unlock = sub(sDeed.locked, escrow.tokens);
sDeed.locked = abs(unlock);
sDeed.free = add(sDeed.free, escrow.tokens);

}
}
}
if (bSig.sig == bool true && aSig == bool true) {
if (bSig.recipient == aSig.recipient) {
transfer(bSig.recipient, escrow.bond);
if (bSig.recipient == escrow.seller){
unlock = sub(sDeed.locked, escrow.tokens);
sDeed.locked = abs(unlock);
total = sub(sDeed.total, escrow.tokens);
sDeed.total = abs(total);
bDeed.total = add(bDeed.total, escrow.tokens);
bDeed.free = add(bDeed.free, escrow.tokens);
} else {
unlock = sub(sDeed.locked, escrow.tokens);
sDeed.locked = abs(unlock);
sDeed.free = add(sDeed.free, escrow.tokens);
}
}
}
sWallet.deeds.push(escrow.property, sDeed);
bWallet.deeds.push(escrow.property, bDeed);
storage.wallets.push(escrow.seller, sWallet);
storage.wallets.push(escrow.buyer, bWallet);
}

The first thing we do in execEscrow is take in the id to the escrow we are executing. We then check is an escrow exists in storage by that id, and if it does we assign it to a local variable called escrow.

After that we check wether the SENDER is the buyer, seller, or auditor of the escrow, because only they are allow to execute it.

After authorizing the SENDER we get the sellers wallet from the wallet storage, along with the deed to to the property the escrow respesents. We assign these values to sWallet, and sDeed.

We then do the same thing for the buyer, except we check whether the buyer has a deed to the property already, and if not one is created. After we have the deeds for the buyers, and sellers wallets, we get all 3 signatures out of the escrow to see if two of them are signed. We then declare int variables for unlock, and total which we will use later in the function’s conditionals.

Next you will see three big if conditionals. These conditionals basically do the same thing, but they check all the possibilities of having two signatures. Whether the buyer and seller signed, the buyer and auditor signed, or the seller and auditor signed. If two signatures signed we then check wether each signature has the same recipient.

If there is an agreement on the recipient, the recipient gets transferred the bond put into the contract escrow by the buyer. The recipient can only be the seller or buyer, which is controlled in the signing function.

We then check if the recipient is the seller. If the recipient is the seller, we use the sellers deed and unlock the token amount in escrow from the locked tokens in the deed. We then subtract the amount from the deed total, and then add the tokens to the buyers deed, adjusting free and total. If the recipient is not the seller, we unlock the tokens locked in escrow to the sellers deed, and make them spendable again.

Finally we push the sellers deed, and the buyers deed back to their wallets, and then push their wallets back into the wallets storage.

Putting It All Together

struct Property(
string addr,
nat supply,
set[address] owners
);
struct Wallet(
map[nat=>Deed] deeds
);
struct Deed(
nat free,
nat locked,
nat total
);
struct Escrow(
address buyer,
address seller,
address auditor,
map[address=>Sig] signatures,
nat property,
nat tokens,
mutez bond,
string conditional,
bool status
);
struct Sig(
bool sig,
address recipient
);
storage map[address=>Wallet] wallets;
storage map[nat=>Property] properties;
storage map[nat=>Escrow] escrow;
storage set[address] managers;
entry addProperty(Property property){
if (in(storage.managers, SENDER) == bool false) {
throw(string "you are not an authorized manager");
}
let nat len = length(storage.properties);
let nat id = add(len, nat 1);
storage.properties.push(id, input.property)
}
entry createWallet(){
if (in(storage.wallets, SENDER) == bool true) {
throw(string "you already have a wallet!");
}
let Wallet wallet;
storage.wallets.push(SENDER, wallet);
}
entry tranferProperty(nat propId, nat amnt, address to){
if (in(storage.wallets, SENDER) == bool false) {
throw(string "no wallet found")
}
if (in(storage.wallets, input.to) == bool false) {
throw(string "no wallet found for to")
}
let Wallet wallet = storage.wallets.get(SENDER);
if (in(wallet.deeds, input.propId) == bool false) {
throw(string "property not found in wallet")
}
let Deed deed = wallet.deeds.get(input.propId);
if (deed.free < input.amnt) {
throw(string "wallet does not hold the amount desired to transfer")
}
let int totalBalance = sub(deed.total, input.amnt);
let int freeBalance = sub(deed.free, input.amnt);
deed.total = abs(totalBalance);
deed.free = abs(freeBalance);

wallet.deeds.push(input.propId, deed);
storage.wallets.push(SENDER, wallet);
let Wallet toWallet = storage.wallets.get(input.to);
let Deed toDeed;
if (in(wallet.deeds, input.propId) == bool false) {
toDeed.free = input.amnt;
toDeed.total = input.amnt;
wallet.deeds.push(input.propId, toDeed);
} else {
toDeed = wallet.deeds.get(input.propId);
toDeed.free = add(toDeed.free, input.amnt);
toDeed.total = add(toDeed.total, input.amnt);
}
toWallet.deeds.push(input.propId, toDeed);
storage.wallets.push(input.to, toWallet);

let Property prop = storage.properties.get(input.propId);
if (in(prop.owners, input.to) == bool false) {
prop.owners.push(input.to);
}
storage.properties.push(input.propId, prop);
}
entry proposeEscrow(address buyer, address seller, address auditor, string cond, nat prop, nat tokens, mutez bond) {
if (in(storage.managers, input.auditor) == bool false){
throw(string "the auditor is not authorized for escrow");
}
let Escrow escrow;
escrow.buyer = input.buyer;
escrow.seller = input.seller;
escrow.auditor = input.auditor;
escrow.conditional = input.cond;
escrow.property = input.prop;
escrow.tokens = input.tokens;
escrow.bond = input.bond;

if (AMOUNT != input.bond) {
transfer(SENDER, AMOUNT);
throw(string "did not fill proposed escrow with the correct amount of xtz");
}

escrow.status = bool true;
let nat len = length(storage.escrow);
let nat id = add(len, nat 1);
storage.escrow.push(id, escrow);
}
entry rejectEscrow(nat id) {
if(in(storage.escrow, input.id)) {
throw(string "escrow not found");
}
let Escrow escrow = storage.escrow.get(input.id);
if (escrow.auditor != SENDER || escrow.seller != SENDER) {
throw(string "unauthorized to reject escrow");
}

escrow.status = bool false;
transfer(escrow.buyer, escrow.bond);
storage.escrow.push(input.id, escrow);
}
entry acceptEscrow(nat id){
if (in(storage.escrow, input.id) == bool false){
throw(string "no escrow found at id");
}

let Escrow escrow = storage.escrow.get(input.id);
if (escrow.seller != SENDER) {
throw(string "not authorized to accept escrow");
}
if (escrow.status == bool false) {
throw(string "escrow has been rejected");
}

let Sig bSig;
let Sig sSig;
let Sig aSig;
bSig.sig = bool false;
sSig.sig = bool false;
aSig.sig = bool false;
escrow.signatures.push(escrow.buyer, bSig);
escrow.signatures.push(escrow.seller, sSig);
escrow.signatures.push(escrow.auditor, aSig);
if (in(storage.wallets, escrow.seller) == bool false) {
throw(string "no wallet found for seller")
}
if (in(storage.wallets, escrow.buyer) == bool false) {
throw(string "no wallet found for buyer")
}
let Wallet wSeller = storage.wallets.get(escrow.seller);
if (in(wSeller.deeds, escrow.property) == bool false) {
throw(string "seller does not own the requested property")
}
let Deed sDeed = wSeller.deeds.get(escrow.property);
if (sDeed.free < escrow.tokens) {
throw(string "seller does not have enough tokens to fufill escrow");
}
sDeed.locked = escrow.tokens;
let int intFree = sub(sDeed.free, escrow.tokens);
sDeed.free = abs(intFree);

wSeller.deeds.push(escrow.property, sDeed);
storage.wallets.push(escrow.seller, wSeller);
storage.escrow.push(input.id, escrow);
}
entry signEscrow(nat id, address recipient){
if (in(storage.escrow, input.id)) {
throw(string "no escrow found at id");
}
let Escrow escrow = storage.escrow.get(input.id);
if (SENDER != escrow.seller || SENDER != escrow.buyer || SENDER != escrow.auditor) {
throw (string "you are unauthorized to sign in this escrow")
}
let Sig sig = escrow.signatures.get(SENDER);
if (input.recipient != escrow.seller || input.recipient != escrow.buyer) {
throw (string "invalid recipient resolution")
}
sig.sig = bool true;
sig.recipient = input.recipient;
escrow.signatures.push(SENDER, sig);
storage.escrow.push(input.id, escrow);
}
entry execEscrow(nat id){
if (in(storage.escrow, input.id)) {
throw(string "no escrow found at id");
}
let Escrow escrow = storage.escrow.get(input.id);
if (SENDER != escrow.seller || SENDER != escrow.buyer || SENDER != escrow.auditor) {
throw (string "you are unauthorized to execute this escrow")
}

let Wallet sWallet = storage.wallets.get(escrow.seller);
let Deed sDeed = sWallet.deeds.get(escrow.property);
let Wallet bWallet = storage.wallets.get(escrow.buyer);
let Deed bDeed;
if (in(bWallet.deeds, escrow.property) == bool false) {
bWallet.deeds.push(escrow.property, bDeed);
}
bDeed = bWallet.deeds.get(escrow.property);



let Sig sSig = escrow.signatures.get(escrow.seller);
let Sig bSig = escrow.signatures.get(escrow.buyer);
let Sig aSig = escrow.signatures.get(escrow.auditor);
let int unlock;
let int total;
if (sSig.sig == bool true && bSig.sig == bool true) {
if (sSig.recipient == bSig.recipient) {
transfer(sSig.recipient, escrow.bond);
if (sSig.recipient == escrow.seller){
unlock = sub(sDeed.locked, escrow.tokens);
sDeed.locked = abs(unlock);
total = sub(sDeed.total, escrow.tokens);
sDeed.total = abs(total);
bDeed.total = add(bDeed.total, escrow.tokens);
bDeed.free = add(bDeed.free, escrow.tokens);
} else {
unlock = sub(sDeed.locked, escrow.tokens);
sDeed.locked = abs(unlock);
sDeed.free = add(sDeed.free, escrow.tokens);

}
}
}
if (sSig.sig == bool true && aSig.sig == bool true) {
if (sSig.recipient == aSig.recipient) {
transfer(sSig.recipient, escrow.bond);
if (sSig.recipient == escrow.seller){
unlock = sub(sDeed.locked, escrow.tokens);
sDeed.locked = abs(unlock);
total = sub(sDeed.total, escrow.tokens);
sDeed.total = abs(total);
bDeed.total = add(bDeed.total, escrow.tokens);
bDeed.free = add(bDeed.free, escrow.tokens);
} else {
unlock = sub(sDeed.locked, escrow.tokens);
sDeed.locked = abs(unlock);
sDeed.free = add(sDeed.free, escrow.tokens);

}
}
}
if (bSig.sig == bool true && aSig == bool true) {
if (bSig.recipient == aSig.recipient) {
transfer(bSig.recipient, escrow.bond);
if (bSig.recipient == escrow.seller){
unlock = sub(sDeed.locked, escrow.tokens);
sDeed.locked = abs(unlock);
total = sub(sDeed.total, escrow.tokens);
sDeed.total = abs(total);
bDeed.total = add(bDeed.total, escrow.tokens);
bDeed.free = add(bDeed.free, escrow.tokens);
} else {
unlock = sub(sDeed.locked, escrow.tokens);
sDeed.locked = abs(unlock);
sDeed.free = add(sDeed.free, escrow.tokens);
}
}
}
sWallet.deeds.push(escrow.property, sDeed);
bWallet.deeds.push(escrow.property, bDeed);
storage.wallets.push(escrow.seller, sWallet);
storage.wallets.push(escrow.buyer, bWallet);
}

Conclusion

Congrats on creating a really killer DAPP with Fi. Remember that when making smart contracts that are meant to hold XTZ, we need to think of all possible use cases for each entry to maintain confidence. It would also be a great idea, to thoroughly unit test all functions, as this is only to be used as a reference.

Some Fi Concepts brought up:

  • How to properly utilize the transferfunction.
  • How to use the length() function.
  • The significance of the AMOUNT, and SENDER key words.
  • How to create types by using Fi Structures.
  • The importance of logical checks in entry points.
  • How to use the in function.
  • How to use a set.

Resources