Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/solana/account.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ defmodule Solana.Account do
@type t :: %__MODULE__{
signer?: boolean(),
writable?: boolean(),
invoked?: boolean(),
key: Solana.key() | nil
}

defstruct [
:key,
signer?: false,
writable?: false
writable?: false,
invoked?: false
]
end
315 changes: 315 additions & 0 deletions lib/solana/lookup_table.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
defmodule Solana.LookupTable do
@moduledoc """
Functions for interacting with Solana's
[Address Lookup Table]
(https://solana.com/developers/guides/advanced/lookup-tables) Program.
"""
alias Solana.{Account, Instruction, Key, SystemProgram}
import Solana.Helpers

@typedoc "Address lookup table account metadata."
@type t :: %__MODULE__{
authority: Key.t() | nil,
keys: [Key.t()],
deactivation_slot: non_neg_integer(),
last_extended_slot: non_neg_integer(),
last_extended_slot_start_index: non_neg_integer()
}

defstruct [
:authority,
:keys,
:deactivation_slot,
:last_extended_slot,
:last_extended_slot_start_index
]

@doc """
The on-chain size of an address lookup table containing the given number of keys.
"""
def byte_size(key_count \\ 0), do: 56 + key_count * 32

@doc """
The Address Lookup Table Program's program ID.
"""
def id(), do: Solana.pubkey!("AddressLookupTab1e1111111111111111111111111")

@doc """
Finds the address lookup table account addresss associated with a given
authority and recent block's slot.
"""
def find_address(authority, recent_slot) do
Solana.Key.find_address([authority, <<recent_slot::little-64>>], id())
end

@doc """
Translates the result of a `Solana.RPC.Request.get_account_info/2` into a
`t:Solana.LookupTable.t/0`.
"""
@spec from_account_info(info :: map) :: t() | :error
def from_account_info(%{"data" => %{"parsed" => %{"info" => info}}}) do
from_lookup_table_account_info(info)
end

def from_account_info(_), do: :error

defp from_lookup_table_account_info(%{
"addresses" => keys,
"deactivationSlot" => deactivation_slot,
"lastExtendedSlot" => last_extended_slot,
"lastExtendedSlotStartIndex" => last_extended_slot_start_index
} = info) do
authority = with encoded when encoded != nil <- Map.get(info, "authority") do
Solana.pubkey!(encoded)
end
%__MODULE__{
authority: authority,
keys: Enum.map(keys, &Solana.pubkey!/1),
deactivation_slot: String.to_integer(deactivation_slot),
last_extended_slot: String.to_integer(last_extended_slot),
last_extended_slot_start_index: last_extended_slot_start_index
}
end

defp from_lookup_table_account_info(_), do: :error

@create_lookup_table_schema [
authority: [
type: {:custom, Solana.Key, :check, []},
required: true,
doc: "Public key of the account used to derive and control the address lookup table"
],
payer: [
type: {:custom, Solana.Key, :check, []},
required: true,
doc: "Public key of the account that will fund the created address lookup table"
],
payer: [
type: {:custom, Solana.Key, :check, []},
required: true,
doc: "Account that will fund the created address lookup table"
],
recent_slot: [
type: :non_neg_integer,
required: true,
doc: "A recent slot must be used in the derivation path for each initialized table"
]
]
@doc """
Generates the instructions for creating a new address lookup table.

Returns a tuple containing the instructions and the public key of the derived
address lookup table account.

## Options

#{NimbleOptions.docs(@create_lookup_table_schema)}
"""
def create_lookup_table(opts) do
with {:ok, params} <- validate(opts, @create_lookup_table_schema) do
create_lookup_table_ix(params)
end
end

defp create_lookup_table_ix(params) do
with {:ok, lookup_table, bump_seed}
<- find_address(params.authority, params.recent_slot) do
ix = %Instruction{
program: id(),
accounts: [
%Account{key: lookup_table, signer?: false, writable?: true},
%Account{key: params.authority, signer?: false, writable?: false},
%Account{key: params.payer, signer?: true, writable?: true},
%Account{key: SystemProgram.id(), signer?: false, writable?: false}
],
data:
Instruction.encode_data([
{0, 32},
{params.recent_slot, 64},
{bump_seed, 8}
])
}
{ix, lookup_table}
end
end

@freeze_lookup_table_schema [
lookup_table: [
type: {:custom, Solana.Key, :check, []},
required: true,
doc: "Public key of the address lookup table"
],
authority: [
type: {:custom, Solana.Key, :check, []},
required: true,
doc: "Public key of the account used to derive and control the address lookup table"
]
]
@doc """
Generates the instructions for freezing an address lookup table.

Freezing an addess lookup table makes it immutable. It can never be closed or
extended again. Only non-empty lookup tables can be frozen.

## Options

#{NimbleOptions.docs(@freeze_lookup_table_schema)}
"""
def freeze_lookup_table(opts) do
with {:ok, params} <- validate(opts, @freeze_lookup_table_schema) do
freeze_lookup_table_ix(params)
end
end

defp freeze_lookup_table_ix(params) do
%Instruction{
program: id(),
accounts:
List.flatten([
%Account{key: params.lookup_table, signer?: false, writable?: true},
%Account{key: params.authority, signer?: true, writable?: false},
]),
data: Instruction.encode_data([{1, 32}])
}
end

@extend_lookup_table_schema [
lookup_table: [
type: {:custom, Solana.Key, :check, []},
required: true,
doc: "Public key of the address lookup table"
],
authority: [
type: {:custom, Solana.Key, :check, []},
required: true,
doc: "Public key of the account used to derive and control the address lookup table"
],
payer: [
type: {:custom, Solana.Key, :check, []},
required: false,
doc: "Public key of the account that will fund any fees needed to extend the lookup table"
],
new_keys: [
type: {:list, {:custom, Solana.Key, :check, []}},
required: true,
doc: "Pubic keys of the accounts that will be added to the address lookup table"
]
]
@doc """
Generates the instructions for extending an address lookup table.

## Options

#{NimbleOptions.docs(@extend_lookup_table_schema)}
"""
def extend_lookup_table(opts) do
with {:ok, params} <- validate(opts, @extend_lookup_table_schema) do
extend_lookup_table_ix(params)
end
end

defp extend_lookup_table_ix(params) do
%Instruction{
program: id(),
accounts:
List.flatten([
%Account{key: params.lookup_table, signer?: false, writable?: true},
%Account{key: params.authority, signer?: true, writable?: false},
if(params[:payer], do: [
%Account{key: params.payer, signer?: true, writable?: true},
%Account{key: SystemProgram.id(), signer?: false, writable?: false}
], else: [])
]),
data: Instruction.encode_data(List.flatten([
{2, 32},
{length(params.new_keys), 64},
params.new_keys
]))
}
end

@deactivate_lookup_table_schema [
lookup_table: [
type: {:custom, Solana.Key, :check, []},
required: true,
doc: "Public key of the address lookup table"
],
authority: [
type: {:custom, Solana.Key, :check, []},
required: true,
doc: "Public key of the account used to derive and control the address lookup table"
]
]
@doc """
Generates the instructions for deactivating an address lookup table.

Once deactivated, an address lookup table can no longer be extended or used
for lookups in transactions. A lookup tables can only be closed once its
deactivation slot is no longer present in the
[SlotHashes](https://docs.anza.xyz/runtime/sysvars/#slothashes) sysvar.

## Options

#{NimbleOptions.docs(@deactivate_lookup_table_schema)}
"""
def deactivate_lookup_table(opts) do
with {:ok, params} <- validate(opts, @deactivate_lookup_table_schema) do
deactivate_lookup_table_ix(params)
end
end

defp deactivate_lookup_table_ix(params) do
%Instruction{
program: id(),
accounts:
List.flatten([
%Account{key: params.lookup_table, signer?: false, writable?: true},
%Account{key: params.authority, signer?: true, writable?: false},
]),
data: Instruction.encode_data([{3, 32}])
}
end

@close_lookup_table_schema [
lookup_table: [
type: {:custom, Solana.Key, :check, []},
required: true,
doc: "Public key of the address lookup table"
],
authority: [
type: {:custom, Solana.Key, :check, []},
required: true,
doc: "Public key of the account used to derive and control the address lookup table"
],
recipient: [
type: {:custom, Solana.Key, :check, []},
required: true,
doc: "Public key of the account to send the closed account's lamports to"
]
]
@doc """
Generates the instructions for closing an address lookup table.

## Options

#{NimbleOptions.docs(@close_lookup_table_schema)}
"""
def close_lookup_table(opts) do
with {:ok, params} <- validate(opts, @close_lookup_table_schema) do
close_lookup_table_ix(params)
end
end

defp close_lookup_table_ix(params) do
%Instruction{
program: id(),
accounts:
List.flatten([
%Account{key: params.lookup_table, signer?: false, writable?: true},
%Account{key: params.authority, signer?: true, writable?: false},
%Account{key: params.recipient, signer?: false, writable?: true},
]),
data: Instruction.encode_data([{4, 32}])
}
end
end
11 changes: 11 additions & 0 deletions lib/solana/rpc/request.ex
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ defmodule Solana.RPC.Request do
{"getBalance", [B58.encode58(account), encode_opts(opts)]}
end

@doc """
Returns the slot that has reached the given or default commitment level.

For more information, see [the Solana
docs](https://solana.com/docs/rpc/http/getslot).
"""
@spec get_slot(opts :: keyword) :: t
def get_slot(opts \\ []) do
{"getSlot", [encode_opts(opts)]}
end

@doc """
Returns identity and transaction information about a confirmed block in the
ledger.
Expand Down
Loading