Current section

Files

Jump to
signet test support solana_client.ex
Raw

test/support/solana_client.ex

defmodule Signet.Solana.Test.Client do
@moduledoc """
Mock Solana JSON-RPC client for testing.
Uses address-based dispatch to return different responses:
- Standard addresses return normal responses
- Specific "magic" addresses trigger error cases or edge cases
"""
# Known test addresses (Base58-encoded)
# All-zeros seed → "4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS"
@nonexistent_account "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
@error_account "11111111111111111111111111111112"
def request(%Finch.Request{body: body}, _finch_name, _opts) do
%{"jsonrpc" => "2.0", "method" => method, "params" => params, "id" => id} =
Jason.decode!(body)
result = dispatch(method, params)
return_body =
case result do
{:rpc_error, error} ->
Jason.encode!(%{"jsonrpc" => "2.0", "error" => error, "id" => id})
_ ->
Jason.encode!(%{"jsonrpc" => "2.0", "result" => result, "id" => id})
end
{:ok, %Finch.Response{status: 200, body: return_body}}
end
# ---------------------------------------------------------------------------
# getBalance
# ---------------------------------------------------------------------------
defp dispatch("getBalance", [@error_account | _]) do
{:rpc_error, %{"code" => -32600, "message" => "Invalid request"}}
end
defp dispatch("getBalance", [_pubkey | _]) do
%{"context" => %{"slot" => 256_000}, "value" => 1_500_000_000}
end
# ---------------------------------------------------------------------------
# getAccountInfo
# ---------------------------------------------------------------------------
defp dispatch("getAccountInfo", [@nonexistent_account, _config]) do
%{"context" => %{"slot" => 256_000}, "value" => nil}
end
defp dispatch("getAccountInfo", [_pubkey, %{"encoding" => "base64"}]) do
%{
"context" => %{"slot" => 256_000},
"value" => %{
"data" => ["AQAAAAA=", "base64"],
"executable" => false,
"lamports" => 1_461_600,
"owner" => "11111111111111111111111111111111",
"rentEpoch" => 18_446_744_073_709_551_615,
"space" => 5
}
}
end
defp dispatch("getAccountInfo", [_pubkey, _config]) do
%{
"context" => %{"slot" => 256_000},
"value" => %{
"data" => ["AQAAAAA=", "base64"],
"executable" => false,
"lamports" => 1_461_600,
"owner" => "11111111111111111111111111111111",
"rentEpoch" => 18_446_744_073_709_551_615,
"space" => 5
}
}
end
# ---------------------------------------------------------------------------
# getMultipleAccounts
# ---------------------------------------------------------------------------
defp dispatch("getMultipleAccounts", [pubkeys, _config]) do
values =
Enum.map(pubkeys, fn
@nonexistent_account ->
nil
_ ->
%{
"data" => ["", "base64"],
"executable" => false,
"lamports" => 500_000,
"owner" => "11111111111111111111111111111111",
"rentEpoch" => 0,
"space" => 0
}
end)
%{"context" => %{"slot" => 256_000}, "value" => values}
end
# ---------------------------------------------------------------------------
# getLatestBlockhash
# ---------------------------------------------------------------------------
defp dispatch("getLatestBlockhash", _) do
%{
"context" => %{"slot" => 256_000},
"value" => %{
"blockhash" => "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZAMdL4VZHirAn",
"lastValidBlockHeight" => 256_200
}
}
end
# ---------------------------------------------------------------------------
# getSlot / getBlockHeight
# ---------------------------------------------------------------------------
defp dispatch("getSlot", _), do: 256_000
defp dispatch("getBlockHeight", _), do: 255_980
# ---------------------------------------------------------------------------
# getTransaction
# ---------------------------------------------------------------------------
defp dispatch("getTransaction", ["not_found_sig" | _]) do
nil
end
defp dispatch("getTransaction", [_sig | _]) do
%{
"blockTime" => 1_708_300_522,
"slot" => 255_900,
"version" => "legacy",
"meta" => %{
"err" => nil,
"fee" => 5000,
"preBalances" => [10_000_000_000, 0, 1],
"postBalances" => [8_999_995_000, 1_000_000_000, 1],
"logMessages" => [
"Program 11111111111111111111111111111111 invoke [1]",
"Program 11111111111111111111111111111111 success"
],
"innerInstructions" => [],
"rewards" => nil,
"loadedAddresses" => %{"readonly" => [], "writable" => []},
"computeUnitsConsumed" => 150
},
"transaction" => %{
"signatures" => [
"4Lz3raap9pEVGjT4EuVmNxTzMEj3EhVFKBonVFcnjiMwFKwEqh9TuPRYSv3TpK6ia4W33kMtJMdRJiL"
],
"message" => %{
"accountKeys" => [
"4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS",
"FVen3X669xLzsi6N2V91DoiyzHzg1uAgqiT8jZ9nS96Z",
"11111111111111111111111111111111"
],
"header" => %{
"numRequiredSignatures" => 1,
"numReadonlySignedAccounts" => 0,
"numReadonlyUnsignedAccounts" => 1
},
"instructions" => [
%{
"programIdIndex" => 2,
"accounts" => [0, 1],
"data" => "3Bxs4h24hBtQy9rw"
}
],
"recentBlockhash" => "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZAMdL4VZHirAn"
}
}
}
end
# ---------------------------------------------------------------------------
# getSignatureStatuses
# ---------------------------------------------------------------------------
defp dispatch("getSignatureStatuses", [sigs | _]) do
values =
Enum.map(sigs, fn
"confirmed_sig" ->
%{
"slot" => 255_900,
"confirmations" => 10,
"err" => nil,
"confirmationStatus" => "confirmed"
}
"failed_sig" ->
%{
"slot" => 255_900,
"confirmations" => nil,
"err" => %{"InstructionError" => [0, "InsufficientFunds"]},
"confirmationStatus" => "finalized"
}
"unknown_sig" ->
nil
_ ->
%{
"slot" => 255_900,
"confirmations" => nil,
"err" => nil,
"confirmationStatus" => "finalized"
}
end)
%{"context" => %{"slot" => 256_000}, "value" => values}
end
# ---------------------------------------------------------------------------
# getMinimumBalanceForRentExemption
# ---------------------------------------------------------------------------
defp dispatch("getMinimumBalanceForRentExemption", [165 | _]), do: 2_039_280
defp dispatch("getMinimumBalanceForRentExemption", [0 | _]), do: 890_880
defp dispatch("getMinimumBalanceForRentExemption", _), do: 890_880
# ---------------------------------------------------------------------------
# getTokenAccountBalance
# ---------------------------------------------------------------------------
defp dispatch("getTokenAccountBalance", [_pubkey | _]) do
%{
"context" => %{"slot" => 256_000},
"value" => %{
"amount" => "1000000000",
"decimals" => 9,
"uiAmount" => 1.0,
"uiAmountString" => "1"
}
}
end
# ---------------------------------------------------------------------------
# getTokenAccountsByOwner
# ---------------------------------------------------------------------------
# Filter by mint - returns matching token accounts
defp dispatch("getTokenAccountsByOwner", [_owner, %{"mint" => _mint}, _config]) do
%{
"context" => %{"slot" => 256_000},
"value" => [
%{
"pubkey" => "AyVfCw5fBuVTkzG4bBPiDfCkuS1YGrh2i6pRgLHMwmZr",
"account" => %{
"data" => %{
"parsed" => %{
"info" => %{
"isNative" => false,
"mint" => "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"owner" => "4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS",
"state" => "initialized",
"tokenAmount" => %{
"amount" => "1500000000",
"decimals" => 6,
"uiAmount" => 1500.0,
"uiAmountString" => "1500"
}
},
"type" => "account"
},
"program" => "spl-token",
"space" => 165
},
"executable" => false,
"lamports" => 2_039_280,
"owner" => "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"rentEpoch" => 18_446_744_073_709_551_615,
"space" => 165
}
}
]
}
end
# Filter by programId - returns all token accounts
defp dispatch("getTokenAccountsByOwner", [_owner, %{"programId" => program_id}, _config]) do
accounts =
case program_id do
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ->
[
token_account_fixture(
"AyVfCw5fBuVTkzG4bBPiDfCkuS1YGrh2i6pRgLHMwmZr",
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"1500000000",
6
),
token_account_fixture(
"BzVfCw5fBuVTkzG4bBPiDfCkuS1YGrh2i6pRgLHMwmZs",
"So11111111111111111111111111111111111111112",
"5000000000",
9
)
]
"TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" ->
[
token_account_fixture(
"CzVfCw5fBuVTkzG4bBPiDfCkuS1YGrh2i6pRgLHMwmZt",
"2DEy3MN8J2zKWVoYTPKg4wifoDBavX6XpSgbRsfuLAMn",
"999000000000",
9
)
]
_ ->
[]
end
%{"context" => %{"slot" => 256_000}, "value" => accounts}
end
# ---------------------------------------------------------------------------
# getRecentPrioritizationFees
# ---------------------------------------------------------------------------
defp dispatch("getRecentPrioritizationFees", _) do
[
%{"slot" => 255_998, "prioritizationFee" => 0},
%{"slot" => 255_999, "prioritizationFee" => 1000},
%{"slot" => 256_000, "prioritizationFee" => 500}
]
end
# ---------------------------------------------------------------------------
# getHealth / getVersion
# ---------------------------------------------------------------------------
defp dispatch("getHealth", _), do: "ok"
defp dispatch("getVersion", _) do
%{"solana-core" => "1.18.26", "feature-set" => 2_891_131_721}
end
# ---------------------------------------------------------------------------
# sendTransaction
# ---------------------------------------------------------------------------
defp dispatch("sendTransaction", [_encoded, _config]) do
"4Lz3raap9pEVGjT4EuVmNxTzMEj3EhVFKBonVFcnjiMwFKwEqh9TuPRYSv3TpK6ia4W33kMtJMdRJiL"
end
# ---------------------------------------------------------------------------
# simulateTransaction
# ---------------------------------------------------------------------------
defp dispatch("simulateTransaction", [_encoded, _config]) do
%{
"context" => %{"slot" => 256_000},
"value" => %{
"err" => nil,
"logs" => [
"Program 11111111111111111111111111111111 invoke [1]",
"Program 11111111111111111111111111111111 success"
],
"unitsConsumed" => 150
}
}
end
# ---------------------------------------------------------------------------
# requestAirdrop
# ---------------------------------------------------------------------------
defp dispatch("requestAirdrop", [_pubkey, _lamports | _]) do
"2ZE3FQsWzjbkyNKP5qEDGjJEsaWmVFBCKSBMxpZUTgBs1PWDM1jN6hUEyFz1"
end
# ---------------------------------------------------------------------------
# Fallback
# ---------------------------------------------------------------------------
defp dispatch(method, _params) do
{:rpc_error, %{"code" => -32601, "message" => "Method not found: #{method}"}}
end
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
defp token_account_fixture(pubkey, mint, amount, decimals) do
%{
"pubkey" => pubkey,
"account" => %{
"data" => %{
"parsed" => %{
"info" => %{
"isNative" => false,
"mint" => mint,
"owner" => "4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS",
"state" => "initialized",
"tokenAmount" => %{
"amount" => amount,
"decimals" => decimals,
"uiAmount" => String.to_integer(amount) / :math.pow(10, decimals),
"uiAmountString" => "#{String.to_integer(amount) / :math.pow(10, decimals)}"
}
},
"type" => "account"
},
"program" => "spl-token",
"space" => 165
},
"executable" => false,
"lamports" => 2_039_280,
"owner" => "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"rentEpoch" => 18_446_744_073_709_551_615,
"space" => 165
}
}
end
end