Work with Transaction
Transaction is one of the most important data structures on Alephium. We can observe some of Alephium's unique designs and features through the lens of transactions.
Alephium leverages the sUTXO model.
The transaction data structure contains inputs and outputs just as
other UTXO based blockchains. What's unique is that Alephium's
transaction output has a tokens
field which is able to store and
transfer tokens natively. Alephium's transaction data structure also
has an optional scriptOpt
field which contains the bytecode that can
be executed by the VM to
interact with the contracts.
An unsigned transaction consists of the following attributes:
version : Version of the transaction, allows for further evolution of the transaction data structure
networkId : Intended network for the transaction, e.g. `Mainnet`, `Testnet`, `Devnet`
scriptOpt : (Optional) script bytecode to be executed by the VM to interact with the contracts
gasAmount : Amount of gas that the transaction can consume
gasPrice : Price per unit of gas that the sender is willing to pay
inputs : References to the transaction outputs to be spent and the corresponding unlocking scripts
fixedOutputs : New transaction outputs created by the transaction
Other than the fixedOutputs
, if a transaction executes the script,
it could potentially generate more transaction outputs as well. Script
might also require contract inputs if it takes assets from the
contracts during its execution.
Before an unsigned transaction can be submitted to the network, it needs to be executed and signed. A signed transaction consists of the following attributes:
unsigned : Serialized version of the unsigned transaction
scriptExecutionOk : Whether the transaction script is executed successfully
contractInputs : Contract inputs required during the script execution
generatedOutputs : Transaction outputs generated during the script execution
inputSignatures : Signatures required for spending the inputs in the unsigned transaction
scriptSignatures : Signatures required during the script execution
In the following sections, we will learn how to build, sign, submit, query and decode transactions.
Build Unsigned Transaction
You can use TransactionBuilder
in Typescript
SDK to build unsigned transactions. Before you
start, it requires both your address and its corresponding public
key. If you have a private key, you can obtain the public key using
the following method:
import { publicKeyFromPrivateKey } from '@alephium/web3'
const publicKey = publicKeyFromPrivateKey('your-private-key')
You can also export the public key from your extension wallet or desktop wallet.
Transfer Transaction
In the following example, we build an unsigned transaction that transfers
ONE_ALPH
to the receiverAddress
:
import { TransactionBuilder } from '@alephium/web3'
const nodeUrl = 'http://127.0.0.1:22973'
const builder = TransactionBuilder.from(nodeUrl)
const senderAddress = '1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH'
const senderPublicKey = '0381818e63bd9e35a5489b52a430accefc608fd60aa2c7c0d1b393b5239aedf6b0'
const receiverAddress = '1FsroWmeJPBhcPiUr37pWXdojRBe6jdey9uukEXk1TheA'
const buildTxResult = await builder.buildTransferTx(
{
signerAddress: senderAddress,
destinations: [{
address: receiverAddress,
attoAlphAmount: ONE_ALPH
}]
},
senderPublicKey
)
console.log('unsigned transaction', buildTxResult.unsignedTx)
You can also use builtTransferTx
to build a token transfer transaction:
import { DUST_AMOUNT } from '@alephium/web3'
const buildTxResult = await builder.buildTransferTx(
{
signerAddress: senderAddress,
destinations: [{
address: receiverAddress,
attoAlphAmount: DUST_AMOUNT,
tokens: [{
id: '19246e8c2899bc258a1156e08466e3cdd3323da756d8a543c7fc911847b96f00',
amount: 1000000000000000000n
}]
}]
},
senderPublicKey
)
console.log('unsigned transaction', buildTxResult.unsignedTx)
Execute Script Transaction
Let's build an unsigned transaction to execute a transaction script.
Once your TxScript
is compiled, its
bytecode can be returned by calling the
buildByteCodeToDeploy
function. For instance, consider the following TxScript
:
TxScript LockAlph(amount: U256) {
let caller = callerAddress!()
lockApprovedAssets!{caller -> ALPH: amount}(caller, blockTimeStamp!() + 86400000)
}
You can obtain the bytecode of LockAlph
by:
const bytecode = LockAlph.script.buildByteCodeToDeploy({ amount: ONE_ALPH })
After obtaining the bytecode for your TxScript
, you can create an
unsigned transaction as follows:
const senderAddress = '1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH'
const senderPublicKey = '0381818e63bd9e35a5489b52a430accefc608fd60aa2c7c0d1b393b5239aedf6b0'
const bytecode = '0101030001000ab41700160013c40de0b6b3a7640000a21600561385265c002abe'
const buildTxResult = await builder.buildExecuteScriptTx(
{
signerAddress: account.address,
bytecode
},
senderPublicKey
)
console.log('unsigned transaction', buildTxResult.unsignedTx)
Deploy Contract Transaction
We can also build an unsigned transaction that deploys a contract. After your contract is
compiled, you
can obtain the bytecode to deploy it using the Contract.buildByteCodeToDeploy
function. The following example assumes the bytecode
is already created:
const senderAddress = '1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH'
const senderPublicKey = '0381818e63bd9e35a5489b52a430accefc608fd60aa2c7c0d1b393b5239aedf6b0'
const bytecode = '00010b010000000001d38d0b36360000'
const buildTxResult = await builder.buildDeployContractTx(
{
signerAddress: senderAddress,
bytecode
},
senderPublicKey
)
console.log('unsigned transaction', buildTxResult.unsignedTx)
Gas Estimation
On Alephium, each transaction involves two key parameters related to gas: gasAmount
and gasPrice
.
gasAmount
: This represents the amount of gas required for on-chain computation and storage.gasPrice
: This is the amount of ALPH (Alephium's native token) that the user is willing to pay per unit of gas consumed during transaction execution.
Specifying gasPrice
Users can set the gasPrice
as long as it meets the minimum required amount.
Estimating gasAmount
Estimating gasAmount
is an integral part of building the transaction. The full node can accurately estimate the gas required based on the current state of the blockchain. However, this estimation can sometimes fail if the contract state changes due to other pending transactions in the mempool.
Using gasEstimationMultiplier
(Optional)
To mitigate the potential estimation errors for dApp transactions, a parameter called gasEstimationMultiplier
can be used to scale up the estimated gas amount, providing a buffer to handle gas requirement changes. Note that the multiplier must be between 1.0 and 2.0.
Example
Here’s how you can use extract the estimated gasAmount
and optionally use gasEstimationMultiplier
in your code:
import { unsignedTxCodec } from '@alephium/web3/codec';
const buildTxResult = await builder.buildExecuteScriptTx(
{
signerAddress: account.address,
bytecode,
gasEstimationMultiplier: 1.05 // Scale up the estimated gas amount by 5%
},
senderPublicKey
);
const decodedTx = unsignedTxCodec.decodeApiUnsignedTx(hexToBinUnsafe(buildTxResult.unsignedTx));
console.log(decodedTx.gasAmount); // This is the scaled-up gas amount used in the transaction
By following this approach, you can ensure that your transactions are more robust against estimation errors, providing a smoother and more reliable user experience on the Alephium network.
Sign and Submit Transaction
We need the SignerProvider to sign and submit the unsigned transaction. The following example assumes that we have already created an unsigned transaction from the previous section:
import { getSigner } from '@alephium/web3-test'
const signer = await getSigner()
const signerAddress = (await signer.getSelectedAccount()).address
const txResult = await signer.signAndSubmitUnsignedTx({
signerAddress: signerAddress,
unsignedTx: buildTxResult.unsignedTx // assuming an unsigned transaction was already created
})
console.log('transaction id', txResult.txId)
Query Transaction
Transaction Status
import { NodeProvider } from '@alephium/web3'
const nodeUrl = 'http://127.0.0.1:12973'
const nodeProvider = new NodeProvider(nodeUrl)
const txId = '919d4e4b1080d74beb56a1f78ea7c0569a358e3ea3988058987cc1addf4b93cc'
const txStatus = await nodeProvider.transactions.getTransactionsStatus({ txId })
console.log('transaction status', txStatus.type)
You can differentiate the transaction status using the txStatus.type
:
MemPooled
: transaction is in the mempoolConfirmed
: transaction has been confirmed, and you can get the number of confirmations usingtxStatus.chainConfirmations
TxNotFound
: transaction does not exist
Transaction Details
const details = await nodeProvider.transactions.getTransactionsDetailsTxid(txId)
console.log('transaction details', details)
For transfer transactions, you can obtain the transaction details if the transaction is successful. For script tx, you need to check scriptExecutionOk
to determine if the transaction is executed successfully:
if (details.unsigned.scriptOpt !== undefined && !details.scriptExecutionOk) {
// the script transaction failed
}
Transaction details consists of the same attributes as a signed transaction.
Decode Unsigned Transaction
The Alephium full node also provides an API to decode unsigned transaction from its serialized format:
const nodeProvider = new NodeProvider('http://127.0.0.1:22973')
const decodedUnsignedTx = await nodeProvider.transactions.postTransactionsDecodeUnsignedTx({ unsignedTx })
console.log('decoded unsigned transaction ', decodedUnsignedTx)
You can also use the codec to decode unsigned transaction at the client side.