Research Preview — APIs may change.
GitHub
Parametric insurance
You’re building crop insurance. When a qualifying weather event occurs within a certain distance of insured farmland, the policy pays out — without a manual claims process or an adjuster visit — once the spatial condition is verified.
Astral verifies the distance computation between the inputs you supply. It does not establish that a qualifying weather event actually occurred, or that the weather-station coordinates are truthful — those come from outside the system (an oracle, a data feed, a registered location). The signed result is one trustworthy link in the chain, not the whole chain. Design the event-reporting and location inputs with the same care, and be explicit with policyholders about what is verified and what is trusted. This is a Research Preview, not a production insurance product.
How it works
The insurance contract stores a farmland location UID and references to weather station locations
When a weather event is reported, the system computes the distance from the event to the insured farmland
If the distance is below the policy threshold, the payout triggers automatically
The signed result provides auditable proof that the spatial condition was met
The flow
Register locations
Both the insured farmland and weather station locations are registered as onchain location records.
import { AstralSDK } from '@decentralized-geo/astral-sdk' ;
const astral = new AstralSDK ({ chainId: 84532 , signer: wallet });
// Register farmland boundary
const farmland = await astral . location . onchain . create ({
location: {
type: 'Polygon' ,
coordinates: [[
[ - 95.123 , 38.456 ],
[ - 95.120 , 38.456 ],
[ - 95.120 , 38.460 ],
[ - 95.123 , 38.460 ],
[ - 95.123 , 38.456 ]
]]
},
memo: "Insured farmland parcel"
});
// Register weather station
const weatherStation = await astral . location . onchain . create ({
location: { type: 'Point' , coordinates: [ - 95.200 , 38.470 ] },
memo: "NOAA weather station KS-042"
});
Compute distance when an event occurs
When a qualifying weather event is reported at the station, compute the distance to the insured farmland.
async function checkPolicyTrigger (
farmlandUID : string ,
weatherStationUID : string ,
thresholdKm : number ,
wallet : Signer
) {
const astral = new AstralSDK ({ chainId: 84532 , signer: wallet });
// Compute distance from weather event to farmland.
// Args are positional: (from, to, options). The numeric result is in meters.
const result = await astral . compute . distance (
weatherStationUID ,
farmlandUID ,
{ schema: SCHEMA_UID , recipient: INSURANCE_CONTRACT_ADDRESS }
);
const distanceKm = result . result / 1000 ;
if ( distanceKm > thresholdKm ) {
return { triggered: false , distanceKm };
}
// Submit signed result to trigger payout
const { uid } = await astral . compute . submit ({
attestation: result . attestation ,
delegatedAttestation: result . delegatedAttestation ,
});
return { triggered: true , distanceKm , attestationUID: uid };
}
Resolver contract
The insurance resolver decodes a numeric signed result and checks whether the distance is below the policy threshold.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0 ;
import "@eas/contracts/resolver/SchemaResolver.sol" ;
import "@openzeppelin/contracts/access/Ownable.sol" ;
contract ParametricInsurance is SchemaResolver , Ownable {
address public astralSigner;
struct Policy {
address beneficiary;
bytes32 farmlandUID;
bytes32 weatherStationUID;
uint256 thresholdCm; // Distance threshold in centimeters
uint256 payoutAmount;
bool active;
bool triggered;
}
mapping ( bytes32 => Policy) public policies;
event PolicyCreated ( bytes32 indexed policyId , address beneficiary , uint256 payout );
event PolicyTriggered ( bytes32 indexed policyId , uint256 distanceCm );
constructor ( IEAS eas , address _astralSigner )
SchemaResolver (eas)
Ownable (msg.sender)
{
astralSigner = _astralSigner;
}
function createPolicy (
bytes32 policyId ,
address beneficiary ,
bytes32 farmlandUID ,
bytes32 weatherStationUID ,
uint256 thresholdKm
) external payable onlyOwner {
require ( msg .value > 0 , "Must fund payout" );
policies[policyId] = Policy ({
beneficiary : beneficiary,
farmlandUID : farmlandUID,
weatherStationUID : weatherStationUID,
thresholdCm : thresholdKm * 100000 , // km to cm
payoutAmount : msg .value,
active : true ,
triggered : false
});
emit PolicyCreated (policyId, beneficiary, msg .value);
}
function onAttest (
Attestation calldata attestation ,
uint256 /* value */
) internal override returns ( bool ) {
require (attestation.attester == astralSigner, "Not from Astral" );
// Decode numeric signed result (distance)
(
uint256 distanceCm,
string memory units,
bytes32 [] memory inputRefs,
uint64 timestamp,
string memory operation
) = abi . decode (
attestation.data,
( uint256 , string , bytes32 [], uint64 , string )
);
// Verify this is a distance computation
require (
keccak256 ( bytes (operation)) == keccak256 ( bytes ( "distance" )),
"Wrong operation"
);
// Extract policy ID from recipient
bytes32 policyId = bytes32 ( uint256 ( uint160 (attestation.recipient)));
Policy storage policy = policies[policyId];
require (policy.active, "Policy not active" );
require ( ! policy.triggered, "Already triggered" );
// Verify correct locations were used
require (inputRefs.length >= 2 , "Invalid inputs" );
require (inputRefs[ 0 ] == policy.weatherStationUID, "Wrong weather station" );
require (inputRefs[ 1 ] == policy.farmlandUID, "Wrong farmland" );
// Check if distance is below threshold
require (distanceCm <= policy.thresholdCm, "Distance exceeds threshold" );
// Trigger payout
policy.triggered = true ;
( bool success, ) = policy.beneficiary.call{value : policy.payoutAmount}( "" );
require (success, "Payout failed" );
emit PolicyTriggered (policyId, distanceCm);
return true ;
}
function onRevoke ( Attestation calldata , uint256 )
internal pure override returns ( bool )
{
return false ;
}
function updateAstralSigner ( address newSigner ) external onlyOwner {
astralSigner = newSigner;
}
}
Why this matters
Traditional crop insurance requires:
Filing a claim
Waiting for an adjuster
Disputing coverage decisions
Parametric insurance reduces much of that. The spatial condition is defined upfront, the distance computation is independently verifiable, and the payout can be automated against it. The signed result is auditable proof that the spatial condition was computed correctly on the given inputs — the remaining trust (that the event occurred, that the station data is honest) sits with whatever feeds those inputs.
Variations
Flood insurance — use intersects to check if a flood polygon overlaps insured property
Wildfire proximity — use distance from fire perimeter to insured structures
Hurricane path — use within to check if insured property falls within the projected storm path
Next: Geofence compliance Prove an asset stayed within an approved boundary