Last active
June 4, 2025 15:59
-
-
Save mazz/28e1438d195f9977d32188abb4f18616 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule LumensAppWebApi.WalletController do | |
use LumensAppWeb, :controller | |
alias StellarBase.StrKey | |
alias Stellar.KeyPair | |
require Logger | |
def show_qr(conn, %{"network" => network}) when network in ["pubnet", "testnet"] do | |
session_id = UUID.uuid6() | |
Logger.debug("Generating QR code for network: #{network}, session_id: #{session_id}") | |
case WalletConnect.generate_qr_code(network, session_id) do | |
{:ok, qr_buffer} -> | |
qr_base64 = Base.encode64(qr_buffer) | |
Logger.debug("QR code generated for session_id: #{session_id}") | |
render(conn, :show_qr, qr_base64: qr_base64, network: network, session_id: session_id, error: nil) | |
{:error, reason} -> | |
Logger.error("Failed to generate QR code: #{reason}, session_id: #{session_id}") | |
render(conn, :show_qr, qr_base64: nil, network: network, session_id: session_id, error: reason) | |
end | |
end | |
def show_qr(conn, _params) do | |
Logger.error("Invalid network provided") | |
render(conn, :show_qr, qr_base64: nil, network: nil, session_id: nil, error: "Invalid network. Use 'pubnet' or 'testnet'") | |
end | |
def wallet_connect_qr(conn, %{"network" => network, "user_id" => user_id}) when network in ["pubnet", "testnet"] do | |
case LumensApp.Accounts.get_user!(user_id) do | |
nil -> | |
Logger.error("User not found for user_id: #{user_id}") | |
conn | |
|> put_status(:not_found) | |
|> json(%{error: "User not found"}) | |
user -> | |
Logger.debug("Checking user auth status for network: #{network}, user: #{user_id}") | |
case check_user_auth_status(conn, user) do | |
{true, _error_map, _status} -> | |
Logger.debug("Generating WalletConnect QR code for network: #{network}, user_id: #{user_id}") | |
{:ok, session_id} = WalletManager.create_session(network, user_id) | |
case WalletConnect.generate_qr_code(network, user_id, session_id) do | |
{:ok, qr_buffer} -> | |
qr_base64 = Base.encode64(qr_buffer) | |
response = %{ | |
qr_base64: qr_base64, | |
session_id: session_id, | |
network: network, | |
error: nil | |
} | |
Logger.debug("Broadcasting QR code for user_id: #{user_id}, session_id: #{session_id}") | |
LumensAppWeb.Endpoint.broadcast("wallet:#{user_id}", "qr_code", response) | |
send_resp(conn, :no_content, "") | |
{:error, reason} -> | |
Logger.error("Failed to generate WalletConnect QR code: #{reason}") | |
response = %{ | |
qr_base64: nil, | |
session_id: session_id, | |
network: network, | |
error: reason | |
} | |
LumensAppWeb.Endpoint.broadcast("wallet:#{user_id}", "qr_code", response) | |
send_resp(conn, :no_content, "") | |
end | |
{false, error_map, status} -> | |
conn | |
|> put_status(status) | |
|> json(error_map) | |
end | |
end | |
end | |
def create_custodial_wallet(conn, %{"user_id" => user_id}) do | |
case LumensApp.Accounts.get_user!(user_id) do | |
nil -> | |
Logger.error("User not found for user_id: #{user_id}") | |
conn | |
|> put_status(:not_found) | |
|> json(%{error: "User not found"}) | |
user -> | |
Logger.debug("Checking user auth status for custodial wallet creation, user: #{user_id}") | |
case check_user_auth_status(conn, user) do | |
{true, _error_map, _status} -> | |
Logger.debug("Creating custodial testnet wallet for user_id: #{user_id}") | |
case LumensApp.Wallets.create_custodial_wallet("testnet", user_id) do | |
{:ok, wallet} -> | |
Logger.info("Custodial wallet created for user_id: #{user_id}, wallet_id: #{wallet.id}") | |
json(conn, %{ | |
wallet_id: wallet.id, | |
public_key: wallet.public_key, | |
network: wallet.network, | |
user_id: wallet.user_id | |
}) | |
{:error, reason} -> | |
Logger.error("Failed to create custodial wallet for user_id: #{user_id}: #{inspect(reason)}") | |
conn | |
|> put_status(:internal_server_error) | |
|> json(%{error: "Failed to create wallet: #{inspect(reason)}"}) | |
end | |
{false, error_map, status} -> | |
conn | |
|> put_status(status) | |
|> json(error_map) | |
end | |
end | |
end | |
def hydrate_wallet(conn, %{"user_id" => user_id, "wallet_id" => wallet_id}) do | |
case LumensApp.Accounts.get_user!(user_id) do | |
nil -> | |
Logger.error("User not found for user_id: #{user_id}") | |
conn | |
|> put_status(:not_found) | |
|> json(%{error: "User not found"}) | |
user -> | |
Logger.debug("Checking user auth status for wallet hydration, user: #{user_id}") | |
case check_user_auth_status(conn, user) do | |
{true, _error_map, _status} -> | |
case validate_public_key(wallet_id) do | |
{:ok, public_key} -> | |
case LumensApp.Repo.get_by(LumensApp.Wallets.Wallet, public_key: public_key, user_id: user_id, network: "testnet") do | |
nil -> | |
Logger.error("Wallet not found for public_key: #{public_key}, user_id: #{user_id}") | |
conn | |
|> put_status(:not_found) | |
|> json(%{error: "Wallet not found"}) | |
wallet -> | |
Logger.debug("Hydrating wallet with public_key #{public_key} for user_id: #{user_id}") | |
case hydrate_test_wallet(wallet) do | |
{:ok, _} -> | |
Logger.info("Wallet hydrated with 10000 USDC for wallet_id: #{wallet.id}") | |
broadcast_wallet_hydration(wallet, :success, "Wallet hydrated with 10000 USDC") | |
send_resp(conn, :no_content, "") | |
{:error, reason} -> | |
Logger.error("Failed to hydrate wallet #{wallet.id}: #{inspect(reason)}") | |
broadcast_wallet_hydration(wallet, :error, "Failed to hydrate wallet: #{inspect(reason)}") | |
conn | |
|> put_status(:internal_server_error) | |
|> json(%{error: "Failed to hydrate wallet: #{inspect(reason)}"}) | |
end | |
end | |
{:error, reason} -> | |
Logger.error("Invalid wallet_id: #{wallet_id}, reason: #{reason}") | |
conn | |
|> put_status(:bad_request) | |
|> json(%{error: "Invalid wallet_id: #{reason}"}) | |
end | |
{false, error_map, status} -> | |
conn | |
|> put_status(status) | |
|> json(error_map) | |
end | |
end | |
end | |
defp validate_public_key(wallet_id) do | |
if is_binary(wallet_id) and String.starts_with?(wallet_id, "G") and String.length(wallet_id) == 56 do | |
case StellarBase.StrKey.decode(wallet_id, :ed25519_public_key) do | |
{:ok, _decoded} -> {:ok, wallet_id} | |
{:error, reason} -> {:error, "Invalid Stellar public key: #{inspect(reason)}"} | |
end | |
else | |
{:error, "Wallet ID must be a Stellar public key starting with 'G' and 56 characters long"} | |
end | |
end | |
defp hydrate_test_wallet(wallet) do | |
alias Stellar.Network | |
alias Stellar.TxBuild.{Account, Asset, ChangeTrust} | |
require Logger | |
network_passphrase = Network.testnet_passphrase() | |
usdc_issuer = "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5" | |
# Fetch assets from Horizon | |
case Stellar.Horizon.Assets.all(Stellar.Horizon.Server.testnet(), asset_issuer: usdc_issuer) do | |
{:ok, %Stellar.Horizon.Collection{records: assets}} -> | |
horizon_usdc_asset = Enum.find(assets, fn asset -> asset.asset_code == "USDC" end) | |
if horizon_usdc_asset do | |
# Validate and normalize asset code | |
asset_code = String.trim(horizon_usdc_asset.asset_code) | |
unless asset_code =~ ~r/^[A-Za-z0-9]{1,12}$/ do | |
Logger.error("Invalid USDC asset code: #{asset_code}") | |
{:error, {:invalid_asset, "Invalid USDC asset code format"}} | |
end | |
# Validate issuer public key | |
case StellarBase.StrKey.decode(usdc_issuer, :ed25519_public_key) do | |
{:ok, _decoded_issuer} -> | |
# Create TxBuild asset | |
usdc_asset = Asset.new(code: asset_code, issuer: usdc_issuer) | |
Logger.debug("USDC asset created: #{inspect(usdc_asset, pretty: true)}") | |
try do | |
# Validate seed (decrypted by cloak_ecto) | |
unless is_binary(wallet.seed) and String.starts_with?(wallet.seed, "S") and String.length(wallet.seed) == 56 do | |
Logger.error("Invalid secret seed format for wallet #{wallet.id}") | |
throw({:invalid_seed, "Secret seed is not a valid Stellar seed"}) | |
end | |
# Validate USDC issuer account existence | |
unless issuer_exists?(usdc_issuer) do | |
Logger.error("USDC issuer account #{usdc_issuer} does not exist on testnet") | |
throw({:invalid_asset, "USDC issuer account does not exist"}) | |
end | |
# Use KeyPair for consistent key handling | |
{public_key_str, secret_seed} = KeyPair.from_secret_seed(wallet.seed) | |
Logger.debug("wallet.seed: #{inspect(wallet.seed)}") | |
Logger.debug("public_key_str: #{inspect(public_key_str)}") | |
Logger.debug("secret_seed: #{inspect(secret_seed)}") | |
# Verify public key matches | |
unless public_key_str == wallet.public_key do | |
Logger.error("Derived public key #{public_key_str} does not match wallet public_key #{wallet.public_key}") | |
throw({:public_key_mismatch, "Derived public key does not match wallet"}) | |
end | |
# Ensure account is funded | |
case ensure_account_funded(public_key_str) do | |
:ok -> | |
# Get sequence number with retry | |
case fetch_sequence_number_with_retry(public_key_str, 3, 5000) do | |
{:ok, seq} -> | |
Logger.debug("Fetched sequence number: #{seq}") | |
source_account = Account.new(public_key_str, sequence: seq) | |
Logger.debug("source_account: #{inspect(source_account, pretty: true)}") | |
# Check if trustline exists | |
unless trustline_exists?(public_key_str, horizon_usdc_asset) do | |
Logger.debug("asset_code: #{inspect(asset_code)}") | |
Logger.debug("usdc_issuer: #{inspect(usdc_issuer)}") | |
# Build trustline operation with explicit limit | |
change_trust_asset = {asset_code, usdc_issuer} | |
Logger.debug("change_trust_asset: #{inspect(change_trust_asset)}") | |
trustline_op = ChangeTrust.new(asset: change_trust_asset, limit: "10000") | |
Logger.debug("Trustline operation: #{inspect(trustline_op, pretty: true)}") | |
trustline_tx = | |
Stellar.TxBuild.new(source_account, network_passphrase: network_passphrase) | |
|> Stellar.TxBuild.add_operation(trustline_op) | |
Logger.debug("Unsigned transaction: #{inspect(trustline_tx, pretty: true)}") | |
# Sign transaction | |
signed_tx = Stellar.TxBuild.sign(trustline_tx, secret_seed) | |
Logger.debug("Signed transaction: #{inspect(signed_tx, pretty: true)}") | |
# Build envelope | |
case Stellar.TxBuild.envelope(signed_tx) do | |
{:ok, envelope_xdr} -> | |
Logger.debug("Envelope xdr: #{inspect(envelope_xdr)}") | |
case submit_transaction(envelope_xdr) do | |
{:ok, _} -> | |
Logger.info("Trustline added for USDC on wallet #{wallet.id}") | |
{:error, reason} -> | |
Logger.error("Failed to submit trustline transaction: #{inspect(reason)}") | |
throw({:trustline_failed, reason}) | |
end | |
{:error, reason} -> | |
Logger.error("Failed to build trustline envelope: #{inspect(reason)}, operation: #{inspect(trustline_op, pretty: true)}") | |
throw({:trustline_failed, reason}) | |
end | |
end | |
# Fund with USDC | |
case fund_with_usdc(public_key_str) do | |
{:ok, _} -> | |
Logger.info("Successfully funded wallet #{wallet.id} with 10000 USDC") | |
{:ok, :funded} | |
{:error, reason} -> | |
Logger.error("Failed to fund with USDC: #{inspect(reason)}") | |
throw({:funding_failed, reason}) | |
end | |
{:error, reason} -> | |
Logger.error("Failed to fetch sequence number: #{inspect(reason)}") | |
throw({:sequence_failed, reason}) | |
end | |
{:error, reason} -> | |
Logger.error("Failed to ensure account funding: #{inspect(reason)}") | |
throw({:funding_failed, reason}) | |
end | |
catch | |
{:trustline_failed, reason} -> {:error, {:trustline_failed, reason}} | |
{:funding_failed, reason} -> {:error, {:funding_failed, reason}} | |
{:sequence_failed, reason} -> {:error, {:sequence_failed, reason}} | |
{:public_key_mismatch, reason} -> {:error, {:public_key_mismatch, reason}} | |
{:invalid_seed, reason} -> {:error, {:invalid_seed, reason}} | |
{:invalid_asset, reason} -> {:error, {:invalid_asset, reason}} | |
end | |
{:error, reason} -> | |
Logger.error("Invalid USDC issuer public key: #{usdc_issuer}, reason: #{inspect(reason)}") | |
{:error, {:invalid_asset, "Invalid USDC issuer public key"}} | |
end | |
else | |
Logger.error("USDC asset not found for issuer #{usdc_issuer}") | |
{:error, {:invalid_asset, "USDC asset not found for issuer"}} | |
end | |
{:error, reason} -> | |
Logger.error("Failed to fetch assets for issuer #{usdc_issuer}: #{inspect(reason)}") | |
{:error, {:invalid_asset, "Failed to fetch assets"}} | |
end | |
end | |
defp fetch_sequence_number_with_retry(public_key, retries, delay) do | |
server = Stellar.Horizon.Server.testnet() | |
case Stellar.Horizon.Accounts.retrieve(server, public_key) do | |
{:ok, %Stellar.Horizon.Account{sequence: sequence}} -> | |
{:ok, sequence} | |
{:error, reason} -> | |
if retries > 0 do | |
Logger.debug("Retrying sequence number fetch, retries left: #{retries}, reason: #{inspect(reason)}") | |
:timer.sleep(delay) | |
fetch_sequence_number_with_retry(public_key, retries - 1, delay) | |
else | |
Logger.error("Failed to fetch sequence number for #{public_key}: #{inspect(reason)}") | |
{:error, reason} | |
end | |
end | |
end | |
defp issuer_exists?(issuer_public_key) do | |
server = Stellar.Horizon.Server.testnet() | |
case Stellar.Horizon.Accounts.retrieve(server, issuer_public_key) do | |
{:ok, _account} -> true | |
{:error, _reason} -> false | |
end | |
end | |
defp ensure_account_funded(public_key) do | |
server = Stellar.Horizon.Server.testnet() | |
case Stellar.Horizon.Accounts.retrieve(server, public_key) do | |
{:ok, _account} -> | |
:ok | |
{:error, %{status: 404}} -> | |
Logger.info("Account #{public_key} not funded, attempting to fund via Friendbot") | |
case fund_with_friendbot(public_key) do | |
{:ok, _} -> :ok | |
{:error, reason} -> {:error, {:friendbot_failed, reason}} | |
end | |
{:error, reason} -> | |
Logger.error("Failed to check account status for #{public_key}: #{inspect(reason)}") | |
{:error, reason} | |
end | |
end | |
defp fund_with_friendbot(public_key) do | |
case HTTPoison.get("https://friendbot.stellar.org/?addr=#{public_key}") do | |
{:ok, %HTTPoison.Response{status_code: 200}} -> | |
Logger.info("Successfully funded account #{public_key} with Friendbot") | |
{:ok, :funded} | |
{:ok, %HTTPoison.Response{status_code: code, body: body}} -> | |
Logger.error("Friendbot funding failed: #{code}, #{body}") | |
{:error, {:friendbot_failed, body}} | |
{:error, reason} -> | |
Logger.error("Friendbot request error: #{inspect(reason)}") | |
{:error, {:friendbot_failed, reason}} | |
end | |
end | |
defp trustline_exists?(public_key, horizon_usdc_asset) do | |
server = Stellar.Horizon.Server.testnet() | |
case Stellar.Horizon.Accounts.retrieve(server, public_key) do | |
{:ok, %Stellar.Horizon.Account{balances: balances}} -> | |
Enum.any?(balances, fn balance -> | |
balance.asset_code == horizon_usdc_asset.asset_code && balance.asset_issuer == horizon_usdc_asset.asset_issuer | |
end) | |
{:error, reason} -> | |
Logger.error("Failed to fetch account balances for #{public_key}: #{inspect(reason)}") | |
false | |
end | |
end | |
defp fund_with_usdc(public_key) do | |
case HTTPoison.post( | |
"https://faucet.circle.com/v1/faucet/stellar", | |
Jason.encode!(%{"address": public_key, "amount": 10000, "asset": "USDC"}), | |
[{"Content-Type", "application/json"}] | |
) do | |
{:ok, %HTTPoison.Response{status_code: 200}} -> | |
{:ok, :funded} | |
{:ok, %HTTPoison.Response{status_code: code, body: body}} -> | |
Logger.error("Faucet request failed: #{code}, #{body}") | |
{:error, {:faucet_failed, body}} | |
{:error, reason} -> | |
{:error, {:faucet_failed, reason}} | |
end | |
end | |
defp submit_transaction(envelope_xdr) do | |
case Stellar.Horizon.Transactions.create(envelope_xdr, network: :testnet) do | |
{:ok, response} -> | |
{:ok, response} | |
{:error, reason} -> | |
{:error, {:submission_failed, reason}} | |
end | |
end | |
defp broadcast_wallet_hydration(wallet, status, message) do | |
payload = %{ | |
wallet_id: wallet.id, | |
public_key: wallet.public_key, | |
network: wallet.network, | |
user_id: wallet.user_id, | |
status: status, | |
message: message | |
} | |
LumensAppWeb.Endpoint.broadcast("wallet:#{wallet.user_id}", "wallet_hydrated", payload) | |
end | |
defp check_user_auth_status(_conn, user) do | |
cond do | |
is_nil(user.confirmed_at) -> | |
error_map = %{ | |
errors: [ | |
%{ | |
status: "403", | |
title: "Forbidden", | |
detail: gettext("The user is not confirmed.") | |
} | |
] | |
} | |
{false, error_map, 403} | |
user.is_suspended -> | |
error_map = %{ | |
errors: [ | |
%{ | |
status: "403", | |
title: "Forbidden", | |
detail: gettext("The user is suspended.") | |
} | |
] | |
} | |
{false, error_map, 403} | |
user.is_deleted -> | |
error_map = %{ | |
errors: [ | |
%{ | |
status: "403", | |
title: "Forbidden", | |
detail: gettext("The user is deleted.") | |
} | |
] | |
} | |
{false, error_map, 403} | |
true -> | |
{true, %{}, 200} | |
end | |
end | |
end | |
logging: | |
[debug] Hydrating wallet with public_key GCSU2BSNO64ZBSDEJR6OXRWIGH7P5E5MCJB5XMBNB477P3TTRU7ZN4SN for user_id: 1f0414d7-48e4-6776-ab90-361e354e2fed | |
[debug] USDC asset created: %Stellar.TxBuild.Asset{ | |
code: "USDC", | |
issuer: %Stellar.TxBuild.AccountID{ | |
account_id: "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5" | |
}, | |
type: :alpha_num4 | |
} | |
[debug] wallet.seed: "SCCJTDGE5VUDGG3C66IH76V6BTJMKMY6W6E37WPJD4HTPB3AYGVTHVYP" | |
[debug] public_key_str: "GCSU2BSNO64ZBSDEJR6OXRWIGH7P5E5MCJB5XMBNB477P3TTRU7ZN4SN" | |
[debug] secret_seed: "SCCJTDGE5VUDGG3C66IH76V6BTJMKMY6W6E37WPJD4HTPB3AYGVTHVYP" | |
[debug] Fetched sequence number: 5699305637675008 | |
[debug] source_account: %Stellar.TxBuild.Account{ | |
address: "GCSU2BSNO64ZBSDEJR6OXRWIGH7P5E5MCJB5XMBNB477P3TTRU7ZN4SN", | |
account_id: "GCSU2BSNO64ZBSDEJR6OXRWIGH7P5E5MCJB5XMBNB477P3TTRU7ZN4SN", | |
muxed_id: nil, | |
type: :ed25519_public_key | |
} | |
[debug] asset_code: "USDC" | |
[debug] usdc_issuer: "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5" | |
[debug] change_trust_asset: {"USDC", "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"} | |
[debug] Trustline operation: %Stellar.TxBuild.ChangeTrust{ | |
asset: %Stellar.TxBuild.Asset{ | |
code: "USDC", | |
issuer: %Stellar.TxBuild.AccountID{ | |
account_id: "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5" | |
}, | |
type: :alpha_num4 | |
}, | |
amount: %Stellar.TxBuild.Amount{ | |
amount: 922337203685.4775, | |
raw: 9223372036854775807 | |
}, | |
source_account: %Stellar.TxBuild.OptionalAccount{account_id: nil} | |
} | |
[debug] Unsigned transaction: {:ok, | |
%Stellar.TxBuild{ | |
tx: %Stellar.TxBuild.Transaction{ | |
source_account: %Stellar.TxBuild.Account{ | |
address: "GCSU2BSNO64ZBSDEJR6OXRWIGH7P5E5MCJB5XMBNB477P3TTRU7ZN4SN", | |
account_id: "GCSU2BSNO64ZBSDEJR6OXRWIGH7P5E5MCJB5XMBNB477P3TTRU7ZN4SN", | |
muxed_id: nil, | |
type: :ed25519_public_key | |
}, | |
sequence_number: %Stellar.TxBuild.SequenceNumber{sequence_number: 0}, | |
base_fee: %Stellar.TxBuild.BaseFee{fee: 100, multiplier: 1}, | |
memo: %Stellar.TxBuild.Memo{type: :MEMO_NONE, value: nil}, | |
preconditions: %Stellar.TxBuild.Preconditions{ | |
type: :none, | |
preconditions: nil | |
}, | |
operations: %Stellar.TxBuild.Operations{ | |
operations: [ | |
%Stellar.TxBuild.Operation{ | |
body: %Stellar.TxBuild.ChangeTrust{ | |
asset: %Stellar.TxBuild.Asset{ | |
code: "USDC", | |
issuer: %Stellar.TxBuild.AccountID{ | |
account_id: "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5" | |
}, | |
type: :alpha_num4 | |
}, | |
amount: %Stellar.TxBuild.Amount{ | |
amount: 922337203685.4775, | |
raw: 9223372036854775807 | |
}, | |
source_account: %Stellar.TxBuild.OptionalAccount{account_id: nil} | |
}, | |
source_account: %Stellar.TxBuild.OptionalAccount{account_id: nil} | |
} | |
], | |
count: 1 | |
}, | |
ext: %StellarBase.XDR.TransactionExt{ | |
value: %StellarBase.XDR.Void{value: nil}, | |
type: 0 | |
} | |
}, | |
signatures: [], | |
tx_envelope: nil, | |
network_passphrase: "Test SDF Network ; September 2015" | |
}} | |
[debug] Signed transaction: {:error, :invalid_signature} | |
[error] Failed to build trustline envelope: :invalid_signature, operation: %Stellar.TxBuild.ChangeTrust{ | |
asset: %Stellar.TxBuild.Asset{ | |
code: "USDC", | |
issuer: %Stellar.TxBuild.AccountID{ | |
account_id: "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5" | |
}, | |
type: :alpha_num4 | |
}, | |
amount: %Stellar.TxBuild.Amount{ | |
amount: 922337203685.4775, | |
raw: 9223372036854775807 | |
}, | |
source_account: %Stellar.TxBuild.OptionalAccount{account_id: nil} | |
} | |
[error] Failed to hydrate wallet 1f0414dc-a6ad-6d12-b166-fcf347eb4379: {:trustline_failed, :invalid_signature} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment