Skip to content

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.

Interact with smart signatures

This guide covers using smart signatures with the Algorand SDKs. Smart signatures are also referred to as stateless smart contracts. Smart signatures can be used to create contract accounts or to handle account delegation which is described in the Modes documentation. In either case, the contracts are written in Transaction Execution Approval Language (TEAL) or with Python using the PyTeal library.

Compiling TEAL program from SDKs

Before a TEAL program can be used, it must be compiled. SDKs provide this capability. The examples in this section use a simple contract which contains one line of TEAL code, int 1 . This will always return an approval for the transaction. So, any transactions that use this TEAL file will succeed. Never use this TEAL in a production application as it approves all transactions.

To use the SDK compile command, the config settings may need to be modified to set a value for EnableDeveloperAPI, which should be set to true. The default is false. If using the sandbox, the following modification is already made. If running your own node, you may see an error similar to "compile was not enabled in the configuration file by setting the EnableDeveloperAPI to true". Make the following modification to the config.json file located in the node’s data directory. First, if there is not a config.json, make a copy of the config.json.example file.

$ goal node stop -d data
$ cd data
$ cp config.json.example config.json

Then edit the config.json file and replace false on the line

"EnableDeveloperAPI": false,

with true

"EnableDeveloperAPI": true,

Restart the node.

$ goal node start -d data
$ goal node status -d data

The following TEAL code is used in the examples below.

sample.teal

// This code is meant for learning purposes only
// It should not be used in production
int 1

Info

The samples on this page use a docker sandbox install.

const smartSigSource = '#pragma version 8\nint 1\nreturn'; // approve everything
const result = await client.compile(Buffer.from(smartSigSource)).do();

// Hash is equivalent to the contract address
console.log('Hash: ', result.hash);
console.log('B64: ', result.result);
const b64program = result.result;
Snippet Source

# read teal program
data = open(lsig_src_path, "r").read()
# compile teal program
response = algod_client.compile(data)
print("Response Result = ", response["result"])
print("Response Hash = ", response["hash"])
Snippet Source

String tealsrc = Files.readString(Paths.get("lsig/simple.teal"));
Response<CompileResponse> compileResp = algodClient.TealCompile().source(tealsrc.getBytes()).execute();
System.out.printf("Program: %s\n", compileResp.body().result);
System.out.printf("Address: %s\n", compileResp.body().hash);
byte[] tealBinary = Encoder.decodeFromBase64(compileResp.body().result);
Snippet Source

teal, err := ioutil.ReadFile("lsig/simple.teal")
if err != nil {
    log.Fatalf("failed to read approval program: %s", err)
}

result, err := algodClient.TealCompile(teal).Do(context.Background())
if err != nil {
    log.Fatalf("failed to compile program: %s", err)
}

lsigBinary, err := base64.StdEncoding.DecodeString(result.Result)
if err != nil {
    log.Fatalf("failed to decode compiled program: %s", err)
}
Snippet Source

Once a TEAL program is compiled, the bytes of the program can be used as a parameter to the LogicSigAccount method. Most of the SDKs support the bytes encoded in base64 or hexadecimal format.

The binary bytes are used in the SDKs as shown below. If using the goal command-line tool to compile the TEAL code, these same bytes can be retrieved using the following commands.

//hexdump 
$ hexdump -C simple.teal.tok
//base64 format
$ cat simple.teal.tok | base64
ASABACI=

The response result from the TEAL compile command above is used to create the program variable. This variable can then be used as an input parameter to the function to make a logic signature.

let smartSig = new algosdk.LogicSig(
  new Uint8Array(Buffer.from(b64program, 'base64'))
);
Snippet Source

program = base64.b64decode(compiled_program)
lsig = transaction.LogicSigAccount(program)
Snippet Source

LogicSigAccount lsig = new LogicSigAccount(tealBinary, null);
Snippet Source

lsig := crypto.LogicSigAccount{
    Lsig: types.LogicSig{Logic: lsigBinary, Args: nil},
}
Snippet Source

Passing parameters using the SDKs

The SDKs require that parameters to a smart signature TEAL program be in byte arrays. This byte array is passed to the method that creates the logic signature. Currently, these parameters must be either unsigned integers or binary strings. If comparing a constant string in TEAL, the constant within the TEAL program must be encoded in hex or base64. See the TEAL tab below for a simple example of comparing the string argument used in the other examples. SDK native language functions can be used to encode the parameters to the TEAL program correctly. The example below illustrates both a string parameter and an integer.

Info

The samples show setting parameters at the creation of the logic signature. These parameters can be changed at the time of submitting the transaction.

const args = [Buffer.from('This is an argument!')];
smartSig = new algosdk.LogicSig(
  new Uint8Array(Buffer.from(b64program, 'base64')),
  args
);
Snippet Source

# string parameter
arg_str = "my string"
arg1 = arg_str.encode()
lsig = transaction.LogicSigAccount(compiled_program, args=[arg1])
# OR integer parameter
arg1 = (123).to_bytes(8, "big")
lsig = transaction.LogicSigAccount(compiled_program, args=[arg1])
Snippet Source

List<byte[]> tealArgs = new ArrayList<byte[]>();
// The arguments _must_ be byte arrays
byte[] arg1 = Encoder.encodeUint64(123l);
tealArgs.add(arg1);
LogicSigAccount lsigWithArgs = new LogicSigAccount(tealBinaryWithArgs, tealArgs);
Snippet Source

encodedArg := make([]byte, 8)
binary.BigEndian.PutUint64(encodedArg, 123)

lsigWithArgs := crypto.LogicSigAccount{
    Lsig: types.LogicSig{Logic: lsigBinary, Args: [][]byte{encodedArg}},
}
Snippet Source

Contract account SDK usage

Smart signatures can be used as contract accounts and allow TEAL logic to determine when outgoing account transactions are approved. The compiled TEAL program produces an Algorand Address, which is funded with Algos or Algorand Assets. As the receiver of a transaction, these accounts function as any other account. When the account is specified as the sender in a transaction, the TEAL logic is evaluated and determines if the transaction is approved. The ASC1 Usage Modes documentation explains ASC1 modes in more detail. In most cases, it is preferrable to use smart contract escrow accounts over smart signatures as smart signatures require the logic to be supplied for every transaction.

Smart signature contract account transactions where the sender is set to the contract account, function much in the same way as normal Algorand transactions. The major difference is that instead of the transaction being signed with a private key, the transaction is signed with a logic signature.

Contract Accounts are created by compiling the TEAL logic within the smart signature. Once the contract account is created, it can be used as any other address. To send tokens or assets from the account the transaction must be signed by a Logic Signature. From an SDK standpoint, the following process should be used.

  • Load the Program Bytes into the SDK.
  • Create a Logic Signature based on the program.
  • Fund the contract address (as any other account the minimum balance is 0.1 ALGO).
  • Create the Transaction.
  • Set the from transaction property to the contract address.
  • Sign the Transaction with the Logic Signature.
  • Send the Transaction to the network.

Transaction From Contract Account
Transaction From Contract Account

The following example illustrates compiling a TEAL program and signing a transaction with a created logic signature.

const smartSigTxn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
  from: smartSig.address(),
  to: funder.addr,
  amount: 0.1e6,
  suggestedParams,
});

const signedSmartSigTxn = algosdk.signLogicSigTransactionObject(
  smartSigTxn,
  smartSig
);

await client.sendRawTransaction(signedSmartSigTxn.blob).do();
await algosdk.waitForConfirmation(client, signedSmartSigTxn.txID, 3);
Snippet Source

# Create an algod client
lsig_args_path = Path("lsig") / "sample_arg.teal"
compiled_program = compile_lsig(lsig_args_path)
program_binary = base64.b64decode(compiled_program)
arg1 = (123).to_bytes(8, "big")
lsig = transaction.LogicSigAccount(program_binary, args=[arg1])
sender = lsig.address()
# Get suggested parameters
params = algod_client.suggested_params()
# Build transaction
amount = 10000
# Create a transaction
txn = transaction.PaymentTxn(sender, params, receiver, amount)
# Create the LogicSigTransaction with contract account LogicSigAccount
lstx = transaction.LogicSigTransaction(txn, lsig)

# Send raw LogicSigTransaction to network
txid = algod_client.send_transaction(lstx)
print("Transaction ID: " + txid)
# wait for confirmation
confirmed_txn = transaction.wait_for_confirmation(algod_client, txid, 4)
print(
    "Result confirmed in round: {}".format(
        confirmed_txn["confirmed-round"]
    )
)
Snippet Source

TransactionParametersResponse params = algodClient.TransactionParams().execute().body();
// create a transaction
Transaction txn = Transaction.PaymentTransactionBuilder()
                .sender(lsig.getAddress())
                .amount(100000)
                .receiver(seedAcct.getAddress())
                .suggestedParams(params)
                .build();
// create the LogicSigTransaction with contract account LogicSigAccount
SignedTransaction stx = Account.signLogicsigTransaction(lsig.lsig, txn);
// send raw LogicSigTransaction to network
Response<PostTransactionsResponse> submitResult = algodClient.RawTransaction()
                .rawtxn(Encoder.encodeToMsgPack(stx)).execute();
String txid = submitResult.body().txId;
// Wait for transaction confirmation
PendingTransactionResponse pTrx = Utils.waitForConfirmation(algodClient, txid, 4);
System.out.printf("Transaction %s confirmed in round %d\n", txid, pTrx.confirmedRound);
Snippet Source

sp, err = algodClient.SuggestedParams().Do(context.Background())
if err != nil {
    log.Fatalf("failed to get suggested params: %s", err)
}

lsigAddr, err := lsig.Address()
if err != nil {
    log.Fatalf("failed to get lsig address: %s", err)
}
ptxn, err := transaction.MakePaymentTxn(lsigAddr.String(), seedAddr, 10000, nil, "", sp)
if err != nil {
    log.Fatalf("failed to make transaction: %s", err)
}
txid, stxn, err := crypto.SignLogicSigAccountTransaction(lsig, ptxn)
if err != nil {
    log.Fatalf("failed to sign transaction with lsig: %s", err)
}
_, err = algodClient.SendRawTransaction(stxn).Do(context.Background())
if err != nil {
    log.Fatalf("failed to send transaction: %s", err)
}

payResult, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background())
if err != nil {
    log.Fatalf("failed while waiting for transaction: %s", err)
}
log.Printf("Lsig pay confirmed in round: %d", payResult.ConfirmedRound)
Snippet Source

Account delegation SDK usage

Smart signatures allow TEAL logic to be used to delegate signature authority. This allows specific accounts or multi-signature accounts to sign logic that allows transactions from the account to be approved based on the TEAL logic. The ASC1 Usage Modes documentation explains ASC1 modes in more detail.

Delegated transactions are special transactions where the sender signs the logic and the transaction is then signed with the logic signature. In all other aspects, the transaction functions as any other transaction.

Delegated Logic Signatures require that the logic signature be signed from a specific account or a multi-signature account. The TEAL program is first loaded, then a Logic Signature is created and then the Logic Signature is signed by a specific account or multi-signature account. The transaction is created as normal. The transaction is then signed with the Logic Signature. From an SDK standpoint, the following process should be used.

  • Load the Program Bytes into the SDK.
  • Create a Logic Signature based on the program.
  • Sign The Logic Signature with a specific account
  • Create the Transaction.
  • Set the from transaction property to the Address that signed the logic.
  • Sign the Transaction with the Logic Signature.
  • Send the Transaction to the network.

Delegated Signature Transaction
Delegated Signature Transaction

The following example illustrates signing a transaction with a created logic signature that is signed by a specific account.

const userAccount = accounts[1];

// sign sig with userAccount so the program can send transactions from userAccount
smartSig.sign(userAccount.privateKey);

const delegatedTxn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
  from: userAccount.addr,
  to: funder.addr,
  amount: 0.1e6,
  suggestedParams,
});

// use signLogicSigTransactionObject instead of the typical Transaction.signTxn function
const signedDelegatedTxn = algosdk.signLogicSigTransactionObject(
  delegatedTxn,
  smartSig
);

await client.sendRawTransaction(signedDelegatedTxn.blob).do();
await algosdk.waitForConfirmation(client, signedDelegatedTxn.txID, 3);
Snippet Source

lsig_args_path = Path("lsig") / "sample_arg.teal"
compiled_program = compile_lsig(lsig_args_path)
program_binary = base64.b64decode(compiled_program)
arg1 = (123).to_bytes(8, "big")
lsig = transaction.LogicSigAccount(program_binary, args=[arg1])

# Sign the logic signature with an account sk
lsig.sign(signer_acct.private_key)

# Get suggested parameters
params = algod_client.suggested_params()
amount = 10000
# Create a transaction where sender is the account that
# is the delegating account
txn = transaction.PaymentTxn(
    signer_acct.address, params, receiver_acct.address, amount
)

# Create the LogicSigTransaction with contract account LogicSigAccount
lstx = transaction.LogicSigTransaction(txn, lsig)

# Send raw LogicSigTransaction to network
txid = algod_client.send_transaction(lstx)
print("Transaction ID: " + txid)

confirmed_txn = transaction.wait_for_confirmation(algod_client, txid, 4)
print(
    "Result confirmed in round: {}".format(
        confirmed_txn["confirmed-round"]
    )
)
Snippet Source

// account signs the logic, and now the logic may be passed instead
// of a signature for a transaction
LogicsigSignature delegateLsig = seedAcct.signLogicsig(lsigWithArgs.lsig);
params = algodClient.TransactionParams().execute().body();
// create a transaction where the sender is the signer of the lsig
txn = Transaction.PaymentTransactionBuilder()
                .sender(seedAcct.getAddress())
                .amount(100000)
                .receiver(delegateLsig.toAddress())
                .suggestedParams(params)
                .build();
// Sign the transaction with the delegate lsig
stx = Account.signLogicsigTransaction(delegateLsig, txn);
// send raw LogicSigTransaction to network
submitResult = algodClient.RawTransaction().rawtxn(Encoder.encodeToMsgPack(stx)).execute();
txid = submitResult.body().txId;
// Wait for transaction confirmation
PendingTransactionResponse delegatResponse = Utils.waitForConfirmation(algodClient, txid, 4);
System.out.printf("Transaction %s confirmed in round %d\n", txid, delegatResponse.confirmedRound);
Snippet Source

// account signs the logic, and now the logic may be passed instead
// of a signature for a transaction
var args [][]byte
delSig, err := crypto.MakeLogicSigAccountDelegated(lsigBinary, args, seedAcct.PrivateKey)
if err != nil {
    log.Fatalf("failed to make delegate lsig: %s", err)
}

delSigPay, err := transaction.MakePaymentTxn(seedAddr, lsigAddr.String(), 10000, nil, "", sp)
if err != nil {
    log.Fatalf("failed to make transaction: %s", err)
}

delTxId, delStxn, err := crypto.SignLogicSigAccountTransaction(delSig, delSigPay)
if err != nil {
    log.Fatalf("failed to sign with delegate sig: %s", err)
}

_, err = algodClient.SendRawTransaction(delStxn).Do(context.Background())
if err != nil {
    log.Fatalf("failed to send transaction: %s", err)
}

delPayResult, err := transaction.WaitForConfirmation(algodClient, delTxId, 4, context.Background())
if err != nil {
    log.Fatalf("failed while waiting for transaction: %s", err)
}

log.Printf("Delegated Lsig pay confirmed in round: %d", delPayResult.ConfirmedRound)
Snippet Source