blockfrost-crystal
A Crystal SDK for the Blockfrost.io API.
Getting started • Installation • Usage
Getting started
To use this SDK, you first need to log in to blockfrost.io to create your project to retrieve your API key.
Installation
- Add the dependency to your
shard.yml
:
dependencies:
blockfrost:
github: blockfrost/blockfrost-crystal
- Run
shards install
Usage
Every endpoint of the Blockfrost API is covered by this library. It's too much to list them all here, but below are a few examples of their usage.
Configuration
require "blockfrost"
Create an initializer to configure the global API key(s):
# e.g. config/blockfrost.cr
Blockfrost.configure do |config|
config.cardano_api_key = ENV.fetch("BLOCKFROST_CARDANO_API_KEY")
config.ipfs_api_key = ENV.fetch("BLOCKFROST_IPFS_API_KEY")
end
There are several configuration options available. Here's an overview with some added information:
Blockfrost.configure do |config|
# an API KEY starting with "testnet", "preview", "preprod" or "mainnet"
config.cardano_api_key = ENV.fetch("BLOCKFROST_CARDANO_API_KEY")
# the api version of the Cardano enpoints (currently only "v0")
config.cardano_api_version = "v0"
# an API KEY starting with "ipfs"
config.ipfs_api_key = ENV.fetch("BLOCKFROST_IPFS_API_KEY")
# the api version of the IPFS enpoints (currently only "v0")
config.ipfs_api_version = "v0"
# Blockfrost::QueryOrder::ASC or Blockfrost::QueryOrder::DESC
config.default_order = Blockfrost::QueryOrder::DESC
# default count per page in collection endpoints (min: 1; max: 100; default: 100)
config.default_count_per_page = 42
# number of times to retry in concurrent requests (min: 0; max: 10; default: 5)
config.retries_in_concurrent_requests = 5
# sleep between retries in concurrent requests (in ms; min: 0; default: 500)
config.sleep_between_retries_ms = 1000
end
To use one or more different configuration values locally in your code, use the
temp_config
method with a block:
# any code here will use the global configuration
Blockfrost.temp_config(cardano_api_key: "preprodAbC...xYz") do
# this code will use the "preprodAbC...xYz" api key
end
# any code following here will use the global configuration again
Single resources
Get the latest block:
block = Blockfrost::Block.latest
Or a specific block:
block_hash = "4ea1ba291e8eef538635a53e59fddba7810d1679631cc3aed7c8e6c4091a516a"
block = Blockfrost::Block.get(block_hash)
Nested resources
To get the transaction ids of a loaded block:
block.tx_ids
The same can be done in one go as well:
Blockfrost::Block.tx_ids(block_hash)
This pattern is used throughout the library. There will always be a class method and a corresponding instance method for nested resources.
Some nested resources have an additional scope parameter, like the utxos of an asset of an address:
address = "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq..."
asset = "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e"
utxos = Blockfrost::Address.utxos_of_asset(address, asset)
Collections, ordering and pagination
Get all assets:
assets = Blockfrost::Asset.all
Almost all collection methods accept three arguments for ordering and pagination:
assets = Blockfrost::Asset.all(
order: "desc",
count: 20,
page: 1
)
NOTE: The count
parameter should be a value between 1
and 100
. Lower
or higher values will be coerced to fit within that range.
The order
parameter is converted to an enum in the background, so the
underlying enum values are also accepted:
assets = Blockfrost::Asset.all(
order: Blockfrost::QueryOrder::DESC,
count: 20,
page: 1
)
NOTE: Using the enum values is considered the safer option. If a string with
a typo is passed (e.g. "decs"), the default_order
from the settings will be
used, whereas passing an enum with a typo will fail to compile. It's a choice
of security over convenience.
Some endpoints don't have an order parameter, like .previous
/next
on blocks:
block_height = 15243592
Blockfrost::Block.get(block_height).next(count: 5, page: 2)
The transactions
method for addresses exceptionally accepts two additional
arguments:
address = "addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq..."
Blockfrost::Address.transactions(
address,
order: "desc",
count: 10,
page: 1,
from: "8929261",
to: "9999269:10"
)
Ordering and count per page can also be configured with the following two settings:
Blockfrost.configure do |config|
# Blockfrost::QueryOrder::ASC or Blockfrost::QueryOrder::DESC
config.default_order = Blockfrost::QueryOrder::DESC
# minimum 1 and maximum 100
config.default_count_per_page = 42
end
Concurrency for large collections
Every method accepting pagination parameters will also have a method overload
accepting a pages : Range
argument instead of page : Int32
:
assets = Blockfrost::Asset.all(pages: 1..10)
assets.size
# => 1000
In the background, this method will make concurrent requests fetching 100 records for every single page number in the range. Then those results are concatenated into one big array and returned as the result.
Except for count
and page
, all other arguments are also still accepted. So
the results can also be ordered:
assets = Blockfrost::Asset.all(1..10, "asc")
Or with nested resources:
pool_id = "pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy"
events = Blockfrost::Pool.history(pool_id, pages: 1..5)
It also handles all possible exceptions of the Blockfrost API. If your account
is temporarily rate-limited (Blockfrost::Client::OverLimitException
), it will
retry 10 times and raise anyway after that. All other exceptions will be raised
immediately.
There are two settings related to rate-limiting:
Blockfrost.configure do |config|
# minimum 0, maximum 10; defaults to 10
config.retries_in_concurrent_requests = 5
# minimum 0, no maximum; defaults to 500
config.validate_sleep_between_retries_ms = 1000
end
Here's how fetching sequentially vs concurrently compares in terms of loading times:
Description | Elapsed | |
---|---|---|
1 page | 100 assets | 187ms |
10 pages sequentially | 1000 assets | 1s 873ms |
10 pages concurrently | 1000 assets | 265ms |
100 pages concurrently | 10000 assets | 427ms |
(tested on a 1 Gb home connection from Spain)
Post endpoints
Submit an already serialized transaction to the network:
tx_data = File.open("path/to/cbor_data")
Blockfrost::Transaction.submit(tx_data)
# => "d1662b24fa9fe985fc2dce47455df399cb2e31e1e1819339e885801cc3578908"
IPFS endpoints
Add an object to IPFS:
object = Blockfrost::IPFS.add("path/to/file")
Pin an object to local storage:
result = object.pin
result.state
# => Blockfrost::IPFS::Pin::State::Queued
Or alternatively (and the same):
Blockfrost::IPFS::Pin.add(object.ipfs_hash)
To get all pinned objects:
Blockfrost::IPFS::Pin.all
Finally, to remove a pin:
Blockfrost::IPFS::Pin.remove(ipfs_hash)
As expected the instance method is also available on a pin:
pin = Blockfrost::IPFS::Pin.get(ipfs_hash)
result = pin.remove
result.state
# => Blockfrost::IPFS::Pin::State::Unpinned
Network selection
The Cardano network is selected based on the API key. If the configured API key starts with preprod...
, then the preprod network will be used.
There are a few helper methods available to verify which network is selected. For example, to get the current network:
Blockfrost.configure do |config|
config.cardano_api_key = "preprodsSDBoik1wn1NxxhB8GB0Bcv7LuarFAKE"
end
Blockfrost.cardano_network
# => "preprod"
Additionally, there are also methods to test against the current network:
Blockfrost.cardano_mainnet?
# => false
Blockfrost.cardano_preprod?
# => true
Blockfrost.temp_config(cardano_api_key: "mainnetsSDBoik1wn1NxxhB8GB0Bcv7LuarFAKE") do
Blockfrost.cardano_mainnet?
# => true
end
Blockfrost.cardano_mainnet?
# => false
Exception handling
All exceptions from the Blockfrost API can be caught with:
begin
# do something
rescue e : Blockfrost::RequestException
puts e.message
end
Or with more specificity:
begin
# do something
rescue e : Blockfrost::Client::BadRequestException
puts "Bad request (400)"
rescue e : Blockfrost::Client::ForbiddenException
puts "Authentication secret is missing or invalid (403)"
rescue e : Blockfrost::Client::NotFoundException
puts "Component not found (404)"
rescue e : Blockfrost::Client::IpBannedException
puts "IP has been auto-banned for extensive sending of requests after usage limit has been reached (418)"
rescue e : Blockfrost::Client::OverLimitException
puts "Usage limit reached (429)"
rescue e : Blockfrost::Client::ServerErrorException
puts "Internal Server Error (500)"
end
Documentation
Development
Make sure you have Guardian.cr installed. Then run:
$ guardian
This will automatically:
- run ameba for src and spec files
- run the relevant spec for any file in the src dir
- run a spec file whenever they are saved
- install shards whenever you save shard.yml
Contributing
- Fork it (https://github.com/blockfrost/blockfrost-crystal/fork)
- Create your feature branch (
git checkout -b my-new-feature
) - Test your changes (
crystal spec
,crystal tool format
andbin/ameba
) - Commit your changes (
git commit -am 'feat: something new'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
NOTE: Please have a look at conventional commits for commit messages.
Contributors
- Wout - creator and maintainer