The Portfolio Manager by Stove Labs is all-in-one Tezos dashboard with configurable widgets.
Make sure you're not missing any dependencies first.
In order to get started with development locally, run the following commands:
# Clone the repository
git clone [email protected]:stove-labs/portfolio-manager.git
# Install project dependencies
npm install
# Run a local development server
npm run dev
# Open your browser at localhost:1234 to view the Portfolio Manager
- Node.js
v17.8.0
+ NPMv8.5.5
- Instance of TzKt.io API
- Publicly available at:
https://api.tzkt.io
- You can alternatively run a Taqueria sandbox which also includes the TzKt API locally
- Publicly available at:
- Access to free and public Coinbase prices API
- Available publicly at:
https://api.coinbase.com/v2/prices
- Available publicly at:
Default configuration can be found in the files linked for each configuration section below
The portfolio manager allows to configure the following options at development/build time:
- Environment configuration
- Available FIAT currencies
- Currency tickers specified here must be available via Coinbase prices API
- Available on-chain tokens
- Tokens specified here must have an XTZ/TOKEN pair on Quipuswap, otherwise the portfolio manager won't be able to determine it's XTZ & FIAT price
This section showcases the currently available/implemented widgets. All of the widgets above can be added to the dashboard using the in-app Widget Store.
Token Balance widget shows the user's current token balance, together with it's fiat value. It also shows the respective percentage change from historical to current balance, respective of the widget historical period settings.
Relative Token Balance Change widget shows the relative change in user's token balance over the specified historical period, together with it's fiat value.
This section breaks down features from both the product and architectural standpoint with the goal to explain the design decision made during the development process.
It is an entity based local 'database' which allows widgets to store and share data in a normalised format, no matter where the data originated from. This ensures that any data requested or shown is going to be consistent across all the containers, components or pages.
Currently supported entities, with the respective APIs for working with them are:
- Blocks (Keeps track of latest/historical block data)
- Actions
- The only public facing action is currently
LOAD_LATEST_BLOCK
, since it's the only one required for the underlying data refetching logic. TODO: add data refetching section
- The only public facing action is currently
- State
- Block
id
is either auuidv4
orblock.level
, depending on if the request has finished fetching or not (since block.level is unknown before the request finishes).
- Block
- Effects
- Library
- Fetching of block(s) data using the TzKT.io API
- Actions
- Balances (Keeps track of balances for a given address/level/tokenId)
- Actions
- Action for fetching both the current and historical balances is unified as
LOAD_BALANCES
. Since both can be represented as balance at level, while keeping in mind that the app is aware of the percieved latest block level at all times.
- Action for fetching both the current and historical balances is unified as
- State
- Balance
id
isblockLevel-address-tokenId
, this allows us to store balances uniquely for any given address, tokenId and level combination. This is useful, since we also need to fetch balances of e.g. pools on Quipuswap to determine spot prices. - When it comes to historical data, the relative historial block at a given timestamp is estimated like this.
- Balance
- Effects
- Library
- Fetching of native & tokens balances for both current and historical blocks
- Actions
- Fiat (Keeps track of the user's preffered FIAT currency and available spot prices between XTZ/FIAT)
Due to the nature of how the data is stored in the normalised store, we can now implement various selectors giving us access to the data in different perspectives. One example might include accessing pool liquidity / spot price for a token through balances of the given pool.
This means that we only store the pool's liquidity, and we can calculate the spot price on the go from the normalised data. This ensures data consistency between widgets which want to display the actual liquidity information vs widgets which are only interested in the price itself.
Please see issue #15 for more insight into balances vs liquidity of pools.
Each widget is responsible for ensuring freshness of its displayed data, since e.g. balances can change every block. The latest block is fetched at a higher level than widget data, ensuring that all widgets are aware of the latest block at the same time. Down the line, if a widget is requesting balance for an address at a certain block level, it will also automatically refetch the balance if the level changes (e.g. new block is available).
If you leave the application running for a while, this will lead to memory leaks, since we're storing data that theoretically no widget will display again (unless user's configuration changes to different historical views).
There is a built in data evicition mechanism in the normalised data store, that keeps track of data that is being either actively displayed, or fetched for the current latest block. This means that the app will persist only the last e.g. 3 block levels worth of data across all entities. Implementation of the eviction mechanism can be found in:
Prices for tokens are determined in $TOKEN/XTZ format through Quipuswap DEX. The XTZ/(EUR, USD) price is fetched from the public and free Coinbase API. Resulting $TOKEN/(EUR,USD) price is routed through the XTZ pair accordingly. All prices are calculated through spot prices.
The portfolio manager can't do much unless you connect a wallet. By default a prompt to connect your wallet is shown - this will lead you through the Beacon SDK wallet connection workflow, allowing you to connect with e.g. the Templte wallet.
As an alternative, you can specify which wallet you'd like to view the configured portfolio as using the query parameter ?address=tz1...
. This is useful since it allows you to use the Portfolio manager in a view only mode for any given tz1/KT1 address.
Example usage: https://localhost:1234/?address=tz1PWtaLXKiHXhXGvpuS8w4sVveNRKedTRSe
The centerpiece of the Portfolio Manager is the widget dashboard, it allows the user to compose their desired widgets on a drag & drop grid.
Widget store is a straightforward modal that shows a non-container version of all available widgets with some default configuration. Users can pick a widget from the store, and it will be automatically appended to the end of their dashboard configuration. You can find the available widget configuration here.
Widgets are displayed based on the layout configuration. Users can drag & drop or re-arrange widgets to their liking and the configuration will be persisted accordingly. You can find the implementation of the layout handling here.
Additionally, the widget layout can be exported to a JSON file. This JSON file can be used to import settings to the portfolio manager as well. You can find all persistable settings here.
Here's an example of the JSON settings export:
{
"widgetLayout": {
"layout": [
{
"w": 6,
"h": 6,
"x": 0,
"y": 3,
"i": "f52534ad-62a7-432d-9c26-bf09ea908e65",
"moved": false,
"static": false
}
],
"widgets": [
{
"name": "TokenBalanceChartWidget",
"id": "f52534ad-62a7-432d-9c26-bf09ea908e65",
"settings": { "token": "24975299837953", "historicalPeriod": "7d" }
}
]
},
"currency": "USD"
}
Since the dashboard can be configured to display any given number of widgets with various configuration, we can run into cases where the widgets may request duplicate data. The current implementation includes a simple de-douping for data requests made from widgets, in order to decrease the network load and increase the dashboard performance.
It works by gathering all data request actions from the widgets and filtering them by uniqueness, before actually dispatching them to our normalised store for processing.
You can find the implementation of the DispatchUniqueProvider
here.
- Dashboard pages to group various dashboard configurations (e.g. by DeFi project)
- Widgets:
- Delegation info
- Token price
- Pool liquidity (quipuswap)
- Tezos domains
- Token list
- Operation list (+ mempool support using Better Call Dev)
- ... is there something you'd like to add? Please open an issue.
If you'd like to contribute by fixing bugs, adding new features or simply by reporting an existing bug - please open an issue in this repository.