Fetch Data via Pull model

This guide is designed for external developers who need to retrieve and use prices provided through the Pull Model of the Entangle Universal Data Feeds protocol on the Solana blockchain.

Pull model is based on the same price feed sources as the push model. The former one is provided by the Entangle facility know as price-publisher that publishes last prices and pays transaction fees on the Solana block-chain. There is a variety of assets provided by the certified Price Providers, each update is verified and signed by them. What the Entangle Price Publisher does it's getting price feeds from the pricefeed.entangle.fi and publishing them on the Solana Price Oracle Smart Contract.

It is important to note that all assets on the list are provided, but not all of them are published in Solana. Some of them have to be published in a chain like Solana on demand. This means that an external developer interested in the latest update must independently retrieve it from pricefeed.entangle.fi and perform a transaction on Solana to ensure that, first, the price update belongs to and is a valid part of the Merkle tree, and second, the Merkle root is signed by a certified price provider.

Updates can be obtained directly from the Entangle Oracle blockchain or taken from the pricefeed.entangle.fi web service with signatures and Merkle proofs proving that they belong to the signed root.

The code below illustrates what pricefeeds received at pricefeed.entangle.fi might look like.

{
  "calldata": {
    "merkleRoot": "0x85cb639e510f748f6aad59d89f05d3e83c6b2c972d7c3240a18d343898654d18",
    "signatures": [
      {
        "R": "0x338235a5efba3bf11714edcf224949915e18956a2f8be5af898e61b70373c190",
        "S": "0x1c6f9b5db535ba075212c5c87e708ceab67d9c884e743519e7ec98bb5dbe6f16",
        "V": 27
      }
    ],
    "feeds": [
      {
        "key": "NGL/USD",
        "value": {
          "data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPG26yIcIVk=",
          "timestamp": 1726795961
        },
        "merkleProofs": [
          "gqFbTZtdA8Gsu4UC6g+fciYBTxeGeq6PAGuLwNTbNa0=",
          "7EuwFZuJIVBNqlNmknmye5B/ujjAuoFbcqweBrWv0KU=",
          "RBmRfPH3nzIhANiw82hQ5N6I480uiRYNYOu70QhHXsU=",
          "sm9TrehjM5j+ra4u+u9Ol94sHbxC54KRhI2GyYmt/Ks=",
          "VbYkk5Z9C9v1hfLS9VRuyKdkIWyv2yzTLsINzY/opuM="
        ]
      }
    ]
  },
  "error": ""
}

This example contains the merkleRoot that is derived within the predefined set of assets e.g.: "NGL/USD", "BTC/USD", "ETH/USD" etc. This merkleRoot is signed by the Certified Price Provider and signatures are supposed to be checked within the Price Oracle Contract. For the broad and comprehensive understanding important to highlight the Price Oracle invokes the Photon Messaging layer to retrieve actual list of Certified Price Providers.

feeds provides latest price updates. Each update has key, base64 encoded value, timestamp and merkleProofs that ensures this certain feed belongs to the Merkle tree and corresponds to the merkleRoot . For this certain case AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPG26yIcIVk= is the 0.068036590393106777 * 18 at Sep 20 2024 01:32:41.

Client application is supposed to deserialize this json data and send a transaction to Solana. Important to know as far as the price is verified it's started to be available on-chain and could be retrieved like it was pictured under the Fetch Data via Push model header.

const udf_program = anchor.workspace.UdfSolana as Program<UdfSolana>;
import { UdfSolana } from "../target/types/udf_solana";
const UDF_PROTOCOL_ID = Buffer.from(
    utf8.encode(
        "universal-data-feeds3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
    )
);
const UDF_ROOT = utf8.encode("UDF0");
const PHOTON_ROOT = utf8.encode("r0");

const publisher = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(require("../keys/publisher.json")));
const udfConfig = web3.PublicKey.findProgramAddressSync([UDF_ROOT, utf8.encode("CONFIG")], udf_program.programId)[0];
const udfProtocolInfo = web3.PublicKey.findProgramAddressSync([PHOTON_ROOT, utf8.encode("PROTOCOL"), UDF_PROTOCOL_ID], ccm_program.programId)[0];

let utf8Encode = new TextEncoder();

const dataKey = new Uint8Array(32);
dataKey.set(utf8Encode.encode("NGL/USD"));

let latestUpdatePda = web3.PublicKey.findProgramAddressSync(
    [UDF_ROOT, utf8.encode("LAST_UPDATE"), UDF_PROTOCOL_ID, dataKey],
    udf_program.programId
)[0];
console.log("Data feed pda", latestUpdatePda.toBase58());

const data = Array.from(Buffer.from("00000000000000000000000000000000000000000000000000f1b6eb221c2159", "hex"));
const timestamp = new anchor.BN(1723502724);
let dataFeed: DataFeed = {
    timestamp: timestamp,
    dataKey: Array.from(dataKey),
    data: data,
    merkleProof: [
        Array.from(Buffer.from("82a15b4d9b5d03c1acbb8502ea0f9f7226014f17867aae8f006b8bc0d4db35ad", "hex")),
        Array.from(Buffer.from("ec4bb0159b8921504daa53669279b27b907fba38c0ba815b72ac1e06b5afd0a5", "hex"))
        Array.from(Buffer.from("4419917cf1f79f322100d8b0f36850e4de88e3cd2e89160d60ebbbd108475ec5", "hex")),
        Array.from(Buffer.from("b26f53ade8633398feadae2efaef4e97de2c1dbc42e78291848d86c989adfcab", "hex"))
        Array.from(Buffer.from("55b62493967d0bdbf585f2d2f5546ec8a764216cafdb2cd32ec20dcd8fe8a6e3", "hex")),
    ],
};
let lastPriceData: LastPriceData = {
    dataFeed: dataFeed,
    signatures: [{
        v: 27,
        r: Buffer.from("338235a5efba3bf11714edcf224949915e18956a2f8be5af898e61b70373c190", "hex"),
        s: Buffer.from("1c6f9b5db535ba075212c5c87e708ceab67d9c884e743519e7ec98bb5dbe6f16", "hex"),
    }, {
        v: 27,
        r: Buffer.from("b6e0bcf9ea53ee55f9be865a0e760f0476d6b6b4c3b3ac343dd63917bca705a5", "hex"),
        s: Buffer.from("04f544edf78a2302b5ec4ffdd69f73afc04a8f5d4add8024f0da7c021d9b3385", "hex"),
    }, {
        v: 27,
        r: Buffer.from("51785610dd7c664c88e595174fe26bb74fad788ff8442f24337fb148aa6a4581", "hex"),
        s: Buffer.from("17b28d0f6ca9f89eef37cbd0c7fd96b417d0832058caa04a07971244c3961b72", "hex"),
    }],
    merkleRoot: Array.from(Buffer.from("85cb639e510f748f6aad59d89f05d3e83c6b2c972d7c3240a18d343898654d18", "hex"))
}

const computeBudgetIx = web3.ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 });
let getLastPriceTx = new web3.Transaction();
getLastPriceTx.add(computeBudgetIx);

const getLastPriceIx = await udf_program.methods.getLastPrice(lastPriceData)
    .accounts({
        publisher: publisher.publicKey,
        config: udfConfig,
        protocolInfo: udfProtocolInfo,
        systemProgram: web3.SystemProgram.programId
    })
    .remainingAccounts([{ pubkey: latestUpdatePda, isSigner: false, isWritable: true }])
    .signers([publisher]).instruction();
getLastPriceTx.add(getLastPriceIx);

const signature = await udf_program.provider.sendAndConfirm(getLastPriceTx, [publisher]);
console.log("Get last price transaction signature", signature);

const result = await udf_program.provider.simulate(getLastPriceTx)
const resultNum = new BN(base64.decode(result.returnData.data[0]), 10, "be");
const divisor = new BN("1000000000000000000", 10);
const int = parseFloat(resultNum.div(divisor).toString())
const reminder = parseFloat(resultNum.mod(divisor).toString(10)) / parseFloat(divisor.toString(10));
console.log("Last NGL/USDT price is:", int + reminder)
const latestUpdate = await udf_program.account.latestUpdate.fetch(latestUpdatePda);
assert.deepEqual(latestUpdate.dataKey, Array.from(dataKey));
assert.deepEqual(latestUpdate.data, data);
assert.ok(latestUpdate.dataTimestamp.eq(timestamp));

The code above requires the following dependencies:

With this guide, you can now integrate Solana's Entangle Universal Data Feeds price data into your decentralized applications. This system efficiently retrieves price and timestamp information, ensuring that your program can access accurate on-chain data with minimal latency.

Last updated