A location proof is a location claim bundled with evidence — stamps from proof-of-location systems. The proof carries everything needed to assess the claim’s credibility.This guide walks you through creating a claim, collecting stamps, bundling them into a proof, and interpreting the verification result.
Claim — an assertion that a subject was at a location during a time window
Stamps — evidence from one or more proof-of-location systems that support (or contradict) the claim
The verification process evaluates the stamps against the claim and produces a credibility assessment — not a simple yes/no, but a structured evaluation of how strong the evidence is.
GeoJSON geometry — where the subject claims to have been
subject
Identifier for the entity making the claim (Ethereum address, DID, etc.)
radius
Spatial uncertainty in meters — you cannot claim presence at an exact point
time
Temporal bounds as Unix timestamps (start and end)
eventType
What kind of event: "presence", "transaction", "delivery"
The radius field is required. Every location claim involves spatial uncertainty. Claiming a smaller radius requires stronger evidence to achieve the same credibility score.
Stamps are evidence from proof-of-location plugins. Each plugin collects signals from its PoL system and produces a stamp:
import { AstralSDK, MockPlugin } from '@decentralized-geo/astral-sdk';const astral = new AstralSDK({ chainId: 84532 });// Register a plugin and collect signals. The SDK ships MockPlugin for// local development; on a real device, evidence comes from a source like// the ProofMode app. stamps.collect returns an array.astral.plugins.register(new MockPlugin({ name: 'mock-1', lat: 37.7749, lon: -122.4194 }));const signals = await astral.stamps.collect({ plugins: ['mock-1'] });const unsignedStamp = await astral.stamps.create({ plugin: 'mock-1' }, signals[0]);const stamp1 = await astral.stamps.sign({ plugin: 'mock-1' }, unsignedStamp, deviceSigner);
For stronger verification, collect stamps from multiple independent systems — independence is what cross-correlation rewards, so two stamps from the same source add little. Real independence means genuinely different proof-of-location systems (for example the ProofMode app plus a network-based plugin). Those client plugins are still being built; the shape is the same:
// A second stamp from an independent system (illustrated with a second// mock instance; in practice this would be a different PoL system).astral.plugins.register(new MockPlugin({ name: 'mock-2', lat: 37.7750, lon: -122.4193 }));const witnessSignals = await astral.stamps.collect({ plugins: ['mock-2'] });const unsignedStamp2 = await astral.stamps.create({ plugin: 'mock-2' }, witnessSignals[0]);const stamp2 = await astral.stamps.sign({ plugin: 'mock-2' }, unsignedStamp2, nodeSigner);
// `mode: 'tee'` routes to the hosted service; the default 'local' mode// evaluates in-process and returns the credibility vector directly.const result = await astral.proofs.verify(proof, { mode: 'tee', chainId: 84532 });
The verification result is a structured credibility assessment, not a simple pass/fail:
The exact structure of the credibility vector is an open research question and will change. The fields below are illustrative of the current shape, not a stable contract.
These dimensions are a preliminary sketch — their exact structure and metrics are an active research area and will change. The credibility vector is a multidimensional assessment grouped into four dimensions — each an object of metrics, not a single score:
Dimension
What it measures
Spatial
How closely the stamps’ observed locations match the claimed location (mean/max distance, fraction within the claimed radius)
Temporal
How well the stamps’ time windows overlap the claimed time window
Validity
Fraction of stamps with valid signatures, structure, and consistent signals
Independence
How independent and corroborating the sources are (unique-plugin ratio, spatial agreement)
There is no top-level confidence field. Collapsing the vector into one number requires deciding which dimensions matter most — that judgment belongs to your application, not to Astral. The SDK ships an exampleWeighting() helper you can use as a starting point, but you’re expected to define your own.
A credibility score is not a probability. Strong metrics mean the evidence is strong — not “an X% chance the claim is true.” Calibrating these to true probabilities is future work.
Multiple stamps from independent systems increase confidence because an attacker would need to compromise multiple unrelated systems simultaneously:
// Single stampconst singleResult = await astral.proofs.verify( astral.proofs.create(claim, [stamp1]), { mode: 'tee', chainId: 84532 });// Multi-stamp from independent systems: the independence dimension reflects// that the evidence is corroborated rather than redundantconst multiResult = await astral.proofs.verify( astral.proofs.create(claim, [stamp1, stamp2]), { mode: 'tee', chainId: 84532 });// multiResult.credibility.dimensions.independence.uniquePluginRatio → higher// multiResult.credibility.dimensions.independence.spatialAgreement → higher
The improvement comes from source independence. Redundant stamps from the same system do not meaningfully strengthen the assessment, but they do not weaken it either.
The level of evidence you need depends on the value of the transaction the proof underpins:
Low-stakes (check-in rewards, social proof) — a single stamp from a device attestation plugin may be sufficient.
Medium-stakes (delivery verification, access control) — two independent stamps provide meaningful forgery resistance.
High-stakes (insurance payouts, land records) — multiple independent stamps with high forgery resistance, plus onchain submission for an immutable audit trail.
Verified location proofs can serve as trusted inputs to geocomputation operations. This connects the verification pipeline to the spatial reasoning pipeline:
const verifiedProof = await astral.proofs.verify(proof, { mode: 'tee', chainId: 84532 });// Use the verified proof as input to a spatial operation. Compute args are// positional; a verified proof is passed as { verifiedProof } (the whole object).const result = await astral.compute.contains( approvedZonePolygonUID, // container { verifiedProof }, // containee — the verified location proof { schema: SCHEMA_UID });