Skip to content

Instantly share code, notes, and snippets.

@Riddlerrr
Created November 11, 2021 18:27
Show Gist options
  • Save Riddlerrr/adc93750a4ba4191e03a915cde1d44cc to your computer and use it in GitHub Desktop.
Save Riddlerrr/adc93750a4ba4191e03a915cde1d44cc to your computer and use it in GitHub Desktop.
Algorand group transactions
import json
import time
import base64
import os
from algosdk.v2client import algod
from algosdk import mnemonic
from algosdk.future import transaction
from algosdk import encoding
from algosdk import account
from algosdk import constants
# This atomic transfer example code requires three (3) acounts:
# - account_1 requires a user-defined mnemonic and be funded with 1001000 microAlgos
# - account_2 requires a user-defined mnemonic and be funded with 2001000 microAlgos
# - account_3 auto-generated within the code, 1000000 microAlgos will be transfered here
# For account_1 and account_2, replcace the string "Your 25-word mnemonic goes here" in the code below.
# For account_3, ensure you note the mnemonic generated for future.
# Faucents available for funding accounts:
# - TestNet: https://developer.algorand.org/docs/reference/algorand-networks/testnet/#faucet
# - BetaNet: https://developer.algorand.org/docs/reference/algorand-networks/betanet/#faucet
# Replace the algod_address and algod_token parameters below to connect to your API host.
# user declared account mnemonics for account1 and account2
mnemonic_1 = "run broom excuse physical mind skill auction post enroll hip symbol talent proof axis hobby inner summer matrix village entry rice beef depth able canal"
mnemonic_2 = "couch enter net visit frown next upgrade auto island deal thank notice success robot runway win nerve there tilt rhythm merge satoshi loan about shadow"
# user declared algod connection parameters
algod_address = "https://testnet-algorand.api.purestake.io/ps2"
algod_token = "eE7U9NAUNh1VlvDyZARGP4F2CIZgBDDB5nhxAS3G"
algod_headers = {'X-Api-key': algod_token}
# Function that waits for a given txId to be confirmed by the network
def wait_for_confirmation(client, txid):
last_round = client.status().get('last-round')
txinfo = client.pending_transaction_info(txid)
while not (txinfo.get('confirmed-round') and txinfo.get('confirmed-round') > 0):
print("Waiting for confirmation...")
last_round += 1
client.status_after_block(last_round)
txinfo = client.pending_transaction_info(txid)
print("Transaction {} confirmed in round {}.".format(txid, txinfo.get('confirmed-round')))
return txinfo
# utility function to get address string
def get_address(mn) :
pk_account_a = mnemonic.to_private_key(mn)
address = account.address_from_private_key(pk_account_a)
print("Address :", address)
return address
# utility function to generate new account
def generate_new_account() :
private_key, address = account.generate_account()
print("Created new account: ", address)
print("Generated mnemonic: \"{}\"".format(mnemonic.from_private_key(private_key)))
return address
# utility function to display account balance
def display_account_algo_balance(client, address) :
account_info = client.account_info(address)
print("{}: {} microAlgos".format(address, account_info["amount"]))
def _correct_padding(a):
if len(a) % 8 == 0:
return a
return a + "=" * (8 - len(a) % 8)
def group_transactions() :
# Initialize an algodClient
algod_client = algod.AlgodClient(algod_token, algod_address, headers=algod_headers)
# declared account1 and account2 based on user supplied mnemonics
print("Loading two existing accounts...")
account_1 = get_address(mnemonic_1)
print("Address with padding {}".format(_correct_padding(account_1)))
account_2 = get_address(mnemonic_2)
# convert mnemonic1 and mnemonic2 using the mnemonic.ToPrivateKey() helper function
sk_1 = mnemonic.to_private_key(mnemonic_1)
sk_2 = mnemonic.to_private_key(mnemonic_2)
# generate account3, display mnemonic, wait
# print("Generating new account...")
account_3 = generate_new_account()
print("!! NOTICE !! Please retain the above generated \"25-word mnemonic passphrase\" for future use.")
# display account balances
print("Initial balances:")
display_account_algo_balance(algod_client, account_1)
display_account_algo_balance(algod_client, account_2)
display_account_algo_balance(algod_client, account_3)
# get node suggested parameters
params = algod_client.suggested_params()
# comment out the next two (2) lines to use suggested fees
params.flat_fee = True
params.fee = 1000
# create transactions
print("Creating transactions...")
# from account 1 to account 3
sender = account_1
receiver = account_3
amount = 1000000
txn_1 = transaction.PaymentTxn(sender, params, receiver, amount)
print("...txn_1: from {} to {} for {} microAlgos".format(sender, receiver, amount))
print("...created txn_1: ", txn_1.get_txid())
# from account 2 to account 1
sender = account_2
receiver = account_1
amount = 2000000
txn_2 = transaction.PaymentTxn(sender, params, receiver, amount)
print("...txn_2: from {} to {} for {} microAlgos".format(sender, receiver, amount))
print("...created txn_2: ", txn_2.get_txid())
# combine transations
print("Combining transactions...")
# the SDK does this implicitly within grouping below
print("Txn 1 for grouping: {}".format(txn_1))
print("Txn 2 for grouping: {}".format(txn_2))
encoded_txn_1 = encoding.msgpack_encode(txn_1)
txn_1_to_hash = constants.txid_prefix + base64.b64decode(encoded_txn_1)
print("Encoded txn1: {}".format(encoded_txn_1))
print("To hash txn1: {}".format(txn_1_to_hash))
print("Grouping transactions...")
# compute group id and put it into each transaction
group_id = transaction.calculate_group_id([txn_1, txn_2])
print("...computed groupId: ", group_id)
txn_1.group = group_id
txn_2.group = group_id
# split transaction group
print("Splitting unsigned transaction group...")
# this example does not use files on disk, so splitting is implicit above
# sign transactions
print("Signing transactions...")
stxn_1 = txn_1.sign(sk_1)
print("...account1 signed txn_1: ", stxn_1.get_txid())
stxn_2 = txn_2.sign(sk_2)
print("...account2 signed txn_2: ", stxn_2.get_txid())
# assemble transaction group
print("Assembling transaction group...")
signed_group = [stxn_1, stxn_2]
# send transactions
print("Sending transaction group...")
tx_id = algod_client.send_transactions(signed_group)
# wait for confirmation
wait_for_confirmation(algod_client, tx_id)
# display account balances
print("Final balances:")
display_account_algo_balance(algod_client, account_1)
display_account_algo_balance(algod_client, account_2)
display_account_algo_balance(algod_client, account_3)
# display confirmed transaction group
# tx1
confirmed_txn = algod_client.pending_transaction_info(txn_1.get_txid())
print("Transaction information: {}".format(json.dumps(confirmed_txn, indent=4)))
# tx2
confirmed_txn = algod_client.pending_transaction_info(txn_2.get_txid())
print("Transaction information: {}".format(json.dumps(confirmed_txn, indent=4)))
group_transactions()
# generate_new_account()
require 'msgpack'
require 'base64'
require 'base32'
def order_keys(hash)
hash.sort_by { |key| key }.to_h
end
def encode_address(addr)
Base32.encode(addr)
end
def dictify(txn)
txn = txn.map{ |k, v| [k.to_s, v] }.to_h # stringify_keys in Rails
res = {}
res['amt'] = txn['amt'] if txn.key?('amt') # transfer tx
res['fee'] = txn['fee'] if txn.key?('fee')
res['fv'] = txn['first_valid_round'] if txn.key?('first_valid_round')
res['gen'] = txn['genesis_id'] if txn.key?('genesis_id')
res['gh'] = Base64.strict_decode64(txn['genesis_hash']) if txn.key?('genesis_hash')
res['grp'] = txn['group'] if txn.key?('group')
res['lv'] = txn['last_valid_round'] if txn.key?('last_valid_round')
res['lx'] = txn['lease'] if txn.key?('lease')
res['note'] = txn['note'] if txn.key?('note')
res['rcv'] = Base32.decode(txn['receiver']) if txn.key?('receiver') # transfer tx
res['snd'] = Base32.decode(txn['sender'])
res['type'] = txn['type']
res['rekey'] = txn['rekey_to'] if txn.key?('rekey_to')
res
end
txn_1 = {
'sender': 'PYL3FVZBGCOPAC4TI3UYK7VXZ6BI4VJO6QY5XXBURLIKNUNHB2CFPNSWMQ',
'fee': 1000,
'first_valid_round': 17825054,
'last_valid_round': 17826054,
'note': nil,
'genesis_id': 'testnet-v1.0',
'genesis_hash': 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=',
'group': nil,
'lease': nil,
'type': 'pay',
'rekey_to': nil,
'receiver': 'Q6WLSZUNNJBZSLQXHFNXAFD526WQIKKXJ2QDQBUCIN4EUDN6CQIC22FJ2M',
'amt': 1000000,
'close_remainder_to': nil
}
txn_2 = {
'sender': '2SEDAL2TPNOYNIN7IGEK2Q34G32U4KN3WMRMEHSMKSOISJKXCDM2OT7MCA',
'fee': 1000,
'first_valid_round': 17825054,
'last_valid_round': 17826054,
'note': nil,
'genesis_id': 'testnet-v1.0',
'genesis_hash': 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=',
'group': nil,
'lease': nil,
'type': 'pay',
'rekey_to': nil,
'receiver': 'PYL3FVZBGCOPAC4TI3UYK7VXZ6BI4VJO6QY5XXBURLIKNUNHB2CFPNSWMQ',
'amt': 2000000,
'close_remainder_to': nil
}
p "TX" + order_keys(dictify(txn_1.compact)).to_msgpack
p "TX" + order_keys(dictify(txn_2.compact)).to_msgpack
p "OK"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment