Skip to main content
Version: Firesquid

Client example

As mentioned before, any client wanting to aggregate multi-chain data available via the Giant Squid API, needs to perform two tasks:

  • Encode the address with the codec appropriate to the chain in question (if the objective is to monitor an account's activity across multiple chains)
  • Generate a query including all the chains interested by the exploration
  • Perform the request and process the result by aggregating the resulting data

The simple client example we are going to build is a Node.js simple project, with only two dependencies. Let's start by initializing the project and installing necessary dependencies, by opening a console window, heading to the directory where we want to create our project and launching these two commands:

npm init
npm i @subsquid/ss58 graphql-request

The first dependency is one of Subsquid's library, specifically used for address encoding (the subject of next section) and the second one is a simple library to perform GraphQL requests, it represent the "actual" client and can be substituted, depending on personal preferences.

Address encoding

In this guide we are going to take a look at an example that does exactly this. For the purpose of this guide, we are using this address as the subject of our research:

YFbLqqwvegzXpE65mGAPSxe2VQaL2u8ApuDT7KMWTSND8Hk

This is the address of a nominator, and it is in the format known as "Any network". As such, it is possible to search it in Polkadot's Subscan, for example, and it will be evident that the address reported in the result is different.

pos.dog account on Polkadot's Subscan

This is because it gets encoded with Polkadot's own codec. If we were to search it on Kusama's Subscan we'd still be able to find it, but the reported address is different:

pos.dog account on Kusama's Subscan

As mentioned at the start, Subsquid's SDK provides a utility package for this, called ss58, which is what we are going to use in our client example:

import * as ss58 from "@subsquid/ss58";

const ANY_ADRESS = "YFbLqqwvegzXpE65mGAPSxe2VQaL2u8ApuDT7KMWTSND8Hk";

const chainNames = [
"kusama",
"polkadot",
"astar"
];

// Creating multichain Account entity
interface Account {
addressBase: Uint8Array;
adresses: Map<string, string>;
}

const account: Account = {
addressBase: ss58.decode(ANY_ADRESS).bytes,
adresses: new Map(),
};

// Creating codec for every chain
var chainCodecs: Map<string, ss58.Codec> = new Map();

chainNames.forEach((chainName) => {
try {
chainCodecs.set(chainName, ss58.codec(chainName));
} catch {
console.error(
`Can't find codec for name ${chainName}. Please specify its prefix manually`
);
process.exit(1);
}
});

// Getting account's addresses for every chain
chainCodecs.forEach((codec, chain) => {
account.adresses.set(chain, codec.encode(account.addressBase));
});

The above code accomplishes these tasks:

  • Compiles a list of blockchains we are interested in
  • Creates an Account interface and instance, containing the base address (the one discussed above) and an empty map
  • Then proceeds in creating a map, named chainCodecs that links a chain's name to its related encoding function, using the appropriate codec version.
  • Finally, using chainCodecs, it fills the Account instance's map with encoded addresses
caution

The only exception to everything explained above are Moonbeam and Moonriver networks, which have Ethereum-formatted Accound addresses, and as such, cannot be directly converted from the "Any network" format.

Generating the query

I has been mentioned in the Query page of this guide that when performing queries against the Giant Squid API, every chain is a separate query on its own.

For this reason, it may be convenient to programmatically build different sub-queries from a template and merging them into a single query object.

This is shown in the code snippet below, leveraging the code shown in the previous section

// Query sample for a single chain
// Latest 10 transfers from every chain
function chainTransfersQuery(chainName: string, address: string) {
return gql`
${chainName} {
accountById(id: "${address}") {
id
transfers(limit: 10, orderBy: transfer_blockNumber_DESC) {
direction
transfer {
fromId
toId
success
amount
timestamp
}
}
}
}
`;
}

// Use sample query to every chain
let query = "";
account.adresses.forEach((address, chain) => {
query += chainTransfersQuery(chain, address);
});

// Wrap final query to gql query syntax
const finalQuery = gql`query AccountTransfersQuery {${query}}`;

Perform request and collect result

The only thing left to do, for a client, although it might seem trivial is to perform the quest and collect the result.

Here is a code snippet that takes in the query generated in the previous section, performs the query request and writes the result in a json file.

import { request, gql, GraphQLClient } from "graphql-request";
import * as fs from "fs";

// Specify stitched squid endpoint
const GQL_ENDPOINT = "https://app.devsquid.net/squids/super-api/v2/graphql";

const graphQLClient = new GraphQLClient(GQL_ENDPOINT);
graphQLClient
.request(finalQuery)
.then((res) =>
fs.writeFileSync("result.json", JSON.stringify(res, null, 2))
);

And here is the resulting JSON:

Conclusion

This client example is purely for demonstrative purposes, most likely a frontend application implementation would look slightly differently, but the base principles shown in this guide like address encoding and query generation should still apply.

You can look at the complete code example here: