ARCs:specs:arc-0001.md•29.7 kB
---
arc: 1
title: Algorand Wallet Transaction Signing API
description: An API for a function used to sign a list of transactions.
author: Fabrice Benhamouda (@fabrice102)
discussions-to: https://github.com/algorandfoundation/ARCs/issues/52
status: Final
type: Standards Track
category: Interface
sub-category: Wallet
created: 2021-07-06
---
# Algorand Wallet Transaction Signing API
## Abstract
The goal of this API is to propose a standard way for a dApp to request the signature of a list of transactions to an Algorand wallet. This document also includes detailed security requirements to reduce the risks of users being tricked to sign dangerous transactions. As the Algorand blockchain adds new features, these requirements may change.
## Specification
The key words "**MUST**", "**MUST NOT**", "**REQUIRED**", "**SHALL**", "**SHALL NOT**", "**SHOULD**", "**SHOULD NOT**", "**RECOMMENDED**", "**MAY**", and "**OPTIONAL**" in this document are to be interpreted as described in <a href="https://www.ietf.org/rfc/rfc2119.txt">RFC-2119</a>.
> Comments like this are non-normative.
### Overview
> This overview section is non-normative.
After this overview, the syntax of the interfaces are described followed by the semantics and the security requirements.
At a high-level the API allows to sign:
* A valid group of transaction (aka atomic transfers).
* (**OPTIONAL**) A list of groups of transactions.
Signatures are requested by calling a function `signTxns(txns)` on a list `txns` of transactions. The dApp may also provide an optional parameter `opts`.
Each transaction is represented by a `WalletTransaction` object. The only required field of a `WalletTransaction` is `txn`, a base64 encoding of the canonical msgpack encoding of the unsigned transaction. There are three main use cases:
1. The transaction needs to be signed and the sender of the transaction is an account known by the wallet. This is the most common case. Example:
```json
{
"txn": "iaNhbXT..."
}
```
The wallet is free to generate the resulting signed transaction in any way it wants. In particular, the signature may be a multisig, may involve rekeying, or for very advanced wallets may use logicsigs.
> Remark: If the wallet uses a large logicsig to sign the transaction and there is congestion, the fee estimated by the dApp may be too low. A future standard may provide a wallet API allowing the dApp to compute correctly the estimated fee. Before such a standard, the dApp may need to retry with a higher fee when this issue arises.
2. The transaction does not need to be signed. This happens when the transaction is part of a group of transaction and is signed by another party or by a logicsig. In that case, the field `signers` is set to an empty array. Example:
```json
{
"txn": "iaNhbXT...",
"signers": []
}
```
3. (**OPTIONAL**) The transaction needs to be signed but the sender of the transaction is *not* an account known by the wallet. This happens when the dApp uses a sender account derived from one or more accounts of the wallet. For example, the sender account may be a multisig account with public keys corresponding to some accounts of the wallet, or the sender account may be rekeyed to an account of the wallet. Example:
```json
{
"txn": "iaNhbXT...",
"authAddr": "HOLQV2G65F6PFM36MEUKZVHK3XM7UEIFLG35UJGND77YDXHKXHKX4UXUQU",
"msig": {
"version": 1,
"threshold": 2,
"addrs": [
"5MF575NQUDMRWOTS27KIBL2MFPJHKQEEF4LZEN6H3CZDAYVUKESMGZPK3Q",
"FS7G3AHTDVMQNQQBHZYMGNWAX7NV2XAQSACQH3QDBDOW66DYTAQQW76RYA",
"DRSHY5ONWKVMWWASTB7HOELVF5HRUTRQGK53ZK3YNMESZJR6BBLMNH4BBY"
]
},
"signers": ...
}
```
Note that in both the first and the third use cases, the wallet may sign the transaction using a multisig and may use a different authorized address (`authAddr`) than the sender address (i.e., rekeying). The main difference is that in the first case, the wallet knows how to sign the transaction (i.e., whether the sender address is a multisig and/or rekeyed), while in the third case, the wallet may not know it.
### Syntax and Interfaces
> Interfaces are defined in TypeScript. All the objects that are defined are valid JSON objects.
#### Interface `SignTxnsFunction`
A wallet transaction signing function `signTxns` is defined by the following interface:
```typescript
export type SignTxnsFunction = (
txns: WalletTransaction[],
opts?: SignTxnsOpts
)
=> Promise<(SignedTxnStr | null)[]>;
```
where:
* `txns` is a non-empty list of `WalletTransaction` objects (defined below).
* `opts` is an optional parameter object `SignTxnsOpts` (defined below).
In case of error, the wallet (i.e., the `signTxns` function in this document) **MUST** reject the promise with an error object `SignTxnsError` defined below.
This ARC uses interchangeably the terms "throw an error" and "reject a promise with an error".
#### Interface `AlgorandAddress`
An Algorand address is represented by a 58-character base32 string. It includes the checksum.
```typescript
export type AlgorandAddress = string;
```
An Algorand address is *valid* is it is a valid base32 string without padding and if the checksum is valid.
> Example: `"6BJ32SU3ABLWSBND7U5H2QICQ6GGXVD7AXSSMRYM2GO3RRNHCZIUT4ISAQ"` is a valid Algorand address.
#### Interface `SignedTxnStr`
`SignedTxnStr` is the base64 encoding of the canonical msgpack encoding of a `SignedTxn` object, as defined in the <a href="https://github.com/algorandfoundation/specs">Algorand specs<a/>. For Algorand version 2.5.5, see the <a href="https://github.com/algorandfoundation/specs/blob/d050b3cade6d5c664df8bd729bf219f179812595/dev/ledger.md#authorization-and-signatures">authorization and signatures Section</a> of the specs or the <a href="https://github.com/algorand/go-algorand/blob/304815d00b9512cf9f91dbb987fead35894676f4/data/transactions/signedtxn.go#L31">Go structure</a>
```typescript
export type SignedTxnStr = string;
```
#### Interface `MultisigMetadata`
A `MultisigMetadata` object specifies the parameters of an Algorand multisig address.
```typescript
export interface MultisigMetadata {
/**
* Multisig version.
*/
version: number;
/**
* Multisig threshold value. Authorization requires a subset of signatures,
* equal to or greater than the threshold value.
*/
threshold: number;
/**
* List of Algorand addresses of possible signers for this
* multisig. Order is important.
*/
addrs: AlgorandAddress[];
}
```
* `version` should always be 1.
* `threshold` should be between 1 and the length of `addrs`.
> Interface originally from github.com/algorand/js-algorand-sdk/blob/e07d99a2b6bd91c4c19704f107cfca398aeb9619/src/types/multisig.ts, where `string` has been replaced by `AlgorandAddress`.
#### Interface `WalletTransaction`
A `WalletTransaction` object represents a transaction to be signed by a wallet.
```typescript
export interface WalletTransaction {
/**
* Base64 encoding of the canonical msgpack encoding of a Transaction.
*/
txn: string;
/**
* Optional authorized address used to sign the transaction when the account
* is rekeyed. Also called the signor/sgnr.
*/
authAddr?: AlgorandAddress;
/**
* Multisig metadata used to sign the transaction
*/
msig?: MultisigMetadata;
/**
* Optional list of addresses that must sign the transactions
*/
signers?: AlgorandAddress[];
/**
* Optional base64 encoding of the canonical msgpack encoding of a
* SignedTxn corresponding to txn, when signers=[]
*/
stxn?: SignedTxnStr;
/**
* Optional message explaining the reason of the transaction
*/
message?: string;
/**
* Optional message explaining the reason of this group of transaction
* Field only allowed in the first transaction of a group
*/
groupMessage?: string;
}
```
#### Interface `SignTxnsOpts`
A `SignTxnsOps` specifies optional parameters of the `signTxns` function:
```typescript
export type SignTxnsOpts = {
/**
* Optional message explaining the reason of the group of transactions
*/
message?: string;
}
```
#### Error Interface `SignTxnsError`
In case of error, the `signTxns` function **MUST** return a `SignTxnsError` object
```typescript
interface SignTxnsError extends Error {
code: number;
data?: any;
}
```
where:
* `message`:
- **MUST** be a human-readable string
- **SHOULD** adhere to the specifications in the Error Standards section below
* `code`:
- **MUST** be an integer number
- **MUST** adhere to the specifications in the Error Standards section below
* `data`:
- **SHOULD** contain any other useful information about the error
> Inspired from github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md
### Error Standards
| Status Code | Name | Description |
| ----------- | ---- | ----------- |
| 4001 | User Rejected Request | The user rejected the request. |
| 4100 | Unauthorized | The requested operation and/or account has not been authorized by the user. |
| 4200 | Unsupported Operation | The wallet does not support the requested operation. |
| 4201 | Too Many Transactions | The wallet does not support signing that many transactions at a time. |
| 4202 | Uninitialized Wallet | The wallet was not initialized properly beforehand. |
| 4300 | Invalid Input | The input provided is invalid. |
### Wallet-specific extensions
Wallets **MAY** use specific extension fields in `WalletTransaction` and in `SignTxnsOpts`. These fields must start with: `_walletName`, where `walletName` is the name of the wallet. Wallet designers **SHOULD** ensure that their wallet name is not already used.
> Example of a wallet-specific fields in `opts` (for the wallet `theBestAlgorandWallet`): `_theBestAlgorandWalletIcon` for displaying an icon related to the transactions.
Wallet-specific extensions **MUST** be designed such that a wallet not understanding them would not provide a lower security level.
> Example of a forbidden wallet-specific field in `WalletTransaction`: `_theWorstAlgorandWalletDisable` requires this transaction not to be signed. This is dangerous for security as any signed transaction may leak and be committed by an attacker. Therefore, the dApp should never submit transactions that should not be signed, and that some wallets (not supporting this extension) may still sign.
### Semantic and Security Requirements
The call `signTxns(txns, opts)` **MUST** either throws an error or return an array `ret` of the same length of the `txns` array:
1. If `txns[i].signers` is an empty array, the wallet **MUST NOT** sign the transaction `txns[i]`, and:
* if `txns[i].stxn` is not present, `ret[i]` **MUST** be set to `null`.
* if `txns[i].stxn` is present and is a valid `SignedTxnStr` with the underlying transaction exactly matching `txns[i].txn`, `ret[i]` **MUST** be set to `txns[i].stxn`. (See section on the semantic of `WalletTransaction` for the exact requirements on `txns[i].stxn`.)
* otherwise, the wallet **MUST** throw a `4300` error.
2. Otherwise, the wallet **MUST** sign the transaction `txns[i].txn` and `ret[i]` **MUST** be set to the corresponding `SignedTxnStr`.
Note that if any transaction `txns[i]` that should be signed (i.e., where `txns[i].signers` is not an empty array) cannot be signed for any reason, the wallet **MUST** throw an error.
#### Terminology: Validation, Warnings, Fields
All the field names below are the ones in the <a href="https://github.com/algorand/go-algorand/blob/304815d00b9512cf9f91dbb987fead35894676f4/data/transactions/signedtxn.go#L31" >Go `SignedTxn` structure</a> and <a href="https://github.com/algorand/go-algorand/blob/304815d00b9512cf9f91dbb987fead35894676f4/data/transactions/transaction.go#L81"></a>. Field of the actual transaction are prefixed with `txn.` (as opposed to fields of the `WalletTransaction` such as `signers`). For example, the sender of a transaction is `txn.Sender`.
**Rejecting** means throwing a `4300` error.
Strong warning / warning / weak warning / informational messages are different level of alerts. Strong warnings **MUST** be displayed in such a way that the user cannot miss the importance of them.
#### Semantic of `WalletTransaction`
* `txn`:
* Must a base64 encoding of the canonical msgpack encoding of a `Transaction` object as defined in the <a href="https://github.com/algorandfoundation/specs">Algorand specs</a>. For Algorand version 2.5.5, see the <a href="https://github.com/algorandfoundation/specs/blob/d050b3cade6d5c664df8bd729bf219f179812595/dev/ledger.md#authorization-and-signatures"> authorization and signatures Section</a> of the specs or the <a href="https://github.com/algorand/go-algorand/blob/304815d00b9512cf9f91dbb987fead35894676f4/data/transactions/transaction.go#L81" >Go structure</a>.
* If `txn` is not a base64 string or cannot be decoded into a `Transaction` object, the wallet **MUST** reject.
* `authAddr`:
* The wallet **MAY** not support this field. In that case, it **MUST** throw a `4200` error.
* If specified, it must be a valid Algorand address. If this is not the case, the wallet **MUST** reject.
* If specified and supported, the wallet **MUST** sign the transaction using this authorized address *even if it sees the sender address `txn.Sender` was not rekeyed to `authAddr`*. This is because the sender may be rekeyed before the transaction is committed. The wallet **SHOULD** display an informational message.
* `msig`:
* The wallet **MAY** not support this field. In that case, it **MUST** throw a `4200` error.
* If specified, it must be a valid `MultisigMetadata` object. If this is not the case, the wallet **MUST** reject.
* If specified and supported, the wallet **MUST** verify `msig` matches `authAddr` (if `authAddr` is specified and supported) or the sender address `txn.Sender` (otherwise). The wallet **MUST** reject if this is not the case.
* If specified and supported and if `signers` is not specified, the wallet **MUST** return a `SignedTxn` with all the subsigs that it can provide and that the wallet user agrees to provide. If the wallet can sign more subsigs than the requested threshold (`msig.threshold`), it **MAY** only provide `msig.threshold` subsigs. It is also possible that the wallet cannot provide at least `msig.threshold` subsigs (either because the user prevented signing with some keys or because the wallet does not know enough keys). In that case, the wallet just provide the subsigs it can provide. However, the wallet **MUST** provide at least one subsig or throw an error.
* `signers`:
* If specified and if not a list of valid Algorand addresses, the wallet **MUST** reject.
* If `signers` is an empty array, the transaction is for information purpose only and the wallet **SHALL NOT** sign it, even if it can (e.g., know the secret key of the sender address).
* If `signers` is an array with more than 1 Algorand addresses:
* The wallet **MUST** reject if `msig` is not specified.
* The wallet **MUST** reject if `signers` is not a subset of `msig.addrs`.
* The wallet **MUST** try to return a `SignedTxn` with all the subsigs corresponding to `signers` signed. If it cannot, it **SHOULD** throw a `4001` error. Note that this is different than when `signers` is not provided, where the signing is only "best effort".
* If `signers` is an array with a single Algorand address:
* If `msig` is specified, the rules as when `signers` is an array with more than 1 Algorand addresses apply.
* If `authAddr` is specified but `msig` is not, the wallet **MUST** reject if `signers[0]` is not equal to `authAddr`.
* If neither `authAddr` nor `msig` are specified, the wallet **MUST** reject if `signers[0]` is not the sender address `txn.Sender`.
* In all cases, the wallet **MUST** only try to provide signatures for `signers[0]`. In particular, if the sender address `txn.Sender` was rekeyed or is a multisig and if `authAddr` and `msig` are not specified, then the wallet **MUST** reject.
* `stxn` if specified:
* If specified and if `signers` is not the empty array, the wallet **MUST** reject.
* If specified:
* It must be a valid `SignedTxnStr`. The wallet **MUST** reject if this is not the case.
* The wallet **MUST** reject if the field `txn` inside the `SignedTxn` object does not match exactly the `Transaction` object in `txn`.
* The wallet **MAY NOT** check whether the other fields of the `SignedTxn` are valid. In particular, it **MAY** accept `stxn` even in the following cases: it contains an invalid signature `sig`, it contains both a signature `sig` and a logicsig `lsig`, it contains a logicsig `lsig` that always reject.
* `message`:
* The wallet **MAY** decide to never print the message, to only print the first characters, or to make any changes to the messages that may be used to ensure a higher level of security. The wallet **MUST** be designed to ensure that the message cannot be easily used to trick the user to do an incorrect action. In particular, if displayed, the message must appear in an area that is easily and clearly identifiable as not trusted by the wallet.
* The wallet **MUST** prevent HTML/JS injection and must only display plaintext messages.
* `groupMessage` obeys the same rules as `message`, except it is a message common to all the transactions of the group containing the current transaction. In addition, the wallet **MUST** reject if `groupMessage` is provided for a transaction that is not the first transaction of the group. Note that `txns` may contain multiple groups of transactions, one after the other (see the Group Validation section for details).
##### Particular Case without `signers`, nor `msig`, nor `senders`
When neither `signers`, nor `msig`, nor `authAddr` are specified, the wallet **MAY** still sign the transaction using a multisig or a different authorized address than the sender address `txn.Sender`. It may also sign the transaction using a logicsig.
However, in all these cases, the resulting `SignedTxn` **MUST** be such that it can be committed to the blockchain (assuming the transaction itself can be executed and that the account is not rekeyed in the meantime).
In particular, if a multisig is used, the numbers of subsigs provided must be at least equal to the multisig threshold. This is different from the case where `msig` is provided, where the wallet **MAY** provide fewer subsigs than the threshold.
#### Semantic of `SignTxnsOpts`
* `message` obeys the rules as `WalletTransaction.message` except it is a message common to all transactions.
#### General Validation
The goal is to ensure the highest level of security for the end-user, even when the transaction is generated by a malicious dApp. Every input must be validated.
Validation:
* **SHALL NOT** rely on TypeScript typing as this can be bypassed. Types **MUST** be manually verified.
* **SHALL NOT** assume the Algorand SDK does any validation, as the Algorand SDK is not meant to receive maliciously generated inputs. Furthermore, the SDK allows for dangerous transactions (such as rekeying). The only exception for the above rule is for de-serialization of transactions. Once de-serialized, every field of the transaction must be manually validated.
> Note: We will be working with the algosdk team to provide helper functions for validation in some cases and to ensure the security of the de-serialization of potentially malicious transactions.
If there is any unexpected field at any level (both in the transaction itself or in the object WalletTransaction), the wallet **MUST** immediately reject. The only exception is for the "wallet-specific extension" fields (see above).
#### Group Validation
The wallet should support the following two use cases:
1. (**REQUIRED**) `txns` is a non-empty array of transactions that belong to the same group of transactions. In other words, either `txns` is an array of a single transaction with a zero group ID (`txn.Group`), or `txns` is an array of one or more transactions with the *same* non-zero group ID. The wallet **MUST** reject if the transactions do not match their group ID. (The dApp must provide the transactions in the order defined by the group ID.)
>An early draft of this ARC required that the size of a group of transactions must be greater than 1 but, since the Algorand protocol supports groups of size 1, this requirement had been changed so dApps don't have to have special cases for single transactions and can always send a group to the wallet.
2. (**OPTIONAL**) `txns` is a concatenation of `txns` arrays of transactions of type 1:
* All transactions with the *same* non-zero group ID must be consecutive and must match their group ID. The wallet **MUST** reject if the above is not satisfied.
* The wallet UI **MUST** be designed so that it is clear to the user when transactions are grouped (aka form an atomic transfers) and when they are not. It **SHOULD** provide very clear explanations that are understandable by beginner users, so that they cannot easily be tricked to sign what they believe is an atomic exchange while it is in actuality a one-sided payment.
If `txns` does not match any of the formats above, the wallet **MUST** reject.
The wallet **MAY** choose to restrict the maximum size of the array `txns`. The maximum size allowed by a wallet **MUST** be at least the maximum size of a group of transactions in the current Algorand protocol on MainNet. (When this ARC was published, this maximum size was 16.) If the wallet rejects `txns` because of its size, it **MUST** throw a 4201 error.
An early draft of this API allowed to sign single transactions in a group without providing the other transactions in the group. For security reasons, this use case is now deprecated and **SHALL** not be allowed in new implementations. Existing implementations may continue allowing for single transactions to be signed if a very clear warning is displayed to the user. The warning **MUST** stress that signing the transaction may incur losses that are much higher than the amount of tokens indicated in the transaction. That is because potential future features of Algorand may later have such consequences (e.g., a signature of a transaction may actually authorize the full group under some circumstances).
#### Transaction Validation
##### Inputs that Must Be Systematically Rejected
* Transactions `WalletTransaction.txn` with fields that are not known by the wallet **MUST** be systematically rejected. In particular:
* Every field **MUST** be validated.
* Any extra field **MUST** systematically make the wallet reject.
* This is to prevent any security issue in case of the introduction of new dangerous fields (such as `txn.RekeyTo` or `txn.CloseRemainderTo`).
* Transactions of an unknown type (field `txn.Type`) **MUST** be rejected.
* Transactions containing fields of a different transaction type (e.g., `txn.Receiver` in an asset transfer transaction) **MUST** be rejected.
##### Inputs that Warrant Display of Warnings
The wallet **MUST**:
* Display a strong warning message when signing a transaction with one of the following fields: `txn.RekeyTo`, `txn.CloseRemainderTo`, `txn.AssetCloseTo`. The warning message **MUST** clearly explain the risks. No warning message is necessary for transactions that are provided for informational purposes in a group and are not signed (i.e., transactions with `signers=[]`).
* Display a strong warning message in case the transaction is signed in the future (first valid round is after current round plus some number, e.g. 500). This is to prevent surprises in the future where a user forgot that they signed a transaction and the dApp maliciously play it later.
* Display a warning message when the fee is too high. The threshold **MAY** depend on the load of the Algorand network.
* Display a weak warning message when signing a transaction that can increase the minimum balance in a way that may be hard or impossible to undo (asset creation or application creation)
* Display an informational message when signing a transaction that can increase the minimum balance in a way that can be undone (opt-in to asset or transaction)
The above is for version 2.5.6 of the Algorand software. Future consensus versions may require additional checks.
Before supporting any new transaction field or type (for a new version of the Algorand blockchain), the wallet authors **MUST** be perform a careful security analysis.
#### Genesis Validation
The wallet **MUST** check that the genesis hash (field `txn.GenesisHash`) and the genesis ID (field `txn.GenesisID`, if provided) match the network used by the wallet. If the wallet supports multiple networks, it **MUST** make clear to the user which network is used.
#### UI
In general, the UI **MUST** ensure that the user cannot be confused by the dApp to perform dangerous operations. In particular, the wallet **MUST** make clear to the user what is part of the wallet UI from what is part of what the dApp provided.
Special care **MUST** be taken of when:
* Displaying the `message` field of `WalletTransaction` and of `SignTxnsOpts`.
* Displaying any arbitrary field of transactions including note field (`txn.Note`), genesis ID (`txn.genesisID`), asset configuration fields (`txn.AssetName`, `txn.UnitName`, `txn.URL`, ...)
* Displaying message hidden in fields that are expected to be base32/base64-strings or addresses. Using a different font for those fields **MAY** be an option to prevent such confusion.
Usual precautions **MUST** be taken regarding the fact that the inputs are provided by an untrusted dApp (e.g., preventing code injection and so on).
## Rationale
The API was designed to:
* Be easily implementable by all Algorand wallets
* Rely on the official <a href="https://github.com/algorandfoundation/specs/blob/master/dev/ledger.md" >specs</a> and the <a href="https://github.com/algorand/go-algorand/blob/304815d00b9512cf9f91dbb987fead35894676f4/data/transactions/signedtxn.go#L31">official source code</a>.
* Only use types supported by JSON to simplify interoperability (avoid Uint8Array for example) and to allow easy serialization / deserialization
* Be easy to extend to support future features of Algorand
* Be secure by design: making it hard for malicious dApps to cause the wallet to sign a transaction without the user understanding the implications of their signature.
The API was not designed to:
* Directly support of the SDK objects. SDK objects must first be serialized.
* Support any listing accounts, connecting to the wallet, sending transactions, ...
* Support of signing logic signatures.
The last two items are expected to be defined in other documents.
### Rationale for Group Validation
The requirements around group validation have been designed to prevent the following attack.
The dApp pretends to buy 1 Algo for 10 USDC, but instead creates an atomic transfer with the user sending 1 Algo to the dApp and the dApp sending 0.01 USDC to the user. However, it sends to the wallet a 1 Algo and 10 USDC transactions.
If the wallet does not verify that this is a valid group, it will make the user believe that they are signing for the correct atomic transfer.
## Reference Implementation
> This section is non-normative.
### Sign a Group of Two Transactions
Here is an example in node.js how to use the wallet interface to sign a group of two transactions and send them to the network. The function `signTxns` is assumed to be a method of `algorandWallet`.
> Note: We will be working with the algosdk development to add two helper functions to facilitate the use of the wallet. Current idea is to add:
`Transaction.toBase64` that does the same as `Transaction.toByte` except it outputs a base64 string
`Algodv2.sendBase64RawTransactions` that does the same as `Algodv2.sendRawTransactions` except it takes an array of base64 string instead of an array of Uint8array
```typescript
import algosdk from 'algosdk';
import * as algorandWallet from './wallet';
import {Buffer} from "buffer";
const firstRound = 13809129;
const suggestedParams = {
flatFee: false,
fee: 0,
firstRound: firstRound,
lastRound: firstRound + 1000,
genesisID: 'testnet-v1.0',
genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI='
};
const txn1 = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
from: "37MSZIPXHGNCKTDJTJDSYIOF4C57JAL2FTKESD2HBVELXYHEIXVZ4JVGFU",
to: "PKSE2TARC645D4O2IO6QNWVW6PLJDTR6IOKNKMGSHQL7JIJHNGNFVISUHI",
amount: 1000,
suggestedParams,
});
const txn2 = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
from: "37MSZIPXHGNCKTDJTJDSYIOF4C57JAL2FTKESD2HBVELXYHEIXVZ4JVGFU",
to: "PKSE2TARC645D4O2IO6QNWVW6PLJDTR6IOKNKMGSHQL7JIJHNGNFVISUHI",
amount: 2000,
suggestedParams,
});
const txs = [txn1, txn2];
algosdk.assignGroupID(txs);
const txn1B64 = Buffer.from(txn1.toByte()).toString("base64");
const txn2B64 = Buffer.from(txn2.toByte()).toString("base64");
(async () => {
const signedTxs = await algorandWallet.signTxns([
{txn: txn1B64},
{txn: txn2B64, signers: []}
]);
const algodClient = new algosdk.Algodv2("", "...", "");
algodClient.sendRawTransaction(
signedTxs.map(stxB64 => Buffer.from(stxB64, "base64"))
)
})();
```
## Security Considerations
None.
## Copyright
Copyright and related rights waived via <a href="https://creativecommons.org/publicdomain/zero/1.0/">CCO</a>.