Created
November 11, 2021 18:27
-
-
Save Riddlerrr/adc93750a4ba4191e03a915cde1d44cc to your computer and use it in GitHub Desktop.
Algorand group transactions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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