defmodule Soroban.Contract.RPCCalls do
@moduledoc """
Exposes the functions to execute the simulate and send_transaction endpoints
"""
alias Soroban.RPC
alias Soroban.RPC.{
GetLatestLedgerResponse,
SendTransactionResponse,
Server,
SimulateTransactionResponse
}
alias Stellar.TxBuild
alias Stellar.TxBuild.{ExtendFootprintTTL, RestoreFootprint}
alias Stellar.TxBuild.SorobanTransactionData, as: TxSorobanTransactionData
alias Stellar.TxBuild.{
Account,
BaseFee,
InvokeHostFunction,
SequenceNumber,
Signature,
SorobanAuthorizationEntry
}
@type server :: Server.t()
@type network_passphrase :: String.t()
@type validation :: {:ok, any()}
@type account :: Account.t()
@type auths :: list(String.t()) | nil
@type auth_secret_key :: String.t() | list() | nil
@type envelope_xdr :: String.t()
@type footprint_operations :: ExtendFootprintTTL.t() | RestoreFootprint.t()
@type operation :: InvokeHostFunction.t() | footprint_operations()
@type simulate_response :: {:ok, SimulateTransactionResponse.t()}
@type send_response :: {:ok, SendTransactionResponse.t()}
@type signature :: Signature.t()
@type sequence_number :: SequenceNumber.t()
@type addl_resources :: keyword()
@type soroban_data :: TxSorobanTransactionData.t() | nil
@spec simulate(
operation :: operation(),
server :: server(),
network_passphrase :: network_passphrase(),
source_account :: account(),
sequence_number :: sequence_number(),
addl_resources :: addl_resources(),
soroban_data :: soroban_data()
) :: simulate_response()
def simulate(
_operation,
_server,
_network_passphrase,
_source_account,
_sequence_number,
_addl_resources,
soroban_data \\ nil
)
def simulate(
operation,
server,
network_passphrase,
source_account,
sequence_number,
addl_resources,
nil
) do
{:ok, envelope_xdr} =
source_account
|> TxBuild.new()
|> TxBuild.set_network_passphrase(network_passphrase)
|> TxBuild.set_sequence_number(sequence_number)
|> TxBuild.add_operation(operation)
|> TxBuild.envelope()
RPC.simulate_transaction(server, transaction: envelope_xdr, addl_resources: addl_resources)
end
def simulate(
operation,
server,
network_passphrase,
source_account,
sequence_number,
addl_resources,
soroban_data
) do
soroban_data = TxSorobanTransactionData.to_xdr(soroban_data)
{:ok, envelope_xdr} =
source_account
|> TxBuild.new()
|> TxBuild.set_network_passphrase(network_passphrase)
|> TxBuild.set_sequence_number(sequence_number)
|> TxBuild.add_operation(operation)
|> TxBuild.set_soroban_data(soroban_data)
|> TxBuild.envelope()
RPC.simulate_transaction(server, transaction: envelope_xdr, addl_resources: addl_resources)
end
@spec send_transaction(
simulate_response :: simulate_response(),
server :: server(),
network_passphrase :: network_passphrase(),
source_account :: account(),
sequence_number :: sequence_number(),
signature :: signature(),
operation :: operation(),
auth_secret_key :: auth_secret_key()
) :: send_response() | simulate_response()
def send_transaction(
_simulate_transaction,
_server,
_network_passphrase,
_source_account,
_sequence_number,
_signature,
_invoke_host_function_op,
auth_secret_key \\ nil
)
def send_transaction(
{:ok,
%SimulateTransactionResponse{
transaction_data: transaction_data,
min_resource_fee: min_resource_fee,
results: [%{auth: auth}]
}},
server,
network_passphrase,
source_account,
sequence_number,
signature,
%InvokeHostFunction{} = operation,
auth_secret_keys
) do
with %InvokeHostFunction{} = invoke_host_function_op <-
set_host_function_auth(server, network_passphrase, operation, auth, auth_secret_keys) do
%BaseFee{fee: base_fee} = BaseFee.new()
fee = BaseFee.new(base_fee + min_resource_fee)
{:ok, envelope_xdr} =
source_account
|> TxBuild.new()
|> TxBuild.set_network_passphrase(network_passphrase)
|> TxBuild.set_sequence_number(sequence_number)
|> TxBuild.add_operation(invoke_host_function_op)
|> TxBuild.set_base_fee(fee)
|> TxBuild.set_soroban_data(transaction_data)
|> TxBuild.sign(signature)
|> TxBuild.envelope()
RPC.send_transaction(server, envelope_xdr)
end
end
def send_transaction(
{:ok,
%SimulateTransactionResponse{
transaction_data: transaction_data,
min_resource_fee: min_resource_fee,
results: results
}},
server,
network_passphrase,
source_account,
sequence_number,
signature,
operation,
_auth_secret_keys
)
when is_nil(results) and is_binary(transaction_data) do
with {:ok, operation} <- validate_operation(operation) do
%BaseFee{fee: base_fee} = BaseFee.new()
fee = BaseFee.new(base_fee + min_resource_fee)
{:ok, envelope_xdr} =
source_account
|> TxBuild.new()
|> TxBuild.set_network_passphrase(network_passphrase)
|> TxBuild.set_sequence_number(sequence_number)
|> TxBuild.add_operation(operation)
|> TxBuild.set_base_fee(fee)
|> TxBuild.set_soroban_data(transaction_data)
|> TxBuild.sign(signature)
|> TxBuild.envelope()
RPC.send_transaction(server, envelope_xdr)
end
end
def send_transaction(
{:ok, %SimulateTransactionResponse{}} = response,
_server,
_network_passphrase,
_source_account,
_sequence_number,
_signature,
_invoke_host_function_op,
_auth_secret_key
),
do: response
@spec retrieve_unsigned_xdr(
simulate_response :: simulate_response(),
server :: server(),
network_passphrase :: network_passphrase(),
source_account :: account(),
sequence_number :: sequence_number(),
invoke_host_function_op :: operation()
) :: envelope_xdr() | simulate_response()
def retrieve_unsigned_xdr(
_simulate_response,
_server,
_network_passphrase,
_source_account,
_sequence_number,
_invoke_host_function_op
)
def retrieve_unsigned_xdr(
{:ok,
%SimulateTransactionResponse{
transaction_data: transaction_data,
min_resource_fee: min_resource_fee,
results: [%{auth: auth}]
}},
server,
network_passphrase,
source_account,
sequence_number,
invoke_host_function_op
) do
invoke_host_function_op =
set_host_function_auth(server, network_passphrase, invoke_host_function_op, auth, [])
%BaseFee{fee: base_fee} = BaseFee.new()
fee = BaseFee.new(base_fee + min_resource_fee)
{:ok, envelope_xdr} =
source_account
|> TxBuild.new()
|> TxBuild.set_network_passphrase(network_passphrase)
|> TxBuild.set_sequence_number(sequence_number)
|> TxBuild.add_operation(invoke_host_function_op)
|> TxBuild.set_base_fee(fee)
|> TxBuild.set_soroban_data(transaction_data)
|> TxBuild.envelope()
envelope_xdr
end
def retrieve_unsigned_xdr(
{:ok, %SimulateTransactionResponse{}} = response,
_server,
_network_passphrase,
_source_account,
_sequence_number,
_invoke_host_function_op
),
do: response
@spec set_host_function_auth(
server :: server(),
network_passphrase :: network_passphrase(),
invoke_host_function :: operation(),
auths :: auths(),
auth_secret_key :: auth_secret_key()
) :: operation() | {:error, atom()}
defp set_host_function_auth(
_server,
_network_passphrase,
invoke_host_function_op,
nil,
_auth_secret_key
),
do: invoke_host_function_op
defp set_host_function_auth(
_server,
_network_passphrase,
%InvokeHostFunction{} = invoke_host_function_op,
auths,
[]
),
do: InvokeHostFunction.set_auth(invoke_host_function_op, auths)
defp set_host_function_auth(
_server,
_network_passphrase,
%InvokeHostFunction{} = invoke_host_function_op,
auths,
nil
),
do: InvokeHostFunction.set_auth(invoke_host_function_op, auths)
defp set_host_function_auth(
server,
network_passphrase,
%InvokeHostFunction{} = invoke_host_function_op,
auths,
auth_secret_keys
)
when length(auths) == length(auth_secret_keys) do
{:ok, %GetLatestLedgerResponse{sequence: latest_ledger}} = RPC.get_latest_ledger(server)
authorizations =
auth_secret_keys
|> Enum.zip(auths)
|> Enum.map(fn {auth_secret_key, auth} ->
SorobanAuthorizationEntry.sign_xdr(
auth,
auth_secret_key,
latest_ledger,
network_passphrase
)
end)
InvokeHostFunction.set_auth(invoke_host_function_op, authorizations)
end
defp set_host_function_auth(
_server,
_network_passphrase,
_invoke_host_function_op,
_auths,
_auth_secret_keys
),
do: {:error, :invalid_auth_secret_keys_length}
@spec validate_operation(operation :: footprint_operations()) :: validation()
defp validate_operation(%ExtendFootprintTTL{} = operation), do: {:ok, operation}
defp validate_operation(%RestoreFootprint{} = operation), do: {:ok, operation}
end