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.

Tutorial Thumbnail
Intermediate · 30 minutes

LimitOrder Contract with Go

This tutorial is intended to help you call a Limit Order Contract using Go. Templates are prebuilt TEAL programs that allow parameters to be injected into them from the SDKs that configure the contract. In this example, we are going to instantiate the LimitOrder Template and show how it can be used with a transaction.

Requirements

Background

Algorand provides many templates for Smart Contract implementation in the SDKs. The LimitOrder Contract is just one of the templates and is described in the reference documentation. Limit Order Contracts are contract accounts that can disburse Algos for Algorand Assets in a defined ratio. Additionally, a minimum withdrawal amount can be set. If the funds are not claimed after a certain period of time, the original owner can reclaim the remaining funds.

Steps

1. Get Asset Owner and the Contract Owner Accounts

The Limit Order template can be instantiated with a set of predefined parameters that configure the Limit Order contract. These parameters should not be confused with Transaction parameters that are passed into the contract when using the Limit Order. These parameters configure how the Limit Order will function:

  • TMPL_ASSET: Integer ID of the asset
  • TMPL_SWAPN: Numerator of the exchange rate (TMPL_SWAPN assets per TMPL_SWAPD microAlgos, or better)
  • TMPL_SWAPD: Denominator of the exchange rate (TMPL_SWAPN assets per TMPL_SWAPD microAlgos, or better)
  • TMPL_TIMEOUT: The round after which all of the algos in this contract may be closed back to TMPL_OWN
  • TMPL_OWN: The recipient of the asset (if the order is filled), or of the contract’s algo balance (after TMPL_TIMEOUT)
  • TMPL_FEE: The maximum fee used in any transaction spending out of this contract
  • TMPL_MINTRD: The minimum number of microAlgos that may be spent out of this contract as part of a trade

So for example, If you want to specify that the contact will approve a transaction where it is willing to spend 3000 microAlgos for 1 Asset with id of TMPL_ASSET, you would set the TMPL_SWAPN parameter to 1 and the TMPL_SWAPD to 3000. The TMPL_MINTRD should be set to 2999 in this example as it must be less than the amount of microAlgos for the one asset.

For this tutorial, we first need to define, the owner of the contract, and the owner of the asset with asset id = TMPL_ASSET.

package main

import (
    "fmt"
    "github.com/algorand/go-algorand-sdk/mnemonic"
    "github.com/algorand/go-algorand-sdk/client/algod"
    "github.com/algorand/go-algorand-sdk/crypto"
    "github.com/algorand/go-algorand-sdk/future"
    "github.com/algorand/go-algorand-sdk/templates"
    "github.com/algorand/go-algorand-sdk/types"

)

// This example creates a contract account
func main() {
    // CHANGE ME
    const algodAddress = "http://<your-algod-host>:<your-algod-port>"
    const algodToken = "<your-api-token>"

    // Create an algod client
    algodClient, err := algod.MakeClient(algodAddress, algodToken)
    if err != nil {
        fmt.Printf("failed to make algod client: %s\n", err)
        return
    }
    // Inputs 
    owner := "AJNNFQN7DSR7QEY766V7JDG35OPM53ZSNF7CU264AWOOUGSZBMLMSKCRIU"

    // Recover accounts used in example
    // Account 1 is the asset owner 
    // Used later in the example
    assetOwnerMnemonic := "portion never forward pill lunch organ biology" +
        " weird catch curve isolate plug innocent skin grunt" +
        " bounce clown mercy hole eagle soul chunk type absorb trim"
    sk1, _ := mnemonic.ToPrivateKey(assetOwnerMnemonic)
}   

2. Create Limit Order Template

The template can now be created with the correct ratio for assets to microAlgos.

package main

import (
    "fmt"
    "github.com/algorand/go-algorand-sdk/mnemonic"
    "github.com/algorand/go-algorand-sdk/client/algod"
    "github.com/algorand/go-algorand-sdk/crypto"
    "github.com/algorand/go-algorand-sdk/future"
    "github.com/algorand/go-algorand-sdk/templates"
    "github.com/algorand/go-algorand-sdk/types"

)

// This example creates a contract account
func main() {
    // CHANGE ME
    const algodAddress = "http://<your-algod-host>:<your-algod-port>"
    const algodToken = "<your-api-token>"

    // Create an algod client
    algodClient, err := algod.MakeClient(algodAddress, algodToken)
    if err != nil {
        fmt.Printf("failed to make algod client: %s\n", err)
        return
    }
    // Inputs 
    owner := "AJNNFQN7DSR7QEY766V7JDG35OPM53ZSNF7CU264AWOOUGSZBMLMSKCRIU"

    // Recover accounts used in example
    // Account 1 is the asset owner 
    // Used later in the example
    assetOwnerMnemonic := "portion never forward pill lunch organ biology" +
        " weird catch curve isolate plug innocent skin grunt" +
        " bounce clown mercy hole eagle soul chunk type absorb trim"
    sk1, _ := mnemonic.ToPrivateKey(assetOwnerMnemonic)

    // Inputs
    // Limit contract should be two receivers in an
    // ratn/ratd ratio of assetid to microalgos
    // ratn is the number of assets
    // ratd is the number of microAlgos 
    ratn := 1
    ratd := 3000
    expiryRound := uint64(5000000)
    maxFee := uint64(2000)
    minTrade := 2999
    // assetID is the asset id number
    // of the asset to be traded
    assetID := 319290;
    //Instaniate the Template
    limit, err := templates.MakeLimitOrder(owner, uint64(assetID), uint64(ratn), uint64(ratd), expiryRound, uint64(minTrade), maxFee)
    if err != nil {
        fmt.Printf("Creating Contract Failed with %v\n", err)
    }
    // At this point the contract address can be funded
    // The program bytes can also be saved off 
    // to be used at a later time or in a
    // different application
    program := limit.GetProgram()
    addr := limit.GetAddress() 
    fmt.Printf("Escrow Address: %s\n" , addr )  
}   

At this point, the Limit Order contract account must be funded before any transactions can be issued against it. Use the dispenser to do this now. Also, note that program bytes can be saved to use at a later time or in another application at this point.


Learn More
- Add Funds using Dispenser
- Smart Contracts - Contract Accounts

3. Create and Submit Transaction against Contract

The Limit Order contract template will soon offer a helper function GetSwapAssetsTransaction. In this tutorial, we create this function within the example. This function takes an assetAmount and a microAlgoAmount for the total transaction and creates two transactions, one for each receiver and then groups these two transactions into an atomic transfer. The first transaction is signed with the program logic and the second transaction is signed with the secret key of the asset owner. The bytes of the grouped transactions to submit are returned. These bytes can then be submitted to the blockchain.

package main

import (
    "fmt"
    "github.com/algorand/go-algorand-sdk/mnemonic"
    "github.com/algorand/go-algorand-sdk/client/algod"
    "github.com/algorand/go-algorand-sdk/crypto"
    "github.com/algorand/go-algorand-sdk/future"
    "github.com/algorand/go-algorand-sdk/templates"
    "github.com/algorand/go-algorand-sdk/types"

)
// GetSwapAssetsTransaction is a
// Utility function to create the two grouped transactions
// This should be replaced in the future with LimitOrder 
// GetSwapAsssetsTransaction function
func GetSwapAssetsTransaction(assetAmount uint64, microAlgoAmount uint64, contract, secretKey []byte, params types.SuggestedParams, owner string, assetID uint64) ([]byte, error) {
    var buyerAddress types.Address
    copy(buyerAddress[:], secretKey[32:])
    contractAddress := crypto.AddressFromProgram(contract)
    algosForAssets, err := future.MakePaymentTxn(contractAddress.String(), buyerAddress.String(), microAlgoAmount, nil, "", params)
    if err != nil {
        return nil, err
    }
    assetsForAlgos, err := future.MakeAssetTransferTxn(buyerAddress.String(), owner, assetAmount, nil, params, "", assetID)
    if err != nil {
        return nil, err
    }

    gid, err := crypto.ComputeGroupID([]types.Transaction{algosForAssets, assetsForAlgos})
    if err != nil {
        return nil, err
    }
    algosForAssets.Group = gid
    assetsForAlgos.Group = gid

    logicSig, err := crypto.MakeLogicSig(contract, nil, nil, crypto.MultisigAccount{})
    if err != nil {
        return nil, err
    }
    _, algosForAssetsSigned, err := crypto.SignLogicsigTransaction(logicSig, algosForAssets)
    if err != nil {
        return nil, err
    }
    _, assetsForAlgosSigned, err := crypto.SignTransaction(secretKey, assetsForAlgos)
    if err != nil {
        return nil, err
    }

    var signedGroup []byte
    signedGroup = append(signedGroup, algosForAssetsSigned...)
    signedGroup = append(signedGroup, assetsForAlgosSigned...)

    return signedGroup, nil
}
// This example creates a contract account
func main() {
    // CHANGE ME
    const algodAddress = "http://<your-algod-host>:<your-algod-port>"
    const algodToken = "<your-api-token>"

    // Create an algod client
    algodClient, err := algod.MakeClient(algodAddress, algodToken)
    if err != nil {
        fmt.Printf("failed to make algod client: %s\n", err)
        return
    }
    // Inputs 
    owner := "AJNNFQN7DSR7QEY766V7JDG35OPM53ZSNF7CU264AWOOUGSZBMLMSKCRIU"

    // Recover accounts used in example
    // Account 1 is the asset owner 
    // Used later in the example
    assetOwnerMnemonic := "portion never forward pill lunch organ biology" +
        " weird catch curve isolate plug innocent skin grunt" +
        " bounce clown mercy hole eagle soul chunk type absorb trim"
    sk1, _ := mnemonic.ToPrivateKey(assetOwnerMnemonic)

    // Inputs
    // Limit contract should be two receivers in an
    // ratn/ratd ratio of assetid to microalgos
    // ratn is the number of assets
    // ratd is the number of microAlgos 
    ratn := 1
    ratd := 3000
    expiryRound := uint64(5000000)
    maxFee := uint64(2000)
    minTrade := 2999
    // assetID is the asset id number
    // of the asset to be traded
    assetID := 319290;
    //Instaniate the Template
    limit, err := templates.MakeLimitOrder(owner, uint64(assetID), uint64(ratn), uint64(ratd), expiryRound, uint64(minTrade), maxFee)
    if err != nil {
        fmt.Printf("Creating Contract Failed with %v\n", err)
    }
    // At this point the contract address can be funded
    // The program bytes can also be saved off 
    // to be used at a later time or in a
    // different application
    program := limit.GetProgram()
    addr := limit.GetAddress() 
    fmt.Printf("Escrow Address: %s\n" , addr )  


    // Create a set of grouped transactions against
    // the limit contract
    sp, err := algodClient.BuildSuggestedParams()
    if err != nil {
        fmt.Printf("Failed to Build Suggested Parameters with %v\n", err)
    }
    assetAmount := 1
    microAlgoAmount := 3000
    sp.FlatFee = true
    sp.Fee = 1000
    txnBytes, err := GetSwapAssetsTransaction(uint64(assetAmount), uint64(microAlgoAmount), program, sk1, sp, owner, uint64(assetID))
    if err != nil {
        fmt.Printf("Creating Transactions Failed with %v\n", err)
    }
    txIDResponse, err := algodClient.SendRawTransaction(txnBytes)
    if err != nil {
        fmt.Printf("Sending failed with %v\n", err)
    }
    fmt.Printf("Transaction: %v\n", txIDResponse.TxID)
}


Learn More
- Atomic Transfers