defmodule Ton do @moduledoc """ SDK for TON (The Open Network). """ alias Ton.Address alias Ton.Cell alias Ton.CommonMessageInfo alias Ton.ExternalMessage alias Ton.KeyPair alias Ton.Transfer alias Ton.Wallet @pbkdf2_options %{ alg: "sha512", iterations: 100_000, length: 64, format: true, salt: "TON default seed" } @doc """ Generate a random mnemonic with the given number of words (24 by default). See https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki Examples: iex> mnemonic1 = Ton.generate_mnemonic() iex> mnemonic2 = Ton.generate_mnemonic() iex> mnemonic1 |> String.split(" ") |> Enum.count() 24 iex> mnemonic1 != mnemonic2 true """ @spec generate_mnemonic(non_neg_integer()) :: String.t() | no_return def generate_mnemonic(word_number \\ 24), do: Mnemoniac.create_mnemonic!(word_number) @doc """ Generates a key pair from a mnemonic. ## Examples iex> Ton.mnemonic_to_keypair("rail sound peasant garment bounce trigger true abuse arctic gravity ribbon ocean absurd okay blue remove neck cash reflect sleep hen portion gossip arrow") %Ton.KeyPair{ secret_key: <<149, 222, 63, 223, 23, 72, 240, 224, 233, 177, 155, 16, 101, 229, 182, 172, 23, 131, 54, 43, 195, 139, 217, 194, 100, 19, 252, 105, 68, 30, 96, 95, 73, 245, 11, 185, 76, 95, 180, 99, 83, 74, 157, 13, 240, 216, 227, 155, 203, 147, 16, 149, 137, 218, 246, 81, 151, 233, 21, 28, 55, 119, 64, 47>>, public_key: <<73, 245, 11, 185, 76, 95, 180, 99, 83, 74, 157, 13, 240, 216, 227, 155, 203, 147, 16, 149, 137, 218, 246, 81, 151, 233, 21, 28, 55, 119, 64, 47>> } """ @spec mnemonic_to_keypair(String.t(), String.t()) :: KeyPair.t() def mnemonic_to_keypair(mnemonic, password \\ "") do {:ok, {public_key, private_key}} = mnemonic |> mnemonic_to_seed(password) |> Cafezinho.keypair_from_seed() KeyPair.new(private_key, public_key) end @doc """ Converts a mnemonic to a seed. ## Examples iex> Ton.mnemonic_to_seed("rail sound peasant garment bounce trigger true abuse arctic gravity ribbon ocean absurd okay blue remove neck cash reflect sleep hen portion gossip arrow") # 95de3fdf1748f0e0e9b19b1065e5b6ac1783362bc38bd9c26413fc69441e605f <<149, 222, 63, 223, 23, 72, 240, 224, 233, 177, 155, 16, 101, 229, 182, 172, 23, 131, 54, 43, 195, 139, 217, 194, 100, 19, 252, 105, 68, 30, 96, 95>> """ @spec mnemonic_to_seed(String.t(), String.t()) :: binary() def mnemonic_to_seed(mnemonic, password \\ "") do <> = mnemonic |> mnemonic_to_entropy(password) |> ExPBKDF2.pbkdf2(@pbkdf2_options) seed end @doc """ Converts a mnemonic to an entropy. ## Examples iex> Ton.mnemonic_to_entropy("rail sound peasant garment bounce trigger true abuse arctic gravity ribbon ocean absurd okay blue remove neck cash reflect sleep hen portion gossip arrow") # "1ab77da4f35581bd6f8efdcaef689ec180c6f77d214e4dd960f1619f6c40599f04798cb4e93e30edceeb02ab36f450df1c359e7b3395f13646134046cc6a7f3a" <<26, 183, 125, 164, 243, 85, 129, 189, 111, 142, 253, 202, 239, 104, 158, 193, 128, 198, 247, 125, 33, 78, 77, 217, 96, 241, 97, 159, 108, 64, 89, 159, 4, 121, 140, 180, 233, 62, 48, 237, 206, 235, 2, 171, 54, 244, 80, 223, 28, 53, 158, 123, 51, 149, 241, 54, 70, 19, 64, 70, 204, 106, 127, 58>> """ @spec mnemonic_to_entropy(String.t(), String.t()) :: binary() def mnemonic_to_entropy(mnemonic, password \\ "") do :crypto.mac(:hmac, :sha512, mnemonic, password) end @doc """ Initializes a `Wallet` struct from public_key, workchain and wallet_id. The version of the wallet contract is v4 r2. ## Examples iex> keypair = Ton.mnemonic_to_keypair("rail sound peasant garment bounce trigger true abuse arctic gravity ribbon ocean absurd okay blue remove neck cash reflect sleep hen portion gossip arrow") iex> %Ton.Wallet{initial_code: _, initial_data: _, workchain: 0, wallet_id: 698983191, public_key: <<73, 245, 11, 185, 76, 95, 180, 99, 83, 74, 157, 13, 240, 216, 227, 155, 203, 147, 16, 149, 137, 218, 246, 81, 151, 233, 21, 28, 55, 119, 64, 47>>} = Ton.create_wallet(keypair.public_key) """ @spec create_wallet(binary(), integer(), integer()) :: Wallet.t() def create_wallet(public_key, workchain \\ 0, wallet_id \\ 698_983_191) do Wallet.create(public_key, workchain, wallet_id) end @doc """ Parses and validates an address ## Examples iex> Ton.parse_address("UQCAIBANQeQX6UHmRgxHGR44oUL7VOQE9v4dxmla23KpjKIj") {:ok, %Ton.Address{test_only: false, bounceable: false, workchain: 0, hash: <<128, 32, 16, 13, 65, 228, 23, 233, 65, 230, 70, 12, 71, 25, 30, 56, 161, 66, 251, 84, 228, 4, 246, 254, 29, 198, 105, 90, 219, 114, 169, 140>>}} iex> Ton.parse_address("address") {:error, :invalid_base64} """ @spec parse_address(binary()) :: {:ok, Address.t()} | {:error, atom()} def parse_address(address) do Address.parse(address) end @doc """ Generates a friendly address from a wallet struct ## Examples iex> keypair = Ton.mnemonic_to_keypair("rail sound peasant garment bounce trigger true abuse arctic gravity ribbon ocean absurd okay blue remove neck cash reflect sleep hen portion gossip arrow") iex> wallet = Ton.create_wallet(keypair.public_key) iex> Ton.wallet_to_friendly_address(wallet) "EQAC824gsw8OZLoMV6_nr4nkxaEQFlbzoiHHOWIYY81eM5rQ" iex> keypair = Ton.mnemonic_to_keypair("rail sound peasant garment bounce trigger true abuse arctic gravity ribbon ocean absurd okay blue remove neck cash reflect sleep hen portion gossip arrow") iex> wallet = Ton.create_wallet(keypair.public_key) iex> Ton.wallet_to_friendly_address(wallet, [url_safe: false, bounceable: false, test_only: true]) "0QAC824gsw8OZLoMV6/nr4nkxaEQFlbzoiHHOWIYY81eM3yf" """ @spec wallet_to_friendly_address(Wallet.t(), Keyword.t()) :: binary() def wallet_to_friendly_address(wallet, params \\ []) do Address.friendly_address(wallet, params) end @doc """ Generates a raw address from a wallet struct ## Examples iex> keypair = Ton.mnemonic_to_keypair("rail sound peasant garment bounce trigger true abuse arctic gravity ribbon ocean absurd okay blue remove neck cash reflect sleep hen portion gossip arrow") iex> wallet = Ton.create_wallet(keypair.public_key) iex> Ton.wallet_to_raw_address(wallet) "0:02f36e20b30f0e64ba0c57afe7af89e4c5a1101656f3a221c739621863cd5e33" iex> keypair = Ton.mnemonic_to_keypair("rail sound peasant garment bounce trigger true abuse arctic gravity ribbon ocean okay absurd blue remove neck cash reflect sleep hen portion gossip arrow") iex> wallet = Ton.create_wallet(keypair.public_key) iex> Ton.wallet_to_raw_address(wallet) "0:9f48f826d3f5d2a986d7af82504b7f036be62a8168c8e0409f32ddbe06565780" """ @spec wallet_to_raw_address(Wallet.t()) :: binary() def wallet_to_raw_address(wallet) do Address.raw_address(wallet) end @doc """ Create a transfer boc which can be used to submit a transaction ## Examples iex> keypair = Ton.mnemonic_to_keypair("rail sound peasant garment bounce trigger true abuse arctic gravity ribbon ocean absurd okay blue remove neck cash reflect sleep hen portion gossip arrow") iex> wallet = Ton.create_wallet(keypair.public_key) iex> {:ok, to_address} = Ton.parse_address("EQAHJQ6gs2NYAXsxsfsucpqhpneZaGP0qCdu9lCEzysMGzst") iex> params = [seqno: 5, bounce: true, secret_key: keypair.secret_key, value: 1, to_address: to_address, timeout: 60, comment: "Hello"] iex> <<181, 238, 156, 114, 65, 1, 2, 1, 0, 176, 0, 1, 225, 136, 0, 5, _tail::binary>> = Ton.create_transfer_boc(wallet, params) """ @spec create_transfer_boc(Wallet.t(), Keyword.t()) :: binary() | no_return() def create_transfer_boc(wallet, params) do seqno = Keyword.fetch!(params, :seqno) bounce = Keyword.fetch!(params, :bounce) secret_key = Keyword.fetch!(params, :secret_key) value = Keyword.fetch!(params, :value) to_address = Keyword.fetch!(params, :to_address) timeout = Keyword.fetch!(params, :timeout) comment = Keyword.get(params, :comment) send_mode = Keyword.get(params, :send_mode, 3) transfer = Transfer.new( seqno: seqno, value: value, bounce: bounce, to: to_address, wallet_id: wallet.wallet_id, timeout: timeout, body: comment, send_mode: send_mode ) |> Transfer.serialize_and_sign(secret_key) common_message_info = if seqno == 0 do CommonMessageInfo.new(wallet, transfer) else CommonMessageInfo.new(nil, transfer) end wallet |> ExternalMessage.new(common_message_info) |> ExternalMessage.serialize() |> Cell.serialize(has_idx: false) end end