Skip to main content

Signing Transactions

Overview

Wallets, if supported, can sign single, atomic or multiple transactions. If the wallet also supports it, the wallet can also sign transactions using multisigs and re-keyed accounts.

Signing a single transaction

Regardless of whether you are sending multiple transactions or a single transaction, an array of base64 encoded transactions will need to be sent.

const {
base64ToBytes,
bytesToBase64,
IBaseResult,
ISignTxnsResult,
} = require('@agoralabs-sh/algorand-provider');
const algosdk = require('algosdk');

try {
const encoder = new TextEncoder();
const client = new algosdk.Algodv2(
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
'http://localhost',
'4001',
);
const suggestedParams = await client.getTransactionParams().do();
const transaction = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
amount: algosdk.algosToMicroalgos(1),
from: 'P3AIQVDJ2CTH54KSJE63YWB7IZGS4W4JGC53I6GK72BGZ5BXO2B2PS4M4U',
note: encoder.encode('Hello Human!'),
suggestedParams,
to: '6GT6EXFDAHZDZYUOPT725ZRWYBZDCEGYT7SYYXGJKRFUAG5B7JMI7DQRNQ',
});
const transactionBytes = transaction.toByte();
const result = await window.algorand.signTxns({
txns: [
{
txn: bytesToBase64(transactionBytes),
},
],
});
let signedTransactionBytes;

console.log(result);
/*
{
id: 'awesome-wallet',
stxns: [
'gqNzaWfEQ...',
],
}
*/

// decode the base 64 encoded signed transactions
signedTransactionBytes = base64ToBytes(result.stxns[0]);

// send to the network
await client.sendRawTransaction(signedTransactionBytes).do();
} catch (error) {
// handle error
}
caution

If this operation is not supported, then a WalletOperationNotSupportedError will be thrown.

Signing atomic transactions

When sending atomic transactions, all transactions, regardless of whether the wallet is signing only some of the transactions, must be sent with a matching group ID.

const {
base64ToBytes,
bytesToBase64,
IBaseResult,
ISignTxnsResult,
} = require('@agoralabs-sh/algorand-provider');
const algosdk = require('algosdk');

try {
const encoder = new TextEncoder();
const client = new algosdk.Algodv2(
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
'http://localhost',
'4001',
);
const suggestedParams = await client.getTransactionParams().do();
const firstTransaction = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
amount: algosdk.algosToMicroalgos(1),
from: 'P3AIQVDJ2CTH54KSJE63YWB7IZGS4W4JGC53I6GK72BGZ5BXO2B2PS4M4U',
note: encoder.encode('I am the first transaction, human!'),
suggestedParams,
to: '6GT6EXFDAHZDZYUOPT725ZRWYBZDCEGYT7SYYXGJKRFUAG5B7JMI7DQRNQ',
});
const secondTransaction = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
amount: algosdk.algosToMicroalgos(2),
from: 'P3AIQVDJ2CTH54KSJE63YWB7IZGS4W4JGC53I6GK72BGZ5BXO2B2PS4M4U',
note: encoder.encode('I am the second transaction, human!'),
suggestedParams,
to: '6GT6EXFDAHZDZYUOPT725ZRWYBZDCEGYT7SYYXGJKRFUAG5B7JMI7DQRNQ',
});
let firstTransactionBytes;
let result;
let secondTransactionBytes;
let signedTransactionBytes;

// assign a group id
algosdk.assignGroupID([firstTransaction, secondTransaction]);

firstTransactionBytes = firstTransaction.toByte();
secondTransactionBytes = secondTransaction.toByte();

// send to the wallet to be signed
result = await window.algorand.signTxns({
txns: [
{
txn: bytesToBase64(firstTransactionBytes),
},
{
txn: bytesToBase64(secondTransactionBytes),
},
],
});

console.log(result);
/*
{
id: 'awesome-wallet',
stxns: [
'gqNzaWfEQ...',
'gqNzaWfEQ...',
],
}
*/

// decode the base 64 encoded signed transactions
signedTransactionBytes = result.stxns.map((value) => base64ToBytes(value));

// send to the network
await client.sendRawTransaction(signedTransactionBytes).do();
} catch (error) {
// handle error
}

Non-wallet signed transactions

In the case of a wallet being sent an atomic transaction, but only needing to sign a subset of the transactions, an empty signers array must be used. This instructs the wallet to skip the transaction even if it can sign it.

For example:

const result = await window.algorand.signTxns({
txns: [
{
txn: 'iqNhbXRko...',
},
{
signers: [], // an empty array instructs the wallet to skip signing this transaction
txn: 'iaRhZnJ6w...',
},
],
});

console.log(result);
/*
{
id: 'awesome-wallet',
stxns: [
'gqNzaWfEQ...',
null,
],
}
*/

As you can see from the above example, when the wallet skips signing that transaction, the resulting signed transaction list contains null where the transactions were not signed. In order to avoid this, you can supply the signed transaction (stxn) to the options object, when signing the transactions:

const result = await window.algorand.signTxns({
txns: [
{
txn: 'iqNhbXRko...',
},
{
signers: [], // an empty array instructs the wallet to skip signing this transaction
stxn: 'gqNzaWfEQ...', // providing the signed transaction will pass it to the result
txn: 'iaRhZnJ6w...',
},
],
});

console.log(result);
/*
{
id: 'awesome-wallet',
stxns: [
'gqNzaWfEQ...',
'gqNzaWfEQ...'
],
}
*/

Signing transactions with multisig accounts

When using multisig accounts to sign transactions, a few extra options must be provided to the options object.

The transaction must now include the msig object that conforms to algosdk.MultisigMetadata.

caution

The wallet may not support multisig accounts, in which case a WalletOperationNotSupportedError will be thrown.

const {
base64ToBytes,
bytesToBase64,
IBaseResult,
ISignTxnsResult,
} = require('@agoralabs-sh/algorand-provider');
const algosdk = require('algosdk');

try {
const encoder = new TextEncoder();
const client = new algosdk.Algodv2(
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
'http://localhost',
'4001',
);
const multisigParams = {
addrs: [
'P3AIQVDJ2CTH54KSJE63YWB7IZGS4W4JGC53I6GK72BGZ5BXO2B2PS4M4U', // authorized in wallet and will be used to sign
'6GT6EXFDAHZDZYUOPT725ZRWYBZDCEGYT7SYYXGJKRFUAG5B7JMI7DQRNQ', // authorized in wallet and will be used to sign
'TKDBCV4SCSVR2GECXL7NJ5U34PDNLEIMFBTOYVURAPG53OXJLF4LEDSNGE', // not in wallet
],
threshold: 1,
version: 1,
};
const multisigAddress = algosdk.multisigAddress(multisigParams);
const suggestedParams = await client.getTransactionParams().do();
const transaction = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
amount: algosdk.algosToMicroalgos(1),
from: multisigAddress,
note: encoder.encode('Hello Human!'),
suggestedParams,
to: '6GT6EXFDAHZDZYUOPT725ZRWYBZDCEGYT7SYYXGJKRFUAG5B7JMI7DQRNQ',
});
const transactionBytes = transaction.toByte();
const result = await window.algorand.signTxns({
txns: [
{
msig: multisigParams,
txn: bytesToBase64(transactionBytes),
},
],
});
let signedTransactionBytes;

console.log(result);
/*
{
id: 'awesome-wallet',
stxns: [
'gqNzaWfEQ...',
],
}
*/

// decode the base 64 encoded signed transaction
signedTransactionBytes = base64ToBytes(result.stxns[0]);

// send to the network
await client.sendRawTransaction(signedTransactionBytes).do();
} catch (error) {
// handle error
}

In the above example, even though the threshold is 1, because two of the signers are present and authorized in the wallet, both accounts were used to sign the transaction. This still makes the transaction valid.

If you want to only use one address, you can use the signers list to instruct the wallet to only use certain addresses to sign the transaction.

const multisigParams = {
addrs: [
'P3AIQVDJ2CTH54KSJE63YWB7IZGS4W4JGC53I6GK72BGZ5BXO2B2PS4M4U', // authorized in wallet, but will not be used to sign
'6GT6EXFDAHZDZYUOPT725ZRWYBZDCEGYT7SYYXGJKRFUAG5B7JMI7DQRNQ', // authorized in wallet, will be used to sign
'TKDBCV4SCSVR2GECXL7NJ5U34PDNLEIMFBTOYVURAPG53OXJLF4LEDSNGE', // not in wallet
],
threshold: 1,
version: 1,
};
const result = await window.algorand.signTxns({
txns: [
{
msig: multisigParams,
signers: [
'6GT6EXFDAHZDZYUOPT725ZRWYBZDCEGYT7SYYXGJKRFUAG5B7JMI7DQRNQ'
], // specifying a signer instructs the wallet to only use this address to sign
txn: 'iaRhZnJ6w...',
},
],
});

console.log(result);
/*
{
id: 'awesome-wallet',
stxns: [
'gqNzaWfEQ...',
],
}
*/
caution

If any of the accounts supplied in the signers list are not an authorized signer, then a UnauthorizedSignerError will be thrown.

Now, in the above example, only the address 6GT6EXFDAHZDZYUOPT725ZRWYBZDCEGYT7SYYXGJKRFUAG5B7JMI7DQRNQ is used to sign the transaction and as the threshold is only 1, the transaction is valid.

Signing transactions with re-keyed accounts

When attempting to sign a transaction using a re-keyed account, the authAddr field may be used to instruct the wallet to use this address as the signer of the transaction. Supplying the authAddr will override the sender address of the transaction field.

caution

The wallet may not support re-keyed accounts, in which case a WalletOperationNotSupportedError will be thrown.

const {
base64ToBytes,
bytesToBase64,
IBaseResult,
ISignTxnsResult,
} = require('@agoralabs-sh/algorand-provider');
const algosdk = require('algosdk');

try {
const encoder = new TextEncoder();
const client = new algosdk.Algodv2(
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
'http://localhost',
'4001',
);
const suggestedParams = await client.getTransactionParams().do();
const transaction = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
amount: algosdk.algosToMicroalgos(1),
from: 'P3AIQVDJ2CTH54KSJE63YWB7IZGS4W4JGC53I6GK72BGZ5BXO2B2PS4M4U', // the re-keyed account
note: encoder.encode('Hello Human!'),
suggestedParams,
to: '6GT6EXFDAHZDZYUOPT725ZRWYBZDCEGYT7SYYXGJKRFUAG5B7JMI7DQRNQ',
});
const transactionBytes = transaction.toByte();
const result = await window.algorand.signTxns({
txns: [
{
authAddr: 'S6V2ITXD3Q4MYATTN5IH75E3KS3WEMB36SVG2LXCQ3XESSH2DYOC3DAXYU', // 'auth-addr' of the re-keyed account
txn: bytesToBase64(transactionBytes),
},
],
});
let signedTransactionBytes;

console.log(result);
/*
{
id: 'awesome-wallet',
stxns: [
'gqNzaWfEQ...',
],
}
*/

// decode the base 64 encoded signed transactions
signedTransactionBytes = base64ToBytes(result.stxns[0]);

// send to the network
await client.sendRawTransaction(signedTransactionBytes).do();
} catch (error) {
// handle error
}
caution

If the supplied authAddr is not an authorized signer, then a UnauthorizedSignerError will be thrown.