Weather Insurance
In this guide, we will examine an example of a simple insurance dApp that uses the FDC's JsonApi attestation type. The dApp will allow users to create insurance policies for temperatures at specific coordinates falling below a specified threshold. Other users will be able to claim those policies, and the policies will be settled automatically by the contract.
All the code described in this guide is available on GitHub, in the Flare Hardhat starter repository.
The Process of Using the dApp
To start with, let us describe the requirements for our Weather Insurance dApp. We will do so by laying out how we expect the users to interact with the main contract. There will be three actors involved in the process:
- policyholder: the entity possessing assets that they want to insure against unfavorable weather conditions
- insurer: the entity willing to take on the risk in exchange for a premium
- contract: the smart contract that will handle the logic of the exchange
With that, we can describe the process of using the Weather Insurance dApp.
- The policyholder creates a policy, specifying:
- Location of insured asset (latitude, longitude)
- Policy duration (start and expiration timestamp)
- Hourly rainfall threshold constituting the loss
- Premium amount
- Loss coverage amount They also deposit the premium to the contract that will resolve the policy.
-
The insurer accepts the policy and deposits the loss coverage amount to the contract. The contract pays out the deposited premium to the insurer. If no insurer has accepted the policy by the time it comes into effect (before its start timestamp), the policy is considered expired.
-
The policy is resolved. This happens in three ways:
- The policy has expired because no insurer accepted it in the allotted time. The contract returns the premium to the policyholder.
- An insurer has accepted the policy, and proof has been provided to the contract, demonstrating that a loss has occurred (in this case, that the temperature at the specified location fell below the agreed-upon threshold). The contract pays out the deposited loss coverage amount to the policyholder.
- An insurer has accepted the policy, and the expiration timestamp has been reached without valid proof having been provided. The contract returns the loss coverage deposit to the insurer.
With that we can now focus on the technical aspects of the procedure described above, starting with the main smart contract.
The Contract
The contract that we will define will be called MinTempAgency
.
This name coincides with the field name in the response on a call to the OpenWeather API, which we will be using to acquire weather data.
We will use the Flare Data Connector to collect the weather data so, in keeping with the JsonApi guide, we start by defining a DataTransportObject
which will determine how the FDC should encode the data.
struct DataTransportObject {
int256 latitude;
int256 longitude;
string description;
int256 temperature;
int256 minTemp;
uint256 windSpeed;
uint256 windDeg;
}
In its response, the API returns the latitude and longitude of the closest weather station to the desired coordinates. The policyholder is thus responsible for providing these, otherwise, they will not be able to prove that a loss has occurred. We store the weather station's latitude and longitude in the policy so that we can later confirm that the proof pertains to the correct location.
The policy also includes a minTemp
field, which will serve as the criterion for a loss.
We save additional values to the policy, which may serve as an inspiration for defining additional policy types.
At the top of the contract, we define a Policy
struct, which will represent a policy once it has been registered.
In addition to the values originating from the DataTransportObject
, we declare the following fields:
- holder: the address of the policyholder that created the policy
- premium: the deposited premium for the policy
- coverage: the expected loss coverage amount if the policy is accepted
- status: a
PolicyStatus
enum value describing the state of the policy, with either of the following values: - Unclaimed: the policyholder has created the policy, but no insurer has accepted it yet
- Open: an insurer has accepted the policy, but it has not been resolved yet
- Settled: the policy has been resolved
- id: a unique value identifying the policy
The contract will save the policies to an array registeredPolicies
.
When a policy is accepted, the contract will save the insurer's address to the mapping insurers
.
We also define several events that will be emitted at different stages of a policy's lifetime.
contract MinTempAgency {
Policy[] public registeredPolicies;
mapping(uint256 => address) insurers;
enum PolicyStatus {
Unclaimed,
Open,
Settled
}
struct Policy {
address holder;
int256 latitude;
int256 longitude;
uint256 startTimestamp;
uint256 expirationTimestamp;
int256 minTempThreshold;
uint256 premium;
uint256 coverage;
PolicyStatus status;
uint256 id;
}
event PolicyCreated(uint256 id);
event PolicyClaimed(uint256 id);
event PolicySettled(uint256 id);
event PolicyExpired(uint256 id);
event PolicyRetired(uint256 id);
...
}
The function for policy creation requires a premium to be paid to the contract. The premium is thus not one of the parameters of this function. If no premium has been deposited, the function reverts. The function also ensures that the expiration timestamp is greater than the start timestamp.
If these two checks are passed, a new Policy
struct is created and added to the array of registered policies.
A PolicyCreated
event is emitted, with the policy ID as value.
function createPolicy(
int256 latitude,
int256 longitude,
uint256 startTimestamp,
uint256 expirationTimestamp,
int256 minTempThreshold,
uint256 coverage
) public payable {
require(msg.value > 0, "No premium paid");
require(startTimestamp < expirationTimestamp, "Value of startTimestamp larger than expirationTimestamp");
Policy memory newPolicy = Policy({
holder: msg.sender,
latitude: latitude,
longitude: longitude,
startTimestamp: startTimestamp,
expirationTimestamp: expirationTimestamp,
minTempThreshold: minTempThreshold,
premium: msg.value,
coverage: coverage,
status: PolicyStatus.Unclaimed,
id: registeredPolicies.length
});
registeredPolicies.push(newPolicy);
emit PolicyCreated(newPolicy.id);
}
Because Solidity does not support floating point numbers, we will store the fractional values as their 10^6
multiple.
So, instead of Celsius, we will use micro-Celsius, instead of degrees for latitude and longitude micro-degrees, and so on.
The claimPolicy
function first retrieves the policy with the given ID.
It checks that the policy is yet Unclaimed
.
Just like the premium, the coverage value is also not a parameter but the amount paid to the contract. The function checks that it has received a sufficient amount before continuing.
If all checks have passed, the policy's status is set to Open
.
The registeredPolicies
array is updated, and the insurer added to the mapping insurers
.
Lastly, the premium is paid to the insurer, and a PolicyClaimed
event is emitted.
function claimPolicy(uint256 id) public payable {
Policy memory policy = registeredPolicies[id];
require(policy.status == PolicyStatus.Unclaimed, "Policy already claimed");
if (block.timestamp > policy.expirationTimestamp) {
retireUnclaimedPolicy(id);
revert("Policy already expired");
}
require(msg.value >= policy.coverage, "Insufficient coverage paid");
policy.status = PolicyStatus.Open;
registeredPolicies[id] = policy;
insurers[id] = msg.sender;
payable(msg.sender).transfer(policy.premium);
emit PolicyClaimed(id);
}
Any coin transfer must be performed only after the state has been updated. Otherwise, the contract is open for a reentrancy attack.
The code that resolves a policy has been extended to provide a better description of conditions that revert the function.
The function first collects the policy with the provided ID, and checks that its status is Open
.
Then, it validates the provided proof with the isJsonApiProofValid
helper function.
If the proof is valid, the resolvePolicy
function decodes the enclosed data to at DataTransportObject
struct.
Several checks follow. The first two ensure that we are currently within the time interval, described by the policy. We assume, that the data relates to the current weather conditions. For that reason, the function compares the timestamp of the current block to the policy's start and expiration timestamp.
If the current timestamp is smaller than the start timestamp, the function reverts. If it exceeds the expiration timestamp, we expire the policy.
Next, the function compares the coordinates provided by the proof to those of the policy, requiring they match. Lastly, it checks that the condition for a loss has been met; namely, that the minimum temperature in the proof falls below the threshold value set by the policy.
Finally, if all checks have passed, the function marks the policy as Settled
, and transfers the coverage amount to the policyholder.
A PolicySettled
event is emitted.
function resolvePolicy(uint256 id, IJsonApi.Proof calldata proof) public {
Policy memory policy = registeredPolicies[id];
require(policy.status == PolicyStatus.Open, "Policy not open");
require(isJsonApiProofValid(proof), "Invalid proof");
DataTransportObject memory dto = abi.decode(proof.data.responseBody.abi_encoded_data, (DataTransportObject));
require(
block.timestamp >= policy.startTimestamp,
string.concat(
"Policy not yet in effect: ",
Strings.toString(block.timestamp),
" vs. ",
Strings.toString(policy.startTimestamp)
)
);
if (block.timestamp > policy.expirationTimestamp) {
expirePolicy(id);
return;
}
require(
dto.latitude == policy.latitude && dto.longitude == policy.longitude,
string.concat(
"Invalid coordinates: ",
Strings.toStringSigned(dto.latitude),
", ",
Strings.toStringSigned(dto.longitude),
" vs. ",
Strings.toStringSigned(policy.latitude),
", ",
Strings.toStringSigned(policy.longitude)
)
);
require(
dto.minTemp <= policy.minTempThreshold,
string.concat(
"Minimum temperature not met: ",
Strings.toStringSigned(dto.minTemp),
" vs. ",
Strings.toStringSigned(policy.minTempThreshold)
)
);
policy.status = PolicyStatus.Settled;
registeredPolicies[id] = policy;
payable(policy.holder).transfer(policy.coverage);
emit PolicySettled(id);
}
The IJsonAPi.Proof
is validated as demonstrated in the JsonApi guide.
function isJsonApiProofValid(IJsonApi.Proof calldata _proof) private view returns (bool) {
return ContractRegistry.auxiliaryGetIJsonApiVerification().verifyJsonApi(_proof);
}
Despite being called from within the function that resolves a policy, the expirePolicy
function can serve as a standalone function.
For that reason, it performs the two checks for the policy's expiration again;
it ensures its status is Open
, and that the timestamp of the current block is greater than the expiration timestamp of the policy.
If the checks pass, the policy is marked as Settled
, and the coverage is returned to the insurer.
A PolicyExpired
event is emitted.
function expirePolicy(uint256 id) public {
Policy memory policy = registeredPolicies[id];
require(policy.status == PolicyStatus.Open, "Policy not open");
require(block.timestamp > policy.expirationTimestamp, "Policy not yet expired");
policy.status = PolicyStatus.Settled;
registeredPolicies[id] = policy;
payable(insurers[id]).transfer(policy.coverage);
emit PolicyExpired(id);
}
The last of the non-helper functions allows us to retire unclaimed policies.
If the policy's status is Unclaimed
, and the timestamp of the current block exceeds the policy's expiration timestamp, the policy is marked as Settled
, and its premium is returned to the policyholder.
A PolicyRetired
event is emitted.
function retireUnclaimedPolicy(uint256 id) public {
Policy memory policy = registeredPolicies[id];
require(policy.status == PolicyStatus.Unclaimed, "Policy not unclaimed");
require(block.timestamp > policy.expirationTimestamp, "Policy not yet expired");
policy.status = PolicyStatus.Settled;
registeredPolicies[id] = policy;
payable(policy.holder).transfer(policy.premium);
emit PolicyRetired(id);
}
The remaining functions serve a utility purpose.
The getInsurer
function allows us to query policy insurers, while the getAllPolicies
returns the whole registeredPolicies
array.
function getInsurer(uint256 id) public view returns (address) {
return insurers[id];
}
function getAllPolicies() public view returns (Policy[] memory) {
return registeredPolicies;
}
Like in the JsonApi guide, we define an abiSignatureHack
function, that will allow us to extract the ABI signature of the DataTransportObject
from the Hardhat-generated artifacts.
This will be important in the next section of this guide, where we will submit a JsonApi attestation request to the FDC.
function abiSignatureHack(DataTransportObject memory dto) public pure {}
The Scripts
The following scripts reflect the process described at the start of this guide. Most of them are straightforward, performing a single function call.
The first script deploys the MinTempAgency
contract and prints its address to the console.
import hre, { run } from "hardhat";
import { MinTempAgencyInstance } from "../../../typechain-types";
const MinTempAgency = artifacts.require("MinTempAgency");
// yarn hardhat run scripts/weatherInsurance/minTemp/deployAgency.ts --network coston2
async function deployAndVerify() {
const args: readonly unknown[] = [];
const agency: MinTempAgencyInstance = await MinTempAgency.new(...args);
try {
await run("verify:verify", {
address: agency.address,
constructorArguments: args,
});
} catch (e: unknown) {
if (e instanceof Error) {
console.error("Verification error:", e.message);
} else {
console.error("Unknown verification error:", e);
}
}
console.log(
`(${hre.network.name}) MinTempAgency deployed to`,
agency.address,
"\n",
);
}
deployAndVerify().then(() => {
process.exit(0);
});
We save the address to a scripts/weatherInsurance/minTemp/config.ts
file because we will access it in other scripts.
export const agencyAddress = "0x04Ca06E78EFaF061173Ca3591f96fE148923707d";
Next, we will create a new policy with the following parameters:
- latitude:
46.419402127862405
- longitude:
15.587079308221126
- start timestamp: 30 seconds from now
- expiration timestamp: an hour from now
- minimum temperature threshold: 30 degrees Celsius
- premium: 10 wei
- coverage: 1000 wei
We have set the minimum temperature threshold high enough that the policy will always be resolved successfully.
Because the response of the OpenWeather API includes not the provided coordinates, but those of the nearest weather station, we will first replace our latitude and longitude with those. This will ensure that the coordinates of the created policy match the ones in the proof data. Without this step, we could never prove that a loss has occurred.
We find the correct coordinates by making a GET request to the same URL that will be provided to the FDC.
To use the API, we need to provide it with an API key, which is available on a free account.
We read the API key from the .env
file.
Also, we explicitly state that we are using metric units, although this is the default.
const latitude = 46.419402127862405;
const longitude = 15.587079308221126;
const apiId = process.env.OPEN_WEATHER_API_KEY ?? "";
const units = "metric";
const apiUrl = `https://api.openweathermap.org/data/2.5/weather
\?lat\=${latitude}
\&lon\=${longitude}
\&appid\=${apiId}
\&units\=${units}`;
The script collects the appropriate coordinates as described above.
It multiplies them with 10^6
to avoid the floating point values.
Lastly, it creates a new policy by calling the createPolicy
function on the MinTempAgency
at the address specified in the config.ts
file.
import { agencyAddress } from "./config";
import { MinTempAgencyInstance } from "../../../typechain-types";
const MinTempAgency = artifacts.require("MinTempAgency");
// yarn hardhat run scripts/weatherInsurance/minTemp/createPolicy.ts --network coston2
const latitude = 46.419402127862405;
const longitude = 15.587079308221126;
const apiId = process.env.OPEN_WEATHER_API_KEY ?? "";
const units = "metric";
const apiUrl = `https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${apiId}&units=${units}`;
const startTimestamp = Math.round(Date.now() / 1000) + 30;
const expirationTimestamp = startTimestamp + 60 * 60;
const minTempThreshold = 20 * 10 ** 6;
const premium = 10;
const coverage = 1000;
async function getWeatherStationCoordinates(apiUrl: string) {
const response = await fetch(apiUrl, { method: "GET" });
const json = await response.json();
console.log("Response:", json, "\n");
return json.coord;
}
interface PolicyParameters {
latitude: number;
longitude: number;
startTimestamp: number;
expirationTimestamp: number;
minTempThreshold: number;
premium: number;
coverage: number;
}
async function createPolicy(
agency: MinTempAgencyInstance,
policyParameters: PolicyParameters,
) {
const transaction = await agency.createPolicy(
policyParameters.latitude,
policyParameters.longitude,
policyParameters.startTimestamp,
policyParameters.expirationTimestamp,
policyParameters.minTempThreshold,
policyParameters.coverage,
{ value: policyParameters.premium },
);
console.log("Transaction:", transaction.tx, "\n");
}
async function main() {
const coordinates = await getWeatherStationCoordinates(apiUrl);
console.log("Coordinates:", coordinates, "\n");
const policyParameters: PolicyParameters = {
latitude: coordinates.lat * 10 ** 6,
longitude: coordinates.lon * 10 ** 6,
startTimestamp,
expirationTimestamp,
minTempThreshold,
coverage,
premium,
};
console.log("Policy parameters:", policyParameters, "\n");
const agency: MinTempAgencyInstance = await MinTempAgency.at(agencyAddress);
console.log("MinTempAgency:", agency.address, "\n");
await createPolicy(agency, policyParameters);
}
main().then(() => {
process.exit(0);
});
The script for claiming a policy has a helper function that calculates the policy coverage of a given policy.
It then calls the claimPolicy
function on the MinTempAgency
at the address read from the config.ts
file, paying the coverage value to the contract.
In this example, the policy claimed has the ID of 0
.
import { agencyAddress } from "./config";
import { MinTempAgencyInstance } from "../../../typechain-types";
const MinTempAgency = artifacts.require("MinTempAgency");
// yarn hardhat run scripts/weatherInsurance/minTemp/claimPolicy.ts --network coston2
const policyId = 0;
async function getPolicyCoverage(
agency: MinTempAgencyInstance,
policyId: number,
) {
const policy = await agency.registeredPolicies(policyId);
const policyCoverage = policy.coverage;
console.log("Policy premium:", policyCoverage, "\n");
return policyCoverage;
}
async function main() {
const agency: MinTempAgencyInstance = await MinTempAgency.at(agencyAddress);
console.log("MinTempAgency:", agency.address, "\n");
const policyCoverage = await getPolicyCoverage(agency, policyId);
const transaction = await agency.claimPolicy(policyId, {
value: policyCoverage,
});
console.log("Transaction:", transaction.tx, "\n");
}
main().then(() => {
process.exit(0);
});
The script for resolving a policy is slightly more complicated.
It involves making a JsonApi attestation request to the FDC and providing the returned proof to the MinTempAgency
contract.
To learn more about the JsonApi attestation request look at the related guide, or its spec.
We prepare an attestation request using the verifier server.
We do so by making a POST request to the appropriate endpoint;
in this case, the URL is https://jq-verifier-test.flare.rocks/JsonApi/prepareRequest
.
The URL we will be submitting to the FDC is the one already mentioned above. More specifically:
https://api.openweathermap.org/data/2.5/weather?
lat=46.419400&lon=15.587100&appid=${apiId}&units=metric
Where apiId
will be replaced with our OpenWeather API key from the .env
file.
We prepare the URL using the following helper function.
Here, we divide the coordinates by 10^6
to return them to their original value.
async function prepareUrl(policy: any) {
return `https://api.openweathermap.org/data/2.5/weather?lat=${
policy.latitude / 10 ** 6
}&lon=${policy.longitude / 10 ** 6}&units=${units}&appid=${apiId}`;
}
We need to specify the jq filter that the FDC should apply to the data received in the response to the POST request to the above URL.
Only a few fields are needed, and most must first be multiplied by 10^6
so that we can store them as uint256
values in Solidity.
We also employ some IF-statements, to avoid the error of multiplying null
values.
The filter we will be using is:
const postprocessJq = `{
latitude: (.coord.lat | if . != null then .*pow(10;6) else null end),
longitude: (.coord.lon | if . != null then .*pow(10;6) else null end),
description: .weather[0].description,
temperature: (.main.temp | if . != null then .*pow(10;6) else null end),
minTemp: (.main.temp_min | if . != null then .*pow(10;6) else null end),
windSpeed: (.wind.speed | if . != null then . *pow(10;6) end),
windDeg: .wind.deg
}`;
The last parameter missing is the ABI encoding for the FDC to store the processed data as.
We extract it from the artifacts for the MinTempAgency
generated by Hardhat, and more specifically from the parameters of the abiSignatureHack
function.
const abiSignature = `{
\"components\": [
{
\"internalType\": \"int256\",
\"name\": \"latitude\",
\"type\": \"int256\"
},
{
\"internalType\": \"int256\",
\"name\": \"longitude\",
\"type\": \"int256\"
},
{
\"internalType\": \"string\",
\"name\": \"description\",
\"type\": \"string\"
},
{
\"internalType\": \"int256\",
\"name\": \"temperature\",
\"type\": \"int256\"
},
{
\"internalType\": \"int256\",
\"name\": \"minTemp\",
\"type\": \"int256\"
},
{
\"internalType\": \"uint256\",
\"name\": \"windSpeed\",
\"type\": \"uint256\"
},
{
\"internalType\": \"uint256\",
\"name\": \"windDeg\",
\"type\": \"uint256\"
}
],
\"internalType\": \"struct DataTransportObject\",
\"name\": \"dto\",
\"type\": \"tuple\"
}`;
The intermediate steps more or less follow the ones in the JsonApi guide.
We make a POST request to the verifier server and thus prepare the attestation request.
We submit the ABI-encoded request to the FDC, wait for the current voting round to finalize, and then collect the data from a DA Layer server.
Finally, we decode the data to an IJsonAPi.Response
struct and add it to an IJsonAPi.Proof
struct, which we input into the resolvePolicy
function, along with the policy ID, of the MinTempAgency
at the address from the config.ts
file.
The entire script looks as follows.
import { agencyAddress } from "./config";
import { MinTempAgencyInstance } from "../../../typechain-types";
import {
prepareAttestationRequestBase,
submitAttestationRequest,
retrieveDataAndProofBase,
sleep,
} from "../../fdcExample/Base";
const {
JQ_VERIFIER_URL_TESTNET,
JQ_VERIFIER_API_KEY_TESTNET,
COSTON2_DA_LAYER_URL,
} = process.env;
const MinTempAgency = artifacts.require("MinTempAgency");
const policyId = 3;
const apiId = process.env.OPEN_WEATHER_API_KEY ?? "";
const units = "metric";
const postprocessJq = `{
latitude: (.coord.lat | if . != null then .*pow(10;6) else null end),
longitude: (.coord.lon | if . != null then .*pow(10;6) else null end),
description: .weather[0].description,
temperature: (.main.temp | if . != null then .*pow(10;6) else null end),
minTemp: (.main.temp_min | if . != null then .*pow(10;6) else null end),
windSpeed: (.wind.speed | if . != null then . *pow(10;6) end),
windDeg: .wind.deg
}`;
const abiSignature = `{
"components": [
{
"internalType": "int256",
"name": "latitude",
"type": "int256"
},
{
"internalType": "int256",
"name": "longitude",
"type": "int256"
},
{
"internalType": "string",
"name": "description",
"type": "string"
},
{
"internalType": "int256",
"name": "temperature",
"type": "int256"
},
{
"internalType": "int256",
"name": "minTemp",
"type": "int256"
},
{
"internalType": "uint256",
"name": "windSpeed",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "windDeg",
"type": "uint256"
}
],
"internalType": "struct DataTransportObject",
"name": "dto",
"type": "tuple"
}`;
const attestationTypeBase = "IJsonApi";
const sourceIdBase = "WEB2";
const verifierUrlBase = JQ_VERIFIER_URL_TESTNET!;
async function getPolicy(agency: MinTempAgencyInstance, id: number) {
const response = await agency.registeredPolicies(id);
const policy = {
latitude: response.latitude,
longitude: response.longitude,
startTimestamp: response.startTimestamp,
expirationTimestamp: response.expirationTimestamp,
minTempThreshold: response.minTempThreshold,
premium: response.premium,
coverage: response.coverage,
status: response.status,
id: response.id,
};
console.log("Policy:", policy, "\n");
return policy;
}
async function prepareUrl(policy: {
latitude: number;
longitude: number;
}): Promise<string> {
return `https://api.openweathermap.org/data/2.5/weather?lat=${
policy.latitude / 10 ** 6
}&lon=${policy.longitude / 10 ** 6}&units=${units}&appid=${apiId}`;
}
async function prepareAttestationRequest(
apiUrl: string,
postprocessJq: string,
abiSignature: string,
) {
const requestBody = {
url: apiUrl,
postprocessJq,
abi_signature: abiSignature,
};
const url = `${verifierUrlBase}JsonApi/prepareRequest`;
const apiKey = JQ_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 retrieveDataAndProofBase(url, abiEncodedRequest, roundId);
}
async function resolvePolicy(
agency: MinTempAgencyInstance,
id: number,
proof: {
proof: string;
response_hex: string;
},
) {
console.log("Proof hex:", proof.response_hex, "\n");
const IJsonApiVerification = await artifacts.require("IJsonApiVerification");
const responseType =
IJsonApiVerification._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");
// Retry mechanism instead of while(true)
for (let attempt = 1; attempt <= 5; attempt++) {
try {
const transaction = await agency.resolvePolicy(id, {
merkleProof: proof.proof,
data: decodedResponse,
});
console.log("Transaction:", transaction.tx, "\n");
return;
} catch (error) {
console.error(`Attempt ${attempt} failed:`, error, "\n");
await sleep(20000);
}
}
throw new Error("resolvePolicy failed after 5 attempts");
}
async function main() {
const agency: MinTempAgencyInstance = await MinTempAgency.at(agencyAddress);
console.log("MinTempAgency:", agency.address, "\n");
const policy = await getPolicy(agency, policyId);
const apiUrl = await prepareUrl(policy);
const data = await prepareAttestationRequest(
apiUrl,
postprocessJq,
abiSignature,
);
console.log("Data:", data, "\n");
const abiEncodedRequest = data.abiEncodedRequest;
const roundId = await submitAttestationRequest(abiEncodedRequest);
const proof = await retrieveDataAndProof(abiEncodedRequest, roundId);
await resolvePolicy(agency, policyId, proof);
}
main().then(() => {
process.exit(0);
});
The two remaining scripts handle the remaining two cases for a policy to reach the end of its lifetime.
The first settles an Open
policy that has reached its expiration timestamp.
It does so by calling the expirePolicy
function of the MinTempAgency
at the address from the config.ts
file.
import { agencyAddress } from "./config";
import { MinTempAgencyInstance } from "../../../typechain-types";
const MinTempAgency = artifacts.require("MinTempAgency");
// yarn hardhat run scripts/weatherInsurance/minTemp/expirePolicy.ts --network coston2
const policyId = 0;
async function main() {
const agency: MinTempAgencyInstance = await MinTempAgency.at(agencyAddress);
console.log("MinTempAgency:", agency.address, "\n");
const transaction = await agency.expirePolicy(policyId);
console.log("Transaction:", transaction.tx, "\n");
}
main().then(() => {
process.exit(0);
});
The second function settles an Unclaimed
policy that has reached it expiration timestamp, through the retireUnclaimedPolicy
of the MinTempAgency
contract.
import { agencyAddress } from "./config";
import { MinTempAgencyInstance } from "../../../typechain-types";
const MinTempAgency = artifacts.require("MinTempAgency");
// yarn hardhat run scripts/weatherInsurance/minTemp/retireUnclaimedPolicy.ts --network coston2
const policyId = 1;
async function main() {
const agency: MinTempAgencyInstance = await MinTempAgency.at(agencyAddress);
console.log("MinTempAgency:", agency.address, "\n");
const transaction = await agency.retireUnclaimedPolicy(policyId);
console.log("Transaction:", transaction.tx, "\n");
}
main().then(() => {
process.exit(0);
});
Modifications and enhancements
In the last section of this guide, we will describe several options for improving the described example. We can diversify the offered policy types, which would require only a small adjustment to the existing code. But an even further improvement and a paradigm shift would be to issue tokens to insurers and policyholders.
Additional data
An example of how the policy types can be extended is provided in the Flare Hardhat starter repository.
The WeatherIdAgency
checks that the ID of the current weather, as described by the OpenWeather specification, matches or exceeds the weather code threshold within the policy.
Other simple modification options are:
- maximum temperature
- atmospheric pressure
- humidity
- rainfall
- snowfall
- visibility
- wind speed
Issuing tokens
Instead of running the insurance agency as a registry, we could issue tokens to represent the policies. One option would be to create an NFT when a policy is created, representing the policyholder's position, and a second NFT for the insurer's position when a policy is claimed. This would prevent either party's funds from being locked within a contract for the duration of the policy.
Another option would be to issue ERC1155 tokens expressing the stake. It would enable the trade of fractions of policies while optimizing gas consumption.
No doubt there is an even better token type for such use case. But that goes beyond the purpose of this guide.