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 · 1 hour

Decentralised co-operative unions with Algorand Multisignature Account

A demonstration of how to run a decentralized co-operative union using the Algornad multisignature implementation. The co-operative has a constitution detailing how the multisignature account should operate. In this example, the rules are set below:

  1. A certain amount of Algo (2 Algos) is deducted from each member’s account into the multisignature co-operative account.
  2. 60% of the co-operative funds is transfered to a randomly selected member who hasn’t been paid in previous rounds. Payments is cyclical.
  3. To allow control of funds, 80% of members must sign the payout transactions. Meaning you need about 80% threshold of all members.

Requirements

Background

Currently, individuals who want to operate a co-operative or group savings account have to rely on a third-party centralised agency. However, a group individuals can use the Algorand blockchain to run such a union and set rules for contributions and payments. The Algorand multisignature account feature allows the individuals to operate a group account, set rules on how it proceeds to that accounts are shared among the members. It handles security and makes it easier to control the funds in such accounts.

The tutorial demonstrates how to process transactions via HTTP request which won’t require an Algorand Node or any third-party API account. This is ideal for developers who want to build using any programming language. It also helps new developers who are not able to set up a node but want to build apps on the chain, do so. Instantiating the client will require a PureStake account or a node set up to do so. For instance, when you use let algodclient = new algosdk.Algodv2(token, server, port);, the developer is required to have a server url, token and port. However, with the API, you are faster in building apps and most developers are familiar with APIs and can get started with Algorand without needed tokens and server ports.

Steps

1. Set up requirements

I am doing this demonstration for 5 accounts who are members of a co-operative.

NOTE. There are helper functions for payments and making HTTPS requests to the API at the end of the script

//inlude the Algorand SDK
<script src="scripts/algosdk.min.js"></script>

//set up the urls for AlgoExplorer. Change to mainnet API //endpoints

const url = "https://api.testnet.algoexplorer.io/v2/transactions/params";
const traxUrl = "https://api.testnet.algoexplorer.io/v2/transactions";

var mnemonic1 = "phone patrol faculty crop exile mom tide want debate post trophy energy collect oil hidden cactus arrow special blossom nerve picture dry spider absent wonder";
  let account1 = algosdk.mnemonicToSecretKey(mnemonic1);
 // console.log(account1);
  var mnemonic2 = "faith roof blame swap neglect kangaroo punch area grow profit guess achieve donate fame host deal casual torch hurdle bulb paddle coconut equal about base";
  let account2 = algosdk.mnemonicToSecretKey(mnemonic2);
  //console.log(account2);
  var mnemonic3 = "dirt weapon various romance abuse awake bonus ecology ginger follow resemble render slice portion imitate since result cheap vital peanut rail winner crunch ability lesson";
  let account3 = algosdk.mnemonicToSecretKey(mnemonic3);
  //console.log(account3);
  var mnemonic4 = "melody swear slot rather dinosaur width tip floor laugh coil copy track flock boy enforce craft equal defy uncle pulp catch basket clap absorb curious";
  let account4 = algosdk.mnemonicToSecretKey(mnemonic4);

2. Create Multisignature Account

Create the multisignature account with the rules. In this example, I set the threshold at 80% which means 80% of members must sign every transaction out of the multisig account

   //Setup the parameters for the multisig corporate account. In the constitution, 80% of members must sign to issue transactions
    var threshold = 3;

   const mparams = {
       version: 1,
       threshold: threshold,
       addrs: [
           account1,
           account2,
           account3,
           account4,

       ],
   };
   var multsigaddr ="CFRR63Y4HWU7WQRIDAE4BJEUXX4RDZC2CURKZUSZYWOYSPZMLN52Y2LNX4"; 
   //console.log(account1.addr);
    const mparamsAddress = {
       version: 1,
       threshold: threshold,
       addrs: [
           account1.addr,
           account2.addr,
           account3.addr,
           account4.addr,

       ],
   };

   if(!multsigaddr){
   let multsigaddr = algosdk.multisigAddress(multsigaddr);

  }

3. Deduct monthly contribution from each member into multisig account

   mparams.addrs.forEach(function (items, index) {
 payer = items.addr;
  let payNote = new Uint8Array("Transaction : " + items.addr);
  var amount = 1000000; //amount to deduct from each member into the corporative multisig account
  recipient = multsigaddr
  //setTimeout(()=> { 
    makePayments (payer, recipient,amount, payNote,items);
//}, 2000);

});

4. Select threshold (signers) and the recipient of the monthly payments

var randomShuffled = mparams.addrs.sort(() => 0.5 - Math.random());

let selected = randomShuffled.slice(0, 4);
var multisigSigners = []; 
selected.forEach(function (itemPay) {

  multisigSigners.push(itemPay.addr);
  //console.log(multisigSigners)
});

   const mparamsAccount = {
       version: 1,
       threshold: threshold,
       addrs:multisigSigners,
   };

   //Assuming 1 member of the savings union received their share of the payments in the last 2 months. The programme will randomly select any of the
//3 remaining members

receivingAccount =  {addrs:[account3.addr,account4.addr,account2.addr],};

let selectedSigners = randomShuffled.slice(0, threshold);

const shuffled = receivingAccount.addrs.sort(() => 0.5 - Math.random());
var PayoutRecipient = shuffled.slice(0,1);

5. Process the payments from the multisig account

The transaction ID and block ID from the payments is shown below.
Successful payment transation ID

// getcorporative account balance
amountUrl = "https://api.testnet.algoexplorer.io/v2/accounts/"+multsigaddr;

              var json_obj = JSON.parse(Get(amountUrl));
             accountBal = json_obj.amount/1000000;

  //pay 60% of coorperative balance to the selected member
amountToPay = Math.round(.6*accountBal);


if (amountToPay>accountBal){alert("You don't have enough funds")}

//the multisignature account needs a threshhold of 80% to sieng a transaction. Select random 4 members from the corporative



//get first and last round params
getParams = JSON.parse(Get(url)); 
var note=  new Uint8Array(0);
var params = {

        "fee": getParams.fee,
        "firstRound": getParams["last-round"],
        "lastRound": getParams["last-round"] + 1000,
        "genesisID": getParams["genesis-id"],
        "genesisHash": getParams["genesis-hash"]


};


var arr= [];

//let each account sign the transactons
selectedSigners.forEach(function (itemPay, index,array) {

   let txn = algosdk.makePaymentTxnWithSuggestedParams(multsigaddr, PayoutRecipient[0], 2000000, undefined, note, params);

//  console.log(txn);
  // console.log(mparamsAccount)
 let signedTxnMerge = algosdk.signMultisigTransaction(txn, mparamsAddress, itemPay.sk).blob

arr.push(signedTxnMerge);

 if (index === array.length - 1){

  let mergedTsigTxn2 = algosdk.mergeMultisigTransactions(arr);
  fetch(traxUrl, {
    method: 'POST', 
      headers: {
    'Content-Type': 'application/x-binary',
     },
      body: mergedTsigTxn2,
     })
    .then(response => response.json())
    .then(data => {
        //data responses will contain the transaction ID. You save or use it for further processing.       
     })
     .catch((error) => {
      console.error('Error:', error);
   });

   }

});
  // console.log(suggestedParams);


  function Get(yourUrl){
    var Httpreq = new XMLHttpRequest(); // a new request
    Httpreq.open("GET",yourUrl,false);
    Httpreq.send(null);
    return Httpreq.responseText;          
}


function makePayments (payer, recipient, amount, payNote,items){           


     params = JSON.parse(Get(url)); 
    params.fee= 1000;

   let suggestedParams = {
       "flatFee": true,
       "fee": params.fee,
       "firstRound":  params["last-round"],
       "lastRound": params["last-round"] + 1000,
       "genesisID": params["genesis-id"],
       "genesisHash": params["genesis-hash"],

   };

    let txn = algosdk.makePaymentTxnWithSuggestedParams(payer, recipient, amount, undefined, payNote, suggestedParams);
     let signedTxnFirst = txn.signTxn(items.sk);

   fetch(traxUrl, {
      method: 'POST', 
       headers: {
        'Content-Type': 'application/x-binary',
       },
       body: signedTxnFirst,
     })
     .then(response => response.json())
     .then(data => {

     })
     .catch((error) => {
       console.error('Error:', error);
     });

}

6. Needed Functions

function Get(yourUrl){
    var Httpreq = new XMLHttpRequest(); // a new request
    Httpreq.open("GET",yourUrl,false);
    Httpreq.send(null);
    return Httpreq.responseText;          
}


function makePayments (payer, recipient, amount, payNote,items){           


     params = JSON.parse(Get(url)); 
    params.fee= 1000;

   let suggestedParams = {
       "flatFee": true,
       "fee": params.fee,
       "firstRound":  params["last-round"],
       "lastRound": params["last-round"] + 1000,
       "genesisID": params["genesis-id"],
       "genesisHash": params["genesis-hash"],

   };

    let txn = algosdk.makePaymentTxnWithSuggestedParams(payer, recipient, amount, undefined, payNote, suggestedParams);
     let signedTxnFirst = txn.signTxn(items.sk);

   fetch(traxUrl, {
      method: 'POST', 
       headers: {
        'Content-Type': 'application/x-binary',
       },
       body: signedTxnFirst,
     })
     .then(response => response.json())
     .then(data => {

     })
     .catch((error) => {
       console.error('Error:', error);
     });

}

7. Github repository

https://github.com/bayuobie/Group-contributions-with-Algorand