Skip to content

Instantly share code, notes, and snippets.

@fassko
Created December 15, 2025 17:51
Show Gist options
  • Select an option

  • Save fassko/4bb50851ebe28dfda1982c84609e4803 to your computer and use it in GitHub Desktop.

Select an option

Save fassko/4bb50851ebe28dfda1982c84609e4803 to your computer and use it in GitHub Desktop.
import { run, web3 } from "hardhat";
import {
prepareAttestationRequestBase,
submitAttestationRequest,
retrieveDataAndProofBaseWithRetry,
} from "../utils/fdc";
import { sleep } from "../utils/core";
// TypeChain typings for Truffle don't always include newly renamed contracts immediately.
// Cast to `any` to avoid artifacts.require overload issues.
const LatviaSnowDepth = (artifacts as any).require("LatviaSnowDepth");
const { WEB2JSON_VERIFIER_URL_TESTNET, VERIFIER_API_KEY_TESTNET, COSTON2_DA_LAYER_URL } = process.env;
// yarn hardhat run scripts/fdcExample/LatviaSnowDepth.ts --network coston2
// Request data
const apiUrl = "https://data.gov.lv/dati/lv/api/action/datastore_search";
const queryParams = JSON.stringify({
resource_id: "17460efb-ae99-4d1d-8144-1068f184b05f",
q: "[RIGASLU,HSNOW,2025-12-14T06:00:00]",
});
// Extract `result.records[0].VALUE` and normalize to a lower-case `value` field.
// Also extract `DATETIME`, treat it as UTC (append "Z"), and convert to unix timestamp (seconds).
const postProcessJq =
`{timestamp: (((.result.records[0].DATETIME // .result.records[0].datetime) + "Z") | fromdateiso8601 | floor), ` +
`value: (.result.records[0].VALUE // .result.records[0].value)}`;
const httpMethod = "GET";
// Defaults to "Content-Type": "application/json"
// Some public APIs respond better with explicit Accept/User-Agent.
const headers = JSON.stringify({
accept: "application/json",
"user-agent": "Mozilla/5.0",
});
const body = "{}";
// Must match the DTO decoded in `LatviaSnowDepth.sol`.
const abiSignature = `{"components": [{"internalType": "uint64", "name": "timestamp", "type": "uint64"},{"internalType": "uint256", "name": "value", "type": "uint256"}],"name": "task","type": "tuple"}`;
// Configuration constants
const attestationTypeBase = "Web2Json";
const sourceIdBase = "PublicWeb2";
const verifierUrlBase = WEB2JSON_VERIFIER_URL_TESTNET;
async function prepareAttestationRequest(apiUrl: string, postProcessJq: string, abiSignature: string) {
const requestBody = {
url: apiUrl,
httpMethod: httpMethod,
headers: headers,
queryParams: queryParams,
body: body,
postProcessJq: postProcessJq,
abiSignature: abiSignature,
};
const url = `${verifierUrlBase}Web2Json/prepareRequest`;
const apiKey = VERIFIER_API_KEY_TESTNET;
return await prepareAttestationRequestBase(url, apiKey ?? "", attestationTypeBase, sourceIdBase, requestBody);
}
async function retrieveDataAndProof(abiEncodedRequest: string, roundId: number) {
const url = `${COSTON2_DA_LAYER_URL}api/v1/fdc/proof-by-request-round-raw`;
console.log("Url:", url, "n");
return await retrieveDataAndProofBaseWithRetry(url, abiEncodedRequest, roundId);
}
async function deployAndVerifyContract() {
const args: any[] = [];
const latviaSnowDepth = await LatviaSnowDepth.new(...args);
console.log("LatviaSnowDepth deployed to", latviaSnowDepth.address, "\n");
// Explorer indexing can lag a bit after deployment; retry verification a few times.
for (let attempt = 1; attempt <= 5; attempt++) {
try {
console.log(`Verifying LatviaSnowDepth (attempt ${attempt}/5)...`);
await run("verify:verify", {
address: latviaSnowDepth.address,
constructorArguments: args,
contract: "contracts/fdcExample/LatviaSnowDepth.sol:LatviaSnowDepth",
});
break;
} catch (e: any) {
console.log(e?.message ?? e);
await sleep(20000);
}
}
return latviaSnowDepth;
}
async function interactWithContract(contract: any, proof: any) {
console.log("Proof hex:", proof.response_hex, "\n");
// A piece of black magic that allows us to read the response type from an artifact
const IWeb2JsonVerification = await artifacts.require("IWeb2JsonVerification");
const responseType = IWeb2JsonVerification._json.abi[0].inputs[0].components[1];
console.log("Response type:", responseType, "\n");
const decodedResponse = web3.eth.abi.decodeParameter(responseType, proof.response_hex);
console.log("Decoded proof:", decodedResponse, "\n");
const transaction = await contract.updateSnowDepth({
merkleProof: proof.proof,
data: decodedResponse,
});
console.log("Transaction:", transaction.tx, "\n");
console.log("Stored lastSnowDepth:", (await contract.lastSnowDepth()).toString(), "\n");
const lastTimestamp = await contract.lastTimestamp();
console.log("Stored lastTimestamp:", lastTimestamp.toString(), "\n");
console.log(
"Stored snowDepthByTimestamp[lastTimestamp]:",
(await contract.snowDepthByTimestamp(lastTimestamp)).toString(),
"\n"
);
}
async function main() {
const data = await prepareAttestationRequest(apiUrl, postProcessJq, abiSignature);
console.log("Data:", data, "\n");
if (!data?.abiEncodedRequest) {
throw new Error(
`Web2Json prepareRequest did not return abiEncodedRequest. Response: ${JSON.stringify(data)}`
);
}
const abiEncodedRequest = data.abiEncodedRequest;
const roundId = await submitAttestationRequest(abiEncodedRequest);
const proof = await retrieveDataAndProof(abiEncodedRequest, roundId);
const latviaSnowDepth = await deployAndVerifyContract();
await interactWithContract(latviaSnowDepth, proof);
}
void main().then(() => {
process.exit(0);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment