Create a smart contract that triggers actions when a geospatial policy check passes — “is the user within 500m of the Eiffel Tower?”
About location verification: This guide uses location data as input, but doesn’t verify where that data came from. GPS is spoofable. We’re working on the first Location Proof plugins to provide evidence-backed location claims — these are still in development and will integrate with the flow shown here.
Create a location attestation for the Eiffel Tower that can be referenced by UID.
// Define the Eiffel Tower as a pointconst eiffelTowerLocation = { type: 'Point', coordinates: [2.2945, 48.8584] // [longitude, latitude]};// Create an onchain location attestationconst eiffelTower = await astral.location.onchain.create({ location: eiffelTowerLocation, memo: 'Eiffel Tower - Iconic Paris landmark'});console.log('Eiffel Tower UID:', eiffelTower.uid);console.log('Transaction hash:', eiffelTower.txHash);// Save this UID - it's a reference to this location
Location attestations follow the Location Protocol v0.2 schema (verification in progress — see Location Records).
Anyone can now reference the Eiffel Tower by UID instead of hardcoding coordinates.
Prerequisite: Before running this step, you need to deploy your resolver contract and register your schema with EAS. See the Prerequisite section below — the RESOLVER_SCHEMA_UID from that step is required here.
When the user is ready to claim, submit their location to Astral for policy evaluation.
Location source matters: The userCoords below comes from GPS, which is spoofable. We’re working on Location Proof plugins to provide evidence-backed location claims — these are still in development.
// User's location (from GPS or location proof plugin)const userCoords = { type: 'Point', coordinates: [2.2951, 48.8580]};// Create user's location attestationconst userLocation = await astral.location.onchain.create({ location: userCoords});// Check proximity using compute moduleconst result = await astral.compute.within( userLocation.uid, // User's location eiffelTower.uid, // Target landmark 500, // Radius in meters { schema: RESOLVER_SCHEMA_UID, recipient: wallet.address });// Behind the scenes:// 1. Astral computes ST_DWithin in PostGIS (inside TEE)// 2. Signs a policy attestation with the result// 3. Returns delegated attestation for you to submitconsole.log('Within 500m:', result.result); // true or falseconsole.log('Input references:', result.inputRefs);// Submit onchain if policy passedif (result.result) { const submission = await astral.compute.submit(result.delegatedAttestation); console.log('Policy attestation UID:', submission.uid);}
The service runs inside EigenCompute’s TEE, providing verifiable execution. The signed attestation proves the computation was performed correctly.
For UX feedback: Use Turf.js for instant client-side distance calculations before submitting to Astral for verified computation.
So far, we’ve performed a geospatial policy check in a verifiable computing environment. How do we connect this to smart contracts?EAS Resolvers are smart contracts that receive callbacks when attestations are submitted to the EAS contract. When you submit a Policy Attestation to EAS with a schema that has a resolver attached, EAS automatically calls your resolver’s onAttest function with the attestation data. This is where you implement your business logic.Here’s an example resolver that gates an action based on the policy result:
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;import "@eas/contracts/resolver/SchemaResolver.sol";import "@openzeppelin/contracts/token/ERC721/ERC721.sol";contract EiffelTowerNFT is SchemaResolver, ERC721 { address public astralSigner; uint256 public nextTokenId = 1; mapping(address => bool) public hasMinted; constructor(IEAS eas, address _astralSigner) SchemaResolver(eas) ERC721("Eiffel Tower Visitor", "EIFFEL") { astralSigner = _astralSigner; } function onAttest( Attestation calldata attestation, uint256 /*value*/ ) internal override returns (bool) { // 1. Verify attestation is from Astral require(attestation.attester == astralSigner, "Not from Astral"); // 2. Decode the policy result ( bool policyPassed, bytes32[] memory inputRefs, uint64 timestamp, string memory operation ) = abi.decode( attestation.data, (bool, bytes32[], uint64, string) ); // 3. Verify policy passed require(policyPassed, "Not close enough"); // 4. One mint per address require(!hasMinted[attestation.recipient], "Already minted"); // 5. Mint NFT atomically hasMinted[attestation.recipient] = true; _mint(attestation.recipient, nextTokenId++); return true; } function onRevoke(Attestation calldata, uint256) internal pure override returns (bool) { return false; }}