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 StdSignMsg, sign it, and add the signatures to an StdTx.

A StdSignMsg 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)

  • fee: the transaction fee paid to network / validators

  • msgs: list of messages to include

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

from terra_sdk.client.lcd import LCDClient
from terra_sdk.core.auth import StdSignMsg
from terra_sdk.core.bank import MsgSend
from terra_sdk.key.mnemonic import MnemonicKey

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

# create tx
unsigned_tx = StdSignMsg(
    chain_id="columbus-4",
    account_number=23982,
    sequence=12,
    fee=StdFee(200000, "120000uluna"),
    msgs=[MsgSend(
        mk.acc_address,
        RECIPIENT,
        "1000000uluna" # send 1 luna
    )],
    memo="test transaction!"
)

# get signature
sig = mk.create_signature(unsigned_tx)

# prepopulate stdtx with details
tx = unsigned_tx.to_stdtx()

# apply signature
tx.signature = [sig]

# broadcast tx
result = terra.tx.broadcast(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 StdSignMsg for each signer to sign individually, and then combine them in the signatures field of the final StdTx object. Each StdSignMsg 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.auth import StdFee
from terra_sdk.core.bank import MsgMultiSend
from terra_sdk.key.mnemonic import MnemonicKey

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

multisend = MsgMultiSend(
    inputs=[
        {"address": wallet1.key.acc_address, "coins": "12000uusd,11000uluna"},
        {"address": wallet2.key.acc_address, "coins": "11000ukrw,10000uluna"}
    ],
    outputs=[
        {"address": wallet1.key.acc_address, "coins": "11000ukrw,10000uluna"},
        {"address": wallet2.key.acc_address, "coins": "12000uusd,11000uluna"}
    ]
)

msgs = [multisend]
fee = StdFee(200000, "12000uluna")
memo = "multisend example"

# create unsigned_tx #1
u_tx1 = wallet1.create_tx(
    msgs=msgs,
    fee=fee,
    memo=memo
)

sig1 = wallet1.key.create_signature(u_tx1)

# create unsigned tx #2
u_tx2 = wallet2.create_tx(
    msgs=msgs,
    fee=fee,
    memo=memo
)

sig2 = wallet2.key.create_signature(u_tx2)

# build stdtx
tx = u_tx1.to_stdtx()

# apply signatures
tx.signatures = [sig1, sig2]

# broadcast tx
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(
    msgs=[MsgSend(...)],
    sequence=sequence
)

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

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