# Creating a custom NEAR Account explorer While creating a webpage that shows information about a user account similar to how https://explorer.testnet.near.org/ does it, I went through some issues finding information. Here is the well packed compilation of my research. This is a project that does the following things: - You write the NEAR account you want to search. - It loads information about the account. NEAR balance, block hash, etc. - It loads recent transactions. With details about each. --- ## Index - [Connecting to NEAR](#Connecting-to-NEAR) - [Getting General Account Information](#Getting-General-Account-Information) - [Checking Transaction History](#Checking-Transaction-History) - [Using an Indexer](#Using-an-Indexer) - [How FTs/NFTs are listed](#How-FTsNFTs-Are-Listed) - [Contact Info](#Contact-Info) --- ## Source Code Source code for this project is available in [Github](https://github.com/On0n0k1/smart-contract-interaction). --- ## Connecting to NEAR (More details on [near-api-js Docs](https://docs.near.org/tools/near-api-js/quick-reference)) Before interacting with the blockchain, we must connect to it. First we need is a config object: ```javascript const config = { keyStore, networkId: network, nodeUrl: "https://rpc.testnet.near.org", walletUrl: "https://wallet.testnet.near.org", helperUrl: "https://helper.testnet.near.org", explorerUrl: "https://explorer.testnet.near.org", }; ``` **Wallet**, **Helper** and **Explorer** Urls are optional if we are not interacting with these networks. All we are going to do next is get information about a NEAR user account. So, only **nodeUrl** will be required next. We also need a keyStore. ```javascript const keyStore = new keyStores.BrowserLocalStorageKeyStore(); ``` Key Stores are used for signing transactions with the blockchain. There's no need for it if you're only querying view methods. BrowserLocalStorageKey is to be used in browsers. For other types of Keystores, check this [documentation link](https://docs.near.org/tools/near-api-js/quick-reference#key-store); Then we call ```connect```. ```javascript const near = await connect(config); ``` Like connecting to a database, this will return a connection instance. ## Getting General Account Information Now we can start querying. ```javascript const CONTRACT_ID = "Account-to-query.testnet"; let response = false; try{ response = await near.connection.provider.query({ request_type: "view_account", finality: "final", account_id: accountId, }); } catch (error) { console.log(JSON.stringify(error)); response = { error: true, exception: error, } } console.log(response); return response; ``` For querying the blockchain, we need a contract ID and the connection. It can cause errors, maybe the api is down (happens sometimes with testnet), maybe the accound ID doesn't exist. So we use try/catch for it. If the query is successfull, it will return a json object with information about the account, here is an example of successful response: ![](https://i.imgur.com/inpILEN.png) ## Checking Transaction History Each transaction associated with our account can be queried manually using NEAR connection. But it's not optimal for production. To retrieve several transactions in a single GET request, we use an indexer. ## Using an Indexer Here is the [source code](https://github.com/near/near-indexer-for-explorer) for a NEAR indexer. It's recommended to host your own private indexer. But, in development, we can use the public hosted one. The address for the public api is [this](https://github.com/near/near-indexer-for-explorer#shared-public-access): - **testnet credentials**: postgres://public_readonly:nearprotocol@testnet.db.explorer.indexer.near.dev/testnet_explorer - **mainnet credentials**: postgres://public_readonly:nearprotocol@mainnet.db.explorer.indexer.near.dev/mainnet_explorer In javascript, the configuration for a connection to the database is shown below. First, install Postgres dependency: ```bash npm i pg ``` We will now open a connection to the public testnet server. ```javascript // /lib/db.js import { Pool } from "pg"; let conn; if(!conn){ conn = new Pool({ user: "public_readonly", password: "nearprotocol", host: "35.184.214.98", database: "testnet_explorer", }); } export default conn; ``` **Security issue: Never leave your private database credentials accessible for external users. This is just an example.** In the example above, we create a **connection pool** and return it. Other modules only need to import ```conn``` and use it to query for the data we need. Next we query the connection: ```javascript // /pages/api/transactions/[accountId].js import conn from '../../../lib/db'; export default async (req, res) => { // Only accepts GET requests. if (req.method !== 'GET'){ res.status(404).send("Invalid request type"); console.log("Invalid request received. Method: ", req.method); return; } try { console.log(`GET transaction request for account ${req.query.accountId}} Received.`); const query = ` SELECT * FROM transactions WHERE signer_account_id = '${req.query.accountId}' ORDER BY block_timestamp DESC LIMIT 10 OFFSET 0 `; // Make the query and return the result. const data = await conn.query(query); // The acquired values. const result = data.rows; res.status(200).json({ transactions: result }); } catch ( error ) { console.log("An Error has occurred."); console.log(error); res.status(500).send("Error fetching data"); } }; ``` Above is the api for transaction in the nextjs application. All that matters are the following lines: ```javascript // import conn import conn from '/path/to/conn'; // The postgres query to retrieve all columns from transactions // Where signer is the accountId is who we want to find. // Order by time. // Start counting from the latest element(0), up to 10 elements. const query = ` SELECT * FROM transactions WHERE signer_account_id = '${accountId}' ORDER BY block_timestamp DESC LIMIT 10 OFFSET 0 `; // Querying and waiting for result const data = await conn.query(query); // data.rows is the acquired results of a successful operation. const result = data.rows; ``` Recommended to use try/catch to handle possible errors. Here is how the [indexer database is structured](https://github.com/near/near-indexer-for-explorer/blob/master/docs/near-indexer-for-explorer-db.png). ## How FTs/NFTs Are Listed Detailed information about how wallet checks for Fungible Tokens is [here](https://github.com/near/near-wallet/blob/1cb459dcdb60e724c1a54b16f524ca80edbbf86b/packages/frontend/docs/FungibleTokenDiscovery.md). Detailed information abou how wallet checks for Non Fungible Tokens is [here](https://github.com/near/near-wallet/blob/1cb459dcdb60e724c1a54b16f524ca80edbbf86b/packages/frontend/docs/NonFungibleTokenDiscovery.md). In summary, **NFT** and **FT** smart contracts are not different from other smart contracts. But **NFTs** and **FTs** follow certain standards. Some methods and behaviors are unique for tokens. How does a backend know if a user has tokens in his/her account? It checks all transaction history of a given account. If the transactions have method names or arguments that are common for token smart contracts, we: - Call the view method ```ft_metadata``` or ```nft_metadata``` to retrieve information about the token. - Call the view method ```ft_balance_of``` or ```nft_tokens_for_owner``` to retrieve how many tokens that user has. The more transactions there is on an account, the longer it takes to retrieve tokens. This information should be cached on the backend to save on computing costs. --- # Contact Info Questions, suggestions, or anything else related to development, message me at discord On0n0k1#3800. Thank you for your time.