AlgoSigner injects a JavaScript library into every web page the browser user visits, which allows the site to interact with the extension. The dApp can use the injected library to connect to the user's Wallet, request account addresses it holds, ask AlgoSigner to request the user to sign a transaction initiated by the application, and to post signed transactions to the network.
As part of the process of supporting the Algorand Foundations ARCs, in 1.10.0 a number of non-breaking additions have been made to support how dApps will work with AlgoSigner. In time, the existing legacy features will be deprecated.
-
A new top level object,
window.algorand
is made available and can be accessed by the dapp to make calls to AlgoSigner. The existingwindow.AlgoSigner
object remains but will be deprecated over the next year. -
An updated connection flow and address discovery process for dApps is in place, using
algorand.enable()
. The existing connection flow persists but will be deprecated over the next 3-6 months. -
Dapps may also now request for AlgoSigner to directly post signed transactions to the network and not return the signed blob to the dApp for handling.
-
Additional documentation regarding the use of
authAddr
for signing transactions with rekeyed accounts.
NOTE: This guide refers only to the post-1.10.0 features and the window.algorand
object. If you're looking for information on the window.AlgoSigner
object, please refer to the legacy Integration Guide
- algorand.enable(enableOpts)
- algorand.signTxns([txnObjects, ...])
- algorand.postTxns([signedTxns, ...])
- algorand.signAndPostTxns([txnObjects, ...])
In order for dApps to interact with AlgoSigner, they must first request access by calling algorand.enable()
. This will prompt the user to select which accounts they which to share with the dApp as well as the network they wish to operate on.
After the user selects which network and accounts to share with the dApp, they'll be returned by AlgoSigner as the response to the algorand.enable()
call.
export type EnableResponse = {
genesisID: specific genesis ID shared by the user,
genesisHash: specific genesis hash shared by the user,
accounts: array of specific accounts shared by the user
};
Request
await algorand.enable();
Response
{
"genesisID": "NETWORK_ID",
"genesisHash": "NETWORK_HASH",
"accounts": [
"ACCOUNT_1",
"ACCOUNT_2",
],
}
In cases were the dApp wishes to request specific accounts, a specific network or both; they will be able to do so by providing additional parameters to the algorand.enable()
call.
export type EnableOpts = {
genesisID?: [optional] specific genesis ID requested by the dApp,
genesisHash?: [optional] specific genesis hash requested by the dApp,
accounts?: [optional] array of specific accounts requested by the dApp,
};
If either EnableOpts.genesisID
or EnableOpts.genesisHash
are provided, they must match one of the networks available in AlgoSigner. In cases were there's ambiguity, such as to networks having a common genesisID
, the user will be prompted to choose between the available matching networks.
If neither EnableOpts.genesisID
nor EnableOpts.genesisHash
are provided, the user will be able to select which network they want to grant access to for the dApp.
If EnableOpts.accounts
is provided, the requested accounts will appear as required for the user and the user will be prompted to grant control over the specified accounts; these accounts will be positioned at the start of the array in the response. The user may share additional accounts than those requested by the dApp, in which case the accounts will be appended at the end of the returning account array. The user may also choose to share fewer accounts than those requested, in which case the promise will be rejected and the rejected accounts will be found inside the data.accounts
property of the error.
If EnableOpts.accounts
is not provided, the user will be prompted to select which accounts they wish to share with the dApp.
Request
await algorand.enable({
genesisID: 'mainnet-v1.0',
accounts: ['REQUESTED_ACCOUNT'],
});
Response
User granting access to an additional account:
{
"genesisID": "mainnet-v1.0",
"genesisHash": "USER_SELECTED_HASH",
"accounts": [
"REQUESTED_ACCOUNT",
"ADDITIONAL_USER_SELECTED_ACCOUNT",
],
}
User not granting access to an account requested by the dApp:
{
"code": 4400,
"message": "...",
"data": {
"accounts": [
"REQUESTED_ACCOUNT",
],
},
}
In cases where an enable call is made after a user has already authorized the dApp there will be additional checks before prompting the user. There is an ephemeral single network cached history and if the requested network from the dApp matches the previous one and the accounts are the same or a subset of the cached approved accounts the cache will be returned instead of prompting the user.
If the network is different or there are additional accounts requested compared to the cache the user will be prompted to re-authorize the request.
Transactions provided to AlgoSigner will be validated against the Algorand JS SDK transaction types. Field names must match and the whole transaction will be rejected otherwise.
Transactions objects need to be presented with the following structure:
export type TxnObject = {
txn: Base64-encoded string of a transaction binary,
signers?: [optional] array of addresses to sign with (defaults to the sender),
stxn?: [optional] Base64-encoded string of a signed transaction binary,
multisig?: [optional] extra metadata needed for multisig transactions,
authAddr?: [optional] used to specify which account is doing the signing when dealing with rekeyed accounts,
};
In order to facilitate conversion between different formats and encodings, helper encoding functions are available on the algorand.encoding.*
namespace.
Also available on transactions built with the JS SDK is the .toByte()
method that converts the SDK transaction object into it's binary format.
Send transaction objects, conforming to the Algorand JS SDK, to AlgoSigner for approval. The network is determined from the genesis-id
property.
If approved, the response is an array of base64-encoded signed transaction objects.
Request
algorand.signTxns([
{
txn: 'iqNhbXRko2ZlZc0D6KJmds4A259Go2dlbqx0ZXN0bmV0LXYxLjCiZ2jEIEhjtRiks8hOyBDyLU8QgcsPcfBZp6wg3sYvf3DlCToio2dycMQgdsLAGqgrtwqqQS4UEN7O8CZHjfhPTwLHrB1A2pXwvKGibHbOANujLqNyY3bEIK0TEDcptY0uFvk2V5LDVzRfdz7O4freYHEuZbpI+6hMo3NuZMQglmyhKUPeU2KALzt/Jcs0GQ55k2vsqZ4pGeNlzpnYLbukdHlwZaNwYXk=',
},
]);
NOTE: Even though the method accepts an array of transactions, it requires atomic transactions that share a groupId and will error on non-atomic groups.
Response
[
"gqNzaWfEQL6mW/7ss2HKAqsuHN/7ePx11wKSAvFocw5QEDvzSvrvJdzWYvT7ua8Lc0SS0zOmUDDaHQC/pGJ0PNqnu7W3qQKjdHhuiaNhbXQGo2ZlZc4AA7U4omZ2zgB+OrujZ2VurHRlc3RuZXQtdjEuMKJnaMQgSGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiKibHbOAH4+o6NyY3bEIHhydylNDQQhpD9QdKWejLCMBgb5UYJTGCfDW3KgLsI+o3NuZMQgZM5ZNuFgR8pz2dHBgDlmHolfGgF96zX/X4x2bnAJ3aqkdHlwZaNwYXk=",
]
Example
// Connect to AlgoSigner
await algorand.enable();
// Create an Algod client to get suggested transaction params
let client = new algosdk.Algodv2(token, server, port, headers);
let suggestedParams = await client.getTransactionParams().do();
// Use the JS SDK to build a Transaction
let sdkTxn = new algosdk.Transaction({
to: 'RECEIVER_ADDRESS',
from: 'SENDER_ADDRESS',
amount: 100,
...suggestedParams,
});
// Get the binary and base64 encode it
let binaryTxn = sdkTxn.toByte();
let base64Txn = algorand.encoding.msgpackToBase64(binaryTxn);
let signedTxns = await algorand.signTxns([
{
txn: base64Txn,
},
]);
Alternatively, you can provide multiple arrays of transactions at once. Same rules regarding the contents of the groups apply.
Request
algorand.signTxns([
[
{
txn: 'iqNhbXRko2ZlZc0D6KJmds4A259Go2dlbqx0ZXN0bmV0LXYxLjCiZ2jEIEhjtRiks8hOyBDyLU8QgcsPcfBZp6wg3sYvf3DlCToio2dycMQgdsLAGqgrtwqqQS4UEN7O8CZHjfhPTwLHrB1A2pXwvKGibHbOANujLqNyY3bEIK0TEDcptY0uFvk2V5LDVzRfdz7O4freYHEuZbpI+6hMo3NuZMQglmyhKUPeU2KALzt/Jcs0GQ55k2vsqZ4pGeNlzpnYLbukdHlwZaNwYXk=',
},
],
[
{
txn: 'iaRhZnJ6w6RmYWRkxCCWbKEpQ95TYoAvO38lyzQZDnmTa+ypnikZ42XOmdgtu6RmYWlkzgDITmiiZnbOAQNjIKNnZW6sdGVzdG5ldC12MS4womdoxCBIY7UYpLPITsgQ8i1PEIHLD3HwWaesIN7GL39w5Qk6IqJsds4BA2cIo3NuZMQglmyhKUPeU2KALzt/Jcs0GQ55k2vsqZ4pGeNlzpnYLbukdHlwZaRhZnJ6',
},
],
]);
Response
[
[
"gqNzaWfEQL6mW/7ss2HKAqsuHN/7ePx11wKSAvFocw5QEDvzSvrvJdzWYvT7ua8Lc0SS0zOmUDDaHQC/pGJ0PNqnu7W3qQKjdHhuiaNhbXQGo2ZlZc4AA7U4omZ2zgB+OrujZ2VurHRlc3RuZXQtdjEuMKJnaMQgSGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiKibHbOAH4+o6NyY3bEIHhydylNDQQhpD9QdKWejLCMBgb5UYJTGCfDW3KgLsI+o3NuZMQgZM5ZNuFgR8pz2dHBgDlmHolfGgF96zX/X4x2bnAJ3aqkdHlwZaNwYXk=",
],
[
"gqNzaWfEQC8ZIPYimAypJD2TmEQjuWxEEk8/gJbBegEHdtyKr6TuA78otKIEB9PYQimgMLGn87YOEB6GgRe5vjWRTuWGsAqjdHhuiaRhZnJ6w6RmYWRkxCCWbKEpQ95TYoAvO38lyzQZDnmTa+ypnikZ42XOmdgtu6RmYWlkzgDITmiiZnbOAQNjO6NnZW6sdGVzdG5ldC12MS4womdoxCBIY7UYpLPITsgQ8i1PEIHLD3HwWaesIN7GL39w5Qk6IqJsds4BA2cjo3NuZMQglmyhKUPeU2KALzt/Jcs0GQ55k2vsqZ4pGeNlzpnYLbukdHlwZaRhZnJ6",
]
]
The signed transactions can then be sent using the SDK (example below) or using the algorand.postTxns() method.
// Get the base64 encoded signed transaction and convert it to binary
let binarySignedTxn = algorand.encoding.base64ToMsgpack(signedTxns[0]);
// Send the transaction through the SDK client
await client.sendRawTransaction(binarySignedTxn).do();
For Atomic transactions, provide an array of transaction objects with the same group ID, provided in the same order as when the group was assigned.
Example
let txn1 = new algosdk.Transaction({
to: 'SECOND_ADDRESS',
from: 'FIRST_ADDRESS',
amount: 100,
...suggestedParams,
});
let txn2 = new algosdk.Transaction({
to: 'FIRST_ADDRESS',
from: 'SECOND_ADDRESS',
amount: 100,
...suggestedParams,
});
// Assign a Group ID to the transactions using the SDK
algosdk.assignGroupID([txn1, txn2]);
let binaryTxns = [txn1.toByte(), txn2.toByte()];
let base64Txns = binaryTxns.map((binary) => algorand.encoding.msgpackToBase64(binary));
let signedTxns = await algorand.signTxns([
{
txn: base64Txns[0],
},
{
txn: base64Txns[1],
},
]);
The signed transaction array can then be sent using the SDK.
let binarySignedTxns = signedTxns.map((stxn) => algorand.encoding.base64ToMsgpack(stxn));
await client.sendRawTransaction(binarySignedTxns).do();
In case not all group transactions belong to accounts on AlgoSigner, you can set the signers
field of the transaction object as an empty array to specify that it's only being sent to AlgoSigner for reference and group validation, not for signing. Reference transactions should look like this:
{
txn: 'B64_TXN',
signers: [],
// This tells AlgoSigner that this transaction is not meant to be signed
}
algorand.signTxns()
will return null
in the position(s) where reference transactions were provided. In these instances, you'd have to sign the missing transaction(s) by your own means before they can be sent. This is useful for transactions that require external signing, like lsig
transactions.
You can provide a signed reference transaction to AlgoSigner via the stxn
field of the transaction object for it to be validated and returned as part of the group. For the transaction(s) where stxn
was provided, algorand.signTxns()
will return the stxn
string in the same position of the response array as the corresponding reference transaction(s) instead of null
.
Example
let txn1 = new algosdk.Transaction({
to: 'EXTERNAL_ACCOUNT',
from: 'ACCOUNT_IN_ALGOSIGNER',
amount: 100,
...suggestedParams,
});
let txn2 = new algosdk.Transaction({
to: 'ACCOUNT_IN_ALGOSIGNER',
from: 'EXTERNAL_ACCOUNT',
amount: 100,
...suggestedParams,
});
algosdk.assignGroupID([txn1, txn2]);
let binaryTxns = [txn1.toByte(), txn2.toByte()];
let base64Txns = binaryTxns.map((binary) => algorand.encoding.msgpackToBase64(binary));
let signedTxns = await algorand.signTxns([
{
txn: base64Txns[0],
},
{
txn: base64Txns[1],
signers: [],
stxn: 'MANUALLY_SIGNED_SECOND_TXN_B64',
},
]);
Response
[
'ALGOSIGNER_SIGNED_B64',
'MANUALLY_SIGNED_SECOND_TXN_B64',
];
In case you can't or don't want to provide the stxn
, the provided transaction should look like this:
Example
let signedTxns = await algorand.signTxns([
{
txn: base64Txns[0],
},
{
txn: base64Txns[1],
signers: [],
},
]);
Response
[
'ALGOSIGNER_SIGNED_B64',
null,
];
Afterwards, you can sign and send the remaining transaction(s) with the SDK:
// Convert first transaction to binary from the response
let signedTxn1Binary = algorand.encoding.base64ToMsgpack(signedTxns[0]);
// Sign leftover transaction with the SDK
let externalAccount = algosdk.mnemonicToSecretKey('EXTERNAL_ACCOUNT_MNEMONIC');
let signedTxn2Binary = txn2.signTxn(externalAccount.sk);
await client.sendRawTransaction([signedTxn1Binary, signedTxn2Binary]).do();
For Multisig transactions, an additional metadata object is required that adheres to the Algorand multisig parameters structure when creating a new multisig account:
{
version: number,
threshold: number,
addrs: string[]
}
algorand.signTxns()
will validate that the resulting multisig address made from the provided parameters matches the sender address and try to sign with every account on the addrs
array that is also on AlgoSigner.
NOTE: algorand.signTxns()
only accepts unsigned multisig transactions. In case you need to add more signatures to partially signed multisig transactions, please use the SDK.
Example
let multisigParams = {
version: 1,
threshold: 1,
addrs: ['FIRST_ADDRESS', 'SECOND_ADDRESS', 'ADDRESS_NOT_IN_ALGOSIGNER'],
};
let multisigAddress = algosdk.multisigAddress(multisigParams);
let multisigTxn = new algosdk.Transaction({
to: 'RECEIVER_ADDRESS',
from: multisigAddress,
amount: 100,
...suggestedParams,
});
// Get the binary and base64 encode it
let binaryMultisigTxn = multisigTxn.toByte();
let base64MultisigTxn = algorand.encoding.msgpackToBase64(binaryMultisigTxn);
// This returns a partially signed Multisig Transaction with signatures for FIRST_ADDRESS and SECOND_ADDRESS
let signedTxns = await algorand.signTxns([
{
txn: base64MultisigTxn,
msig: multisigParams,
},
]);
In case you want to specify a subset of addresses to sign with, you can add them to the signers
list on the transaction object, like so:
// This returns a partially signed Multisig Transaction with signatures for SECOND_ADDRESS
let signedTxns = await algorand.signTxns([
{
txn: base64MultisigTxn,
msig: multisigParams,
signers: ['SECOND_ADDRESS'],
},
]);
When dealing with rekeyed accounts, the authorized address to be used to sign the transaction differs from the rekeyed account. The TxnObject.authAddr
field allows to specify a different sender address in those cases.
NOTE: If specified, AlgoSigner will sign the transaction using this authorized address even if it sees the sender address was not rekeyed to authAddr. This is because the sender may be rekeyed before the transaction is committed.
Example
let txn = new algosdk.Transaction({
to: 'REKEYED_ACCOUNT',
from: 'REKEYED_ACCOUNT',
amount: 100,
...suggestedParams,
});
let base64Txn = algorand.encoding.msgpackToBase64(txn.toByte());
let signedTxns = await algorand.signTxns([
{
txn: base64Txn,
authAddr: 'AUTHORIZED_ADDRESS_FOR_REKEY',
},
]);
Special Considerations
In cases where both TxnObject.authAddr
and TxnObject.msig
are provided, both addresses need to match or the transaction will be rejected.
In cases where both TxnObject.authAddr
and a TxnObject.signers
array with a single address are provided, but no TxnObject.msig
was provided; authAddr
needs to match the signer
or the transaction will be rejected.
Send a base64 encoded signed transaction blob to AlgoSigner to transmit to the Network.
Request
algorand.postTxns([signedTx]);
Response
export type PostResult = {
txnIDs: string[] | string[][],
};
{
"txnIDs": [
"OKU6A2QYMRSZAUEJUZL3PW5XKLTA6TKWQHIIBXDCO3KT5OHCULBA"
]
}
When providing more than one group of transactions at the same time, all transactions will be sent to the network simultaneously. In case some of the groups were unable to be commited to the network, the returning error will provide information on which ones were successful, as well as the reason behind the failed ones.
export type PartialPostError = {
successTxnIDs: (string | null)[][],
data: (string | null)[][],
code: number,
message: string,
};
The successTxnIDs
property includes the IDs of the transactions that were succesfully commited to the network while the data
property includes the reasons behind the failing ones. Both arrays are ordered respecting the original positions of the provided groups of transactions.
Request
algorand.postTxns([successfulTxn], [rejectedTxn]);
Response
{
"successTxnIDs": [
"OKU6A2QYMRSZAUEJUZL3PW5XKLTA6TKWQHIIBXDCO3KT5OHCULBA",
null,
],
"data": [
null,
"Reason behind network refusal",
],
"code": 4400,
"message": "...",
}
This methods takes the input from algorand.signTxns() and internally posts the signed transactions to the network before returning the response of algorand.postTxns() to the dApp.
Request
algorand.signTxns([
{
txn: 'iqNhbXRko2ZlZc0D6KJmds4A259Go2dlbqx0ZXN0bmV0LXYxLjCiZ2jEIEhjtRiks8hOyBDyLU8QgcsPcfBZp6wg3sYvf3DlCToio2dycMQgdsLAGqgrtwqqQS4UEN7O8CZHjfhPTwLHrB1A2pXwvKGibHbOANujLqNyY3bEIK0TEDcptY0uFvk2V5LDVzRfdz7O4freYHEuZbpI+6hMo3NuZMQglmyhKUPeU2KALzt/Jcs0GQ55k2vsqZ4pGeNlzpnYLbukdHlwZaNwYXk=',
},
]);
Response
{
"txnIDs": [
"OKU6A2QYMRSZAUEJUZL3PW5XKLTA6TKWQHIIBXDCO3KT5OHCULBA"
]
}
Custom networks beta support is now in AlgoSigner. A setup Guide can be found here.
- algorand.enable() calls accept genesis IDs/hashes that have been added to the user's custom network list as valid networks.
- Non-matching
genesisID
orgenesisHash
will result in a error.
- Non-matching
- Transaction requests will require a valid matching
genesisID
, even for custom networks.
Some of the following error codes may be returned when interacting with AlgoSigner. When available, any additional info regarding the error will be found on the data
property of the error.
Error Code | Description | Additional notes |
---|---|---|
4000 | An unknown error occured. | N/A |
4001 | The user rejected the signature request. | N/A |
4100 | The requested operation and/or account has not been authorized by the user. | This is usually due to the connection between the dApp and the wallet becoming stale and the user needing to reconnect. Otherwise, it may signal that you are trying to sign with private keys not found on AlgoSigner. |
4200 | The wallet does not support the requested operation. | Users need to have imported or created an account on AlgoSigner before connecting to dApps, as well as succesfully having configured any custom networks required. |
4201 | The wallet does not support signing that many transactions at a time. | The max number of transactions per group is 16. For Ledger devices, they currently can't sign more than one transaction at the same time. |
4202 | The wallet was not initialized properly beforehand. | The extension user has not authorized requests from this website. |
4300 | The input provided is invalid. | AlgoSigner rejected some of the transactions due to invalid fields. |
4400 | Some transactions were not sent properly. | Some, but not all of the transactions were able to be posted to the network. The IDs of the succesfully posted transactions as well as information on the failing ones are provided on the error. |
Additional information, if available, would be provided in the data
field of the error object.
Returned errors have the following object structure:
{
message: string;
code: number;
name: string;
data?: any;
}
Errors may be passed back to the dApp from the Algorand JS SDK if a transaction is valid, but has some other issue - for example, insufficient funds in the sending account.
algorand.enconding.*
contains a few different methods in order to help with the different formats and encodings that are needed when working with dApps and the SDK.
algorand.encoding.msgpackToBase64(): receives a binary object (as a Uint8Array) and returns the corresponding base64 encoded string,
algorand.encoding.base64ToMsgpack(): receives a base64 encoded string and returns the corresponding binary object (as a Uint8Array),
algorand.encoding.stringToByteArray(): receives a plain unencoded string and returns the corresponding binary object (as a Uint8Array),
algorand.encoding.byteArrayToString(): receives a binary object (as a Uint8Array) and returns the corresponding plain unencoded string,