{"id":15713649,"url":"https://github.com/blockfrost/blockfrost-crystal","last_synced_at":"2025-05-12T22:55:40.347Z","repository":{"id":103813530,"uuid":"562151280","full_name":"blockfrost/blockfrost-crystal","owner":"blockfrost","description":"A Crystal SDK for the Blockfrost.io API","archived":false,"fork":false,"pushed_at":"2024-04-19T17:36:48.000Z","size":542,"stargazers_count":3,"open_issues_count":1,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-05-12T22:55:34.124Z","etag":null,"topics":["api","api-client","api-rest","blockchain","blockfrost","cardano","cardano-addresses","cardano-client","cardano-crystal-client","crystal","crystal-lang","ipfs","json-api","nutlink"],"latest_commit_sha":null,"homepage":"","language":"Crystal","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/blockfrost.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-05T13:19:29.000Z","updated_at":"2025-04-22T20:35:20.000Z","dependencies_parsed_at":null,"dependency_job_id":"463e142d-05f2-4d39-8f87-cedca6962185","html_url":"https://github.com/blockfrost/blockfrost-crystal","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blockfrost%2Fblockfrost-crystal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blockfrost%2Fblockfrost-crystal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blockfrost%2Fblockfrost-crystal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blockfrost%2Fblockfrost-crystal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/blockfrost","download_url":"https://codeload.github.com/blockfrost/blockfrost-crystal/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253837400,"owners_count":21971982,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","api-client","api-rest","blockchain","blockfrost","cardano","cardano-addresses","cardano-client","cardano-crystal-client","crystal","crystal-lang","ipfs","json-api","nutlink"],"created_at":"2024-10-03T21:32:38.762Z","updated_at":"2025-05-12T22:55:40.326Z","avatar_url":"https://github.com/blockfrost.png","language":"Crystal","funding_links":[],"categories":[],"sub_categories":[],"readme":"![GitHub](https://img.shields.io/github/license/blockfrost/blockfrost-crystal)\n![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/blockfrost/blockfrost-crystal)\n![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/blockfrost/blockfrost-crystal/ci.yml?branch=main)\n\u003ca href=\"https://fivebinaries.com/\"\u003e\u003cimg src=\"https://img.shields.io/badge/made%20by-Five%20Binaries-darkviolet.svg?style=flat-square\" /\u003e\u003c/a\u003e\n\n\u003cimg src=\"https://blockfrost.io/images/logo.svg\"\n       width=\"250\"\n       align=\"right\"\n       height=\"90\"\u003e\n\n# blockfrost-crystal\n\n\u003cbr\u003e\n\n\u003cp align=\"center\"\u003eA Crystal SDK for the Blockfrost.io API.\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#getting-started\"\u003eGetting started\u003c/a\u003e •\n  \u003ca href=\"#installation\"\u003eInstallation\u003c/a\u003e •\n  \u003ca href=\"#usage\"\u003eUsage\u003c/a\u003e\n\u003c/p\u003e\n\u003cbr\u003e\n\n## Getting started\n\nTo use this SDK, you first need to log in to\n[blockfrost.io](https://blockfrost.io) to create your project to retrieve your\nAPI key.\n\n\u003cimg src=\"https://i.imgur.com/smY12ro.png\"\u003e\n\n\u003cbr\u003e\n\n## Installation\n\n1. Add the dependency to your `shard.yml`:\n\n  ```yaml\n  dependencies:\n    blockfrost:\n      github: blockfrost/blockfrost-crystal\n  ```\n\n2. Run `shards install`\n\n## Usage\n\nEvery endpoint of the [Blockfrost API](https://docs.blockfrost.io/) is covered\nby this library. It's too much to list them all here, but below are a few\nexamples of their usage.\n\n### Configuration\n\n```crystal\nrequire \"blockfrost\"\n```\n\nCreate an initializer to configure the global API key(s):\n\n```crystal\n# e.g. config/blockfrost.cr\nBlockfrost.configure do |config|\n  config.cardano_api_key = ENV.fetch(\"BLOCKFROST_CARDANO_API_KEY\")\n  config.ipfs_api_key = ENV.fetch(\"BLOCKFROST_IPFS_API_KEY\")\nend\n```\n\nThere are several configuration options available. Here's an overview with\nsome added information:\n\n```crystal\nBlockfrost.configure do |config|\n  # an API KEY starting with \"testnet\", \"preview\", \"preprod\" or \"mainnet\"\n  config.cardano_api_key = ENV.fetch(\"BLOCKFROST_CARDANO_API_KEY\")\n\n  # the api version of the Cardano enpoints (currently only \"v0\") \n  config.cardano_api_version = \"v0\"\n\n  # an API KEY starting with \"ipfs\"\n  config.ipfs_api_key = ENV.fetch(\"BLOCKFROST_IPFS_API_KEY\")\n\n  # the api version of the IPFS enpoints (currently only \"v0\") \n  config.ipfs_api_version = \"v0\"\n\n  # Blockfrost::QueryOrder::ASC or Blockfrost::QueryOrder::DESC\n  config.default_order = Blockfrost::QueryOrder::DESC\n\n  # default count per page in collection endpoints (min: 1; max: 100; default: 100)\n  config.default_count_per_page = 42\n\n  # number of times to retry in concurrent requests (min: 0; max: 10; default: 5)\n  config.retries_in_concurrent_requests = 5\n\n  # sleep between retries in concurrent requests (in ms; min: 0; default: 500)\n  config.sleep_between_retries_ms = 1000\nend\n```\n\nTo use one or more different configuration values locally in your code, use the\n`temp_config` method with a block:\n \n```crystal\n# any code here will use the global configuration\n\nBlockfrost.temp_config(cardano_api_key: \"preprodAbC...xYz\") do\n  # this code will use the \"preprodAbC...xYz\" api key\nend\n\n# any code following here will use the global configuration again\n```\n\n### Single resources\n\nGet the latest block:\n\n```crystal\nblock = Blockfrost::Block.latest\n```\n\nOr a specific block:\n\n```crystal\nblock_hash = \"4ea1ba291e8eef538635a53e59fddba7810d1679631cc3aed7c8e6c4091a516a\"\nblock = Blockfrost::Block.get(block_hash)\n```\n\n### Nested resources\n\nTo get the transaction ids of a loaded block:\n\n```crystal\nblock.tx_ids\n```\n\nThe same can be done in one go as well:\n\n```crystal\nBlockfrost::Block.tx_ids(block_hash)\n```\n\nThis pattern is used throughout the library. There will always be a class method\nand a corresponding instance method for nested resources.\n\nSome nested resources have an additional scope parameter, like the utxos of an\nasset of an address:\n\n```crystal\naddress = \"addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq...\"\nasset = \"b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a76e7574636f696e\"\nutxos = Blockfrost::Address.utxos_of_asset(address, asset)\n```\n\n### Collections, ordering and pagination\n\nGet all assets:\n\n```crystal\nassets = Blockfrost::Asset.all\n```\n\nAlmost all collection methods accept three arguments for ordering and\npagination:\n\n```crystal\nassets = Blockfrost::Asset.all(\n  order: \"desc\",\n  count: 20,\n  page: 1\n)\n```\n\n**NOTE**: *The `count` parameter should be a value between `1` and `100`. Lower\nor higher values will be coerced to fit within that range.*\n\nThe `order` parameter is converted to an enum in the background, so the \nunderlying enum values are also accepted:\n\n```crystal\nassets = Blockfrost::Asset.all(\n  order: Blockfrost::QueryOrder::DESC,\n  count: 20,\n  page: 1\n)\n```\n\n**NOTE**: *Using the enum values is considered the safer option. If a string with\na typo is passed (e.g. \"decs\"), the `default_order` from the settings will be\nused, whereas passing an enum with a typo will fail to compile. It's a choice\nof security over convenience.*\n\nSome endpoints don't have an order parameter, like `.previous`/`next` on blocks:\n\n```crystal\nblock_height = 15243592\nBlockfrost::Block.get(block_height).next(count: 5, page: 2)\n```\n\nThe `transactions` method for addresses exceptionally accepts two additional\narguments:\n\n```crystal\naddress = \"addr1qxqs59lphg8g6qndelq8xwqn60ag3aeyfcp33c2kdp46a09re5df3pzwwmyq...\"\nBlockfrost::Address.transactions(\n  address,\n  order: \"desc\",\n  count: 10,\n  page: 1,\n  from: \"8929261\",\n  to: \"9999269:10\"\n)\n```\n\nOrdering and count per page can also be configured with the following two\nsettings:\n\n```crystal\nBlockfrost.configure do |config|\n  # Blockfrost::QueryOrder::ASC or Blockfrost::QueryOrder::DESC\n  config.default_order = Blockfrost::QueryOrder::DESC\n\n  # minimum 1 and maximum 100\n  config.default_count_per_page = 42\nend\n```\n\n### Concurrency for large collections\n\nEvery method accepting pagination parameters will also have a method overload\naccepting a `pages : Range` argument instead of `page : Int32`:\n\n```crystal\nassets = Blockfrost::Asset.all(pages: 1..10)\nassets.size\n# =\u003e 1000\n```\n\nIn the background, this method will make concurrent requests fetching 100\nrecords for every single page number in the range. Then those results are\nconcatenated into one big array and returned as the result.\n\nExcept for `count` and `page`, all other arguments are also still accepted. So\nthe results can also be ordered:\n\n```crystal\nassets = Blockfrost::Asset.all(1..10, \"asc\")\n```\n\nOr with nested resources:\n\n```crystal\npool_id = \"pool1pu5jlj4q9w9jlxeu370a3c9myx47md5j5m2str0naunn2q3lkdy\"\nevents = Blockfrost::Pool.history(pool_id, pages: 1..5)\n```\n\nIt also handles all possible exceptions of the Blockfrost API. If your account\nis temporarily rate-limited (`Blockfrost::Client::OverLimitException`), it will\nretry 10 times and raise anyway after that. All other exceptions will be raised\nimmediately.\n\nThere are two settings related to rate-limiting:\n\n```crystal\nBlockfrost.configure do |config|\n  # minimum 0, maximum 10; defaults to 10\n  config.retries_in_concurrent_requests = 5\n\n  # minimum 0, no maximum; defaults to 500\n  config.validate_sleep_between_retries_ms = 1000\nend\n```\n\nHere's how fetching sequentially vs concurrently compares in terms of loading\ntimes:\n\n| Description            |              | Elapsed  |\n| :--------------------- | ------------ | -------: |\n| 1 page                 |   100 assets |    187ms |\n| 10 pages sequentially  |  1000 assets | 1s 873ms |\n| 10 pages concurrently  |  1000 assets |    265ms |\n| 100 pages concurrently | 10000 assets |    427ms |\n\n_(tested on a 1 Gb home connection from Spain)_\n\n### Post endpoints\n\nSubmit an already serialized transaction to the network:\n\n```crystal\ntx_data = File.open(\"path/to/cbor_data\")\nBlockfrost::Transaction.submit(tx_data)\n# =\u003e \"d1662b24fa9fe985fc2dce47455df399cb2e31e1e1819339e885801cc3578908\"\n```\n\n### IPFS endpoints\n\nAdd an object to IPFS:\n\n```crystal\nobject = Blockfrost::IPFS.add(\"path/to/file\")\n```\n\nPin an object to local storage:\n\n```crystal\nresult = object.pin\nresult.state\n# =\u003e Blockfrost::IPFS::Pin::State::Queued\n```\n\nOr alternatively (and the same):\n\n```crystal\nBlockfrost::IPFS::Pin.add(object.ipfs_hash)\n```\n\nTo get all pinned objects:\n\n```crystal\nBlockfrost::IPFS::Pin.all\n```\n\nFinally, to remove a pin:\n\n```crystal\nBlockfrost::IPFS::Pin.remove(ipfs_hash)\n```\n\nAs expected the instance method is also available on a pin:\n\n```crystal\npin = Blockfrost::IPFS::Pin.get(ipfs_hash)\nresult = pin.remove\nresult.state\n# =\u003e Blockfrost::IPFS::Pin::State::Unpinned\n```\n\n### Network selection\n\nThe Cardano network is selected based on the API key. If the configured API key starts with `preprod...`, then the **preprod** network will be used.\n\nThere are a few helper methods available to verify which network is selected.\nFor example, to get the current network:\n\n```crystal\nBlockfrost.configure do |config|\n  config.cardano_api_key = \"preprodsSDBoik1wn1NxxhB8GB0Bcv7LuarFAKE\"\nend\n\nBlockfrost.cardano_network\n# =\u003e \"preprod\"\n```\n\nAdditionally, there are also methods to test against the current network:\n\n```crystal\nBlockfrost.cardano_mainnet?\n# =\u003e false\nBlockfrost.cardano_preprod?\n# =\u003e true\n\nBlockfrost.temp_config(cardano_api_key: \"mainnetsSDBoik1wn1NxxhB8GB0Bcv7LuarFAKE\") do\n  Blockfrost.cardano_mainnet?\n  # =\u003e true\nend\n\nBlockfrost.cardano_mainnet?\n# =\u003e false\n```\n\n### Exception handling\n\nAll exceptions from the Blockfrost API can be caught with:\n\n```crystal\nbegin\n  # do something\nrescue e : Blockfrost::RequestException\n  puts e.message\nend\n```\n\nOr with more specificity:\n\n```crystal\nbegin\n  # do something\nrescue e : Blockfrost::Client::BadRequestException\n  puts \"Bad request (400)\"\nrescue e : Blockfrost::Client::ForbiddenException\n  puts \"Authentication secret is missing or invalid (403)\"\nrescue e : Blockfrost::Client::NotFoundException\n  puts \"Component not found (404)\"\nrescue e : Blockfrost::Client::IpBannedException\n  puts \"IP has been auto-banned for extensive sending of requests after usage limit has been reached (418)\"\nrescue e : Blockfrost::Client::OverLimitException\n  puts \"Usage limit reached (429)\"\nrescue e : Blockfrost::Client::ServerErrorException\n  puts \"Internal Server Error (500)\"\nend\n```\n\n## Documentation\n\n- [API (main)](https://blockfrost.github.io/blockfrost-crystal/)\n- [Blockfrost API](https://docs.blockfrost.io/)\n\n## Development\n\nMake sure you have [Guardian.cr](https://github.com/f/guardian) installed. Then\nrun:\n\n```bash\n$ guardian\n```\n\nThis will automatically:\n- run ameba for src and spec files\n- run the relevant spec for any file in the src dir\n- run a spec file whenever they are saved\n- install shards whenever you save shard.yml\n\n## Contributing\n\n1. Fork it (\u003chttps://github.com/blockfrost/blockfrost-crystal/fork\u003e)\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Test your changes (`crystal spec`, `crystal tool format` and `bin/ameba`)\n4. Commit your changes (`git commit -am 'feat: something new'`)\n5. Push to the branch (`git push origin my-new-feature`)\n6. Create a new Pull Request\n\n**NOTE**: *Please have a look at\n[conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) for\ncommit messages.*\n\n## Contributors\n\n- [Wout](https://github.com/wout) - creator and maintainer\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblockfrost%2Fblockfrost-crystal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fblockfrost%2Fblockfrost-crystal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblockfrost%2Fblockfrost-crystal/lists"}