Solutions
No Results

Create Publication

We are looking for publications that demonstrate building dApps or smart contracts!
See the full list of Gitcoin bounties that are eligible for rewards.

Solution

Decentralized Market Place for NFTs Using Go and Vue

Overview

This solution guides you in setting up a decentralized market place, using the power of Atomic transactions and stateless smart contracts. The core of the application lies in the TEAL program, and although we use Golang and Vue.js to interact with it, absolutely anyone can exchange NFTs with our TEAL programs, using the goal command line tool or SDKs.

  • Golang will be used as a backend, generates the TEAL programs and the transactions.
  • Vue.js will act as a frontend to our application
  • MyAlgo Connect by Randlabs will enable the user sign transactions from their browser.

Why use stateless smart contracts ?

The added value of the solution lies in the fact that when the user wants to sell an asset, they create a stateless smart contract account which stores the assets they are selling. The user sets an amount of ALGO or any asset ID (for example USDC or Monerium EUR), that can be used to get an asset from the contract account. The contract account accepts groups of atomic transactions:

  1. The buyer opts-in the asset being sold.
  2. The contract account sends 1 asset.
  3. The buyer sends the configured amount of ALGO/Assets.

This means than anyone with the right conditions can interact and buy assets from this account, which is exactly the goal of decentralized finance: there are no parties to trust, and our application just makes the creation of these contracts easier. In no cases it can revoke the selling, hence the user feels safe. The solution even gives an endpoint that lists all contract accounts it created, what asset they sell and the amount ALGO/assets to buy from it. Anyone can then build their own application and query this endpoint. These transactions being grouped in an “atomic way” brings this interesting feature: if one of them fails, the remaining ones are canceled. The output of the atomic group is known: we either get the expected result or nothing.

Introduction

This guide uses Vue.js and Golang, but any other frontend frameworks and programming languages with algorand-sdk would do. For this program to work, it needs to interact with the Algorand blockchain. If you do not know what that means, you should first check this link that describes how to connect to the Algorand blockchain.

Requirements

Environment setup

You can get the code here: GitHub

Once downloaded, open two terminals. In the first one, run:
$ cd AlgoDecentralizedMarketgo/smartcontract && go get
Then, modify helpers.go to set the following variables:

const algodToken = ""
const algodAddress = ""
const mnemonic1 = "25 words"
const indexerAddress = ""
const indexerToken = ""

The program needs an account to allow transparent opts-in, hence the need for a mnemonic.
Afterwards, run:

go run *.go

The backend is now on, and listens on 0.0.0.0:8081, which means that it reachable on all network interfaces.
In the second terminal, run:

$ cd AlgoDecentralizedMarketgo/frontend && npm install

Then open src/components/Charts.vue, and modify line 79 to add a node configuration. If you are running a sandbox environment, it will look like the following:

const algodClient = new algosdk.Algodv2('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'http://localhost', 4001);

Afterwards, start the frontend:

$ npm run serve

The frontend is now listening on on 0.0.0.0:8080.

Application’s core: the stateless smart contract.

As previously stated, the frontend and backend are here to facilitate the creation of the contract account that do the heavy lifting. Let’s focus on what function it must fulfill:

  • Any operations that are not listed below MUST be disabled, for the integrity of the contract account.
  • The smart contract must be given the asset to sell. To be able to hold this asset, it must first opt-in.
  • The smart contract can sell this asset if it is given the right amount of payment asset / ALGO. To be able to accept the payment assets, it must be able to opt-in these assets.
  • The creator of the smart contract can at any point withdraw any asset or ALGO the contract account holds.

Three smart contract are setup, for different payment supported: only ALGO, ALGO and one asset, ALGO and 2 assets. Let’s have look at the latter smart contract, as it is the most complete one, and check that it follows the above requirements. We first disable any rekeing and AssetCloseTo, as they are unwanted operations that might harm the contract account:

#pragma 3
gtxn 0 AssetCloseTo
global ZeroAddress
==
assert 
gtxn 1 AssetCloseTo
global ZeroAddress
==
assert 
gtxn 0 RekeyTo
global ZeroAddress
==
assert 
gtxn 1 RekeyTo
global ZeroAddress
==
assert
gtxn 0 AssetCloseTo
global ZeroAddress
==
assert
gtxn 1 AssetCloseTo
global ZeroAddress
==
assert
gtxn 0 Fee
int 2000
<=
assert
gtxn 1 Fee 
int 2000
<=
assert

We then check if the amount of grouped transactions. A user wants to buy the asset, they opt-in for the asset being sold, send the required amount of payment asset or ALGO, receive the asset and pay the fees for the whole transaction group. This brings our atomic group to four transactions.
EditorImages/2021/06/16 13:31/premier.png
If there are two transactions, branch to a later part of the code. If there are less than two or more than three transaction, the group of transaction will fail:

global GroupSize
int 2
==
bnz optIn
global GroupSize
int 4
==
assert

We then check more parameters to be safe against abuse of the logic to drain the account’s ALGO amount. More about guidelines against bad practices here

gtxn 2 AssetCloseTo
global ZeroAddress
==
assert 
gtxn 2 AssetCloseTo
global ZeroAddress
==
assert 
gtxn 2 RekeyTo
global ZeroAddress
==
assert
gtxn 3 AssetCloseTo
global ZeroAddress
==
assert 
gtxn 3 AssetCloseTo
global ZeroAddress
==
assert 
gtxn 3 RekeyTo
global ZeroAddress
==
assert
gtxn 2 Fee 
int 2000
<=
assert
gtxn 3 Fee 
int 2000
<=
assert

Final check before the program core starts: check if the user is paying the fee for the contract account:

gtxn 3 TypeEnum 
int 1
==
assert
gtxn 1 Fee 
gtxn 3 Amount
==
assert
gtxn 3 Receiver 
gtxn 1 Sender 
==
assert

Now the real logic starts. We first check that the first 3 transactions are assets transfers: a 0 asset transfer for opt-in, an asset transfer from buyer to contract account, and an asset transfer from contract account to buyer. If the third transaction is not an asset transfer, it better be an ALGO payment. Branches to check that later.

gtxn 0 TypeEnum
int 4
==
gtxn 1 TypeEnum
int 4
==
&&
gtxn 0 XferAsset
int %d
==
&&
gtxn 1 XferAsset
int %d
==
&&
gtxn 0 AssetAmount
int 0
==
&&
gtxn 1 AssetAmount
int 1
==
&&
gtxn 0 Sender
gtxn 0 AssetReceiver
==
&&
gtxn 2 TypeEnum
int 4
==
bz algoPayment
gtxn 1 Sender 
gtxn 2 AssetReceiver 
==
&&
gtxn 2 XferAsset 
int %d
==
bz secondAsset
gtxn 2 AssetAmount
int %d
==
&&
return

If it was the first asset, the program is done ! If it was not, check that the ID and amount of second asset are right:

secondAsset:
gtxn 2 XferAsset
int %d
==
assert
gtxn 2 AssetAmount
int %d
==
&&
return

If it was not an asset being sent as payment, but ALGO, check that the transaction is an ALGO payment with right amount of ALGO:

algoPayment:
gtxn 1 Sender 
gtxn 2 Receiver 
==
&&
gtxn 2 TypeEnum
int 1
==
&&
gtxn 2 Amount
int %d
==
&&
return

We now covered all the cases for a user buying an asset from the contract account. 2 Cases are left: contract opts-in, and withdrawal from owner.
We branched from checking the amount of transactions. We now check if there are 2 of them. If not, the program fails.
We also check the type of the second transaction: if it’s not an asset transfer, branch to the next part.
EditorImages/2021/06/16 13:51/second.png

optIn:
global GroupSize
int 2
==
gtxn 1 TypeEnum 
int 4
==
bz secondOptin
gtxn 0 TypeEnum
int 4
==
&&
gtxn 0 XferAsset
int %d
==
&&
gtxn 1 XferAsset 
int %d
==
&&
gtxn 0 AssetAmount
int 0
==
&&
gtxn 0 Sender
gtxn 0 AssetReceiver
==
&&
gtxn 1 Sender 
addr %s
==
&&
return 

If the asset ID is not the first configured by our backend, it means it must be an opt-in for payment assets. In that case, first transaction must be opt-in, second transaction an ALGO payment from the application to contract account, so our application can transparently opt-in the assets for the user.

secondOptin:
gtxn 0 XferAsset
int %d
==
bz thirdOptin
gtxn 1 TypeEnum 
int 1
==
gtxn 0 AssetAmount
int 0
==
bz withdraw
gtxn 0 Sender
gtxn 0 AssetReceiver
==
&&
gtxn 1 Sender 
addr %s
==
&&
return
thirdOptin:
gtxn 0 XferAsset
int %d
==
bz withdraw
gtxn 1 TypeEnum 
int 1
==
gtxn 0 AssetAmount
int 0
==
bz withdraw
gtxn 0 Sender
gtxn 0 AssetReceiver
==
&&
gtxn 1 Sender 
addr %s
==
&&
return

Last case, the seller wants to withdraw. First transaction is the withdrawal, second transaction is an ALGO payment from the creator to the contract account, which covers the fees:

withdraw:
gtxn 1 Amount 
gtxn 0 Fee 
==
gtxn 0 Receiver
addr %s
==
bz assetWithdraw
gtxn 1 Sender
addr %s
==
&&
return
assetWithdraw:
gtxn 0 AssetReceiver
addr %s
==
&&
gtxn 1 Sender 
addr %s
==
&&
return

EditorImages/2021/06/16 14:26/trois.png
We finally have a fully functional and secure contract account! The goal is now to setup an application to create and list these accounts.

Backend: Creation and listing of contract accounts

We need a way to create, store, and interact with the contract account. To do so, let’s run a backend that has endpoints to trigger functions. This way, we enable interactions with both the frontend or any client that anyone could write.

cors := handlers.CORS(
        handlers.AllowedHeaders([]string{"content-type"}),
        handlers.AllowedOrigins([]string{"*"}),
    )
    r := mux.NewRouter()
    r.HandleFunc("/createEscrow", createEscrow)
    r.HandleFunc("/lookup", lookupAssets)
    r.HandleFunc("/getSellings", lookupEscrowAssets)
    r.HandleFunc("/buy", buy)
    r.HandleFunc("/lookupSellings", lookupSellings)
    r.HandleFunc("/withdraw", withdraw)
    r.Use(cors)
    log.Fatal(http.ListenAndServe(":8081", (r)))

Generating the TEAL Programs

When creating an contract account, the application needs to generate a TEAL program with the parameters from the endpoint’s http request. To do so, multiple function call the generateTeal function:

func (c *AlgoClient) generateTeal(p Choice) (lsig types.LogicSig, addr string, err error) {
    var (
        sk ed25519.PrivateKey
        ma crypto.MultisigAccount
        teal []byte
    )
    account1 := getAddress(mnemonic1)
    if p.PaymentAssetId == 0 {
        teal = ##Generates a teal with only algo payment
    } else {
        if p.SecondPaymentAssetId == 0 {
            teal = ##Generates a teal with algo or one asset payment
        } else {
            teal = ##Generates a teal with algo or one of the two asset payment
        }
    }

    response, err := c.c.TealCompile(teal).Do(context.Background())
    if err != nil {
        return
    }
    program, err :=  base64.StdEncoding.DecodeString(response.Result)   
    lsig, err = crypto.MakeLogicSig(program, nil, sk, ma)
    if err != nil {
        return 
    }
    addr = crypto.LogicSigAddress(lsig).String()
    err = ioutil.WriteFile(addr, teal, 0644)
    if err != nil {
        return
    }
    return
}

Create a contract account

The following function does:

  1. Create an algod client
  2. Generate a TEAL program, based on the parameters given in the request body.
  3. Send ALGO from the program’s account, to activate the contract account.
  4. Make the contract account opt-in the two assets it accepts as payment.
  5. Create a transaction group, one asset transfer from user to contract account, one opt-in for the contract account to accept this transfer. It then signs the opt-in transaction with the logic, and sends back to two transactions. The user needs to sign the other transaction and send both to the network.
  6. Saves the contract account address and payment assets accepted into a variable. This is fine in development, however setting up a database would be needed for production.

type Selling struct {
    Asset   uint64
    Address string  
    FirstAsset  uint64
    FAmount   uint64
    SecondAsset uint64
    SAmount uint64
    AlgoAmount uint64
    CreatorAddress string
}
var sellings []Selling

func createEscrow(w http.ResponseWriter, r *http.Request) {
    var p Choice

    decoder := json.NewDecoder(r.Body)
    err := decoder.Decode(&p)
    if err != nil {
        log.Printf("Error decoding body: %v\n", err)
        http.Error(w, err.Error(), 400)
        return
    }
    account1 := getAddress(mnemonic1)
    sk1, err := mnemonic.ToPrivateKey(mnemonic1)

    var algoClient AlgoClient
    algoClient.c, err = algod.MakeClient(algodAddress, algodToken)
    if err != nil {
        log.Printf("failed to make algod client: %v\n\n", err) 
        http.Error(w, err.Error(), 500)
        return
    }
    lsig, addr, err := algoClient.generateTeal(p)
    if err != nil {
        log.Printf("Error compiling teal: %v\n", err)
        http.Error(w, err.Error(), 500)
        return
    }

    genID, genHash, minFee, firstValidRound, lastValidRound, err := algoClient.getParams()
    if err != nil {
        log.Printf("Error getting suggested parameters: %v\n", err)
        http.Error(w, err.Error(), 500)
        return
    }
    var amount uint64 = 1000000
    tx1, err := transaction.MakePaymentTxnWithFlatFee(account1, addr, minFee, amount, firstValidRound, lastValidRound, nil, "", genID, genHash)
    if err != nil {
        log.Printf("Error creating transaction: %v\n\n", err)
        http.Error(w, err.Error(), 400)
        return
    }

    _, stx1, err := crypto.SignTransaction(sk1, tx1)
    if err != nil {
        log.Printf("Failed to sign transaction: %s\n", err)
        http.Error(w, err.Error(), 500)
        return
    }

    pendingTxID, err := algoClient.c.SendRawTransaction(stx1).Do(context.Background())
    if err != nil {
        log.Printf("Failed to send transaction: %s\n", err)
        http.Error(w, err.Error(), 500)
        return
    }
    err = algoClient.opTin(p.PaymentAssetId, addr, account1, lsig, sk1)

    if err != nil {
        log.Printf("Failed to opt-in asset %d: %v\n", p.PaymentAssetId, err)
        http.Error(w, err.Error(), 400)
        return
    }
    if p.SecondPaymentAssetId != 0 {
        err = algoClient.opTin(p.SecondPaymentAssetId, addr, account1, lsig, sk1)
        if err != nil {
            log.Printf("Failed to opt-in asset %d: %v\n", p.SecondPaymentAssetId, err)
            http.Error(w, err.Error(), 400)
            return
        }
    }

    err = algoClient.waitForConfirmation(pendingTxID)
    if err != nil {
        log.Printf("Failed to Send algos to %s: %v\n", addr, err)
        http.Error(w, err.Error(), 500)
        return
    }

    txGroup, err := fundEscrow(p, addr, &algoClient, w)
    if err != nil {
        log.Printf("Error funding the account %s: %v\n", addr, err)
        http.Error(w, err.Error(), 400)
        return
    }

    var toSave Selling
    toSave.Asset = p.AssetId
    toSave.Address = addr 
    toSave.FirstAsset = p.PaymentAssetId
    toSave.FAmount = p.PaymentAssetAmount
    toSave.SecondAsset = p.SecondPaymentAssetId
    toSave.SAmount = p.SecondPaymentAssetAmount
    toSave.AlgoAmount = p.AlgoAmount
    toSave.CreatorAddress = p.CreatorAddress
    sellings = append(sellings, toSave)
    if err := json.NewEncoder(w).Encode(txGroup); err != nil {
        log.Printf("Error sending back response: %s", err)
    }
}

Buy from a contract account.

To buy from the contract, you need four transactions:

  1. Buyer opts-in = Asset transfer from buyer to buyer, asset amount is zero.
  2. Contract account sends 1 asset to buyer
  3. Buyer sends correct amount of payment asset or ALGO to contract account.
  4. Buyer sends fee amount for second transaction to contract account.

The buy function builds those 4 transactions, groups them, signs the second one with logic, and returns the whole group for the buyer to sign remaining transactions.

func buy(w http.ResponseWriter, r *http.Request) {

    txn1, err := transaction.MakeAssetAcceptanceTxn(tx.Sender, 1, firstValidRound, lastValidRound, nil, genID, genHash64, tx.ToBuy)
    if err != nil {
        log.Printf("Error making opt-in transaction of account %s for asset %d: %v\n", tx.Sender, tx.ToBuy, err)
        http.Error(w, err.Error(), 400)
        return
    }
    txn2, err := transaction.MakeAssetTransferTxnWithFlatFee(tx.Address, tx.Sender, "", 1, minFee, firstValidRound, lastValidRound, nil,
        genID, genHash64, tx.ToBuy)
    if err != nil {
        log.Printf("Failed making asset transfer for asset %d from %s to %s: %v\n\n", tx.ToBuy, tx.Address, tx.Sender, err)
        http.Error(w, err.Error(), 400)
        return
    }
    var txn3 types.Transaction
    if tx.AlgoAmount != 0 {
        txn3, err = transaction.MakePaymentTxnWithFlatFee(tx.Sender, tx.Address, minFee, tx.AlgoAmount, firstValidRound, lastValidRound, nil, "", genID, genHash)
        if err != nil {
            log.Printf("Error making payment transaction of %d Algos from %s to %s: %v\n\n", tx.AlgoAmount, tx.Sender, tx.Address, err)
            http.Error(w, err.Error(), 400)
            return
        }
    } else {
        txn3, err = transaction.MakeAssetTransferTxnWithFlatFee(tx.Sender, tx.Address, "", tx.Amount, minFee, firstValidRound, lastValidRound, nil,
        genID, genHash64, tx.ToPay)
        if err != nil {
            log.Printf("Failed making asset transfer of asset %d from %s to %s: %v\n\n", tx.ToPay, tx.Sender, tx.Address, err)
            http.Error(w, err.Error(), 400)
            return
        }
    }
    txn4, err := transaction.MakePaymentTxnWithFlatFee(tx.Sender, tx.Address, minFee, minFee, firstValidRound, lastValidRound, nil, "", genID, genHash)
    if err != nil {
        log.Printf("Failed making payment transaction of amount %d from %s to %s: %v\n\n", minFee, tx.Sender, tx.Address, err)
            http.Error(w, err.Error(), 400)
            return
    }
    teal, err := readTeal(tx.Address)
    if err != nil {
        log.Printf("Error reading teal: %v\n", err)
        http.Error(w, err.Error(), 500)
        return
    }
    response, err := algoClient.c.TealCompile(teal).Do(context.Background())
    if err != nil {
        log.Printf("Error compiling teal: %v\n", err)
        http.Error(w, err.Error(), 400)
        return
    }

    program, err :=  base64.StdEncoding.DecodeString(response.Result)   
    lsig, err := crypto.MakeLogicSig(program, nil, sk, ma)
    if err != nil {
        log.Printf("Error signing teal: %v\n", err)
        http.Error(w, err.Error(), 400)
        return
    }

    gid, err := crypto.ComputeGroupID([]types.Transaction{txn1, txn2, txn3, txn4})
    if err != nil {
        log.Printf("Error calculating the group ID: %v\n", err)
        http.Error(w, err.Error(), 400)
        return
    }
    txn1.Group = gid
    txn2.Group = gid
    txn3.Group = gid
    txn4.Group = gid
    _, stx2, err := crypto.SignLogicsigTransaction(lsig, txn2)
    if err != nil {
        log.Printf("Error signing with logic: %v\n", err)
        http.Error(w, err.Error(), 400)
        return
    }
    txGroup.FirstTx = txn1
    txGroup.SecondTx = stx2
    txGroup.ThirdTx = txn3
    txGroup.ForthTx = txn4

    if err := json.NewEncoder(w).Encode(txGroup); err != nil {
        log.Printf("Error sending back response: %v\n", err)
    }
}

Withdral

Finally, we need the seller to be able to withdraw any amount of assets / ALGO from the contract account. To do so, we build two transactions:

  1. Asset or ALGO from contract account to seller.
  2. Seller pays the fee for the first transaction.
    By hard coding the seller address into the second transaction, we make sure only they can withdraw.

if (withdraw.Algo != 0){
        txn1, err = transaction.MakePaymentTxnWithFlatFee(withdraw.Address, withdraw.Creator, minFee, withdraw.Algo, firstValidRound, lastValidRound, nil, "", genID, genHash)
        if err != nil {
            log.Printf("Error making payment transaction of %d Algos from %s to %s: %v\n", withdraw.Algo, withdraw.Address, withdraw.Creator, err)
            http.Error(w, err.Error(), 400)
            return
    }
    } else {
        txn1, err = transaction.MakeAssetTransferTxnWithFlatFee(withdraw.Address, withdraw.Creator, "", withdraw.AssetAmount, minFee, firstValidRound, lastValidRound, nil,
        genID, genHash64, withdraw.AssetID)
        if err != nil {
            log.Printf("Failed to transfer asset %d from %s to %s: %v\n", withdraw.AssetID, withdraw.Address, withdraw.Creator, err)
            http.Error(w, err.Error(), 400)
            return
    }
    }
    txn2, err := transaction.MakePaymentTxnWithFlatFee(withdraw.Creator, withdraw.Address, minFee, minFee, firstValidRound, lastValidRound, nil, "", genID, genHash)
    if err != nil {
        log.Printf("Error making payment transaction of %d Algo from %s to %s: %v\n\n", minFee, withdraw.Creator, withdraw.Address,err)
        http.Error(w, err.Error(), 400)
        return
    }

Frontend: interact with the backend

Frontend is a simple page that holds function to query backend’s endpoint with the right parameters. It also integrates myAlgo connect to let users sign their transactions with ease.

Let’s go through a workflow to create, buy from, and withdraw from a contract account.

Connect to MyAlgo

First step is to connect to MyAlgo, so we can sign the transactions. Press “Connect to MyAlgo” at the top right corner, and, if you have multiple addresses, select one to use.

Sell an asset

Go over the “Sell an asset” tab, hit the button, and choose an asset to sell with parameters:
- How much to sell ?
- How many ALGO for one asset ?
- (Optionnal) Other payment assets.
EditorImages/2021/06/17 10:25/Screen_Shot_2021-06-17_at_11.50.27.png
Sign the transaction with the myAlgo pop-up. You can copy the “to” address to check if the contract account has been successfully created:

sandbox % ./sandbox goal account dump -a LJMSUTONI44VQIG35JHGJ54QHN2NPPHBAJWBBNIRRD6HXXFGIRQTA6CGGY
{
  "addr": "LJMSUTONI44VQIG35JHGJ54QHN2NPPHBAJWBBNIRRD6HXXFGIRQTA6CGGY",
  "algo": 999000,
  "asset": {
    "146": {},          ##Opted-in the asset it accepts as payment
    "177": {
      "a": 100          ##Has the asset to sell
    }
  },
  "ebase": 4753
}

Buy an asset

List the assets being sold, and buy one of them.
Let’s buy the asset 177 with 100 asset 146:
EditorImages/2021/06/17 10:28/Screen_Shot_2021-06-17_at_12.12.03.png

Sign the three transactions:

  1. opt-in asset 177
  2. send 100 asset 146
  3. pay the transactions fees.

Once this is done, check the contract account again:

sandbox % ./sandbox goal account dump -a LJMSUTONI44VQIG35JHGJ54QHN2NPPHBAJWBBNIRRD6HXXFGIRQTA6CGGY
{
  "addr": "LJMSUTONI44VQIG35JHGJ54QHN2NPPHBAJWBBNIRRD6HXXFGIRQTA6CGGY",
  "algo": 999000,
  "asset": {
    "146": {
      "a": 100          ##Got the right amount of payment asset
    },
    "177": {
      "a": 99           ##It sold one
    }
  },
  "ebase": 4759
}

Creator withdraws

Finally, we, as creator, want to withdraw assets from the contract account. Go to the withdraw tab and hit the button. Let’s withdraw the 100 asset 146 we got from a buyer:
EditorImages/2021/06/17 10:31/Screen_Shot_2021-06-17_at_12.19.28.png
Hit Withdraw. Check the contract account again:

sandbox % ./sandbox goal account dump -a LJMSUTONI44VQIG35JHGJ54QHN2NPPHBAJWBBNIRRD6HXXFGIRQTA6CGGY
{
  "addr": "LJMSUTONI44VQIG35JHGJ54QHN2NPPHBAJWBBNIRRD6HXXFGIRQTA6CGGY",
  "algo": 999000,
  "asset": {
    "146": {},
    "177": {
      "a": 99
    }
  },
  "ebase": 4761
}

Success ! We managed to sell, buy and withdraw assets from the contract account.

Conclusion

We created a robust, functional and secure program, that enables decentralized finance: once the user creates the contract account, no one can stop it and anyone can exchange assets or ALGO from it.
We then created a backend, to provide endpoints that interacts with TEAL programs and contract accounts.
Finally, the frontend allows users to interact with the backend in a user friendly way so they can easily sell, buy and withdraws from contract account.
The solution is suited for development environment, and one needs to cover the edges -for example, setting up a database to store contract account addresses. But this is a really robust base, that one can with ease build upon for a decentralized market place.

July 08, 2021