Skip to content

Logic Signature

← Back to Transactions

This example demonstrates how to use a logic signature (lsig) to authorize transactions. Key concepts:

  • Compiling a TEAL program using algod.tealCompile()
  • Creating a LogicSig from compiled program bytes
  • Understanding the logic signature address (derived from program hash)
  • Funding and using a logic signature as a standalone account
  • Creating a delegated logic signature where an account delegates signing to a program Logic signatures allow transactions to be authorized by a program instead of (or in addition to) a cryptographic signature. This enables smart contracts that can hold and send funds based purely on program logic.
  • LocalNet running (via algokit localnet start)

From the repository root:

Terminal window
cd examples
npm run example transact/11-logic-sig.ts

View source on GitHub

11-logic-sig.ts
/**
* Example: Logic Signature
*
* This example demonstrates how to use a logic signature (lsig) to authorize transactions.
*
* Key concepts:
* - Compiling a TEAL program using algod.tealCompile()
* - Creating a LogicSig from compiled program bytes
* - Understanding the logic signature address (derived from program hash)
* - Funding and using a logic signature as a standalone account
* - Creating a delegated logic signature where an account delegates signing to a program
*
* Logic signatures allow transactions to be authorized by a program instead of (or in addition to)
* a cryptographic signature. This enables smart contracts that can hold and send funds based
* purely on program logic.
*
* Prerequisites:
* - LocalNet running (via `algokit localnet start`)
*/
import { AlgorandClient } from '@algorandfoundation/algokit-utils'
import {
assignFee,
LogicSig,
LogicSigAccount,
Transaction,
TransactionType,
type PaymentTransactionFields,
} from '@algorandfoundation/algokit-utils/transact'
import {
createAlgodClient,
formatAlgo,
getAccountBalance,
loadTealSource,
printHeader,
printInfo,
printStep,
printSuccess,
shortenAddress,
waitForConfirmation,
} from '../shared/utils.js'
/**
* Gets a funded account from LocalNet's KMD wallet
*/
async function getLocalNetFundedAccount(algorand: AlgorandClient) {
return await algorand.account.kmd.getLocalNetDispenserAccount()
}
async function main() {
printHeader('Logic Signature Example')
// Step 1: Initialize clients
printStep(1, 'Initialize Algod Client')
const algod = createAlgodClient()
const algorand = AlgorandClient.defaultLocalNet()
printInfo('Connected to LocalNet Algod')
// Step 2: Compile a simple TEAL program using algod.tealCompile()
printStep(2, 'Compile TEAL Program')
// Load the "always approve" TEAL program from shared artifacts
// In real-world use cases, you would have logic that validates:
// - Who the receiver is
// - Maximum amount that can be sent
// - Time-based restrictions
// - etc.
const tealSource = loadTealSource('always-approve.teal')
printInfo('TEAL source code:')
printInfo(' #pragma version 10')
printInfo(' int 1')
printInfo(' return')
printInfo('')
printInfo('This program always returns 1 (true), meaning it approves all transactions.')
printInfo('WARNING: Real logic sigs should have proper validation logic!')
printInfo('')
// Compile the TEAL program using algod
const compileResult = await algod.tealCompile(tealSource)
const programBytes = new Uint8Array(Buffer.from(compileResult.result, 'base64'))
printInfo(`Compiled program size: ${programBytes.length} bytes`)
printInfo(`Program hash (base32): ${compileResult.hash}`)
// Step 3: Create LogicSig from the compiled program bytes
printStep(3, 'Create LogicSig from Program Bytes')
// The LogicSig wraps the compiled program
// Optionally, you can pass arguments to the program
const logicSig = new LogicSig(programBytes)
printInfo('LogicSig created from compiled program bytes')
printInfo('')
printInfo('How LogicSig address is derived:')
printInfo(' 1. Prefix "Program" is concatenated with program bytes')
printInfo(' 2. SHA512/256 hash is computed')
printInfo(' 3. Hash becomes the 32-byte public key equivalent')
printInfo(' 4. Address is derived same as for ed25519 keys')
// Step 4: Show the logic signature address
printStep(4, 'Show Logic Signature Address')
const lsigAddress = logicSig.addr
printInfo(`Logic signature address: ${lsigAddress.toString()}`)
printInfo('')
printInfo('This address is deterministically derived from the program.')
printInfo('Anyone with the same program can compute this address.')
printInfo('Funds sent to this address can only be spent by providing the program.')
// Step 5: Fund the logic signature address
printStep(5, 'Fund the Logic Signature Address')
const dispenser = await getLocalNetFundedAccount(algorand)
const fundingAmount = 5_000_000n // 5 ALGO
const suggestedParams = await algod.suggestedParams()
const fundTx = new Transaction({
type: TransactionType.Payment,
sender: dispenser.addr,
firstValid: suggestedParams.firstValid,
lastValid: suggestedParams.lastValid,
genesisHash: suggestedParams.genesisHash,
genesisId: suggestedParams.genesisId,
payment: {
receiver: lsigAddress,
amount: fundingAmount,
},
})
const fundTxWithFee = assignFee(fundTx, {
feePerByte: suggestedParams.fee,
minFee: suggestedParams.minFee,
})
const signedFundTx = await dispenser.signer([fundTxWithFee], [0])
await algod.sendRawTransaction(signedFundTx)
await waitForConfirmation(algod, fundTxWithFee.txId())
const lsigBalance = await getAccountBalance(algorand, lsigAddress.toString())
printInfo(`Funded logic signature with ${formatAlgo(fundingAmount)}`)
printInfo(`Logic signature balance: ${formatAlgo(lsigBalance.microAlgo)}`)
// Step 6: Create LogicSigAccount and use its signer to authorize a payment
printStep(6, 'Create LogicSigAccount and Send Payment')
// LogicSigAccount wraps the LogicSig and provides a signer function
// For a non-delegated lsig, the sender is the lsig address itself
const lsigAccount = new LogicSigAccount(programBytes)
const receiver = algorand.account.random()
const paymentAmount = 1_000_000n // 1 ALGO
const payParams = await algod.suggestedParams()
const paymentFields: PaymentTransactionFields = {
receiver: receiver.addr,
amount: paymentAmount,
}
const paymentTx = new Transaction({
type: TransactionType.Payment,
sender: lsigAddress, // The logic signature is the sender
firstValid: payParams.firstValid,
lastValid: payParams.lastValid,
genesisHash: payParams.genesisHash,
genesisId: payParams.genesisId,
payment: paymentFields,
})
const paymentTxWithFee = assignFee(paymentTx, {
feePerByte: payParams.fee,
minFee: payParams.minFee,
})
printInfo(`Payment amount: ${formatAlgo(paymentAmount)}`)
printInfo(`Sender (lsig): ${shortenAddress(lsigAddress.toString())}`)
printInfo(`Receiver: ${shortenAddress(receiver.addr.toString())}`)
printInfo('')
printInfo('How logic signature authorization works:')
printInfo(' 1. Transaction is created with lsig address as sender')
printInfo(' 2. Instead of a signature, the program bytes are attached')
printInfo(' 3. Network executes the program to validate the transaction')
printInfo(' 4. If program returns non-zero, transaction is authorized')
// Step 7: Submit transaction authorized by the logic signature
printStep(7, 'Submit Logic Signature Transaction')
// The LogicSigAccount.signer attaches the program instead of a signature
const signedTxns = await lsigAccount.signer([paymentTxWithFee], [0])
printInfo(`Signed transaction size: ${signedTxns[0].length} bytes`)
printInfo('(Contains program bytes instead of ed25519 signature)')
await algod.sendRawTransaction(signedTxns)
printInfo('Transaction submitted to network...')
const pendingInfo = await waitForConfirmation(algod, paymentTxWithFee.txId())
printInfo(`Transaction confirmed in round: ${pendingInfo.confirmedRound}`)
// Verify balances
const lsigBalanceAfter = await getAccountBalance(algorand, lsigAddress.toString())
let receiverBalance: bigint
try {
const info = await getAccountBalance(algorand, receiver.addr.toString())
receiverBalance = info.microAlgo
} catch {
receiverBalance = 0n
}
printInfo(`Logic signature balance after: ${formatAlgo(lsigBalanceAfter.microAlgo)}`)
printInfo(`Receiver balance: ${formatAlgo(receiverBalance)}`)
if (receiverBalance === paymentAmount) {
printSuccess('Receiver received the payment from logic signature!')
}
// Step 8: Demonstrate delegated logic signature
printStep(8, 'Demonstrate Delegated Logic Signature')
printInfo('A delegated logic signature allows an account to delegate')
printInfo('transaction authorization to a program. The account signs')
printInfo('the program once, and then transactions from that account')
printInfo('can be authorized by the program without further signatures.')
printInfo('')
// Create an account that will delegate to the lsig using AlgorandClient helper
const delegator = algorand.account.random()
// Fund the delegator account
const fundDelegatorParams = await algod.suggestedParams()
const fundDelegatorTx = new Transaction({
type: TransactionType.Payment,
sender: dispenser.addr,
firstValid: fundDelegatorParams.firstValid,
lastValid: fundDelegatorParams.lastValid,
genesisHash: fundDelegatorParams.genesisHash,
genesisId: fundDelegatorParams.genesisId,
payment: {
receiver: delegator.addr,
amount: 3_000_000n, // 3 ALGO
},
})
const fundDelegatorTxWithFee = assignFee(fundDelegatorTx, {
feePerByte: fundDelegatorParams.fee,
minFee: fundDelegatorParams.minFee,
})
const signedFundDelegatorTx = await dispenser.signer([fundDelegatorTxWithFee], [0])
await algod.sendRawTransaction(signedFundDelegatorTx)
await waitForConfirmation(algod, fundDelegatorTxWithFee.txId())
const delegatorBalance = await getAccountBalance(algorand, delegator.addr.toString())
printInfo(`Delegator account: ${shortenAddress(delegator.addr.toString())}`)
printInfo(`Delegator balance: ${formatAlgo(delegatorBalance.microAlgo)}`)
printInfo('')
// Create a delegated logic signature
// The delegator signs the program, allowing it to authorize transactions on their behalf
printInfo('Creating delegated logic signature...')
printInfo('The delegator signs the program bytes to create a delegation.')
printInfo('')
// Create a LogicSigAccount with the delegator's address
const delegatedLsig = new LogicSigAccount(programBytes, null, delegator.addr)
// Sign the lsig for delegation using the delegator's lsigSigner
await delegatedLsig.signForDelegation(delegator)
printInfo('Delegator has signed the program for delegation.')
printInfo(`Delegated lsig will authorize transactions FROM: ${shortenAddress(delegator.addr.toString())}`)
printInfo('')
printInfo('How delegation works:')
printInfo(' 1. Delegator signs: Hash("Program" || program_bytes) with their key')
printInfo(' 2. This signature is stored in the LogicSigAccount')
printInfo(' 3. Transactions include: program + delegator signature')
printInfo(' 4. Network verifies signature matches delegator public key')
printInfo(' 5. Then executes program to authorize the transaction')
// Create a payment from the delegator, authorized by the delegated lsig
const delegatedReceiver = algorand.account.random()
const delegatedPaymentAmount = 500_000n // 0.5 ALGO
const delegatedPayParams = await algod.suggestedParams()
const delegatedPaymentTx = new Transaction({
type: TransactionType.Payment,
sender: delegator.addr, // Sender is the delegator's address, NOT the lsig address
firstValid: delegatedPayParams.firstValid,
lastValid: delegatedPayParams.lastValid,
genesisHash: delegatedPayParams.genesisHash,
genesisId: delegatedPayParams.genesisId,
payment: {
receiver: delegatedReceiver.addr,
amount: delegatedPaymentAmount,
},
})
const delegatedPaymentTxWithFee = assignFee(delegatedPaymentTx, {
feePerByte: delegatedPayParams.fee,
minFee: delegatedPayParams.minFee,
})
printInfo(`Delegated payment amount: ${formatAlgo(delegatedPaymentAmount)}`)
printInfo(`Sender (delegator account): ${shortenAddress(delegator.addr.toString())}`)
printInfo(`Receiver: ${shortenAddress(delegatedReceiver.addr.toString())}`)
// Sign with the delegated lsig - this uses the program + stored delegation signature
const delegatedSignedTxns = await delegatedLsig.signer([delegatedPaymentTxWithFee], [0])
await algod.sendRawTransaction(delegatedSignedTxns)
printInfo('Delegated transaction submitted to network...')
const delegatedPendingInfo = await waitForConfirmation(algod, delegatedPaymentTxWithFee.txId())
printInfo(`Transaction confirmed in round: ${delegatedPendingInfo.confirmedRound}`)
// Verify balances
const delegatorBalanceAfter = await getAccountBalance(algorand, delegator.addr.toString())
let delegatedReceiverBalance: bigint
try {
const info = await getAccountBalance(algorand, delegatedReceiver.addr.toString())
delegatedReceiverBalance = info.microAlgo
} catch {
delegatedReceiverBalance = 0n
}
printInfo(`Delegator balance after: ${formatAlgo(delegatorBalanceAfter.microAlgo)}`)
printInfo(`Receiver balance: ${formatAlgo(delegatedReceiverBalance)}`)
if (delegatedReceiverBalance === delegatedPaymentAmount) {
printSuccess('Delegated logic signature successfully authorized the transaction!')
}
// Summary
printInfo('')
printInfo('Summary - Logic Signature Key Points:')
printInfo(' - LogicSig wraps a compiled TEAL program')
printInfo(' - The lsig address is derived from the program hash')
printInfo(' - Non-delegated: lsig acts as its own account')
printInfo(' - Delegated: an account signs the program to delegate auth')
printInfo(' - Program is executed to validate each transaction')
printInfo(' - Real programs should have strict validation logic!')
printInfo('')
printInfo('Common use cases for logic signatures:')
printInfo(' - Escrow accounts with release conditions')
printInfo(' - Hash time-locked contracts (HTLC)')
printInfo(' - Recurring payment authorizations')
printInfo(' - Multi-condition authorization logic')
printSuccess('Logic signature example completed!')
}
main().catch((error) => {
console.error('Error:', error)
process.exit(1)
})