Skip to content

Commit

Permalink
Prepare v0.2.0 release (#93)
Browse files Browse the repository at this point in the history
* Add SDK base documentation and initial examples

* Include examples into mix docs

* Minor tweeks for the Readme

* Add v2.0.0 changelog

* Update LICENSE copyright
  • Loading branch information
Juan Hurtado authored Feb 7, 2022
1 parent bbe0c0c commit 5c80123
Show file tree
Hide file tree
Showing 7 changed files with 442 additions and 9 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## 0.2.0 (07.02.2022)
* Add base documentation and initial examples.
* Dynamically update the transaction's BaseFee.
* Submit transactions to Horizon.
* Fetch next account's sequence number from Horizon.
* Retrieve account data from Horizon.
* Implement transaction signatures.
* Add TxBuild validations.
* TxBuild constructions for operations.
* Define a Horizon abstraction. Add a default implementation.
* Define a TxBuild abstraction. Add a default implementation.

## 0.1.1 (05.11.2021)
* Update library namespace from StellarSDK to Stellar.
* Upgrade stellar_base dependency.
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021 kommitters Open Source
Copyright (c) 2022 kommitters Open Source

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
318 changes: 313 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,320 @@
![Downloads Badge](https://img.shields.io/hexpm/dt/stellar_sdk?style=for-the-badge)
[![License badge](https://img.shields.io/hexpm/l/stellar_sdk.svg?style=for-the-badge)](https://github.com/kommitters/stellar_sdk/blob/main/LICENSE.md)

### ⚠️ Warning! This library is under active development. DO NOT use it in production environments.
**`stellar_sdk`** is an **Elixir library** that provides a client for interfacing with **Horizon** endpoints to retrieve ledger information, and to submit transactions. **`stelar_sdk`** uses the [**`stellar_base`**][base] library to enable the construction, signing and encoding of Stellar primitive XDR constructs.

`stellar_sdk` is an **Elixir library** that provides a client for interfacing with **Horizon** server REST endpoints to retrieve ledger information, and to submit transactions. `stelar_sdk` uses the [stellar_base][base] library to enable the construction, signing and encoding of Stellar primitive XDR constructs.
This library is aimed at developers building Elixir applications that interact with the [**Stellar network**][stellar].

This library is aimed at developers building Elixir applications that interact with the [Stellar network][stellar].
## Documentation
* [**API Reference**][api-reference]
* [**Examples**](/docs/examples.md)

## Installation
[Available in Hex][hex], add `stellar_sdk` to your list of dependencies in `mix.exs`:
[**Available in Hex**][hex], add `stellar_sdk` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[
{:stellar_sdk, "~> 0.1.0"}
{:stellar_sdk, "~> 0.2.0"}
]
end
```

## Configuration
```elixir
config :stellar_sdk, network: :test # Default is `:test`. To use the public network, set it to `:public`

```

The default HTTP Client is `:hackney`. Options to `:hackney` can be passed through configuration params.
```elixir
config :stellar_sdk, hackney_opts: [{:connect_timeout, 1000}, {:recv_timeout, 5000}]
```

### Custom HTTP Client
Stellar allows you to use your HTTP client of choice. Specification in [**Stellar.Horizon.Client.Spec**][http_client_spec]

```elixir
config :stellar_sdk, :http_client_impl, YourApp.CustomClientImpl
```

## Usage

### KeyPairs
Stellar relies on public key cryptography to ensure that transactions are secure: every account requires a valid keypair consisting of a public key and a private key.

```elixir
# generate a random key pair
{public_key, secret_seed} = Stellar.KeyPair.random()

# derive a key pair from a secret seed
{public_key, secret_seed} = Stellar.KeyPair.from_secret_seed("SA33J3ACZZCV35FNSS655WXLIPTQOJS6WPQCKKYJSREDQY7KRLECEZSZ")
```

### Building transactions
[Transactions][stellar-docs-tx] are commands that modify the ledger state. They consist of a list of operations (up to 100) used to send payments, enter orders into the decentralized exchange, change settings on accounts, and authorize accounts to hold assets.

```elixir
# set the source account (this accound should exist in the ledger)
source_account = Stellar.TxBuild.Account.new("GDC3W2X5KUTZRTQIKXM5D2I5WG5JYSEJQWEELVPQ5YMWZR6CA2JJ35RW")

# initialize a transaction
Stellar.TxBuild.new(source_account)

# initialize a transaction with options
# allowed options: memo, sequence_number, base_fee, time_bounds
Stellar.TxBuild.new(source_account, memo: memo, sequence_number: sequence_number)
```

#### Memos
A [memo][stellar-docs-memo] contains optional extra information for the transaction. Memos can be one of the following types:

* **`MEMO_TEXT`** : A string encoded using either ASCII or UTF-8, up to 28-bytes long.
* **`MEMO_ID`** A 64 bit unsigned integer.
* **`MEMO_HASH`** : A 32 byte hash.
* **`MEMO_RETURN`** : A 32 byte hash intended to be interpreted as the hash of the transaction the sender is refunding.

```elixir
# set the source account (this accound should exist in the ledger)
source_account = Stellar.TxBuild.Account.new("GDC3W2X5KUTZRTQIKXM5D2I5WG5JYSEJQWEELVPQ5YMWZR6CA2JJ35RW")

# build a memo
memo = Stellar.TxBuild.Memo.new(:none)
memo = Stellar.TxBuild.Memo.new(text: "MEMO")
memo = Stellar.TxBuild.Memo.new(id: 123_4565)
memo = Stellar.TxBuild.Memo.new(hash: "0859239b58d3f32972fc9124559cea7251225f2dbc6f0d83f67dc041e6608510")
memo = Stellar.TxBuild.Memo.new(return: "d83f67dc041e66085100859239b58d3f32924559cea72512272fc915f2dbc6f0")

# add a memo for the transaction
tx =
source_account
|> Stellar.TxBuild.new()
|> Stellar.TxBuild.add_memo(memo)
```

#### Sequence number
Each transaction has a [sequence number][stellar-docs-sequence-number] associated with the source account. Transactions follow a strict ordering rule when it comes to processing transactions per account in order to prevent double-spending.

```elixir
# set the source account (this accound should exist in the ledger)
source_account = Stellar.TxBuild.Account.new("GDC3W2X5KUTZRTQIKXM5D2I5WG5JYSEJQWEELVPQ5YMWZR6CA2JJ35RW")

# fetch next account's sequence number from Horizon
{:ok, seq_num} = Stellar.Horizon.Accounts.fetch_next_sequence_number("GDC3W2X5KUTZRTQIKXM5D2I5WG5JYSEJQWEELVPQ5YMWZR6CA2JJ35RW")

# build a sequence number
sequence_number = Stellar.TxBuild.SequenceNumber.new(seq_num)

# set the sequence number for the transaction
tx =
source_account
|> Stellar.TxBuild.new()
|> Stellar.TxBuild.set_sequence_number(sequence_number)
```

#### Base fee
Each transaction incurs a [fee][stellar-docs-fee], which is paid by the source account. When you submit a transaction, you set the maximum that you are willing to pay per operation, but you’re charged the minimum fee possible based on network activity.

```elixir
# set the source account (this accound should exist in the ledger)
source_account = Stellar.TxBuild.Account.new("GDC3W2X5KUTZRTQIKXM5D2I5WG5JYSEJQWEELVPQ5YMWZR6CA2JJ35RW")

# build a fee
base_fee = Stellar.TxBuild.BaseFee.new(1_000)

# set a fee for the transaction
tx =
source_account
|> Stellar.TxBuild.new()
|> Stellar.TxBuild.set_base_fee(base_fee)
```

#### Time bounds
[TimeBounds][stellar-docs-time-bounds] are optional UNIX timestamps (in seconds), determined by ledger time. A lower and upper bound of when this transaction will be valid.

```elixir
# set the source account (this accound should exist in the ledger)
source_account = Stellar.TxBuild.Account.new("GDC3W2X5KUTZRTQIKXM5D2I5WG5JYSEJQWEELVPQ5YMWZR6CA2JJ35RW")

# no time bounds for a transaction
time_bounds = Stellar.TxBuild.TimeBounds.new(:none)

# time bounds using UNIX timestamps
time_bounds = Stellar.TxBuild.TimeBounds.new(
min_time: 1_643_558_792,
max_time: 1_643_990_815
)

# time bounds using DateTime
time_bounds = Stellar.TxBuild.TimeBounds.new(
min_time: ~U[2022-01-30 16:06:32.963238Z],
max_time: ~U[2022-02-04 16:06:55.734317Z]
)

# timeout
time_bounds = Stellar.TxBuild.TimeBounds.set_timeout(1_643_990_815)

# set the time bounds for the transaction
tx =
source_account
|> Stellar.TxBuild.new()
|> Stellar.TxBuild.set_time_bounds(time_bounds)
```

#### Operations
[Operations][stellar-docs-list-operations] represent a desired change to the ledger: payments, offers to exchange currency, changes made to account options, etc. Operations are submitted to the Stellar network grouped in a Transaction. Single transactions may have up to 100 operations.

```elixir
# set the source account (this accound should exist in the ledger)
source_account = Stellar.TxBuild.Account.new("GDC3W2X5KUTZRTQIKXM5D2I5WG5JYSEJQWEELVPQ5YMWZR6CA2JJ35RW")

# build a create_account operation
# the destination account does not exist in the ledger
create_account_op = Stellar.TxBuild.CreateAccount.new(
destination: "GDWD36NCYNN4UFX63F2235QGA2XVGTXG7NQW6MN754SHTQM4XSLTXLYK",
starting_balance: 2
)

# build a payment operation
# the destination account should exist in the ledger
payment_op = Stellar.TxBuild.Payment.new(
destination: "GDWD36NCYNN4UFX63F2235QGA2XVGTXG7NQW6MN754SHTQM4XSLTXLYK",
asset: :native,
amount: 5
)

# add a single operation to a transaction
tx =
source_account
|> Stellar.TxBuild.new()
|> Stellar.TxBuild.add_operation(create_account_op)


# add multiple operations to a transaction
tx =
source_account
|> Stellar.TxBuild.new()
|> Stellar.TxBuild.add_operations([create_account_op, payment_op])
```

#### Signing and multi-sig
Stellar uses [signatures][stellar-docs-tx-signatures] as authorization. Transactions always need authorization from at least one public key in order to be considered valid.

In two cases, a transaction may need more than one signature. If the transaction has operations that affect more than one account, it will need authorization from every account in question. A transaction will also need additional signatures if the account associated with the transaction has multiple public keys.

```elixir
# set the source account (this accound should exist in the ledger)
source_account = Stellar.TxBuild.Account.new("GDC3W2X5KUTZRTQIKXM5D2I5WG5JYSEJQWEELVPQ5YMWZR6CA2JJ35RW")

# build an operation
# the destination account should exist in the ledger
operation = Stellar.TxBuild.Payment.new(
destination: "GDWD36NCYNN4UFX63F2235QGA2XVGTXG7NQW6MN754SHTQM4XSLTXLYK",
asset: :native,
amount: 5
)

# build transaction signatures
# signer accounts should exist in the ledger
signer_key_pair = Stellar.KeyPair.from_secret_seed("SBJJSBBXGKNXALBZ3F3UTHAPKJSESACSKPLW2ZEMM5E5WPVNNKTW55XN")
signer_key_pair2 = Stellar.KeyPair.from_secret_seed("SA2NWVOPQMQYAU5RATOE7HJLMPLQZRNPGSOQGGEG6P2ZSYTWFORY5AV5")

signature1 = Stellar.TxBuild.Signature.new(signer_key_pair)
signature2 = Stellar.TxBuild.Signature.new(signer_key_pair2)

# add a single signature to a transaction
tx =
source_account
|> Stellar.TxBuild.new()
|> Stellar.TxBuild.add_operation(operation)
|> Stellar.TxBuild.sign(signature1)


# add multiple signatures to a transaction
tx =
source_account
|> Stellar.TxBuild.new()
|> Stellar.TxBuild.add_operation(operation)
|> Stellar.TxBuild.sign([signature1, signature2])
```

#### Transaction envelope
Once a transaction has been filled out, it is wrapped in a [Transaction envelope][stellar-docs-tx-envelope] containing the transaction as well as a set of signatures.

```elixir
# set the source account (this accound should exist in the ledger)
source_account = Stellar.TxBuild.Account.new("GDC3W2X5KUTZRTQIKXM5D2I5WG5JYSEJQWEELVPQ5YMWZR6CA2JJ35RW")

# build an operation
# the destination account should exist in the ledger
operation = Stellar.TxBuild.Payment.new(
destination: "GDWD36NCYNN4UFX63F2235QGA2XVGTXG7NQW6MN754SHTQM4XSLTXLYK",
asset: :native,
amount: 5
)

# build the transaction signatures
# signer account should exist in the ledger
signer_key_pair = Stellar.KeyPair.from_secret_seed("SBJJSBBXGKNXALBZ3F3UTHAPKJSESACSKPLW2ZEMM5E5WPVNNKTW55XN")
signature = Stellar.TxBuild.Signature.new(signer_key_pair)

# build a base64 transaction envelope
tx =
source_account
|> Stellar.TxBuild.new()
|> Stellar.TxBuild.add_operation(operation)
|> Stellar.TxBuild.sign(signature)
|> Stellar.TxBuild.envelope()

"AAAAAgAAAACuy1AULv6LOdXRYjVYl9u0g62aLg/LPRx+KKAgsCUp2wAAAGQAAA+pAAAAFAAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAL5xix0HYeCnnvADhMs2eqCLBfE+WT3Kh7axgdzPzWX0AAAAAAAAAAAC+vCAAAAAAAAAAAKwJSnbAAAAQMhnGfygZvau5bXFHnJ1rCLIiqiZiI+C4Xf4bWCrxTERPOM/nJKuDottj48bep8NlI42WIUgqZVeQAKykWE74AXPzWX0AAAAQHp0wWKyv80frbOkX3QgOFtflxExX9H46b8ws8fMznSt6/9Le567cqoPxLb/SYvw3Wh6j3B5Vl04CBWyKnLYUwg="
```

#### Submitting a transaction
The transaction can now be submitted to the Stellar network.

```elixir
# set the source account (this accound should exist in the ledger)
source_account = Stellar.TxBuild.Account.new("GDC3W2X5KUTZRTQIKXM5D2I5WG5JYSEJQWEELVPQ5YMWZR6CA2JJ35RW")

# fetch next account's sequence number from Horizon
{:ok, seq_num} = Stellar.Horizon.Accounts.fetch_next_sequence_number("GDC3W2X5KUTZRTQIKXM5D2I5WG5JYSEJQWEELVPQ5YMWZR6CA2JJ35RW")

# set the sequence number
sequence_number = Stellar.TxBuild.SequenceNumber.new(seq_num)

# set a memo
memo = Stellar.TxBuild.Memo.new(text: "MEMO")

# build an operation
# the destination account should exist in the ledger
operation = Stellar.TxBuild.Payment.new(
destination: "GDWD36NCYNN4UFX63F2235QGA2XVGTXG7NQW6MN754SHTQM4XSLTXLYK",
asset: :native,
amount: 5
)

# build the transaction signatures
# signer account should exist in the ledger
signer_key_pair = Stellar.KeyPair.from_secret_seed("SBJJSBBXGKNXALBZ3F3UTHAPKJSESACSKPLW2ZEMM5E5WPVNNKTW55XN")
signature = Stellar.TxBuild.Signature.new(signer_key_pair)

# build a base64 transaction envelope
base64_envelope =
source_account
|> Stellar.TxBuild.new(sequence_number: sequence_number)
|> Stellar.TxBuild.add_memo(memo)
|> Stellar.TxBuild.add_operation(operation)
|> Stellar.TxBuild.sign(signature)
|> Stellar.TxBuild.envelope()

# submit transaction to Horizon
{:ok, submitted_tx} = Stellar.Horizon.Transactions.create(base64_envelope)
```

More examples can be found in the [**tests**][sdk-tests].

---

## Development
* Install any Elixir version above 1.10.
* Compile dependencies: `mix deps.get`.
Expand All @@ -48,5 +345,16 @@ Made with 💙 by [kommitters Open Source](https://kommit.co)
[contributing]: https://github.com/kommitters/stellar_sdk/blob/main/CONTRIBUTING.md
[base]: https://github.com/kommitters/stellar_base
[sdk]: https://github.com/kommitters/stellar_sdk
[sdk-tests]: https://github.com/kommitters/stellar_sdk/blob/main/test/tx_build
[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
[stellar-docs-memo]: https://developers.stellar.org/docs/glossary/transactions/#memo
[stellar-docs-time-bounds]: https://developers.stellar.org/docs/glossary/transactions/#time-bounds
[stellar-docs-tx-envelope]: https://developers.stellar.org/docs/glossary/transactions/#transaction-envelopes
[stellar-docs-list-operations]: https://developers.stellar.org/docs/start/list-of-operations
[stellar-docs-tx-signatures]: https://developers.stellar.org/docs/glossary/multisig/#transaction-signatures
3 changes: 3 additions & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Examples
* [Creating an account](/docs/examples/create_account.md)
* [Payments](/docs/examples/payments.md)
Loading

0 comments on commit 5c80123

Please sign in to comment.