Interact with smart contracts
This guide covers using smart contracts with the Algorand SDKs. Smart contracts form the basis for applications written in Transaction Execution Approval Language (TEAL) or with Python using the PyTeal library.
Application lifecycle¶
This guide follows an application throughout its lifecycle from initial creation, to usage, to modification and finally deletion. The application stores the number of times it is called within its global state and also stores the number of times each user account calls the application within their local state. Midway through the lifecycle, the application is upgraded to add an additional key:value pair to the user's local storage for storing the call timestamp.
Environment setup¶
This guide requires two accounts:
# user declared account mnemonics
creator_mnemonic = "Your first 25-word mnemonic goes here"
user_mnemonic = "A second distinct 25-word mnemonic goes here"
// user declared account mnemonics
creatorMnemonic = "Your 25-word mnemonic goes here";
userMnemonic = "A second distinct 25-word mnemonic goes here";
// user declared account mnemonics
String creatorMnemonic = "Your 25-word mnemonic goes here";
String userMnemonic = "A second distinct 25-word mnemonic goes here";
// user defined mnemonics
const creatorMnemonic = "Your 25-word mnemonic goes here"
const userMnemonic = "A second distinct 25-word mnemonic goes here"
An algod
client connection is also required. The following connects using Sandbox:
# Create a new algod client, configured to connect to our local sandbox
algod_address = "http://localhost:4001"
algod_token = "a" * 64
algod_client = algod.AlgodClient(algod_token, algod_address)
# Or, if necessary, pass alternate headers
# Create a new client with an alternate api key header
special_algod_client = algod.AlgodClient(
"", algod_address, headers={"X-API-Key": algod_token}
)
const algodToken = 'a'.repeat(64);
const algodServer = 'http://localhost';
const algodPort = 4001;
const algodClient = new algosdk.Algodv2(algodToken, algodServer, algodPort);
String algodHost = "http://localhost";
int algodPort = 4001;
String algodToken = "a".repeat(64);
AlgodClient algodClient = new AlgodClient(algodHost, algodPort, algodToken);
// OR if the API provider requires a specific header key for the token
String tokenHeader = "X-API-Key";
AlgodClient otherAlgodClient = new AlgodClient(algodHost, algodPort, algodToken, tokenHeader);
// Create a new algod client, configured to connect to out local sandbox
var algodAddress = "http://localhost:4001"
var algodToken = strings.Repeat("a", 64)
algodClient, _ := algod.MakeClient(
algodAddress,
algodToken,
)
// Or, if necessary, pass alternate headers
var algodHeader common.Header
algodHeader.Key = "X-API-Key"
algodHeader.Value = algodToken
algodClientWithHeaders, _ := algod.MakeClientWithHeaders(
algodAddress,
algodToken,
[]*common.Header{&algodHeader},
)
Info
Ensure the algod
node has the "EnableDeveloperAPI" parameter set to true within the config.json
file. This is required to enable the SDK access to the compile and dryrun endpoints.
Declarations¶
All smart contracts are comprised of state storage, an approval program and a clear program. Details of each are found within the smart contract guide.
State storage¶
Begin by defining the application's global_schema and local_schema storage requirements. These values are immutable once the application is created, so they must specify the maximum number required by the initial application and any future updates.
The example application defined below may hold up to one each of bytes
and ints
value within the local storage of the user account, as well as a single ints
value within global storage of the application:
# create schema for both global and local state, specifying
# how many keys of each type we need to have available
local_schema = transaction.StateSchema(num_uints=1, num_byte_slices=1)
global_schema = transaction.StateSchema(num_uints=1, num_byte_slices=1)
// define uint64s and byteslices stored in global/local storage
const numGlobalByteSlices = 1;
const numGlobalInts = 1;
const numLocalByteSlices = 1;
const numLocalInts = 1;
int localInts = 1;
int localBytes = 1;
int globalInts = 1;
int globalBytes = 0;
StateSchema localSchema = new StateSchema(localInts, localBytes);
StateSchema globalSchema = new StateSchema(globalInts, globalBytes);
// declare application state storage (immutable)
var (
localInts uint64 = 1
localBytes uint64 = 1
globalInts uint64 = 1
globalBytes uint64 = 0
)
// define schema
globalSchema := types.StateSchema{NumUint: globalInts, NumByteSlice: globalBytes}
localSchema := types.StateSchema{NumUint: localInts, NumByteSlice: localBytes}
Info
The example application is not allowed to hold any bytes
value within global storage.
Approval program¶
The approval program handles the main logic of the application.
Clear program¶
This is the most basic clear program and returns true when an account clears its participation in a smart contract:
# read the `.teal` source files from disk
with open("application/approval.teal", "r") as f:
approval_program = f.read()
with open("application/clear.teal", "r") as f:
clear_program = f.read()
// define TEAL source from string or from a file
const approvalProgram = fs.readFileSync(
path.join(__dirname, '/application/approval.teal'),
'utf8'
);
const clearProgram = fs.readFileSync(
path.join(__dirname, '/application/clear.teal'),
'utf8'
);
// Read in the `teal` source files as a string
String approvalSource = Files.readString(Paths.get("application/approval.teal"));
String clearSource = Files.readString(Paths.get("application/clear.teal"));
approvalTeal, err := ioutil.ReadFile("application/approval.teal")
if err != nil {
log.Fatalf("failed to read approval program: %s", err)
}
clearTeal, err := ioutil.ReadFile("application/clear.teal")
if err != nil {
log.Fatalf("failed to read clear program: %s", err)
}
Application methods¶
Create¶
The creator will deploy the application using the create app method. It requires 7 parameters:
- sender: address, representing the creator of the app
- sp: suggested parameters obtained from the network
- on_complete: enum value, representing NoOp
- approval_program: compiled program
- clear program: compiled program
- local_schema: maximum local storage allocation, immutable
- global_schema: maximum global storage allocation, immutable
Use the creator_mnemonic to define sender:
mn = "cost piano sample enough south bar diet garden nasty mystery mesh sadness convince bacon best patch surround protect drum actress entire vacuum begin abandon hair"
pk = mnemonic.to_private_key(mn)
print(f"Base64 encoded private key: {pk}")
addr = account.address_from_private_key(pk)
print(f"Address: {addr}")
// restore 25-word mnemonic from a string
// Note the mnemonic should _never_ appear in your source code
const mnemonic =
'creek phrase island true then hope employ veteran rapid hurdle above liberty tissue connect alcohol timber idle ten frog bulb embody crunch taxi abstract month';
const recoveredAccount = algosdk.mnemonicToSecretKey(mnemonic);
console.log('Recovered mnemonic account: ', recoveredAccount.addr);
// Space delimited 25 word mnemonic
String mn = "cost piano sample enough south bar diet garden nasty mystery mesh sadness convince bacon best patch surround protect drum actress entire vacuum begin abandon hair";
// We can get the private key
byte[] key = Mnemonic.toKey(mn);
// Or just init the account directly from the mnemonic
Account acct = new Account(mn);
k, err := mnemonic.ToPrivateKey(mn)
if err != nil {
log.Fatalf("failed to parse mnemonic: %s", err)
}
recovered, err := crypto.AccountFromPrivateKey(k)
if err != nil {
log.Fatalf("failed to recover account from key: %s", err)
}
log.Printf("%+v", recovered)
Compile the programs using the compile
endpoint:
# pass the `.teal` files to the compile endpoint
# and b64 decode the result to bytes
approval_result = algod_client.compile(approval_program)
approval_binary = base64.b64decode(approval_result["result"])
clear_result = algod_client.compile(clear_program)
clear_binary = base64.b64decode(clear_result["result"])
const approvalCompileResp = await algodClient
.compile(Buffer.from(approvalProgram))
.do();
const compiledApprovalProgram = new Uint8Array(
Buffer.from(approvalCompileResp.result, 'base64')
);
const clearCompileResp = await algodClient
.compile(Buffer.from(clearProgram))
.do();
const compiledClearProgram = new Uint8Array(
Buffer.from(clearCompileResp.result, 'base64')
);
CompileResponse approvalResponse = algodClient.TealCompile().source(approvalSource.getBytes()).execute()
.body();
CompileResponse clearResponse = algodClient.TealCompile().source(clearSource.getBytes()).execute()
.body();
TEALProgram approvalProg = new TEALProgram(approvalResponse.result);
TEALProgram clearProg = new TEALProgram(clearResponse.result);
approvalResult, err := algodClient.TealCompile(approvalTeal).Do(context.Background())
if err != nil {
log.Fatalf("failed to compile program: %s", err)
}
approvalBinary, err := base64.StdEncoding.DecodeString(approvalResult.Result)
if err != nil {
log.Fatalf("failed to decode compiled program: %s", err)
}
clearResult, err := algodClient.TealCompile(clearTeal).Do(context.Background())
if err != nil {
log.Fatalf("failed to compile program: %s", err)
}
clearBinary, err := base64.StdEncoding.DecodeString(clearResult.Result)
if err != nil {
log.Fatalf("failed to decode compiled program: %s", err)
}
Construct the transaction with defined values then sign, send, and await confirmation
sp = algod_client.suggested_params()
# create the app create transaction, passing compiled programs and schema
app_create_txn = transaction.ApplicationCreateTxn(
creator.address,
sp,
transaction.OnComplete.NoOpOC,
approval_program=approval_binary,
clear_program=clear_binary,
global_schema=global_schema,
local_schema=local_schema,
)
# sign transaction
signed_create_txn = app_create_txn.sign(creator.private_key)
txid = algod_client.send_transaction(signed_create_txn)
result = transaction.wait_for_confirmation(algod_client, txid, 4)
app_id = result["application-index"]
print(f"Created app with id: {app_id}")
const appCreateTxn = algosdk.makeApplicationCreateTxnFromObject({
from: creator.addr,
approvalProgram: compiledApprovalProgram,
clearProgram: compiledClearProgram,
numGlobalByteSlices,
numGlobalInts,
numLocalByteSlices,
numLocalInts,
suggestedParams,
onComplete: algosdk.OnApplicationComplete.NoOpOC,
});
// Sign and send
await algodClient
.sendRawTransaction(appCreateTxn.signTxn(creator.privateKey))
.do();
const result = await algosdk.waitForConfirmation(
algodClient,
appCreateTxn.txID().toString(),
3
);
// Grab app id from confirmed transaction result
const appId = result['application-index'];
console.log(`Created app with index: ${appId}`);
Response<TransactionParametersResponse> rsp = algodClient.TransactionParams().execute();
TransactionParametersResponse sp = rsp.body();
Transaction appCreate = ApplicationCreateTransactionBuilder.Builder()
.sender(creator.getAddress())
.suggestedParams(sp)
.approvalProgram(approvalProg)
.clearStateProgram(clearProg)
.localStateSchema(localSchema)
.globalStateSchema(globalSchema)
.build();
SignedTransaction signedAppCreate = creator.signTransaction(appCreate);
Response<PostTransactionsResponse> createResponse = algodClient.RawTransaction()
.rawtxn(Encoder.encodeToMsgPack(signedAppCreate)).execute();
PendingTransactionResponse result = Utils.waitForConfirmation(algodClient, createResponse.body().txId,
4);
Long appId = result.applicationIndex;
System.out.printf("Created application with id: %d\n", appId);
// Create application
sp, err := algodClient.SuggestedParams().Do(context.Background())
if err != nil {
log.Fatalf("error getting suggested tx params: %s", err)
}
txn, err := transaction.MakeApplicationCreateTx(
false, approvalBinary, clearBinary, globalSchema, localSchema,
nil, nil, nil, nil, sp, creator.Address, nil,
types.Digest{}, [32]byte{}, types.ZeroAddress,
)
if err != nil {
log.Fatalf("failed to make txn: %s", err)
}
txid, stx, err := crypto.SignTransaction(creator.PrivateKey, txn)
if err != nil {
log.Fatalf("failed to sign transaction: %s", err)
}
_, err = algodClient.SendRawTransaction(stx).Do(context.Background())
if err != nil {
log.Fatalf("failed to send transaction: %s", err)
}
confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background())
if err != nil {
log.Fatalf("error waiting for confirmation: %s", err)
}
appID := confirmedTxn.ApplicationIndex
log.Printf("Created app with id: %d", appID)
Notice
Note down the app-id from the confirmed transaction response. Place this value into the index
parameter within all remaining code samples.
Opt-in¶
The user must opt-in to call the application if some local state is used during evaluation of the call. This method requires 3 parameters:
- sender: address, representing the user intending to opt-in to using the app
- sp: suggested parameters obtained from the network
- index: the app-id as defined by the create method result
Use the user_mnemonic to define sender:
mn = "cost piano sample enough south bar diet garden nasty mystery mesh sadness convince bacon best patch surround protect drum actress entire vacuum begin abandon hair"
pk = mnemonic.to_private_key(mn)
print(f"Base64 encoded private key: {pk}")
addr = account.address_from_private_key(pk)
print(f"Address: {addr}")
// restore 25-word mnemonic from a string
// Note the mnemonic should _never_ appear in your source code
const mnemonic =
'creek phrase island true then hope employ veteran rapid hurdle above liberty tissue connect alcohol timber idle ten frog bulb embody crunch taxi abstract month';
const recoveredAccount = algosdk.mnemonicToSecretKey(mnemonic);
console.log('Recovered mnemonic account: ', recoveredAccount.addr);
// Space delimited 25 word mnemonic
String mn = "cost piano sample enough south bar diet garden nasty mystery mesh sadness convince bacon best patch surround protect drum actress entire vacuum begin abandon hair";
// We can get the private key
byte[] key = Mnemonic.toKey(mn);
// Or just init the account directly from the mnemonic
Account acct = new Account(mn);
k, err := mnemonic.ToPrivateKey(mn)
if err != nil {
log.Fatalf("failed to parse mnemonic: %s", err)
}
recovered, err := crypto.AccountFromPrivateKey(k)
if err != nil {
log.Fatalf("failed to recover account from key: %s", err)
}
log.Printf("%+v", recovered)
Construct the transaction with defined values then sign, send, and await confirmation:
opt_in_txn = transaction.ApplicationOptInTxn(user.address, sp, app_id)
signed_opt_in = opt_in_txn.sign(user.private_key)
txid = algod_client.send_transaction(signed_opt_in)
optin_result = transaction.wait_for_confirmation(algod_client, txid, 4)
assert optin_result["confirmed-round"] > 0
const appOptInTxn = algosdk.makeApplicationOptInTxnFromObject({
from: caller.addr,
appIndex: appId,
suggestedParams,
});
await algodClient
.sendRawTransaction(appOptInTxn.signTxn(caller.privateKey))
.do();
await algosdk.waitForConfirmation(
algodClient,
appOptInTxn.txID().toString(),
3
);
Transaction optInTxn = ApplicationOptInTransactionBuilder.Builder()
.sender(user.getAddress())
.suggestedParams(sp)
.applicationId(appId)
.build();
SignedTransaction signedOptIn = user.signTransaction(optInTxn);
Response<PostTransactionsResponse> optInResponse = algodClient.RawTransaction()
.rawtxn(Encoder.encodeToMsgPack(signedOptIn)).execute();
PendingTransactionResponse optInResult = Utils.waitForConfirmation(algodClient,
optInResponse.body().txId, 4);
assert optInResult.confirmedRound > 0;
sp, err := algodClient.SuggestedParams().Do(context.Background())
if err != nil {
log.Fatalf("error getting suggested tx params: %s", err)
}
// Create a new clawback transaction with the target of the user address and the recipient as the creator
// address, being sent from the address marked as `clawback` on the asset, in this case the same as creator
txn, err := transaction.MakeApplicationOptInTx(
appID, nil, nil, nil, nil, sp,
caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress,
)
if err != nil {
log.Fatalf("failed to make txn: %s", err)
}
// sign the transaction
txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn)
if err != nil {
log.Fatalf("failed to sign transaction: %s", err)
}
// Broadcast the transaction to the network
_, err = algodClient.SendRawTransaction(stx).Do(context.Background())
if err != nil {
log.Fatalf("failed to send transaction: %s", err)
}
// Wait for confirmation
confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background())
if err != nil {
log.Fatalf("error waiting for confirmation: %s", err)
}
log.Printf("OptIn Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound)
Call (NoOp)¶
The user may now call the application. This method requires 3 parameters:
- sender: address, representing the user intending to optin to using the app
- sp: suggested parameters obtained from the network
- index: the app-id as defined by the create method result
noop_txn = transaction.ApplicationNoOpTxn(user.address, sp, app_id)
signed_noop = noop_txn.sign(user.private_key)
txid = algod_client.send_transaction(signed_noop)
noop_result = transaction.wait_for_confirmation(algod_client, txid, 4)
assert noop_result["confirmed-round"] > 0
const appNoOpTxn = algosdk.makeApplicationNoOpTxnFromObject({
from: caller.addr,
appIndex: appId,
suggestedParams,
});
await algodClient
.sendRawTransaction(appNoOpTxn.signTxn(caller.privateKey))
.do();
await algosdk.waitForConfirmation(
algodClient,
appNoOpTxn.txID().toString(),
3
);
Transaction noopTxn = ApplicationCallTransactionBuilder.Builder()
.sender(user.getAddress())
.suggestedParams(sp)
.applicationId(appId)
.build();
SignedTransaction signedNoop = user.signTransaction(noopTxn);
Response<PostTransactionsResponse> noopResponse = algodClient.RawTransaction()
.rawtxn(Encoder.encodeToMsgPack(signedNoop)).execute();
PendingTransactionResponse noopResult = Utils.waitForConfirmation(algodClient, noopResponse.body().txId,
4);
assert noopResult.confirmedRound > 0;
sp, err := algodClient.SuggestedParams().Do(context.Background())
if err != nil {
log.Fatalf("error getting suggested tx params: %s", err)
}
var (
appArgs [][]byte
accts []string
apps []uint64
assets []uint64
)
// Add an arg to our app call
appArgs = append(appArgs, []byte("arg0"))
txn, err := transaction.MakeApplicationNoOpTx(
appID, appArgs, accts, apps, assets, sp,
caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress,
)
if err != nil {
log.Fatalf("failed to make txn: %s", err)
}
// sign the transaction
txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn)
if err != nil {
log.Fatalf("failed to sign transaction: %s", err)
}
// Broadcast the transaction to the network
_, err = algodClient.SendRawTransaction(stx).Do(context.Background())
if err != nil {
log.Fatalf("failed to send transaction: %s", err)
}
// Wait for confirmation
confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background())
if err != nil {
log.Fatalf("error waiting for confirmation: %s", err)
}
log.Printf("NoOp Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound)
Read state¶
Anyone may read the global state of any application or the local state of an application within a given user account using the REST API account_info endpoint.
acct_info = algod_client.account_application_info(user.address, app_id)
# base64 encoded keys and values
print(acct_info["app-local-state"]["key-value"])
const appInfo = await algodClient.getApplicationByID(appId).do();
const globalState = appInfo.params['global-state'][0];
console.log(`Raw global state - ${JSON.stringify(globalState)}`);
// decode b64 string key with Buffer
const globalKey = Buffer.from(globalState.key, 'base64').toString();
// decode b64 address value with encodeAddress and Buffer
const globalValue = algosdk.encodeAddress(
Buffer.from(globalState.value.bytes, 'base64')
);
console.log(`Decoded global state - ${globalKey}: ${globalValue}`);
const accountAppInfo = await algodClient
.accountApplicationInformation(caller.addr, appId)
.do();
const localState = accountAppInfo['app-local-state']['key-value'][0];
console.log(`Raw local state - ${JSON.stringify(localState)}`);
// decode b64 string key with Buffer
const localKey = Buffer.from(localState.key, 'base64').toString();
// get uint value directly
const localValue = localState.value.uint;
console.log(`Decoded local state - ${localKey}: ${localValue}`);
// grab global state and config of application
appInfo, err := algodClient.GetApplicationByID(appID).Do(context.Background())
if err != nil {
log.Fatalf("failed to get app info: %s", err)
}
log.Printf("app info: %+v", appInfo)
// grab local state for an app id for a single account
acctInfo, err := algodClient.AccountApplicationInformation(
acct1.Address.String(), appID,
).Do(context.Background())
if err != nil {
log.Fatalf("failed to get app info: %s", err)
}
log.Printf("app info: %+v", acctInfo)
Update¶
The creator may update the approval program using the update method (if the current approval program allows it). The refactored approval program source code adds a key/value pair to the user's local storage indicating the timestamp when the application was called. Refer to the appendix for details. The original clear program will be reused.
The update method method requires 6 parameters:
- sender: address, representing the user intending to opt-in to using the app
- sp: suggested parameters obtained from the network
- index: the app-id as defined by the create method result
- approval_program: compiled program
- clear program: compiled program
- app_args: application arguments used by approval program
Construct the update transaction and await the response:
with open("application/approval_refactored.teal", "r") as f:
approval_program = f.read()
approval_result = algod_client.compile(approval_program)
approval_binary = base64.b64decode(approval_result["result"])
sp = algod_client.suggested_params()
# create the app update transaction, passing compiled programs and schema
# note that schema is immutable, we cant change it after create
app_update_txn = transaction.ApplicationUpdateTxn(
creator.address,
sp,
app_id,
approval_program=approval_binary,
clear_program=clear_binary,
)
signed_update = app_update_txn.sign(creator.private_key)
txid = algod_client.send_transaction(signed_update)
update_result = transaction.wait_for_confirmation(algod_client, txid, 4)
assert update_result["confirmed-round"] > 0
const newProgram = fs.readFileSync(
path.join(__dirname, '/application/approval_refactored.teal'),
'utf8'
);
const compiledNewProgram = await compileProgram(algodClient, newProgram);
const appUpdateTxn = algosdk.makeApplicationUpdateTxnFromObject({
from: creator.addr,
suggestedParams,
appIndex: appId,
// updates must define both approval and clear programs, even if unchanged
approvalProgram: compiledNewProgram,
clearProgram: compiledClearProgram,
});
await algodClient
.sendRawTransaction(appUpdateTxn.signTxn(creator.privateKey))
.do();
await algosdk.waitForConfirmation(
algodClient,
appUpdateTxn.txID().toString(),
3
);
String approvalSourceUpdated = Files.readString(Paths.get("application/approval_refactored.teal"));
CompileResponse approvalUpdatedResponse = algodClient.TealCompile()
.source(approvalSourceUpdated.getBytes())
.execute()
.body();
TEALProgram approvalProgUpdated = new TEALProgram(approvalUpdatedResponse.result);
Transaction appUpdate = ApplicationUpdateTransactionBuilder.Builder()
.sender(creator.getAddress())
.suggestedParams(sp)
.applicationId(appId)
.approvalProgram(approvalProgUpdated)
.clearStateProgram(clearProg)
.build();
SignedTransaction signedAppUpdate = creator.signTransaction(appUpdate);
Response<PostTransactionsResponse> updateResponse = algodClient.RawTransaction()
.rawtxn(Encoder.encodeToMsgPack(signedAppUpdate)).execute();
PendingTransactionResponse updateResult = Utils.waitForConfirmation(algodClient,
updateResponse.body().txId, 4);
assert updateResult.confirmedRound > 0;
sp, err := algodClient.SuggestedParams().Do(context.Background())
if err != nil {
log.Fatalf("error getting suggested tx params: %s", err)
}
var (
appArgs [][]byte
accts []string
apps []uint64
assets []uint64
)
txn, err := transaction.MakeApplicationUpdateTx(
appID, appArgs, accts, apps, assets, approvalBinary, clearBinary,
sp, caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress,
)
if err != nil {
log.Fatalf("failed to make txn: %s", err)
}
// sign the transaction
txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn)
if err != nil {
log.Fatalf("failed to sign transaction: %s", err)
}
// Broadcast the transaction to the network
_, err = algodClient.SendRawTransaction(stx).Do(context.Background())
if err != nil {
log.Fatalf("failed to send transaction: %s", err)
}
// Wait for confirmation
confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background())
if err != nil {
log.Fatalf("error waiting for confirmation: %s", err)
}
log.Printf("Update Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound)
Call with arguments¶
A program may process arguments passed at run-time. The NoOp call method has an optional app_args parameter where smart contract parameters can be supplied:
now = datetime.datetime.now().strftime("%H:%M:%S")
app_args = [now.encode("utf-8")]
call_txn = transaction.ApplicationNoOpTxn(user.address, sp, app_id, app_args)
signed_call = call_txn.sign(user.private_key)
txid = algod_client.send_transaction(signed_call)
call_result = transaction.wait_for_confirmation(algod_client, txid, 4)
assert call_result["confirmed-round"] > 0
# display results
print("Called app-id: ", call_result["txn"]["txn"]["apid"])
if "global-state-delta" in call_result:
print("Global State updated :\n", call_result["global-state-delta"])
if "local-state-delta" in call_result:
print("Local State updated :\n", call_result["local-state-delta"])
const now = new Date().toString();
const simpleAddTxn = algosdk.makeApplicationNoOpTxnFromObject({
from: caller.addr,
suggestedParams,
appIndex: appId,
appArgs: [new Uint8Array(Buffer.from(now))],
});
await algodClient
.sendRawTransaction(simpleAddTxn.signTxn(caller.privateKey))
.do();
await algosdk.waitForConfirmation(
algodClient,
simpleAddTxn.txID().toString(),
3
);
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss");
Date date = new Date(System.currentTimeMillis());
List<byte[]> appArgs = new ArrayList<byte[]>();
appArgs.add(formatter.format(date).toString().getBytes());
// create unsigned transaction
Transaction callTransaction = ApplicationCallTransactionBuilder.Builder()
.sender(user.getAddress())
.suggestedParams(sp)
.applicationId(appId)
.args(appArgs)
.build();
SignedTransaction signedCallTransaction = user.signTransaction(callTransaction);
Response<PostTransactionsResponse> callResponse = algodClient.RawTransaction()
.rawtxn(Encoder.encodeToMsgPack(signedCallTransaction)).execute();
PendingTransactionResponse callResult = Utils.waitForConfirmation(algodClient, callResponse.body().txId,
4);
assert callResult.confirmedRound > 0;
// display results
if (callResult.globalStateDelta != null) {
System.out.printf("\tGlobal state: %s\n", callResult.globalStateDelta);
}
if (callResult.localStateDelta != null) {
System.out.printf("\tLocal state: %s\n", callResult.localStateDelta);
}
sp, err := algodClient.SuggestedParams().Do(context.Background())
if err != nil {
log.Fatalf("error getting suggested tx params: %s", err)
}
var (
appArgs [][]byte
accts []string
apps []uint64
assets []uint64
)
datetime := time.Now().Format("2006-01-02 at 15:04:05")
appArgs = append(appArgs, []byte(datetime))
txn, err := transaction.MakeApplicationNoOpTx(
appID, appArgs, accts, apps, assets, sp,
caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress,
)
if err != nil {
log.Fatalf("failed to make txn: %s", err)
}
// sign the transaction
txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn)
if err != nil {
log.Fatalf("failed to sign transaction: %s", err)
}
// Broadcast the transaction to the network
_, err = algodClient.SendRawTransaction(stx).Do(context.Background())
if err != nil {
log.Fatalf("failed to send transaction: %s", err)
}
// Wait for confirmation
confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background())
if err != nil {
log.Fatalf("error waiting for confirmation: %s", err)
}
log.Printf("NoOp Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound)
Close out¶
The user may discontinue use of the application by sending a close out transaction. This will remove the local state for this application from the user's account. This method requires 3 parameters:
- sender: address, representing the user intending to optin to using the app
- sp: suggested parameters obtained from the network
- index: the app-id as defined by the create method result
close_txn = transaction.ApplicationCloseOutTxn(user.address, sp, app_id)
signed_close = close_txn.sign(user.private_key)
txid = algod_client.send_transaction(signed_close)
optin_result = transaction.wait_for_confirmation(algod_client, txid, 4)
assert optin_result["confirmed-round"] > 0
const appCloseOutTxn = algosdk.makeApplicationCloseOutTxnFromObject({
from: caller.addr,
appIndex: appId,
suggestedParams,
});
await algodClient
.sendRawTransaction(appCloseOutTxn.signTxn(caller.privateKey))
.do();
await algosdk.waitForConfirmation(
algodClient,
appCloseOutTxn.txID().toString(),
3
);
Transaction closeOutTxn = ApplicationCloseTransactionBuilder.Builder()
.sender(user.getAddress())
.suggestedParams(sp)
.applicationId(appId)
.build();
SignedTransaction signedCloseOut = user.signTransaction(closeOutTxn);
Response<PostTransactionsResponse> closeOutResponse = algodClient.RawTransaction()
.rawtxn(Encoder.encodeToMsgPack(signedCloseOut)).execute();
PendingTransactionResponse closeOutResult = Utils.waitForConfirmation(algodClient,
closeOutResponse.body().txId,
4);
assert closeOutResult.confirmedRound > 0;
sp, err := algodClient.SuggestedParams().Do(context.Background())
if err != nil {
log.Fatalf("error getting suggested tx params: %s", err)
}
var (
appArgs [][]byte
accts []string
apps []uint64
assets []uint64
)
txn, err := transaction.MakeApplicationCloseOutTx(
appID, appArgs, accts, apps, assets, sp,
caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress,
)
if err != nil {
log.Fatalf("failed to make txn: %s", err)
}
// sign the transaction
txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn)
if err != nil {
log.Fatalf("failed to sign transaction: %s", err)
}
// Broadcast the transaction to the network
_, err = algodClient.SendRawTransaction(stx).Do(context.Background())
if err != nil {
log.Fatalf("failed to send transaction: %s", err)
}
// Wait for confirmation
confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background())
if err != nil {
log.Fatalf("error waiting for confirmation: %s", err)
}
log.Printf("Closeout Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound)
Delete¶
The approval program defines the creator as the only account able to delete the application. This removes the global state, but does not impact any user's local state. This method uses the same 3 parameters.
delete_txn = transaction.ApplicationDeleteTxn(creator.address, sp, app_id)
signed_delete = delete_txn.sign(creator.private_key)
txid = algod_client.send_transaction(signed_delete)
optin_result = transaction.wait_for_confirmation(algod_client, txid, 4)
assert optin_result["confirmed-round"] > 0
const appDeleteTxn = algosdk.makeApplicationDeleteTxnFromObject({
from: creator.addr,
suggestedParams,
appIndex: appId,
});
await algodClient
.sendRawTransaction(appDeleteTxn.signTxn(creator.privateKey))
.do();
await algosdk.waitForConfirmation(
algodClient,
appDeleteTxn.txID().toString(),
3
);
Transaction appDelete = ApplicationDeleteTransactionBuilder.Builder()
.sender(creator.getAddress())
.suggestedParams(sp)
.applicationId(appId)
.build();
SignedTransaction signedAppDelete = creator.signTransaction(appDelete);
Response<PostTransactionsResponse> deleteResponse = algodClient.RawTransaction()
.rawtxn(Encoder.encodeToMsgPack(signedAppDelete)).execute();
PendingTransactionResponse deleteResult = Utils.waitForConfirmation(algodClient,
deleteResponse.body().txId, 4);
assert deleteResult.confirmedRound > 0;
sp, err := algodClient.SuggestedParams().Do(context.Background())
if err != nil {
log.Fatalf("error getting suggested tx params: %s", err)
}
var (
appArgs [][]byte
accts []string
apps []uint64
assets []uint64
)
txn, err := transaction.MakeApplicationDeleteTx(
appID, appArgs, accts, apps, assets, sp,
caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress,
)
if err != nil {
log.Fatalf("failed to make txn: %s", err)
}
// sign the transaction
txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn)
if err != nil {
log.Fatalf("failed to sign transaction: %s", err)
}
// Broadcast the transaction to the network
_, err = algodClient.SendRawTransaction(stx).Do(context.Background())
if err != nil {
log.Fatalf("failed to send transaction: %s", err)
}
// Wait for confirmation
confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background())
if err != nil {
log.Fatalf("error waiting for confirmation: %s", err)
}
log.Printf("Delete Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound)
Clear state¶
The user may clear the local state for an application at any time, even if the application was deleted by the creator. This method uses the same 3 parameters.
clear_txn = transaction.ApplicationClearStateTxn(user.address, sp, app_id)
# .. sign, send, wait
const appClearTxn = algosdk.makeApplicationClearStateTxnFromObject({
from: anotherCaller.addr,
suggestedParams,
appIndex: appId,
});
await algodClient
.sendRawTransaction(appClearTxn.signTxn(anotherCaller.privateKey))
.do();
await algosdk.waitForConfirmation(
algodClient,
appClearTxn.txID().toString(),
3
);
Transaction clearTxn = ApplicationClearTransactionBuilder.Builder()
.sender(user.getAddress())
.suggestedParams(sp)
.applicationId(appId)
.build();
SignedTransaction signedClear = user.signTransaction(clearTxn);
// ... sign, send, wait
sp, err := algodClient.SuggestedParams().Do(context.Background())
if err != nil {
log.Fatalf("error getting suggested tx params: %s", err)
}
var (
appArgs [][]byte
accts []string
apps []uint64
assets []uint64
)
txn, err := transaction.MakeApplicationClearStateTx(
appID, appArgs, accts, apps, assets, sp,
caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress,
)
if err != nil {
log.Fatalf("failed to make txn: %s", err)
}
// sign the transaction
txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn)
if err != nil {
log.Fatalf("failed to sign transaction: %s", err)
}
// Broadcast the transaction to the network
_, err = algodClient.SendRawTransaction(stx).Do(context.Background())
if err != nil {
log.Fatalf("failed to send transaction: %s", err)
}
// Wait for confirmation
confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background())
if err != nil {
log.Fatalf("error waiting for confirmation: %s", err)
}
log.Printf("ClearState Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound)
Appendix¶
Approval program walkthrough¶
#pragma version 4
// Handle each possible OnCompletion type. We don't have to worry about
// handling ClearState, because the ClearStateProgram will execute in that
// case, not the ApprovalProgram.
txn OnCompletion
int NoOp
==
bnz handle_noop
txn OnCompletion
int OptIn
==
bnz handle_optin
txn OnCompletion
int CloseOut
==
bnz handle_closeout
txn OnCompletion
int UpdateApplication
==
bnz handle_updateapp
txn OnCompletion
int DeleteApplication
==
bnz handle_deleteapp
// Unexpected OnCompletion value. Should be unreachable.
err
handle_noop:
// Handle NoOp
// Check for creator
addr 5XWY6RBNYHCSY2HK5HCTO62DUJJ4PT3G4L77FQEBUKE6ZYRGQAFTLZSQQ4
txn Sender
==
bnz handle_optin
// read global state
byte "counter"
dup
app_global_get
// increment the value
int 1
+
// store to scratch space
dup
store 0
// update global state
app_global_put
// read local state for sender
int 0
byte "counter"
app_local_get
// increment the value
int 1
+
store 1
// update local state for sender
int 0
byte "counter"
load 1
app_local_put
// load return value as approval
load 0
return
handle_optin:
// Handle OptIn
// approval
int 1
return
handle_closeout:
// Handle CloseOut
//approval
int 1
return
handle_deleteapp:
// Check for creator
addr 5XWY6RBNYHCSY2HK5HCTO62DUJJ4PT3G4L77FQEBUKE6ZYRGQAFTLZSQQ4
txn Sender
==
return
handle_updateapp:
// Check for creator
addr 5XWY6RBNYHCSY2HK5HCTO62DUJJ4PT3G4L77FQEBUKE6ZYRGQAFTLZSQQ4
txn Sender
==
return
Clear program walkthrough¶
Refactored approval program¶
#pragma version 4
// Handle each possible OnCompletion type. We don't have to worry about
// handling ClearState, because the ClearStateProgram will execute in that
// case, not the ApprovalProgram.
txn OnCompletion
int NoOp
==
bnz handle_noop
txn OnCompletion
int OptIn
==
bnz handle_optin
txn OnCompletion
int CloseOut
==
bnz handle_closeout
txn OnCompletion
int UpdateApplication
==
bnz handle_updateapp
txn OnCompletion
int DeleteApplication
==
bnz handle_deleteapp
// Unexpected OnCompletion value. Should be unreachable.
err
handle_noop:
// Handle NoOp
// Check for creator
addr 5XWY6RBNYHCSY2HK5HCTO62DUJJ4PT3G4L77FQEBUKE6ZYRGQAFTLZSQQ4
txn Sender
==
bnz handle_optin
// read global state
byte "counter"
dup
app_global_get
// increment the value
int 1
+
// store to scratch space
dup
store 0
// update global state
app_global_put
// read local state for sender
int 0
byte "counter"
app_local_get
// increment the value
int 1
+
store 1
// update local state for sender
// update "counter"
int 0
byte "counter"
load 1
app_local_put
// update "timestamp"
int 0
byte "timestamp"
txn ApplicationArgs 0
app_local_put
// load return value as approval
load 0
return
handle_optin:
// Handle OptIn
// approval
int 1
return
handle_closeout:
// Handle CloseOut
//approval
int 1
return
handle_deleteapp:
// Check for creator
addr 5XWY6RBNYHCSY2HK5HCTO62DUJJ4PT3G4L77FQEBUKE6ZYRGQAFTLZSQQ4
txn Sender
==
return
handle_updateapp:
// Check for creator
addr 5XWY6RBNYHCSY2HK5HCTO62DUJJ4PT3G4L77FQEBUKE6ZYRGQAFTLZSQQ4
txn Sender
==
return