SignA

Legacy dApp Integration: the AlgoSigner object

This is the old interface. window.AlgoSigner is kept so dApps written against AlgoSigner before 1.10.0 keep working, and SignA still answers every method below. New integrations should use the ARC-0027 provider or window.algorand instead - see the dApp integration guide.

The extension injects this library into every page the browser visits. A dApp can use it to connect to the wallet, discover the account addresses it holds, query the network through the wallet's algod and indexer connections, and ask the user to sign a transaction the dApp built. Every method returns a Promise.

Methods

Misc

Rejection Messages

Working with Custom Networks

Helper Functions

Method Detail

AlgoSigner.connect()

Requests access to the Wallet for the dApp, may be rejected or approved. Every access to the extension begins with a connect request, which if approved by the user, allows the dApp to follow-up with other requests.

AlgoSigner.accounts({ ledger: 'MainNet|TestNet' })

Returns an array of accounts present in the Wallet for the given Network.

Request

AlgoSigner.accounts({ ledger: 'TestNet' });

Response

[
  {
    "address": "U2VHSZL3LNGATL3IBCXFCPBTYSXYZBW2J4OGMPLTA4NA2CB4PR7AW7C77E"
  },
  {
    "address": "MTHFSNXBMBD4U46Z2HAYAOLGD2EV6GQBPXVTL727RR3G44AJ3WVFMZGSBE"
  }
]

API Calls

Developers may use SignA's connection to Algorand and Indexer API services. This is a convenience; dApps may just as well establish their own connections to retrieve parameters, query the Indexer, and send transactions to the chain.

Proxied requests go to whichever algod and indexer the selected network is configured with - Nodely's free tier for the three built-in networks, or whatever the user set for a custom one. Available endpoints are limited to what that server exposes, and it is not guaranteed to respond as expected. Only text responses are supported here, not message-packed ones.

AlgoSigner.algod({ ledger: 'MainNet|TestNet', path: 'algod v2 path', ... })

Proxies the requested path to the Algod v2 API. Is limited to endpoints made available by the API server. By default, all calls to the AlgoSigner.algod method are GET.

Request (GET)

AlgoSigner.algod({
  ledger: 'TestNet',
  path: '/v2/transactions/params',
});

Response

{
  "consensus-version": "https://github.com/algorandfoundation/specs/tree/e5f565421d720c6f75cdd186f7098495caf9101f",
  "fee": 1,
  "genesis-hash": "SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=",
  "genesis-id": "testnet-v1.0",
  "last-round": 8271323,
  "min-fee": 1000
}

To make a POST requests, more details need to be included in as input. More information can be found here.

Request (POST)

AlgoSigner.algod({
  ledger: 'TestNet',
  path: '/v2/teal/compile',
  body: 'int 0',
  method: 'POST',
  contentType: 'text/plain',
});

Response

{
  "hash": "KI4DJG2OOFJGUERJGSWCYGFZWDNEU2KWTU56VRJHITP62PLJ5VYMBFDBFE",
  "result": "ASABACI="
}

AlgoSigner.indexer({ ledger: 'MainNet|TestNet', path: 'indexer v2 path' })

Proxies the requested path to the Indexer v2 API. Is limited to endpoints made available by the API server. The API backend may be configured by advanced users and is not guaranteed to respond as expected. More information can be found here.

Request

AlgoSigner.indexer({
  ledger: 'TestNet',
  path: '/v2/assets/150821',
});

Response

{
  "asset": {
    "index": 150821,
    "params": {
      "clawback": "Q2SLSQTBMVJYVT2AANUAXY4A5G7A3Y6L2M6L3WIXKNYBTMMQFGUOQGKSRQ",
      "creator": "Q2SLSQTBMVJYVT2AANUAXY4A5G7A3Y6L2M6L3WIXKNYBTMMQFGUOQGKSRQ",
      "decimals": 15,
      "default-frozen": false,
      "freeze": "Q2SLSQTBMVJYVT2AANUAXY4A5G7A3Y6L2M6L3WIXKNYBTMMQFGUOQGKSRQ",
      "manager": "Q2SLSQTBMVJYVT2AANUAXY4A5G7A3Y6L2M6L3WIXKNYBTMMQFGUOQGKSRQ",
      "name": "decimal Test",
      "reserve": "Q2SLSQTBMVJYVT2AANUAXY4A5G7A3Y6L2M6L3WIXKNYBTMMQFGUOQGKSRQ",
      "total": 1000,
      "unit-name": "dectest"
    }
  },
  "current-round": 8271410
}

Working with Transactions

Sent in transactions will be validated against the Algorand JS SDK transaction types - field names must match, and the whole transaction will be rejected otherwise.

This section covers the new v2 Signing. Additional end-to-end examples will be included in the next weeks.

AlgoSigner.signTxn([txnObjects,...])

Send transaction objects, conforming to the Algorand JS SDK, to SignA for approval. The Network is determined from the 'genesis-id' property. If approved, the response is an array of signed transaction objects, with the binary blob field base64 encoded to prevent transmission issues.

Transaction Requirements

Transactions objects need to be presented with the following structure:

{
  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,
};

In order to facilitate conversion between different formats and encodings, helper functions are available on the AlgoSigner.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.

Request

AlgoSigner.signTxn([
  {
    txn: 'iqNhbXRko2ZlZc0D6KJmds4A259Go2dlbqx0ZXN0bmV0LXYxLjCiZ2jEIEhjtRiks8hOyBDyLU8QgcsPcfBZp6wg3sYvf3DlCToio2dycMQgdsLAGqgrtwqqQS4UEN7O8CZHjfhPTwLHrB1A2pXwvKGibHbOANujLqNyY3bEIK0TEDcptY0uFvk2V5LDVzRfdz7O4freYHEuZbpI+6hMo3NuZMQglmyhKUPeU2KALzt/Jcs0GQ55k2vsqZ4pGeNlzpnYLbukdHlwZaNwYXk=',
  },
]);

NOTE: Even though the method accepts an array of transactions, it requires atomic transactions that have a groupId and will error on non-atomic groups.

Response

[
  {
    "txID": "4F6GE5EBTBJ7DOTWKA3GK4JYARFDCVR5CYEXP6O27FUCE5SGFDYQ",
    "blob": "gqNzaWfEQL6mW/7ss2HKAqsuHN/7ePx11wKSAvFocw5QEDvzSvrvJdzWYvT7ua8Lc0SS0zOmUDDaHQC/pGJ0PNqnu7W3qQKjdHhuiaNhbXQGo2ZlZc4AA7U4omZ2zgB+OrujZ2VurHRlc3RuZXQtdjEuMKJnaMQgSGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiKibHbOAH4+o6NyY3bEIHhydylNDQQhpD9QdKWejLCMBgb5UYJTGCfDW3KgLsI+o3NuZMQgZM5ZNuFgR8pz2dHBgDlmHolfGgF96zX/X4x2bnAJ3aqkdHlwZaNwYXk="
  }
]

Example

await AlgoSigner.connect();

// 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 sdkTx = new algosdk.Transaction({
  to: 'RECEIVER_ADDRESS',
  from: 'SENDER_ADDRESS',
  amount: 100,
  ...suggestedParams,
});

// Get the binary and base64 encode it
let binaryTx = sdkTx.toByte();
let base64Tx = AlgoSigner.encoding.msgpackToBase64(binaryTx);

let signedTxs = await AlgoSigner.signTxn([
  {
    txn: base64Tx,
  },
]);

Alternatively, you can provide multiple arrays of transactions at once. Same rules regarding the contents of the groups apply.

Request

AlgoSigner.signTxn([
  [
    {
      txn: 'iqNhbXRko2ZlZc0D6KJmds4A259Go2dlbqx0ZXN0bmV0LXYxLjCiZ2jEIEhjtRiks8hOyBDyLU8QgcsPcfBZp6wg3sYvf3DlCToio2dycMQgdsLAGqgrtwqqQS4UEN7O8CZHjfhPTwLHrB1A2pXwvKGibHbOANujLqNyY3bEIK0TEDcptY0uFvk2V5LDVzRfdz7O4freYHEuZbpI+6hMo3NuZMQglmyhKUPeU2KALzt/Jcs0GQ55k2vsqZ4pGeNlzpnYLbukdHlwZaNwYXk=',
    },
  ],
  [
    {
      txn: 'iaRhZnJ6w6RmYWRkxCCWbKEpQ95TYoAvO38lyzQZDnmTa+ypnikZ42XOmdgtu6RmYWlkzgDITmiiZnbOAQNjIKNnZW6sdGVzdG5ldC12MS4womdoxCBIY7UYpLPITsgQ8i1PEIHLD3HwWaesIN7GL39w5Qk6IqJsds4BA2cIo3NuZMQglmyhKUPeU2KALzt/Jcs0GQ55k2vsqZ4pGeNlzpnYLbukdHlwZaRhZnJ6',
    },
  ],
]);

Response

[
  [
    {
      "txID": "4F6GE5EBTBJ7DOTWKA3GK4JYARFDCVR5CYEXP6O27FUCE5SGFDYQ",
      "blob": "gqNzaWfEQL6mW/7ss2HKAqsuHN/7ePx11wKSAvFocw5QEDvzSvrvJdzWYvT7ua8Lc0SS0zOmUDDaHQC/pGJ0PNqnu7W3qQKjdHhuiaNhbXQGo2ZlZc4AA7U4omZ2zgB+OrujZ2VurHRlc3RuZXQtdjEuMKJnaMQgSGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiKibHbOAH4+o6NyY3bEIHhydylNDQQhpD9QdKWejLCMBgb5UYJTGCfDW3KgLsI+o3NuZMQgZM5ZNuFgR8pz2dHBgDlmHolfGgF96zX/X4x2bnAJ3aqkdHlwZaNwYXk="
    }
  ],
  [
    {
      "txID": "QK4XYJYGN7CLER25SKT3DV4UWNI5DVYXRBJRZEUWYA523EU5ZB7A",
      "blob": "gqNzaWfEQC8ZIPYimAypJD2TmEQjuWxEEk8/gJbBegEHdtyKr6TuA78otKIEB9PYQimgMLGn87YOEB6GgRe5vjWRTuWGsAqjdHhuiaRhZnJ6w6RmYWRkxCCWbKEpQ95TYoAvO38lyzQZDnmTa+ypnikZ42XOmdgtu6RmYWlkzgDITmiiZnbOAQNjO6NnZW6sdGVzdG5ldC12MS4womdoxCBIY7UYpLPITsgQ8i1PEIHLD3HwWaesIN7GL39w5Qk6IqJsds4BA2cjo3NuZMQglmyhKUPeU2KALzt/Jcs0GQ55k2vsqZ4pGeNlzpnYLbukdHlwZaRhZnJ6"
    }
  ]
]

The signed transactions can then be sent using the SDK (example below) or using the AlgoSigner.send() method.

// Get the base64 encoded signed transaction and convert it to binary
let binarySignedTx = AlgoSigner.encoding.base64ToMsgpack(signedTxs[0].blob);

// Send the transaction through the SDK client
await client.sendRawTransaction(binarySignedTx).do();

Atomic Transactions

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 tx1 = new algosdk.Transaction({
  to: 'SECOND_ADDRESS',
  from: 'FIRST_ADDRESS',
  amount: 100,
  ...suggestedParams,
});
let tx2 = new algosdk.Transaction({
  to: 'FIRST_ADDRESS',
  from: 'SECOND_ADDRESS',
  amount: 100,
  ...suggestedParams,
});

// Assign a Group ID to the transactions using the SDK
algosdk.assignGroupID([tx1, tx2]);

let binaryTxs = [tx1.toByte(), tx2.toByte()];
let base64Txs = binaryTxs.map((binary) => AlgoSigner.encoding.msgpackToBase64(binary));

let signedTxs = await AlgoSigner.signTxn([
  {
    txn: base64Txs[0],
  },
  {
    txn: base64Txs[1],
  },
]);

The signed transaction array can then be sent using the SDK.

let binarySignedTxs = signedTxs.map((tx) => AlgoSigner.encoding.base64ToMsgpack(tx.blob));
await client.sendRawTransaction(binarySignedTxs).do();

Reference Atomic transactions

In case not all group transactions belong to accounts on SignA, you can set the signers field of the transaction object as an empty array to specify that it's only being sent to SignA for reference and group validation, not for signing. Reference transactions should look like this:

{
  txn: 'B64_TXN',
  signers: [],
  // This tells SignA that this transaction is not meant to be signed
}

AlgoSigner.signTxn() 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.

Providing Signed reference transaction(s)

You can provide a signed reference transaction to SignA 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, AlgoSigner.signTxn() will return the stxn string in the same position of the response array as the corresponding reference transaction(s) instead of null.

Example

let tx1 = new algosdk.Transaction({
  to: 'EXTERNAL_ACCOUNT',
  from: 'ACCOUNT_IN_ALGOSIGNER',
  amount: 100,
  ...suggestedParams,
});
let tx2 = new algosdk.Transaction({
  to: 'ACCOUNT_IN_ALGOSIGNER',
  from: 'EXTERNAL_ACCOUNT',
  amount: 100,
  ...suggestedParams,
});

algosdk.assignGroupID([tx1, tx2]);
let binaryTxs = [tx1.toByte(), tx2.toByte()];
let base64Txs = binaryTxs.map((binary) => AlgoSigner.encoding.msgpackToBase64(binary));

let signedTxs = await AlgoSigner.signTxn([
  {
    txn: base64Txs[0],
  },
  {
    txn: base64Txs[1],
    signers: [],
    stxn: 'MANUALLY_SIGNED_SECOND_TXN_B64',
  },
]);

Response

[
  {
    txID: '...',
    blob: 'ALGOSIGNER_SIGNED_B64',
  },
  {
    txID: '...',
    blob: 'MANUALLY_SIGNED_SECOND_TXN_B64',
  },
];

Signing reference transactions manually

In case you can't or don't want to provide the stxn, the provided transaction should look like this:

Example

let signedTxs = await AlgoSigner.signTxn([
  {
    txn: base64Txs[0],
  },
  {
    txn: base64Txs[1],
    signers: [],
  },
]);

Response

[
  {
    txID: '...',
    blob: '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 signedTx1Binary = AlgoSigner.encoding.base64ToMsgpack(signedTxs[0].blob);
// Sign leftover transaction with the SDK
let externalAccount = algosdk.mnemonicToSecretKey('EXTERNAL_ACCOUNT_MNEMONIC');
let signedTx2Binary = tx2.signTxn(externalAccount.sk);

await client.sendRawTransaction([signedTx1Binary, signedTx2Binary]).do();

Alternatively, if you're using the AlgoSigner.send() to send the transaction(s), you have to merge the binaries before converting to a single base64 encoded string.

// Merge transaction binaries into a single Uint8Array
let combinedBinaryTxns = new Uint8Array(signedTx1Binary.byteLength + signedTx2Binary.byteLength);
combinedBinaryTxns.set(signedTx1Binary, 0);
combinedBinaryTxns.set(signedTx2Binary, signedTx1Binary.byteLength);

// Convert the combined array values back to base64
let combinedBase64Txns = AlgoSigner.encoding.msgpackToBase64(combinedBinaryTxns);

await AlgoSigner.send({
  ledger: 'TestNet',
  tx: combinedBase64Txns,
});

Multisig Transactions

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[]
}

AlgoSigner.signTxn() 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: AlgoSigner.signTxn() 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 multisigTx = new algosdk.Transaction({
  to: 'RECEIVER_ADDRESS',
  from: multisigAddress,
  amount: 100,
  ...suggestedParams,
});

// Get the binary and base64 encode it
let binaryMultisigTx = multisigTx.toByte();
let base64MultisigTx = AlgoSigner.encoding.msgpackToBase64(binaryMultisigTx);

// This returns a partially signed Multisig Transaction with signatures for FIRST_ADDRESS and SECOND_ADDRESS
let signedTxs = await AlgoSigner.signTxn([
  {
    txn: base64MultisigTx,
    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 signedTxs = await AlgoSigner.signTxn([
  {
    txn: base64MultisigTx,
    msig: multisigParams,
    signers: ['SECOND_ADDRESS'],
  },
]);

AlgoSigner.send({ ledger: 'MainNet|TestNet', txBlob })

Send a base64 encoded signed transaction blob to SignA to transmit to the Network.

Request

AlgoSigner.send({
  ledger: 'TestNet',
  tx: signedTx.blob,
});

Response

{ "txId": "OKU6A2QYMRSZAUEJUZL3PW5XKLTA6TKWQHIIBXDCO3KT5OHCULBA" }

Custom Networks

Signature Rejection Messages

SignA may return some of the following error codes when requesting signatures:

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 SignA 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 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. SignA rejected some of the transactions due to invalid fields.

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.

Helper Functions

AlgoSigner.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.

  AlgoSigner.encoding.msgpackToBase64(): receives a binary object (as a Uint8Array) and returns the corresponding base64 encoded string,
  AlgoSigner.encoding.base64ToMsgpack(): receives a base64 encoded string and returns the corresponding binary object (as a Uint8Array),
  AlgoSigner.encoding.stringToByteArray(): receives a plain unencoded string and returns the corresponding binary object (as a Uint8Array),
  AlgoSigner.encoding.byteArrayToString(): receives a binary object (as a Uint8Array) and returns the corresponding plain unencoded string,