Coda is a cryptocurrency that uses a succinct blockchain that is enabled through the recursive composition of zk-SNARKs. For clients, this means they can verify the chain with only a few kB of data which opens up exciting opportunities for scalable blockchain applications.
Coda recently launched their public testnet and encourages users who want to help develop the protocol to join the testnet and complete various weekly challenges. While Coda has a traditional command-line interface (CLI) that will be familiar to anyone running a full node implementation, e.g. Bitcoin Core, it also exposes a GraphQL API that we will explore in this article.
GraphQL, which is an open-source query language for APIs, is relatively new being publicly released in 2015 and was developed by Facebook to resolve some common issues with more traditional REST APIs. Now hosted under the umbrella of the GraphQL Foundation (part of the Linux Foundation) many organisations, notably including Github, Gitlab, Shopify, Facebook, and many more all offer a GraphQL API.
While REST APIs still dominate, with GraphQL, clients can obtain all of the data they need in one request and reduces the data returned to only what is required. Let’s illustrate these concepts through an example of retrieving block and transaction data from the Bitcoin blockchain. We’ll use the BitcoinChain API, though the exact implementation is largely irrelevant in exploring the concepts.
We can retrieve the latest block via https://api-r.bitcoinchain.com/v1/blocks/1/withTx
which returns the following response containing the full block data as well as all the transactions contained in the block (only three shown in the example below for brevity).
{
"hash": "0000000000000000001592abc23acc699f54145b5794f56cf60f63f7e412ba2c",
"height": 591791,
"prev_block": "0000000000000000000d8dd10509cbfbd778d4ddb359bee4f4700178aff7f015",
"next_block": "",
"mrkl_root": "b1700f7e82d199fb8102e88988dae3e5570c7d3731b3864c3131963104613f8b",
"tx": [
"e4ea88cf747e9cd3a848a9b933f8b231cdd6b2fbaf950f2b6a202aaefd7c5ea8",
"09c33e084f7d6fcf2fdc94cb0a80681e82378a0afeeca8563a350c0a5ee2b9f9",
"c7a3c795142bec4b7b3049a1e3af95adcf20c4490fd2ae631ba63ce7831a0d19",
],
"tx_count": 2748,
"reward": "12.50000000",
"fee": "0.22152797",
"vout_sum": "7230.03209165",
"is_main": true,
"version": "1073676288",
"difficulty": "10183488432890.000000",
"size": 1150937,
"bits": "387687377",
"nonce": "1158314858",
"time": 1566791311
}
Let’s say for our sample application, we are merely interested in the hash
and height
of the block. In this case, we are discarding ~99% of the data received, which is not applicable to our use-case. This is an example of overfetching, where we are receiving more data then we require. In GraphQL, we construct the query to only contain the data we want to be returned. An overly simplified example (in reality we would likely pass in arguments such as first to specify how many results we want to return) of the same request would be:
query {
blocks {
hash
height
}
}
which would return the following JSON:
{
"data": {
"blocks": {
"hash": "0000000000000000001592abc23acc699f54145b5794f56cf60f63f7e412ba2c",
"height": 591791
}
}
}
Conversely, let’s say we wanted all the transaction information for the latest block from the REST API. In this case, we’d need to construct multiple further queries for each transaction using the transaction id to get the required data. As we don’t have all the data we require in our API response, this is known as underfetching. GraphQL solves this problem with the ability to nest our queries, and when it is executed, it can traverse related objects so we can make one request and get two types of data (in this case blocks and transaction data). Using GraphQL, such a query may look like the below:
query {
blocks {
hash
height
transactions {
transaction_id
block_time
inputs {
value
sender
}
outputs {
value
receiver
}
}
}
}
Whenever a query is executed against a GraphQL server, it is validated against a type system defined by a schema. Coda’s GraphQL schema is published here which defines the objects and types permitted by the API, a number of which we will explore in the remainder of this article. Note that the Coda GraphQL API is in active development and liable to change.
Initial setup of Coda GraphQL API
We’ll first need to install Coda for the current active testnet. Once installed the GraphQL API is enabled via passing the -rest-port
flag to the coda daemon
startup command, specifying the port to expose, e.g. the following exposes the GraphQL API on port 8000.
coda daemon -rest-port 8000
Note that the GraphQL API is bound to localhost
for security. The API is unauthenticated, and as we will demonstrate later with the sendPayment
mutation, anyone with access can send payments from a wallet known to the daemon. You can disable this security feature with the -insecure-rest-server
option which will listen on all interfaces and not just localhost
. If you are running Coda on a remote host the best way is to use SSH port forwarding to forward a local port on your machine to the GraphQL port on the remote machine e.g.
ssh -L 8000:127.0.0.1:8000 user@remotehost
Finally, if you want to work with an existing wallet, you’ll need to import it with a temporary workaround that copies it and strips it of its password. You can also alternatively, as we will demonstrate, create a new wallet via the GraphQL API.
coda daemon -propose-key /keys/my-private-key -unsafe-track-propose-key -rest-port 8000
Now you should be able to point your browser to localhost:8000/graphql
and can explore the GraphQL API via the GraphiQL tool, an in-browser IDE for explorer GraphQL APIs.
On the right-hand side, you will see the Documentation explorer that lists the different types available to use. At the root level, GraphQL supports query, mutation and subscription types. Let’s start with some GraphQL queries.
Querying the Coda GraphQL API
Clicking on the query type in the interactive documentation displays a list of fields, the first being syncStatus
which returns the “Network sync status”. Enter the following into the left panel and execute the query:
query {
syncStatus
}
The return value SYNCED
is defined as one of the following values. You can use the GraphiQL IDE to explore and test the various queries available. In this example, there was a single return value, but now we’ll use a more advanced query to retrieve block information that requires specifying the exact data we require via nesting.
Blocks data
In Coda, a node by default will only store information about blocks it has seen (unless it is running as an archive node), so before running these queries leave your node running for a while before executing these queries to get some meaningful results.
According to the documentation, the blocks
object accepts the before
, last
, after
, first
and filter
arguments to specify which blocks to return. All of these are optional (nullable) as indicated by the lack of a trailing !
. It returns a BlockConnection
object (required) which itself is comprised of various fields that we can explore by clicking through on the explorer documentation.
blocks(
before: String
last: Int
after: String
first: Int
filter: BlockFilterInput): BlockConnection!
First, let’s get the totalCount
of blocks known to this node:
query {
blocks {
totalCount
}
}
Which returns:
{
"data": {
"blocks": {
"totalCount": 236
}
}
}
We can also filter the blocks to only contain blocks that contain a public key whose transaction is in a block or produced this block. Applying this to my staking node and getting the first result:
query {
blocks(first: 1, filter: {relatedTo: "tNciEHZuC9SdpdKjG3cLw8dpdKUdXpbrqkhctcE422NyspG8G3ddArHJupGvV591mRKd6hEMBoPNPNRZMSjyN1S8Jp4jD8zMyHSzvsFhzKhqa7Q3F2a3uNmNUHp2rtVsj3HuLt9Zw3whHM"}) {
totalCount
nodes {
creator
stateHash
transactions {
coinbase
userCommands {
id
nonce
from
to
amount
fee
}
}
}
}
}
This returns the following response. In this instance, there were no transactions in the block, but the public key produced the block. Note, I have specified the fields I am interested in returning, but there are many more available as demonstrated in the schema.
{
"data": {
"blocks": {
"totalCount": 17,
"nodes": [
{
"creator": "tNciEHZuC9SdpdKjG3cLw8dpdKUdXpbrqkhctcE422NyspG8G3ddArHJupGvV591mRKd6hEMBoPNPNRZMSjyN1S8Jp4jD8zMyHSzvsFhzKhqa7Q3F2a3uNmNUHp2rtVsj3HuLt9Zw3whHM",
"stateHash": "4ApWKSeBhPyZDgYVFCYg6WzN1oGHpQxmdTfPbVs4LEd2JCxvwR3hzcVsjto45Kt58cpbciFxvt7ZoCq6HsM3iqZfpNh5p9DWXR8RHZFnVRczvPij7xeMYvBXcJ3kVzqkf9aufNS344VoR",
"transactions": {
"coinbase": "0",
"userCommands": []
}
}
]
}
}
}
Mutations, modifying data with the Coda GraphQL API
So far, we have explored some basic queries using the GraphQL API. GraphQL supports two additional root types of mutations and subscriptions. We won’t touch on subscriptions in this article, but they allow GraphQL clients to subscribe to published events. Coda’s GraphQL API provides two events newSyncUpdate
that triggers when the network sync status changes and newBlock
that triggers when a new block is created.
Mutations, as the name suggests, modify data in some way. Coda’s GraphQL API includes the following mutations:
addWallet
deleteWallet
sendPayment
sendDelegation
addPaymentReceipt
setStaking
Let’s list any existing wallets known to the daemon:
query {
ownedWallets{
publicKey,
balance {
total
unknown
}
nonce
}
}
If you didn’t import an existing wallet, likely this will be empty. Create a new one:
mutation {
addWallet{
publicKey
}
}
This will return the public key of your new wallet. Use this to request some Coda from the testnet faucet on Discord) or any other helpful Codans. Now retrying the ownedWallets
query should return the wallet and balance associated.
{
"data": {
"ownedWallets": [
{
"publicKey": "tNciDhKWNmKpoQ4jHvP4oqt7gnuQYaUYz8pMSE1wewiXjaozksHVL5YpkC4bSx3hqaZHH6cgxwLicrXAdddGyHx8eh3WJBZN6XzpAZdfYM861t53khP9v74Kj3CBxHCbfVPisD8ikNaBGE",
"balance": {
"total": "470",
"unknown": "470"
},
"nonce": "0"
}
]
}
}
Next, let’s send a payment specifying the from
address as your wallet with funds in it and a to
address (e.g. to the echo service) and the fee associated with the transaction, and we’ll return the full details of the transaction.
mutation {
sendPayment(input: {fee: 5, amount: 10, to: "tNciEHZuC9SdpdKjG3cLw8dpdKUdXpbrqkhctcE422NyspG8G3ddArHJupGvV591mRKd6hEMBoPNPNRZMSjyN1S8Jp4jD8zMyHSzvsFhzKhqa7Q3F2a3uNmNUHp2rtVsj3HuLt9Zw3whHM", from: "tNciDhKWNmKpoQ4jHvP4oqt7gnuQYaUYz8pMSE1wewiXjaozksHVL5YpkC4bSx3hqaZHH6cgxwLicrXAdddGyHx8eh3WJBZN6XzpAZdfYM861t53khP9v74Kj3CBxHCbfVPisD8ikNaBGE"}) {
payment {
id
isDelegation
nonce
from
to
amount
fee
memo
}
}
}
This produced the following response:
{
"data": {
"sendPayment": {
"payment": {
"id": "3XCgvAHM3twoJ2T8dcTvxpuyrnBJ12b7Y1HXYjAgVXVLHNPcr95VSaQ9eoGZTmq3LXh2rKvePPes58Zi4EJXjiFo77uo1meAMh3qty88RXmfrPjP1xQ58wm9oop499ZUgT5Fspbok7Yxg4eCFR8zRvd8bob7RdojY38DLDiHD9hrEmEXbZhAThZWS6sdxuHBpsimL4ZZJkvbQcs24MonG2Fd1MvbjNhfSE5sR7PGGjqAX4hbEb6hskjF8czVDnCqD3yLwGRFrL2tchCrZUHc6njci3294SsFMmbgAqwog6nBVQojE42sYgcXdPK7oPEFV2Com53QCSPPSfdiCeRNF5QhG5tEC1viwGtBLbKmF9qFR7PVg8UEJhBwsyu5LpLNwxzVEA4rdEy3syDZFrC4GfYmKdohQvBWv2DgVgUKwe7xRH5fh5UPx2f3GyV87pcxvdoq5BnZYDB6MkktsQxpeTk8zykrvXacHtYkZhMfLVLhgxW9EwEb6dfJTUMHaSp2F1uXFzwa6cJe8RdUt4EWQiVYgDUN8Hwai5AGLaXfn86Yjck3D6E5MiBDwhrcQBiKF6xFSzEFJGcnjTrFd5aMKE9KKqyyUuUjoj8YKz4woUXUBHbyaTBrJP2dVs8vaePyieueq5pFxtD9Lhqwx6f9ejUQnbz53HPzfsNzkDNA9n5NJimrgWDrZvkUmq8F2B3zj35Mzf4M9PFbjeWkUwAjRveRpBEy2oBWswX",
"isDelegation": false,
"nonce": 0,
"from": "tNciDhKWNmKpoQ4jHvP4oqt7gnuQYaUYz8pMSE1wewiXjaozksHVL5YpkC4bSx3hqaZHH6cgxwLicrXAdddGyHx8eh3WJBZN6XzpAZdfYM861t53khP9v74Kj3CBxHCbfVPisD8ikNaBGE",
"to": "tNciEHZuC9SdpdKjG3cLw8dpdKUdXpbrqkhctcE422NyspG8G3ddArHJupGvV591mRKd6hEMBoPNPNRZMSjyN1S8Jp4jD8zMyHSzvsFhzKhqa7Q3F2a3uNmNUHp2rtVsj3HuLt9Zw3whHM",
"amount": "10",
"fee": "5",
"memo": "2pmu64f2x97tNiDXMycnLwBSECDKbX77MTXVWVsG8hcRFsedhXDWWq"
}
}
}
}
The nonce value is used to prevent replaying transactions on the network. As this was our first transaction, the nonce was 0. If we repeat this transaction we’ll see the nonce has increased to 1.
{
"data": {
"sendPayment": {
"payment": {
"id": "3XCgvAHM3txTHrcrWcodUm1nkUVEQjUa8Q4WxkrwVueTvAvCM6NVehmxiVZuzMY3gV5cRanzk2A41vQp59sdGfAothQuoydrcW9s12NSgM9wqqgGtJidUq29b6qDmUh7N6RVkaJtynNuEtYgbeubWaYQfNvN3nruehRHnY6Xuk4uyuvqob4zGerMKS4Qpzq1nirb6GbWYCYouwz5fhZPmRtPuX3FDR5LtgSY1JqV8VrcHY7BRgb6qQJco83n1HcDZS1uQsxRTJEDzM4EvHCeg6qx7Wc6GeuDEDTMmkvZyP3jgvRXS3oLRhnXLDzR9EhoHu2pwLZGmwEBBYrSeoEY7sFeMTByRTbsaHZi69rJsrxBAHmpY8CY7WZypf9mxiz2aRfRffy5AGCEY74EsGpKbAYszBww6k5p6PUvnuk2PTnFdagdMeJ6Udm7qGDcRwA6FL9VixEazyMYtZSMNaFweyTo1zic5viusJfZiTAQZ6JnJ7R88uAbRiwZ7v5oweqLrkHi9gLcqF8ULwWTQwh84zW9ei8GGNneFHn4SY2c2AZK7NLPMtQhLXbtALG7CMN3V1u8NoFzCUCYx3omAP8qHiDCAjxPvjkPViYj9m3YeBEkukMzjW7BMQK52BxBKSbQC7xXbxm4x4dyCKW9BSkrvkz3a27ePbwouSESoHURDmSqGYUjd9uSaboVmkvdqpFdw43sd3dPqbEaXXDxvdgkGrUBXd24DzuriaF",
"isDelegation": false,
"nonce": 1,
"from": "tNciDhKWNmKpoQ4jHvP4oqt7gnuQYaUYz8pMSE1wewiXjaozksHVL5YpkC4bSx3hqaZHH6cgxwLicrXAdddGyHx8eh3WJBZN6XzpAZdfYM861t53khP9v74Kj3CBxHCbfVPisD8ikNaBGE",
"to": "tNciEHZuC9SdpdKjG3cLw8dpdKUdXpbrqkhctcE422NyspG8G3ddArHJupGvV591mRKd6hEMBoPNPNRZMSjyN1S8Jp4jD8zMyHSzvsFhzKhqa7Q3F2a3uNmNUHp2rtVsj3HuLt9Zw3whHM",
"amount": "10",
"fee": "5",
"memo": "2pmu64f2x97tNiDXMycnLwBSECDKbX77MTXVWVsG8hcRFsedhXDWWq"
}
}
}
}
Now finally, we’ll check the status of our transaction on the Coda network using the returned id
of the payment.
query {
transactionStatus(payment: "3XCgvAHM3txTHrcrWcodUm1nkUVEQjUa8Q4WxkrwVueTvAvCM6NVehmxiVZuzMY3gV5cRanzk2A41vQp59sdGfAothQuoydrcW9s12NSgM9wqqgGtJidUq29b6qDmUh7N6RVkaJtynNuEtYgbeubWaYQfNvN3nruehRHnY6Xuk4uyuvqob4zGerMKS4Qpzq1nirb6GbWYCYouwz5fhZPmRtPuX3FDR5LtgSY1JqV8VrcHY7BRgb6qQJco83n1HcDZS1uQsxRTJEDzM4EvHCeg6qx7Wc6GeuDEDTMmkvZyP3jgvRXS3oLRhnXLDzR9EhoHu2pwLZGmwEBBYrSeoEY7sFeMTByRTbsaHZi69rJsrxBAHmpY8CY7WZypf9mxiz2aRfRffy5AGCEY74EsGpKbAYszBww6k5p6PUvnuk2PTnFdagdMeJ6Udm7qGDcRwA6FL9VixEazyMYtZSMNaFweyTo1zic5viusJfZiTAQZ6JnJ7R88uAbRiwZ7v5oweqLrkHi9gLcqF8ULwWTQwh84zW9ei8GGNneFHn4SY2c2AZK7NLPMtQhLXbtALG7CMN3V1u8NoFzCUCYx3omAP8qHiDCAjxPvjkPViYj9m3YeBEkukMzjW7BMQK52BxBKSbQC7xXbxm4x4dyCKW9BSkrvkz3a27ePbwouSESoHURDmSqGYUjd9uSaboVmkvdqpFdw43sd3dPqbEaXXDxvdgkGrUBXd24DzuriaF")
}
This returns the transactionStatus
which in our case is PENDING
i.e. is waiting to be included in a block.
{
"data": {
"transactionStatus": "PENDING"
}
}
Next steps
We’ve only touched on a few queries available via the Coda GraphQL API, and there are many more to explore. While the API is in active development, it’s a great learning experience to interact with the Coda protocol this way. It offers some improvements over the existing CLI interface but more importantly allows the development of future tools such as block explorers, wallets etc…
You can also interact with the GraphQL API using multiple client libraries such as Apollo, building a status page for your own node, for example. Additionally, the excellent Postman client now supports GraphQL, which can be used to further experiment with and develop for the Coda GraphQL API.