Research Preview — Code snippets demonstrate the intended API and need testing against the actual implementation.
Build a Location-Gated Smart Contract
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.
What You’ll Learn
Register a reference location
Create a location attestation for the Eiffel Tower
Check the geospatial policy
Use Astral to verify proximity
Gate the smart contract
Use EAS resolvers to trigger onchain actions based on the policy result
Prerequisites
npm install @decentralized-geo/astral-sdk ethers
You’ll need:
- A wallet with testnet ETH (Base Sepolia)
- Basic TypeScript and Solidity knowledge
Step 1: Initialize the SDK
import { AstralSDK } from '@decentralized-geo/astral-sdk';
import { ethers } from 'ethers';
// Connect your wallet
const provider = new ethers.JsonRpcProvider('https://sepolia.base.org');
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
// Initialize SDK
const astral = new AstralSDK({
chainId: 84532, // Base Sepolia
signer: wallet,
apiUrl: 'https://api.astral.global'
});
Step 2: Register the Eiffel Tower
Create a location attestation for the Eiffel Tower that can be referenced by UID.
// Define the Eiffel Tower as a point
const eiffelTowerLocation = {
type: 'Point',
coordinates: [2.2945, 48.8584] // [longitude, latitude]
};
// Create an onchain location attestation
const 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.
Step 3: Check the Geospatial Policy
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 attestation
const userLocation = await astral.location.onchain.create({
location: userCoords
});
// Check proximity using compute module
const 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 submit
console.log('Within 500m:', result.result); // true or false
console.log('Input references:', result.inputRefs);
// Submit onchain if policy passed
if (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.
Step 4: The Smart Contract (EAS Resolver)
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: MIT
pragma 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;
}
}
Prerequisite: Deploy Resolver and Register Schema
You must complete this step before running Step 3. The RESOLVER_SCHEMA_UID is required for the policy check to trigger your resolver.
import { ethers } from 'ethers';
import { SchemaRegistry } from '@ethereum-attestation-service/eas-sdk';
// Deploy resolver
const EiffelNFT = await ethers.getContractFactory("EiffelTowerNFT");
const resolver = await EiffelNFT.deploy(
EAS_ADDRESS,
ASTRAL_SIGNER_ADDRESS // See security notes for signer management
);
await resolver.waitForDeployment();
// Register schema with EAS
const schemaRegistry = new SchemaRegistry(SCHEMA_REGISTRY_ADDRESS);
schemaRegistry.connect(wallet);
const schema = "bool result,bytes32[] inputRefs,uint64 timestamp,string operation";
const tx = await schemaRegistry.register({
schema,
resolverAddress: await resolver.getAddress(),
revocable: false
});
const receipt = await tx.wait();
const RESOLVER_SCHEMA_UID = receipt.logs[0].args.uid;
console.log('Schema UID:', RESOLVER_SCHEMA_UID);
// Use this UID in Step 3 when calling astral.compute.within()
The Complete Flow
import { AstralSDK } from '@decentralized-geo/astral-sdk';
import { ethers } from 'ethers';
// Setup
const provider = new ethers.JsonRpcProvider('https://sepolia.base.org');
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
const astral = new AstralSDK({
chainId: 84532,
signer: wallet,
apiUrl: 'https://api.astral.global'
});
// 0. Deploy resolver and register schema (see Prerequisite section)
const RESOLVER_SCHEMA_UID = '0x...'; // From schema registration
// 1. Reference location exists (or create it)
const EIFFEL_TOWER_UID = '0x...'; // Pre-registered landmark
// 2. User arrives — create location attestation
const userLocation = await astral.location.onchain.create({
location: { type: 'Point', coordinates: [2.2951, 48.8580] }
});
// 3. Check policy
const result = await astral.compute.within(
userLocation.uid,
EIFFEL_TOWER_UID,
500, // 500 meters
{ schema: RESOLVER_SCHEMA_UID, recipient: wallet.address }
);
console.log('Policy check result:', result.result);
// 4. Submit to trigger resolver and execute onchain action
if (result.result) {
const submission = await astral.compute.submit(result.delegatedAttestation);
console.log('Action triggered! Attestation UID:', submission.uid);
}
Alternative: Offchain Location Attestations
If you don’t need onchain location records, use offchain attestations to save gas:
// Create offchain attestation (no gas)
const userLocation = await astral.location.offchain.create({
location: { type: 'Point', coordinates: [2.2951, 48.8580] }
});
// Use the offchain attestation UID + URI
const result = await astral.compute.within(
{ uid: userLocation.uid, uri: 'ipfs://...' }, // Offchain reference
EIFFEL_TOWER_UID,
500,
{ schema: RESOLVER_SCHEMA_UID, recipient: wallet.address }
);
Next Steps