Packages
stellar_sdk
0.17.0
0.23.0
0.22.0
0.21.2
0.21.1
0.21.0
0.20.0
0.19.0
0.18.1
0.18.0
0.17.1
0.17.0
0.16.1
0.16.0
0.15.1
0.15.0
0.14.0
0.13.1
0.13.0
0.12.0
0.11.8
0.11.7
0.11.6
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.2
0.10.1
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.1
0.8.0
0.7.1
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
Elixir SDK for the Stellar network.
Current section
Files
Jump to
Current section
Files
lib/tx_build/default.ex
defmodule Stellar.TxBuild.Default do
@moduledoc """
Default TxBuild implementation.
"""
alias StellarBase.XDR.{TransactionExt, SorobanTransactionData}
alias Stellar.TxBuild
alias Stellar.TxBuild.{
Account,
Memo,
BaseFee,
Operation,
Operations,
SequenceNumber,
Signature,
Preconditions,
TimeBounds,
Transaction,
TransactionEnvelope,
TransactionSignature
}
@behaviour Stellar.TxBuild.Spec
@preconditions_keys [
:time_bounds,
:ledger_bounds,
:min_seq_num,
:min_seq_age,
:min_seq_ledger_gap,
:extra_signers
]
@impl true
def new(%Account{} = source_account, opts) do
sequence_number = Keyword.get(opts, :sequence_number, SequenceNumber.new())
base_fee = Keyword.get(opts, :base_fee, BaseFee.new())
memo = Keyword.get(opts, :memo, Memo.new())
operations = Keyword.get(opts, :operations, Operations.new())
preconditions =
opts
|> Keyword.take(@preconditions_keys)
|> Preconditions.new()
case Transaction.new(
source_account: source_account,
sequence_number: sequence_number,
base_fee: base_fee,
preconditions: preconditions,
memo: memo,
operations: operations
) do
%Transaction{} = transaction ->
{:ok, %TxBuild{tx: transaction, signatures: [], tx_envelope: nil}}
error ->
error
end
end
def new(_source_account, _opts), do: {:error, :invalid_source_account}
@impl true
def add_memo({:ok, %TxBuild{tx: tx} = tx_build}, %Memo{} = memo) do
transaction = %{tx | memo: memo}
{:ok, %{tx_build | tx: transaction}}
end
def add_memo({:ok, %TxBuild{}}, _memo), do: {:error, :invalid_memo}
def add_memo(error, _memo), do: error
@impl true
def set_time_bounds(
{:ok, %TxBuild{tx: %{preconditions: %{type: :none} = preconditions} = tx} = tx_build},
%TimeBounds{} = time_bounds
) do
preconditions = %{preconditions | type: :precond_time, preconditions: time_bounds}
transaction = %{tx | preconditions: preconditions}
{:ok, %{tx_build | tx: transaction}}
end
def set_time_bounds(
{:ok,
%TxBuild{tx: %{preconditions: %{type: :precond_time} = preconditions} = tx} = tx_build},
%TimeBounds{} = time_bounds
) do
preconditions = %{preconditions | preconditions: time_bounds}
transaction = %{tx | preconditions: preconditions}
{:ok, %{tx_build | tx: transaction}}
end
def set_time_bounds(
{:ok,
%TxBuild{
tx:
%{
preconditions:
%{type: :precond_v2, preconditions: inner_preconditions} = preconditions
} = tx
} = tx_build},
%TimeBounds{} = time_bounds
) do
inner_preconditions = Keyword.put(inner_preconditions, :time_bounds, time_bounds)
preconditions = %{preconditions | preconditions: inner_preconditions}
transaction = %{tx | preconditions: preconditions}
{:ok, %{tx_build | tx: transaction}}
end
def set_time_bounds({:ok, %TxBuild{}}, _time_bounds), do: {:error, :invalid_time_bounds}
def set_time_bounds(error, _time_bounds), do: error
@impl true
def set_preconditions({:ok, %TxBuild{tx: tx} = tx_build}, %Preconditions{} = preconditions) do
transaction = %{tx | preconditions: preconditions}
{:ok, %{tx_build | tx: transaction}}
end
def set_preconditions({:ok, %TxBuild{}}, _preconditions), do: {:error, :invalid_preconditions}
def set_preconditions(error, _preconditions), do: error
@impl true
def set_base_fee({:ok, %TxBuild{tx: tx} = tx_build}, %BaseFee{} = base_fee) do
%Transaction{operations: %Operations{count: ops_count}} = tx
transaction = %{tx | base_fee: BaseFee.increment(base_fee, ops_count)}
{:ok, %{tx_build | tx: transaction}}
end
def set_base_fee({:ok, %TxBuild{}}, _base_fee), do: {:error, :invalid_base_fee}
def set_base_fee(error, _base_fee), do: error
@impl true
def set_sequence_number({:ok, %TxBuild{tx: tx} = tx_build}, %SequenceNumber{} = seq_num) do
transaction = %{tx | sequence_number: seq_num}
{:ok, %{tx_build | tx: transaction}}
end
def set_sequence_number({:ok, %TxBuild{}}, _seq_num), do: {:error, :invalid_sequence_number}
def set_sequence_number(error, _seq_num), do: error
@impl true
def add_operations({:ok, %TxBuild{}} = tx_build, []), do: tx_build
def add_operations({:ok, %TxBuild{}} = tx_build, [operation | operations]) do
tx_build
|> add_operation(operation)
|> add_operations(operations)
end
def add_operations({:ok, %TxBuild{}}, _operations), do: {:error, :invalid_operation}
def add_operations(error, _operations), do: error
@impl true
def add_operation({:ok, %TxBuild{tx: tx} = tx_build}, operation_body) do
with %Operation{} = operation <- Operation.new(operation_body),
%Operations{} = operations <- Operations.add(tx.operations, operation) do
transaction = %{tx | operations: operations, base_fee: BaseFee.increment(tx.base_fee)}
{:ok, %{tx_build | tx: transaction}}
end
end
def add_operation(error, _operation), do: error
@impl true
def set_soroban_data({:ok, %TxBuild{}} = tx_build, soroban_data)
when is_binary(soroban_data) do
case check_soroban_data(soroban_data) do
{:ok, soroban_tx_data} ->
set_soroban_data(tx_build, soroban_tx_data)
error ->
error
end
end
def set_soroban_data(
{:ok, %TxBuild{tx: tx} = tx_build},
%SorobanTransactionData{} = soroban_tx_data
) do
ext = TransactionExt.new(soroban_tx_data, 1)
transaction = %{tx | ext: ext}
{:ok, %{tx_build | tx: transaction}}
end
def set_soroban_data({:ok, %TxBuild{}}, _soroban_tx_data),
do: {:error, :invalid_soroban_data}
def set_soroban_data(error, _soroban_data), do: error
@impl true
def sign({:ok, %TxBuild{}} = tx_build, []), do: tx_build
def sign({:ok, %TxBuild{}} = tx_build, [%Signature{} = signature | signatures]) do
tx_build
|> sign(signature)
|> sign(signatures)
end
def sign({:ok, %TxBuild{signatures: signatures} = tx_build}, %Signature{} = signature) do
{:ok, %{tx_build | signatures: signatures ++ [signature]}}
end
def sign({:ok, %TxBuild{}}, _signature), do: {:error, :invalid_signature}
def sign(error, _signature), do: error
@impl true
def build({:ok, %TxBuild{tx: tx, signatures: signatures} = tx_build}) do
{:ok, %{tx_build | tx_envelope: TransactionEnvelope.new(tx, signatures)}}
end
def build(error), do: error
@impl true
def envelope({:ok, %TxBuild{tx: tx, signatures: signatures}}) do
tx
|> TransactionEnvelope.new(signatures)
|> TransactionEnvelope.to_xdr()
|> TransactionEnvelope.to_base64()
|> (&{:ok, &1}).()
end
def envelope(error), do: error
@impl true
def sign_envelope(tx_base64, []), do: tx_base64
def sign_envelope({:ok, tx_base64}, signatures), do: sign_envelope(tx_base64, signatures)
def sign_envelope(tx_base64, [%Signature{} = signature | signatures]) do
tx_base64
|> sign_envelope(signature)
|> sign_envelope(signatures)
end
def sign_envelope(tx_base64, %Signature{} = signature) do
tx_base64
|> TransactionEnvelope.add_signature(signature)
|> TransactionEnvelope.to_base64()
|> (&{:ok, &1}).()
end
def sign_envelope(_tx_base64, _signature), do: {:error, :invalid_signature}
@impl true
def hash({:ok, %TxBuild{tx: tx}}) do
tx
|> Transaction.to_xdr()
|> TransactionSignature.base_signature()
|> Base.encode16(case: :lower)
|> (&{:ok, &1}).()
end
def hash(error), do: error
@spec check_soroban_data(soroban_data :: binary()) ::
{:ok, SorobanTransactionData.t()} | {:error, atom()}
defp check_soroban_data(soroban_data) do
with {:ok, raw_soroban_data} <- Base.decode64(soroban_data),
{:ok, {%SorobanTransactionData{} = soroban_tx_data, ""}} <-
SorobanTransactionData.decode_xdr(raw_soroban_data) do
{:ok, soroban_tx_data}
else
_ ->
{:error, :invalid_soroban_data}
end
end
end