# Icon
![Build status](https://github.com/alexdesousa/icon/actions/workflows/checks.yml/badge.svg) [![Hex pm](http://img.shields.io/hexpm/v/icon.svg?style=flat)](https://hex.pm/packages/icon) [![hex.pm downloads](https://img.shields.io/hexpm/dt/icon.svg?style=flat)](https://hex.pm/packages/icon) [![Coverage Status](https://coveralls.io/repos/github/alexdesousa/icon/badge.svg?branch=master)](https://coveralls.io/github/alexdesousa/icon?branch=master)
> _"Knowledge is of no value unless you put it into practice."_ - Anton Chekhov
`Icon` is a library for interacting with the interoperable decentralized
aggregator network [ICON 2.0](https://icon.foundation).
This document gives a general overview of the current state of the project and
its future:
- [Motivation](#motivation)
- [Overview](#overview)
+ [Example: Transferring ICX](#example-transferring-icx)
+ [Example: SCORE Transaction Call](#example-score-transaction-call)
- [Wallets and Node Connections](#wallets-and-node-connections)
- [Realtime Updates](#realtime-updates)
+ [Example: Subscribing to Blocks](#example-subscribing-to-blocks)
+ [Example: Subscribing to Events](#example-subscribing-to-events)
- [TODO](#todo)
- [Installation](#installation)
+ [Containerized Testing](#containerized-testing)
+ [Installing Elixir](#installing-elixir)
## Motivation
The motivation for building a SDK in Elixir is to be able to use:
- The battle-tested Erlang runtime and Erlang supervisors,
- The amazing Elixir real-time libraries,
- Documentation as first class citizen,
- And my favorite language (strong bias here)
while writing client applications for ICON 2.0.
This library is a work in progress, so if you want to use a production ready SDK
you're better off using one of the official ones:
- [Java SDK](https://github.com/icon-project/icon-sdk-java)
- [Javascript SDK](https://github.com/icon-project/icon-sdk-js)
- [Python SDK](https://github.com/icon-project/icon-sdk-python)
- [Swift (iOS development)](https://github.com/icon-project/ICONKit)
## Overview
Every single JSON API v3 method is already implemented (no BTP nor IISS
extensions yet).
For most applications, we'll need just two modules:
- `Icon` - where we'll find the JSON RPC API.
- `Icon.RPC.Identity` - where we'll find the wallet and node connection
initialization.
Though this SDK was heavily inspired by the
[ICON Python SDK](https://github.com/icon-project/icon-sdk-python), it difers
slightly from it:
- (Mostly) automatic type translation from ICON 2.0 representation to Elixir's
for both inputs and outputs.
- Automatic `stepLimit` estimation via `debug_estimateStep` method (it can be
overriden).
- `icx_sendTransaction` method become several function calls depending of the
objective of the transaction:
+ `Icon.transfer/4` for transferring ICX from an EOA address to another
address (EOA or SCORE).
+ `Icon.send_message/4` for sending messages from an EOA address to another.
+ `Icon.transaction_call/5` for calling SCORE functions.
+ `Icon.install_score/3` for installing SCOREs in the ICON blockchain.
+ `Icon.update_score/4` for updating SCOREs in the ICON blockchain.
+ `Icon.deposit_shared_fee/4` for withdrawing shared fees from SCOREs.
+ `Icon.withdraw_shared_fee/4` for depositing shared fees into SCOREs.
- Any of the previous function calls can use `icx_sendTransactionAndWait` just
by setting a `timeout` in the options.
### Example: Transferring ICX
The following example shows how to send 1 ICX from our wallet to another:
```elixir
iex> identity = Icon.RPC.Identity.new(private_key: "8ad9...")
iex> Icon.transfer(identity, "hx2e243ad926ac48d15156756fce28314357d49d83", 1_000_000_000_000_000_000)
{:ok, "0xd579ce6162019928d874da9bd1dbf7cced2359a5614e8aa0bf7cf75f3770504b"}
```
> *Note*: In this library, ICX is always expressed in _loop_ as units, where
> 1 ICX = 10¹⁸ loop.
### Example: SCORE Transaction Call
The following example calls a fuction in a SCORE in the Sejong testnet:
```elixir
iex> identity = Icon.RPC.Identity.new(private_key: "8ad9...", network_id: :sejong)
iex> params = %{
...> _strategy: "cx31f04b8d24628463db5ac9f04a7d33ba32e44680",
...> _start: 1,
...> _end: 51
...> }
iex> schema = %{
...> _strategy: {:score_address, required: true},
...> _start: :integer,
...> _end: :integer
...> }
iex> Icon.transaction_call(
...> identity,
...> "cx9cd4af2976c8ffabf3146d1d166e83a6dd689d50",
...> "claim",
...> params,
...> call_schema: schema
...> )
{:ok, "0xd579ce6162019928d874da9bd1dbf7cced2359a5614e8aa0bf7cf75f3770504b"}
```
> *Note*: The previous example was taken from
> [Optimus Finance](https://optimus.finance) SCORE for claiming rewards from
> strategies.
### Wallets and Node Connections
Every request to the API requires an `Icon.RPC.Identity.t()` instance. There
are two types of identities:
- Without wallet (for most readonly remote calls):
```elixir
iex> Icon.RPC.Identity.new()
#Identity<[
node: "https://ctz.solidwallet.io",
network_id: "0x1 (Mainnet)",
debug: false
]>
```
- With wallet (for transactions and SCORE readonly calls):
```elixir
iex> Icon.RPC.Identity.new(private_key: "8ad9...")
#Identity<[
node: "https://ctz.solidwallet.io",
network_id: "0x1 (Mainnet)",
debug: false,
address: "hxfd7e4560ba363f5aabd32caac7317feeee70ea57",
private_key: "8ad9..."
]>
```
> **Note**: the private key is always redacted when inspecting the
> `Icon.RPC.Identity.t()` struct. The idea is to be able to safely log an
> identity without compromising the security of the underlying wallet by
> revealing sensitive data.
For more customization options, checkout the module `Icon.RPC.Identity`
documentation.
## Realtime Updates
It is possible to subscribe to the ICON 2.0 websocket to get either or both
block and event log updates in realtime. It leverages
[Yggdrasil](https://github.com/gmtprime/yggdrasil) (built with `Phoenix.PubSub`)
for handling incoming messages.
The channel has two main fields:
- `adapter` - Which for this specific adapter, it should be set to `:icon`.
- `name` - Where we'll find information of the websocket connection. For this
adapter, it is a map with the following keys:
+ `source` - Whether `:block` or `:event` (required).
+ `identity` - `Icon.RPC.Identity` instance pointed to the right network. It
defaults to Mainnet if no identity is provided.
+ `from_height` - Block height from which we should start receiving messages.
It defaults to `:latest`.
+ `data` - It varies depending on the `source` chosen (see
`Yggdrasil.Adapter.Icon` for more information).
> **Important**: We need to be careful when using `from_height` in the channel
> because `Yggdrasil` will restart the synchronization process from the
> chosen height if the process crashes.
### Example: Subscribing to Blocks
We can subscribe to blocks using `Yggdrasil.subscribe/1`:
```elixir
iex> channel = [name: %{source: :block}, adapter: :icon]
iex> Yggdrasil.subscribe(channel)
:ok
```
and our subscriber process will get notifications in its mailbox every time a
block is produced. If we use `flush/0` to flush the messages from the IEX
mailbox, we'll get something like the following:
```elixir
iex> flush()
{:Y_CONNECTED, %Yggdrasil.Channel{adapter: :icon, ...}}
{:Y_EVENT, %Yggdrasil.Channel{adapter: :icon, ...}, %Icon.Schema.Types.Block.Tick{height: 42, hash: "0xc71303ef8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"}}
...
```
When we're done, we can unsubscribe using the following:
```elixir
iex> Yggdrasil.unsubscribe(channel)
:ok
```
> **Note**: Also we can subscribe to one or more events using the block
> subscription. We'll get both `Icon.Schema.Types.Block.Tick.t()` and
> `Icon.Schema.Types.EventLog.t()`. For this, we need to provide a list of
> events we want to subscribe to in the `data` field:
>
> ```elixir
> iex> channel = [
> ...> adapter: :icon,
> ...> name: %{
> ...> source: :block,
> ...> data: [
> ...> %{
> ...> event: "Transfer(Address,Address,int)"
> ...> }
> ...> ]
> ...> }
> ...> ]
> iex> Yggdrasil.subscribe(channel)
> :ok
> ```
>
> For more info about the notifications, check the next section.
### Example: Subscribing to Events
Following the previous example, if we're only interested in a single event, we
can just subscribe to it e.g. the following shows a subscription to the event
`Transfer(Address,Address,int)` for the SCORE address
`cx31f04b8d24628463db5ac9f04a7d33ba32e44680`:
```elixir
iex> channel = [
...> adapter: :icon,
...> name: %{
...> source: :event,
...> data: %{
...> addr: "cx31f04b8d24628463db5ac9f04a7d33ba32e44680",
...> event: "Transfer(Address,Address,int)"
...> }
...> }
...> ]
iex> Yggdrasil.subscribe(channel)
:ok
```
Our subscriber process will get notifications in its mailbox every time an
event matches our query. If we use `flush/0` to flush the messages from the IEX
mailbox, we'll get something like the following:
```elixir
iex> flush()
{:Y_CONNECTED, %Yggdrasil.Channel{adapter: :icon, ...}}
{:Y_EVENT, %Yggdrasil.Channel{adapter: :icon, ...}, %Icon.Schema.Types.Block.Tick{height: 42, hash: "0xc71303ef8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"}}
{:Y_EVENT, %Yggdrasil.Channel{adapter: :icon, ...}, %Icon.Schema.Types.EventLog{header: "Transfer(Address,Address,int)", indexed: ["hxfd7e4560ba363f5aabd32caac7317feeee70ea57", "hxbe7e4560ba363f5aabd32caac7317feeee70ea57"], ...}}
...
```
Also, when we're done, we can unsubscribe using the following:
```elixir
iex> Yggdrasil.unsubscribe(channel)
:ok
```
## TODO
The following is a list of functionalities this SDK aims to support in the
future:
- [x] [Goloop API](https://www.icondev.io/icon-node/goloop/json-rpc/jsonrpc_v3)
- [ ] [BTP Extension](https://www.icondev.io/icon-node/goloop/json-rpc/btp_extension)
- [x] [Yggdrasil](https://github.com/gmtprime/yggdrasil) support for BTP websockets.
- [ ] [IISS Extension](https://www.icondev.io/icon-node/goloop/json-rpc/iiss_extension)
## Installation
The package is available in [Hex](https://hex.pm/packages/icon) and can be
installed by adding `icon` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:icon, "~> 0.2"}
]
end
```
### Containerized Testing
If you just want to try it out, you can run this project inside a docker
container with Elixir:
```bash
$ git clone https://github.com/alexdesousa/icon.git
$ cd icon/
$ docker run -it --rm -v $PWD:/data -w /data elixir:latest iex -S mix
```
> **Note**: For certain `docker` setups, you'll need to add `sudo` at the beginning
> of the command.
While in the IEx shell, you can check the documentation for any module and
function just by typing `h` in front of it e.g:
```elixir
iex> h Icon.transfer
# ... shows documentation for `Icon.transfer/3` and `Icon.transfer/4` ...
```
### Installing Elixir
If you want to install Elixir, I recommend using
[asdf](http://asdf-vm.com/guide/getting-started.html#_1-install-dependencies)
to get it (you'll need both Erlang and Elixir to be able to run this library).
In Ubuntu/Linux Mint, you'll need the following dependencies to be able to
build Erlang:
```bash
$ sudo apt install \
build-essential \
autoconf \
m4 \
libncurses5-dev \
libwxgtk3.0-gtk3-dev \
libgl1-mesa-dev \
libglu1-mesa-dev \
libpng-dev \
libssh-dev \
unixodbc-dev \
xsltproc \
fop
```
Once they're installed you can add Erlang and build it:
```bash
$ asdf plugin-add erlang
$ asdf install erlang 24.2 # This step takes some time.
$ asdf global erlang 24.2
```
Finally, you can install Elixir running the following:
```bash
$ asdf plugin-add elixir
$ asdf install elixir 1.13.2-otp-24
$ asdf global elixir 1.13.2-otp-24
```
## Author
Alex de Sousa (a.k.a. Etadelius).
## License
`Icon` is released under the MIT License. See the LICENSE file for further
details.