Marketplace Contract Part 1 - Asset for Algo
Video Walkthrough
Watch the tutorial video for this chapter:
Step 1: Generate Two Accounts
- One for Lister, one for Buyer
from algosdk.account import generate_account
private_key, address = generate_account()
print(private_key, address)
Step 2: Load each Account with Funds
Use the following link to load funds into each account:
https://bank.testnet.algorand.network/Step 3: Create .env
algod_token =
algod_server = https://testnet-api.4160.nodely.dev
private_key = lqPuVOtF4p6QpsaL7iTA6mbHoy7a5+nuAPjzw7HvckiXQWUlnZQVYSCZSvUK8w+MGjh09sNsx7WybOeb0MLD/Q==
buyer_private_key = jHx+aE6G/5RKAMJK7J7R+j3SyjKhzLsrbYIrzmmhWphJsT7CpTxqdhsjWQx4wDpx2I5B+1FiBSp16O5fXd/Sqg==
asset_1 = 724395141
asset_2 = 724449087
app_id = 724455779
Step 4: Compile Asset for Algo Listing Contract
Create and compile the asset listing contract:
algokit compile py fileName.py
Use the following implementation to compile your contract:
from algopy import ARC4Contract, String, gtxn, Asset, Txn, GlobalState, itxn, Global, BoxRef, UInt64, TransactionType
from algopy import ARC4Contract, String, gtxn, Asset, Txn, GlobalState, itxn, Global, BoxRef, UInt64, TransactionType
from algopy.arc4 import abimethod, Address, Struct, Bool
from algopy.arc4 import UInt64 as arc4UInt64
class listingName(Struct):
lister: Address
counter: arc4UInt64
class listingValue(Struct):
asset_listed: arc4UInt64
algo_requested: arc4UInt64
class listings(ARC4Contract):
def __init__(self) -> None:
self.listingCounter = GlobalState(arc4UInt64(0))
@abimethod
def triggerOptIn(
self,
asset_to_opt_into: Asset,
fee_payment: gtxn.PaymentTransaction
) -> tuple[String, UInt64]:
assert fee_payment.amount >= 101_000
assert fee_payment.receiver == Global.current_application_address
itxn.AssetTransfer(
xfer_asset=asset_to_opt_into,
asset_receiver=Global.current_application_address,
fee=Global.min_txn_fee
).submit()
return String("Successfully opted in to asset: "), asset_to_opt_into.id
@abimethod
def postListingRequest(
self,
asset_to_list: gtxn.AssetTransferTransaction,
algo_amount_requested: arc4UInt64,
box_fee_mbr_payment: gtxn.PaymentTransaction,
) -> tuple[String, UInt64, String, arc4UInt64]:
assert box_fee_mbr_payment.amount == 24_900
assert asset_to_list.asset_receiver == Global.current_application_address
assert asset_to_list.asset_amount == 1
self.listingCounter.value = arc4UInt64(self.listingCounter.value.native + 1)
listing_box_name_info = listingName(Address(Txn.sender), self.listingCounter.value)
listing_box = BoxRef(key=listing_box_name_info.bytes)
value, exists = listing_box.maybe()
assert not exists
listing_box.create(size=16)
listing_box_value_info = listingValue(arc4UInt64(asset_to_list.xfer_asset.id), algo_amount_requested)
listing_box.put(listing_box_value_info.bytes)
return String("User Listed Asset Successfully: "), asset_to_list.xfer_asset.id, String("User is requesting x amount of Algo: "), algo_amount_requested
@abimethod
def fulfillListingRequest(
self,
asset_listed: Asset,
listing_name: listingName,
payment: gtxn.PaymentTransaction,
transfer_fees: gtxn.PaymentTransaction
) -> tuple[String, UInt64, String, UInt64]:
assert transfer_fees.amount == 2000
listing_box = BoxRef(key=listing_name.bytes)
value, exists = listing_box.maybe()
assert exists
listing_value = listingValue.from_bytes(value)
assert listing_value.asset_listed == asset_listed.id
assert listing_value.algo_requested == payment.amount
itxn.Payment(
receiver=listing_name.lister.native,
amount=payment.amount,
fee=Global.min_txn_fee
).submit()
itxn.AssetTransfer(
xfer_asset=asset_listed,
asset_receiver=Txn.sender,
asset_amount=1,
fee=Global.min_txn_fee
).submit()
listing_box.delete()
return String("Listing Fulfilled for Asset: "), asset_listed.id, String("Lister received x amount of Algo: "), payment.amount
@abimethod
def cancelListingRequest(
self,
listing_asset: Asset,
listing_name: listingName,
transfer_fee: gtxn.PaymentTransaction
) -> String:
assert transfer_fee.amount == 1000
assert listing_name.lister.native == Txn.sender
listing_box = BoxRef(key=listing_name.bytes)
value, exists = listing_box.maybe()
assert exists
listing_value = listingValue.from_bytes(value)
assert listing_value.asset_listed == listing_asset.id
itxn.AssetTransfer(
xfer_asset=listing_asset.id,
asset_amount=1,
asset_receiver=Txn.sender,
fee=Global.min_txn_fee
).submit()
listing_box.delete()
return String("Listing Request Cancelled")
Ensure you have the necessary imports and class definitions for your contract.
Step 5: Create Two Arbitrary Assets for Experimenting
Follow the steps to create and configure two assets for testing purposes.
from algosdk.v2client.algod import AlgodClient
from algosdk.transaction import AssetConfigTxn, wait_for_confirmation
from algosdk.account import address_from_private_key
import os
from dotenv import load_dotenv
load_dotenv()
algod_token = os.getenv('algod_token')
algod_server = os.getenv('algod_server')
algod_client = AlgodClient(algod_token, algod_server)
private_key = os.getenv('buyer_private_key')
address = address_from_private_key(private_key)
params = algod_client.suggested_params()
asset_creation_txn = AssetConfigTxn(
sender=address,
sp=params,
total=100,
default_frozen=False,
asset_name='Test Asset Two',
unit_name='TA2',
manager=address,
reserve=address,
strict_empty_address_check=False,
)
signed_tx = asset_creation_txn.sign(private_key)
tx_id = algod_client.send_transaction(signed_tx)
print(tx_id)
wait_for_confirmation(algod_client, tx_id)
asset_id = algod_client.pending_transaction_info(tx_id)['asset-index']
asset_info = algod_client.asset_info(asset_id)
print(asset_info)
Step 6: Store Generated Asset IDs in .env
asset_1 = 123456
asset_2 = 654321
Step 7: Experiment with Opting the Contract into an Asset
If you're creating multiple listings, you need to opt the contract into each asset the first time.
from algokit_utils import ApplicationClient
from algosdk.v2client.algod import AlgodClient
from algosdk.account import address_from_private_key
from algosdk.atomic_transaction_composer import AccountTransactionSigner, TransactionWithSigner, AtomicTransactionComposer
from algosdk.transaction import PaymentTxn
from pathlib import Path
import os
from dotenv import load_dotenv
load_dotenv()
algod_token = os.getenv('algod_token')
algod_server = os.getenv('algod_server')
algod_client = AlgodClient(algod_token, algod_server)
private_key = os.getenv('private_key')
address = address_from_private_key(private_key)
app_spec = Path(__file__).parent / './listings.arc32.json'
app_id = int(os.getenv('app_id'))
signer = AccountTransactionSigner(private_key)
params = algod_client.suggested_params()
app_client = ApplicationClient(
algod_client=algod_client,
app_spec=app_spec,
app_id=app_id,
signer=signer,
sender=address,
suggested_params=params,
)
atc = AtomicTransactionComposer()
asset_1 = int(os.getenv('asset_1'))
fee_payment_tx = PaymentTxn(sender=address, sp=params, receiver=app_client.app_address, amt=101_000)
wrapped_payment = TransactionWithSigner(fee_payment_tx, signer)
app_client.compose_call(atc, call_abi_method='triggerOptIn', asset_to_opt_into=asset_1, fee_payment=wrapped_payment)
results = atc.execute(algod_client, 2)
tx_ids = [results.tx_ids[i] for i in range(len(results.tx_ids))]
abi_results = [results.abi_results[i].return_value for i in range(len(results.abi_results))]
print(tx_ids)
print(abi_results)
Step 8: Make an Asset for Algo Listing Box (All listing types are supported from parts 1-4)
Create an asset listing box to manage your asset listings.
from algokit_utils import ApplicationClient
from algosdk.v2client.algod import AlgodClient
from algosdk.account import address_from_private_key
from algosdk.atomic_transaction_composer import AccountTransactionSigner, TransactionWithSigner, AtomicTransactionComposer
from algosdk.transaction import PaymentTxn, AssetTransferTxn
from pathlib import Path
from algosdk.util import algos_to_microalgos
from algosdk.abi import ABIType
from base64 import b64decode
import os
from dotenv import load_dotenv
load_dotenv()
algod_token = os.getenv('algod_token')
algod_server = os.getenv('algod_server')
algod_client = AlgodClient(algod_token, algod_server)
private_key = os.getenv('private_key')
address = address_from_private_key(private_key)
app_spec = Path(__file__).parent / './listings.arc32.json'
app_id = int(os.getenv('app_id'))
signer = AccountTransactionSigner(private_key)
params = algod_client.suggested_params()
app_client = ApplicationClient(
algod_client=algod_client,
app_spec=app_spec,
app_id=app_id,
signer=signer,
sender=address,
suggested_params=params,
)
atc = AtomicTransactionComposer()
asset_1 = int(os.getenv('asset_1'))
fee_payment_tx = PaymentTxn(sender=address, sp=params, receiver=app_client.app_address, amt=101_000)
wrapped_payment = TransactionWithSigner(fee_payment_tx, signer)
app_client.compose_call(
atc,
call_abi_method='triggerOptIn',
asset_to_opt_into=asset_1,
fee_payment=wrapped_payment)
asset_to_list_transaction = AssetTransferTxn(sender=address, sp=params, receiver=app_client.app_address, amt=1, index=asset_1)
wrapped_asset_list_transaction = TransactionWithSigner(asset_to_list_transaction, signer)
box_fee_payment_tx = PaymentTxn(sender=address, sp=params, receiver=app_client.app_address, amt=24_900)
wrapped_box_fee_payment = TransactionWithSigner(box_fee_payment_tx, signer)
current_global_listing_counter = algod_client.application_info(app_id)['params']['global-state'][0]['value']['bytes']
uint64_coder = ABIType.from_string('(uint64)')
b64_decoded_global_listing_counter = b64decode(current_global_listing_counter)
encoded_global_listing_counter = uint64_coder.decode(b64_decoded_global_listing_counter)[0]
listing_box_counter = encoded_global_listing_counter + 1
listing_box_coder = ABIType.from_string('(address,uint64)')
users_listing_box_name = listing_box_coder.encode((address, listing_box_counter))
app_client.compose_call(
atc,
call_abi_method='postListingRequest',
asset_to_list=wrapped_asset_list_transaction,
algo_amount_requested=algos_to_microalgos(1),
box_fee_mbr_payment=wrapped_box_fee_payment,
transaction_parameters={'boxes': [[app_id, users_listing_box_name]]}
)
results = atc.execute(algod_client, 2)
tx_ids = [results.tx_ids[i] for i in range(len(results.tx_ids))]
abi_results = [results.abi_results[i].return_value for i in range(len(results.abi_results))]
print(tx_ids)
print(abi_results)
Step 9: Check Respective Listing Information
Retrieve and verify the listing information from the contract.
import os
from algosdk.v2client.algod import AlgodClient
from algosdk.abi import ABIType
from base64 import b64decode
from dotenv import load_dotenv
load_dotenv()
algod_token = os.getenv('algod_token')
algod_server = os.getenv('algod_server')
algod_client = AlgodClient(algod_token, algod_server)
app_id = int(os.getenv('app_id'))
boxes = algod_client.application_boxes(app_id)['boxes']
for box in boxes:
box_name_b64encoded = box['name']
print(b64decode(box_name_b64encoded))
box_value = b64decode(algod_client.application_box_by_name(app_id, b64decode(box_name_b64encoded))['value'])
print(box_value)
#Asset for Algo: '(uint64,uint64)'
#Asset for Asset: '(uint64,uint64,bool)'
#Asset for Asset Quantity: '(uint64,uint64,uint64,bool)
#Asset Quantity for Asset Quantity: '(uint64,uint64,uint64,uint64,bool)'
#Universal Listing: '(uint64,uint64,uint64,uint64,bool)'
listing_box_value_coder = ABIType.from_string('(uint64,uint64)')
print(listing_box_value_coder.decode(box_value))
Step 10: Purchase the Asset with Your Buyer Private Key
When generating a listing, the counter variable should be incremented.
counter = 1
Note; counter should match the number of listings generated so far.
Execute the purchase of the asset using the buyer's private key.
from algokit_utils import ApplicationClient
from algosdk.v2client.algod import AlgodClient
from algosdk.account import address_from_private_key
from algosdk.atomic_transaction_composer import AccountTransactionSigner, TransactionWithSigner, AtomicTransactionComposer
from algosdk.transaction import PaymentTxn, AssetTransferTxn
from pathlib import Path
from algosdk.abi import ABIType
import os
from dotenv import load_dotenv
load_dotenv()
algod_token = os.getenv('algod_token')
algod_server = os.getenv('algod_server')
algod_client = AlgodClient(algod_token, algod_server)
private_key = os.getenv('buyer_private_key')
address = address_from_private_key(private_key)
app_spec = Path(__file__).parent / './listings.arc32.json'
app_id = int(os.getenv('app_id'))
signer = AccountTransactionSigner(private_key)
params = algod_client.suggested_params()
app_client = ApplicationClient(
algod_client=algod_client,
app_spec=app_spec,
app_id=app_id,
signer=signer,
sender=address,
suggested_params=params,
)
atc = AtomicTransactionComposer()
asset_1 = int(os.getenv('asset_1'))
asset_opt_in_transaction = AssetTransferTxn(sender=address, sp=params, receiver=address, amt=0, index=asset_1)
wrapped_opt_in_transaction = TransactionWithSigner(asset_opt_in_transaction, signer)
atc.add_transaction(wrapped_opt_in_transaction)
asset_payment_tx = PaymentTxn(sender=address, sp=params, receiver=app_client.app_address, amt=1_000_000)
wrapped_asset_payment = TransactionWithSigner(asset_payment_tx, signer)
fee_payment_tx = PaymentTxn(sender=address, sp=params, receiver=app_client.app_address, amt=2000)
wrapped_fee_payment = TransactionWithSigner(fee_payment_tx, signer)
lister_address = address_from_private_key(os.getenv('private_key'))
counter = 1 #Remember to increment counter for additional listings
listing_box_coder = ABIType.from_string('(address,uint64)')
box_name = listing_box_coder.encode((lister_address, counter))
app_client.compose_call(
atc,
call_abi_method='fulfillListingRequest',
asset_listed=asset_1,
listing_name=(lister_address,counter),
payment=wrapped_asset_payment,
transfer_fees=wrapped_fee_payment,
transaction_parameters={'boxes': [[app_id, box_name]], 'accounts':[lister_address]})
results = atc.execute(algod_client, 2)
tx_ids = [results.tx_ids[i] for i in range(len(results.tx_ids))]
abi_results = [results.abi_results[i].return_value for i in range(len(results.abi_results))]
print(tx_ids)
print(abi_results)