diff --git a/CHANGELOG.md b/CHANGELOG.md index c863d2ba..81398186 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.4.0 (11.03.2022) +* Compose functional Horizon requests. +* Build structures for Horizon resources. +* Build structures for all the operation types. +* Implement functions to consume Horizon endpoints. +* Querying Horizon docs. + ## 0.3.0 (11.02.2022) * CAP-0027 Add suport for multiplexed accounts. diff --git a/README.md b/README.md index b25d5e24..2ff927e8 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,8 @@ This library is aimed at developers building Elixir applications that interact with the [**Stellar network**][stellar]. ## Documentation -* [**API Reference**][api-reference] +* [**Building transactions**](#building-transactions) +* [**Querying Horizon**](#querying-horizon) * [**Examples**](/docs/examples.md) ## Installation @@ -19,7 +20,7 @@ This library is aimed at developers building Elixir applications that interact w ```elixir def deps do [ - {:stellar_sdk, "~> 0.3.0"} + {:stellar_sdk, "~> 0.4.0"} ] end ``` @@ -42,7 +43,7 @@ Stellar allows you to use your HTTP client of choice. Specification in [**Stella config :stellar_sdk, :http_client_impl, YourApp.CustomClientImpl ``` -## Usage +## Building transactions ### Accounts Accounts are the central data structure in Stellar. They hold balances, sign transactions, and issue assets. All entries that persist on the ledger are owned by a particular account. @@ -348,9 +349,282 @@ base64_envelope = # submit transaction to Horizon {:ok, submitted_tx} = Stellar.Horizon.Transactions.create(base64_envelope) ``` - More examples can be found in the [**tests**][sdk-tests]. +## Querying Horizon +Horizon is an API for interacting with the Stellar network. + +To make it possible to explore the millions of records for resources like transactions and operations, this library paginates the data it returns for collection-based resources. Each individual transaction, operation, ledger, etc. is returned as a record. + +```elixir +{:ok, + %Ledger{ + base_fee_in_stroops: 100, + closed_at: ~U[2015-09-30 17:16:29Z], + failed_transaction_count: 0, + fee_pool: 3.0e-5, + ... + }} = Ledgers.retrieve(1234) +``` + +A group of records is called a **collection**, records are returned as a list in the [**Stellar.Horizon.Collection**](https://hexdocs.pm/stellar_sdk/Stellar.Horizon.Collection.html#content) structure. To move between pages of a collection of records, use the `next` and `prev` attributes. + + +```elixir +{:ok, + %Stellar.Horizon.Collection{ + next: + "https://horizon.stellar.org/ledgers?cursor=33736968114176\u0026limit=3\u0026order=asc", + prev: + "https://horizon.stellar.org/ledgers?cursor=12884905984\u0026limit=3\u0026order=desc", + records: [ + %Stellar.Horizon.Ledger{...}, + %Stellar.Horizon.Ledger{...}, + %Stellar.Horizon.Ledger{...} + ] + }} = Stellar.Horizon.Ledgers.all() +``` + +### Accounts +```elixir +# retrieve an account +Stellar.Horizon.Accounts.retrieve("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") + +# fetch the ledger's sequence number for the account +Stellar.Horizon.Accounts.fetch_next_sequence_number("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") + +# list accounts +Stellar.Horizon.Accounts.all(limit: 10, order: :asc) + +# list accounts by sponsor +Stellar.Horizon.Accounts.all(sponsor: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") + +# list accounts by signer +Stellar.Horizon.Accounts.all(signer: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", order: :desc) + +# list accounts by canonical asset address +Stellar.Horizon.Accounts.all(asset: "TEST:GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", limit: 20) + +# list account's transactions +Stellar.Horizon.Accounts.list_transactions("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", limit: 20) + +# list account's payments +Stellar.Horizon.Accounts.list_payments("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", limit: 20) +``` + +See [**Stellar.Horizon.Accounts**](https://hexdocs.pm/stellar_sdk/Stellar.Horizon.Accounts.html#content) for more details. + +### Transactions +```elixir +# submit a transaction +Stellar.Horizon.Transactions.create(base64_tx_envelope) + +# retrieve a transaction +Stellar.Horizon.Transactions.retrieve("5ebd5c0af4385500b53dd63b0ef5f6e8feef1a7e1c86989be3cdcce825f3c0cc") + +# list transactions +Stellar.Horizon.Transactions.all(limit: 10, order: :asc) + +# include failed transactions +Stellar.Horizon.Transactions.all(limit: 10, include_failed: true) + +# list transaction's effects +Stellar.Horizon.Transactions.list_effects("6b983a4e0dc3c04f4bd6b9037c55f70a09c434dfd01492be1077cf7ea68c2e4a", limit: 20) + +# list transaction's operations +Stellar.Horizon.Transactions.list_operations("6b983a4e0dc3c04f4bd6b9037c55f70a09c434dfd01492be1077cf7ea68c2e4a", limit: 20) + +# join transactions in the operations response +Stellar.Horizon.Transactions.list_operations("6b983a4e0dc3c04f4bd6b9037c55f70a09c434dfd01492be1077cf7ea68c2e4a", join: "transactions") +``` + +See [**Stellar.Horizon.Transactions**](https://hexdocs.pm/stellar_sdk/Stellar.Horizon.Transactions.html#content) for more details. + + +### Operations +```elixir +# retrieve an operation +Stellar.Horizon.Operations.retrieve(121693057904021505) + +# list operations +Stellar.Horizon.Operations.all(limit: 10, order: :asc) + +# include failed operations +Stellar.Horizon.Operations.all(limit: 10, include_failed: true) + +# include operation's transactions +Stellar.Horizon.Operations.all(limit: 10, join: "transactions") + +# list operation's payments +Stellar.Horizon.Operations.list_payments(limit: 20) + +# list operation's effects +Stellar.Horizon.Operations.list_effects(121693057904021505, limit: 20) +``` + +See [**Stellar.Horizon.Operations**](https://hexdocs.pm/stellar_sdk/Stellar.Horizon.Operations.html#content) for more details. + + +### Assets +```elixir +# list ledger's assets +Stellar.Horizon.Assets.all(limit: 20, order: :desc) + +# list assets by asset issuer +Stellar.Horizon.Assets.all(asset_issuer: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") + +# list assets by asset code +Stellar.Horizon.Assets.list_by_asset_code("TEST") +Stellar.Horizon.Assets.all(asset_code: "TEST") + +# list assets by asset issuer +Stellar.Horizon.Assets.all(asset_issuer: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") +Stellar.Horizon.Assets.list_by_asset_issuer("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") +``` + +See [**Stellar.Horizon.Assets**](https://hexdocs.pm/stellar_sdk/Stellar.Horizon.Assets.html#content) for more details. + + +### Ledgers +```elixir +# retrieve a ledger +Stellar.Horizon.Ledgers.retrieve(27147222) + +# list ledgers +Stellar.Horizon.Ledgers.all(limit: 10, order: :asc) + +# list ledger's transactions +Stellar.Horizon.Ledgers.list_transactions(27147222, limit: 20) + +# list ledger's operations +Stellar.Horizon.Ledgers.list_operations(27147222, join: "transactions") + +# list ledger's payments including failed transactions +Stellar.Horizon.Ledgers.list_payments(27147222, include_failed: true) + +# list ledger's effects +Stellar.Horizon.Ledgers.list_effects(27147222, limit: 20) +``` + +See [**Stellar.Horizon.Ledgers**](https://hexdocs.pm/stellar_sdk/Stellar.Horizon.Ledgers.html#content) for more details. + + +### Offers +```elixir +# list offers +Stellar.Horizon.Offers.all(limit: 20, order: :asc) + +# list offers by sponsor +Stellar.Horizon.Offers.all(sponsor: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") + +# list offers by seller +Stellar.Horizon.Offers.all(seller: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", order: :desc) + +# list offers by selling_asset_issuer +Stellar.Horizon.Offers.all(selling_asset_issuer: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", limit: 20) + +# list offers by buying_asset_type and buying_asset_code +Stellar.Horizon.Offers.all(buying_asset_type: "credit_alphanum4", buying_asset_code: "TEST") + +# list offer's trades +Stellar.Horizon.Offers.list_trades(165563085, limit: 20) +``` + +See [**Stellar.Horizon.Offers**](https://hexdocs.pm/stellar_sdk/Stellar.Horizon.Offers.html#content) for more details. + + +### Trades +```elixir +# list tardes +Stellar.Horizon.Trades.all(limit: 20, order: :asc) + +# list trades by offer_id +Stellar.Horizon.Trades.all(offer_id: 165563085) + +# list trades by base_asset_type and base_asset_code +Stellar.Horizon.Trades.all(base_asset_type: "credit_alphanum4", base_asset_code: "TEST") + +# list trades by counter_asset_issuer +Stellar.Horizon.Trades.all(counter_asset_issuer: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", limit: 20) + +# list trades by trade_type +Stellar.Horizon.Trades.all(trade_type: "liquidity_pools", limit: 20) +``` + +See [**Stellar.Horizon.Trades**](https://hexdocs.pm/stellar_sdk/Stellar.Horizon.Trades.html#content) for more details. + + +### ClaimableBalances +```elixir +# retrieve a claimable balance +Stellar.Horizon.ClaimableBalances.retrieve("00000000ca6aba5fb0993844e0076f75bee53f2b8014be29cd8f2e6ae19fb0a17fc68695") + +# list claimable balances +Stellar.Horizon.ClaimableBalances.all(limit: 2, order: :asc) + +# list claimable balances by sponsor +Stellar.Horizon.ClaimableBalances.list_by_sponsor("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") +Stellar.Horizon.ClaimableBalances.all(sponsor: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") + +# list claimable balances by claimant +Stellar.Horizon.ClaimableBalances.list_by_claimant("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") +Stellar.Horizon.ClaimableBalances.all(claimant: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", order: :desc) + +# list claimable balances by canonical asset address +Stellar.Horizon.ClaimableBalances.list_by_asset("TEST:GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") +Stellar.Horizon.ClaimableBalances.all(asset: "TEST:GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", limit: 20) + +# list claimable balance's transactions +Stellar.Horizon.ClaimableBalances.list_transactions("00000000ca6aba5fb0993844e0076f75bee53f2b8014be29cd8f2e6ae19fb0a17fc68695", limit: 20) + +# list claimable balance's operations +Stellar.Horizon.ClaimableBalances.list_operations("00000000ca6aba5fb0993844e0076f75bee53f2b8014be29cd8f2e6ae19fb0a17fc68695", limit: 20) + +# join transactions in the operations response +Stellar.Horizon.ClaimableBalances.list_operations("00000000ca6aba5fb0993844e0076f75bee53f2b8014be29cd8f2e6ae19fb0a17fc68695", join: "transactions") +``` + +See [**Stellar.Horizon.Balances**](https://hexdocs.pm/stellar_sdk/Stellar.Horizon.ClaimableBalances.html#content) for more details. + + +### LiquidityPools +```elixir +# retrieve a liquidity pool +Stellar.Horizon.LiquidityPools.retrieve("001365fc79ca661f31ba3ee0849ae4ba36f5c377243242d37fad5b1bb8912dbc") + +# list liquidity pools +Stellar.Horizon.LiquidityPools.all(limit: 2, order: :asc) + +# list liquidity pools by reserves +Stellar.Horizon.LiquidityPools.all(reserves: "TEST:GCXMW..., TEST2:GCXMW...") + +# list liquidity pools by account +Stellar.Horizon.LiquidityPools.all(account: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", order: :desc) + +# list liquidity pool's effects +Stellar.Horizon.LiquidityPools.list_effects("001365fc79ca661f31ba3ee0849ae4ba36f5c377243242d37fad5b1bb8912dbc", limit: 20) + +# list liquidity pool's trades +Stellar.Horizon.LiquidityPools.list_trades("001365fc79ca661f31ba3ee0849ae4ba36f5c377243242d37fad5b1bb8912dbc", limit: 20) + +# list liquidity pool's transactions +Stellar.Horizon.LiquidityPools.list_transactions("001365fc79ca661f31ba3ee0849ae4ba36f5c377243242d37fad5b1bb8912dbc", limit: 20) + +# list liquidity pool's operations +Stellar.Horizon.LiquidityPools.list_operations("001365fc79ca661f31ba3ee0849ae4ba36f5c377243242d37fad5b1bb8912dbc", limit: 20) +``` + +See [**Stellar.Horizon.Pools**](https://hexdocs.pm/stellar_sdk/Stellar.Horizon.LiquidityPools.html#content) for more details. + + +### Effects +```elixir +# list effects +Stellar.Horizon.Effects.all(limit: 10, order: :asc) +``` + +See [**Stellar.Horizon.Effects**](https://hexdocs.pm/stellar_sdk/Stellar.Horizon.Effects.html#content) for more details. + --- ## Development @@ -383,7 +657,6 @@ Made with 💙 by [kommitters Open Source](https://kommit.co) [hex]: https://hex.pm/packages/stellar_sdk [stellar]: https://www.stellar.org/ [http_client_spec]: https://github.com/kommitters/stellar_sdk/blob/main/lib/horizon/client/spec.ex -[api-reference]: https://hexdocs.pm/stellar_sdk/api-reference.html#content [stellar-docs-tx]: https://developers.stellar.org/docs/glossary/transactions [stellar-docs-sequence-number]: https://developers.stellar.org/docs/glossary/transactions/#sequence-number [stellar-docs-fee]: https://developers.stellar.org/docs/glossary/transactions/#fee diff --git a/lib/horizon/account/account.ex b/lib/horizon/account.ex similarity index 86% rename from lib/horizon/account/account.ex rename to lib/horizon/account.ex index 089a0627..6084780e 100644 --- a/lib/horizon/account/account.ex +++ b/lib/horizon/account.ex @@ -6,7 +6,7 @@ defmodule Stellar.Horizon.Account do @behaviour Stellar.Horizon.Resource alias Stellar.Horizon.Mapping - alias Stellar.Horizon.Account.{Balance, Signer, Thresholds, Flags} + alias Stellar.Horizon.Account.{Balance, Flags, Signer, Thresholds} @type t :: %__MODULE__{ id: String.t(), @@ -49,10 +49,10 @@ defmodule Stellar.Horizon.Account do @mapping [ sequence: :integer, last_modified_time: :date_time, - thresholds: %Thresholds{}, - flags: %Flags{}, - balances: %Balance{}, - signers: %Signer{} + thresholds: {:struct, Thresholds}, + flags: {:struct, Flags}, + balances: {:list, :struct, Balance}, + signers: {:list, :struct, Signer} ] @impl true diff --git a/lib/horizon/account/balance.ex b/lib/horizon/account/balance.ex index 36efbe9a..4ef60ede 100644 --- a/lib/horizon/account/balance.ex +++ b/lib/horizon/account/balance.ex @@ -8,7 +8,7 @@ defmodule Stellar.Horizon.Account.Balance do alias Stellar.Horizon.Mapping @type t :: %__MODULE__{ - balance: non_neg_integer(), + balance: float(), asset_type: String.t(), buying_liabilities: float(), selling_liabilities: float(), diff --git a/lib/horizon/account/data.ex b/lib/horizon/account/data.ex new file mode 100644 index 00000000..ce5d1b28 --- /dev/null +++ b/lib/horizon/account/data.ex @@ -0,0 +1,22 @@ +defmodule Stellar.Horizon.Account.Data do + @moduledoc """ + Represents `Data` for an account. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{value: String.t()} + + defstruct [:value] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse([]) + end +end diff --git a/lib/horizon/accounts.ex b/lib/horizon/accounts.ex index 7e9da0c4..059520ec 100644 --- a/lib/horizon/accounts.ex +++ b/lib/horizon/accounts.ex @@ -3,29 +3,71 @@ defmodule Stellar.Horizon.Accounts do Exposes functions to interact with Accounts in Horizon. You can: - - Retrieve an account. - - Fetch next account sequence number. + * Retrieve an account. + * Fetch an account next sequence number. + * List all accounts. + * List an account's effects. + * List an account's offers. + * List an account's trades. + * List an account's transactions. + * List an account's operations. + * List an account's payments. + * List an account's data. Horizon API reference: https://developers.stellar.org/api/resources/accounts/ """ - alias Stellar.Horizon.{Account, Error} - alias Stellar.Horizon.Client, as: Horizon + alias Stellar.Horizon.{ + Account, + Account.Data, + Collection, + Effect, + Error, + Offer, + Operation, + Request, + Trade, + Transaction + } @type account_id :: String.t() - @type resource :: Account.t() | non_neg_integer() + @type options :: Keyword.t() + @type resource :: Account.t() | Collection.t() @type response :: {:ok, resource()} | {:error, Error.t()} - @endpoint "/accounts/" + @endpoint "accounts" + @doc """ + Retrieves information of a specific account. + + ## Parameters: + * `account_id`: The account’s public key encoded in a base32 string representation. + + ## Examples + + iex> Accounts.retrieve("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") + {:ok, %Account{}} + """ @spec retrieve(account_id :: account_id()) :: response() def retrieve(account_id) do - case Horizon.request(:get, @endpoint <> account_id) do - {:ok, account} -> {:ok, Account.new(account)} - error -> error - end + :get + |> Request.new(@endpoint, path: account_id) + |> Request.perform() + |> Request.results(&Account.new(&1)) end + @doc """ + Fetches the ledger's sequence number for the given account. + + ## Parameters: + + * `account_id`: The account’s public key encoded in a base32 string representation. + + ## Examples + + iex> Accounts.fetch_next_sequence_number("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") + {:ok, 17218523889687} + """ @spec fetch_next_sequence_number(account_id :: account_id()) :: response() def fetch_next_sequence_number(account_id) do case retrieve(account_id) do @@ -33,4 +75,225 @@ defmodule Stellar.Horizon.Accounts do error -> error end end + + @doc """ + Lists all accounts or by one of these three filters: `signer`, `asset`, or `sponsor`. + + ## Options + + * `signer`: Account ID of the signer. + * `asset`: An issued asset represented as “Code:IssuerAccountID”. + * `sponsor`: Account ID of the sponsor. + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> Accounts.all(limit: 2, order: :asc) + {:ok, %Collection{records: [%Account{}, ...]}} + + # list by sponsor + iex> Accounts.all(sponsor: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") + {:ok, %Collection{records: [%Account{}, ...]}} + + # list by signer + iex> Accounts.all(signer: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", order: :desc) + {:ok, %Collection{records: [%Account{}, ...]}} + + # list by canonical asset address + iex> Accounts.all(asset: "TEST:GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", limit: 20) + {:ok, %Collection{records: [%Account{}, ...]}} + """ + @spec all(options :: options()) :: response() + def all(options \\ []) do + :get + |> Request.new(@endpoint) + |> Request.add_query(options, extra_params: [:sponsor, :asset, :signer, :liquidity_pool]) + |> Request.perform() + |> Request.results(&Collection.new({Account, &1})) + end + + @doc """ + Lists successful transactions for a given account. + + ## Parameters + * `account_id`: The account’s public key encoded in a base32 string representation. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + * `include_failed`: Set to true to include failed operations in results. + + ## Examples + + iex> Accounts.list_transactions("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", limit: 20) + {:ok, %Collection{records: [%Transaction{}, ...]}} + """ + @spec list_transactions(account_id :: account_id(), options :: options()) :: response() + def list_transactions(account_id, options \\ []) do + :get + |> Request.new(@endpoint, path: account_id, segment: "transactions") + |> Request.add_query(options, extra_params: [:include_failed]) + |> Request.perform() + |> Request.results(&Collection.new({Transaction, &1})) + end + + @doc """ + Lists successful operations for a given account. + + ## Parameters + * `account_id`: The account’s public key encoded in a base32 string representation. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + * `include_failed`: Set to true to include failed operations in results. + * `join`: Set to `transactions` to include the transactions which created each of the operations in the response. + + ## Examples + + iex> Accounts.list_transactions("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", limit: 20) + {:ok, %Collection{records: [%Operation{}, ...]}} + + # join transactions + iex> Accounts.list_transactions("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", join: "transactions") + {:ok, %Collection{records: [%Operation{transaction: %Transaction{}}, ...]}} + """ + @spec list_operations(account_id :: account_id(), options :: options()) :: response() + def list_operations(account_id, options \\ []) do + :get + |> Request.new(@endpoint, path: account_id, segment: "operations") + |> Request.add_query(options, extra_params: [:include_failed, :join]) + |> Request.perform() + |> Request.results(&Collection.new({Operation, &1})) + end + + @doc """ + Lists successful payments for a given account. + + ## Parameters + * `account_id`: The account’s public key encoded in a base32 string representation. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + * `include_failed`: Set to true to include failed operations in results. + * `join`: Set to `transactions` to include the transactions which created each of the operations in the response. + + ## Examples + + iex> Accounts.list_payments("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", limit: 20) + {:ok, %Collection{records: [%Operation{body: %Payment{}}, ...]}} + + # include failed + iex> Accounts.list_payments("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", include_failed: true) + {:ok, %Collection{records: [%Operation{body: %Payment{}}, ...]}} + """ + @spec list_payments(account_id :: account_id(), options :: options()) :: response() + def list_payments(account_id, options \\ []) do + :get + |> Request.new(@endpoint, path: account_id, segment: "payments") + |> Request.add_query(options, extra_params: [:include_failed, :join]) + |> Request.perform() + |> Request.results(&Collection.new({Operation, &1})) + end + + @doc """ + Lists the effects of a specific account. + + ## Parameters + * `account_id`: The account’s public key encoded in a base32 string representation. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> Accounts.list_effects("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", limit: 20) + {:ok, %Collection{records: [%Effect{}, ...]}} + """ + @spec list_effects(account_id :: account_id(), options :: options()) :: response() + def list_effects(account_id, options \\ []) do + :get + |> Request.new(@endpoint, path: account_id, segment: "effects") + |> Request.add_query(options) + |> Request.perform() + |> Request.results(&Collection.new({Effect, &1})) + end + + @doc """ + Lists all offers a given account has currently open. + + ## Parameters + * `account_id`: The account’s public key encoded in a base32 string representation. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> Accounts.list_offers("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", limit: 20) + {:ok, %Collection{records: [%Offer{}, ...]}} + """ + @spec list_offers(account_id :: account_id(), options :: options()) :: response() + def list_offers(account_id, options \\ []) do + :get + |> Request.new(@endpoint, path: account_id, segment: "offers") + |> Request.add_query(options) + |> Request.perform() + |> Request.results(&Collection.new({Offer, &1})) + end + + @doc """ + Lists all trades for a given account. + + ## Parameters + * `account_id`: The account’s public key encoded in a base32 string representation. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> Accounts.list_trades("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", limit: 20) + {:ok, %Collection{records: [%Trade{}, ...]}} + """ + @spec list_trades(account_id :: account_id(), options :: options()) :: response() + def list_trades(account_id, options \\ []) do + :get + |> Request.new(@endpoint, path: account_id, segment: "trades") + |> Request.add_query(options) + |> Request.perform() + |> Request.results(&Collection.new({Trade, &1})) + end + + @doc """ + Retrieves a single data for a given account. + + ## Parameters + * `account_id`: The account’s public key encoded in a base32 string representation. + * `key`: The key name for this data. + + ## Examples + + iex> Accounts.data("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", "config.memo_required") + {:ok, %Account.Data{}} + """ + @spec data(account_id :: account_id(), key :: String.t()) :: response() + def data(account_id, key) do + :get + |> Request.new(@endpoint, path: account_id, segment: "data", segment_path: key) + |> Request.perform() + |> Request.results(&Data.new(&1)) + end end diff --git a/lib/horizon/asset.ex b/lib/horizon/asset.ex new file mode 100644 index 00000000..2aec58ac --- /dev/null +++ b/lib/horizon/asset.ex @@ -0,0 +1,60 @@ +defmodule Stellar.Horizon.Asset do + @moduledoc """ + Represents a `Asset` resource from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + paging_token: String.t(), + asset_type: String.t(), + asset_code: String.t(), + asset_issuer: String.t(), + accounts: map(), + num_claimable_balances: non_neg_integer(), + balances: map(), + claimable_balances_amount: float(), + amount: float(), + num_accounts: non_neg_integer(), + flags: map() + } + + defstruct [ + :paging_token, + :asset_type, + :asset_code, + :asset_issuer, + :accounts, + :num_claimable_balances, + :balances, + :claimable_balances_amount, + :amount, + :num_accounts, + :flags + ] + + @balances_mapping [ + authorized: :float, + authorized_to_maintain_liabilities: :float, + unauthorized: :float + ] + + @mapping [ + num_claimable_balances: :integer, + claimable_balances_amount: :float, + amount: :float, + num_accounts: :integer, + balances: {:map, @balances_mapping} + ] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/assets.ex b/lib/horizon/assets.ex new file mode 100644 index 00000000..dd86a36e --- /dev/null +++ b/lib/horizon/assets.ex @@ -0,0 +1,105 @@ +defmodule Stellar.Horizon.Assets do + @moduledoc """ + Exposes functions to interact with Assets in Horizon. + + You can: + * List all assets. + * List assets by asset_code. + * List assets by asset_issuer. + + Horizon API reference: https://developers.stellar.org/api/resources/assets/ + """ + + alias Stellar.Horizon.{Asset, Collection, Error, Request} + + @type asset_code :: String.t() + @type asset_issuer :: String.t() + @type options :: Keyword.t() + @type resource :: Asset.t() | Collection.t() + @type response :: {:ok, resource()} | {:error, Error.t()} + + @endpoint "assets" + + @doc """ + Lists all assets or by one of these filters: `asset_code` or `asset_issuer`. + + ## Options + + * `asset_code`: The code of the asset you would like to filter by. + * `asset_issuer`: The issuer's Account ID for the asset you would like to filter by. + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> Assets.all(limit: 20, order: :desc) + {:ok, %Collection{records: [%Asset{}, ...]}} + + # list by asset_code + iex> Assets.all(asset_code: "TEST") + {:ok, %Collection{records: [%Asset{}, ...]}} + + # list by asset_issuer + iex> Assets.all(asset_issuer: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") + {:ok, %Collection{records: [%Asset{}, ...]}} + """ + @spec all(options :: options()) :: response() + def all(options \\ []) do + :get + |> Request.new(@endpoint) + |> Request.add_query(options, extra_params: [:asset_code, :asset_issuer]) + |> Request.perform() + |> Request.results(&Collection.new({Asset, &1})) + end + + @doc """ + Lists assets matching the given asset code. + + ## Parameters: + + * `asset_code`: The code of the asset you would like to filter by. + + ## Options + + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> Assets.list_by_asset_code("TEST") + {:ok, %Collection{records: [%Asset{asset_code: "TEST"}, ...]}} + """ + @spec list_by_asset_code(asset_code :: asset_code(), options :: options()) :: response() + def list_by_asset_code(asset_code, options \\ []) do + options + |> Keyword.put(:asset_code, asset_code) + |> all() + end + + @doc """ + Lists assets matching the given asset issuer. + + ## Parameters: + + * `asset_issuer`: The issuer's Account ID for the asset you would like to filter by. + + ## Options + + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> Assets.list_by_asset_issuer("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") + {:ok, %Collection{records: [%Asset{asset_issuer: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD"}, ...]}} + """ + @spec list_by_asset_issuer(asset_issuer :: asset_issuer(), options :: options()) :: response() + def list_by_asset_issuer(asset_issuer, options \\ []) do + options + |> Keyword.put(:asset_issuer, asset_issuer) + |> all() + end +end diff --git a/lib/horizon/claimable_balance.ex b/lib/horizon/claimable_balance.ex new file mode 100644 index 00000000..f3be67a3 --- /dev/null +++ b/lib/horizon/claimable_balance.ex @@ -0,0 +1,46 @@ +defmodule Stellar.Horizon.ClaimableBalance do + @moduledoc """ + Represents a `ClaimableBalance` resource from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + id: String.t(), + paging_token: String.t(), + asset: String.t(), + amount: float(), + sponsor: String.t() | nil, + last_modified_ledger: non_neg_integer(), + last_modified_time: DateTime.t(), + claimants: list(map()) + } + + defstruct [ + :id, + :paging_token, + :asset, + :amount, + :sponsor, + :last_modified_ledger, + :last_modified_time, + :claimants + ] + + @mapping [ + amount: :float, + last_modified_ledger: :integer, + last_modified_time: :date_time + ] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/claimable_balances.ex b/lib/horizon/claimable_balances.ex new file mode 100644 index 00000000..adfe115c --- /dev/null +++ b/lib/horizon/claimable_balances.ex @@ -0,0 +1,215 @@ +defmodule Stellar.Horizon.ClaimableBalances do + @moduledoc """ + Exposes functions to interact with ClaimableBalances in Horizon. + + You can: + * Retrieve a claimable balance. + * List all claimable balances. + * List a claimable balance's transactions. + * List a claimable balance's operations. + + Horizon API reference: https://developers.stellar.org/api/resources/claimablebalances/ + """ + + alias Stellar.Horizon.{ClaimableBalance, Collection, Error, Operation, Request, Transaction} + + @type claimable_balance_id :: String.t() + @type account_id :: String.t() + @type asset :: String.t() + @type options :: Keyword.t() + @type resource :: ClaimableBalance.t() | Collection.t() + @type response :: {:ok, resource()} | {:error, Error.t()} + + @endpoint "claimable_balances" + + @doc """ + Retrieves information of a specific claimable balance. + + ## Parameters: + * `claimable_balance_id`: A unique identifier for the claimable balance. + + ## Examples + + iex> ClaimableBalances.retrieve("00000000ca6aba5fb0993844e0076f75bee53f2b8014be29cd8f2e6ae19fb0a17fc68695") + {:ok, %ClaimableBalance{}} + """ + @spec retrieve(claimable_balance_id :: claimable_balance_id()) :: response() + def retrieve(claimable_balance_id) do + :get + |> Request.new(@endpoint, path: claimable_balance_id) + |> Request.perform() + |> Request.results(&ClaimableBalance.new(&1)) + end + + @doc """ + Lists all available claimable balances. + + ## Options + + * `sponsor`: Account ID of the sponsors. + * `asset`: An issued asset represented as “Code:IssuerAccountID”. + * `claimant`: Account ID of the destination address. + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> ClaimableBalances.all(limit: 2, order: :asc) + {:ok, %Collection{records: [%ClaimableBalance{}, ...]}} + + # list by sponsor + iex> ClaimableBalances.all(sponsor: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") + {:ok, %Collection{records: [%ClaimableBalance{}, ...]}} + + # list by claimant + iex> ClaimableBalances.all(claimant: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", order: :desc) + {:ok, %Collection{records: [%ClaimableBalance{}, ...]}} + + # list by canonical asset address + iex> ClaimableBalances.all(asset: "TEST:GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", limit: 20) + {:ok, %Collection{records: [%ClaimableBalance{}, ...]}} + """ + @spec all(options :: options()) :: response() + def all(options \\ []) do + :get + |> Request.new(@endpoint) + |> Request.add_query(options, extra_params: [:sponsor, :asset, :claimant]) + |> Request.perform() + |> Request.results(&Collection.new({ClaimableBalance, &1})) + end + + @doc """ + Lists successful transactions referencing a given claimable balance. + + ## Parameters + * `claimable_balance_id`: A unique identifier for the claimable balance. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + * `include_failed`: Set to true to include failed operations in results. + + ## Examples + + iex> ClaimableBalances.list_transactions("00000000ca6aba5fb0993844e0076f75bee53f2b8014be29cd8f2e6ae19fb0a17fc68695", limit: 20) + {:ok, %Collection{records: [%Transaction{}, ...]}} + """ + @spec list_transactions(claimable_balance_id :: claimable_balance_id(), options :: options()) :: + response() + def list_transactions(claimable_balance_id, options \\ []) do + :get + |> Request.new(@endpoint, path: claimable_balance_id, segment: "transactions") + |> Request.add_query(options, extra_params: [:include_failed]) + |> Request.perform() + |> Request.results(&Collection.new({Transaction, &1})) + end + + @doc """ + Lists successful operations referencing a given claimable balance. + + ## Parameters + * `claimable_balance_id`: A unique identifier for the claimable balance. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + * `include_failed`: Set to true to include failed operations in results. + * `join`: Set to `transactions` to include the transactions which created each of the operations in the response. + + ## Examples + + iex> ClaimableBalances.list_operations("00000000ca6aba5fb0993844e0076f75bee53f2b8014be29cd8f2e6ae19fb0a17fc68695", limit: 20) + {:ok, %Collection{records: [%Operation{}, ...]}} + + # join transactions + iex> ClaimableBalances.list_operations("00000000ca6aba5fb0993844e0076f75bee53f2b8014be29cd8f2e6ae19fb0a17fc68695", join: "transactions") + {:ok, %Collection{records: [%Operation{transaction: %Transaction{}}, ...]}} + """ + @spec list_operations(claimable_balance_id :: claimable_balance_id(), options :: options()) :: + response() + def list_operations(claimable_balance_id, options \\ []) do + :get + |> Request.new(@endpoint, path: claimable_balance_id, segment: "operations") + |> Request.add_query(options, extra_params: [:include_failed, :join]) + |> Request.perform() + |> Request.results(&Collection.new({Operation, &1})) + end + + @doc """ + Lists claimable balances matching the given sponsor. + + ## Parameters: + + * `sponsor`: Account ID of the sponsor. + + ## Options + + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> ClaimableBalances.list_by_sponsor("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") + {:ok, %Collection{records: [%ClaimableBalance{sponsor: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD"}, ...]}} + """ + @spec list_by_sponsor(sponsor :: account_id(), options :: options()) :: response() + def list_by_sponsor(sponsor, options \\ []) do + options + |> Keyword.put(:sponsor, sponsor) + |> all() + end + + @doc """ + Lists claimable balances matching the given claimant. + + ## Parameters: + + * `claimant`: Account ID of the destination address. + + ## Options + + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> ClaimableBalances.list_by_claimant("GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") + {:ok, %Collection{records: [%ClaimableBalance{claimant: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD"}, ...]}} + """ + @spec list_by_claimant(claimant :: account_id(), options :: options()) :: response() + def list_by_claimant(claimant, options \\ []) do + options + |> Keyword.put(:claimant, claimant) + |> all() + end + + @doc """ + Lists claimable balances matching the given canonical asset. + + ## Parameters: + + * `asset`: An issued asset represented as “Code:IssuerAccountID”. + + ## Options + + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> ClaimableBalances.list_by_asset("TEST:GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") + {:ok, %Collection{records: [%ClaimableBalance{asset: "TEST:GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD"}, ...]}} + """ + @spec list_by_asset(asset :: asset(), options :: options()) :: response() + def list_by_asset(asset, options \\ []) do + options + |> Keyword.put(:asset, asset) + |> all() + end +end diff --git a/lib/horizon/collection.ex b/lib/horizon/collection.ex new file mode 100644 index 00000000..49c04c5c --- /dev/null +++ b/lib/horizon/collection.ex @@ -0,0 +1,28 @@ +defmodule Stellar.Horizon.Collection do + @moduledoc """ + Paginates data and returns collection-based results for multiple Horizon endpoints. + """ + alias Stellar.Horizon.CollectionError + + @type resource :: module() + @type records :: [resource(), ...] + @type response :: map() + @type link :: String.t() + + @type t :: %__MODULE__{records: records(), self: link(), prev: link(), next: link()} + + defstruct [:records, :self, :next, :prev] + + @spec new(response :: {resource(), response()}) :: t() + def new( + {resource, + %{ + _embedded: %{records: records}, + _links: %{self: %{href: self}, prev: %{href: prev}, next: %{href: next}} + }} + ) do + %__MODULE__{records: Enum.map(records, &resource.new/1), self: self, prev: prev, next: next} + end + + def new(_response), do: raise(CollectionError, :invalid_collection) +end diff --git a/lib/horizon/collection_error.ex b/lib/horizon/collection_error.ex new file mode 100644 index 00000000..4dc5b6d3 --- /dev/null +++ b/lib/horizon/collection_error.ex @@ -0,0 +1,14 @@ +defmodule Stellar.Horizon.CollectionError do + @moduledoc """ + Handle exceptions that may arise from the `Stellar.Horizon.Collection`. + """ + @type t :: %__MODULE__{message: String.t()} + + defexception [:message] + + @spec exception(type :: atom()) :: no_return() + def exception(:invalid_collection), + do: %__MODULE__{message: "can't parse response as a collection"} + + def exception(message), do: %__MODULE__{message: message} +end diff --git a/lib/horizon/effect.ex b/lib/horizon/effect.ex new file mode 100644 index 00000000..28701e7b --- /dev/null +++ b/lib/horizon/effect.ex @@ -0,0 +1,57 @@ +defmodule Stellar.Horizon.Effect do + @moduledoc """ + Represents a `Effect` resource from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + id: String.t(), + paging_token: String.t(), + account: String.t(), + type: String.t(), + type_i: non_neg_integer(), + attributes: map(), + created_at: DateTime.t() + } + + defstruct [ + :id, + :paging_token, + :account, + :type, + :type_i, + :attributes, + :created_at + ] + + @mapping [ + type_i: :integer, + created_at: :date_time, + attributes: {:map, [amount: :float, starting_balance: :float]} + ] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + effect_attrs = map_effect_attrs(attrs) + + %__MODULE__{} + |> Mapping.build(effect_attrs) + |> Mapping.parse(@mapping) + end + + @spec map_effect_attrs(attrs :: map()) :: map() + defp map_effect_attrs(attrs) do + effect_attrs = + %__MODULE__{} + |> Map.from_struct() + |> Map.keys() + |> (&Map.drop(attrs, &1 ++ [:_links])).() + + Map.put(attrs, :attributes, effect_attrs) + end +end diff --git a/lib/horizon/effects.ex b/lib/horizon/effects.ex new file mode 100644 index 00000000..58998335 --- /dev/null +++ b/lib/horizon/effects.ex @@ -0,0 +1,41 @@ +defmodule Stellar.Horizon.Effects do + @moduledoc """ + Exposes functions to interact with Effects in Horizon. + + You can: + * List all effects. + + Horizon API reference: https://developers.stellar.org/api/resources/effects/ + """ + + alias Stellar.Horizon.{Collection, Effect, Error, Request} + + @type options :: Keyword.t() + @type resource :: Effect.t() | Collection.t() + @type response :: {:ok, resource()} | {:error, Error.t()} + + @endpoint "effects" + + @doc """ + Lists all effects. + + ## Options + + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> Effects.all(limit: 10, order: :asc) + {:ok, %Collection{records: [%Effect{}, ...]}} + """ + @spec all(options :: options()) :: response() + def all(options \\ []) do + :get + |> Request.new(@endpoint) + |> Request.add_query(options) + |> Request.perform() + |> Request.results(&Collection.new({Effect, &1})) + end +end diff --git a/lib/horizon/ledger.ex b/lib/horizon/ledger.ex new file mode 100644 index 00000000..1e4dc9d2 --- /dev/null +++ b/lib/horizon/ledger.ex @@ -0,0 +1,72 @@ +defmodule Stellar.Horizon.Ledger do + @moduledoc """ + Represents a `Ledger` resource from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + id: String.t(), + paging_token: String.t(), + hash: String.t(), + prev_hash: String.t(), + sequence: non_neg_integer(), + successful_transaction_count: non_neg_integer(), + failed_transaction_count: non_neg_integer(), + operation_count: non_neg_integer(), + tx_set_operation_count: non_neg_integer(), + closed_at: DateTime.t(), + total_coins: float(), + fee_pool: float(), + base_fee_in_stroops: non_neg_integer(), + base_reserve_in_stroops: non_neg_integer(), + max_tx_set_size: non_neg_integer(), + protocol_version: non_neg_integer(), + header_xdr: String.t() + } + + defstruct [ + :id, + :paging_token, + :hash, + :prev_hash, + :sequence, + :successful_transaction_count, + :failed_transaction_count, + :operation_count, + :tx_set_operation_count, + :closed_at, + :total_coins, + :fee_pool, + :base_fee_in_stroops, + :base_reserve_in_stroops, + :max_tx_set_size, + :protocol_version, + :header_xdr + ] + + @mapping [ + closed_at: :date_time, + total_coins: :float, + fee_pool: :float, + sequence: :integer, + successful_transaction_count: :integer, + failed_transaction_count: :integer, + tx_set_operation_count: :integer, + operation_count: :integer, + base_fee_in_stroops: :integer, + base_reserve_in_stroops: :integer, + max_tx_set_size: :integer + ] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/ledgers.ex b/lib/horizon/ledgers.ex new file mode 100644 index 00000000..6d0eeefd --- /dev/null +++ b/lib/horizon/ledgers.ex @@ -0,0 +1,186 @@ +defmodule Stellar.Horizon.Ledgers do + @moduledoc """ + Exposes functions to interact with Ledgers in Horizon. + + You can: + * Retrieve a ledger. + * List all ledgers. + * List a ledger's effects. + * List a ledger's transactions. + * List a ledger's operations. + + Horizon API reference: https://developers.stellar.org/api/resources/ledgers/ + """ + + alias Stellar.Horizon.{ + Collection, + Effect, + Error, + Ledger, + Operation, + Request, + Transaction + } + + @type sequence :: non_neg_integer() + @type params :: Keyword.t() + @type resource :: Ledger.t() | Collection.t() + @type response :: {:ok, resource()} | {:error, Error.t()} + + @endpoint "ledgers" + + @doc """ + Retrieves information of a specific ledger. + + ## Parameters: + * `sequence`: The sequence number of a specific ledger. + + ## Examples + + iex> Ledgers.retrieve(27147222) + {:ok, %Ledger{}} + """ + @spec retrieve(sequence :: sequence()) :: response() + def retrieve(sequence) do + :get + |> Request.new(@endpoint, path: sequence) + |> Request.perform() + |> Request.results(&Ledger.new(&1)) + end + + @doc """ + Lists all ledgers. + + ## Options + + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> Ledgers.all(limit: 10, order: :asc) + {:ok, %Collection{records: [%Ledger{}, ...]}} + """ + @spec all(params :: params()) :: response() + def all(params \\ []) do + :get + |> Request.new(@endpoint) + |> Request.add_query(params) + |> Request.perform() + |> Request.results(&Collection.new({Ledger, &1})) + end + + @doc """ + Lists successful transactions in a specific ledger. + + ## Parameters + * `sequence`: The sequence number of a specific ledger. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + * `include_failed`: Set to true to include failed operations in results. + + ## Examples + + iex> Ledgers.list_transactions(27147222, limit: 20) + {:ok, %Collection{records: [%Transaction{}, ...]}} + """ + @spec list_transactions(sequence :: sequence(), params :: params()) :: response() + def list_transactions(sequence, params \\ []) do + :get + |> Request.new(@endpoint, path: sequence, segment: "transactions") + |> Request.add_query(params, extra_params: [:include_failed]) + |> Request.perform() + |> Request.results(&Collection.new({Transaction, &1})) + end + + @doc """ + Lists successful operations in a specific ledger. + + ## Parameters + * `sequence`: The sequence number of a specific ledger. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + * `include_failed`: Set to true to include failed operations in results. + * `join`: Set to `transactions` to include the transactions which created each of the operations in the response. + + ## Examples + + iex> Ledgers.list_operations(27147222, limit: 20) + {:ok, %Collection{records: [%Operation{}, ...]}} + + # join transactions + iex> Ledgers.list_operations(27147222, join: "transactions") + {:ok, %Collection{records: [%Operation{transaction: %Transaction{}}, ...]}} + """ + @spec list_operations(sequence :: sequence(), params :: params()) :: response() + def list_operations(sequence, params \\ []) do + :get + |> Request.new(@endpoint, path: sequence, segment: "operations") + |> Request.add_query(params, extra_params: [:include_failed, :join]) + |> Request.perform() + |> Request.results(&Collection.new({Operation, &1})) + end + + @doc """ + Lists successful payments in a specific ledger. + + ## Parameters + * `sequence`: The sequence number of a specific ledger. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + * `include_failed`: Set to true to include failed operations in results. + * `join`: Set to `transactions` to include the transactions which created each of the operations in the response. + + ## Examples + + iex> Ledgers.list_payments(27147222, limit: 20) + {:ok, %Collection{records: [%Operation{body: %Payment{}}, ...]}} + + # include failed + iex> Ledgers.list_payments(27147222, include_failed: true) + {:ok, %Collection{records: [%Operation{body: %Payment{}}, ...]}} + """ + @spec list_payments(sequence :: sequence(), params :: params()) :: response() + def list_payments(sequence, params \\ []) do + :get + |> Request.new(@endpoint, path: sequence, segment: "payments") + |> Request.add_query(params, extra_params: [:include_failed, :join]) + |> Request.perform() + |> Request.results(&Collection.new({Operation, &1})) + end + + @doc """ + Lists the effects of a specific ledger. + + ## Parameters + * `sequence`: The sequence number of a specific ledger. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> Ledgers.list_effects(27147222, limit: 20) + {:ok, %Collection{records: [%Effect{}, ...]}} + """ + @spec list_effects(sequence :: sequence(), params :: params()) :: response() + def list_effects(sequence, params \\ []) do + :get + |> Request.new(@endpoint, path: sequence, segment: "effects") + |> Request.add_query(params) + |> Request.perform() + |> Request.results(&Collection.new({Effect, &1})) + end +end diff --git a/lib/horizon/liquidity_pool.ex b/lib/horizon/liquidity_pool.ex new file mode 100644 index 00000000..8eb4bb81 --- /dev/null +++ b/lib/horizon/liquidity_pool.ex @@ -0,0 +1,51 @@ +defmodule Stellar.Horizon.LiquidityPool do + @moduledoc """ + Represents a `LiquidityPool` resource from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + id: String.t(), + paging_token: String.t(), + fee_bp: non_neg_integer(), + type: String.t(), + total_trustlines: non_neg_integer(), + total_shares: float(), + reserves: list(map()), + last_modified_ledger: non_neg_integer(), + last_modified_time: DateTime.t() + } + + defstruct [ + :id, + :paging_token, + :fee_bp, + :type, + :total_trustlines, + :total_shares, + :reserves, + :last_modified_ledger, + :last_modified_time + ] + + @mapping [ + fee_bp: :integer, + total_trustlines: :integer, + total_shares: :float, + last_modified_ledger: :integer, + last_modified_time: :date_time, + reserves: {:list, :map, [amount: :float]} + ] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/liquidity_pools.ex b/lib/horizon/liquidity_pools.ex new file mode 100644 index 00000000..a9229e5e --- /dev/null +++ b/lib/horizon/liquidity_pools.ex @@ -0,0 +1,194 @@ +defmodule Stellar.Horizon.LiquidityPools do + @moduledoc """ + Exposes functions to interact with LiquidityPools in Horizon. + + You can: + * Retrieve a liquidity pool. + * List all liquidity pools. + * List a liquidity pool's effects. + * List a liquidity pool's trades. + * List a liquidity pool's transactions. + * List a liquidity pool's operations. + + Horizon API reference: https://developers.stellar.org/api/resources/liquiditypools/ + """ + + alias Stellar.Horizon.{ + Collection, + Effect, + Error, + LiquidityPool, + Operation, + Request, + Trade, + Transaction + } + + @type liquidity_pool_id :: String.t() + @type options :: Keyword.t() + @type resource :: LiquidityPool.t() | Collection.t() + @type response :: {:ok, resource()} | {:error, Error.t()} + + @endpoint "liquidity_pools" + + @doc """ + Retrieves information of a specific liquidity pool. + + ## Parameters: + * `liquidity_pool_id`: A liquidity pool’s id encoded in a hex string representation. + + ## Examples + + iex> LiquidityPools.retrieve("001365fc79ca661f31ba3ee0849ae4ba36f5c377243242d37fad5b1bb8912dbc") + {:ok, %LiquidityPool{}} + """ + @spec retrieve(liquidity_pool_id :: liquidity_pool_id()) :: response() + def retrieve(liquidity_pool_id) do + :get + |> Request.new(@endpoint, path: liquidity_pool_id) + |> Request.perform() + |> Request.results(&LiquidityPool.new(&1)) + end + + @doc """ + Lists all available claimable balances. + + ## Options + + * `reserves`: Comma-separated list of assets in canonical form “Code:IssuerAccountID”. + * `account`: Account ID of the destination address. + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> LiquidityPools.all(limit: 2, order: :asc) + {:ok, %Collection{records: [%LiquidityPool{}, ...]}} + + # list by reserves + iex> LiquidityPools.all(reserves: "TEST:GCXMW..., TEST2:GCXMW...") + {:ok, %Collection{records: [%LiquidityPool{}, ...]}} + + # list by account + iex> LiquidityPools.all(account: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", order: :desc) + {:ok, %Collection{records: [%LiquidityPool{}, ...]}} + """ + @spec all(options :: options()) :: response() + def all(options \\ []) do + :get + |> Request.new(@endpoint) + |> Request.add_query(options, extra_options: [:reserves, :account]) + |> Request.perform() + |> Request.results(&Collection.new({LiquidityPool, &1})) + end + + @doc """ + Lists the effects of a specific liquidity pool. + + ## Parameters + * `liquidity_pool_id`: A liquidity pool’s id encoded in a hex string representation. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> LiquidityPools.list_effects("001365fc79ca661f31ba3ee0849ae4ba36f5c377243242d37fad5b1bb8912dbc", limit: 20) + {:ok, %Collection{records: [%Effect{}, ...]}} + """ + @spec list_effects(liquidity_pool_id :: liquidity_pool_id(), options :: options()) :: response() + def list_effects(liquidity_pool_id, options \\ []) do + :get + |> Request.new(@endpoint, path: liquidity_pool_id, segment: "effects") + |> Request.add_query(options) + |> Request.perform() + |> Request.results(&Collection.new({Effect, &1})) + end + + @doc """ + Lists the successful trades fulfilled by the given liquidity pool. + + ## Parameters + * `liquidity_pool_id`: A liquidity pool’s id encoded in a hex string representation. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> LiquidityPools.list_trades("001365fc79ca661f31ba3ee0849ae4ba36f5c377243242d37fad5b1bb8912dbc", limit: 20) + {:ok, %Collection{records: [%Trade{}, ...]}} + """ + @spec list_trades(liquidity_pool_id :: liquidity_pool_id(), options :: options()) :: response() + def list_trades(liquidity_pool_id, options \\ []) do + :get + |> Request.new(@endpoint, path: liquidity_pool_id, segment: "trades") + |> Request.add_query(options) + |> Request.perform() + |> Request.results(&Collection.new({Trade, &1})) + end + + @doc """ + Lists successful transactions referencing a given liquidity pool. + + ## Parameters + * `liquidity_pool_id`: A liquidity pool’s id encoded in a hex string representation. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + * `include_failed`: Set to true to include failed operations in results. + + ## Examples + + iex> LiquidityPools.list_transactions("001365fc79ca661f31ba3ee0849ae4ba36f5c377243242d37fad5b1bb8912dbc", limit: 20) + {:ok, %Collection{records: [%Transaction{}, ...]}} + """ + @spec list_transactions(liquidity_pool_id :: liquidity_pool_id(), options :: options()) :: + response() + def list_transactions(liquidity_pool_id, options \\ []) do + :get + |> Request.new(@endpoint, path: liquidity_pool_id, segment: "transactions") + |> Request.add_query(options, extra_options: [:include_failed]) + |> Request.perform() + |> Request.results(&Collection.new({Transaction, &1})) + end + + @doc """ + Lists successful operations referencing a given liquidity pool. + + ## Parameters + * `liquidity_pool_id`: A liquidity pool’s id encoded in a hex string representation. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + * `include_failed`: Set to true to include failed operations in results. + * `join`: Set to `transactions` to include the transactions which created each of the operations in the response. + + ## Examples + + iex> LiquidityPools.list_operations("001365fc79ca661f31ba3ee0849ae4ba36f5c377243242d37fad5b1bb8912dbc", limit: 20) + {:ok, %Collection{records: [%Operation{}, ...]}} + + # join transactions + iex> LiquidityPools.list_operations("001365fc79ca661f31ba3ee0849ae4ba36f5c377243242d37fad5b1bb8912dbc", join: "transactions") + {:ok, %Collection{records: [%Operation{transaction: %Transaction{}}, ...]}} + """ + @spec list_operations(liquidity_pool_id :: liquidity_pool_id(), options :: options()) :: + response() + def list_operations(liquidity_pool_id, options \\ []) do + :get + |> Request.new(@endpoint, path: liquidity_pool_id, segment: "operations") + |> Request.add_query(options, extra_options: [:include_failed, :join]) + |> Request.perform() + |> Request.results(&Collection.new({Operation, &1})) + end +end diff --git a/lib/horizon/mapping.ex b/lib/horizon/mapping.ex index fecf4237..3049faa5 100644 --- a/lib/horizon/mapping.ex +++ b/lib/horizon/mapping.ex @@ -4,8 +4,14 @@ defmodule Stellar.Horizon.Mapping do (e.g. `%Horizon.Transaction{}`) or list of structs. """ - @type attr_type :: :integer | :float | :date_time | struct() - @type attr_value :: String.t() | integer() | float() | boolean() | struct() | list() | nil + @type attr_value :: any() + @type attr_type :: + :integer + | :float + | :date_time + | {:map, Keyword.t()} + | {:struct, module()} + | {:list, atom(), Keyword.t() | module()} @spec build(module :: struct(), attrs :: map()) :: struct() def build(module, attrs) do @@ -42,11 +48,24 @@ defmodule Stellar.Horizon.Mapping do end end - defp do_parse(%{__struct__: module}, values) when is_list(values) do - Enum.map(values, &module.new(&1)) + defp do_parse({:map, mapping}, value) when is_map(value) do + Enum.reduce(mapping, value, fn {key, type}, acc -> + acc + |> Map.get(key) + |> (&do_parse(type, &1)).() + |> (&Map.put(acc, key, &1)).() + end) + end + + defp do_parse({:struct, module}, value) when not is_nil(value), do: module.new(value) + + defp do_parse({:list, :map, mapping}, values) when is_list(values) do + Enum.map(values, &do_parse({:map, mapping}, &1)) end - defp do_parse(%{__struct__: module}, value) when not is_nil(value), do: module.new(value) + defp do_parse({:list, :struct, module}, values) when is_list(values) do + Enum.map(values, &module.new(&1)) + end defp do_parse(_type, value), do: value end diff --git a/lib/horizon/offer.ex b/lib/horizon/offer.ex new file mode 100644 index 00000000..c1aa57d1 --- /dev/null +++ b/lib/horizon/offer.ex @@ -0,0 +1,53 @@ +defmodule Stellar.Horizon.Offer do + @moduledoc """ + Represents a `Offer` resource from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + id: String.t(), + paging_token: String.t(), + seller: String.t(), + selling: map(), + buying: map(), + amount: float(), + price: float(), + price_r: map(), + last_modified_ledger: non_neg_integer(), + last_modified_time: DateTime.t(), + sponsor: String.t() + } + + defstruct [ + :id, + :paging_token, + :seller, + :selling, + :buying, + :amount, + :price, + :price_r, + :last_modified_ledger, + :last_modified_time, + :sponsor + ] + + @mapping [ + amount: :float, + price: :float, + last_modified_ledger: :integer, + last_modified_time: :date_time + ] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/offers.ex b/lib/horizon/offers.ex new file mode 100644 index 00000000..1eccf9ec --- /dev/null +++ b/lib/horizon/offers.ex @@ -0,0 +1,126 @@ +defmodule Stellar.Horizon.Offers do + @moduledoc """ + Exposes functions to interact with Offers in Horizon. + + You can: + * Retrieve an offer. + * List all offers. + * List an offer's trades. + + Horizon API reference: https://developers.stellar.org/api/resources/offers/ + """ + + alias Stellar.Horizon.{Collection, Error, Offer, Request, Trade} + + @type offer_id :: String.t() + @type options :: Keyword.t() + @type resource :: Offer.t() | Collection.t() + @type response :: {:ok, resource()} | {:error, Error.t()} + + @endpoint "offers" + + @doc """ + Retrieves information of a specific offer. + + ## Parameters: + * `offer_id`: The unique identifier for the offer. + + ## Examples + + iex> Offers.retrieve(165563085) + {:ok, %Offer{}} + """ + @spec retrieve(offer_id :: offer_id()) :: response() + def retrieve(offer_id) do + :get + |> Request.new(@endpoint, path: offer_id) + |> Request.perform() + |> Request.results(&Offer.new(&1)) + end + + @doc """ + Lists all currently open offers. + + ## Options + + * `sponsor`: The account ID of the sponsor who is paying the reserves for all the offers included in the response. + * `seller`: The account ID of the offer creator. + * `selling_asset_type`: The type for the selling asset. Either `native`, `credit_alphanum4`, or `credit_alphanum12`. + * `selling_asset_issuer`: The account ID of the selling asset’s issuer. + * `selling_asset_code`: The code for the selling asset. + * `buying_asset_type`: The type for the buying asset. Either `native`, `credit_alphanum4`, or `credit_alphanum12`. + * `buying_asset_issuer`: The account ID of the buying asset’s issuer. + * `buying_asset_code`: The code for the buying asset. + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> Offers.all(limit: 20, order: :asc) + {:ok, %Collection{records: [%Offer{}, ...]}} + + # list by sponsor + iex> Offers.all(sponsor: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD") + {:ok, %Collection{records: [%Offer{}, ...]}} + + # list by seller + iex> Offers.all(seller: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", order: :desc) + {:ok, %Collection{records: [%Offer{}, ...]}} + + # list by selling_asset_issuer + iex> Offers.all(selling_asset_issuer: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", limit: 20) + {:ok, %Collection{records: [%Offer{}, ...]}} + + # list by buying_asset_type and buying_asset_code + iex> Offers.all(buying_asset_type: "credit_alphanum4", buying_asset_code: "TEST") + {:ok, %Collection{records: [%Offer{}, ...]}} + """ + @spec all(options :: options()) :: response() + def all(options \\ []) do + :get + |> Request.new(@endpoint) + |> Request.add_query(options, extra_params: allowed_query_options()) + |> Request.perform() + |> Request.results(&Collection.new({Offer, &1})) + end + + @doc """ + Lists all trades for a given offer. + + ## Parameters + * `offer_id`: The unique identifier for the offer. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> Offers.list_trades(165563085, limit: 20) + {:ok, %Collection{records: [%Trade{}, ...]}} + """ + @spec list_trades(offer_id :: offer_id(), options :: options()) :: response() + def list_trades(offer_id, options \\ []) do + :get + |> Request.new(@endpoint, path: offer_id, segment: "trades") + |> Request.add_query(options) + |> Request.perform() + |> Request.results(&Collection.new({Trade, &1})) + end + + @spec allowed_query_options() :: list() + defp allowed_query_options do + [ + :sponsor, + :seller, + :selling_asset_type, + :selling_asset_issuer, + :selling_asset_code, + :buying_asset_type, + :buying_asset_issuer, + :buying_asset_code + ] + end +end diff --git a/lib/horizon/operation.ex b/lib/horizon/operation.ex new file mode 100644 index 00000000..67876cbf --- /dev/null +++ b/lib/horizon/operation.ex @@ -0,0 +1,141 @@ +defmodule Stellar.Horizon.Operation do + @moduledoc """ + Represents a `Operation` resource from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.{Mapping, Transaction} + + alias Stellar.Horizon.Operation.{ + AccountMerge, + AllowTrust, + BeginSponsoringFutureReserves, + BumpSequence, + ChangeTrust, + CreateAccount, + CreateClaimableBalance, + ClaimClaimableBalance, + CreatePassiveSellOffer, + EndSponsoringFutureReserves, + LiquidityPoolDeposit, + LiquidityPoolWithdraw, + ManageBuyOffer, + ManageSellOffer, + ManageData, + PathPaymentStrictReceive, + PathPaymentStrictSend, + Payment, + RevokeSponsorship, + SetOptions + } + + @type body :: + AccountMerge.t() + | AllowTrust.t() + | BeginSponsoringFutureReserves.t() + | BumpSequence.t() + | ChangeTrust.t() + | CreateAccount.t() + | CreateClaimableBalance.t() + | ClaimClaimableBalance.t() + | CreatePassiveSellOffer.t() + | EndSponsoringFutureReserves.t() + | LiquidityPoolDeposit.t() + | LiquidityPoolWithdraw.t() + | ManageBuyOffer.t() + | ManageSellOffer.t() + | ManageData.t() + | PathPaymentStrictReceive.t() + | PathPaymentStrictSend.t() + | Payment.t() + | RevokeSponsorship.t() + | SetOptions.t() + + @type t :: %__MODULE__{ + id: String.t(), + paging_token: String.t(), + source_account: String.t(), + type_i: non_neg_integer(), + type: String.t(), + transaction_hash: String.t(), + transaction_successful: boolean(), + body: body(), + transaction: Transaction.t() | nil, + created_at: DateTime.t() + } + + defstruct [ + :id, + :paging_token, + :source_account, + :type, + :type_i, + :transaction_hash, + :transaction_successful, + :body, + :transaction, + :created_at + ] + + @mapping [ + id: :integer, + type_i: :integer, + transaction: {:struct, Transaction}, + created_at: :date_time + ] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + mapping = + attrs + |> Map.get(:type) + |> operation_type_mapping() + + attrs + |> operation_attrs() + |> (&Mapping.build(%__MODULE__{}, &1)).() + |> Mapping.parse(mapping) + end + + @spec operation_attrs(attrs :: map()) :: map() + defp operation_attrs(attrs) do + body_attrs = + %__MODULE__{} + |> Map.from_struct() + |> Map.keys() + |> (&Map.drop(attrs, &1 ++ [:_links])).() + + Map.put(attrs, :body, body_attrs) + end + + @spec operation_type_mapping(type :: any()) :: Keyword.t() + defp operation_type_mapping(nil), do: @mapping + + defp operation_type_mapping(type), + do: Keyword.merge(@mapping, body: {:struct, operation_type(type)}) + + @spec operation_type(type :: String.t()) :: module() + defp operation_type("create_account"), do: CreateAccount + defp operation_type("payment"), do: Payment + defp operation_type("path_payment_strict_receive"), do: PathPaymentStrictReceive + defp operation_type("path_payment_strict_send"), do: PathPaymentStrictSend + defp operation_type("manage_sell_offer"), do: ManageSellOffer + defp operation_type("manage_buy_offer"), do: ManageBuyOffer + defp operation_type("create_passive_sell_offer"), do: CreatePassiveSellOffer + defp operation_type("set_options"), do: SetOptions + defp operation_type("change_trust"), do: ChangeTrust + defp operation_type("allow_trust"), do: AllowTrust + defp operation_type("account_merge"), do: AccountMerge + defp operation_type("manage_data"), do: ManageData + defp operation_type("bump_sequence"), do: BumpSequence + defp operation_type("create_claimable_balance"), do: CreateClaimableBalance + defp operation_type("claim_claimable_balance"), do: ClaimClaimableBalance + defp operation_type("begin_sponsoring_future_reserves"), do: BeginSponsoringFutureReserves + defp operation_type("end_sponsoring_future_reserves"), do: EndSponsoringFutureReserves + defp operation_type("revoke_sponsorship"), do: RevokeSponsorship + defp operation_type("liquidity_pool_deposit"), do: LiquidityPoolDeposit + defp operation_type("liquidity_pool_withdraw"), do: LiquidityPoolWithdraw +end diff --git a/lib/horizon/operation/account_merge.ex b/lib/horizon/operation/account_merge.ex new file mode 100644 index 00000000..38731186 --- /dev/null +++ b/lib/horizon/operation/account_merge.ex @@ -0,0 +1,20 @@ +defmodule Stellar.Horizon.Operation.AccountMerge do + @moduledoc """ + Represents a `AccountMerge` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{account: String.t(), into: String.t()} + + defstruct [:account, :into] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + Mapping.build(%__MODULE__{}, attrs) + end +end diff --git a/lib/horizon/operation/allow_trust.ex b/lib/horizon/operation/allow_trust.ex new file mode 100644 index 00000000..ea876b5d --- /dev/null +++ b/lib/horizon/operation/allow_trust.ex @@ -0,0 +1,27 @@ +defmodule Stellar.Horizon.Operation.AllowTrust do + @moduledoc """ + Represents a `AllowTrust` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + asset_type: String.t(), + asset_code: String.t(), + asset_issuer: String.t(), + authorize: boolean(), + trustee: String.t(), + trustor: String.t() + } + + defstruct [:asset_type, :asset_code, :asset_issuer, :authorize, :trustee, :trustor] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + Mapping.build(%__MODULE__{}, attrs) + end +end diff --git a/lib/horizon/operation/begin_sponsoring_future_reserves.ex b/lib/horizon/operation/begin_sponsoring_future_reserves.ex new file mode 100644 index 00000000..5982b03f --- /dev/null +++ b/lib/horizon/operation/begin_sponsoring_future_reserves.ex @@ -0,0 +1,20 @@ +defmodule Stellar.Horizon.Operation.BeginSponsoringFutureReserves do + @moduledoc """ + Represents a `BeginSponsoringFutureReserves` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{sponsored_id: String.t()} + + defstruct [:sponsored_id] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + Mapping.build(%__MODULE__{}, attrs) + end +end diff --git a/lib/horizon/operation/bump_sequence.ex b/lib/horizon/operation/bump_sequence.ex new file mode 100644 index 00000000..3237de51 --- /dev/null +++ b/lib/horizon/operation/bump_sequence.ex @@ -0,0 +1,24 @@ +defmodule Stellar.Horizon.Operation.BumpSequence do + @moduledoc """ + Represents a `BumpSequence` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{bump_to: String.t()} + + defstruct [:bump_to] + + @mapping [bump_to: :integer] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/operation/change_trust.ex b/lib/horizon/operation/change_trust.ex new file mode 100644 index 00000000..4f713e91 --- /dev/null +++ b/lib/horizon/operation/change_trust.ex @@ -0,0 +1,40 @@ +defmodule Stellar.Horizon.Operation.ChangeTrust do + @moduledoc """ + Represents a `ChangeTrust` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + asset_type: String.t(), + asset_code: String.t(), + asset_issuer: String.t(), + limit: float(), + trustee: String.t(), + trustor: String.t(), + liquidity_pool_id: String.t() + } + + defstruct [ + :asset_type, + :asset_code, + :asset_issuer, + :limit, + :trustee, + :trustor, + :liquidity_pool_id + ] + + @mapping [limit: :float] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/operation/claim_claimable_balance.ex b/lib/horizon/operation/claim_claimable_balance.ex new file mode 100644 index 00000000..2bd49efb --- /dev/null +++ b/lib/horizon/operation/claim_claimable_balance.ex @@ -0,0 +1,20 @@ +defmodule Stellar.Horizon.Operation.ClaimClaimableBalance do + @moduledoc """ + Represents a `ClaimClaimableBalance` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{balance_id: String.t(), claimant: String.t()} + + defstruct [:balance_id, :claimant] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + Mapping.build(%__MODULE__{}, attrs) + end +end diff --git a/lib/horizon/operation/create_account.ex b/lib/horizon/operation/create_account.ex new file mode 100644 index 00000000..94560e17 --- /dev/null +++ b/lib/horizon/operation/create_account.ex @@ -0,0 +1,28 @@ +defmodule Stellar.Horizon.Operation.CreateAccount do + @moduledoc """ + Represents a `CreateAccount` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + starting_balance: float(), + funder: String.t(), + account: String.t() + } + + defstruct [:starting_balance, :funder, :account] + + @mapping [starting_balance: :float] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/operation/create_claimable_balance.ex b/lib/horizon/operation/create_claimable_balance.ex new file mode 100644 index 00000000..663e50cc --- /dev/null +++ b/lib/horizon/operation/create_claimable_balance.ex @@ -0,0 +1,28 @@ +defmodule Stellar.Horizon.Operation.CreateClaimableBalance do + @moduledoc """ + Represents a `CreateClaimableBalance` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + asset: String.t(), + amount: float(), + claimants: list(map()) + } + + defstruct [:asset, :amount, :claimants] + + @mapping [amount: :float] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/operation/create_passive_sell_offer.ex b/lib/horizon/operation/create_passive_sell_offer.ex new file mode 100644 index 00000000..81042288 --- /dev/null +++ b/lib/horizon/operation/create_passive_sell_offer.ex @@ -0,0 +1,46 @@ +defmodule Stellar.Horizon.Operation.CreatePassiveSellOffer do + @moduledoc """ + Represents a `CreatePassiveSellOffer` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + amount: float(), + price: float(), + price_r: map(), + buying_asset_type: String.t(), + buying_asset_issuer: String.t(), + buying_asset_code: String.t(), + selling_asset_type: String.t(), + selling_asset_issuer: String.t(), + selling_asset_code: String.t(), + offer_id: String.t() + } + + defstruct [ + :amount, + :price, + :price_r, + :buying_asset_type, + :buying_asset_issuer, + :buying_asset_code, + :selling_asset_type, + :selling_asset_issuer, + :selling_asset_code, + :offer_id + ] + + @mapping [amount: :float, price: :float] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/operation/end_sponsoring_future_reserves.ex b/lib/horizon/operation/end_sponsoring_future_reserves.ex new file mode 100644 index 00000000..d9d4e250 --- /dev/null +++ b/lib/horizon/operation/end_sponsoring_future_reserves.ex @@ -0,0 +1,20 @@ +defmodule Stellar.Horizon.Operation.EndSponsoringFutureReserves do + @moduledoc """ + Represents a `EndSponsoringFutureReserves` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{begin_sponsor: String.t()} + + defstruct [:begin_sponsor] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + Mapping.build(%__MODULE__{}, attrs) + end +end diff --git a/lib/horizon/operation/liquidity_pool_deposit.ex b/lib/horizon/operation/liquidity_pool_deposit.ex new file mode 100644 index 00000000..90858c22 --- /dev/null +++ b/lib/horizon/operation/liquidity_pool_deposit.ex @@ -0,0 +1,48 @@ +defmodule Stellar.Horizon.Operation.LiquidityPoolDeposit do + @moduledoc """ + Represents a `LiquidityPoolDeposit` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + liquidity_pool_id: String.t(), + reserves_max: list(map()), + min_price: float(), + min_price_r: map(), + max_price: float(), + max_price_r: map(), + reserves_deposited: list(map()), + shares_received: integer() + } + + defstruct [ + :liquidity_pool_id, + :reserves_max, + :min_price, + :min_price_r, + :max_price, + :max_price_r, + :reserves_deposited, + :shares_received + ] + + @mapping [ + min_price: :float, + max_price: :float, + shares_received: :integer, + reserves_max: {:list, :map, [amount: :float]}, + reserves_deposited: {:list, :map, [amount: :float]} + ] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/operation/liquidity_pool_withdraw.ex b/lib/horizon/operation/liquidity_pool_withdraw.ex new file mode 100644 index 00000000..6cc14556 --- /dev/null +++ b/lib/horizon/operation/liquidity_pool_withdraw.ex @@ -0,0 +1,33 @@ +defmodule Stellar.Horizon.Operation.LiquidityPoolWithdraw do + @moduledoc """ + Represents a `LiquidityPoolWithdraw` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + liquidity_pool_id: String.t(), + reserves_min: list(map()), + shares: integer(), + reserves_received: list(map()) + } + + defstruct [:liquidity_pool_id, :reserves_min, :shares, :reserves_received] + + @mapping [ + shares: :integer, + reserves_received: {:list, :map, [amount: :float]}, + reserves_min: {:list, :map, [amount: :float]} + ] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/operation/manage_buy_offer.ex b/lib/horizon/operation/manage_buy_offer.ex new file mode 100644 index 00000000..a4d5a3d7 --- /dev/null +++ b/lib/horizon/operation/manage_buy_offer.ex @@ -0,0 +1,46 @@ +defmodule Stellar.Horizon.Operation.ManageBuyOffer do + @moduledoc """ + Represents a `ManageBuyOffer` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + amount: float(), + price: float(), + price_r: map(), + buying_asset_type: String.t(), + buying_asset_issuer: String.t(), + buying_asset_code: String.t(), + selling_asset_type: String.t(), + selling_asset_issuer: String.t(), + selling_asset_code: String.t(), + offer_id: String.t() + } + + defstruct [ + :amount, + :price, + :price_r, + :buying_asset_type, + :buying_asset_issuer, + :buying_asset_code, + :selling_asset_type, + :selling_asset_issuer, + :selling_asset_code, + :offer_id + ] + + @mapping [amount: :float, price: :float] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/operation/manage_data.ex b/lib/horizon/operation/manage_data.ex new file mode 100644 index 00000000..98c6c8ce --- /dev/null +++ b/lib/horizon/operation/manage_data.ex @@ -0,0 +1,20 @@ +defmodule Stellar.Horizon.Operation.ManageData do + @moduledoc """ + Represents a `ManageData` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{name: String.t(), value: String.t()} + + defstruct [:name, :value] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + Mapping.build(%__MODULE__{}, attrs) + end +end diff --git a/lib/horizon/operation/manage_sell_offer.ex b/lib/horizon/operation/manage_sell_offer.ex new file mode 100644 index 00000000..0b8beacb --- /dev/null +++ b/lib/horizon/operation/manage_sell_offer.ex @@ -0,0 +1,46 @@ +defmodule Stellar.Horizon.Operation.ManageSellOffer do + @moduledoc """ + Represents a `ManageSellOffer` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + amount: float(), + price: float(), + price_r: map(), + buying_asset_type: String.t(), + buying_asset_issuer: String.t(), + buying_asset_code: String.t(), + selling_asset_type: String.t(), + selling_asset_issuer: String.t(), + selling_asset_code: String.t(), + offer_id: String.t() + } + + defstruct [ + :amount, + :price, + :price_r, + :buying_asset_type, + :buying_asset_issuer, + :buying_asset_code, + :selling_asset_type, + :selling_asset_issuer, + :selling_asset_code, + :offer_id + ] + + @mapping [amount: :float, price: :float] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/operation/path_payment_strict_receive.ex b/lib/horizon/operation/path_payment_strict_receive.ex new file mode 100644 index 00000000..d3327612 --- /dev/null +++ b/lib/horizon/operation/path_payment_strict_receive.ex @@ -0,0 +1,50 @@ +defmodule Stellar.Horizon.Operation.PathPaymentStrictReceive do + @moduledoc """ + Represents a `PathPaymentStrictReceive` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + asset_type: float(), + asset_code: String.t(), + asset_issuer: String.t(), + from: String.t(), + to: String.t(), + amount: float(), + path: list(map()), + source_amount: String.t(), + source_max: String.t(), + source_asset_type: String.t(), + source_asset_code: String.t(), + source_asset_issuer: String.t() + } + + defstruct [ + :asset_type, + :asset_code, + :asset_issuer, + :from, + :to, + :amount, + :path, + :source_amount, + :source_max, + :source_asset_type, + :source_asset_code, + :source_asset_issuer + ] + + @mapping [amount: :float, source_amount: :float, source_max: :float] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/operation/path_payment_strict_send.ex b/lib/horizon/operation/path_payment_strict_send.ex new file mode 100644 index 00000000..e4029fd3 --- /dev/null +++ b/lib/horizon/operation/path_payment_strict_send.ex @@ -0,0 +1,50 @@ +defmodule Stellar.Horizon.Operation.PathPaymentStrictSend do + @moduledoc """ + Represents a `PathPaymentStrictSend` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + asset_type: float(), + asset_code: String.t(), + asset_issuer: String.t(), + from: String.t(), + to: String.t(), + amount: float(), + path: list(map()), + source_amount: String.t(), + destination_min: String.t(), + source_asset_type: String.t(), + source_asset_code: String.t(), + source_asset_issuer: String.t() + } + + defstruct [ + :asset_type, + :asset_code, + :asset_issuer, + :from, + :to, + :amount, + :path, + :source_amount, + :destination_min, + :source_asset_type, + :source_asset_code, + :source_asset_issuer + ] + + @mapping [amount: :float, source_amount: :float, destination_min: :float] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/operation/payment.ex b/lib/horizon/operation/payment.ex new file mode 100644 index 00000000..79ce77ac --- /dev/null +++ b/lib/horizon/operation/payment.ex @@ -0,0 +1,31 @@ +defmodule Stellar.Horizon.Operation.Payment do + @moduledoc """ + Represents a `Payment` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + asset_type: float(), + asset_code: String.t(), + asset_issuer: String.t(), + from: String.t(), + to: String.t(), + amount: float() + } + + defstruct [:asset_type, :asset_code, :asset_issuer, :from, :to, :amount] + + @mapping [amount: :float] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/operation/revoke_sponsorship.ex b/lib/horizon/operation/revoke_sponsorship.ex new file mode 100644 index 00000000..32e2b4e4 --- /dev/null +++ b/lib/horizon/operation/revoke_sponsorship.ex @@ -0,0 +1,42 @@ +defmodule Stellar.Horizon.Operation.RevokeSponsorship do + @moduledoc """ + Represents a `RevokeSponsorship` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type optional :: String.t() | nil + + @type t :: %__MODULE__{ + account_id: String.t(), + claimable_balance_id: String.t(), + data_account_id: String.t(), + data_name: String.t(), + offer_id: String.t(), + trustline_account_id: String.t(), + trustline_asset: String.t(), + signer_account_id: String.t(), + signer_key: String.t() + } + + defstruct [ + :account_id, + :claimable_balance_id, + :data_account_id, + :data_name, + :offer_id, + :trustline_account_id, + :trustline_asset, + :signer_account_id, + :signer_key + ] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + Mapping.build(%__MODULE__{}, attrs) + end +end diff --git a/lib/horizon/operation/set_options.ex b/lib/horizon/operation/set_options.ex new file mode 100644 index 00000000..a42acbf4 --- /dev/null +++ b/lib/horizon/operation/set_options.ex @@ -0,0 +1,56 @@ +defmodule Stellar.Horizon.Operation.SetOptions do + @moduledoc """ + Represents a `SetOptions` operation from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type weight :: 0..255 + + @type t :: %__MODULE__{ + signer_key: String.t(), + signer_weight: weight(), + master_key_weight: weight(), + low_threshold: weight(), + med_threshold: weight(), + high_threshold: weight(), + home_domain: String.t(), + set_flags: list(), + set_flags_s: list(), + clear_flags: list(), + clear_flags_s: list() + } + + defstruct [ + :signer_key, + :signer_weight, + :master_key_weight, + :low_threshold, + :med_threshold, + :high_threshold, + :home_domain, + :set_flags, + :set_flags_s, + :clear_flags, + :clear_flags_s + ] + + @mapping [ + signer_weight: :integer, + master_key_weight: :integer, + low_threshold: :integer, + med_threshold: :integer, + high_threshold: :integer + ] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/operations.ex b/lib/horizon/operations.ex new file mode 100644 index 00000000..00561fde --- /dev/null +++ b/lib/horizon/operations.ex @@ -0,0 +1,125 @@ +defmodule Stellar.Horizon.Operations do + @moduledoc """ + Exposes functions to interact with Operations in Horizon. + + You can: + * Retrieve an operation. + * List all operations. + * List operation's effects. + * List all payments. + + Horizon API reference: https://developers.stellar.org/api/resources/operations/ + """ + + alias Stellar.Horizon.{Collection, Effect, Error, Operation, Request} + + @type operation_id :: String.t() + @type options :: Keyword.t() + @type resource :: Operation.t() | Collection.t() + @type response :: {:ok, resource()} | {:error, Error.t()} + + @endpoint "operations" + @payments_endpoint "payments" + + @doc """ + Retrieves information of a specific operation. + + ## Parameters: + * `operation_id`: The ID number for the operation. + + ## Examples + + iex> Operations.retrieve(121693057904021505) + {:ok, %Operation{}} + """ + @spec retrieve(operation_id :: operation_id(), options :: options()) :: response() + def retrieve(operation_id, options \\ []) do + :get + |> Request.new(@endpoint, path: operation_id) + |> Request.add_query(options, extra_params: [:join]) + |> Request.perform() + |> Request.results(&Operation.new(&1)) + end + + @doc """ + Lists all successful operations. + + ## Options + + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + * `include_failed`: Set to true to include failed operations in results. + * `join`: Set to `transactions` to include the transactions which created each of the operations in the response. + + ## Examples + + iex> Operations.all(limit: 10, order: :asc) + {:ok, %Collection{records: [%Operation{}, ...]}} + + # include failed + iex> Operations.all(limit: 10, include_failed: true) + {:ok, %Collection{records: [%Operation{}, ...]}} + + # join transactions + iex> Operations.all(limit: 10, join: "transactions") + {:ok, %Collection{records: [%Operation{transaction: %Transaction{}}, ...]}} + """ + @spec all(options :: options()) :: response() + def all(options \\ []) do + :get + |> Request.new(@endpoint) + |> Request.add_query(options, extra_params: [:include_failed, :join]) + |> Request.perform() + |> Request.results(&Collection.new({Operation, &1})) + end + + @doc """ + Lists successful payment-related operations. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + * `include_failed`: Set to true to include failed operations in results. + * `join`: Set to `transactions` to include the transactions which created each of the operations in the response. + + ## Examples + + iex> Operations.list_payments(limit: 20) + {:ok, %Collection{records: [%Operation{body: %Payment{}}, ...]}} + """ + @spec list_payments(options :: options()) :: response() + def list_payments(options \\ []) do + :get + |> Request.new(@payments_endpoint) + |> Request.add_query(options, extra_params: [:include_failed, :join]) + |> Request.perform() + |> Request.results(&Collection.new({Operation, &1})) + end + + @doc """ + Lists the effects of a specific operation. + + ## Parameters + * `operation_id`: The ID number for the operation. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> Operations.list_effects(121693057904021505, limit: 20) + {:ok, %Collection{records: [%Effect{}, ...]}} + """ + @spec list_effects(operation_id :: operation_id(), options :: options()) :: response() + def list_effects(operation_id, options \\ []) do + :get + |> Request.new(@endpoint, path: operation_id, segment: "effects") + |> Request.add_query(options) + |> Request.perform() + |> Request.results(&Collection.new({Effect, &1})) + end +end diff --git a/lib/horizon/request.ex b/lib/horizon/request.ex new file mode 100644 index 00000000..f28bfb46 --- /dev/null +++ b/lib/horizon/request.ex @@ -0,0 +1,142 @@ +defmodule Stellar.Horizon.Request do + @moduledoc """ + A module for working with requests to the Horizon API. + + Requests are composed in a functional manner. The request does not happen + until it is configured and passed to `perform/1`. + + Generally intended to be used internally, but can also be used by end-users + to work around missing endpoints (if any). + + At a minimum, a request must have the endpoint and method specified to be valid. + """ + + alias Stellar.Horizon.Error + alias Stellar.Horizon.Client, as: Horizon + + @type method :: :get | :post + @type headers :: [{binary(), binary()}, ...] | [] + @type body :: Keyword.t() + @type query :: Keyword.t() + @type encoded_query :: String.t() | nil + @type endpoint :: String.t() | nil + @type path :: String.t() | nil + @type segment :: String.t() | nil + @type opts :: Keyword.t() + @type params :: Keyword.t() + @type query_params :: list(atom()) + @type response :: {:ok, map()} | {:error, Error.t()} + @type parsed_response :: {:ok, struct()} | {:error, Error.t()} + + @type t :: %__MODULE__{ + method: method(), + endpoint: endpoint(), + path: path(), + segment: segment(), + segment_path: path(), + query: query(), + headers: headers(), + body: body(), + encoded_query: encoded_query() + } + + defstruct [ + :method, + :headers, + :body, + :endpoint, + :path, + :segment, + :segment_path, + :query, + :encoded_query + ] + + @default_query_params ~w(cursor order limit)a + + @spec new(method :: method(), endpoint :: endpoint(), opts :: opts()) :: t() + def new(method, endpoint, opts \\ []) when method in [:get, :post] do + path = Keyword.get(opts, :path) + segment = Keyword.get(opts, :segment) + segment_path = Keyword.get(opts, :segment_path) + + %__MODULE__{ + method: method, + endpoint: endpoint, + path: path, + segment: segment, + segment_path: segment_path, + query: [], + headers: [], + body: [] + } + end + + @spec add_body(request :: t(), body :: body()) :: t() + def add_body(%__MODULE__{} = request, body) do + %{request | body: body} + end + + def add_body(_request, _body), do: {:error, :invalid_request_body} + + @spec add_headers(request :: t(), headers :: headers()) :: t() + def add_headers(%__MODULE__{} = request, headers) do + %{request | headers: headers} + end + + @spec add_query(request :: t(), params :: params(), opts :: opts()) :: t() + def add_query(request, params \\ [], opts \\ []) + + def add_query(%__MODULE__{} = request, params, opts) do + extra_params = Keyword.get(opts, :extra_params, []) + query_params = @default_query_params ++ extra_params + + %{request | query: params, encoded_query: build_query_string(params, query_params)} + end + + @spec perform(request :: t()) :: response() + def perform(%__MODULE__{method: method, headers: headers, body: body} = request) do + encoded_body = URI.encode_query(body) + + request + |> build_request_url() + |> (&Horizon.request(method, &1, headers, encoded_body)).() + end + + @spec results(response :: response(), resource :: function()) :: parsed_response() + def results({:ok, results}, resource), do: {:ok, resource.(results)} + def results(error, _resource), do: error + + @spec build_request_url(request :: t()) :: binary() + defp build_request_url(%__MODULE__{ + endpoint: endpoint, + path: path, + segment: segment, + segment_path: segment_path, + encoded_query: encoded_query + }) do + IO.iodata_to_binary([ + if(endpoint, do: ["/" | to_string(endpoint)], else: []), + if(path, do: ["/" | to_string(path)], else: []), + if(segment, do: ["/" | to_string(segment)], else: []), + if(segment_path, do: ["/" | to_string(segment_path)], else: []), + if(encoded_query, do: ["?" | encoded_query], else: []) + ]) + end + + @spec build_query_string(params :: params(), query_params :: query_params()) :: encoded_query() + defp build_query_string(params, query_params) do + params + |> Keyword.take(query_params) + |> Enum.reject(&is_empty_param/1) + |> encode_query() + end + + @spec encode_query(query :: query()) :: encoded_query() + defp encode_query([]), do: nil + defp encode_query(query), do: URI.encode_query(query) + + @spec is_empty_param(param :: {atom(), any()}) :: boolean() + defp is_empty_param({_key, nil}), do: true + defp is_empty_param({_key, value}), do: to_string(value) == "" +end diff --git a/lib/horizon/trade.ex b/lib/horizon/trade.ex new file mode 100644 index 00000000..7cf1b843 --- /dev/null +++ b/lib/horizon/trade.ex @@ -0,0 +1,71 @@ +defmodule Stellar.Horizon.Trade do + @moduledoc """ + Represents a `Trade` resource from Horizon API. + """ + + @behaviour Stellar.Horizon.Resource + + alias Stellar.Horizon.Mapping + + @type t :: %__MODULE__{ + id: String.t(), + paging_token: String.t(), + ledger_close_time: DateTime.t(), + trade_type: String.t(), + base_account: String.t() | nil, + base_offer_id: String.t() | nil, + base_liquidity_pool_id: String.t() | nil, + base_amount: float(), + base_asset_type: String.t(), + base_asset_code: String.t(), + base_asset_issuer: String.t(), + counter_account: String.t() | nil, + counter_offer_id: String.t() | nil, + counter_liquidity_pool_id: String.t() | nil, + counter_amount: float(), + counter_asset_type: String.t(), + counter_asset_code: String.t(), + counter_asset_issuer: String.t(), + price: map(), + base_is_seller: boolean() + } + + defstruct [ + :id, + :paging_token, + :ledger_close_time, + :trade_type, + :base_account, + :base_offer_id, + :base_liquidity_pool_id, + :base_amount, + :base_asset_type, + :base_asset_code, + :base_asset_issuer, + :counter_account, + :counter_offer_id, + :counter_liquidity_pool_id, + :counter_amount, + :counter_asset_type, + :counter_asset_code, + :counter_asset_issuer, + :price, + :base_is_seller + ] + + @mapping [ + base_offer_id: :integer, + ledger_close_time: :date_time, + base_amount: :float, + counter_amount: :float + ] + + @impl true + def new(attrs, opts \\ []) + + def new(attrs, _opts) do + %__MODULE__{} + |> Mapping.build(attrs) + |> Mapping.parse(@mapping) + end +end diff --git a/lib/horizon/trades.ex b/lib/horizon/trades.ex new file mode 100644 index 00000000..515ce080 --- /dev/null +++ b/lib/horizon/trades.ex @@ -0,0 +1,79 @@ +defmodule Stellar.Horizon.Trades do + @moduledoc """ + Exposes functions to interact with Trades in Horizon. + + You can: + * List all trades. + + Horizon API reference: https://developers.stellar.org/api/resources/trades/ + """ + + alias Stellar.Horizon.{Collection, Error, Request, Trade} + + @type options :: Keyword.t() + @type resource :: Trade.t() | Collection.t() + @type response :: {:ok, resource()} | {:error, Error.t()} + + @endpoint "trades" + + @doc """ + Lists all trades. + + ## Options + + * `offer_id`: The offer ID. Used to filter for trades originating from a specific offer. + * `base_asset_type`: The type for the base asset. Either `native`, `credit_alphanum4`, or `credit_alphanum12`. + * `base_asset_issuer`: The account ID of the base asset’s issuer. + * `base_asset_code`: The code for the base asset. + * `counter_asset_type`: The type for the counter asset. Either `native`, `credit_alphanum4`, or `credit_alphanum12`. + * `counter_asset_issuer`: The account ID of the counter asset’s issuer. + * `counter_asset_code`: The code for the counter asset. + * `trade_type`: Can be set to `all`, `orderbook`, or `liquidity_pools` to filter only trades executed across a given mechanism. + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> Trades.all(limit: 20, order: :asc) + {:ok, %Collection{records: [%Trade{}, ...]}} + + # list by offer_id + iex> Trades.all(offer_id: 165563085) + {:ok, %Collection{records: [%Trade{}, ...]}} + + # list by base_asset_type and base_asset_code + iex> Trades.all(base_asset_type: "credit_alphanum4", base_asset_code: "TEST") + {:ok, %Collection{records: [%Trade{}, ...]}} + + # list by counter_asset_issuer + iex> Trades.all(counter_asset_issuer: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", limit: 20) + {:ok, %Collection{records: [%Trade{}, ...]}} + + # list by trade_type + iex> Trades.all(trade_type: "liquidity_pools", limit: 20) + {:ok, %Collection{records: [%Trade{}, ...]}} + """ + @spec all(options :: options()) :: response() + def all(options \\ []) do + :get + |> Request.new(@endpoint) + |> Request.add_query(options, extra_params: allowed_query_options()) + |> Request.perform() + |> Request.results(&Collection.new({Trade, &1})) + end + + @spec allowed_query_options() :: list() + defp allowed_query_options do + [ + :offer_id, + :base_asset_type, + :base_asset_issuer, + :base_asset_code, + :counter_asset_type, + :counter_asset_issuer, + :counter_asset_code, + :trade_type + ] + end +end diff --git a/lib/horizon/transaction/transaction.ex b/lib/horizon/transaction.ex similarity index 100% rename from lib/horizon/transaction/transaction.ex rename to lib/horizon/transaction.ex diff --git a/lib/horizon/transactions.ex b/lib/horizon/transactions.ex index a7710af2..ab0c60c3 100644 --- a/lib/horizon/transactions.ex +++ b/lib/horizon/transactions.ex @@ -3,26 +3,145 @@ defmodule Stellar.Horizon.Transactions do Exposes functions to interact with Transactions in Horizon. You can: - - Create a transaction. + * Create a transaction. + * Retrieve a transaction. + * List all transactions. + * List a transaction's effects. + * List a transaction's operations. Horizon API reference: https://developers.stellar.org/api/resources/transactions/ """ - alias Stellar.Horizon.{Error, Transaction} - alias Stellar.Horizon.Client, as: Horizon + alias Stellar.Horizon.{Collection, Effect, Error, Operation, Transaction, Request} - @type response :: {:ok, Transaction.t()} | {:error, Error.t()} + @type hash :: String.t() + @type options :: Keyword.t() + @type resource :: Transaction.t() | Collection.t() + @type response :: {:ok, resource()} | {:error, Error.t()} - @endpoint "/transactions/" + @endpoint "transactions" + @doc """ + Creates a transaction to the Stellar network. + + ## Parameters: + * `tx`: The base64-encoded XDR of the transaction. + + ## Examples + + iex> Transactions.create("AAAAAgAAAACQcEK2yfQA9CHrX+2UMkRIb/1wzltKqHpbdIcJbp+b/QAAAGQAAiEYAAAAAQAAAAEAAAAAAAAAAAAAAABgXP3QAAAAAQAAABBUZXN0IFRyYW5zYWN0aW9uAAAAAQAAAAAAAAABAAAAAJBwQrbJ9AD0Ietf7ZQyREhv/XDOW0qoelt0hwlun5v9AAAAAAAAAAAF9eEAAAAAAAAAAAFun5v9AAAAQKdJnG8QRiv9xGp1Oq7ACv/xR2BnNqjfUHrGNua7m4tWbrun3+GmAj6ca3xz+4ZppWRTbvTUcCxvpbHERZ85QgY=") + {:ok, %Transaction{}} + """ @spec create(base64_envelope :: String.t()) :: response() def create(base64_envelope) do - headers = [{"Content-Type", "application/x-www-form-urlencoded"}] - body = URI.encode_query(tx: base64_envelope) + :post + |> Request.new(@endpoint) + |> Request.add_headers([{"Content-Type", "application/x-www-form-urlencoded"}]) + |> Request.add_body(tx: base64_envelope) + |> Request.perform() + |> Request.results(&Transaction.new(&1)) + end + + @doc """ + Retrieves information of a specific transaction. + + ## Parameters: + * `hash`: A hex-encoded SHA-256 hash of this transaction’s XDR-encoded form. + + ## Examples + + iex> Transactions.retrieve("5ebd5c0af4385500b53dd63b0ef5f6e8feef1a7e1c86989be3cdcce825f3c0cc") + {:ok, %Transaction{}} + """ + @spec retrieve(hash :: hash()) :: response() + def retrieve(hash) do + :get + |> Request.new(@endpoint, path: hash) + |> Request.perform() + |> Request.results(&Transaction.new(&1)) + end + + @doc """ + Lists all successful transactions. + + ## Options + + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + * `include_failed`: Set to true to include failed operations in results. + + ## Examples + + iex> Transactions.all(limit: 10, order: :asc) + {:ok, %Collection{records: [%Transaction{}, ...]}} - case Horizon.request(:post, @endpoint, headers, body) do - {:ok, tx} -> {:ok, Transaction.new(tx)} - error -> error - end + # include failed + iex> Transactions.all(limit: 10, include_failed: true) + {:ok, %Collection{records: [%Transaction{}, ...]}} + """ + @spec all(options :: options()) :: response() + def all(options \\ []) do + :get + |> Request.new(@endpoint) + |> Request.add_query(options, extra_params: [:include_failed]) + |> Request.perform() + |> Request.results(&Collection.new({Transaction, &1})) + end + + @doc """ + Lists the effects of a specific transaction. + + ## Parameters + * `hash`: A hex-encoded SHA-256 hash of this transaction’s XDR-encoded form. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + + ## Examples + + iex> Transactions.list_effects("6b983a4e0dc3c04f4bd6b9037c55f70a09c434dfd01492be1077cf7ea68c2e4a", limit: 20) + {:ok, %Collection{records: [%Effect{}, ...]}} + """ + @spec list_effects(hash :: hash(), options :: options()) :: response() + def list_effects(hash, options \\ []) do + :get + |> Request.new(@endpoint, path: hash, segment: "effects") + |> Request.add_query(options) + |> Request.perform() + |> Request.results(&Collection.new({Effect, &1})) + end + + @doc """ + Lists successful operations for a specific transaction. + + ## Parameters + * `hash`: A hex-encoded SHA-256 hash of this transaction’s XDR-encoded form. + + ## Options + * `cursor`: A number that points to a specific location in a collection of responses and is pulled from the `paging_token` value of a record. + * `order`: A designation of the order in which records should appear. Options include `asc` (ascending) or `desc` (descending). + * `limit`: The maximum number of records returned. The limit can range from 1 to 200. Defaults to 10. + * `include_failed`: Set to true to include failed operations in results. + * `join`: Set to `transactions` to include the transactions which created each of the operations in the response. + + ## Examples + + iex> Transactions.list_operations("6b983a4e0dc3c04f4bd6b9037c55f70a09c434dfd01492be1077cf7ea68c2e4a", limit: 20) + {:ok, %Collection{records: [%Operation{}, ...]}} + + # join transactions + iex> Transactions.list_operations("6b983a4e0dc3c04f4bd6b9037c55f70a09c434dfd01492be1077cf7ea68c2e4a", join: "transactions") + {:ok, %Collection{records: [%Operation{transaction: %Transaction{}}, ...]}} + """ + @spec list_operations(hash :: hash(), options :: options()) :: response() + def list_operations(hash, options \\ []) do + :get + |> Request.new(@endpoint, path: hash, segment: "operations") + |> Request.add_query(options, extra_params: [:include_failed, :join]) + |> Request.perform() + |> Request.results(&Collection.new({Operation, &1})) end end diff --git a/mix.exs b/mix.exs index 09015e41..1dceeb2e 100644 --- a/mix.exs +++ b/mix.exs @@ -2,7 +2,7 @@ defmodule Stellar.MixProject do use Mix.Project @github_url "https://github.com/kommitters/stellar_sdk" - @version "0.3.0" + @version "0.4.0" def project do [ @@ -82,9 +82,48 @@ defmodule Stellar.MixProject do defp groups_for_modules do [ - Horizon: ~r/^Stellar\.Horizon\./, - KeyPair: ~r/^Stellar\.KeyPair\./, - TxBuild: ~r/^Stellar\.TxBuild\./ + "Building a Transaction": [ + Stellar.TxBuild, + Stellar.TxBuild.Spec, + Stellar.TxBuild.Default, + Stellar.TxBuild.AccountMerge, + Stellar.TxBuild.BumpSequence, + Stellar.TxBuild.BeginSponsoringFutureReserves, + Stellar.TxBuild.ChangeTrust, + Stellar.TxBuild.Clawback, + Stellar.TxBuild.ClawbackClaimableBalance, + Stellar.TxBuild.CreateAccount, + Stellar.TxBuild.CreatePassiveSellOffer, + Stellar.TxBuild.EndSponsoringFutureReserves, + Stellar.TxBuild.ManageData, + Stellar.TxBuild.ManageSellOffer, + Stellar.TxBuild.ManageBuyOffer, + Stellar.TxBuild.Payment, + Stellar.TxBuild.PathPaymentStrictSend, + Stellar.TxBuild.PathPaymentStrictReceive, + Stellar.TxBuild.SetOptions + ], + "Querying Horizon": [ + Stellar.Horizon.Ledgers, + Stellar.Horizon.Transactions, + Stellar.Horizon.Operations, + Stellar.Horizon.Effects, + Stellar.Horizon.Accounts, + Stellar.Horizon.Offers, + Stellar.Horizon.Trades, + Stellar.Horizon.Assets, + Stellar.Horizon.ClaimableBalances, + Stellar.Horizon.LiquidityPools, + Stellar.Horizon.Accounts + ], + KeyPairs: [ + Stellar.KeyPair, + Stellar.KeyPair.Spec, + Stellar.KeyPair.Default + ], + "Horizon Resources": ~r/^Stellar\.Horizon\./, + "TxBuild Resources": ~r/^Stellar\.TxBuild\./, + Utils: Stellar.Network ] end diff --git a/test/horizon/account/data_test.exs b/test/horizon/account/data_test.exs new file mode 100644 index 00000000..0873fe6f --- /dev/null +++ b/test/horizon/account/data_test.exs @@ -0,0 +1,17 @@ +defmodule Stellar.Horizon.Account.DataTest do + use ExUnit.Case + + alias Stellar.Horizon.Account.Data + + setup do + %{attrs: %{value: "MQ=="}} + end + + test "new/2", %{attrs: attrs} do + %Data{value: "MQ=="} = Data.new(attrs) + end + + test "new/2 empty_attrs" do + %Data{value: nil} = Data.new(%{}) + end +end diff --git a/test/horizon/account/account_test.exs b/test/horizon/account_test.exs similarity index 100% rename from test/horizon/account/account_test.exs rename to test/horizon/account_test.exs diff --git a/test/horizon/accounts_test.exs b/test/horizon/accounts_test.exs index 93b9ac35..2099d3e4 100644 --- a/test/horizon/accounts_test.exs +++ b/test/horizon/accounts_test.exs @@ -18,17 +18,127 @@ defmodule Stellar.Horizon.Client.CannedAccountRequests do {:ok, 404, [], json_error} end - def request(:get, @base_url <> "/accounts/" <> _account_id, _headers, _body, _opts) do + def request( + :get, + @base_url <> + "/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/effects" <> _query, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("effects") + {:ok, 200, [], json_body} + end + + def request( + :get, + @base_url <> + "/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/offers" <> _query, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("offers") + {:ok, 200, [], json_body} + end + + def request( + :get, + @base_url <> + "/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/trades" <> _query, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("trades") + {:ok, 200, [], json_body} + end + + def request( + :get, + @base_url <> + "/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/transactions" <> + _query, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("transactions") + {:ok, 200, [], json_body} + end + + def request( + :get, + @base_url <> + "/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/operations" <> + _query, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("operations") + {:ok, 200, [], json_body} + end + + def request( + :get, + @base_url <> + "/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/payments" <> _query, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("payments") + {:ok, 200, [], json_body} + end + + def request( + :get, + @base_url <> + "/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/data" <> _query, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("data") + {:ok, 200, [], json_body} + end + + def request( + :get, + @base_url <> "/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + _headers, + _body, + _opts + ) do json_body = Horizon.fixture("account") {:ok, 200, [], json_body} end + + def request(:get, @base_url <> "/accounts" <> _query, _headers, _body, _opts) do + json_body = Horizon.fixture("accounts") + {:ok, 200, [], json_body} + end end defmodule Stellar.Horizon.AccountsTest do use ExUnit.Case alias Stellar.Horizon.Client.CannedAccountRequests - alias Stellar.Horizon.{Account, Accounts, Error} + + alias Stellar.Horizon.{ + Account, + Accounts, + Collection, + Effect, + Error, + Offer, + Operation, + Trade, + Transaction + } + + alias Stellar.Horizon.Operation.{CreateAccount, Payment, SetOptions} setup do Application.put_env(:stellar_sdk, :http_client, CannedAccountRequests) @@ -40,36 +150,235 @@ defmodule Stellar.Horizon.AccountsTest do %{account_id: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD"} end - describe "retrieve/1" do - test "success", %{account_id: account_id} do - {:ok, %Account{id: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD"}} = - Accounts.retrieve(account_id) - end - - test "error" do - {:error, - %Error{ - extras: nil, - status_code: 404, - title: "Resource Missing", - type: "https://stellar.org/horizon-errors/not_found" - }} = Accounts.retrieve("not_found") - end - end - - describe "fetch_next_sequence_number/1" do - test "success", %{account_id: account_id} do - {:ok, 17_218_523_889_681} = Accounts.fetch_next_sequence_number(account_id) - end - - test "error" do - {:error, - %Error{ - extras: nil, - status_code: 404, - title: "Resource Missing", - type: "https://stellar.org/horizon-errors/not_found" - }} = Accounts.fetch_next_sequence_number("not_found") - end + test "retrieve/1", %{account_id: account_id} do + {:ok, + %Account{ + account_id: ^account_id, + balances: [ + %Account.Balance{ + asset_code: "EURT", + asset_issuer: "GAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S", + asset_type: "credit_alphanum4" + }, + %Account.Balance{ + asset_code: nil, + asset_issuer: nil, + asset_type: "native", + balance: 100.99983 + } + ], + data: %{NFT: "QkdFR0ZFVEVHRUhIRUVI"}, + flags: %Account.Flags{ + auth_clawback_enabled: false, + auth_immutable: false, + auth_required: false, + auth_revocable: false + }, + id: ^account_id, + last_modified_time: ~U[2022-01-20 21:21:44Z], + paging_token: ^account_id, + sequence: 17_218_523_889_680, + signers: [ + _signer1, + _signer2, + %Account.Signer{ + key: ^account_id, + type: "ed25519_public_key", + weight: 1 + } + ], + thresholds: %Account.Thresholds{ + high_threshold: 3, + low_threshold: 1, + med_threshold: 2 + } + }} = Accounts.retrieve(account_id) + end + + test "fetch_next_sequence_number/1", %{account_id: account_id} do + {:ok, 17_218_523_889_681} = Accounts.fetch_next_sequence_number(account_id) + end + + test "all/1" do + {:ok, + %Collection{ + records: [ + %Account{id: "GD42RQNXTRIW6YR3E2HXV5T2AI27LBRHOERV2JIYNFMXOBA234SWLQQB"}, + %Account{id: "GAP2KHWUMOHY7IO37UJY7SEBIITJIDZS5DRIIQRPEUT4VUKHZQGIRWS4"}, + %Account{id: "GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB"} + ] + }} = Accounts.all() + end + + test "list_effects/1", %{account_id: account_id} do + {:ok, + %Collection{ + next: + "https://horizon.stellar.org/effects?cursor=12884905985-3\u0026limit=3\u0026order=asc", + prev: + "https://horizon.stellar.org/effects?cursor=12884905985-1\u0026limit=3\u0026order=desc", + records: [ + %Effect{type: "account_created", created_at: ~U[2015-09-30 17:15:54Z]}, + %Effect{type: "account_debited", created_at: ~U[2015-09-30 17:16:54Z]}, + %Effect{type: "signer_created", created_at: ~U[2015-09-30 17:17:54Z]} + ] + }} = Accounts.list_effects(account_id, limit: 3) + end + + test "list_offers/1", %{account_id: account_id} do + {:ok, + %Collection{ + next: + "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/offers?cursor=164943216\u0026limit=10\u0026order=asc", + prev: + "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/offers?cursor=164555927\u0026limit=10\u0026order=desc", + records: [ + %Offer{ + seller: ^account_id, + buying: %{ + asset_type: "credit_alphanum4", + asset_code: "BTCN", + asset_issuer: "GD5J6HLF5666X4AZLTFTXLY46J5SW7EXRKBLEYPJP33S33MXZGV6CWFN" + }, + amount: 214.9999939 + }, + %Offer{ + seller: ^account_id, + selling: %{ + asset_type: "credit_alphanum4", + asset_code: "BTCN", + asset_issuer: "GD5J6HLF5666X4AZLTFTXLY46J5SW7EXRKBLEYPJP33S33MXZGV6CWFN" + }, + amount: 24.9999990 + } + ] + }} = Accounts.list_offers(account_id) + end + + test "list_trades/1", %{account_id: account_id} do + {:ok, + %Collection{ + records: [ + %Trade{ + base_offer_id: 165_561_423, + counter_account: ^account_id, + base_amount: 4433.20 + }, + %Trade{ + base_offer_id: 165_561_423, + counter_account: ^account_id, + base_amount: 10.0 + }, + %Trade{ + base_offer_id: 165_561_423, + counter_account: ^account_id, + base_amount: 748.5338945 + } + ] + }} = Accounts.list_trades(account_id) + end + + test "list_transactions/1", %{account_id: account_id} do + {:ok, + %Collection{ + next: + "https://horizon.stellar.org/transactions?cursor=33736968114176\u0026limit=3\u0026order=asc", + prev: + "https://horizon.stellar.org/transactions?cursor=12884905984\u0026limit=3\u0026order=desc", + records: [ + %Transaction{ + id: "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889", + source_account: ^account_id, + ledger: 7840 + }, + %Transaction{ + id: "2db4b22ca018119c5027a80578813ffcf582cda4aa9e31cd92b43cf1bda4fc5a", + source_account: ^account_id, + ledger: 7841 + }, + %Transaction{ + id: "3ce2aca2fed36da2faea31352c76c5e412348887a4c119b1e90de8d1b937396a", + source_account: ^account_id, + ledger: 7855 + } + ] + }} = Accounts.list_transactions(account_id, limit: 3, order: :asc) + end + + test "list_operations/1", %{account_id: account_id} do + {:ok, + %Collection{ + next: + "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31/operations?cursor=12884905985&limit=3&order=desc", + prev: + "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31/operations?cursor=12884905987&limit=3&order=asc", + records: [ + %Operation{ + body: %SetOptions{}, + source_account: ^account_id, + type: "set_options", + type_i: 5 + }, + %Operation{ + body: %Payment{}, + source_account: ^account_id, + type: "payment", + type_i: 1 + }, + %Operation{ + body: %CreateAccount{}, + source_account: ^account_id, + type: "create_account", + type_i: 0 + } + ] + }} = Accounts.list_operations(account_id, limit: 3, order: :desc) + end + + test "list_payments/1", %{account_id: account_id} do + {:ok, + %Collection{ + records: [ + %Operation{ + source_account: ^account_id, + body: %Payment{from: ^account_id, amount: 0.0000001}, + type: "payment" + }, + %Operation{ + body: %Payment{to: ^account_id, amount: 1.0}, + type: "payment", + type_i: 1 + }, + %Operation{ + body: %Payment{to: ^account_id, amount: 1.0}, + type: "payment", + type_i: 1 + } + ] + }} = Accounts.list_payments(account_id, limit: 3) + end + + test "data/1", %{account_id: account_id} do + {:ok, %Account.Data{value: "QkdFR0ZFVEVHRUhIRUVI"}} = Accounts.data(account_id, "NFT") + end + + test "error" do + {:error, + %Error{ + extras: nil, + status_code: 404, + title: "Resource Missing", + type: "https://stellar.org/horizon-errors/not_found" + }} = Accounts.retrieve("not_found") + end + + test "fetch_next_sequence_number error" do + {:error, + %Error{ + extras: nil, + status_code: 404, + title: "Resource Missing", + type: "https://stellar.org/horizon-errors/not_found" + }} = Accounts.fetch_next_sequence_number("not_found") end end diff --git a/test/horizon/asset_test.exs b/test/horizon/asset_test.exs new file mode 100644 index 00000000..ea7e5f16 --- /dev/null +++ b/test/horizon/asset_test.exs @@ -0,0 +1,47 @@ +defmodule Stellar.Horizon.AssetTest do + use ExUnit.Case + + alias Stellar.Test.Fixtures.Horizon + alias Stellar.Horizon.Asset + + setup do + json_body = Horizon.fixture("asset") + attrs = Jason.decode!(json_body, keys: :atoms) + + %{attrs: attrs} + end + + test "new/2", %{attrs: attrs} do + %Asset{ + asset_type: "credit_alphanum4", + asset_code: "USD", + asset_issuer: "GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX", + claimable_balances_amount: 36_303.8674450, + amount: 1_347_404.4083346, + num_accounts: 9390, + accounts: %{ + authorized: 9390, + authorized_to_maintain_liabilities: 1240, + unauthorized: 5 + }, + balances: %{ + authorized: 1_347_404.4083346, + authorized_to_maintain_liabilities: 177_931.9984610, + unauthorized: 717.4677360 + } + } = Asset.new(attrs) + end + + test "new/2 empty_attrs" do + %Asset{ + asset_type: nil, + asset_code: nil, + asset_issuer: nil, + claimable_balances_amount: nil, + amount: nil, + num_accounts: nil, + accounts: nil, + balances: nil + } = Asset.new(%{}) + end +end diff --git a/test/horizon/assets_test.exs b/test/horizon/assets_test.exs new file mode 100644 index 00000000..b07b46fe --- /dev/null +++ b/test/horizon/assets_test.exs @@ -0,0 +1,124 @@ +defmodule Stellar.Horizon.Client.CannedAssetRequests do + @moduledoc false + + alias Stellar.Horizon.Error + alias Stellar.Test.Fixtures.Horizon + + @base_url "https://horizon-testnet.stellar.org" + + @spec request( + method :: atom(), + url :: String.t(), + headers :: list(), + body :: String.t(), + options :: list() + ) :: {:ok, non_neg_integer(), list(), String.t()} | {:error, atom()} + def request(:get, @base_url <> "/assets?asset_code=" <> _asset_code, _headers, _body, _opts) do + json_body = Horizon.fixture("assets") + {:ok, 200, [], json_body} + end + + def request(:get, @base_url <> "/assets?asset_issuer=" <> _asset_issuer, _headers, _body, _opts) do + json_body = Horizon.fixture("assets") + {:ok, 200, [], json_body} + end + + def request(:get, @base_url <> "/assets?cursor=error", _headers, _body, _opts) do + json_error = Horizon.fixture("400") + {:ok, 400, [], json_error} + end + + def request(:get, @base_url <> "/assets" <> _query, _headers, _body, _opts) do + json_body = Horizon.fixture("assets") + {:ok, 200, [], json_body} + end +end + +defmodule Stellar.Horizon.AssetsTest do + use ExUnit.Case + + alias Stellar.Horizon.Client.CannedAssetRequests + alias Stellar.Horizon.{Asset, Assets, Collection, Error} + + setup do + Application.put_env(:stellar_sdk, :http_client, CannedAssetRequests) + + on_exit(fn -> + Application.delete_env(:stellar_sdk, :http_client) + end) + + %{ + asset_code: "BTCN", + asset_issuer: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD" + } + end + + test "all/1" do + {:ok, + %Collection{ + next: + "https://horizon-testnet.stellar.org/assets?cursor=MTK_GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD_credit_alphanum4&limit=3&order=desc", + prev: + "https://horizon-testnet.stellar.org/assets?cursor=BTCNEW2022_GCEU4UKMCN6XDTXOKVR35HEMSNT5HRT36ZM6QCZPFEVSSP6M2NCYZOJW_credit_alphanum12&limit=3&order=asc", + records: [ + %Asset{ + asset_type: "credit_alphanum12", + asset_code: "BTCNEW2022", + asset_issuer: "GCEU4UKMCN6XDTXOKVR35HEMSNT5HRT36ZM6QCZPFEVSSP6M2NCYZOJW" + }, + %Asset{ + asset_type: "credit_alphanum4", + asset_code: "BTCN", + asset_issuer: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD" + }, + %Asset{ + asset_type: "credit_alphanum4", + asset_code: "MTK", + asset_issuer: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD" + } + ] + }} = Assets.all(limit: 3) + end + + test "list_by_asset_code/2", %{asset_code: asset_code} do + {:ok, + %Collection{ + records: [ + _asset, + %Asset{ + asset_code: "BTCN", + asset_issuer: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD" + }, + _asset2 + ] + }} = Assets.list_by_asset_code(asset_code, limit: 3) + end + + test "list_by_asset_issuer/2", %{asset_issuer: asset_issuer} do + {:ok, + %Collection{ + records: [ + _asset, + %Asset{ + asset_code: "BTCN", + asset_issuer: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD" + }, + %Asset{ + asset_code: "MTK", + asset_issuer: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD" + } + ] + }} = Assets.list_by_asset_issuer(asset_issuer, limit: 3) + end + + test "error" do + {:error, + %Error{ + detail: "The request you sent was invalid in some way.", + extras: %{invalid_field: "cursor", reason: "cursor: invalid value"}, + status_code: 400, + title: "Bad Request", + type: "https://stellar.org/horizon-errors/bad_request" + }} = Assets.all(cursor: "error") + end +end diff --git a/test/horizon/claimable_balance_test.exs b/test/horizon/claimable_balance_test.exs new file mode 100644 index 00000000..8e91120e --- /dev/null +++ b/test/horizon/claimable_balance_test.exs @@ -0,0 +1,47 @@ +defmodule Stellar.Horizon.ClaimableBalanceTest do + use ExUnit.Case + + alias Stellar.Test.Fixtures.Horizon + alias Stellar.Horizon.ClaimableBalance + + setup do + json_body = Horizon.fixture("claimable_balance") + attrs = Jason.decode!(json_body, keys: :atoms) + + %{attrs: attrs} + end + + test "new/2", %{attrs: attrs} do + %ClaimableBalance{ + id: "00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072", + asset: "native", + amount: 10.0000000, + claimants: [ + %{ + destination: "GC3C4AKRBQLHOJ45U4XG35ESVWRDECWO5XLDGYADO6DPR3L7KIDVUMML", + predicate: %{ + and: [ + _predicate, + %{ + not: %{unconditional: true} + } + ] + } + } + ], + last_modified_ledger: 28_411_995, + last_modified_time: ~U[2020-02-26 19:29:16Z] + } = ClaimableBalance.new(attrs) + end + + test "new/2 empty_attrs" do + %ClaimableBalance{ + id: nil, + asset: nil, + amount: nil, + claimants: nil, + last_modified_ledger: nil, + last_modified_time: nil + } = ClaimableBalance.new(%{}) + end +end diff --git a/test/horizon/claimable_balances_test.exs b/test/horizon/claimable_balances_test.exs new file mode 100644 index 00000000..273aec1c --- /dev/null +++ b/test/horizon/claimable_balances_test.exs @@ -0,0 +1,315 @@ +defmodule Stellar.Horizon.Client.CannedClaimableBalanceRequests do + @moduledoc false + + alias Stellar.Horizon.Error + alias Stellar.Test.Fixtures.Horizon + + @base_url "https://horizon-testnet.stellar.org" + + @spec request( + method :: atom(), + url :: String.t(), + headers :: list(), + body :: String.t(), + options :: list() + ) :: {:ok, non_neg_integer(), list(), String.t()} | {:error, atom()} + def request( + :get, + @base_url <> + "/claimable_balances/00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072", + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("claimable_balance") + {:ok, 200, [], json_body} + end + + def request(:get, @base_url <> "/claimable_balances/not_found", _headers, _body, _opts) do + json_error = Horizon.fixture("404") + {:ok, 404, [], json_error} + end + + def request( + :get, + @base_url <> + "/claimable_balances/00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072/transactions" <> + _query, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("transactions") + {:ok, 200, [], json_body} + end + + def request( + :get, + @base_url <> + "/claimable_balances/00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072/operations" <> + _query, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("operations") + {:ok, 200, [], json_body} + end + + def request( + :get, + @base_url <> "/claimable_balances?sponsor=" <> _sponsor, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("claimable_balances") + {:ok, 200, [], json_body} + end + + def request( + :get, + @base_url <> "/claimable_balances?claimant=" <> _claimant, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("claimable_balances") + {:ok, 200, [], json_body} + end + + def request(:get, @base_url <> "/claimable_balances?asset=" <> _asset, _headers, _body, _opts) do + json_body = Horizon.fixture("claimable_balances") + {:ok, 200, [], json_body} + end + + def request(:get, @base_url <> "/claimable_balances" <> _query, _headers, _body, _opts) do + json_body = Horizon.fixture("claimable_balances") + {:ok, 200, [], json_body} + end +end + +defmodule Stellar.Horizon.ClaimableBalancesTest do + use ExUnit.Case + + alias Stellar.Horizon.Client.CannedClaimableBalanceRequests + + alias Stellar.Horizon.{ + ClaimableBalance, + ClaimableBalances, + Collection, + Error, + Operation, + Transaction + } + + alias Stellar.Horizon.Operation.{CreateAccount, Payment, SetOptions} + + setup do + Application.put_env(:stellar_sdk, :http_client, CannedClaimableBalanceRequests) + + on_exit(fn -> + Application.delete_env(:stellar_sdk, :http_client) + end) + + %{ + claimable_balance_id: + "00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072" + } + end + + test "retrieve/1", %{claimable_balance_id: claimable_balance_id} do + {:ok, + %ClaimableBalance{ + amount: 10.0, + asset: "native", + claimants: [ + %{ + destination: "GC3C4AKRBQLHOJ45U4XG35ESVWRDECWO5XLDGYADO6DPR3L7KIDVUMML", + predicate: %{ + and: [ + %{ + or: [ + %{relBefore: "12"}, + %{ + absBefore: "2020-08-26T11:15:39Z", + absBeforeEpoch: "1598440539" + } + ] + }, + %{not: %{unconditional: true}} + ] + } + } + ], + id: "00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072", + last_modified_ledger: 28_411_995, + last_modified_time: ~U[2020-02-26 19:29:16Z], + paging_token: "00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072", + sponsor: nil + }} = ClaimableBalances.retrieve(claimable_balance_id) + end + + test "all/1" do + {:ok, + %Collection{ + next: + "https://horizon-testnet.stellar.org/claimable_balances/?cursor=1268715-0000000032d73407ea08b122407e32833cc6f9f6dc72ee0dd01e482fa06fdfadc32d01ea&limit=3&order=desc", + prev: + "https://horizon-testnet.stellar.org/claimable_balances/?cursor=1269595-00000000d6575278b432b3ee4e07cc0b17721fafc32c3b5c1403be68d8309268e91e585f&limit=3&order=asc", + records: [ + %ClaimableBalance{ + amount: 82.419084, + asset: "BTCN:GDGHQTCJ3SGFBWBHJGVRUFBRLZGS5VS52HEDH4GVPX5GZRJQAOW7ZM37", + claimants: [ + %{ + destination: "GATBAGTTQLQ4VKZMXLINLS6M4F2PEXMAZCK5ZE5ES4B6A2DXNGCFRX54", + predicate: %{ + abs_before: "2001-09-09T01:46:40Z", + abs_before_epoch: "1000000000" + } + } + ], + id: "00000000d6575278b432b3ee4e07cc0b17721fafc32c3b5c1403be68d8309268e91e585f", + last_modified_ledger: 1_269_595, + last_modified_time: ~U[2022-03-02 14:39:20Z], + sponsor: "GBXISGJYYKE6RUO6L6KXBUJ7FJU4CWF647FLQAT3TZ2Q47IZHXFNYKYH" + }, + %ClaimableBalance{ + amount: 912.5569285, + asset: "y00XLM:GDGHQTCJ3SGFBWBHJGVRUFBRLZGS5VS52HEDH4GVPX5GZRJQAOW7ZM37", + claimants: [ + %{ + destination: "GATBAGTTQLQ4VKZMXLINLS6M4F2PEXMAZCK5ZE5ES4B6A2DXNGCFRX54", + predicate: %{not: %{unconditional: true}} + } + ], + id: "00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072", + last_modified_ledger: 1_269_432, + last_modified_time: ~U[2022-03-02 14:25:09Z], + sponsor: "GBXISGJYYKE6RUO6L6KXBUJ7FJU4CWF647FLQAT3TZ2Q47IZHXFNYKYH" + }, + %ClaimableBalance{ + amount: 1.0e-7, + asset: "OxCBQksBk:GB3A3CK64CZDZ63FZTVI3OKK7ZCD75YQCPA2EKHPDFD6ABKEQ3ESTWVV", + claimants: [ + %{ + destination: "GASY6U7YOOQSBSWORHZ3QI4N46XHZUKEAKOP7XEZZ5XOFP5HMNV5CFZ6", + predicate: %{unconditional: true} + }, + %{ + destination: "GB3A3CK64CZDZ63FZTVI3OKK7ZCD75YQCPA2EKHPDFD6ABKEQ3ESTWVV", + predicate: %{unconditional: true} + } + ], + id: "0000000032d73407ea08b122407e32833cc6f9f6dc72ee0dd01e482fa06fdfadc32d01ea", + last_modified_ledger: 1_268_715, + last_modified_time: ~U[2022-03-02 13:22:04Z], + sponsor: "GB3A3CK64CZDZ63FZTVI3OKK7ZCD75YQCPA2EKHPDFD6ABKEQ3ESTWVV" + } + ] + }} = ClaimableBalances.all() + end + + test "list_by_sponsor/2" do + {:ok, + %Collection{ + records: [ + %ClaimableBalance{sponsor: "GBXISGJYYKE6RUO6L6KXBUJ7FJU4CWF647FLQAT3TZ2Q47IZHXFNYKYH"} + | _claimable_balances + ] + }} = + ClaimableBalances.list_by_sponsor( + "GBXISGJYYKE6RUO6L6KXBUJ7FJU4CWF647FLQAT3TZ2Q47IZHXFNYKYH" + ) + end + + test "list_by_claimant/2" do + {:ok, + %Collection{ + records: [ + %ClaimableBalance{ + claimants: [%{destination: "GATBAGTTQLQ4VKZMXLINLS6M4F2PEXMAZCK5ZE5ES4B6A2DXNGCFRX54"}] + } + | _claimable_balances + ] + }} = + ClaimableBalances.list_by_claimant( + "GATBAGTTQLQ4VKZMXLINLS6M4F2PEXMAZCK5ZE5ES4B6A2DXNGCFRX54" + ) + end + + test "list_by_asset/1" do + {:ok, + %Collection{ + records: [ + %ClaimableBalance{asset: "BTCN:GDGHQTCJ3SGFBWBHJGVRUFBRLZGS5VS52HEDH4GVPX5GZRJQAOW7ZM37"} + | _claimable_balances + ] + }} = + ClaimableBalances.list_by_asset( + "BTCN:GDGHQTCJ3SGFBWBHJGVRUFBRLZGS5VS52HEDH4GVPX5GZRJQAOW7ZM37" + ) + end + + test "list_transactions/1", %{claimable_balance_id: claimable_balance_id} do + {:ok, + %Collection{ + next: + "https://horizon.stellar.org/transactions?cursor=33736968114176\u0026limit=3\u0026order=asc", + prev: + "https://horizon.stellar.org/transactions?cursor=12884905984\u0026limit=3\u0026order=desc", + records: [ + %Transaction{ + id: "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889", + ledger: 7840 + }, + %Transaction{ + id: "2db4b22ca018119c5027a80578813ffcf582cda4aa9e31cd92b43cf1bda4fc5a", + ledger: 7841 + }, + %Transaction{ + id: "3ce2aca2fed36da2faea31352c76c5e412348887a4c119b1e90de8d1b937396a", + ledger: 7855 + } + ] + }} = ClaimableBalances.list_transactions(claimable_balance_id, limit: 3, order: :asc) + end + + test "list_operations/1", %{claimable_balance_id: claimable_balance_id} do + {:ok, + %Collection{ + next: + "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31/operations?cursor=12884905985&limit=3&order=desc", + prev: + "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31/operations?cursor=12884905987&limit=3&order=asc", + records: [ + %Operation{ + body: %SetOptions{}, + type: "set_options", + type_i: 5 + }, + %Operation{ + body: %Payment{}, + type: "payment", + type_i: 1 + }, + %Operation{ + body: %CreateAccount{}, + type: "create_account", + type_i: 0 + } + ] + }} = ClaimableBalances.list_operations(claimable_balance_id, limit: 3, order: :desc) + end + + test "error" do + {:error, + %Error{ + extras: nil, + status_code: 404, + title: "Resource Missing", + type: "https://stellar.org/horizon-errors/not_found" + }} = ClaimableBalances.retrieve("not_found") + end +end diff --git a/test/horizon/client/default_test.exs b/test/horizon/client/default_test.exs index 43f13e9a..8b691013 100644 --- a/test/horizon/client/default_test.exs +++ b/test/horizon/client/default_test.exs @@ -18,7 +18,7 @@ defmodule Stellar.Horizon.Client.CannedHTTPClient do end def request(:post, @base_url <> "/transactions?tx=bad", _headers, _body, _opts) do - json_error = Horizon.fixture("400") + json_error = Horizon.fixture("400_invalid_tx") {:ok, 400, [], json_error} end diff --git a/test/horizon/collection_error_test.exs b/test/horizon/collection_error_test.exs new file mode 100644 index 00000000..abb7a3d5 --- /dev/null +++ b/test/horizon/collection_error_test.exs @@ -0,0 +1,17 @@ +defmodule Stellar.Horizon.CollectionErrorTest do + use ExUnit.Case + + alias Stellar.Horizon.CollectionError + + test "invalid_collection" do + assert_raise CollectionError, "can't parse response as a collection", fn -> + raise CollectionError, :invalid_collection + end + end + + test "error" do + assert_raise CollectionError, "error", fn -> + raise CollectionError, "error" + end + end +end diff --git a/test/horizon/collection_test.exs b/test/horizon/collection_test.exs new file mode 100644 index 00000000..97b720d6 --- /dev/null +++ b/test/horizon/collection_test.exs @@ -0,0 +1,40 @@ +defmodule Stellar.Horizon.CollectionTest do + use ExUnit.Case + + alias Stellar.Test.Fixtures.Horizon + alias Stellar.Horizon.{Collection, CollectionError, Transaction} + + describe "new/1" do + setup do + json_body = Horizon.fixture("transactions") + response = Jason.decode!(json_body, keys: :atoms) + + %{response: response} + end + + test "success", %{response: response} do + %Collection{ + records: [ + %Transaction{hash: "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889"}, + %Transaction{hash: "2db4b22ca018119c5027a80578813ffcf582cda4aa9e31cd92b43cf1bda4fc5a"}, + _tx + ] + } = Collection.new({Transaction, response}) + end + + test "error" do + assert_raise CollectionError, "can't parse response as a collection", fn -> + Collection.new({Transaction, %{}}) + end + end + + test "pagination", %{response: response} do + %Collection{ + prev: + "https://horizon.stellar.org/transactions?cursor=12884905984\u0026limit=3\u0026order=desc", + next: + "https://horizon.stellar.org/transactions?cursor=33736968114176\u0026limit=3\u0026order=asc" + } = Collection.new({Transaction, response}) + end + end +end diff --git a/test/horizon/effect_test.exs b/test/horizon/effect_test.exs new file mode 100644 index 00000000..277219fa --- /dev/null +++ b/test/horizon/effect_test.exs @@ -0,0 +1,42 @@ +defmodule Stellar.Horizon.EffectTest do + use ExUnit.Case + + alias Stellar.Test.Fixtures.Horizon + alias Stellar.Horizon.Effect + + setup do + json_body = Horizon.fixture("effect") + attrs = Jason.decode!(json_body, keys: :atoms) + + %{attrs: attrs} + end + + test "new/2", %{attrs: attrs} do + %Effect{ + id: "0000000549755817992-0000000001", + paging_token: "549755817992-1", + account: "GCNP7JE6KR5CKHMVVFTZJUSP7ALAXWP62SK6IMIY4IF3JCHEZKBJKDZF", + type: "trustline_created", + type_i: 20, + attributes: %{ + asset_code: "TEST", + asset_issuer: "GDNFUWF2EO4OWXYLI4TDEH4DXUCN6PB24R6XQW4VATORK6WGMHGRXJVB", + asset_type: "credit_alphanum4", + limit: "922337203685.4775807" + }, + created_at: ~U[2021-12-15 09:38:34Z] + } = Effect.new(attrs) + end + + test "new/2 empty_attrs" do + %Effect{ + id: nil, + paging_token: nil, + account: nil, + type: nil, + type_i: nil, + attributes: %{}, + created_at: nil + } = Effect.new(%{}) + end +end diff --git a/test/horizon/effects_test.exs b/test/horizon/effects_test.exs new file mode 100644 index 00000000..fd527cd7 --- /dev/null +++ b/test/horizon/effects_test.exs @@ -0,0 +1,94 @@ +defmodule Stellar.Horizon.Client.CannedEffectRequests do + @moduledoc false + + alias Stellar.Horizon.Error + alias Stellar.Test.Fixtures.Horizon + + @base_url "https://horizon-testnet.stellar.org" + + @spec request( + method :: atom(), + url :: String.t(), + headers :: list(), + body :: String.t(), + options :: list() + ) :: {:ok, non_neg_integer(), list(), String.t()} | {:error, atom()} + def request(:get, @base_url <> "/effects?cursor=error", _headers, _body, _opts) do + json_error = Horizon.fixture("400") + {:ok, 400, [], json_error} + end + + def request(:get, @base_url <> "/effects" <> _query, _headers, _body, _opts) do + json_body = Horizon.fixture("effects") + {:ok, 200, [], json_body} + end +end + +defmodule Stellar.Horizon.EffectsTest do + use ExUnit.Case + + alias Stellar.Horizon.Client.CannedEffectRequests + alias Stellar.Horizon.{Collection, Error, Effect, Effects} + + setup do + Application.put_env(:stellar_sdk, :http_client, CannedEffectRequests) + + on_exit(fn -> + Application.delete_env(:stellar_sdk, :http_client) + end) + end + + test "all/1" do + {:ok, + %Collection{ + next: "https://horizon.stellar.org/effects?cursor=12884905985-3&limit=3&order=asc", + prev: "https://horizon.stellar.org/effects?cursor=12884905985-1&limit=3&order=desc", + records: [ + %Effect{ + account: "GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB", + attributes: %{amount: nil, starting_balance: 20.0}, + created_at: ~U[2015-09-30 17:15:54Z], + id: "0000000012884905985-0000000001", + paging_token: "12884905985-1", + type: "account_created", + type_i: 0 + }, + %Effect{ + account: "GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN7", + attributes: %{amount: 20.0, asset_type: "native", starting_balance: nil}, + created_at: ~U[2015-09-30 17:16:54Z], + id: "0000000012884905985-0000000002", + paging_token: "12884905985-2", + type: "account_debited", + type_i: 3 + }, + %Effect{ + account: "GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB", + attributes: %{ + amount: nil, + key: "", + public_key: "GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB", + starting_balance: nil, + weight: 1 + }, + created_at: ~U[2015-09-30 17:17:54Z], + id: "0000000012884905985-0000000003", + paging_token: "12884905985-3", + type: "signer_created", + type_i: 10 + } + ] + }} = Effects.all(limit: 3) + end + + test "error" do + {:error, + %Error{ + detail: "The request you sent was invalid in some way.", + extras: %{invalid_field: "cursor", reason: "cursor: invalid value"}, + status_code: 400, + title: "Bad Request", + type: "https://stellar.org/horizon-errors/bad_request" + }} = Effects.all(cursor: "error") + end +end diff --git a/test/horizon/ledger_test.exs b/test/horizon/ledger_test.exs new file mode 100644 index 00000000..b1ea1755 --- /dev/null +++ b/test/horizon/ledger_test.exs @@ -0,0 +1,57 @@ +defmodule Stellar.Horizon.LedgerTest do + use ExUnit.Case + + alias Stellar.Test.Fixtures.Horizon + alias Stellar.Horizon.Ledger + + setup do + json_body = Horizon.fixture("ledger") + attrs = Jason.decode!(json_body, keys: :atoms) + + %{attrs: attrs} + end + + test "new/2", %{attrs: attrs} do + %Ledger{ + id: "31c33314a9d6f1d1e07040029f56403fc410829a45dfe9cc662c6b2dce8f53b3", + paging_token: "42949672960", + hash: "31c33314a9d6f1d1e07040029f56403fc410829a45dfe9cc662c6b2dce8f53b3", + prev_hash: "da0516f96a9d3a00e8f60c60307cad062021cbad3c99f72ad8e277803dece482", + sequence: 10, + successful_transaction_count: 0, + failed_transaction_count: 0, + operation_count: 0, + tx_set_operation_count: 0, + closed_at: ~U[2015-09-30 17:16:29Z], + total_coins: 100_000_000_000.0, + fee_pool: 0.0000300, + base_fee_in_stroops: 100, + base_reserve_in_stroops: 100_000_000, + max_tx_set_size: 500, + protocol_version: 1, + header_xdr: "AAAAAdofgbAA===" + } = Ledger.new(attrs) + end + + test "new/2 empty_attrs" do + %Ledger{ + id: nil, + paging_token: nil, + hash: nil, + prev_hash: nil, + sequence: nil, + successful_transaction_count: nil, + failed_transaction_count: nil, + operation_count: nil, + tx_set_operation_count: nil, + closed_at: nil, + total_coins: nil, + fee_pool: nil, + base_fee_in_stroops: nil, + base_reserve_in_stroops: nil, + max_tx_set_size: nil, + protocol_version: nil, + header_xdr: nil + } = Ledger.new(%{}) + end +end diff --git a/test/horizon/ledgers_test.exs b/test/horizon/ledgers_test.exs new file mode 100644 index 00000000..4f256dd8 --- /dev/null +++ b/test/horizon/ledgers_test.exs @@ -0,0 +1,222 @@ +defmodule Stellar.Horizon.Client.CannedLedgerRequests do + @moduledoc false + + alias Stellar.Horizon.Error + alias Stellar.Test.Fixtures.Horizon + + @base_url "https://horizon-testnet.stellar.org" + + @spec request( + method :: atom(), + url :: String.t(), + headers :: list(), + body :: String.t(), + options :: list() + ) :: {:ok, non_neg_integer(), list(), String.t()} | {:error, atom()} + def request(:get, @base_url <> "/ledgers/10", _headers, _body, _opts) do + json_body = Horizon.fixture("ledger") + {:ok, 200, [], json_body} + end + + def request(:get, @base_url <> "/ledgers/not_found", _headers, _body, _opts) do + json_error = Horizon.fixture("404") + {:ok, 404, [], json_error} + end + + def request(:get, @base_url <> "/ledgers/10/effects" <> _query, _headers, _body, _opts) do + json_body = Horizon.fixture("effects") + {:ok, 200, [], json_body} + end + + def request(:get, @base_url <> "/ledgers/10/transactions" <> _query, _headers, _body, _opts) do + json_body = Horizon.fixture("transactions") + {:ok, 200, [], json_body} + end + + def request(:get, @base_url <> "/ledgers/10/operations" <> _query, _headers, _body, _opts) do + json_body = Horizon.fixture("operations") + {:ok, 200, [], json_body} + end + + def request(:get, @base_url <> "/ledgers/10/payments" <> _query, _headers, _body, _opts) do + json_body = Horizon.fixture("payments") + {:ok, 200, [], json_body} + end + + def request(:get, @base_url <> "/ledgers" <> _query, _headers, _body, _opts) do + json_body = Horizon.fixture("ledgers") + {:ok, 200, [], json_body} + end +end + +defmodule Stellar.Horizon.LedgersTest do + use ExUnit.Case + + alias Stellar.Horizon.Client.CannedLedgerRequests + + alias Stellar.Horizon.{ + Collection, + Effect, + Error, + Ledger, + Ledgers, + Operation, + Transaction + } + + alias Stellar.Horizon.Operation.{CreateAccount, Payment, SetOptions} + + setup do + Application.put_env(:stellar_sdk, :http_client, CannedLedgerRequests) + + on_exit(fn -> + Application.delete_env(:stellar_sdk, :http_client) + end) + + %{ledger_sequence: 10} + end + + test "retrieve/1", %{ledger_sequence: ledger_sequence} do + {:ok, + %Ledger{ + base_fee_in_stroops: 100, + base_reserve_in_stroops: 100_000_000, + closed_at: ~U[2015-09-30 17:16:29Z], + failed_transaction_count: 0, + fee_pool: 3.0e-5, + hash: "31c33314a9d6f1d1e07040029f56403fc410829a45dfe9cc662c6b2dce8f53b3", + header_xdr: "AAAAAdofgbAA===", + id: "31c33314a9d6f1d1e07040029f56403fc410829a45dfe9cc662c6b2dce8f53b3", + max_tx_set_size: 500, + operation_count: 0, + paging_token: "42949672960", + prev_hash: "da0516f96a9d3a00e8f60c60307cad062021cbad3c99f72ad8e277803dece482", + protocol_version: 1, + sequence: 10, + successful_transaction_count: 0, + total_coins: 1.0e11, + tx_set_operation_count: 0 + }} = Ledgers.retrieve(ledger_sequence) + end + + test "all/1" do + {:ok, + %Collection{ + records: [ + %Ledger{ + hash: "29b15176e88b828c219ab75760a2e3abe052c16f1c34d069320c7591a4bd7a77", + sequence: 1_258_232, + protocol_version: 18 + }, + %Ledger{ + hash: "ea3f21f0748f52fe700317971c46bca873b8cf6eb92296ea23d5a9416c455578", + sequence: 1_258_231, + protocol_version: 18 + }, + %Ledger{ + hash: "b05802d4c9f132d6ec0bb60defd0a0e5b41c1ccdcc52849fbad0725d83a54b77", + sequence: 1_258_230, + protocol_version: 18 + } + ] + }} = Ledgers.all() + end + + test "list_transactions/1", %{ledger_sequence: ledger_sequence} do + {:ok, + %Collection{ + next: + "https://horizon.stellar.org/transactions?cursor=33736968114176\u0026limit=3\u0026order=asc", + prev: + "https://horizon.stellar.org/transactions?cursor=12884905984\u0026limit=3\u0026order=desc", + records: [ + %Transaction{ + id: "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889", + ledger: 7840 + }, + %Transaction{ + id: "2db4b22ca018119c5027a80578813ffcf582cda4aa9e31cd92b43cf1bda4fc5a", + ledger: 7841 + }, + %Transaction{ + id: "3ce2aca2fed36da2faea31352c76c5e412348887a4c119b1e90de8d1b937396a", + ledger: 7855 + } + ] + }} = Ledgers.list_transactions(ledger_sequence, limit: 3, order: :asc) + end + + test "list_operations/1", %{ledger_sequence: ledger_sequence} do + {:ok, + %Collection{ + next: + "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31/operations?cursor=12884905985&limit=3&order=desc", + prev: + "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31/operations?cursor=12884905987&limit=3&order=asc", + records: [ + %Operation{ + body: %SetOptions{}, + type: "set_options", + type_i: 5 + }, + %Operation{ + body: %Payment{}, + type: "payment", + type_i: 1 + }, + %Operation{ + body: %CreateAccount{}, + type: "create_account", + type_i: 0 + } + ] + }} = Ledgers.list_operations(ledger_sequence, limit: 3, order: :desc) + end + + test "list_payments/1", %{ledger_sequence: ledger_sequence} do + {:ok, + %Collection{ + records: [ + %Operation{ + body: %Payment{amount: 0.0000001}, + type: "payment" + }, + %Operation{ + body: %Payment{amount: 1.0}, + type: "payment", + type_i: 1 + }, + %Operation{ + body: %Payment{amount: 1.0}, + type: "payment", + type_i: 1 + } + ] + }} = Ledgers.list_payments(ledger_sequence, limit: 3) + end + + test "list_effects/1", %{ledger_sequence: ledger_sequence} do + {:ok, + %Collection{ + next: + "https://horizon.stellar.org/effects?cursor=12884905985-3\u0026limit=3\u0026order=asc", + prev: + "https://horizon.stellar.org/effects?cursor=12884905985-1\u0026limit=3\u0026order=desc", + records: [ + %Effect{type: "account_created", created_at: ~U[2015-09-30 17:15:54Z]}, + %Effect{type: "account_debited", created_at: ~U[2015-09-30 17:16:54Z]}, + %Effect{type: "signer_created", created_at: ~U[2015-09-30 17:17:54Z]} + ] + }} = Ledgers.list_effects(ledger_sequence, limit: 3) + end + + test "error" do + {:error, + %Error{ + extras: nil, + status_code: 404, + title: "Resource Missing", + type: "https://stellar.org/horizon-errors/not_found" + }} = Ledgers.retrieve("not_found") + end +end diff --git a/test/horizon/liquidity_pool_test.exs b/test/horizon/liquidity_pool_test.exs new file mode 100644 index 00000000..1331e57c --- /dev/null +++ b/test/horizon/liquidity_pool_test.exs @@ -0,0 +1,48 @@ +defmodule Stellar.Horizon.LiquidityPoolTest do + use ExUnit.Case + + alias Stellar.Test.Fixtures.Horizon + alias Stellar.Horizon.LiquidityPool + + setup do + json_body = Horizon.fixture("liquidity_pool") + attrs = Jason.decode!(json_body, keys: :atoms) + + %{attrs: attrs} + end + + test "new/2", %{attrs: attrs} do + %LiquidityPool{ + id: "0052d44a6c260660115f07c5a78631770e62aae3ffde96731c44b1509e9c8434", + fee_bp: 30, + type: "constant_product", + total_trustlines: 1, + total_shares: 500_000_000.0, + reserves: [ + %{ + asset: "XAU:GB3OE4IBQTYQFZZS5RXQHE4IPQL7ONOFOSAIS2NFRNMJKZEAI3AAUT2A", + amount: 522_256_061.0743940 + }, + %{ + asset: "ERRES:GA6ZAQGLDUEODDUUD3UD6PUFJYABWQA26SG5RK6E6CZ6OMD6AZKK5QNF", + amount: 533_666_459.4045717 + } + ], + last_modified_ledger: 39_767_560, + last_modified_time: ~U[2022-02-24 11:48:09Z] + } = LiquidityPool.new(attrs) + end + + test "new/2 empty_attrs" do + %LiquidityPool{ + id: nil, + fee_bp: nil, + type: nil, + total_trustlines: nil, + total_shares: nil, + reserves: nil, + last_modified_ledger: nil, + last_modified_time: nil + } = LiquidityPool.new(%{}) + end +end diff --git a/test/horizon/liquidity_pools_test.exs b/test/horizon/liquidity_pools_test.exs new file mode 100644 index 00000000..597dd484 --- /dev/null +++ b/test/horizon/liquidity_pools_test.exs @@ -0,0 +1,286 @@ +defmodule Stellar.Horizon.Client.CannedLiquidityPoolRequests do + @moduledoc false + + alias Stellar.Horizon.Error + alias Stellar.Test.Fixtures.Horizon + + @base_url "https://horizon-testnet.stellar.org" + + @spec request( + method :: atom(), + url :: String.t(), + headers :: list(), + body :: String.t(), + options :: list() + ) :: {:ok, non_neg_integer(), list(), String.t()} | {:error, atom()} + def request( + :get, + @base_url <> + "/liquidity_pools/0052d44a6c260660115f07c5a78631770e62aae3ffde96731c44b1509e9c8434", + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("liquidity_pool") + {:ok, 200, [], json_body} + end + + def request(:get, @base_url <> "/liquidity_pools/not_found", _headers, _body, _opts) do + json_error = Horizon.fixture("404") + {:ok, 404, [], json_error} + end + + def request( + :get, + @base_url <> + "/liquidity_pools/0052d44a6c260660115f07c5a78631770e62aae3ffde96731c44b1509e9c8434/transactions" <> + _query, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("transactions") + {:ok, 200, [], json_body} + end + + def request( + :get, + @base_url <> + "/liquidity_pools/0052d44a6c260660115f07c5a78631770e62aae3ffde96731c44b1509e9c8434/operations" <> + _query, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("operations") + {:ok, 200, [], json_body} + end + + def request( + :get, + @base_url <> + "/liquidity_pools/0052d44a6c260660115f07c5a78631770e62aae3ffde96731c44b1509e9c8434/effects" <> + _query, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("effects") + {:ok, 200, [], json_body} + end + + def request( + :get, + @base_url <> + "/liquidity_pools/0052d44a6c260660115f07c5a78631770e62aae3ffde96731c44b1509e9c8434/trades" <> + _query, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("trades") + {:ok, 200, [], json_body} + end + + def request(:get, @base_url <> "/liquidity_pools" <> _query, _headers, _body, _opts) do + json_body = Horizon.fixture("liquidity_pools") + {:ok, 200, [], json_body} + end +end + +defmodule Stellar.Horizon.LiquidityPoolsTest do + use ExUnit.Case + + alias Stellar.Horizon.Client.CannedLiquidityPoolRequests + + alias Stellar.Horizon.{ + Collection, + Effect, + Error, + LiquidityPool, + LiquidityPools, + Operation, + Trade, + Transaction + } + + alias Stellar.Horizon.Operation.{CreateAccount, Payment, SetOptions} + + setup do + Application.put_env(:stellar_sdk, :http_client, CannedLiquidityPoolRequests) + + on_exit(fn -> + Application.delete_env(:stellar_sdk, :http_client) + end) + + %{liquidity_pool_id: "0052d44a6c260660115f07c5a78631770e62aae3ffde96731c44b1509e9c8434"} + end + + test "retrieve/1", %{liquidity_pool_id: liquidity_pool_id} do + {:ok, + %LiquidityPool{ + fee_bp: 30, + id: "0052d44a6c260660115f07c5a78631770e62aae3ffde96731c44b1509e9c8434", + last_modified_ledger: 39_767_560, + last_modified_time: ~U[2022-02-24 11:48:09Z], + paging_token: "0052d44a6c260660115f07c5a78631770e62aae3ffde96731c44b1509e9c8434", + reserves: [ + %{ + amount: 522_256_061.074394, + asset: "XAU:GB3OE4IBQTYQFZZS5RXQHE4IPQL7ONOFOSAIS2NFRNMJKZEAI3AAUT2A" + }, + %{ + amount: 533_666_459.4045717, + asset: "ERRES:GA6ZAQGLDUEODDUUD3UD6PUFJYABWQA26SG5RK6E6CZ6OMD6AZKK5QNF" + } + ], + total_shares: 5.0e8, + total_trustlines: 1, + type: "constant_product" + }} = LiquidityPools.retrieve(liquidity_pool_id) + end + + test "all/1" do + {:ok, + %Collection{ + next: + "https://horizon-testnet.stellar.org/liquidity_pools?cursor=fd498c395920d57156e86b2d52acd2bbbfc164836a204c73d3b64bec33874d38&limit=3&order=desc", + prev: + "https://horizon-testnet.stellar.org/liquidity_pools?cursor=fe6490f5e64f25e62df49a6a4b5542b8e5411e84c6d3158613abb2271824deb9&limit=3&order=asc", + records: [ + %LiquidityPool{ + fee_bp: 30, + id: "fe6490f5e64f25e62df49a6a4b5542b8e5411e84c6d3158613abb2271824deb9", + last_modified_ledger: 1_137_275, + last_modified_time: ~U[2022-02-22 13:30:09Z], + reserves: [ + %{ + amount: 228.685116, + asset: "BTCN:GDGUZCQFXCLZ775VZH3YGWRUIBKD6XKDJEW3E7CE5COT4EO7A3W22YUX" + } + | _reserves1 + ], + total_shares: 1.98, + total_trustlines: 1, + type: "constant_product" + }, + %LiquidityPool{ + fee_bp: 30, + id: "fdccc907a78a11e592c122144f702fdb093160c510c0f4327994d9be5d951092", + last_modified_ledger: 384_280, + last_modified_time: ~U[2022-01-07 18:19:08Z], + reserves: [ + %{ + amount: 100_005.015296, + asset: "DOLLAR:GCO7B6KEDWOBM5X642ZOTPYTYTTBZIGVGUED4ZSBILJOAU4XB7ISJBFF" + } + | _reserves2 + ], + total_shares: 1.0e5, + total_trustlines: 1, + type: "constant_product" + }, + %LiquidityPool{ + fee_bp: 30, + id: "fd498c395920d57156e86b2d52acd2bbbfc164836a204c73d3b64bec33874d38", + last_modified_ledger: 973_360, + last_modified_time: ~U[2022-02-12 14:16:51Z], + reserves: [ + %{amount: 130.8900524, asset: "native"} + | _reserves3 + ], + total_shares: 180.893651, + total_trustlines: 4, + type: "constant_product" + } + ] + }} = LiquidityPools.all() + end + + test "list_transactions/1", %{liquidity_pool_id: liquidity_pool_id} do + {:ok, + %Collection{ + next: + "https://horizon.stellar.org/transactions?cursor=33736968114176\u0026limit=3\u0026order=asc", + prev: + "https://horizon.stellar.org/transactions?cursor=12884905984\u0026limit=3\u0026order=desc", + records: [ + %Transaction{ + id: "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889", + ledger: 7840 + }, + %Transaction{ + id: "2db4b22ca018119c5027a80578813ffcf582cda4aa9e31cd92b43cf1bda4fc5a", + ledger: 7841 + }, + %Transaction{ + id: "3ce2aca2fed36da2faea31352c76c5e412348887a4c119b1e90de8d1b937396a", + ledger: 7855 + } + ] + }} = LiquidityPools.list_transactions(liquidity_pool_id, limit: 3, order: :asc) + end + + test "list_operations/1", %{liquidity_pool_id: liquidity_pool_id} do + {:ok, + %Collection{ + next: + "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31/operations?cursor=12884905985&limit=3&order=desc", + prev: + "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31/operations?cursor=12884905987&limit=3&order=asc", + records: [ + %Operation{ + body: %SetOptions{}, + type: "set_options", + type_i: 5 + }, + %Operation{ + body: %Payment{}, + type: "payment", + type_i: 1 + }, + %Operation{ + body: %CreateAccount{}, + type: "create_account", + type_i: 0 + } + ] + }} = LiquidityPools.list_operations(liquidity_pool_id, limit: 3, order: :desc) + end + + test "list_effects/1", %{liquidity_pool_id: liquidity_pool_id} do + {:ok, + %Collection{ + next: + "https://horizon.stellar.org/effects?cursor=12884905985-3\u0026limit=3\u0026order=asc", + prev: + "https://horizon.stellar.org/effects?cursor=12884905985-1\u0026limit=3\u0026order=desc", + records: [ + %Effect{type: "account_created", created_at: ~U[2015-09-30 17:15:54Z]}, + %Effect{type: "account_debited", created_at: ~U[2015-09-30 17:16:54Z]}, + %Effect{type: "signer_created", created_at: ~U[2015-09-30 17:17:54Z]} + ] + }} = LiquidityPools.list_effects(liquidity_pool_id, limit: 3) + end + + test "list_trades/2", %{liquidity_pool_id: liquidity_pool_id} do + {:ok, + %Collection{ + records: [ + %Trade{base_amount: 4433.20}, + %Trade{base_amount: 10.0}, + %Trade{base_amount: 748.5338945} + ] + }} = LiquidityPools.list_trades(liquidity_pool_id) + end + + test "error" do + {:error, + %Error{ + extras: nil, + status_code: 404, + title: "Resource Missing", + type: "https://stellar.org/horizon-errors/not_found" + }} = LiquidityPools.retrieve("not_found") + end +end diff --git a/test/horizon/mapping_test.exs b/test/horizon/mapping_test.exs index f1d7d46d..548b05d2 100644 --- a/test/horizon/mapping_test.exs +++ b/test/horizon/mapping_test.exs @@ -1,7 +1,7 @@ defmodule Stellar.Horizon.FakeTransaction do @moduledoc false - defstruct [:id, :source_account, :fee, :created_at, :operation] + defstruct [:id, :source_account, :fee, :created_at, :operation, :balances] end defmodule Stellar.Horizon.FakeOperation do @@ -89,11 +89,25 @@ defmodule Stellar.Horizon.MappingTest do |> Mapping.parse(fee: :float) end + test "parse/2 map", %{resource: resource} do + %FakeTransaction{fee: %{amount: 100.15}} = + resource + |> Mapping.build(%{fee: %{amount: "100.15"}}) + |> Mapping.parse(fee: {:map, [amount: :float]}) + end + + test "parse/2 list_of_maps", %{resource: resource} do + %FakeTransaction{balances: [%{amount: 100.010}, %{amount: 200.15}]} = + resource + |> Mapping.build(%{balances: [%{amount: "100.010"}, %{amount: "200.15"}]}) + |> Mapping.parse(balances: {:list, :map, [amount: :float]}) + end + test "parse/2 struct", %{id: id, resource: resource} do %FakeTransaction{operation: %FakeOperation{}} = resource |> Mapping.build(%{operation: %{id: id, hash: id, balance: 100}}) - |> Mapping.parse(operation: %FakeOperation{}) + |> Mapping.parse(operation: {:struct, FakeOperation}) end test "parse/2 list_of_structs", %{id: id, resource: resource} do @@ -105,6 +119,6 @@ defmodule Stellar.Horizon.MappingTest do %FakeTransaction{operation: [%FakeOperation{balance: 100}, %FakeOperation{balance: 200}]} = resource |> Mapping.build(%{operation: raw_operations}) - |> Mapping.parse(operation: %FakeOperation{}) + |> Mapping.parse(operation: {:list, :struct, FakeOperation}) end end diff --git a/test/horizon/offer_test.exs b/test/horizon/offer_test.exs new file mode 100644 index 00000000..2e59c69b --- /dev/null +++ b/test/horizon/offer_test.exs @@ -0,0 +1,50 @@ +defmodule Stellar.Horizon.OfferTest do + use ExUnit.Case + + alias Stellar.Test.Fixtures.Horizon + alias Stellar.Horizon.Offer + + setup do + json_body = Horizon.fixture("offer") + attrs = Jason.decode!(json_body, keys: :atoms) + + %{attrs: attrs} + end + + test "new/2", %{attrs: attrs} do + %Offer{ + id: 165_561_423, + seller: "GCK4WSNF3F6ZNCMK6BU77ZCZ3NMF3JGU2U3ZAPKXYBKYYCJA72FDBY7K", + selling: %{ + asset_type: "credit_alphanum4", + asset_code: "NGNT", + asset_issuer: "GAWODAROMJ33V5YDFY3NPYTHVYQG7MJXVJ2ND3AOGIHYRWINES6ACCPD" + }, + buying: %{ + asset_type: "native" + }, + amount: 18_421.4486092, + price_r: %{ + n: 45_112_058, + d: 941_460_545 + }, + price: 0.0479171, + last_modified_ledger: 28_411_995, + last_modified_time: ~U[2020-02-26 19:29:16Z] + } = Offer.new(attrs) + end + + test "new/2 empty_attrs" do + %Offer{ + id: nil, + seller: nil, + selling: nil, + buying: nil, + amount: nil, + price_r: nil, + price: nil, + last_modified_ledger: nil, + last_modified_time: nil + } = Offer.new(%{}) + end +end diff --git a/test/horizon/offers_test.exs b/test/horizon/offers_test.exs new file mode 100644 index 00000000..d8a1d49b --- /dev/null +++ b/test/horizon/offers_test.exs @@ -0,0 +1,120 @@ +defmodule Stellar.Horizon.Client.CannedOfferRequests do + @moduledoc false + + alias Stellar.Horizon.Error + alias Stellar.Test.Fixtures.Horizon + + @base_url "https://horizon-testnet.stellar.org" + + @spec request( + method :: atom(), + url :: String.t(), + headers :: list(), + body :: String.t(), + options :: list() + ) :: {:ok, non_neg_integer(), list(), String.t()} | {:error, atom()} + def request(:get, @base_url <> "/offers/165561423", _headers, _body, _opts) do + json_body = Horizon.fixture("offer") + {:ok, 200, [], json_body} + end + + def request(:get, @base_url <> "/offers/not_found", _headers, _body, _opts) do + json_error = Horizon.fixture("404") + {:ok, 404, [], json_error} + end + + def request(:get, @base_url <> "/offers/165561423/trades" <> _query, _headers, _body, _opts) do + json_body = Horizon.fixture("trades") + {:ok, 200, [], json_body} + end + + def request(:get, @base_url <> "/offers" <> _query, _headers, _body, _opts) do + json_body = Horizon.fixture("offers") + {:ok, 200, [], json_body} + end +end + +defmodule Stellar.Horizon.OffersTest do + use ExUnit.Case + + alias Stellar.Horizon.Client.CannedOfferRequests + alias Stellar.Horizon.{Collection, Error, Offer, Offers, Trade} + + setup do + Application.put_env(:stellar_sdk, :http_client, CannedOfferRequests) + + on_exit(fn -> + Application.delete_env(:stellar_sdk, :http_client) + end) + + %{offer_id: 165_561_423} + end + + test "retrieve/1", %{offer_id: offer_id} do + {:ok, + %Offer{ + amount: 18_421.4486092, + buying: %{asset_type: "native"}, + id: 165_561_423, + last_modified_ledger: 28_411_995, + last_modified_time: ~U[2020-02-26 19:29:16Z], + paging_token: "165561423", + price: 0.0479171, + price_r: %{d: 941_460_545, n: 45_112_058}, + seller: "GCK4WSNF3F6ZNCMK6BU77ZCZ3NMF3JGU2U3ZAPKXYBKYYCJA72FDBY7K", + selling: %{ + asset_code: "NGNT", + asset_issuer: "GAWODAROMJ33V5YDFY3NPYTHVYQG7MJXVJ2ND3AOGIHYRWINES6ACCPD", + asset_type: "credit_alphanum4" + }, + sponsor: nil + }} = Offers.retrieve(offer_id) + end + + test "all/1" do + {:ok, + %Collection{ + records: [ + %Offer{ + id: 164_555_927, + buying: %{ + asset_type: "credit_alphanum4", + asset_code: "BTCN", + asset_issuer: "GD5J6HLF5666X4AZLTFTXLY46J5SW7EXRKBLEYPJP33S33MXZGV6CWFN" + }, + price: 0.1162791 + }, + %Offer{ + id: 164_943_216, + selling: %{ + asset_type: "credit_alphanum4", + asset_code: "BTCN", + asset_issuer: "GD5J6HLF5666X4AZLTFTXLY46J5SW7EXRKBLEYPJP33S33MXZGV6CWFN" + }, + price: 12.8899964 + } + ] + }} = Offers.all() + end + + test "list_trades/2", %{offer_id: offer_id} do + {:ok, + %Collection{ + records: [ + %Trade{base_offer_id: ^offer_id, base_amount: 4433.20}, + %Trade{base_offer_id: ^offer_id, base_amount: 10.0}, + %Trade{base_offer_id: ^offer_id, base_amount: 748.5338945} + ] + }} = Offers.list_trades(offer_id) + end + + test "error" do + {:error, + %Error{ + extras: nil, + status_code: 404, + title: "Resource Missing", + type: "https://stellar.org/horizon-errors/not_found" + }} = Offers.retrieve("not_found") + end +end diff --git a/test/horizon/operation/account_merge_test.exs b/test/horizon/operation/account_merge_test.exs new file mode 100644 index 00000000..38e420b0 --- /dev/null +++ b/test/horizon/operation/account_merge_test.exs @@ -0,0 +1,22 @@ +defmodule Stellar.Horizon.Operation.AccountMergeTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.AccountMerge + + setup do + %{ + attrs: %{ + account: "GCVLWV5B3L3YE6DSCCMHLCK7QIB365NYOLQLW3ZKHI5XINNMRLJ6YHVX", + into: "GATL3ETTZ3XDGFXX2ELPIKCZL7S5D2HY3VK4T7LRPD6DW5JOLAEZSZBA" + } + } + end + + test "new/2", %{attrs: %{account: account, into: into} = attrs} do + %AccountMerge{account: ^account, into: ^into} = AccountMerge.new(attrs) + end + + test "new/2 empty_attrs" do + %AccountMerge{account: nil, into: nil} = AccountMerge.new(%{}) + end +end diff --git a/test/horizon/operation/allow_trust_test.exs b/test/horizon/operation/allow_trust_test.exs new file mode 100644 index 00000000..bdadfd43 --- /dev/null +++ b/test/horizon/operation/allow_trust_test.exs @@ -0,0 +1,50 @@ +defmodule Stellar.Horizon.Operation.AllowTrustTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.AllowTrust + + setup do + %{ + attrs: %{ + asset_type: "credit_alphanum4", + asset_code: "LSV1", + asset_issuer: "GCRZQVBBDAWVOCO5R2NI34YR55RO2GQXPTDUE5OZESXGZRRTAEQLKEKN", + trustee: "GCRZQVBBDAWVOCO5R2NI34YR55RO2GQXPTDUE5OZESXGZRRTAEQLKEKN", + trustor: "GDSYBYRG6NIBJWR7BLY72HYV7VM4A7WWHUJ45FI7H4Q2U2RPR3BB3CFR", + authorize: true + } + } + end + + test "new/2", %{ + attrs: + %{ + asset_type: asset_type, + asset_code: asset_code, + asset_issuer: asset_issuer, + trustee: trustee, + trustor: trustor, + authorize: authorize + } = attrs + } do + %AllowTrust{ + asset_type: ^asset_type, + asset_code: ^asset_code, + asset_issuer: ^asset_issuer, + trustee: ^trustee, + trustor: ^trustor, + authorize: ^authorize + } = AllowTrust.new(attrs) + end + + test "new/2 empty_attrs" do + %AllowTrust{ + asset_type: nil, + asset_code: nil, + asset_issuer: nil, + trustee: nil, + trustor: nil, + authorize: nil + } = AllowTrust.new(%{}) + end +end diff --git a/test/horizon/operation/begin_sponsoring_future_reserves_test.exs b/test/horizon/operation/begin_sponsoring_future_reserves_test.exs new file mode 100644 index 00000000..1f2da29f --- /dev/null +++ b/test/horizon/operation/begin_sponsoring_future_reserves_test.exs @@ -0,0 +1,18 @@ +defmodule Stellar.Horizon.Operation.BeginSponsoringFutureReservesTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.BeginSponsoringFutureReserves + + setup do + %{attrs: %{sponsored_id: "GC3C4AKRBQLHOJ45U4XG35ESVWRDECWO5XLDGYADO6DPR3L7KIDVUMML"}} + end + + test "new/2", %{attrs: %{sponsored_id: sponsored_id} = attrs} do + %BeginSponsoringFutureReserves{sponsored_id: ^sponsored_id} = + BeginSponsoringFutureReserves.new(attrs) + end + + test "new/2 empty_attrs" do + %BeginSponsoringFutureReserves{sponsored_id: nil} = BeginSponsoringFutureReserves.new(%{}) + end +end diff --git a/test/horizon/operation/bump_sequence_test.exs b/test/horizon/operation/bump_sequence_test.exs new file mode 100644 index 00000000..47559eb1 --- /dev/null +++ b/test/horizon/operation/bump_sequence_test.exs @@ -0,0 +1,17 @@ +defmodule Stellar.Horizon.Operation.BumpSequenceTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.BumpSequence + + setup do + %{attrs: %{bump_to: "120192344968520085"}} + end + + test "new/2", %{attrs: attrs} do + %BumpSequence{bump_to: 120_192_344_968_520_085} = BumpSequence.new(attrs) + end + + test "new/2 empty_attrs" do + %BumpSequence{bump_to: nil} = BumpSequence.new(%{}) + end +end diff --git a/test/horizon/operation/change_trust_test.exs b/test/horizon/operation/change_trust_test.exs new file mode 100644 index 00000000..5a89405f --- /dev/null +++ b/test/horizon/operation/change_trust_test.exs @@ -0,0 +1,51 @@ +defmodule Stellar.Horizon.Operation.ChangeTrustTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.ChangeTrust + + setup do + %{ + attrs: %{ + asset_type: "credit_alphanum4", + asset_code: "LSV1", + limit: "922337203685.4775807", + limit_f: 922_337_203_685.4775807, + asset_issuer: "GCRZQVBBDAWVOCO5R2NI34YR55RO2GQXPTDUE5OZESXGZRRTAEQLKEKN", + trustee: "GCRZQVBBDAWVOCO5R2NI34YR55RO2GQXPTDUE5OZESXGZRRTAEQLKEKN", + trustor: "GDSYBYRG6NIBJWR7BLY72HYV7VM4A7WWHUJ45FI7H4Q2U2RPR3BB3CFR" + } + } + end + + test "new/2", %{ + attrs: + %{ + asset_type: asset_type, + asset_code: asset_code, + asset_issuer: asset_issuer, + limit_f: limit, + trustee: trustee, + trustor: trustor + } = attrs + } do + %ChangeTrust{ + asset_type: ^asset_type, + asset_code: ^asset_code, + asset_issuer: ^asset_issuer, + limit: ^limit, + trustee: ^trustee, + trustor: ^trustor + } = ChangeTrust.new(attrs) + end + + test "new/2 empty_attrs" do + %ChangeTrust{ + asset_type: nil, + asset_code: nil, + asset_issuer: nil, + trustee: nil, + trustor: nil, + liquidity_pool_id: nil + } = ChangeTrust.new(%{}) + end +end diff --git a/test/horizon/operation/claim_claimable_balance_test.exs b/test/horizon/operation/claim_claimable_balance_test.exs new file mode 100644 index 00000000..02710023 --- /dev/null +++ b/test/horizon/operation/claim_claimable_balance_test.exs @@ -0,0 +1,23 @@ +defmodule Stellar.Horizon.Operation.ClaimClaimableBalanceTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.ClaimClaimableBalance + + setup do + %{ + attrs: %{ + balance_id: "00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072", + claimant: "GC3C4AKRBQLHOJ45U4XG35ESVWRDECWO5XLDGYADO6DPR3L7KIDVUMML" + } + } + end + + test "new/2", %{attrs: %{balance_id: balance_id, claimant: claimant} = attrs} do + %ClaimClaimableBalance{balance_id: ^balance_id, claimant: ^claimant} = + ClaimClaimableBalance.new(attrs) + end + + test "new/2 empty_attrs" do + %ClaimClaimableBalance{balance_id: nil, claimant: nil} = ClaimClaimableBalance.new(%{}) + end +end diff --git a/test/horizon/operation/create_account_test.exs b/test/horizon/operation/create_account_test.exs new file mode 100644 index 00000000..b505175c --- /dev/null +++ b/test/horizon/operation/create_account_test.exs @@ -0,0 +1,24 @@ +defmodule Stellar.Horizon.Operation.CreateAccountTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.CreateAccount + + setup do + %{ + attrs: %{ + starting_balance: "2.0000000", + account: "GCVLWV5B3L3YE6DSCCMHLCK7QIB365NYOLQLW3ZKHI5XINNMRLJ6YHVX", + funder: "GATL3ETTZ3XDGFXX2ELPIKCZL7S5D2HY3VK4T7LRPD6DW5JOLAEZSZBA" + } + } + end + + test "new/2", %{attrs: %{account: account, funder: funder} = attrs} do + %CreateAccount{starting_balance: 2.0, account: ^account, funder: ^funder} = + CreateAccount.new(attrs) + end + + test "new/2 empty_attrs" do + %CreateAccount{starting_balance: nil, account: nil, funder: nil} = CreateAccount.new(%{}) + end +end diff --git a/test/horizon/operation/create_claimable_balance_test.exs b/test/horizon/operation/create_claimable_balance_test.exs new file mode 100644 index 00000000..8f9319bd --- /dev/null +++ b/test/horizon/operation/create_claimable_balance_test.exs @@ -0,0 +1,47 @@ +defmodule Stellar.Horizon.Operation.CreateClaimableBalanceTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.CreateClaimableBalance + + setup do + %{ + attrs: %{ + asset: "NGNT:GAWODAROMJ33V5YDFY3NPYTHVYQG7MJXVJ2ND3AOGIHYRWINES6ACCPD", + amount: "200.0000000", + claimants: [ + %{ + destination: "GC3C4AKRBQLHOJ45U4XG35ESVWRDECWO5XLDGYADO6DPR3L7KIDVUMML", + predicate: %{ + and: [ + %{ + or: [ + %{ + relBefore: "12" + }, + %{ + absBefore: "2020-08-26T11:15:39Z", + absBeforeEpoch: "1598440539" + } + ] + }, + %{ + not: %{unconditional: true} + } + ] + } + } + ] + } + } + end + + test "new/2", %{attrs: %{asset: asset, claimants: claimants} = attrs} do + %CreateClaimableBalance{asset: ^asset, amount: 200.0, claimants: ^claimants} = + CreateClaimableBalance.new(attrs) + end + + test "new/2 empty_attrs" do + %CreateClaimableBalance{asset: nil, amount: nil, claimants: nil} = + CreateClaimableBalance.new(%{}) + end +end diff --git a/test/horizon/operation/create_passive_sell_offer_test.exs b/test/horizon/operation/create_passive_sell_offer_test.exs new file mode 100644 index 00000000..90deef15 --- /dev/null +++ b/test/horizon/operation/create_passive_sell_offer_test.exs @@ -0,0 +1,63 @@ +defmodule Stellar.Horizon.Operation.CreatePassiveSellOfferTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.CreatePassiveSellOffer + + setup do + %{ + attrs: %{ + amount: "1.0000000", + price: "1.0000000", + price_r: %{ + n: 1, + d: 1 + }, + buying_asset_type: "credit_alphanum4", + buying_asset_code: "USD", + buying_asset_issuer: "GBNLJIYH34UWO5YZFA3A3HD3N76R6DOI33N4JONUOHEEYZYCAYTEJ5AK", + selling_asset_type: "credit_alphanum4", + selling_asset_code: "USD", + selling_asset_issuer: "GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX" + } + } + end + + test "new/2", %{ + attrs: + %{ + price_r: price_r, + buying_asset_type: buying_asset_type, + buying_asset_code: buying_asset_code, + buying_asset_issuer: buying_asset_issuer, + selling_asset_type: selling_asset_type, + selling_asset_code: selling_asset_code, + selling_asset_issuer: selling_asset_issuer + } = attrs + } do + %CreatePassiveSellOffer{ + amount: 1.0, + price: 1.0, + price_r: ^price_r, + buying_asset_type: ^buying_asset_type, + buying_asset_code: ^buying_asset_code, + buying_asset_issuer: ^buying_asset_issuer, + selling_asset_type: ^selling_asset_type, + selling_asset_code: ^selling_asset_code, + selling_asset_issuer: ^selling_asset_issuer + } = CreatePassiveSellOffer.new(attrs) + end + + test "new/2 empty_attrs" do + %CreatePassiveSellOffer{ + amount: nil, + price: nil, + price_r: nil, + buying_asset_type: nil, + buying_asset_code: nil, + buying_asset_issuer: nil, + selling_asset_type: nil, + selling_asset_code: nil, + selling_asset_issuer: nil + } = CreatePassiveSellOffer.new(%{}) + end +end diff --git a/test/horizon/operation/end_sponsoring_future_reserves_test.exs b/test/horizon/operation/end_sponsoring_future_reserves_test.exs new file mode 100644 index 00000000..8f84f7ef --- /dev/null +++ b/test/horizon/operation/end_sponsoring_future_reserves_test.exs @@ -0,0 +1,18 @@ +defmodule Stellar.Horizon.Operation.EndSponsoringFutureReservesTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.EndSponsoringFutureReserves + + setup do + %{attrs: %{begin_sponsor: "GC3C4AKRBQLHOJ45U4XG35ESVWRDECWO5XLDGYADO6DPR3L7KIDVUMML"}} + end + + test "new/2", %{attrs: %{begin_sponsor: begin_sponsor} = attrs} do + %EndSponsoringFutureReserves{begin_sponsor: ^begin_sponsor} = + EndSponsoringFutureReserves.new(attrs) + end + + test "new/2 empty_attrs" do + %EndSponsoringFutureReserves{begin_sponsor: nil} = EndSponsoringFutureReserves.new(%{}) + end +end diff --git a/test/horizon/operation/liquidity_pool_deposit_test.exs b/test/horizon/operation/liquidity_pool_deposit_test.exs new file mode 100644 index 00000000..715bc90b --- /dev/null +++ b/test/horizon/operation/liquidity_pool_deposit_test.exs @@ -0,0 +1,71 @@ +defmodule Stellar.Horizon.Operation.LiquidityPoolDepositTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.LiquidityPoolDeposit + + setup do + %{ + attrs: %{ + liquidity_pool_id: "929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072", + reserves_max: [ + %{ + asset: "JPY:GBVAOIACNSB7OVUXJYC5UE2D4YK2F7A24T7EE5YOMN4CE6GCHUTOUQXM", + amount: "1000.0000005" + }, + %{ + asset: "EURT:GAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S", + amount: "3000.0000005" + } + ], + min_price: "0.2680000", + min_price_r: %{n: 67, d: 250}, + max_price: "0.3680000", + max_price_r: %{n: 73, d: 250}, + reserves_deposited: [ + %{ + asset: "JPY:GBVAOIACNSB7OVUXJYC5UE2D4YK2F7A24T7EE5YOMN4CE6GCHUTOUQXM", + amount: "983.0000005" + }, + %{ + asset: "EURT:GAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S", + amount: "2378.0000005" + } + ], + shares_received: "1000" + } + } + end + + test "new/2", %{ + attrs: + %{ + liquidity_pool_id: liquidity_pool_id, + min_price_r: min_price_r, + max_price_r: max_price_r + } = attrs + } do + %LiquidityPoolDeposit{ + liquidity_pool_id: ^liquidity_pool_id, + reserves_max: [%{amount: 1000.0000005}, %{amount: 3000.0000005}], + min_price: 0.268, + min_price_r: ^min_price_r, + max_price: 0.368, + max_price_r: ^max_price_r, + reserves_deposited: [%{amount: 983.0000005}, %{amount: 2378.0000005}], + shares_received: 1000 + } = LiquidityPoolDeposit.new(attrs) + end + + test "new/2 empty_attrs" do + %LiquidityPoolDeposit{ + liquidity_pool_id: nil, + reserves_max: nil, + min_price: nil, + min_price_r: nil, + max_price: nil, + max_price_r: nil, + reserves_deposited: nil, + shares_received: nil + } = LiquidityPoolDeposit.new(%{}) + end +end diff --git a/test/horizon/operation/liquidity_pool_withdraw_test.exs b/test/horizon/operation/liquidity_pool_withdraw_test.exs new file mode 100644 index 00000000..fa6239ec --- /dev/null +++ b/test/horizon/operation/liquidity_pool_withdraw_test.exs @@ -0,0 +1,52 @@ +defmodule Stellar.Horizon.Operation.LiquidityPoolWithdrawTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.LiquidityPoolWithdraw + + setup do + %{ + attrs: %{ + liquidity_pool_id: "929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072", + reserves_received: [ + %{ + asset: "JPY:GBVAOIACNSB7OVUXJYC5UE2D4YK2F7A24T7EE5YOMN4CE6GCHUTOUQXM", + amount: "1000.0000005" + }, + %{ + asset: "EURT:GAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S", + amount: "3000.0000005" + } + ], + reserves_min: [ + %{ + asset: "JPY:GBVAOIACNSB7OVUXJYC5UE2D4YK2F7A24T7EE5YOMN4CE6GCHUTOUQXM", + amount: "983.0000005" + }, + %{ + asset: "EURT:GAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S", + amount: "2378.0000005" + } + ], + shares: "1000" + } + } + end + + test "new/2", %{attrs: %{liquidity_pool_id: liquidity_pool_id} = attrs} do + %LiquidityPoolWithdraw{ + liquidity_pool_id: ^liquidity_pool_id, + reserves_received: [%{amount: 1000.0000005}, %{amount: 3000.0000005}], + reserves_min: [%{amount: 983.0000005}, %{amount: 2378.0000005}], + shares: 1000 + } = LiquidityPoolWithdraw.new(attrs) + end + + test "new/2 empty_attrs" do + %LiquidityPoolWithdraw{ + liquidity_pool_id: nil, + reserves_received: nil, + reserves_min: nil, + shares: nil + } = LiquidityPoolWithdraw.new(%{}) + end +end diff --git a/test/horizon/operation/manage_buy_offer_test.exs b/test/horizon/operation/manage_buy_offer_test.exs new file mode 100644 index 00000000..c4335798 --- /dev/null +++ b/test/horizon/operation/manage_buy_offer_test.exs @@ -0,0 +1,59 @@ +defmodule Stellar.Horizon.Operation.ManageBuyOfferTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.ManageBuyOffer + + setup do + %{ + attrs: %{ + amount: "1336.0326986", + price: "0.0559999", + price_r: %{ + n: 559_999, + d: 10_000_000 + }, + selling_asset_type: "credit_alphanum4", + selling_asset_code: "USD", + selling_asset_issuer: "GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX", + buying_asset_type: "native", + offer_id: "0" + } + } + end + + test "new/2", %{ + attrs: + %{ + price_r: price_r, + selling_asset_type: selling_asset_type, + selling_asset_code: selling_asset_code, + selling_asset_issuer: selling_asset_issuer, + buying_asset_type: buying_asset_type, + offer_id: offer_id + } = attrs + } do + %ManageBuyOffer{ + amount: 1336.0326986, + price: 0.0559999, + price_r: ^price_r, + selling_asset_type: ^selling_asset_type, + selling_asset_code: ^selling_asset_code, + selling_asset_issuer: ^selling_asset_issuer, + buying_asset_type: ^buying_asset_type, + offer_id: ^offer_id + } = ManageBuyOffer.new(attrs) + end + + test "new/2 empty_attrs" do + %ManageBuyOffer{ + amount: nil, + price: nil, + price_r: nil, + selling_asset_type: nil, + selling_asset_code: nil, + selling_asset_issuer: nil, + buying_asset_type: nil, + offer_id: nil + } = ManageBuyOffer.new(%{}) + end +end diff --git a/test/horizon/operation/manage_data_test.exs b/test/horizon/operation/manage_data_test.exs new file mode 100644 index 00000000..534ce24a --- /dev/null +++ b/test/horizon/operation/manage_data_test.exs @@ -0,0 +1,22 @@ +defmodule Stellar.Horizon.Operation.ManageDataTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.ManageData + + setup do + %{ + attrs: %{ + name: "config.memo_required", + value: "MQ==" + } + } + end + + test "new/2", %{attrs: %{name: name, value: value} = attrs} do + %ManageData{name: ^name, value: ^value} = ManageData.new(attrs) + end + + test "new/2 empty_attrs" do + %ManageData{name: nil, value: nil} = ManageData.new(%{}) + end +end diff --git a/test/horizon/operation/manage_sell_offer_test.exs b/test/horizon/operation/manage_sell_offer_test.exs new file mode 100644 index 00000000..185047a2 --- /dev/null +++ b/test/horizon/operation/manage_sell_offer_test.exs @@ -0,0 +1,59 @@ +defmodule Stellar.Horizon.Operation.ManageSellOfferTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.ManageSellOffer + + setup do + %{ + attrs: %{ + amount: "1336.0326986", + price: "0.0559999", + price_r: %{ + n: 559_999, + d: 10_000_000 + }, + buying_asset_type: "credit_alphanum4", + buying_asset_code: "USD", + buying_asset_issuer: "GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX", + selling_asset_type: "native", + offer_id: "0" + } + } + end + + test "new/2", %{ + attrs: + %{ + price_r: price_r, + buying_asset_type: buying_asset_type, + buying_asset_code: buying_asset_code, + buying_asset_issuer: buying_asset_issuer, + selling_asset_type: selling_asset_type, + offer_id: offer_id + } = attrs + } do + %ManageSellOffer{ + amount: 1336.0326986, + price: 0.0559999, + price_r: ^price_r, + buying_asset_type: ^buying_asset_type, + buying_asset_code: ^buying_asset_code, + buying_asset_issuer: ^buying_asset_issuer, + selling_asset_type: ^selling_asset_type, + offer_id: ^offer_id + } = ManageSellOffer.new(attrs) + end + + test "new/2 empty_attrs" do + %ManageSellOffer{ + amount: nil, + price: nil, + price_r: nil, + buying_asset_type: nil, + buying_asset_code: nil, + buying_asset_issuer: nil, + selling_asset_type: nil, + offer_id: nil + } = ManageSellOffer.new(%{}) + end +end diff --git a/test/horizon/operation/path_payment_strict_receive_test.exs b/test/horizon/operation/path_payment_strict_receive_test.exs new file mode 100644 index 00000000..913301c7 --- /dev/null +++ b/test/horizon/operation/path_payment_strict_receive_test.exs @@ -0,0 +1,80 @@ +defmodule Stellar.Horizon.Operation.PathPaymentStrictReceiveTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.PathPaymentStrictReceive + + setup do + %{ + attrs: %{ + asset_type: "credit_alphanum4", + asset_code: "BRL", + asset_issuer: "GDVKY2GU2DRXWTBEYJJWSFXIGBZV6AZNBVVSUHEPZI54LIS6BA7DVVSP", + from: "GBZH7S5NC57XNHKHJ75C5DGMI3SP6ZFJLIKW74K6OSMA5E5DFMYBDD2Z", + to: "GBZH7S5NC57XNHKHJ75C5DGMI3SP6ZFJLIKW74K6OSMA5E5DFMYBDD2Z", + amount: "0.1000000", + path: [ + %{ + asset_type: "credit_alphanum4", + asset_code: "USD", + asset_issuer: "GBUYUAI75XXWDZEKLY66CFYKQPET5JR4EENXZBUZ3YXZ7DS56Z4OKOFU" + }, + %{ + asset_type: "native" + } + ], + source_amount: "0.0198773", + source_max: "0.0198774", + source_asset_type: "credit_alphanum4", + source_asset_code: "USD", + source_asset_issuer: "GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX" + } + } + end + + test "new/2", %{ + attrs: + %{ + asset_type: asset_type, + asset_code: asset_code, + asset_issuer: asset_issuer, + from: from, + to: to, + path: path, + source_asset_type: source_asset_type, + source_asset_code: source_asset_code, + source_asset_issuer: source_asset_issuer + } = attrs + } do + %PathPaymentStrictReceive{ + asset_type: ^asset_type, + asset_code: ^asset_code, + asset_issuer: ^asset_issuer, + from: ^from, + to: ^to, + amount: 0.1, + path: ^path, + source_amount: 0.0198773, + source_max: 0.0198774, + source_asset_type: ^source_asset_type, + source_asset_code: ^source_asset_code, + source_asset_issuer: ^source_asset_issuer + } = PathPaymentStrictReceive.new(attrs) + end + + test "new/2 empty_attrs" do + %PathPaymentStrictReceive{ + asset_type: nil, + asset_code: nil, + asset_issuer: nil, + from: nil, + to: nil, + amount: nil, + path: nil, + source_amount: nil, + source_max: nil, + source_asset_type: nil, + source_asset_code: nil, + source_asset_issuer: nil + } = PathPaymentStrictReceive.new(%{}) + end +end diff --git a/test/horizon/operation/path_payment_strict_send_test.exs b/test/horizon/operation/path_payment_strict_send_test.exs new file mode 100644 index 00000000..1c4c4df7 --- /dev/null +++ b/test/horizon/operation/path_payment_strict_send_test.exs @@ -0,0 +1,80 @@ +defmodule Stellar.Horizon.Operation.PathPaymentStrictSendTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.PathPaymentStrictSend + + setup do + %{ + attrs: %{ + asset_type: "credit_alphanum4", + asset_code: "BRL", + asset_issuer: "GDVKY2GU2DRXWTBEYJJWSFXIGBZV6AZNBVVSUHEPZI54LIS6BA7DVVSP", + from: "GBZH7S5NC57XNHKHJ75C5DGMI3SP6ZFJLIKW74K6OSMA5E5DFMYBDD2Z", + to: "GBZH7S5NC57XNHKHJ75C5DGMI3SP6ZFJLIKW74K6OSMA5E5DFMYBDD2Z", + amount: "0.1000000", + path: [ + %{ + asset_type: "credit_alphanum4", + asset_code: "USD", + asset_issuer: "GBUYUAI75XXWDZEKLY66CFYKQPET5JR4EENXZBUZ3YXZ7DS56Z4OKOFU" + }, + %{ + asset_type: "native" + } + ], + source_amount: "0.0198773", + destination_min: "0.0198774", + source_asset_type: "credit_alphanum4", + source_asset_code: "USD", + source_asset_issuer: "GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX" + } + } + end + + test "new/2", %{ + attrs: + %{ + asset_type: asset_type, + asset_code: asset_code, + asset_issuer: asset_issuer, + from: from, + to: to, + path: path, + source_asset_type: source_asset_type, + source_asset_code: source_asset_code, + source_asset_issuer: source_asset_issuer + } = attrs + } do + %PathPaymentStrictSend{ + asset_type: ^asset_type, + asset_code: ^asset_code, + asset_issuer: ^asset_issuer, + from: ^from, + to: ^to, + amount: 0.1, + path: ^path, + source_amount: 0.0198773, + destination_min: 0.0198774, + source_asset_type: ^source_asset_type, + source_asset_code: ^source_asset_code, + source_asset_issuer: ^source_asset_issuer + } = PathPaymentStrictSend.new(attrs) + end + + test "new/2 empty_attrs" do + %PathPaymentStrictSend{ + asset_type: nil, + asset_code: nil, + asset_issuer: nil, + from: nil, + to: nil, + amount: nil, + path: nil, + source_amount: nil, + destination_min: nil, + source_asset_type: nil, + source_asset_code: nil, + source_asset_issuer: nil + } = PathPaymentStrictSend.new(%{}) + end +end diff --git a/test/horizon/operation/payment_test.exs b/test/horizon/operation/payment_test.exs new file mode 100644 index 00000000..9bd38107 --- /dev/null +++ b/test/horizon/operation/payment_test.exs @@ -0,0 +1,34 @@ +defmodule Stellar.Horizon.Operation.PaymentTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.Payment + + setup do + %{ + attrs: %{ + asset_type: "credit_alphanum4", + asset_code: "NGNT", + asset_issuer: "GAWODAROMJ33V5YDFY3NPYTHVYQG7MJXVJ2ND3AOGIHYRWINES6ACCPD", + from: "GCAXBKU3AKYJPLQ6PEJ6L47KOATCYCBJ2NFRGAK7FUUA2DCEUC265SU2", + to: "GC2QCKFI3DOBEYVBONPVNA2PMLU225IKKI6XPENMWR2CTWSFBAOU7T34", + amount: "5.0267500000" + } + } + end + + test "new/2", %{ + attrs: %{asset_type: asset_type, asset_code: asset_code, from: from, to: to} = attrs + } do + %Payment{ + asset_type: ^asset_type, + asset_code: ^asset_code, + from: ^from, + to: ^to, + amount: 5.02675 + } = Payment.new(attrs) + end + + test "new/2 empty_attrs" do + %Payment{asset_type: nil, asset_code: nil, from: nil, to: nil, amount: nil} = Payment.new(%{}) + end +end diff --git a/test/horizon/operation/revoke_sponsorship_test.exs b/test/horizon/operation/revoke_sponsorship_test.exs new file mode 100644 index 00000000..7588e186 --- /dev/null +++ b/test/horizon/operation/revoke_sponsorship_test.exs @@ -0,0 +1,44 @@ +defmodule Stellar.Horizon.Operation.RevokeSponsorshipTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.RevokeSponsorship + + setup do + %{ + attrs: %{ + account_id: "GC3C4AKRBQLHOJ45U4XG35ESVWRDECWO5XLDGYADO6DPR3L7KIDVUMML", + claimable_balance_id: "929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072", + signer_key: "GCVLWV5B3L3YE6DSCCMHLCK7QIB365NYOLQLW3ZKHI5XINNMRLJ6YHVX" + } + } + end + + test "new/2", %{ + attrs: + %{ + account_id: account_id, + claimable_balance_id: claimable_balance_id, + signer_key: signer_key + } = attrs + } do + %RevokeSponsorship{ + account_id: ^account_id, + claimable_balance_id: ^claimable_balance_id, + signer_key: ^signer_key + } = RevokeSponsorship.new(attrs) + end + + test "new/2 empty_attrs" do + %RevokeSponsorship{ + account_id: nil, + claimable_balance_id: nil, + data_account_id: nil, + data_name: nil, + offer_id: nil, + trustline_account_id: nil, + trustline_asset: nil, + signer_account_id: nil, + signer_key: nil + } = RevokeSponsorship.new(%{}) + end +end diff --git a/test/horizon/operation/set_options_test.exs b/test/horizon/operation/set_options_test.exs new file mode 100644 index 00000000..9c53033f --- /dev/null +++ b/test/horizon/operation/set_options_test.exs @@ -0,0 +1,62 @@ +defmodule Stellar.Horizon.Operation.SetOptionsTest do + use ExUnit.Case + + alias Stellar.Horizon.Operation.SetOptions + + setup do + %{ + attrs: %{ + signer_weight: 2, + signer_key: "GCVLWV5B3L3YE6DSCCMHLCK7QIB365NYOLQLW3ZKHI5XINNMRLJ6YHVX", + master_key_weight: 1, + low_threshold: 0, + med_threshold: 1, + high_threshold: 2, + home_domain: "www.stellar.org", + set_flags: [1, 2], + set_flags_s: ["AUTH_REQUIRED_FLAG", "AUTH_REVOCABLE_FLAG"] + } + } + end + + test "new/2", %{ + attrs: + %{ + signer_weight: signer_weight, + signer_key: signer_key, + master_key_weight: master_key_weight, + low_threshold: low_threshold, + med_threshold: med_threshold, + high_threshold: high_threshold, + home_domain: home_domain, + set_flags: set_flags, + set_flags_s: set_flags_s + } = attrs + } do + %SetOptions{ + signer_weight: ^signer_weight, + signer_key: ^signer_key, + master_key_weight: ^master_key_weight, + low_threshold: ^low_threshold, + med_threshold: ^med_threshold, + high_threshold: ^high_threshold, + home_domain: ^home_domain, + set_flags: ^set_flags, + set_flags_s: ^set_flags_s + } = SetOptions.new(attrs) + end + + test "new/2 empty_attrs" do + %SetOptions{ + signer_weight: nil, + signer_key: nil, + master_key_weight: nil, + low_threshold: nil, + med_threshold: nil, + high_threshold: nil, + home_domain: nil, + set_flags: nil, + set_flags_s: nil + } = SetOptions.new(%{}) + end +end diff --git a/test/horizon/operation_test.exs b/test/horizon/operation_test.exs new file mode 100644 index 00000000..c914753c --- /dev/null +++ b/test/horizon/operation_test.exs @@ -0,0 +1,114 @@ +defmodule Stellar.Horizon.OperationTest do + use ExUnit.Case + + alias Stellar.Test.Fixtures.Horizon + alias Stellar.Horizon.Operation + + alias Stellar.Horizon.Operation.{ + AccountMerge, + AllowTrust, + BeginSponsoringFutureReserves, + BumpSequence, + ChangeTrust, + CreateAccount, + CreateClaimableBalance, + ClaimClaimableBalance, + CreatePassiveSellOffer, + EndSponsoringFutureReserves, + LiquidityPoolDeposit, + LiquidityPoolWithdraw, + ManageBuyOffer, + ManageSellOffer, + ManageData, + PathPaymentStrictReceive, + PathPaymentStrictSend, + Payment, + RevokeSponsorship, + SetOptions + } + + setup do + json_body = Horizon.fixture("operation") + attrs = Jason.decode!(json_body, keys: :atoms) + + %{attrs: attrs} + end + + test "new/2", %{attrs: attrs} do + %Operation{ + body: %PathPaymentStrictSend{ + amount: 26.5544244, + asset_code: "BRL", + asset_issuer: "GDVKY2GU2DRXWTBEYJJWSFXIGBZV6AZNBVVSUHEPZI54LIS6BA7DVVSP", + asset_type: "credit_alphanum4", + destination_min: 26.5544244, + from: "GBZH7S5NC57XNHKHJ75C5DGMI3SP6ZFJLIKW74K6OSMA5E5DFMYBDD2Z", + path: [ + %{ + asset_code: "EURT", + asset_issuer: "GAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S", + asset_type: "credit_alphanum4" + }, + %{asset_type: "native"} + ], + source_amount: 5.0, + source_asset_code: "USD", + source_asset_issuer: "GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX", + source_asset_type: "credit_alphanum4", + to: "GBZH7S5NC57XNHKHJ75C5DGMI3SP6ZFJLIKW74K6OSMA5E5DFMYBDD2Z" + }, + created_at: ~U[2020-04-04 13:47:50Z], + id: 124_624_072_438_579_201, + paging_token: "124624072438579201", + source_account: "GBZH7S5NC57XNHKHJ75C5DGMI3SP6ZFJLIKW74K6OSMA5E5DFMYBDD2Z", + transaction: nil, + transaction_hash: "2b863994825fe85b80bfdff433b348d5ce80b23cd9ee2a56dcd6ee1abd52c9f8", + transaction_successful: true, + type: "path_payment_strict_send", + type_i: 13 + } = Operation.new(attrs) + end + + test "new/2 empty_attrs" do + %Operation{ + body: %{}, + created_at: nil, + id: nil, + paging_token: nil, + source_account: nil, + transaction_hash: nil, + transaction_successful: nil, + type: nil, + type_i: nil, + transaction: nil + } = Operation.new(%{}) + end + + test "operation_body" do + for {type_i, type, body} <- [ + {0, "create_account", %CreateAccount{}}, + {1, "payment", %Payment{}}, + {2, "path_payment_strict_receive", %PathPaymentStrictReceive{}}, + {3, "manage_sell_offer", %ManageSellOffer{}}, + {4, "create_passive_sell_offer", %CreatePassiveSellOffer{}}, + {5, "set_options", %SetOptions{}}, + {6, "change_trust", %ChangeTrust{}}, + {7, "allow_trust", %AllowTrust{}}, + {8, "account_merge", %AccountMerge{}}, + {10, "manage_data", %ManageData{}}, + {11, "bump_sequence", %BumpSequence{}}, + {12, "manage_buy_offer", %ManageBuyOffer{}}, + {13, "path_payment_strict_send", %PathPaymentStrictSend{}}, + {14, "create_claimable_balance", %CreateClaimableBalance{}}, + {15, "claim_claimable_balance", %ClaimClaimableBalance{}}, + {16, "begin_sponsoring_future_reserves", %BeginSponsoringFutureReserves{}}, + {17, "end_sponsoring_future_reserves", %EndSponsoringFutureReserves{}}, + {19, "revoke_sponsorship", %RevokeSponsorship{}}, + {22, "liquidity_pool_deposit", %LiquidityPoolDeposit{}}, + {23, "liquidity_pool_withdraw", %LiquidityPoolWithdraw{}} + ] do + %Operation{body: ^body, type_i: ^type_i, type: ^type} = + Operation.new(%{type_i: type_i, type: type}) + end + end +end diff --git a/test/horizon/operations_test.exs b/test/horizon/operations_test.exs new file mode 100644 index 00000000..b0a2db8d --- /dev/null +++ b/test/horizon/operations_test.exs @@ -0,0 +1,211 @@ +defmodule Stellar.Horizon.Client.CannedOperationRequests do + @moduledoc false + + alias Stellar.Horizon.Error + alias Stellar.Test.Fixtures.Horizon + + @base_url "https://horizon-testnet.stellar.org" + + @spec request( + method :: atom(), + url :: String.t(), + headers :: list(), + body :: String.t(), + options :: list() + ) :: {:ok, non_neg_integer(), list(), String.t()} | {:error, atom()} + def request(:get, @base_url <> "/operations/124624072438579201", _headers, _body, _opts) do + json_body = Horizon.fixture("operation") + {:ok, 200, [], json_body} + end + + def request( + :get, + @base_url <> "/operations/124624072438579201/effects" <> _query, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("effects") + {:ok, 200, [], json_body} + end + + def request(:get, @base_url <> "/operations/error", _headers, _body, _opts) do + json_error = Horizon.fixture("404") + {:ok, 404, [], json_error} + end + + def request(:get, @base_url <> "/operations" <> _query, _headers, _body, _opts) do + json_body = Horizon.fixture("operations") + {:ok, 200, [], json_body} + end + + def request(:get, @base_url <> "/payments" <> _query, _headers, _body, _opts) do + json_body = Horizon.fixture("payments") + {:ok, 200, [], json_body} + end +end + +defmodule Stellar.Horizon.OperationsTest do + use ExUnit.Case + + alias Stellar.Horizon.Client.CannedOperationRequests + + alias Stellar.Horizon.{ + Collection, + Effect, + Error, + Operation, + Operations + } + + alias Stellar.Horizon.Operation.{CreateAccount, Payment, SetOptions, PathPaymentStrictSend} + + setup do + Application.put_env(:stellar_sdk, :http_client, CannedOperationRequests) + + on_exit(fn -> + Application.delete_env(:stellar_sdk, :http_client) + end) + + %{operation_id: 124_624_072_438_579_201} + end + + test "retrieve/1", %{operation_id: operation_id} do + {:ok, + %Operation{ + body: %PathPaymentStrictSend{ + amount: 26.5544244, + asset_code: "BRL", + asset_issuer: "GDVKY2GU2DRXWTBEYJJWSFXIGBZV6AZNBVVSUHEPZI54LIS6BA7DVVSP", + asset_type: "credit_alphanum4", + destination_min: 26.5544244, + from: "GBZH7S5NC57XNHKHJ75C5DGMI3SP6ZFJLIKW74K6OSMA5E5DFMYBDD2Z", + source_amount: 5.0, + source_asset_code: "USD", + source_asset_issuer: "GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX", + source_asset_type: "credit_alphanum4", + to: "GBZH7S5NC57XNHKHJ75C5DGMI3SP6ZFJLIKW74K6OSMA5E5DFMYBDD2Z" + }, + created_at: ~U[2020-04-04 13:47:50Z], + id: 124_624_072_438_579_201, + source_account: "GBZH7S5NC57XNHKHJ75C5DGMI3SP6ZFJLIKW74K6OSMA5E5DFMYBDD2Z", + transaction_hash: "2b863994825fe85b80bfdff433b348d5ce80b23cd9ee2a56dcd6ee1abd52c9f8", + transaction_successful: true, + type: "path_payment_strict_send", + type_i: 13 + }} = Operations.retrieve(operation_id) + end + + test "all/1" do + {:ok, + %Collection{ + next: + "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31/operations?cursor=12884905985&limit=3&order=desc", + prev: + "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31/operations?cursor=12884905987&limit=3&order=asc", + records: [ + %Operation{ + body: %SetOptions{ + master_key_weight: 0 + }, + created_at: ~U[2015-09-30 17:15:54Z], + id: 12_884_905_987, + paging_token: "12884905987", + source_account: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + transaction: nil, + transaction_hash: "132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31", + transaction_successful: true, + type: "set_options", + type_i: 5 + }, + %Operation{ + body: %Payment{ + amount: 99_999_999_959.99997, + asset_code: nil, + asset_issuer: nil, + asset_type: "native", + from: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + to: "GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB" + }, + created_at: ~U[2015-09-30 17:15:54Z], + id: 12_884_905_986, + paging_token: "12884905986", + source_account: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + transaction: nil, + transaction_hash: "132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31", + transaction_successful: true, + type: "payment", + type_i: 1 + }, + %Operation{ + body: %CreateAccount{ + account: "GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB", + funder: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + starting_balance: 20.0 + }, + created_at: ~U[2015-09-30 17:15:54Z], + id: 12_884_905_985, + paging_token: "12884905985", + source_account: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + transaction: nil, + transaction_hash: "132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31", + transaction_successful: true, + type: "create_account", + type_i: 0 + } + ], + self: + "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31/operations?cursor=&limit=3&order=desc" + }} = Operations.all() + end + + test "list_effects/2", %{operation_id: operation_id} do + {:ok, + %Collection{ + next: + "https://horizon.stellar.org/effects?cursor=12884905985-3\u0026limit=3\u0026order=asc", + prev: + "https://horizon.stellar.org/effects?cursor=12884905985-1\u0026limit=3\u0026order=desc", + records: [ + %Effect{type: "account_created", created_at: ~U[2015-09-30 17:15:54Z]}, + %Effect{type: "account_debited", created_at: ~U[2015-09-30 17:16:54Z]}, + %Effect{type: "signer_created", created_at: ~U[2015-09-30 17:17:54Z]} + ] + }} = Operations.list_effects(operation_id, limit: 3) + end + + test "list_payments/1" do + {:ok, + %Collection{ + records: [ + %Operation{ + id: 165_248_737_866_100_737, + body: %Payment{amount: 0.0000001}, + type: "payment" + }, + %Operation{ + id: 164_316_377_777_254_401, + body: %Payment{amount: 1.0}, + type: "payment", + type_i: 1 + }, + %Operation{ + id: 163_385_576_757_178_369, + body: %Payment{amount: 1.0}, + type: "payment", + type_i: 1 + } + ] + }} = Operations.list_payments(limit: 3) + end + + test "error" do + {:error, + %Error{ + extras: nil, + status_code: 404, + title: "Resource Missing", + type: "https://stellar.org/horizon-errors/not_found" + }} = Operations.retrieve("error") + end +end diff --git a/test/horizon/request_test.exs b/test/horizon/request_test.exs new file mode 100644 index 00000000..d5a92413 --- /dev/null +++ b/test/horizon/request_test.exs @@ -0,0 +1,164 @@ +defmodule Stellar.Horizon.Client.CannedRequestImpl do + @moduledoc false + + @behaviour Stellar.Horizon.Client.Spec + + @impl true + def request(_method, "/transactions/f08b8/effects" <> _query, _headers, _body, _opts) do + send(self(), {:horizon_requested, 200}) + {:ok, 200, [], nil} + end +end + +defmodule Stellar.Horizon.RequestTest do + use ExUnit.Case + + alias Stellar.Test.Fixtures.Horizon + alias Stellar.Horizon.Client.CannedRequestImpl + alias Stellar.Horizon.{Collection, Error, Request, Transaction} + + setup do + Application.put_env(:stellar_sdk, :http_client_impl, CannedRequestImpl) + + on_exit(fn -> + Application.delete_env(:stellar_sdk, :http_client_impl) + end) + + endpoint = "transactions" + hash = "f08b83906eaebfbf359182cc1e47a0e5c4bbdbad1a897785d5677a2bfc54b5b1" + body = [tx: "AAAAAgAAABAAAAAAAAAA==="] + headers = [{"Content-Type", "application/x-www-form-urlencoded"}] + query = [cursor: "549755817992-1", order: "desc", limit: 25] + segment = "trades" + segment_path = "0052d44a6c260660115f07c5a78631770e62aae3ffde96731c44b1509e9c8434" + + %{ + endpoint: endpoint, + hash: hash, + segment: segment, + segment_path: segment_path, + body: body, + headers: headers, + query: query + } + end + + test "new/1", %{endpoint: endpoint, hash: hash} do + %Request{method: :get, endpoint: ^endpoint, path: ^hash, body: [], headers: [], query: []} = + Request.new(:get, endpoint, path: hash) + end + + test "new/1 with_segment", %{ + endpoint: endpoint, + hash: hash, + segment: segment, + segment_path: segment_path + } do + %Request{ + method: :get, + endpoint: ^endpoint, + path: ^hash, + segment: ^segment, + segment_path: ^segment_path, + body: [], + headers: [], + query: [] + } = Request.new(:get, endpoint, path: hash, segment: segment, segment_path: segment_path) + end + + test "add_body/2", %{endpoint: endpoint, body: body} do + %Request{method: :post, endpoint: ^endpoint, body: ^body} = + :post + |> Request.new(endpoint) + |> Request.add_body(body) + end + + test "add_headers/2", %{endpoint: endpoint, headers: headers} do + %Request{method: :post, endpoint: ^endpoint, headers: ^headers} = + :post + |> Request.new(endpoint) + |> Request.add_headers(headers) + end + + test "add_query/3", %{endpoint: endpoint, query: query} do + %Request{ + method: :get, + endpoint: ^endpoint, + query: ^query, + encoded_query: "cursor=549755817992-1&order=desc&limit=25" + } = + :get + |> Request.new(endpoint) + |> Request.add_query(query) + end + + test "add_query/3 extra_params", %{endpoint: endpoint, query: query} do + query = query ++ [include_failed: true] + + %Request{ + method: :get, + endpoint: ^endpoint, + query: ^query, + encoded_query: "cursor=549755817992-1&order=desc&limit=25&include_failed=true" + } = + :get + |> Request.new(endpoint) + |> Request.add_query(query, extra_params: [:include_failed]) + end + + test "add_query/3 invalid_params", %{endpoint: endpoint} do + %Request{ + method: :get, + endpoint: ^endpoint, + encoded_query: "cursor=549755817992-1" + } = + :get + |> Request.new(endpoint) + |> Request.add_query([cursor: "549755817992-1", test: "test"], + extra_params: [:include_failed] + ) + end + + test "perform/2" do + :get + |> Request.new("transactions", path: "f08b8", segment: "effects", segment_path: "123") + |> Request.add_query(limit: 10) + |> Request.perform() + + assert_receive({:horizon_requested, 200}) + end + + test "results/2 success" do + body = Horizon.fixture("transaction") + transaction = Jason.decode!(body, keys: :atoms) + + {:ok, + %Transaction{ + hash: "132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31", + ledger: 27_956_256 + }} = Request.results({:ok, transaction}, &Transaction.new(&1)) + end + + test "results/2 collection" do + body = Horizon.fixture("transactions") + transactions = Jason.decode!(body, keys: :atoms) + + {:ok, + %Collection{ + records: [ + %Transaction{hash: "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889"}, + %Transaction{hash: "2db4b22ca018119c5027a80578813ffcf582cda4aa9e31cd92b43cf1bda4fc5a"}, + %Transaction{hash: "3ce2aca2fed36da2faea31352c76c5e412348887a4c119b1e90de8d1b937396a"} + ], + next: + "https://horizon.stellar.org/transactions?cursor=33736968114176\u0026limit=3\u0026order=asc", + prev: + "https://horizon.stellar.org/transactions?cursor=12884905984\u0026limit=3\u0026order=desc", + self: "https://horizon.stellar.org/transactions?cursor=\u0026limit=3\u0026order=asc" + }} = Request.results({:ok, transactions}, &Collection.new({Transaction, &1})) + end + + test "results/2 error" do + {:error, %Error{}} = Request.results({:error, %Error{}}, &Collection.new({Transaction, &1})) + end +end diff --git a/test/horizon/trade_test.exs b/test/horizon/trade_test.exs new file mode 100644 index 00000000..6c3df4e5 --- /dev/null +++ b/test/horizon/trade_test.exs @@ -0,0 +1,55 @@ +defmodule Stellar.Horizon.TradeTest do + use ExUnit.Case + + alias Stellar.Test.Fixtures.Horizon + alias Stellar.Horizon.Trade + + setup do + json_body = Horizon.fixture("trade") + attrs = Jason.decode!(json_body, keys: :atoms) + + %{attrs: attrs} + end + + test "new/2", %{attrs: attrs} do + %Trade{ + id: "3697472920621057-2", + ledger_close_time: ~U[2015-11-18 03:47:47Z], + trade_type: "orderbook", + base_offer_id: 8, + base_account: "GAVH5JM5OKXGMQDS7YPRJ4MQCPXJUGH26LYQPQJ4SOMOJ4SXY472ZM7G", + base_amount: 20.0000000, + base_asset_type: "native", + counter_offer_id: "10", + counter_account: "GBB4JST32UWKOLGYYSCEYBHBCOFL2TGBHDVOMZP462ET4ZRD4ULA7S2L", + counter_amount: 5.3600000, + counter_asset_type: "credit_alphanum4", + counter_asset_code: "JPY", + counter_asset_issuer: "GBVAOIACNSB7OVUXJYC5UE2D4YK2F7A24T7EE5YOMN4CE6GCHUTOUQXM", + base_is_seller: true, + price: %{ + n: "67", + d: "250" + } + } = Trade.new(attrs) + end + + test "new/2 empty_attrs" do + %Trade{ + id: nil, + ledger_close_time: nil, + base_offer_id: nil, + base_account: nil, + base_amount: nil, + base_asset_type: nil, + counter_offer_id: nil, + counter_account: nil, + counter_amount: nil, + counter_asset_type: nil, + counter_asset_code: nil, + counter_asset_issuer: nil, + base_is_seller: nil, + price: nil + } = Trade.new(%{}) + end +end diff --git a/test/horizon/trades_test.exs b/test/horizon/trades_test.exs new file mode 100644 index 00000000..375df90f --- /dev/null +++ b/test/horizon/trades_test.exs @@ -0,0 +1,129 @@ +defmodule Stellar.Horizon.Client.CannedTradeRequests do + @moduledoc false + + alias Stellar.Horizon.Error + alias Stellar.Test.Fixtures.Horizon + + @base_url "https://horizon-testnet.stellar.org" + + @spec request( + method :: atom(), + url :: String.t(), + headers :: list(), + body :: String.t(), + options :: list() + ) :: {:ok, non_neg_integer(), list(), String.t()} | {:error, atom()} + def request(:get, @base_url <> "/trades?cursor=error", _headers, _body, _opts) do + json_error = Horizon.fixture("400") + {:ok, 400, [], json_error} + end + + def request(:get, @base_url <> "/trades" <> _query, _headers, _body, _opts) do + json_body = Horizon.fixture("trades") + {:ok, 200, [], json_body} + end +end + +defmodule Stellar.Horizon.TradesTest do + use ExUnit.Case + + alias Stellar.Horizon.Client.CannedTradeRequests + alias Stellar.Horizon.{Collection, Error, Trade, Trades} + + setup do + Application.put_env(:stellar_sdk, :http_client, CannedTradeRequests) + + on_exit(fn -> + Application.delete_env(:stellar_sdk, :http_client) + end) + end + + test "all/1" do + {:ok, + %Collection{ + next: + "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/trades?cursor=107449584845914113-0&limit=3&order=asc", + prev: + "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/trades?cursor=107449468881756161-0&limit=3&order=desc", + records: [ + %Trade{ + base_account: "GCO7OW5P2PP7WDN6YUDXUUOPAR4ZHJSDDCZTIAQRTRZHKQWV45WUPBWX", + base_amount: 4433.2, + base_asset_code: nil, + base_asset_issuer: nil, + base_asset_type: "native", + base_is_seller: true, + base_liquidity_pool_id: nil, + base_offer_id: 165_561_423, + counter_account: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + counter_amount: 443.32, + counter_asset_code: "BB1", + counter_asset_issuer: "GD5J6HLF5666X4AZLTFTXLY46J5SW7EXRKBLEYPJP33S33MXZGV6CWFN", + counter_asset_type: "credit_alphanum4", + counter_liquidity_pool_id: nil, + counter_offer_id: "4719135487309144065", + id: "107449468881756161-0", + ledger_close_time: ~U[2019-07-26 09:17:02Z], + paging_token: "107449468881756161-0", + price: %{d: "10", n: "1"}, + trade_type: nil + }, + %Trade{ + base_account: "GCQDOTIILRG634IRWAODTUS6H6Q7VUUNKZINBDJOXGJFR7YZ57FGYV7B", + base_amount: 10.0, + base_asset_code: nil, + base_asset_issuer: nil, + base_asset_type: "native", + base_is_seller: true, + base_liquidity_pool_id: nil, + base_offer_id: 165_561_423, + counter_account: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + counter_amount: 1.0, + counter_asset_code: "BB1", + counter_asset_issuer: "GD5J6HLF5666X4AZLTFTXLY46J5SW7EXRKBLEYPJP33S33MXZGV6CWFN", + counter_asset_type: "credit_alphanum4", + counter_liquidity_pool_id: nil, + counter_offer_id: "4719135504489037825", + id: "107449486061649921-0", + ledger_close_time: ~U[2019-07-26 09:17:25Z], + paging_token: "107449486061649921-0", + price: %{d: "10", n: "1"}, + trade_type: nil + }, + %Trade{ + base_account: "GAMU5TQFUMDGVKYQPPDCD2MKKUUWELSQAEKNNU4RFQCWFSRBPJA2MAGQ", + base_amount: 748.5338945, + base_asset_code: nil, + base_asset_issuer: nil, + base_asset_type: "native", + base_is_seller: true, + base_liquidity_pool_id: nil, + base_offer_id: 165_561_423, + counter_account: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + counter_amount: 74.8533887, + counter_asset_code: "BB1", + counter_asset_issuer: "GD5J6HLF5666X4AZLTFTXLY46J5SW7EXRKBLEYPJP33S33MXZGV6CWFN", + counter_asset_type: "credit_alphanum4", + counter_liquidity_pool_id: nil, + counter_offer_id: "104299548", + id: "107449584845914113-0", + ledger_close_time: ~U[2019-07-26 09:19:30Z], + paging_token: "107449584845914113-0", + price: %{d: "100000001", n: "10000000"}, + trade_type: nil + } + ] + }} = Trades.all(limit: 3) + end + + test "error" do + {:error, + %Error{ + detail: "The request you sent was invalid in some way.", + extras: %{invalid_field: "cursor", reason: "cursor: invalid value"}, + status_code: 400, + title: "Bad Request", + type: "https://stellar.org/horizon-errors/bad_request" + }} = Trades.all(cursor: "error") + end +end diff --git a/test/horizon/transaction/transaction_test.exs b/test/horizon/transaction_test.exs similarity index 94% rename from test/horizon/transaction/transaction_test.exs rename to test/horizon/transaction_test.exs index fa541441..5d101aa9 100644 --- a/test/horizon/transaction/transaction_test.exs +++ b/test/horizon/transaction_test.exs @@ -19,7 +19,7 @@ defmodule Stellar.Horizon.TransactionTest do max_fee: 100, operation_count: 1, hash: "132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31", - source_account: "GCO2IP3MJNUOKS4PUDI4C7LGGMQDJGXG3COYX3WSB4HHNAHKYV5YL3VC", + source_account: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", source_account_sequence: 64_034_663_849_209_932, created_at: ~U[2020-01-27 22:13:17Z], result_xdr: "AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAA=", diff --git a/test/horizon/transactions_test.exs b/test/horizon/transactions_test.exs index fe96d6b6..357856a4 100644 --- a/test/horizon/transactions_test.exs +++ b/test/horizon/transactions_test.exs @@ -13,12 +13,54 @@ defmodule Stellar.Horizon.Client.CannedTransactionRequests do body :: String.t(), options :: list() ) :: {:ok, non_neg_integer(), list(), String.t()} | {:error, atom()} - def request(:post, @base_url <> "/transactions/", _headers, "tx=bad", _opts) do - json_error = Horizon.fixture("400") + def request( + :get, + @base_url <> + "/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31/effects" <> + _query, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("effects") + {:ok, 200, [], json_body} + end + + def request( + :get, + @base_url <> + "/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31/operations" <> + _query, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("operations") + {:ok, 200, [], json_body} + end + + def request(:get, @base_url <> "/transactions/" <> _hash, _headers, _body, _opts) do + json_body = Horizon.fixture("transaction") + {:ok, 200, [], json_body} + end + + def request( + :get, + @base_url <> "/transactions?cursor=33736968114176&limit=" <> _limit, + _headers, + _body, + _opts + ) do + json_body = Horizon.fixture("transactions") + {:ok, 200, [], json_body} + end + + def request(:post, @base_url <> "/transactions", _headers, "tx=bad", _opts) do + json_error = Horizon.fixture("400_invalid_tx") {:ok, 400, [], json_error} end - def request(:post, @base_url <> "/transactions/", _headers, "tx=" <> _hash, _opts) do + def request(:post, @base_url <> "/transactions", _headers, "tx=" <> _envelope, _opts) do json_body = Horizon.fixture("transaction") {:ok, 200, [], json_body} end @@ -28,7 +70,8 @@ defmodule Stellar.Horizon.TransactionsTest do use ExUnit.Case alias Stellar.Horizon.Client.CannedTransactionRequests - alias Stellar.Horizon.{Error, Transaction, Transactions} + alias Stellar.Horizon.{Collection, Effect, Error, Operation, Transaction, Transactions} + alias Stellar.Horizon.Operation.{CreateAccount, Payment, SetOptions} setup do Application.put_env(:stellar_sdk, :http_client, CannedTransactionRequests) @@ -38,25 +81,117 @@ defmodule Stellar.Horizon.TransactionsTest do end) %{ + source_account: "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", base64_envelope: "AAAAAJ2kP2xLaOVLj6DRwX1mMyA0mubYnYvu0g8OdoDqxXuFAAAAZADjfzAACzBMAAAAAQAAAAAAAAAAAAAAAF4vYIYAAAABAAAABjI5ODQyNAAAAAAAAQAAAAAAAAABAAAAAKdeYELovtcnTxqPEVsdbxHLMoMRalZsK7lo/+3ARzUZAAAAAAAAAADUFJPYAAAAAAAAAAHqxXuFAAAAQBpLpQyh+mwDd5nDSxTaAh5wopBBUaSD1eOK9MdiO+4kWKVTqSr/Ko3kYE/+J42Opsewf81TwINONPbY2CtPggE=", hash: "132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31" } end - describe "create/1" do - test "success", %{base64_envelope: base64_envelope, hash: hash} do - {:ok, %Transaction{successful: true, envelope_xdr: ^base64_envelope, hash: ^hash}} = - Transactions.create(base64_envelope) - end - - test "error" do - {:error, - %Error{ - status_code: 400, - title: "Transaction Failed", - extras: %{result_codes: %{transaction: "tx_insufficient_fee"}} - }} = Transactions.create("bad") - end + test "create/1", %{base64_envelope: base64_envelope, hash: hash} do + {:ok, %Transaction{successful: true, envelope_xdr: ^base64_envelope, hash: ^hash}} = + Transactions.create(base64_envelope) + end + + test "retrieve/1", %{ + hash: hash, + base64_envelope: base64_envelope, + source_account: source_account + } do + {:ok, + %Transaction{ + created_at: ~U[2020-01-27 22:13:17Z], + envelope_xdr: ^base64_envelope, + fee_charged: 100, + hash: ^hash, + id: ^hash, + ledger: 27_956_256, + max_fee: 100, + memo: "298424", + memo_type: "text", + operation_count: 1, + result_xdr: "AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAA=", + signatures: [ + "GkulDKH6bAN3mcNLFNoCHnCikEFRpIPV44r0x2I77iRYpVOpKv8qjeRgT/4njY6mx7B/zVPAg0409tjYK0+CAQ==" + ], + source_account: ^source_account, + source_account_sequence: 64_034_663_849_209_932, + successful: true, + valid_after: ~U[1970-01-01 00:00:00Z], + valid_before: ~U[2020-01-27 22:13:26Z] + }} = Transactions.retrieve(hash) + end + + test "all/1" do + {:ok, + %Collection{ + records: [ + %Transaction{ + hash: "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889" + }, + %Transaction{ + hash: "2db4b22ca018119c5027a80578813ffcf582cda4aa9e31cd92b43cf1bda4fc5a" + }, + %Transaction{ + hash: "3ce2aca2fed36da2faea31352c76c5e412348887a4c119b1e90de8d1b937396a" + } + ], + next: + "https://horizon.stellar.org/transactions?cursor=33736968114176\u0026limit=3\u0026order=asc" + }} = Transactions.all(cursor: "33736968114176", limit: 3) + end + + test "list_effects/2", %{hash: hash} do + {:ok, + %Collection{ + next: + "https://horizon.stellar.org/effects?cursor=12884905985-3\u0026limit=3\u0026order=asc", + prev: + "https://horizon.stellar.org/effects?cursor=12884905985-1\u0026limit=3\u0026order=desc", + records: [ + %Effect{type: "account_created", created_at: ~U[2015-09-30 17:15:54Z]}, + %Effect{type: "account_debited", created_at: ~U[2015-09-30 17:16:54Z]}, + %Effect{type: "signer_created", created_at: ~U[2015-09-30 17:17:54Z]} + ] + }} = Transactions.list_effects(hash, limit: 3) + end + + test "list_operations/2", %{hash: hash, source_account: source_account} do + {:ok, + %Collection{ + next: + "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31/operations?cursor=12884905985&limit=3&order=desc", + prev: + "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31/operations?cursor=12884905987&limit=3&order=asc", + records: [ + %Operation{ + body: %SetOptions{}, + source_account: ^source_account, + type: "set_options", + type_i: 5 + }, + %Operation{ + body: %Payment{}, + source_account: ^source_account, + type: "payment", + type_i: 1 + }, + %Operation{ + body: %CreateAccount{}, + source_account: ^source_account, + type: "create_account", + type_i: 0 + } + ] + }} = Transactions.list_operations(hash, limit: 3, order: :desc) + end + + test "error" do + {:error, + %Error{ + status_code: 400, + title: "Transaction Failed", + extras: %{result_codes: %{transaction: "tx_insufficient_fee"}} + }} = Transactions.create("bad") end end diff --git a/test/support/fixtures/horizon/400.json b/test/support/fixtures/horizon/400.json index d73216d2..cd402a30 100644 --- a/test/support/fixtures/horizon/400.json +++ b/test/support/fixtures/horizon/400.json @@ -1,13 +1,10 @@ { - "type": "https://stellar.org/horizon-errors/transaction_failed", - "title": "Transaction Failed", + "type": "https://stellar.org/horizon-errors/bad_request", + "title": "Bad Request", "status": 400, - "detail": "The transaction failed when submitted to the stellar network. The `extras.result_codes` field on this response contains further details. Descriptions of each code can be found at: https://www.stellar.org/developers/guides/concepts/list-of-operations.html", + "detail": "The request you sent was invalid in some way.", "extras": { - "envelope_xdr": "AAAAAgAAAADdfhHDs4Vaug6p8Oxb1QRjNRdJt3pYKKBVhFHrEgd9QAAAAAoAEi4YAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAESB31AAAAAQFhc/liVXbLk3NtB2BtweFJ064JdDIfrTSrqKMhb1oIRK+0PSyvjzZTkRCJmQY3bHNXYNuepa2TF7aBdibrb1gI=", - "result_codes": { - "transaction": "tx_insufficient_fee" - }, - "result_xdr": "AAAAAAAAAAr////3AAAAAA==" + "invalid_field": "cursor", + "reason": "cursor: invalid value" } } diff --git a/test/support/fixtures/horizon/400_invalid_tx.json b/test/support/fixtures/horizon/400_invalid_tx.json new file mode 100644 index 00000000..d73216d2 --- /dev/null +++ b/test/support/fixtures/horizon/400_invalid_tx.json @@ -0,0 +1,13 @@ +{ + "type": "https://stellar.org/horizon-errors/transaction_failed", + "title": "Transaction Failed", + "status": 400, + "detail": "The transaction failed when submitted to the stellar network. The `extras.result_codes` field on this response contains further details. Descriptions of each code can be found at: https://www.stellar.org/developers/guides/concepts/list-of-operations.html", + "extras": { + "envelope_xdr": "AAAAAgAAAADdfhHDs4Vaug6p8Oxb1QRjNRdJt3pYKKBVhFHrEgd9QAAAAAoAEi4YAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAESB31AAAAAQFhc/liVXbLk3NtB2BtweFJ064JdDIfrTSrqKMhb1oIRK+0PSyvjzZTkRCJmQY3bHNXYNuepa2TF7aBdibrb1gI=", + "result_codes": { + "transaction": "tx_insufficient_fee" + }, + "result_xdr": "AAAAAAAAAAr////3AAAAAA==" + } +} diff --git a/test/support/fixtures/horizon/accounts.json b/test/support/fixtures/horizon/accounts.json new file mode 100644 index 00000000..f8aa3006 --- /dev/null +++ b/test/support/fixtures/horizon/accounts.json @@ -0,0 +1,265 @@ +{ + "_links": { + "self": { + "href": "https://horizon.stellar.org/accounts?cursor=\u0026limit=10\u0026order=asc\u0026signer=GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD" + }, + "next": { + "href": "https://horizon.stellar.org/accounts?cursor=GDRREYWHQWJDICNH4SAH4TT2JRBYRPTDYIMLK4UWBDT3X3ZVVYT6I4UQ\u0026limit=10\u0026order=asc\u0026signer=GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD" + }, + "prev": { + "href": "https://horizon.stellar.org/accounts?cursor=GDRREYWHQWJDICNH4SAH4TT2JRBYRPTDYIMLK4UWBDT3X3ZVVYT6I4UQ\u0026limit=10\u0026order=desc\u0026signer=GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD" + } + }, + "_embedded": { + "records": [ + { + "_links": { + "self": { + "href": "https://horizon.stellar.org/accounts/GD42RQNXTRIW6YR3E2HXV5T2AI27LBRHOERV2JIYNFMXOBA234SWLQQB" + }, + "transactions": { + "href": "https://horizon.stellar.org/accounts/GD42RQNXTRIW6YR3E2HXV5T2AI27LBRHOERV2JIYNFMXOBA234SWLQQB/transactions{?cursor,limit,order}", + "templated": true + }, + "operations": { + "href": "https://horizon.stellar.org/accounts/GD42RQNXTRIW6YR3E2HXV5T2AI27LBRHOERV2JIYNFMXOBA234SWLQQB/operations{?cursor,limit,order}", + "templated": true + }, + "payments": { + "href": "https://horizon.stellar.org/accounts/GD42RQNXTRIW6YR3E2HXV5T2AI27LBRHOERV2JIYNFMXOBA234SWLQQB/payments{?cursor,limit,order}", + "templated": true + }, + "effects": { + "href": "https://horizon.stellar.org/accounts/GD42RQNXTRIW6YR3E2HXV5T2AI27LBRHOERV2JIYNFMXOBA234SWLQQB/effects{?cursor,limit,order}", + "templated": true + }, + "offers": { + "href": "https://horizon.stellar.org/accounts/GD42RQNXTRIW6YR3E2HXV5T2AI27LBRHOERV2JIYNFMXOBA234SWLQQB/offers{?cursor,limit,order}", + "templated": true + }, + "trades": { + "href": "https://horizon.stellar.org/accounts/GD42RQNXTRIW6YR3E2HXV5T2AI27LBRHOERV2JIYNFMXOBA234SWLQQB/trades{?cursor,limit,order}", + "templated": true + }, + "data": { + "href": "https://horizon.stellar.org/accounts/GD42RQNXTRIW6YR3E2HXV5T2AI27LBRHOERV2JIYNFMXOBA234SWLQQB/data/{key}", + "templated": true + } + }, + "id": "GD42RQNXTRIW6YR3E2HXV5T2AI27LBRHOERV2JIYNFMXOBA234SWLQQB", + "paging_token": "", + "account_id": "GD42RQNXTRIW6YR3E2HXV5T2AI27LBRHOERV2JIYNFMXOBA234SWLQQB", + "sequence": 7275146318446606, + "last_modified_ledger": 22379074, + "subentry_count": 4, + "num_sponsoring": 0, + "num_sponsored": 0, + "thresholds": { + "low_threshold": 0, + "med_threshold": 0, + "high_threshold": 0 + }, + "flags": { + "auth_required": false, + "auth_revocable": false, + "auth_immutable": false, + "auth_clawback_enabled": false + }, + "balances": [ + { + "balance": "1000000.0000000", + "limit": "922337203685.4775807", + "buying_liabilities": "0.0000000", + "selling_liabilities": "0.0000000", + "last_modified_ledger": 632070, + "asset_type": "credit_alphanum4", + "asset_code": "FOO", + "asset_issuer": "GAGLYFZJMN5HEULSTH5CIGPOPAVUYPG5YSWIYDJMAPIECYEBPM2TA3QR", + "is_authorized": true, + "is_clawback_enabled": true + }, + { + "balance": "10000.0000000", + "buying_liabilities": "0.0000000", + "selling_liabilities": "0.0000000", + "asset_type": "native" + } + ], + "signers": [ + { + "public_key": "GDLEPBJBC2VSKJCLJB264F2WDK63X4NKOG774A3QWVH2U6PERGDPUCS4", + "weight": 1, + "key": "GDLEPBJBC2VSKJCLJB264F2WDK63X4NKOG774A3QWVH2U6PERGDPUCS4", + "type": "ed25519_public_key" + }, + { + "public_key": "GBPOFUJUHOFTZHMZ63H5GE6NX5KVKQRD6N3I2E5AL3T2UG7HSLPLXN2K", + "weight": 1, + "key": "GBPOFUJUHOFTZHMZ63H5GE6NX5KVKQRD6N3I2E5AL3T2UG7HSLPLXN2K", + "type": "sha256_hash" + }, + { + "public_key": "GDUDIN23QQTB23Q3Q6GUL6I7CEAQY4CWCFVRXFWPF4UJAQO47SPUFCXG", + "weight": 1, + "key": "GDUDIN23QQTB23Q3Q6GUL6I7CEAQY4CWCFVRXFWPF4UJAQO47SPUFCXG", + "type": "preauth_tx" + }, + { + "public_key": "GD42RQNXTRIW6YR3E2HXV5T2AI27LBRHOERV2JIYNFMXOBA234SWLQQB", + "weight": 1, + "key": "GD42RQNXTRIW6YR3E2HXV5T2AI27LBRHOERV2JIYNFMXOBA234SWLQQB", + "type": "ed25519_public_key" + } + ], + "data": { + "best_friend": "c3Ryb29weQ==" + } + }, + { + "_links": { + "self": { + "href": "https://horizon.stellar.org/accounts/GAP2KHWUMOHY7IO37UJY7SEBIITJIDZS5DRIIQRPEUT4VUKHZQGIRWS4" + }, + "transactions": { + "href": "https://horizon.stellar.org/accounts/GAP2KHWUMOHY7IO37UJY7SEBIITJIDZS5DRIIQRPEUT4VUKHZQGIRWS4/transactions{?cursor,limit,order}", + "templated": true + }, + "operations": { + "href": "https://horizon.stellar.org/accounts/GAP2KHWUMOHY7IO37UJY7SEBIITJIDZS5DRIIQRPEUT4VUKHZQGIRWS4/operations{?cursor,limit,order}", + "templated": true + }, + "payments": { + "href": "https://horizon.stellar.org/accounts/GAP2KHWUMOHY7IO37UJY7SEBIITJIDZS5DRIIQRPEUT4VUKHZQGIRWS4/payments{?cursor,limit,order}", + "templated": true + }, + "effects": { + "href": "https://horizon.stellar.org/accounts/GAP2KHWUMOHY7IO37UJY7SEBIITJIDZS5DRIIQRPEUT4VUKHZQGIRWS4/effects{?cursor,limit,order}", + "templated": true + }, + "offers": { + "href": "https://horizon.stellar.org/accounts/GAP2KHWUMOHY7IO37UJY7SEBIITJIDZS5DRIIQRPEUT4VUKHZQGIRWS4/offers{?cursor,limit,order}", + "templated": true + }, + "trades": { + "href": "https://horizon.stellar.org/accounts/GAP2KHWUMOHY7IO37UJY7SEBIITJIDZS5DRIIQRPEUT4VUKHZQGIRWS4/trades{?cursor,limit,order}", + "templated": true + }, + "data": { + "href": "https://horizon.stellar.org/accounts/GAP2KHWUMOHY7IO37UJY7SEBIITJIDZS5DRIIQRPEUT4VUKHZQGIRWS4/data/{key}", + "templated": true + } + }, + "id": "GAP2KHWUMOHY7IO37UJY7SEBIITJIDZS5DRIIQRPEUT4VUKHZQGIRWS4", + "account_id": "GAP2KHWUMOHY7IO37UJY7SEBIITJIDZS5DRIIQRPEUT4VUKHZQGIRWS4", + "sequence": "27692931217358855", + "subentry_count": 0, + "inflation_destination": "GA3FUYFOPWZ25YXTCA73RK2UGONHCO27OHQRSGV3VCE67UEPEFEDCOPA", + "home_domain": "strllar.org", + "last_modified_ledger": 38159678, + "last_modified_time": "2021-11-06T12:14:07Z", + "thresholds": { + "low_threshold": 0, + "med_threshold": 0, + "high_threshold": 0 + }, + "flags": { + "auth_required": false, + "auth_revocable": false, + "auth_immutable": false, + "auth_clawback_enabled": false + }, + "balances": [ + { + "balance": "21156412.7170658", + "buying_liabilities": "0.0000000", + "selling_liabilities": "0.0000000", + "asset_type": "native" + } + ], + "signers": [ + { + "weight": 1, + "key": "GAP2KHWUMOHY7IO37UJY7SEBIITJIDZS5DRIIQRPEUT4VUKHZQGIRWS4", + "type": "ed25519_public_key" + } + ], + "data": {}, + "num_sponsoring": 0, + "num_sponsored": 0, + "paging_token": "GAP2KHWUMOHY7IO37UJY7SEBIITJIDZS5DRIIQRPEUT4VUKHZQGIRWS4" + }, + { + "_links": { + "self": { + "href": "https://horizon.stellar.org/accounts/GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB" + }, + "transactions": { + "href": "https://horizon.stellar.org/accounts/GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB/transactions{?cursor,limit,order}", + "templated": true + }, + "operations": { + "href": "https://horizon.stellar.org/accounts/GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB/operations{?cursor,limit,order}", + "templated": true + }, + "payments": { + "href": "https://horizon.stellar.org/accounts/GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB/payments{?cursor,limit,order}", + "templated": true + }, + "effects": { + "href": "https://horizon.stellar.org/accounts/GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB/effects{?cursor,limit,order}", + "templated": true + }, + "offers": { + "href": "https://horizon.stellar.org/accounts/GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB/offers{?cursor,limit,order}", + "templated": true + }, + "trades": { + "href": "https://horizon.stellar.org/accounts/GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB/trades{?cursor,limit,order}", + "templated": true + }, + "data": { + "href": "https://horizon.stellar.org/accounts/GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB/data/{key}", + "templated": true + } + }, + "id": "GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB", + "account_id": "GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB", + "sequence": "27692914037489676", + "subentry_count": 0, + "inflation_destination": "GAP2KHWUMOHY7IO37UJY7SEBIITJIDZS5DRIIQRPEUT4VUKHZQGIRWS4", + "last_modified_ledger": 38157159, + "last_modified_time": "2021-11-06T08:20:12Z", + "thresholds": { + "low_threshold": 0, + "med_threshold": 0, + "high_threshold": 0 + }, + "flags": { + "auth_required": false, + "auth_revocable": false, + "auth_immutable": false, + "auth_clawback_enabled": false + }, + "balances": [ + { + "balance": "109.6995858", + "buying_liabilities": "0.0000000", + "selling_liabilities": "0.0000000", + "asset_type": "native" + } + ], + "signers": [ + { + "weight": 1, + "key": "GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB", + "type": "ed25519_public_key" + } + ], + "data": {}, + "num_sponsoring": 0, + "num_sponsored": 0, + "paging_token": "GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB" + } + ] + } +} diff --git a/test/support/fixtures/horizon/asset.json b/test/support/fixtures/horizon/asset.json new file mode 100644 index 00000000..f54023df --- /dev/null +++ b/test/support/fixtures/horizon/asset.json @@ -0,0 +1,30 @@ +{ + "_links": { + "toml": { + "href": "https://www.anchorusd.com/.well-known/stellar.toml" + } + }, + "asset_type": "credit_alphanum4", + "asset_code": "USD", + "asset_issuer": "GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX", + "paging_token": "USD_GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX_credit_alphanum4", + "accounts": { + "authorized": 9390, + "authorized_to_maintain_liabilities": 1240, + "unauthorized": 5 + }, + "num_claimable_balances": 253, + "balances": { + "authorized": "1347404.4083346", + "authorized_to_maintain_liabilities": "177931.9984610", + "unauthorized": "717.4677360" + }, + "claimable_balances_amount": "36303.8674450", + "amount": "1347404.4083346", + "num_accounts": 9390, + "flags": { + "auth_required": false, + "auth_revocable": false, + "auth_immutable": false + } +} diff --git a/test/support/fixtures/horizon/assets.json b/test/support/fixtures/horizon/assets.json new file mode 100644 index 00000000..8409c339 --- /dev/null +++ b/test/support/fixtures/horizon/assets.json @@ -0,0 +1,116 @@ +{ + "_links": { + "self": { + "href": "https://horizon-testnet.stellar.org/assets?cursor=&limit=3&order=desc" + }, + "next": { + "href": "https://horizon-testnet.stellar.org/assets?cursor=MTK_GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD_credit_alphanum4&limit=3&order=desc" + }, + "prev": { + "href": "https://horizon-testnet.stellar.org/assets?cursor=BTCNEW2022_GCEU4UKMCN6XDTXOKVR35HEMSNT5HRT36ZM6QCZPFEVSSP6M2NCYZOJW_credit_alphanum12&limit=3&order=asc" + } + }, + "_embedded": { + "records": [ + { + "_links": { + "toml": { + "href": "https://https://tracified.com/.well-known/stellar.toml" + } + }, + "asset_type": "credit_alphanum12", + "asset_code": "BTCNEW2022", + "asset_issuer": "GCEU4UKMCN6XDTXOKVR35HEMSNT5HRT36ZM6QCZPFEVSSP6M2NCYZOJW", + "paging_token": "BTCNEW2022_GCEU4UKMCN6XDTXOKVR35HEMSNT5HRT36ZM6QCZPFEVSSP6M2NCYZOJW_credit_alphanum12", + "num_accounts": 1, + "num_claimable_balances": 0, + "num_liquidity_pools": 0, + "amount": "10000000.0000", + "accounts": { + "authorized": 1, + "authorized_to_maintain_liabilities": 0, + "unauthorized": 0 + }, + "claimable_balances_amount": "0.0000000", + "liquidity_pools_amount": "0.0000000", + "balances": { + "authorized": "1.0000000", + "authorized_to_maintain_liabilities": "0.0000000", + "unauthorized": "0.0000000" + }, + "flags": { + "auth_required": false, + "auth_revocable": false, + "auth_immutable": false, + "auth_clawback_enabled": false + } + }, + { + "_links": { + "toml": { + "href": "" + } + }, + "asset_type": "credit_alphanum4", + "asset_code": "BTCN", + "asset_issuer": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "paging_token": "BTCN_GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD_credit_alphanum4", + "num_accounts": 1, + "num_claimable_balances": 0, + "num_liquidity_pools": 0, + "amount": "20000.0000000", + "accounts": { + "authorized": 1, + "authorized_to_maintain_liabilities": 0, + "unauthorized": 0 + }, + "claimable_balances_amount": "0.0000000", + "liquidity_pools_amount": "0.0000000", + "balances": { + "authorized": "1001.0000000", + "authorized_to_maintain_liabilities": "0.0000000", + "unauthorized": "0.0000000" + }, + "flags": { + "auth_required": false, + "auth_revocable": false, + "auth_immutable": false, + "auth_clawback_enabled": false + } + }, + { + "_links": { + "toml": { + "href": "" + } + }, + "asset_type": "credit_alphanum4", + "asset_code": "MTK", + "asset_issuer": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "paging_token": "MTK_GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD_credit_alphanum4", + "num_accounts": 2, + "num_claimable_balances": 0, + "num_liquidity_pools": 0, + "amount": "10004.0000000", + "accounts": { + "authorized": 2, + "authorized_to_maintain_liabilities": 0, + "unauthorized": 0 + }, + "claimable_balances_amount": "0.0000000", + "liquidity_pools_amount": "0.0000000", + "balances": { + "authorized": "10004.0000000", + "authorized_to_maintain_liabilities": "0.0000000", + "unauthorized": "0.0000000" + }, + "flags": { + "auth_required": false, + "auth_revocable": false, + "auth_immutable": false, + "auth_clawback_enabled": false + } + } + ] + } +} diff --git a/test/support/fixtures/horizon/claimable_balance.json b/test/support/fixtures/horizon/claimable_balance.json new file mode 100644 index 00000000..5cd9490b --- /dev/null +++ b/test/support/fixtures/horizon/claimable_balance.json @@ -0,0 +1,44 @@ +{ + "_links": { + "self": { + "href": "https://horizon.stellar.org/claimable_balances/00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072" + }, + "operations": { + "href": "https://horizon.stellar.org/claimable_balances/00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072/operations{?cursor,limit,order}", + "templated": true + }, + "transactions": { + "href": "https://horizon.stellar.org/claimable_balances/00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072/transactions{?cursor,limit,order}", + "templated": true + } + }, + "id": "00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072", + "paging_token": "00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072", + "asset": "native", + "amount": "10.0000000", + "claimants": [ + { + "destination": "GC3C4AKRBQLHOJ45U4XG35ESVWRDECWO5XLDGYADO6DPR3L7KIDVUMML", + "predicate": { + "and": [ + { + "or": [ + { + "relBefore": "12" + }, + { + "absBefore": "2020-08-26T11:15:39Z", + "absBeforeEpoch": "1598440539" + } + ] + }, + { + "not": { "unconditional": true } + } + ] + } + } + ], + "last_modified_ledger": 28411995, + "last_modified_time": "2020-02-26T19:29:16Z" +} diff --git a/test/support/fixtures/horizon/claimable_balances.json b/test/support/fixtures/horizon/claimable_balances.json new file mode 100644 index 00000000..af0f9104 --- /dev/null +++ b/test/support/fixtures/horizon/claimable_balances.json @@ -0,0 +1,125 @@ +{ + "_links": { + "self": { + "href": "https://horizon-testnet.stellar.org/claimable_balances/?cursor=&limit=3&order=desc" + }, + "next": { + "href": "https://horizon-testnet.stellar.org/claimable_balances/?cursor=1268715-0000000032d73407ea08b122407e32833cc6f9f6dc72ee0dd01e482fa06fdfadc32d01ea&limit=3&order=desc" + }, + "prev": { + "href": "https://horizon-testnet.stellar.org/claimable_balances/?cursor=1269595-00000000d6575278b432b3ee4e07cc0b17721fafc32c3b5c1403be68d8309268e91e585f&limit=3&order=asc" + } + }, + "_embedded": { + "records": [ + { + "_links": { + "self": { + "href": "https://horizon-testnet.stellar.org/claimable_balances/00000000d6575278b432b3ee4e07cc0b17721fafc32c3b5c1403be68d8309268e91e585f" + }, + "transactions": { + "href": "https://horizon-testnet.stellar.org/claimable_balances/00000000d6575278b432b3ee4e07cc0b17721fafc32c3b5c1403be68d8309268e91e585f/transactions{?cursor,limit,order}", + "templated": true + }, + "operations": { + "href": "https://horizon-testnet.stellar.org/claimable_balances/00000000d6575278b432b3ee4e07cc0b17721fafc32c3b5c1403be68d8309268e91e585f/operations{?cursor,limit,order}", + "templated": true + } + }, + "id": "00000000d6575278b432b3ee4e07cc0b17721fafc32c3b5c1403be68d8309268e91e585f", + "asset": "BTCN:GDGHQTCJ3SGFBWBHJGVRUFBRLZGS5VS52HEDH4GVPX5GZRJQAOW7ZM37", + "amount": "82.4190840", + "sponsor": "GBXISGJYYKE6RUO6L6KXBUJ7FJU4CWF647FLQAT3TZ2Q47IZHXFNYKYH", + "last_modified_ledger": 1269595, + "last_modified_time": "2022-03-02T14:39:20Z", + "claimants": [ + { + "destination": "GATBAGTTQLQ4VKZMXLINLS6M4F2PEXMAZCK5ZE5ES4B6A2DXNGCFRX54", + "predicate": { + "abs_before": "2001-09-09T01:46:40Z", + "abs_before_epoch": "1000000000" + } + } + ], + "flags": { + "clawback_enabled": true + }, + "paging_token": "1269595-00000000d6575278b432b3ee4e07cc0b17721fafc32c3b5c1403be68d8309268e91e585f" + }, + { + "_links": { + "self": { + "href": "https://horizon-testnet.stellar.org/claimable_balances/00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072" + }, + "transactions": { + "href": "https://horizon-testnet.stellar.org/claimable_balances/00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072/transactions{?cursor,limit,order}", + "templated": true + }, + "operations": { + "href": "https://horizon-testnet.stellar.org/claimable_balances/00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072/operations{?cursor,limit,order}", + "templated": true + } + }, + "id": "00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072", + "asset": "y00XLM:GDGHQTCJ3SGFBWBHJGVRUFBRLZGS5VS52HEDH4GVPX5GZRJQAOW7ZM37", + "amount": "912.5569285", + "sponsor": "GBXISGJYYKE6RUO6L6KXBUJ7FJU4CWF647FLQAT3TZ2Q47IZHXFNYKYH", + "last_modified_ledger": 1269432, + "last_modified_time": "2022-03-02T14:25:09Z", + "claimants": [ + { + "destination": "GATBAGTTQLQ4VKZMXLINLS6M4F2PEXMAZCK5ZE5ES4B6A2DXNGCFRX54", + "predicate": { + "not": { + "unconditional": true + } + } + } + ], + "flags": { + "clawback_enabled": true + }, + "paging_token": "1269432-00000000929b20b72e5890ab51c24f1cc46fa01c4f318d8d33367d24dd614cfdf5491072" + }, + { + "_links": { + "self": { + "href": "https://horizon-testnet.stellar.org/claimable_balances/0000000032d73407ea08b122407e32833cc6f9f6dc72ee0dd01e482fa06fdfadc32d01ea" + }, + "transactions": { + "href": "https://horizon-testnet.stellar.org/claimable_balances/0000000032d73407ea08b122407e32833cc6f9f6dc72ee0dd01e482fa06fdfadc32d01ea/transactions{?cursor,limit,order}", + "templated": true + }, + "operations": { + "href": "https://horizon-testnet.stellar.org/claimable_balances/0000000032d73407ea08b122407e32833cc6f9f6dc72ee0dd01e482fa06fdfadc32d01ea/operations{?cursor,limit,order}", + "templated": true + } + }, + "id": "0000000032d73407ea08b122407e32833cc6f9f6dc72ee0dd01e482fa06fdfadc32d01ea", + "asset": "OxCBQksBk:GB3A3CK64CZDZ63FZTVI3OKK7ZCD75YQCPA2EKHPDFD6ABKEQ3ESTWVV", + "amount": "0.0000001", + "sponsor": "GB3A3CK64CZDZ63FZTVI3OKK7ZCD75YQCPA2EKHPDFD6ABKEQ3ESTWVV", + "last_modified_ledger": 1268715, + "last_modified_time": "2022-03-02T13:22:04Z", + "claimants": [ + { + "destination": "GASY6U7YOOQSBSWORHZ3QI4N46XHZUKEAKOP7XEZZ5XOFP5HMNV5CFZ6", + "predicate": { + "unconditional": true + } + }, + { + "destination": "GB3A3CK64CZDZ63FZTVI3OKK7ZCD75YQCPA2EKHPDFD6ABKEQ3ESTWVV", + "predicate": { + "unconditional": true + } + } + ], + "flags": { + "clawback_enabled": false + }, + "paging_token": "1268715-0000000032d73407ea08b122407e32833cc6f9f6dc72ee0dd01e482fa06fdfadc32d01ea" + } + ] + } +} diff --git a/test/support/fixtures/horizon/data.json b/test/support/fixtures/horizon/data.json new file mode 100644 index 00000000..f30f843b --- /dev/null +++ b/test/support/fixtures/horizon/data.json @@ -0,0 +1,3 @@ +{ + "value": "QkdFR0ZFVEVHRUhIRUVI" +} diff --git a/test/support/fixtures/horizon/effect.json b/test/support/fixtures/horizon/effect.json new file mode 100644 index 00000000..28921951 --- /dev/null +++ b/test/support/fixtures/horizon/effect.json @@ -0,0 +1,23 @@ +{ + "_links": { + "operation": { + "href": "https://horizon-testnet.stellar.org/operations/549755817992" + }, + "precedes": { + "href": "https://horizon-testnet.stellar.org/effects?order=asc&cursor=549755817992-1" + }, + "succeeds": { + "href": "https://horizon-testnet.stellar.org/effects?order=desc&cursor=549755817992-1" + } + }, + "account": "GCNP7JE6KR5CKHMVVFTZJUSP7ALAXWP62SK6IMIY4IF3JCHEZKBJKDZF", + "asset_code": "TEST", + "asset_issuer": "GDNFUWF2EO4OWXYLI4TDEH4DXUCN6PB24R6XQW4VATORK6WGMHGRXJVB", + "asset_type": "credit_alphanum4", + "created_at": "2021-12-15T09:38:34Z", + "id": "0000000549755817992-0000000001", + "limit": "922337203685.4775807", + "paging_token": "549755817992-1", + "type": "trustline_created", + "type_i": 20 +} diff --git a/test/support/fixtures/horizon/effects.json b/test/support/fixtures/horizon/effects.json new file mode 100644 index 00000000..a609244a --- /dev/null +++ b/test/support/fixtures/horizon/effects.json @@ -0,0 +1,80 @@ +{ + "_links": { + "self": { + "href": "https://horizon.stellar.org/effects?cursor=\u0026limit=3\u0026order=asc" + }, + "next": { + "href": "https://horizon.stellar.org/effects?cursor=12884905985-3\u0026limit=3\u0026order=asc" + }, + "prev": { + "href": "https://horizon.stellar.org/effects?cursor=12884905985-1\u0026limit=3\u0026order=desc" + } + }, + "_embedded": { + "records": [ + { + "_links": { + "operation": { + "href": "https://horizon.stellar.org/operations/12884905985" + }, + "succeeds": { + "href": "https://horizon.stellar.org/effects?order=desc\u0026cursor=12884905985-1" + }, + "precedes": { + "href": "https://horizon.stellar.org/effects?order=asc\u0026cursor=12884905985-1" + } + }, + "id": "0000000012884905985-0000000001", + "paging_token": "12884905985-1", + "account": "GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB", + "type": "account_created", + "type_i": 0, + "created_at": "2015-09-30T17:15:54Z", + "starting_balance": "20.0000000" + }, + { + "_links": { + "operation": { + "href": "https://horizon.stellar.org/operations/12884905985" + }, + "succeeds": { + "href": "https://horizon.stellar.org/effects?order=desc\u0026cursor=12884905985-2" + }, + "precedes": { + "href": "https://horizon.stellar.org/effects?order=asc\u0026cursor=12884905985-2" + } + }, + "id": "0000000012884905985-0000000002", + "paging_token": "12884905985-2", + "account": "GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN7", + "type": "account_debited", + "type_i": 3, + "created_at": "2015-09-30T17:16:54Z", + "asset_type": "native", + "amount": "20.0000000" + }, + { + "_links": { + "operation": { + "href": "https://horizon.stellar.org/operations/12884905985" + }, + "succeeds": { + "href": "https://horizon.stellar.org/effects?order=desc\u0026cursor=12884905985-3" + }, + "precedes": { + "href": "https://horizon.stellar.org/effects?order=asc\u0026cursor=12884905985-3" + } + }, + "id": "0000000012884905985-0000000003", + "paging_token": "12884905985-3", + "account": "GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB", + "type": "signer_created", + "type_i": 10, + "created_at": "2015-09-30T17:17:54Z", + "weight": 1, + "public_key": "GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB", + "key": "" + } + ] + } +} diff --git a/test/support/fixtures/horizon/ledger.json b/test/support/fixtures/horizon/ledger.json new file mode 100644 index 00000000..62b06b9f --- /dev/null +++ b/test/support/fixtures/horizon/ledger.json @@ -0,0 +1,40 @@ +{ + "_links": { + "self": { + "href": "https://horizon.stellar.org/ledgers/10" + }, + "transactions": { + "href": "https://horizon.stellar.org/ledgers/10/transactions{?cursor,limit,order}", + "templated": true + }, + "operations": { + "href": "https://horizon.stellar.org/ledgers/10/operations{?cursor,limit,order}", + "templated": true + }, + "payments": { + "href": "https://horizon.stellar.org/ledgers/10/payments{?cursor,limit,order}", + "templated": true + }, + "effects": { + "href": "https://horizon.stellar.org/ledgers/10/effects{?cursor,limit,order}", + "templated": true + } + }, + "id": "31c33314a9d6f1d1e07040029f56403fc410829a45dfe9cc662c6b2dce8f53b3", + "paging_token": "42949672960", + "hash": "31c33314a9d6f1d1e07040029f56403fc410829a45dfe9cc662c6b2dce8f53b3", + "prev_hash": "da0516f96a9d3a00e8f60c60307cad062021cbad3c99f72ad8e277803dece482", + "sequence": 10, + "successful_transaction_count": 0, + "failed_transaction_count": 0, + "operation_count": 0, + "tx_set_operation_count": 0, + "closed_at": "2015-09-30T17:16:29Z", + "total_coins": "100000000000.0000000", + "fee_pool": "0.0000300", + "base_fee_in_stroops": 100, + "base_reserve_in_stroops": 100000000, + "max_tx_set_size": 500, + "protocol_version": 1, + "header_xdr": "AAAAAdofgbAA===" +} diff --git a/test/support/fixtures/horizon/ledgers.json b/test/support/fixtures/horizon/ledgers.json new file mode 100644 index 00000000..847af91c --- /dev/null +++ b/test/support/fixtures/horizon/ledgers.json @@ -0,0 +1,137 @@ +{ + "_links": { + "self": { + "href": "https://horizon-testnet.stellar.org/ledgers?cursor=&limit=3&order=desc" + }, + "next": { + "href": "https://horizon-testnet.stellar.org/ledgers?cursor=5404056700846080&limit=3&order=desc" + }, + "prev": { + "href": "https://horizon-testnet.stellar.org/ledgers?cursor=5404065290780672&limit=3&order=asc" + } + }, + "_embedded": { + "records": [ + { + "_links": { + "self": { + "href": "https://horizon-testnet.stellar.org/ledgers/1258232" + }, + "transactions": { + "href": "https://horizon-testnet.stellar.org/ledgers/1258232/transactions{?cursor,limit,order}", + "templated": true + }, + "operations": { + "href": "https://horizon-testnet.stellar.org/ledgers/1258232/operations{?cursor,limit,order}", + "templated": true + }, + "payments": { + "href": "https://horizon-testnet.stellar.org/ledgers/1258232/payments{?cursor,limit,order}", + "templated": true + }, + "effects": { + "href": "https://horizon-testnet.stellar.org/ledgers/1258232/effects{?cursor,limit,order}", + "templated": true + } + }, + "id": "29b15176e88b828c219ab75760a2e3abe052c16f1c34d069320c7591a4bd7a77", + "paging_token": "5404065290780672", + "hash": "29b15176e88b828c219ab75760a2e3abe052c16f1c34d069320c7591a4bd7a77", + "prev_hash": "ea3f21f0748f52fe700317971c46bca873b8cf6eb92296ea23d5a9416c455578", + "sequence": 1258232, + "successful_transaction_count": 0, + "failed_transaction_count": 0, + "operation_count": 0, + "tx_set_operation_count": 0, + "closed_at": "2022-03-01T22:04:15Z", + "total_coins": "100000000000.0000000", + "fee_pool": "11917.7311764", + "base_fee_in_stroops": 100, + "base_reserve_in_stroops": 5000000, + "max_tx_set_size": 105, + "protocol_version": 18, + "header_xdr": "AAAAEuo/IfB0j1L+cAMXlxxGvKhzuM9uuSKW6iPVqUFsRVV4LtpYA6SsbyGAplpE11+ZVnH8lTSnpNuFXEx9kCyjCxkAAAAAYh6YXwAAAAAAAAABAAAAAKgkzRi8nXUGTSmaW1uspDvDqi8yaTgVPYwvm7XLbfAzAAAAQHVw/G0xKVJNmoPu/M/kwX0RjXn5CcEHxtxUGZ6gKqyJKGBn/QFQ9Rji/OL7HMdPMx7Hfc6pMRr35fKllf+0FgzfP2GYBKkv20BXGS3EPddI6neK3FK8SYzoBSTAFLgRGY0vtF+XWE0EpiRLAVUoxXyqYsJqFGuUOWBY04nqRWRSABMy+A3gtrOnZAAAAAAAG7+FdhQAAAAAAAAAAAAGo5sAAABkAExLQAAAAGm1Vnv9HPcxSGdJboV1//Bb3XA3w1QpCBCFoBtKTLDf+6ymX4P4soLbVUYGIGHWuguVq9IP/H7Und6xALuV89nicJGgsPHId3xJqCG5OS+3UXluVOz6883qlt7vWuK5KGmWhqPhp8bOFHNEFz7xobjBm7eYAO1drSR9L4pnioti2QAAAAA=" + }, + { + "_links": { + "self": { + "href": "https://horizon-testnet.stellar.org/ledgers/1258231" + }, + "transactions": { + "href": "https://horizon-testnet.stellar.org/ledgers/1258231/transactions{?cursor,limit,order}", + "templated": true + }, + "operations": { + "href": "https://horizon-testnet.stellar.org/ledgers/1258231/operations{?cursor,limit,order}", + "templated": true + }, + "payments": { + "href": "https://horizon-testnet.stellar.org/ledgers/1258231/payments{?cursor,limit,order}", + "templated": true + }, + "effects": { + "href": "https://horizon-testnet.stellar.org/ledgers/1258231/effects{?cursor,limit,order}", + "templated": true + } + }, + "id": "ea3f21f0748f52fe700317971c46bca873b8cf6eb92296ea23d5a9416c455578", + "paging_token": "5404060995813376", + "hash": "ea3f21f0748f52fe700317971c46bca873b8cf6eb92296ea23d5a9416c455578", + "prev_hash": "b05802d4c9f132d6ec0bb60defd0a0e5b41c1ccdcc52849fbad0725d83a54b77", + "sequence": 1258231, + "successful_transaction_count": 2, + "failed_transaction_count": 0, + "operation_count": 4, + "tx_set_operation_count": 4, + "closed_at": "2022-03-01T22:04:10Z", + "total_coins": "100000000000.0000000", + "fee_pool": "11917.7311764", + "base_fee_in_stroops": 100, + "base_reserve_in_stroops": 5000000, + "max_tx_set_size": 105, + "protocol_version": 18, + "header_xdr": "AAAAErBYAtTJ8TLW7Au2De/QoOW0HBzNzFKEn7rQcl2DpUt3YPY0JvoqzPTsyg2ieat9hrkeZYMV8DfH7c5R58/jDbkAAAAAYh6YWgAAAAAAAAABAAAAANVyadliUPdJbQeb4ug1Ejbv/+jTnC4Gv6uxQh8X/GccAAAAQJu5FLu87/nC+7CEks8/oUwTN31dffILHgMmq7y0Qx33bEEAYAAPyxNKxSce1a0qZK1ARHt6iXisoMDRspt+2QZ92CZPdjjwe9OpPvVDEad5tfz/XuWxvlU5q/5bHrnWM9NFDKP5W2O+Vi/TXHE7iofT8Ka7dfnzy6Dv45piDDOpABMy9w3gtrOnZAAAAAAAG7+FdhQAAAAAAAAAAAAGo5sAAABkAExLQAAAAGm1Vnv9HPcxSGdJboV1//Bb3XA3w1QpCBCFoBtKTLDf+6ymX4P4soLbVUYGIGHWuguVq9IP/H7Und6xALuV89nicJGgsPHId3xJqCG5OS+3UXluVOz6883qlt7vWuK5KGmWhqPhp8bOFHNEFz7xobjBm7eYAO1drSR9L4pnioti2QAAAAA=" + }, + { + "_links": { + "self": { + "href": "https://horizon-testnet.stellar.org/ledgers/1258230" + }, + "transactions": { + "href": "https://horizon-testnet.stellar.org/ledgers/1258230/transactions{?cursor,limit,order}", + "templated": true + }, + "operations": { + "href": "https://horizon-testnet.stellar.org/ledgers/1258230/operations{?cursor,limit,order}", + "templated": true + }, + "payments": { + "href": "https://horizon-testnet.stellar.org/ledgers/1258230/payments{?cursor,limit,order}", + "templated": true + }, + "effects": { + "href": "https://horizon-testnet.stellar.org/ledgers/1258230/effects{?cursor,limit,order}", + "templated": true + } + }, + "id": "b05802d4c9f132d6ec0bb60defd0a0e5b41c1ccdcc52849fbad0725d83a54b77", + "paging_token": "5404056700846080", + "hash": "b05802d4c9f132d6ec0bb60defd0a0e5b41c1ccdcc52849fbad0725d83a54b77", + "prev_hash": "d9da7c643cbf60b3023c296b790bfedcb51ea1a08a179163e28fb3655cdfc3bf", + "sequence": 1258230, + "successful_transaction_count": 0, + "failed_transaction_count": 0, + "operation_count": 0, + "tx_set_operation_count": 0, + "closed_at": "2022-03-01T22:04:04Z", + "total_coins": "100000000000.0000000", + "fee_pool": "11917.7311364", + "base_fee_in_stroops": 100, + "base_reserve_in_stroops": 5000000, + "max_tx_set_size": 105, + "protocol_version": 18, + "header_xdr": "AAAAEtnafGQ8v2CzAjwpa3kL/ty1HqGgiheRY+KPs2Vc38O/FIRrvEKxLN+mp3daL/sdBoY8BC7LipMFxcgIxX9TQ0gAAAAAYh6YVAAAAAAAAAABAAAAANVyadliUPdJbQeb4ug1Ejbv/+jTnC4Gv6uxQh8X/GccAAAAQG5erVoCE24OdmKtkxMbA6COiZq5LxNCJ7qgbuiwpu969lihajBpAcnCpY+wvE+h0gOgWHFJ8Gb10+v34rUhiwffP2GYBKkv20BXGS3EPddI6neK3FK8SYzoBSTAFLgRGTN4nkK/bW4rcqb0/Pi256tw8WckrMJK9jcUZ91/3nw7ABMy9g3gtrOnZAAAAAAAG7+FdIQAAAAAAAAAAAAGo5sAAABkAExLQAAAAGm1Vnv9HPcxSGdJboV1//Bb3XA3w1QpCBCFoBtKTLDf+6ymX4P4soLbVUYGIGHWuguVq9IP/H7Und6xALuV89nicJGgsPHId3xJqCG5OS+3UXluVOz6883qlt7vWuK5KGmWhqPhp8bOFHNEFz7xobjBm7eYAO1drSR9L4pnioti2QAAAAA=" + } + ] + } +} diff --git a/test/support/fixtures/horizon/liquidity_pool.json b/test/support/fixtures/horizon/liquidity_pool.json new file mode 100644 index 00000000..02e5685f --- /dev/null +++ b/test/support/fixtures/horizon/liquidity_pool.json @@ -0,0 +1,33 @@ +{ + "_links": { + "self": { + "href": "https://horizon.stellar.org/liquidity_pools/0052d44a6c260660115f07c5a78631770e62aae3ffde96731c44b1509e9c8434" + }, + "transactions": { + "href": "https://horizon.stellar.org/liquidity_pools/0052d44a6c260660115f07c5a78631770e62aae3ffde96731c44b1509e9c8434/transactions{?cursor,limit,order}", + "templated": true + }, + "operations": { + "href": "https://horizon.stellar.org/liquidity_pools/0052d44a6c260660115f07c5a78631770e62aae3ffde96731c44b1509e9c8434/operations{?cursor,limit,order}", + "templated": true + } + }, + "id": "0052d44a6c260660115f07c5a78631770e62aae3ffde96731c44b1509e9c8434", + "paging_token": "0052d44a6c260660115f07c5a78631770e62aae3ffde96731c44b1509e9c8434", + "fee_bp": 30, + "type": "constant_product", + "total_trustlines": "1", + "total_shares": "500000000.0000000", + "reserves": [ + { + "asset": "XAU:GB3OE4IBQTYQFZZS5RXQHE4IPQL7ONOFOSAIS2NFRNMJKZEAI3AAUT2A", + "amount": "522256061.0743940" + }, + { + "asset": "ERRES:GA6ZAQGLDUEODDUUD3UD6PUFJYABWQA26SG5RK6E6CZ6OMD6AZKK5QNF", + "amount": "533666459.4045717" + } + ], + "last_modified_ledger": 39767560, + "last_modified_time": "2022-02-24T11:48:09Z" +} diff --git a/test/support/fixtures/horizon/liquidity_pools.json b/test/support/fixtures/horizon/liquidity_pools.json new file mode 100644 index 00000000..0580dd3f --- /dev/null +++ b/test/support/fixtures/horizon/liquidity_pools.json @@ -0,0 +1,116 @@ +{ + "_links": { + "self": { + "href": "https://horizon-testnet.stellar.org/liquidity_pools?cursor=&limit=3&order=desc" + }, + "next": { + "href": "https://horizon-testnet.stellar.org/liquidity_pools?cursor=fd498c395920d57156e86b2d52acd2bbbfc164836a204c73d3b64bec33874d38&limit=3&order=desc" + }, + "prev": { + "href": "https://horizon-testnet.stellar.org/liquidity_pools?cursor=fe6490f5e64f25e62df49a6a4b5542b8e5411e84c6d3158613abb2271824deb9&limit=3&order=asc" + } + }, + "_embedded": { + "records": [ + { + "_links": { + "self": { + "href": "https://horizon-testnet.stellar.org/liquidity_pools/fe6490f5e64f25e62df49a6a4b5542b8e5411e84c6d3158613abb2271824deb9" + }, + "transactions": { + "href": "https://horizon-testnet.stellar.org/liquidity_pools/fe6490f5e64f25e62df49a6a4b5542b8e5411e84c6d3158613abb2271824deb9/transactions{?cursor,limit,order}", + "templated": true + }, + "operations": { + "href": "https://horizon-testnet.stellar.org/liquidity_pools/fe6490f5e64f25e62df49a6a4b5542b8e5411e84c6d3158613abb2271824deb9/operations{?cursor,limit,order}", + "templated": true + } + }, + "id": "fe6490f5e64f25e62df49a6a4b5542b8e5411e84c6d3158613abb2271824deb9", + "paging_token": "fe6490f5e64f25e62df49a6a4b5542b8e5411e84c6d3158613abb2271824deb9", + "fee_bp": 30, + "type": "constant_product", + "total_trustlines": "1", + "total_shares": "1.9800000", + "reserves": [ + { + "asset": "BTCN:GDGUZCQFXCLZ775VZH3YGWRUIBKD6XKDJEW3E7CE5COT4EO7A3W22YUX", + "amount": "228.6851160" + }, + { + "asset": "ETH:GATPY6X6OYTXKNRKVP6LEMUUQKFDUW5P7HL4XI3KWRCY52RAWYJ5FLMC", + "amount": "0.0174209" + } + ], + "last_modified_ledger": 1137275, + "last_modified_time": "2022-02-22T13:30:09Z" + }, + { + "_links": { + "self": { + "href": "https://horizon-testnet.stellar.org/liquidity_pools/fdccc907a78a11e592c122144f702fdb093160c510c0f4327994d9be5d951092" + }, + "transactions": { + "href": "https://horizon-testnet.stellar.org/liquidity_pools/fdccc907a78a11e592c122144f702fdb093160c510c0f4327994d9be5d951092/transactions{?cursor,limit,order}", + "templated": true + }, + "operations": { + "href": "https://horizon-testnet.stellar.org/liquidity_pools/fdccc907a78a11e592c122144f702fdb093160c510c0f4327994d9be5d951092/operations{?cursor,limit,order}", + "templated": true + } + }, + "id": "fdccc907a78a11e592c122144f702fdb093160c510c0f4327994d9be5d951092", + "paging_token": "fdccc907a78a11e592c122144f702fdb093160c510c0f4327994d9be5d951092", + "fee_bp": 30, + "type": "constant_product", + "total_trustlines": "1", + "total_shares": "100000.0000000", + "reserves": [ + { + "asset": "DOLLAR:GCO7B6KEDWOBM5X642ZOTPYTYTTBZIGVGUED4ZSBILJOAU4XB7ISJBFF", + "amount": "100005.0152960" + }, + { + "asset": "JasonHartman:GA5P6KUCQAKEZCVXBCTYQH2ANNPZSYNAGIEYEEWDLGLBOHIO73GVTRYO", + "amount": "99995.0000000" + } + ], + "last_modified_ledger": 384280, + "last_modified_time": "2022-01-07T18:19:08Z" + }, + { + "_links": { + "self": { + "href": "https://horizon-testnet.stellar.org/liquidity_pools/fd498c395920d57156e86b2d52acd2bbbfc164836a204c73d3b64bec33874d38" + }, + "transactions": { + "href": "https://horizon-testnet.stellar.org/liquidity_pools/fd498c395920d57156e86b2d52acd2bbbfc164836a204c73d3b64bec33874d38/transactions{?cursor,limit,order}", + "templated": true + }, + "operations": { + "href": "https://horizon-testnet.stellar.org/liquidity_pools/fd498c395920d57156e86b2d52acd2bbbfc164836a204c73d3b64bec33874d38/operations{?cursor,limit,order}", + "templated": true + } + }, + "id": "fd498c395920d57156e86b2d52acd2bbbfc164836a204c73d3b64bec33874d38", + "paging_token": "fd498c395920d57156e86b2d52acd2bbbfc164836a204c73d3b64bec33874d38", + "fee_bp": 30, + "type": "constant_product", + "total_trustlines": "4", + "total_shares": "180.8936510", + "reserves": [ + { + "asset": "native", + "amount": "130.8900524" + }, + { + "asset": "EURC:GDBDEI3NV72XSORX7DNYMGRRNXAXF62RPTGGVEXM2RLXIUIUU5DNZWWH", + "amount": "249.9999998" + } + ], + "last_modified_ledger": 973360, + "last_modified_time": "2022-02-12T14:16:51Z" + } + ] + } +} diff --git a/test/support/fixtures/horizon/offer.json b/test/support/fixtures/horizon/offer.json new file mode 100644 index 00000000..eab9524f --- /dev/null +++ b/test/support/fixtures/horizon/offer.json @@ -0,0 +1,29 @@ +{ + "_links": { + "self": { + "href": "https://horizon.stellar.org/offers/165561423" + }, + "offer_maker": { + "href": "https://horizon.stellar.org/accounts/GCK4WSNF3F6ZNCMK6BU77ZCZ3NMF3JGU2U3ZAPKXYBKYYCJA72FDBY7K" + } + }, + "id": 165561423, + "paging_token": "165561423", + "seller": "GCK4WSNF3F6ZNCMK6BU77ZCZ3NMF3JGU2U3ZAPKXYBKYYCJA72FDBY7K", + "selling": { + "asset_type": "credit_alphanum4", + "asset_code": "NGNT", + "asset_issuer": "GAWODAROMJ33V5YDFY3NPYTHVYQG7MJXVJ2ND3AOGIHYRWINES6ACCPD" + }, + "buying": { + "asset_type": "native" + }, + "amount": "18421.4486092", + "price_r": { + "n": 45112058, + "d": 941460545 + }, + "price": "0.0479171", + "last_modified_ledger": 28411995, + "last_modified_time": "2020-02-26T19:29:16Z" +} diff --git a/test/support/fixtures/horizon/offers.json b/test/support/fixtures/horizon/offers.json new file mode 100644 index 00000000..3169cc27 --- /dev/null +++ b/test/support/fixtures/horizon/offers.json @@ -0,0 +1,75 @@ +{ + "_links": { + "self": { + "href": "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/offers?cursor=\u0026limit=10\u0026order=asc" + }, + "next": { + "href": "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/offers?cursor=164943216\u0026limit=10\u0026order=asc" + }, + "prev": { + "href": "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/offers?cursor=164555927\u0026limit=10\u0026order=desc" + } + }, + "_embedded": { + "records": [ + { + "_links": { + "self": { + "href": "https://horizon.stellar.org/offers/164555927" + }, + "offer_maker": { + "href": "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD" + } + }, + "id": 164555927, + "paging_token": "164555927", + "seller": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "selling": { + "asset_type": "native" + }, + "buying": { + "asset_type": "credit_alphanum4", + "asset_code": "BTCN", + "asset_issuer": "GD5J6HLF5666X4AZLTFTXLY46J5SW7EXRKBLEYPJP33S33MXZGV6CWFN" + }, + "amount": "214.9999939", + "price_r": { + "n": 10000000, + "d": 86000001 + }, + "price": "0.1162791", + "last_modified_ledger": 28383147, + "last_modified_time": "2020-02-24T22:58:38Z" + }, + { + "_links": { + "self": { + "href": "https://horizon.stellar.org/offers/164943216" + }, + "offer_maker": { + "href": "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD" + } + }, + "id": 164943216, + "paging_token": "164943216", + "seller": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "selling": { + "asset_type": "credit_alphanum4", + "asset_code": "BTCN", + "asset_issuer": "GD5J6HLF5666X4AZLTFTXLY46J5SW7EXRKBLEYPJP33S33MXZGV6CWFN" + }, + "buying": { + "asset_type": "native" + }, + "amount": "24.9999990", + "price_r": { + "n": 32224991, + "d": 2500000 + }, + "price": "12.8899964", + "last_modified_ledger": 28394149, + "last_modified_time": "2020-02-25T15:49:57Z" + } + ] + } +} diff --git a/test/support/fixtures/horizon/operation.json b/test/support/fixtures/horizon/operation.json new file mode 100644 index 00000000..7c3902c2 --- /dev/null +++ b/test/support/fixtures/horizon/operation.json @@ -0,0 +1,48 @@ +{ + "_links": { + "self": { + "href": "https://horizon.stellar.org/operations/124624072438579201" + }, + "transaction": { + "href": "https://horizon.stellar.org/transactions/2b863994825fe85b80bfdff433b348d5ce80b23cd9ee2a56dcd6ee1abd52c9f8" + }, + "effects": { + "href": "https://horizon.stellar.org/operations/124624072438579201/effects" + }, + "succeeds": { + "href": "https://horizon.stellar.org/effects?order=desc\u0026cursor=124624072438579201" + }, + "precedes": { + "href": "https://horizon.stellar.org/effects?order=asc\u0026cursor=124624072438579201" + } + }, + "id": "124624072438579201", + "paging_token": "124624072438579201", + "transaction_successful": true, + "source_account": "GBZH7S5NC57XNHKHJ75C5DGMI3SP6ZFJLIKW74K6OSMA5E5DFMYBDD2Z", + "type": "path_payment_strict_send", + "type_i": 13, + "created_at": "2020-04-04T13:47:50Z", + "transaction_hash": "2b863994825fe85b80bfdff433b348d5ce80b23cd9ee2a56dcd6ee1abd52c9f8", + "asset_type": "credit_alphanum4", + "asset_code": "BRL", + "asset_issuer": "GDVKY2GU2DRXWTBEYJJWSFXIGBZV6AZNBVVSUHEPZI54LIS6BA7DVVSP", + "from": "GBZH7S5NC57XNHKHJ75C5DGMI3SP6ZFJLIKW74K6OSMA5E5DFMYBDD2Z", + "to": "GBZH7S5NC57XNHKHJ75C5DGMI3SP6ZFJLIKW74K6OSMA5E5DFMYBDD2Z", + "amount": "26.5544244", + "path": [ + { + "asset_type": "credit_alphanum4", + "asset_code": "EURT", + "asset_issuer": "GAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S" + }, + { + "asset_type": "native" + } + ], + "source_amount": "5.0000000", + "destination_min": "26.5544244", + "source_asset_type": "credit_alphanum4", + "source_asset_code": "USD", + "source_asset_issuer": "GDUKMGUGDZQK6YHYA5Z6AY2G4XDSZPSZ3SW5UN3ARVMO6QSRDWP5YLEX" +} diff --git a/test/support/fixtures/horizon/operations.json b/test/support/fixtures/horizon/operations.json new file mode 100644 index 00000000..317023a1 --- /dev/null +++ b/test/support/fixtures/horizon/operations.json @@ -0,0 +1,106 @@ +{ + "_links": { + "self": { + "href": "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31/operations?cursor=&limit=3&order=desc" + }, + "next": { + "href": "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31/operations?cursor=12884905985&limit=3&order=desc" + }, + "prev": { + "href": "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31/operations?cursor=12884905987&limit=3&order=asc" + } + }, + "_embedded": { + "records": [ + { + "_links": { + "self": { + "href": "https://horizon.stellar.org/operations/12884905987" + }, + "transaction": { + "href": "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31" + }, + "effects": { + "href": "https://horizon.stellar.org/operations/12884905987/effects" + }, + "succeeds": { + "href": "https://horizon.stellar.org/effects?order=desc&cursor=12884905987" + }, + "precedes": { + "href": "https://horizon.stellar.org/effects?order=asc&cursor=12884905987" + } + }, + "id": "12884905987", + "paging_token": "12884905987", + "transaction_successful": true, + "source_account": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "type": "set_options", + "type_i": 5, + "created_at": "2015-09-30T17:15:54Z", + "transaction_hash": "132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31", + "master_key_weight": 0 + }, + { + "_links": { + "self": { + "href": "https://horizon.stellar.org/operations/12884905986" + }, + "transaction": { + "href": "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31" + }, + "effects": { + "href": "https://horizon.stellar.org/operations/12884905986/effects" + }, + "succeeds": { + "href": "https://horizon.stellar.org/effects?order=desc&cursor=12884905986" + }, + "precedes": { + "href": "https://horizon.stellar.org/effects?order=asc&cursor=12884905986" + } + }, + "id": "12884905986", + "paging_token": "12884905986", + "transaction_successful": true, + "source_account": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "type": "payment", + "type_i": 1, + "created_at": "2015-09-30T17:15:54Z", + "transaction_hash": "132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31", + "asset_type": "native", + "from": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "to": "GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB", + "amount": "99999999959.9999700" + }, + { + "_links": { + "self": { + "href": "https://horizon.stellar.org/operations/12884905985" + }, + "transaction": { + "href": "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31" + }, + "effects": { + "href": "https://horizon.stellar.org/operations/12884905985/effects" + }, + "succeeds": { + "href": "https://horizon.stellar.org/effects?order=desc&cursor=12884905985" + }, + "precedes": { + "href": "https://horizon.stellar.org/effects?order=asc&cursor=12884905985" + } + }, + "id": "12884905985", + "paging_token": "12884905985", + "transaction_successful": true, + "source_account": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "type": "create_account", + "type_i": 0, + "created_at": "2015-09-30T17:15:54Z", + "transaction_hash": "132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31", + "starting_balance": "20.0000000", + "funder": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "account": "GALPCCZN4YXA3YMJHKL6CVIECKPLJJCTVMSNYWBTKJW4K5HQLYLDMZTB" + } + ] + } +} diff --git a/test/support/fixtures/horizon/payments.json b/test/support/fixtures/horizon/payments.json new file mode 100644 index 00000000..92830c5b --- /dev/null +++ b/test/support/fixtures/horizon/payments.json @@ -0,0 +1,110 @@ +{ + "_links": { + "self": { + "href": "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/payments?cursor=&limit=3&order=desc" + }, + "next": { + "href": "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/payments?cursor=163385576757178369&limit=3&order=desc" + }, + "prev": { + "href": "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/payments?cursor=165248737866100737&limit=3&order=asc" + } + }, + "_embedded": { + "records": [ + { + "_links": { + "self": { + "href": "https://horizon.stellar.org/operations/165248737866100737" + }, + "transaction": { + "href": "https://horizon.stellar.org/transactions/3381ba3ccb6415dc36f0ed47bcbbfe918dca1da797d89701c67585d5516512f7" + }, + "effects": { + "href": "https://horizon.stellar.org/operations/165248737866100737/effects" + }, + "succeeds": { + "href": "https://horizon.stellar.org/effects?order=desc&cursor=165248737866100737" + }, + "precedes": { + "href": "https://horizon.stellar.org/effects?order=asc&cursor=165248737866100737" + } + }, + "id": "165248737866100737", + "paging_token": "165248737866100737", + "transaction_successful": true, + "source_account": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "type": "payment", + "type_i": 1, + "created_at": "2021-11-27T13:15:49Z", + "transaction_hash": "3381ba3ccb6415dc36f0ed47bcbbfe918dca1da797d89701c67585d5516512f7", + "asset_type": "native", + "from": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "to": "GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN7", + "amount": "0.0000001" + }, + { + "_links": { + "self": { + "href": "https://horizon.stellar.org/operations/164316377777254401" + }, + "transaction": { + "href": "https://horizon.stellar.org/transactions/3e9d7c75898bc7a09fe5346284afe2f76313f3fd1e0ea101d5cf00d414b1b96f" + }, + "effects": { + "href": "https://horizon.stellar.org/operations/164316377777254401/effects" + }, + "succeeds": { + "href": "https://horizon.stellar.org/effects?order=desc&cursor=164316377777254401" + }, + "precedes": { + "href": "https://horizon.stellar.org/effects?order=asc&cursor=164316377777254401" + } + }, + "id": "164316377777254401", + "paging_token": "164316377777254401", + "transaction_successful": true, + "source_account": "GCKEYXSQNHQP2RT7DR34GZVMZQVOKW2O7SKULVTZ7BP3P4BLXF3GOL4R", + "type": "payment", + "type_i": 1, + "created_at": "2021-11-12T22:45:39Z", + "transaction_hash": "3e9d7c75898bc7a09fe5346284afe2f76313f3fd1e0ea101d5cf00d414b1b96f", + "asset_type": "native", + "from": "GCKEYXSQNHQP2RT7DR34GZVMZQVOKW2O7SKULVTZ7BP3P4BLXF3GOL4R", + "to": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "amount": "1.0000000" + }, + { + "_links": { + "self": { + "href": "https://horizon.stellar.org/operations/163385576757178369" + }, + "transaction": { + "href": "https://horizon.stellar.org/transactions/fb3678ab77eac6748948bd319e448fa46b5abc8d9c07c112f5a80dd77123c749" + }, + "effects": { + "href": "https://horizon.stellar.org/operations/163385576757178369/effects" + }, + "succeeds": { + "href": "https://horizon.stellar.org/effects?order=desc&cursor=163385576757178369" + }, + "precedes": { + "href": "https://horizon.stellar.org/effects?order=asc&cursor=163385576757178369" + } + }, + "id": "163385576757178369", + "paging_token": "163385576757178369", + "transaction_successful": true, + "source_account": "GCDDCVXNWNFBJMUSBQWA7TCG6KJK7K3HGE2TZWC7PSXTSVGZMRTCCLU7", + "type": "payment", + "type_i": 1, + "created_at": "2021-10-29T17:31:06Z", + "transaction_hash": "fb3678ab77eac6748948bd319e448fa46b5abc8d9c07c112f5a80dd77123c749", + "asset_type": "native", + "from": "GCDDCVXNWNFBJMUSBQWA7TCG6KJK7K3HGE2TZWC7PSXTSVGZMRTCCLU7", + "to": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "amount": "1.0000000" + } + ] + } +} diff --git a/test/support/fixtures/horizon/trade.json b/test/support/fixtures/horizon/trade.json new file mode 100644 index 00000000..97133cd7 --- /dev/null +++ b/test/support/fixtures/horizon/trade.json @@ -0,0 +1,35 @@ +{ + "_links": { + "self": { + "href": "" + }, + "base": { + "href": "https://horizon.stellar.org/accounts/GAVH5JM5OKXGMQDS7YPRJ4MQCPXJUGH26LYQPQJ4SOMOJ4SXY472ZM7G" + }, + "counter": { + "href": "https://horizon.stellar.org/accounts/GBB4JST32UWKOLGYYSCEYBHBCOFL2TGBHDVOMZP462ET4ZRD4ULA7S2L" + }, + "operation": { + "href": "https://horizon.stellar.org/operations/3697472920621057" + } + }, + "id": "3697472920621057-2", + "paging_token": "3697472920621057-2", + "ledger_close_time": "2015-11-18T03:47:47Z", + "trade_type": "orderbook", + "base_offer_id": "8", + "base_account": "GAVH5JM5OKXGMQDS7YPRJ4MQCPXJUGH26LYQPQJ4SOMOJ4SXY472ZM7G", + "base_amount": "20.0000000", + "base_asset_type": "native", + "counter_offer_id": "10", + "counter_account": "GBB4JST32UWKOLGYYSCEYBHBCOFL2TGBHDVOMZP462ET4ZRD4ULA7S2L", + "counter_amount": "5.3600000", + "counter_asset_type": "credit_alphanum4", + "counter_asset_code": "JPY", + "counter_asset_issuer": "GBVAOIACNSB7OVUXJYC5UE2D4YK2F7A24T7EE5YOMN4CE6GCHUTOUQXM", + "base_is_seller": true, + "price": { + "n": "67", + "d": "250" + } +} diff --git a/test/support/fixtures/horizon/trades.json b/test/support/fixtures/horizon/trades.json new file mode 100644 index 00000000..169152d2 --- /dev/null +++ b/test/support/fixtures/horizon/trades.json @@ -0,0 +1,119 @@ +{ + "_links": { + "self": { + "href": "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/trades?cursor=\u0026limit=3\u0026order=asc" + }, + "next": { + "href": "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/trades?cursor=107449584845914113-0\u0026limit=3\u0026order=asc" + }, + "prev": { + "href": "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD/trades?cursor=107449468881756161-0\u0026limit=3\u0026order=desc" + } + }, + "_embedded": { + "records": [ + { + "_links": { + "self": { + "href": "" + }, + "base": { + "href": "https://horizon.stellar.org/accounts/GCO7OW5P2PP7WDN6YUDXUUOPAR4ZHJSDDCZTIAQRTRZHKQWV45WUPBWX" + }, + "counter": { + "href": "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD" + }, + "operation": { + "href": "https://horizon.stellar.org/operations/107449468881756161" + } + }, + "id": "107449468881756161-0", + "paging_token": "107449468881756161-0", + "ledger_close_time": "2019-07-26T09:17:02Z", + "base_offer_id": "165561423", + "base_account": "GCO7OW5P2PP7WDN6YUDXUUOPAR4ZHJSDDCZTIAQRTRZHKQWV45WUPBWX", + "base_amount": "4433.2000000", + "base_asset_type": "native", + "counter_offer_id": "4719135487309144065", + "counter_account": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "counter_amount": "443.3200000", + "counter_asset_type": "credit_alphanum4", + "counter_asset_code": "BB1", + "counter_asset_issuer": "GD5J6HLF5666X4AZLTFTXLY46J5SW7EXRKBLEYPJP33S33MXZGV6CWFN", + "base_is_seller": true, + "price": { + "n": "1", + "d": "10" + } + }, + { + "_links": { + "self": { + "href": "" + }, + "base": { + "href": "https://horizon.stellar.org/accounts/GCQDOTIILRG634IRWAODTUS6H6Q7VUUNKZINBDJOXGJFR7YZ57FGYV7B" + }, + "counter": { + "href": "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD" + }, + "operation": { + "href": "https://horizon.stellar.org/operations/107449486061649921" + } + }, + "id": "107449486061649921-0", + "paging_token": "107449486061649921-0", + "ledger_close_time": "2019-07-26T09:17:25Z", + "base_offer_id": "165561423", + "base_account": "GCQDOTIILRG634IRWAODTUS6H6Q7VUUNKZINBDJOXGJFR7YZ57FGYV7B", + "base_amount": "10.0000000", + "base_asset_type": "native", + "counter_offer_id": "4719135504489037825", + "counter_account": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "counter_amount": "1.0000000", + "counter_asset_type": "credit_alphanum4", + "counter_asset_code": "BB1", + "counter_asset_issuer": "GD5J6HLF5666X4AZLTFTXLY46J5SW7EXRKBLEYPJP33S33MXZGV6CWFN", + "base_is_seller": true, + "price": { + "n": "1", + "d": "10" + } + }, + { + "_links": { + "self": { + "href": "" + }, + "base": { + "href": "https://horizon.stellar.org/accounts/GAMU5TQFUMDGVKYQPPDCD2MKKUUWELSQAEKNNU4RFQCWFSRBPJA2MAGQ" + }, + "counter": { + "href": "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD" + }, + "operation": { + "href": "https://horizon.stellar.org/operations/107449584845914113" + } + }, + "id": "107449584845914113-0", + "paging_token": "107449584845914113-0", + "ledger_close_time": "2019-07-26T09:19:30Z", + "base_offer_id": "165561423", + "base_account": "GAMU5TQFUMDGVKYQPPDCD2MKKUUWELSQAEKNNU4RFQCWFSRBPJA2MAGQ", + "base_amount": "748.5338945", + "base_asset_type": "native", + "counter_offer_id": "104299548", + "counter_account": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "counter_amount": "74.8533887", + "counter_asset_type": "credit_alphanum4", + "counter_asset_code": "BB1", + "counter_asset_issuer": "GD5J6HLF5666X4AZLTFTXLY46J5SW7EXRKBLEYPJP33S33MXZGV6CWFN", + "base_is_seller": true, + "price": { + "n": "10000000", + "d": "100000001" + } + } + ] + } +} diff --git a/test/support/fixtures/horizon/transaction.json b/test/support/fixtures/horizon/transaction.json index 82f5b5e5..3b7882e5 100644 --- a/test/support/fixtures/horizon/transaction.json +++ b/test/support/fixtures/horizon/transaction.json @@ -5,7 +5,7 @@ "href": "https://horizon.stellar.org/transactions/132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31" }, "account": { - "href": "https://horizon.stellar.org/accounts/GCO2IP3MJNUOKS4PUDI4C7LGGMQDJGXG3COYX3WSB4HHNAHKYV5YL3VC" + "href": "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD" }, "ledger": { "href": "https://horizon.stellar.org/ledgers/27956256" @@ -31,7 +31,7 @@ "hash": "132c440e984ab97d895f3477015080aafd6c4375f6a70a87327f7f95e13c4e31", "ledger": 27956256, "created_at": "2020-01-27T22:13:17Z", - "source_account": "GCO2IP3MJNUOKS4PUDI4C7LGGMQDJGXG3COYX3WSB4HHNAHKYV5YL3VC", + "source_account": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", "source_account_sequence": "64034663849209932", "fee_charged": 100, "max_fee": 100, diff --git a/test/support/fixtures/horizon/transactions.json b/test/support/fixtures/horizon/transactions.json new file mode 100644 index 00000000..d63947ed --- /dev/null +++ b/test/support/fixtures/horizon/transactions.json @@ -0,0 +1,158 @@ +{ + "_links": { + "self": { + "href": "https://horizon.stellar.org/transactions?cursor=\u0026limit=3\u0026order=asc" + }, + "next": { + "href": "https://horizon.stellar.org/transactions?cursor=33736968114176\u0026limit=3\u0026order=asc" + }, + "prev": { + "href": "https://horizon.stellar.org/transactions?cursor=12884905984\u0026limit=3\u0026order=desc" + } + }, + "_embedded": { + "records": [ + { + "memo": "hello world", + "_links": { + "self": { + "href": "https://horizon.stellar.org/transactions/3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889" + }, + "account": { + "href": "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD" + }, + "ledger": { + "href": "https://horizon.stellar.org/ledgers/3" + }, + "operations": { + "href": "https://horizon.stellar.org/transactions/3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889/operations{?cursor,limit,order}", + "templated": true + }, + "effects": { + "href": "https://horizon.stellar.org/transactions/3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889/effects{?cursor,limit,order}", + "templated": true + }, + "precedes": { + "href": "https://horizon.stellar.org/transactions?order=asc\u0026cursor=12884905984" + }, + "succeeds": { + "href": "https://horizon.stellar.org/transactions?order=desc\u0026cursor=12884905984" + } + }, + "id": "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889", + "paging_token": "12884905984", + "successful": true, + "hash": "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889", + "ledger": 7840, + "created_at": "2015-09-30T17:15:54Z", + "source_account": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "source_account_sequence": "12884601890", + "fee_charged": 300, + "max_fee": 300, + "operation_count": 3, + "envelope_xdr": "AAAAAAGUcmKO5465JxTSLQOQljwk2SfqAJmZSG6JH6wtqpwhAAABLAAAAAAAAAABAAAAAAAAAAEAAAALaGVsbG8gd29ybGQAAAAAAwAAAAAAAAAAAAAAABbxCy3mLg3hiTqX4VUEEp60pFOrJNxYM1JtxXTwXhY2AAAAAAvrwgAAAAAAAAAAAQAAAAAW8Qst5i4N4Yk6l+FVBBKetKRTqyTcWDNSbcV08F4WNgAAAAAN4Lazj4x61AAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABLaqcIQAAAEBKwqWy3TaOxoGnfm9eUjfTRBvPf34dvDA0Nf+B8z4zBob90UXtuCqmQqwMCyH+okOI3c05br3khkH0yP4kCwcE", + "result_xdr": "AAAAAAAAASwAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAFAAAAAAAAAAA=", + "result_meta_xdr": "AAAAAAAAAAMAAAACAAAAAAAAAAMAAAAAAAAAABbxCy3mLg3hiTqX4VUEEp60pFOrJNxYM1JtxXTwXhY2AAAAAAvrwgAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAMAAAAAAAAAAAGUcmKO5465JxTSLQOQljwk2SfqAJmZSG6JH6wtqpwhDeC2s5t4PNQAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAEAAAADAAAAAAAAAAABlHJijueOuScU0i0DkJY8JNkn6gCZmUhuiR+sLaqcIQAAAAAL68IAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAMAAAADAAAAAAAAAAAW8Qst5i4N4Yk6l+FVBBKetKRTqyTcWDNSbcV08F4WNgAAAAAL68IAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAEAAAADAAAAAAAAAAAW8Qst5i4N4Yk6l+FVBBKetKRTqyTcWDNSbcV08F4WNg3gtrObeDzUAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAwAAAAAAAAAAAZRyYo7njrknFNItA5CWPCTZJ+oAmZlIbokfrC2qnCEAAAAAC+vCAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "fee_meta_xdr": "AAAAAgAAAAMAAAABAAAAAAAAAAABlHJijueOuScU0i0DkJY8JNkn6gCZmUhuiR+sLaqcIQ3gtrOnZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAEAAAADAAAAAAAAAAABlHJijueOuScU0i0DkJY8JNkn6gCZmUhuiR+sLaqcIQ3gtrOnY/7UAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAA==", + "memo_type": "text", + "signatures": [ + "SsKlst02jsaBp35vXlI300Qbz39+HbwwNDX/gfM+MwaG/dFF7bgqpkKsDAsh/qJDiN3NOW695IZB9Mj+JAsHBA==" + ] + }, + { + "memo": "testpool,faucet,sdf", + "_links": { + "self": { + "href": "https://horizon.stellar.org/transactions/2db4b22ca018119c5027a80578813ffcf582cda4aa9e31cd92b43cf1bda4fc5a" + }, + "account": { + "href": "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD" + }, + "ledger": { + "href": "https://horizon.stellar.org/ledgers/7841" + }, + "operations": { + "href": "https://horizon.stellar.org/transactions/2db4b22ca018119c5027a80578813ffcf582cda4aa9e31cd92b43cf1bda4fc5a/operations{?cursor,limit,order}", + "templated": true + }, + "effects": { + "href": "https://horizon.stellar.org/transactions/2db4b22ca018119c5027a80578813ffcf582cda4aa9e31cd92b43cf1bda4fc5a/effects{?cursor,limit,order}", + "templated": true + }, + "precedes": { + "href": "https://horizon.stellar.org/transactions?order=asc\u0026cursor=33676838572032" + }, + "succeeds": { + "href": "https://horizon.stellar.org/transactions?order=desc\u0026cursor=33676838572032" + } + }, + "id": "2db4b22ca018119c5027a80578813ffcf582cda4aa9e31cd92b43cf1bda4fc5a", + "paging_token": "33676838572032", + "successful": true, + "hash": "2db4b22ca018119c5027a80578813ffcf582cda4aa9e31cd92b43cf1bda4fc5a", + "ledger": 7841, + "created_at": "2015-10-01T04:15:01Z", + "source_account": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "source_account_sequence": "12884901890", + "fee_charged": 300, + "max_fee": 300, + "operation_count": 3, + "envelope_xdr": "AAAAABbxCy3mLg3hiTqX4VUEEp60pFOrJNxYM1JtxXTwXhY2AAABLAAAAAMAAAACAAAAAAAAAAEAAAATdGVzdHBvb2wsZmF1Y2V0LHNkZgAAAAADAAAAAAAAAAAAAAAAH6Ue1GOPj6Hb/ROPyIFCJpQPMujihEIvJSfK0UfMDIgAAAAAC+vCAAAAAAAAAAAAAAAAALMw4P7yJTyqj6ptNh7BPyXEoT+zVwTcU4JVbGyonvgbAAAAAAvrwgAAAAAAAAAAAAAAAABJlwu05Op/5x1uyrweYsyR6pTTos33hRNZe5IF6blnzwAAAAAL68IAAAAAAAAAAAHwXhY2AAAAQDSBB5eNEKkWIoQbZ1YQabJuE5mW/AKhrHTxw9H3m/sai90YcaZlsAe3ueO9jExjSZF289ZcR4vc0wFw1p/WyAc=", + "result_xdr": "AAAAAAAAASwAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "result_meta_xdr": "AAAAAAAAAAMAAAACAAAAAAAAHqEAAAAAAAAAAB+lHtRjj4+h2/0Tj8iBQiaUDzLo4oRCLyUnytFHzAyIAAAAAAvrwgAAAB6hAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQAAHqEAAAAAAAAAABbxCy3mLg3hiTqX4VUEEp60pFOrJNxYM1JtxXTwXhY2DeC2s4+MeHwAAAADAAAAAgAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAB6hAAAAAAAAAACzMOD+8iU8qo+qbTYewT8lxKE/s1cE3FOCVWxsqJ74GwAAAAAL68IAAAAeoQAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAEAAB6hAAAAAAAAAAAW8Qst5i4N4Yk6l+FVBBKetKRTqyTcWDNSbcV08F4WNg3gtrODoLZ8AAAAAwAAAAIAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAeoQAAAAAAAAAASZcLtOTqf+cdbsq8HmLMkeqU06LN94UTWXuSBem5Z88AAAAAC+vCAAAAHqEAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAABAAAeoQAAAAAAAAAAFvELLeYuDeGJOpfhVQQSnrSkU6sk3FgzUm3FdPBeFjYN4Lazd7T0fAAAAAMAAAACAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAA=", + "fee_meta_xdr": "AAAAAgAAAAMAAB55AAAAAAAAAAAW8Qst5i4N4Yk6l+FVBBKetKRTqyTcWDNSbcV08F4WNg3gtrObeDuoAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAEAAB6hAAAAAAAAAAAW8Qst5i4N4Yk6l+FVBBKetKRTqyTcWDNSbcV08F4WNg3gtrObeDp8AAAAAwAAAAIAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAA==", + "memo_type": "text", + "signatures": [ + "NIEHl40QqRYihBtnVhBpsm4TmZb8AqGsdPHD0feb+xqL3RhxpmWwB7e5472MTGNJkXbz1lxHi9zTAXDWn9bIBw==" + ] + }, + { + "memo": "", + "_links": { + "self": { + "href": "https://horizon.stellar.org/transactions/3ce2aca2fed36da2faea31352c76c5e412348887a4c119b1e90de8d1b937396a" + }, + "account": { + "href": "https://horizon.stellar.org/accounts/GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD" + }, + "ledger": { + "href": "https://horizon.stellar.org/ledgers/7855" + }, + "operations": { + "href": "https://horizon.stellar.org/transactions/3ce2aca2fed36da2faea31352c76c5e412348887a4c119b1e90de8d1b937396a/operations{?cursor,limit,order}", + "templated": true + }, + "effects": { + "href": "https://horizon.stellar.org/transactions/3ce2aca2fed36da2faea31352c76c5e412348887a4c119b1e90de8d1b937396a/effects{?cursor,limit,order}", + "templated": true + }, + "precedes": { + "href": "https://horizon.stellar.org/transactions?order=asc\u0026cursor=33736968114176" + }, + "succeeds": { + "href": "https://horizon.stellar.org/transactions?order=desc\u0026cursor=33736968114176" + } + }, + "id": "3ce2aca2fed36da2faea31352c76c5e412348887a4c119b1e90de8d1b937396a", + "paging_token": "33736968114176", + "successful": true, + "hash": "3ce2aca2fed36da2faea31352c76c5e412348887a4c119b1e90de8d1b937396a", + "ledger": 7855, + "created_at": "2015-10-01T04:16:11Z", + "source_account": "GCXMWUAUF37IWOOV2FRDKWEX3O2IHLM2FYH4WPI4PYUKAIFQEUU5X3TD", + "source_account_sequence": "12884901891", + "fee_charged": 100, + "max_fee": 100, + "operation_count": 1, + "envelope_xdr": "AAAAABbxCy3mLg3hiTqX4VUEEp60pFOrJNxYM1JtxXTwXhY2AAAAZAAAAAMAAAADAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAAFAAAAAQAAAAAfpR7UY4+Podv9E4/IgUImlA8y6OKEQi8lJ8rRR8wMiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwXhY2AAAAQNbDcWsR3s3z8Qzqatcdc/k2L4LXWJMA6eXac8dbXkAdc4ppH25isGC5OwvG06Vwvc3Ce3/r2rYcBP3vxhx18A8=", + "result_xdr": "AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAAFAAAAAAAAAAA=", + "result_meta_xdr": "AAAAAAAAAAEAAAABAAAAAQAAHq8AAAAAAAAAABbxCy3mLg3hiTqX4VUEEp60pFOrJNxYM1JtxXTwXhY2DeC2s3e09BgAAAADAAAAAwAAAAAAAAABAAAAAB+lHtRjj4+h2/0Tj8iBQiaUDzLo4oRCLyUnytFHzAyIAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAA", + "fee_meta_xdr": "AAAAAgAAAAMAAB6hAAAAAAAAAAAW8Qst5i4N4Yk6l+FVBBKetKRTqyTcWDNSbcV08F4WNg3gtrN3tPR8AAAAAwAAAAIAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAEAAB6vAAAAAAAAAAAW8Qst5i4N4Yk6l+FVBBKetKRTqyTcWDNSbcV08F4WNg3gtrN3tPQYAAAAAwAAAAMAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAA==", + "memo_type": "text", + "signatures": [ + "1sNxaxHezfPxDOpq1x1z+TYvgtdYkwDp5dpzx1teQB1zimkfbmKwYLk7C8bTpXC9zcJ7f+vathwE/e/GHHXwDw==" + ] + } + ] + } +}