Building and Signing Transactions

If you want to perform a state-changing operation on the Terra blockchain such as sending tokens, swapping assets, withdrawing rewards, or even invoking functions on smart contracts, you must create a transaction and broadcast it to the network.

An StdTx is a data object that represents a transaction. It contains:

  • msgs: a list of state-altering messages

  • fee: the transaction fee paid to network / validators

  • signatures: a list of signatures from required signers (depends on messages)

  • memo: a short string describing transaction (can be empty string)

Terra SDK provides functions that help create StdTx objects.

Signing transactions manually

Below is the full process of signing a transaction manually that does not use Wallet. You will need to build a SignDoc, sign it, and add the signatures to an Tx.

A SignDoc contains the information required to build a StdTx:

  • chain_id: chain ID of blockchain network

  • account_number: account number in blockchain

  • sequence: sequence number (# of prior transactions)

  • auth_info: transaction authentication info

  • tx_body: body of a transaction. containing messages.

from terra_sdk.client.lcd.api.tx import CreateTxOptions, SignerOptions
from terra_sdk.client.lcd import LCDClient
from terra_sdk.core.bank import MsgSend
from terra_sdk.core.tx import SignMode
from terra_sdk.key.key import SignOptions
from terra_sdk.key.mnemonic import MnemonicKey
from terra_sdk.core import Coin, Coins

terra = LCDClient("https://lcd.terra.dev", "columbus-5")
key = MnemonicKey(mnemonic=MNEMONIC)

msg = MsgSend(
    key.acc_address,
    "terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v",
    Coins(uluna=30000),
)

tx_opt = CreateTxOptions(
    msgs=[msg], memo="send test", gas_adjustment=1.5
)

signer_opt = SignerOptions(
    address=key.acc_address,
)

acc_info = terra.auth.account_info(key.acc_address)

sign_opt = SignOptions(
    account_number=acc_info.account_number,
    sequence=acc_info.sequence,
    sign_mode=SignMode.SIGN_MODE_DIRECT,
    chain_id='columbus-5'
)

tx = terra.tx.create([signer_opt], tx_opt)

signed_tx = key.sign_tx(tx, sign_opt)

# broadcast tx
result = terra.tx.broadcast(signed_tx)
print(result)

Applying multiple signatures

Some messages, such as MsgMultiSend, require the transaction to be signed with multiple signatures. You must prepare a separate SignDoc for each signer to sign individually, and then combine them in the signatures field of the final StdTx object. Each SignDoc should only differ by account and sequence, which vary according to the signing key.

Note

In a transaction with multiple signers, the account of the first signature in the StdTx is responsible for paying the fee.

from terra_sdk.client.lcd import LCDClient
from terra_sdk.core.fee import Fee
from terra_sdk.core.bank import MsgMultiSend
from terra_sdk.key.mnemonic import MnemonicKey
from terra_sdk.core.bank import MsgMultiSend, MultiSendInput, MultiSendOutput

terra = LCDClient("https://lcd.terra.dev", "columbus-5")
wallet1 = terra.wallet(MnemonicKey(mnemonic=MNEMONIC_1))
wallet2 = terra.wallet(MnemonicKey(mnemonic=MNEMONIC_2))

inputs = [
    MultiSendInput(
        address=wallet1.key.acc_address,
        coins=Coins(uluna=10000),
    ),
    MultiSendInput(
        address=wallet2.key.acc_address,
        coins=Coins(uluna=20000),
    )
]
outputs = [
    MultiSendOutput(
        address=wallet1.key.acc_address,
        coins=Coins(uluna=20000),
    ),
    MultiSendOutput(
        address=wallet2.key.acc_address,
        coins=Coins(uluna=10000),
    ),
]

msg = MsgMultiSend(inputs, outputs)

opt = CreateTxOptions(
    msgs=[msg]
)

tx = terra.tx.create(
    [SignerOptions(address=wallet1.key.acc_address), SignerOptions(address=wallet2.key.acc_address)], opt)

info1 = wallet1.account_number_and_sequence()
info2 = wallet2.account_number_and_sequence()

signdoc1 = SignDoc(
    chain_id=terra.chain_id,
    account_number=info1["account_number"],
    sequence=info1["sequence"],
    auth_info=tx.auth_info,
    tx_body=tx.body,
)

signdoc2 = SignDoc(
    chain_id=terra.chain_id,
    account_number=info2["account_number"],
    sequence=info2["sequence"],
    auth_info=tx.auth_info,
    tx_body=tx.body,
)
sig1 = wallet1.key.create_signature_amino(signdoc1)
sig2 = wallet2.key.create_signature_amino(signdoc2)
tx.append_signatures([sig1, sig2])

result = terra.tx.broadcast(tx)
print(result)

Signing multiple offline transactions

In some cases, you may wish to sign and save multiple transactions in advance, in order to broadcast them at a later date. To do so, you will need to manually update the sequence number to override the Wallet’s automatic default behavior of loading the latest sequence number from the blockchain (which will not have been updated).

# get first sequence
sequence = wallet.sequence()
tx1 = wallet.create_and_sign_tx(
    CreateTxOptions(
        msgs=[MsgSend(...)],
        sequence=sequence
    )
)

tx2 = wallet.create_and_sign_tx(
    CreateTxOptions(
        msgs=[MsgSwap(...)],
        sequence=sequence+1
    )
)

tx3 = wallet.create_and_sign_tx(
    CreateTxOptions(
        msgs=[MsgExecuteContract(...)],
        sequence=sequence+2
    )
)