Retrieve current price data off-chain with AMB Access Polygon and Chainlink Data Feeds

9 minute read
Content level: Foundational
3

Learn how to retrieve asset price data using Amazon Managed Blockchain (AMB) Access and Chainlink Price Feeds

Introduction

In this tutorial, you will learn how to retrieve current price data off-chain for various cryptocurrencies and real-world assets by using Amazon Managed Blockchain (AMB) Access Polygon and Chainlink Data Feeds. You will retrieve data from Chainlink's deployed Verified Price Feed smart contracts on Polygon Mainnet using AMB Access and web3.js, a widely-used JavaScript library for Ethereum. Chainlink Data Feeds gather and publish aggregated data from a variety of sources on-chain, utilizing Chainlink's decentralized oracle network.

Prerequisites
  • Installation of Node.js (version 18 or above)
  • A text editor (such as Visual Studio Code)
  • Basic knowledge of Ethereum and smart contracts
  • Installation of the AWS Command Line Interface (CLI).
    • Run the command aws configure to set the variables for your IAM User’s Access Key ID, Secret Access Key, and Region.
      • Ensure that this User has the appropriate IAM permissions for AMB Access Polygon. For this tutorial, you can use the AmazonManagedBlockchainFullAccess managed policy.

Step 1: Setting up the project

  1. Create a new directory for your project and change into it by running the following command:
mkdir chainlinkPriceFeeds && cd chainlinkPriceFeeds
  1. Run the following command to install the necessary dependencies:
npm install web3 @aws-sdk/client-managedblockchain dotenv

Web3 enables blockchain interactions, dotenv handles environment variables, and the Managed Blockchain client is used to initialize your AMB Access Polygon Mainnet endpoint.

Step 2: AMB Access Polygon Endpoint Configuration

The following script automates the following tasks:

  • Retrieval or creation of an AMB (Amazon Managed Blockchain) Polygon Mainnet Accessor token.
    • The script first checks for any existing Polygon Mainnet Accessor tokens in your AWS account. If an available token is found, it will be reused. Otherwise, a new Accessor token will be created.
  • Configuration of an AMB Access Polygon Mainnet endpoint with your Accessor token.
    • Your configured endpoint will allow you to make JSON-RPC calls to Polygon Mainnet with web3.js.

Warning: Never share or expose your Accessor token publicly. It is important to note that this code should not be used in production environments, as it poses a security risk.

Create a new file (e.g init.js) in the root of your directory and copy the following code:

const fs = require('fs').promises;
const { Web3 } = require('web3');
const { ManagedBlockchainClient, CreateAccessorCommand, ListAccessorsCommand, GetAccessorCommand } = require("@aws-sdk/client-managedblockchain");

const network = "POLYGON_MAINNET";

async function getExistingAccessor(client) {
    try {
        const response = await client.send(new ListAccessorsCommand({ NetworkType: network }));

        for (const accessor of response.Accessors) {
            if (accessor.Status === "AVAILABLE") {
                const accessorResponse = await client.send(new GetAccessorCommand({ AccessorId: accessor.Id }));
                return accessorResponse.Accessor;
            }
        }
    } catch (error) {
        console.error('Error retrieving existing Accessor:', error);
        throw error;
    }
    return null;
}

async function createAccessor() {
    const client = new ManagedBlockchainClient();
    const existingAccessor = await getExistingAccessor(client);

    if (existingAccessor) {
        console.log('Using existing Accessor token.');
        return {
            billingToken: existingAccessor.BillingToken
        };
    }
    else {
        console.log('Creating a new Accessor token.');
    }

    try {
        const input = {
            AccessorType: "BILLING_TOKEN",
            NetworkType: network,
        };
        const command = new CreateAccessorCommand(input);
        const response = await client.send(command);
        return {
            billingToken: response.BillingToken,
        };
    } catch (error) {
        console.error('Error creating Accessor token:', error);
        throw error;
    }
}

async function main() {
    try {
        console.log('Creating or retrieving AMB Access Polygon Accessor token...');
        const accessor = await createAccessor();
        const accessEndpoint = `https://mainnet.polygon.managedblockchain.us-east-1.amazonaws.com?billingtoken=${accessor.billingToken}`;
        const dataToSave = `AMB_ACCESS_POLYGON_MAINNET=${accessEndpoint}`;
        await fs.writeFile('.env', dataToSave);
        console.log('Accessor token created or retrieved. Details saved to .env file.');
    } catch (error) {
        console.error('Setup failed:', error);
    }
}

main();

Execute the script by running:

node init.js

Your endpoint for AMB Access Polygon Mainnet has been securely stored in a generated .env file.

Step 3: Write the Chainlink Price Feeds Script

Create a new file (e.g priceFeeds.js) and copy the following code:

const { Web3 } = require('web3') 
require('dotenv').config();

const web3 = new Web3(process.env.AMB_ACCESS_POLYGON_MAINNET);

const abi = [{ "inputs": [{ "internalType": "address", "name": "_aggregator", "type": "address" }, { "internalType": "address", "name": "_accessController", "type": "address" }], "stateMutability": "nonpayable", "type": "constructor" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "int256", "name": "current", "type": "int256" }, { "indexed": true, "internalType": "uint256", "name": "roundId", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "updatedAt", "type": "uint256" }], "name": "AnswerUpdated", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "uint256", "name": "roundId", "type": "uint256" }, { "indexed": true, "internalType": "address", "name": "startedBy", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "startedAt", "type": "uint256" }], "name": "NewRound", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "from", "type": "address" }, { "indexed": true, "internalType": "address", "name": "to", "type": "address" }], "name": "OwnershipTransferRequested", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "from", "type": "address" }, { "indexed": true, "internalType": "address", "name": "to", "type": "address" }], "name": "OwnershipTransferred", "type": "event" }, { "inputs": [], "name": "acceptOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "accessController", "outputs": [{ "internalType": "contract AccessControllerInterface", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "aggregator", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "_aggregator", "type": "address" }], "name": "confirmAggregator", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "decimals", "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "description", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "_roundId", "type": "uint256" }], "name": "getAnswer", "outputs": [{ "internalType": "int256", "name": "", "type": "int256" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint80", "name": "_roundId", "type": "uint80" }], "name": "getRoundData", "outputs": [{ "internalType": "uint80", "name": "roundId", "type": "uint80" }, { "internalType": "int256", "name": "answer", "type": "int256" }, { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint256", "name": "_roundId", "type": "uint256" }], "name": "getTimestamp", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "latestAnswer", "outputs": [{ "internalType": "int256", "name": "", "type": "int256" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "latestRound", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "latestRoundData", "outputs": [{ "internalType": "uint80", "name": "roundId", "type": "uint80" }, { "internalType": "int256", "name": "answer", "type": "int256" }, { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "latestTimestamp", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "owner", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint16", "name": "", "type": "uint16" }], "name": "phaseAggregators", "outputs": [{ "internalType": "contract AggregatorV2V3Interface", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "phaseId", "outputs": [{ "internalType": "uint16", "name": "", "type": "uint16" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "_aggregator", "type": "address" }], "name": "proposeAggregator", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "proposedAggregator", "outputs": [{ "internalType": "contract AggregatorV2V3Interface", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "uint80", "name": "_roundId", "type": "uint80" }], "name": "proposedGetRoundData", "outputs": [{ "internalType": "uint80", "name": "roundId", "type": "uint80" }, { "internalType": "int256", "name": "answer", "type": "int256" }, { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "proposedLatestRoundData", "outputs": [{ "internalType": "uint80", "name": "roundId", "type": "uint80" }, { "internalType": "int256", "name": "answer", "type": "int256" }, { "internalType": "uint256", "name": "startedAt", "type": "uint256" }, { "internalType": "uint256", "name": "updatedAt", "type": "uint256" }, { "internalType": "uint80", "name": "answeredInRound", "type": "uint80" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "_accessController", "type": "address" }], "name": "setController", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{ "internalType": "address", "name": "_to", "type": "address" }], "name": "transferOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "version", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" }]

//Contract addresses for Chainlink Data Feeds on Polygon Mainnet 
const contracts = [
    { name: 'Bitcoin', address: '0xc907E116054Ad103354f2D350FD2514433D57F6f' },
    { name: 'Ethereum', address: '0xF9680D99D6C9589e2a93a78A04A279e509205945' },
    { name: 'Polygon', address: '0xAB594600376Ec9fD91F8e885dADF0CE036862dE0' },
    { name: 'XAU (Gold)', address: '0x0C466540B2ee1a31b441671eac0ca886e051E410' },
    { name: 'GBP/USD', address: '0x099a2540848573e94fb1Ca0Fa420b00acbBc845a' }
];

const getDecimals = async (priceFeed) => {
    return await priceFeed.methods.decimals().call();
}

const getLatestRoundData = async (priceFeed) => {
    return await priceFeed.methods.latestRoundData().call();
}

async function main() {
    for (const contract of contracts) {
        try {
            const priceFeed = new web3.eth.Contract(abi, contract.address);
            const decimals = await getDecimals(priceFeed);
            const roundData = await getLatestRoundData(priceFeed);

            const balanceNumber = Number(roundData.answer) / (10 ** Number(decimals));
            const formattedBalanceString = balanceNumber.toLocaleString('en-US', {
                style: 'currency',
                currency: 'USD'
            });

            console.log(`${contract.name} Price: ${formattedBalanceString}`);
        } catch (error) {
            console.error(`An error occurred with ${contract.name}:`, error);
        }
    }
}

main();

Run the script with the following command:

node priceFeeds.js

You will see the current prices in USD for Bitcoin, Ethereum, Polygon, gold (in ounces), and the Great Britain Pound. This data was saved in a decentralized manner to Chainlink’s on-chain Verified Price Feeds. If you would like to incorporate price feed data for other assets, you can refer to Chainlink’s extensive list of Price Feed Contract Addresses.

Conclusion

You have successfully utilized AMB Access and Chainlink Price Feeds to fetch real time prices of various assets from off-chain using web3.js. If you would like to learn more about Chainlink Price Feeds, you can refer to the official documentation. Remember to prioritize security, especially with sensitive data such as your Amazon Managed Blockchain Accessor token.

Please leave a comment below if you have any questions. For further information on AMB Access, you can refer to the documentation.

profile pictureAWS
EXPERT
published 3 months ago1926 views