From f3efd8d910f0daf2d9d53cbd46cd7abb01768001 Mon Sep 17 00:00:00 2001 From: John X Date: Mon, 16 Feb 2026 18:33:16 -0500 Subject: [PATCH 01/17] docs: propose information architecture restructure Adds RESTRUCTURE.md (rationale, audience analysis, migration plan) and mint.proposed.json (new Mintlify navigation config). Key changes: lead with verifiable computation not blockchain, add trust model section, add agent integration as peer path, relocate Ethereum-specific concepts into guides. Terminology aligned with concept-map.md from docs-plan repo. --- RESTRUCTURE.md | 158 ++++++++++++++++++++++++++++++++++++++++ mint.proposed.json | 174 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 332 insertions(+) create mode 100644 RESTRUCTURE.md create mode 100644 mint.proposed.json diff --git a/RESTRUCTURE.md b/RESTRUCTURE.md new file mode 100644 index 0000000..2d6538b --- /dev/null +++ b/RESTRUCTURE.md @@ -0,0 +1,158 @@ +# Documentation restructure plan + +_Context: The v0 product works. The docs need to present it to a wider audience than Ethereum developers — anyone who needs verifiable spatial computation, including agent developers, logistics platforms, insurance, compliance. The blockchain integration is one path, not the identity._ + +_Source: Claude Code session `b7e7e54d-1e4a-4825-b342-abf678f74fe8` in `~/tmp-astral`._ + +_Canonical terminology: see `concept-map.md` in the docs-plan repo (`~/Code/astral/docs-plan/concept-map.md`). All docs pages should use the definitions established there. Key discipline: never say "attestation" when you mean "stamp" or "proof" — reserve "attestation" for EAS-specific contexts._ + +--- + +## Principles + +1. **Lead with the capability, not the delivery mechanism.** "Verifiable geospatial computation" is the headline. Blockchain is an integration path introduced after the reader understands what the system does. + +2. **Separate the concept from the quickstart.** The landing page sells the idea. The quickstart is for people who've already decided to try it. Don't mix them. + +3. **Task-oriented guides, not feature-oriented.** "How do I verify a delivery?" not "The contains endpoint." Each guide answers a question and is self-contained. + +4. **Trust is the product — make the trust model prominent.** If you're selling verifiability, be transparent about what's verified and what's assumed. Elevating this signals confidence; burying it signals doubt. + +5. **Blockchain and agent integration are peer paths.** Neither is privileged. A dapp developer goes to the blockchain guide. An agent developer goes to the agent guide. Both are first-class. + +6. **Examples are complete stories, not snippets.** Each example is a scenario: who you are, what you need, what you call, what you get back, what you do with it. + +## Audiences + +- **Any developer** who needs a spatial answer they can trust (the widest aperture) +- **Agent/AI developers** building autonomous systems with spatial reasoning needs +- **Blockchain developers** who want verifiable spatial results onchain +- **Enterprise evaluators** (insurance, logistics, compliance) assessing the product +- **Researchers** who want to understand the trust model and contribute + +## Proposed structure + +See `mint.proposed.json` for the Mintlify navigation config. + +### Getting Started +- **Introduction** — What is Astral. The problem (untrusted location data), the solution (verifiable spatial computation), who it's for. No jargon. No chain IDs. +- **Quickstart** — Zero to first API call. curl examples against the hosted service. Under 5 minutes. Include the "aha" moment: you sent two points, you got back a signed distance with a cryptographic receipt. +- **How it works** — Architecture in 60 seconds. Geometry in → PostGIS computes in TEE → signed result out. Diagram. Then: "the result can go anywhere — an agent, a smart contract, a database, a compliance report." + +### Core Concepts +Reference material for when people need to understand the underlying model. Terminology follows the concept map (`docs-plan/concept-map.md`) exactly — one canonical definition per term. + +- **Geographic features** — What the system operates on. GeoJSON geometries (points, polygons, lines), verified locations from location proofs, or references to previously computed results. The provenance of a feature matters: a raw coordinate and a verified location are both valid inputs to geocomputation, but they carry different trust properties. This is the most fundamental concept — every API call starts with geographic features. +- **Geocomputation** — The six spatial operations (distance, area, length, contains, within, intersects), what they compute, when to use which. Results are signed with proof of correct execution. Geocomputation can take verified locations as input, bridging the two halves of the system. +- **Verifiable computation** — What "verifiable" means: TEE execution, signed results, input hashing. What the signature covers. What you're trusting. Applies to both geocomputation and the evaluation function. +- **Location proofs** — The full lifecycle from the concept map: location claims → PoL systems → signals → location stamps → location evidence → location proofs → evaluation function → credibility scores → weighting schemes → decisions. This is the deeper conceptual contribution. Each term has a precise definition; the page should walk through the dependency graph. +- **Result format** — How signed results are structured, what the signature covers, how to decode and verify independently. This is the universal format — not EAS-specific. Any integrator (agent, backend, smart contract) needs to understand how to consume and verify a result. +- **Privacy model** — Encrypted inputs, what the TEE operator can and can't see, current guarantees. The TEE accepts encrypted inputs, decrypts inside the enclave, computes, and returns only the result — the operator never sees raw location data. + +### Guides +Task-oriented walkthroughs. +- **Local development setup** — Clone, Docker, env config, health check, first local API call. Thorough. Covers port conflicts, env file loading, platform notes. +- **Calling the API** — Request format (from/to/chainId), geographic feature inputs (raw GeoJSON, verified locations, UID references), authentication, interpreting responses, error handling. The "I want to integrate this into my backend" guide. +- **Verifying location proofs** — Submitting location proofs to the verify API, understanding credibility scores, applying weighting schemes, using plugins. +- **Blockchain integration** — EAS attestations, delegated signing, onchain submission via SDK, chain configuration, schema UIDs. Everything Ethereum lives here. +- **Agent integration** — Using Astral as a spatial oracle in agent workflows. How to call it from an agent framework, how to use the result in decision-making. +- **Building verification plugins** — Writing a new PoL system adapter for the verify module. Plugin interface, registration, testing. + +### Use Cases +Complete, runnable scenarios. Each one is a story with a protagonist, a problem, and a solution. +- **Delivery verification** — "Was the courier within the delivery geofence?" (within/contains) +- **Parametric insurance** — "Was the weather event close enough to trigger the policy?" (distance) +- **Geofence compliance** — "Did the drone stay within the approved corridor?" (contains) +- **Onchain attestation** — End-to-end: compute → sign → submit onchain (the full blockchain flow) + +### API Reference +Exhaustive, mechanical, one page per endpoint. +- **Overview** — Base URL, authentication, rate limits, error format, common request fields +- **Compute API** — distance, area, length, contains, within, intersects (one page each) +- **Verify API** — stamp, proof, plugins +- **Records API** — overview, list, get, stats, config (existing) + +### SDK Reference +Mirrors the SDK's module structure. +- **Overview** — What the SDK does, when to use it vs. raw API +- **Installation** — pnpm install, configuration, initialization +- **Location module** — Location format handling, extensions +- **Compute module** — SDK compute methods (wraps the API) +- **Verify module** — SDK verify methods +- **EAS module** — Onchain submission, chain config, schema management +- **Types** — TypeScript type reference +- **Migration** — Upgrading from previous versions + +### Trust Model +Dedicated section because trust is the product. +- **Architecture** — TEE, PostGIS, stateless model, container design, what runs where +- **What is verified** — What the signature covers, input hashing, computation reproducibility +- **What you are trusting** — Honest accounting of assumptions. TEE hardware trust, current state vs. target state with full remote attestation. The gap between "signed by a key" and "signed by an attested enclave." +- **Security considerations** — Threat model, known limitations, responsible disclosure + +### Resources +- **Playground** — Interactive tool +- **Staging environment** — Testing against hosted service +- **Schema registry** — EAS schema UIDs by chain +- **Research** — Links to papers, research agenda, location proofs framework +- **FAQ** +- **Changelog** + +## Concept map → docs page mapping + +How each term from `concept-map.md` maps to a docs page: + +| Concept map term | Docs page | Notes | +|---|---|---| +| Geographic feature (new) | `concepts/geographic-features` | Not in concept map yet — should be added. The input type for all operations. | +| Geocomputation | `concepts/geocomputation` | The six operations + proof of correct execution | +| TEE | `concepts/verifiable-computation` | Implementation detail, not a core concept (per concept map) | +| Location claim | `concepts/location-proofs` | First half of the lifecycle | +| PoL system, signals, location stamp | `concepts/location-proofs` | Evidence production chain | +| Location evidence, location proof | `concepts/location-proofs` | Composition and bundling | +| Evaluation function, credibility scores | `concepts/location-proofs` | Assessment pipeline | +| Weighting scheme | `concepts/location-proofs` | Application-specific decision logic | +| Location proof plugin | `guides/building-plugins` | How to connect a new PoL system | +| EAS, resolver | `guides/blockchain-integration` | Blockchain-specific delivery mechanism | +| Result format (signed output) | `concepts/result-format` | Universal — how any integrator consumes results | + +### Terminology discipline + +- **"Credibility scores"** (casual) / **"credibility vector"** (formal). Use "scores" in guides and use cases, define "vector" on first use in the concepts page. +- **"Attestation"** only in EAS/blockchain contexts. Never use it when you mean stamp, proof, or signed result. +- **"Location stamp"** not "location attestation." Stamps come from PoL systems. Attestations are EAS artifacts. +- **"Geographic feature"** for inputs. Covers raw GeoJSON, verified locations, and UID references. + +## What changes from current structure + +| Current | Proposed | Why | +|---|---|---| +| Name: "Astral Location Services" | Name: "Astral" (or "Astral Docs") | The service is one component, not the whole product | +| Introduction leads with EAS/attestations | Introduction leads with verifiable spatial computation | Wider audience | +| Concepts: location-attestations, policy-attestations, eas-resolvers | These move into guides/blockchain-integration | Blockchain-specific concepts don't belong in universal concepts section | +| Concepts: geospatial-operations | Renamed to "geocomputation," promoted to second concept (after geographic features) | Aligns with concept map terminology | +| No geographic features concept | `concepts/geographic-features` | Foundation concept — what the system operates on, including provenance | +| Concepts: attestation-format | Renamed to "result-format" | The signed result is universal; EAS encoding is blockchain-specific | +| Guides: location-gated-nft, geofenced-token | Move to use-cases (blockchain-specific examples) or fold into blockchain guide | These are blockchain use cases, not general guides | +| No trust model section | Dedicated trust-model section | Trust is the product | +| No agent integration | guides/agent-integration | Peer path with blockchain | +| No privacy docs | concepts/privacy-model | Key differentiator | +| No local setup guide | guides/local-development | The #1 onboarding issue from v0 review | + +## What to preserve + +- All existing API reference content (restructure, don't rewrite) +- All existing SDK reference content +- The playground and staging resources +- The schema registry +- Any existing content that's accurate — just relocate it + +## Migration approach + +1. Add "geographic feature" definition to `concept-map.md` in docs-plan +2. Write the new `mint.json` navigation (done — see `mint.proposed.json`) +3. Create stub files for new pages (title + one-line description) +4. Relocate existing content into the new structure +5. Fill in new pages (introduction rewrite, trust model, guides) +6. Review pass for terminology discipline (attestation→stamp/proof, credibility vector→scores) +7. Review pass for Ethereum jargon above the fold diff --git a/mint.proposed.json b/mint.proposed.json new file mode 100644 index 0000000..15f3467 --- /dev/null +++ b/mint.proposed.json @@ -0,0 +1,174 @@ +{ + "$schema": "https://mintlify.com/schema.json", + "name": "Astral", + "redirects": [ + { + "source": "/", + "destination": "/introduction" + } + ], + "logo": { + "dark": "/logo/dark.svg", + "light": "/logo/light.svg" + }, + "favicon": "/favicon.svg", + "colors": { + "primary": "#D4A63A", + "light": "#FEE9B3", + "dark": "#B8860B", + "anchors": { + "from": "#D4A63A", + "to": "#FEE9B3" + } + }, + "topbarLinks": [ + { + "name": "Playground", + "url": "https://playground.astral.global" + }, + { + "name": "GitHub", + "url": "https://github.com/AstralProtocol" + } + ], + "topbarCtaButton": { + "name": "Get Started", + "url": "/quickstart" + }, + "tabs": [ + { + "name": "SDK", + "url": "sdk" + }, + { + "name": "API Reference", + "url": "api-reference" + } + ], + "anchors": [ + { + "name": "GitHub", + "icon": "github", + "url": "https://github.com/AstralProtocol" + }, + { + "name": "Community", + "icon": "telegram", + "url": "https://t.me/+UkTOSXnDcDM5ZTBk" + } + ], + "navigation": [ + { + "group": "Getting Started", + "pages": [ + "introduction", + "quickstart", + "how-it-works" + ] + }, + { + "group": "Core Concepts", + "pages": [ + "concepts/geographic-features", + "concepts/geocomputation", + "concepts/verifiable-computation", + "concepts/location-proofs", + "concepts/result-format", + "concepts/privacy-model" + ] + }, + { + "group": "Guides", + "pages": [ + "guides/local-development", + "guides/calling-the-api", + "guides/verifying-location-proofs", + "guides/blockchain-integration", + "guides/agent-integration", + "guides/building-plugins" + ] + }, + { + "group": "Use Cases", + "pages": [ + "use-cases/delivery-verification", + "use-cases/parametric-insurance", + "use-cases/geofence-compliance", + "use-cases/onchain-attestation" + ] + }, + { + "group": "API Reference", + "pages": [ + "api-reference/overview", + { + "group": "Compute API", + "pages": [ + "api-reference/compute/distance", + "api-reference/compute/area", + "api-reference/compute/length", + "api-reference/compute/contains", + "api-reference/compute/within", + "api-reference/compute/intersects" + ] + }, + { + "group": "Verify API", + "pages": [ + "api-reference/verify/stamp", + "api-reference/verify/proof", + "api-reference/verify/plugins" + ] + }, + { + "group": "Records API", + "pages": [ + "api-reference/records/overview", + "api-reference/records/list", + "api-reference/records/get", + "api-reference/records/stats", + "api-reference/records/config" + ] + } + ] + }, + { + "group": "SDK Reference", + "pages": [ + "sdk/overview", + "sdk/installation", + "sdk/location", + "sdk/compute", + "sdk/verify", + "sdk/eas", + "sdk/types", + "sdk/migration" + ] + }, + { + "group": "Trust Model", + "pages": [ + "trust-model/architecture", + "trust-model/what-is-verified", + "trust-model/what-you-are-trusting", + "trust-model/security" + ] + }, + { + "group": "Resources", + "pages": [ + "resources/playground", + "resources/staging", + "resources/schemas", + "resources/research", + "resources/faq", + "resources/changelog" + ] + } + ], + "footerSocials": { + "twitter": "https://twitter.com/AstralProtocol", + "github": "https://github.com/AstralProtocol", + "telegram": "https://t.me/+UkTOSXnDcDM5ZTBk" + } +} From b61d47d45747af52c5fc755c620dc81a33cdc157 Mon Sep 17 00:00:00 2001 From: John X Date: Mon, 16 Feb 2026 21:24:53 -0500 Subject: [PATCH 02/17] docs: swap in new information architecture navigation Replaces mint.json with proposed navigation structure: - Getting Started, Core Concepts, Guides, Use Cases sections - API Reference split into Compute, Verify, Records groups - New Trust Model section - Resources section with research and changelog --- mint.json | 66 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/mint.json b/mint.json index 760c8d9..15f3467 100644 --- a/mint.json +++ b/mint.json @@ -1,6 +1,6 @@ { "$schema": "https://mintlify.com/schema.json", - "name": "Astral Location Services", + "name": "Astral", "redirects": [ { "source": "/", @@ -28,7 +28,7 @@ }, { "name": "GitHub", - "url": "https://github.com/AstralProtocol/astral-location-services" + "url": "https://github.com/AstralProtocol" } ], "topbarCtaButton": { @@ -69,26 +69,32 @@ { "group": "Core Concepts", "pages": [ - "concepts/location-attestations", + "concepts/geographic-features", + "concepts/geocomputation", + "concepts/verifiable-computation", "concepts/location-proofs", - "concepts/geospatial-operations", - "concepts/policy-attestations", - "concepts/eas-resolvers", - "concepts/verifiable-computation" + "concepts/result-format", + "concepts/privacy-model" ] }, { "group": "Guides", "pages": [ - "guides/location-gated-nft", - "guides/geofenced-token", - "guides/delivery-verification" + "guides/local-development", + "guides/calling-the-api", + "guides/verifying-location-proofs", + "guides/blockchain-integration", + "guides/agent-integration", + "guides/building-plugins" ] }, { "group": "Use Cases", "pages": [ - "use-cases" + "use-cases/delivery-verification", + "use-cases/parametric-insurance", + "use-cases/geofence-compliance", + "use-cases/onchain-attestation" ] }, { @@ -96,20 +102,22 @@ "pages": [ "api-reference/overview", { - "group": "Verify API", + "group": "Compute API", "pages": [ - "api-reference/verify" + "api-reference/compute/distance", + "api-reference/compute/area", + "api-reference/compute/length", + "api-reference/compute/contains", + "api-reference/compute/within", + "api-reference/compute/intersects" ] }, { - "group": "Compute API", + "group": "Verify API", "pages": [ - "api-reference/distance", - "api-reference/contains", - "api-reference/within", - "api-reference/intersects", - "api-reference/area", - "api-reference/length" + "api-reference/verify/stamp", + "api-reference/verify/proof", + "api-reference/verify/plugins" ] }, { @@ -130,21 +138,31 @@ "sdk/overview", "sdk/installation", "sdk/location", - "sdk/verify", "sdk/compute", + "sdk/verify", "sdk/eas", + "sdk/types", "sdk/migration" ] }, + { + "group": "Trust Model", + "pages": [ + "trust-model/architecture", + "trust-model/what-is-verified", + "trust-model/what-you-are-trusting", + "trust-model/security" + ] + }, { "group": "Resources", "pages": [ "resources/playground", "resources/staging", "resources/schemas", - "resources/security", - "resources/roadmap", - "resources/faq" + "resources/research", + "resources/faq", + "resources/changelog" ] } ], From 3029cb5076e622877fca64c2b97c8ded106257f9 Mon Sep 17 00:00:00 2001 From: John X Date: Mon, 16 Feb 2026 21:25:02 -0500 Subject: [PATCH 03/17] docs: rewrite introduction, quickstart, and how-it-works - Introduction: lead with verifiable spatial computation, not Ethereum - Quickstart: curl-first approach, 4 clear steps, onchain is optional - How-it-works: rename Policy Engine to Compute Engine, update diagrams Reframes the entry point for a wider audience beyond blockchain developers. Blockchain integration is positioned as one path among several (agents, backends, compliance). --- how-it-works.mdx | 108 ++++++-------- introduction.mdx | 176 ++++++++-------------- quickstart.mdx | 378 +++++++++++------------------------------------ 3 files changed, 193 insertions(+), 469 deletions(-) diff --git a/how-it-works.mdx b/how-it-works.mdx index cb5c12c..7c50be4 100644 --- a/how-it-works.mdx +++ b/how-it-works.mdx @@ -1,17 +1,17 @@ --- -title: "How It Works" -description: "The architecture behind verifiable geospatial computation" +title: "How it works" +description: "The architecture behind verifiable spatial computation" --- - - **Research Preview** — Architecture is under active development. - +**Research preview** — APIs may change. [GitHub](https://github.com/AstralProtocol) -# How Astral Location Services Work +# How it works -Astral provides verifiable geospatial computation by combining three key technologies: **PostGIS** for spatial operations, **EigenCompute TEE** for verifiable execution, and **EAS** for attestation management. +Astral runs spatial computations inside a Trusted Execution Environment and returns signed results. Here's what happens under the hood. -## System Architecture +The core flow: geometry in, TEE computes, signed result out. The result can go anywhere — an agent, a smart contract, a database, a compliance report. + +## System architecture ```mermaid flowchart LR @@ -20,10 +20,10 @@ flowchart LR SDK[Astral SDK] end - subgraph Astral["Astral Services"] + subgraph Astral["Astral"] API[API Gateway] subgraph TEE["EigenCompute TEE"] - Engine[Policy Engine + PostGIS] + Engine[Compute Engine + PostGIS] end end @@ -40,43 +40,43 @@ flowchart LR EAS --> Resolver ``` -## Key Architectural Decisions +## Key architectural decisions - + PostGIS runs **inside** the Docker container, not as an external service. This is essential for verifiable computation in the TEE — no external dependencies means the entire execution environment is attested. - + Each request brings all required inputs. No persistent state between requests. This ensures determinism and simplifies verification — same inputs always produce same outputs. (Note: additional testing is needed to fully validate determinism across different environments.) - - Developers submit Policy Attestations onchain using EAS's delegated attestation pattern. Astral signs, developer pays gas, Astral is recorded as the attester. + + Developers submit signed results onchain using EAS's delegated attestation pattern. Astral signs, the developer pays gas, and Astral is recorded as the attester. -## The Computation Flow +## The computation flow - + Developer's app calls `astral.compute.within(uid1, uid2, 500)` via the SDK - - The compute service fetches/validates location attestations by UID, or accepts raw GeoJSON + + The compute service fetches and validates location records by UID, or accepts raw GeoJSON - + PostGIS executes the spatial operation (e.g., `ST_DWithin`) inside the TEE - - The service creates a Policy Attestation and signs it with the TEE-held key + + The service creates a signed result using the TEE-held key - Signed attestation returned to SDK, ready for offchain use or onchain submission + The signed result is returned to the SDK, ready for offchain use or onchain submission -## Verifiable Execution with EigenCompute +## Verifiable execution with EigenCompute -The Geospatial Policy Engine runs in **EigenCompute**, part of the EigenCloud ecosystem: +The compute engine runs in **EigenCompute**, part of the EigenCloud ecosystem: ```mermaid flowchart LR @@ -88,27 +88,27 @@ flowchart LR end Input[Request] --> API - API --> Output[Signed Attestation] + API --> Output[Signed Result] ``` PostGIS uses [GEOS](https://libgeos.org/) for geometry operations — the same C++ library used by QGIS, GDAL, and most professional geospatial software. See the [GEOS documentation](https://libgeos.org/usage/download/) for more on its reliability and widespread adoption. -### Verifiability Properties +### Verifiability properties -| Property | How It's Achieved | +| Property | How it's achieved | |----------|-------------------| -| **Input Verification** | Attestation signatures verified at TEE boundary | -| **Deterministic Computation** | Same inputs always produce same result | -| **Signed Output** | Service key signs Policy Attestation inside TEE | -| **TEE Attestation** | EigenCompute provides hardware attestation of execution | +| **Input verification** | Attestation signatures verified at TEE boundary | +| **Deterministic computation** | Same inputs always produce same result | +| **Signed output** | Service key signs the result inside the TEE | +| **TEE attestation** | EigenCompute provides hardware attestation of execution | -## Input Resolution +## Input resolution The service accepts multiple input formats: -| Input Format | Resolution | +| Input format | Resolution | |--------------|------------| | UID string | Fetch from EAS contracts (onchain attestations) | | `{ uid, uri }` | Fetch from URI, verify UID matches, verify signature | @@ -116,28 +116,12 @@ The service accepts multiple input formats: | Raw GeoJSON | Use directly, hash for `inputRefs` | - For offchain attestations, the UID is deterministically derived from the attestation data. Even when fetching from HTTPS (not content-addressed), we verify the fetched attestation produces the expected UID. Mismatch = reject. + For offchain attestations, the UID is deterministically derived from the attestation data. Even when fetching from HTTPS (not content-addressed), Astral verifies the fetched attestation produces the expected UID. Mismatch = reject. -## Delegated Attestation Flow +## Delegated attestation flow -```mermaid -sequenceDiagram - participant Dev as Developer App - participant SDK as Astral SDK - participant Svc as Compute Service - participant EAS as EAS Contracts - participant Res as Resolver - - Dev->>SDK: compute.within(...) - SDK->>Svc: POST /compute/within - Svc->>Svc: Execute PostGIS operation - Svc->>Svc: Sign attestation - Svc-->>SDK: Signed attestation + delegated sig - SDK->>EAS: Submit with Astral's signature - EAS->>Res: onAttest() callback - Res->>Res: Execute business logic -``` +For the full blockchain integration flow, see [Blockchain integration](/guides/blockchain-integration). The delegated attestation pattern means: - **Astral signs** the attestation data offchain @@ -149,7 +133,7 @@ The delegated attestation pattern means: Resolver contracts must be deployed and registered by the developer. Astral does not provide pre-built resolver contracts — you create them to implement your specific business logic. See [EAS Resolvers](/concepts/eas-resolvers) for patterns and examples. -## Trust Model +## Trust model There are two distinct trust questions in location-based systems: @@ -157,23 +141,23 @@ There are two distinct trust questions in location-based systems: This is the **location verification problem** — how do you know a user was actually at the location they claim? GPS is spoofable, and proving physical presence remains an open research problem. -We're actively working on this through our [Location Proof framework](https://collective.flashbots.net/t/towards-stronger-location-proofs/5323). For now, Astral Location Services trust the location data provided as input. +We're actively working on this through our [location proof framework](https://collective.flashbots.net/t/towards-stronger-location-proofs/5323). For now, Astral trusts the location data provided as input. ### 2. Was the computation performed correctly? -This is what Astral Location Services solve. Given location inputs, how do you prove the geospatial computation (distance, containment, etc.) was performed correctly? +This is what Astral solves. Given location inputs, how do you prove the geocomputation (distance, containment, etc.) was performed correctly? **Current approach:** - **Open source code**: The compute service code is public and auditable - **TEE execution**: Code runs inside EigenCompute's trusted execution environment -- **Astral signing key**: Attestations are signed by a key held inside the TEE +- **Astral signing key**: Results are signed by a key held inside the TEE - **Deterministic operations**: Same inputs produce same outputs (additional testing in progress) **Future improvements:** -- **AVS Consensus**: Multiple operators verify computations -- **ZK Proofs**: Cryptographic proof of correct execution -- **Decentralized Signers**: Multi-party attestation signing +- **AVS consensus**: Multiple operators verify computations +- **ZK proofs**: Cryptographic proof of correct execution +- **Decentralized signers**: Multi-party result signing - - Deep dive into Location Attestations + + How Astral represents spatial data diff --git a/introduction.mdx b/introduction.mdx index 645102c..bcee51c 100644 --- a/introduction.mdx +++ b/introduction.mdx @@ -1,166 +1,112 @@ --- title: "Introduction" -description: "Geospatial policy engine for Ethereum" +description: "Verifiable spatial computation" --- - - **Research Preview** — Astral Location Services are under active development and not yet production-ready. - APIs may change. We're building in public and welcome your feedback! - + + **Research preview** — Astral is under active development and not yet production-ready. APIs may change. We're building in public and welcome feedback. + -# What is Astral Location Services? +**Astral provides verifiable spatial computation.** Ask a spatial question — how far apart are these two points? Is this location inside that boundary? Do these regions overlap? — and get back a signed, verifiable answer. -Astral Location Services is a **geospatial policy engine** that connects location-based logic to smart contracts on Ethereum. Think of it as a **geofencing oracle** for onchain applications. - -We provide spatial operations (distance, containment, intersection) that run in a trusted execution environment and produce signed attestations you can use to trigger onchain actions. +The answer comes with cryptographic proof that it was computed correctly inside a Trusted Execution Environment. You can use it in an agent workflow, a backend service, a compliance report, or a smart contract. The result is the same regardless of where it ends up. - - Check if locations are within boundaries, enable region-based logic and territorial restrictions + + Distance, containment, intersection, area, and length — computed with proof of correct execution - - Trigger onchain actions based on real-world proximity, distance, and spatial relationships + + Evidence-based verification of location claims using multi-factor location proofs - - Verify users visited specific locations and reward them with tokens, NFTs, or access rights + + Use Astral as a spatial oracle in autonomous agent workflows - - Create verifiable location-based rules that execute automatically through EAS resolvers + + Submit signed results onchain as EAS attestations to trigger smart contract logic -## The Core Flow +## The core flow ```mermaid graph LR - A[Location Data] --> B[Geospatial Policy Engine] - B --> C[Policy Attestation] - C --> D[EAS.sol] - D --> E[Resolver Contract] - E --> F[Onchain Action] + A[Geographic features] --> B[Astral TEE] + B --> C[Signed result] + C --> D[Your application] ``` -1. **Input**: Location records — EAS attestations or raw GeoJSON -2. **Processing**: Geospatial computations in a verifiable environment (TEE with PostGIS) -3. **Output**: Signed Policy Attestations submitted to EAS, triggering resolver contracts +1. **Input**: Geographic features — points, polygons, lines, verified locations +2. **Compute**: Spatial operations execute inside a TEE with PostGIS +3. **Output**: A signed result you can verify independently + +## Quick example + +```bash +# How far is it from point A to point B? +curl -X POST https://api.astral.global/compute/distance \ + -H "Content-Type: application/json" \ + -d '{ + "from": { "type": "Point", "coordinates": [2.2945, 48.8584] }, + "to": { "type": "Point", "coordinates": [2.2951, 48.8580] }, + "chainId": 84532 + }' +``` -## Quick Example +The response includes the distance in meters, a cryptographic signature, and references to the inputs — everything needed to verify the result independently. ```typescript import { AstralSDK } from '@decentralized-geo/astral-sdk'; const astral = new AstralSDK({ chainId: 84532, - signer: wallet, apiUrl: 'https://api.astral.global' }); -// Create a location attestation -const location = await astral.location.onchain.create({ - location: { type: 'Point', coordinates: [2.2951, 48.8580] } +const result = await astral.compute.distance({ + from: { type: 'Point', coordinates: [2.2945, 48.8584] }, + to: { type: 'Point', coordinates: [2.2951, 48.8580] }, + chainId: 84532 }); -// Check if user is within 500m of a landmark -const result = await astral.compute.within( - location.uid, - landmarkUID, - 500, // meters - { schema: RESOLVER_SCHEMA_UID, recipient: userAddress } -); - -console.log(`Nearby: ${result.result}`); - -// Submit onchain (developer pays gas, Astral is attester) -if (result.result) { - await astral.compute.submit(result.delegatedAttestation); -} +console.log(`Distance: ${result.result} meters`); ``` -## Why Astral? - -### The Problem +And if you want it onchain: -Ethereum has no idea where things are happening. Smart contracts can't create location-aware logic or reason about real-world geography — they can't natively perform geospatial computations, verify location claims, or check spatial relationships like "is this point inside this boundary?" or "how far apart are these two locations?" +```typescript +// Submit as an EAS attestation +await astral.eas.submitDelegated(result.delegatedAttestation); +``` -Without verifiable geocomputation, any location-based policy check can't be trusted onchain. Even if you have verified location data, an attacker could simply fabricate the computation results. +## Why verifiable spatial computation? -### The Solution +### The problem -Astral solves this by providing: +Location data is untrusted everywhere. GPS is spoofable. Geocoding APIs are black boxes. When a delivery app says "the courier was within 100 meters," there's no way to independently verify that claim. The computation happened on someone's server, and you're taking their word for it. -1. **Verifiable Computation** — Spatial operations run in a TEE (trusted execution environment), producing cryptographically signed results -2. **Geospatial Policy Engine** — PostGIS-powered computations for distance, containment, intersection, and more -3. **EAS Integration** — Results are attestations that trigger resolver contracts for onchain actions +### The solution -This introduces **location as a new onchain primitive** — enabling smart contracts to reason about geography for the first time. +Astral makes spatial computation verifiable. The same PostGIS operations that power professional geospatial software — distance, containment, intersection — run inside a Trusted Execution Environment. The TEE signs each result with a key that only it holds. Anyone can verify the signature and confirm the computation was performed correctly on the stated inputs. - - **What about location verification?** GPS is spoofable, and verifying *where* a user actually is remains an open problem. We're developing the [Location Proof framework](https://collective.flashbots.net/t/towards-stronger-location-proofs/5323) to address this. Astral Location Services provide the geospatial policy layer that location proofs will plug into. - +This matters for any application where spatial answers have consequences: insurance payouts, delivery escrow, geofence compliance, access control, autonomous agent decisions. -## What You Can Build +## What you can build - - Token swaps that only work if you're in the region + + Escrow that releases when the courier arrives at the right location - - Collectibles for visiting landmarks + + Policies that trigger based on verified proximity to weather events - - Governance tokens for local residents + + Prove an asset stayed within an approved corridor - - Escrow that releases when packages arrive + + Onchain actions triggered by verified spatial relationships -## The SDK - -The Astral SDK provides a namespaced API for location attestations and geospatial computations: - -```typescript -// Location attestations -astral.location.offchain.create(input); -astral.location.onchain.create(input); - -// Geospatial computations -astral.compute.distance(from, to, options); -astral.compute.contains(container, containee, options); -astral.compute.within(geometry, target, radius, options); -astral.compute.submit(delegatedAttestation); -``` - - - Full SDK documentation - - -## Architecture Overview - -```mermaid -graph LR - subgraph Client["Developer Application"] - A[Astral SDK] - end - - subgraph TEE["EigenCompute TEE"] - B[Geospatial Policy Engine] - C[PostGIS] - B --> C - end - - subgraph Chain["Ethereum / Base"] - D[EAS Contracts] - E[Resolver Contract] - end - - A --> B - B --> D - D --> E -``` - -The Geospatial Policy Engine runs inside EigenCompute's TEE with PostGIS embedded, enabling verifiable computation. Policy Attestations are submitted to EAS contracts, which trigger your resolver contracts for onchain actions. - - Build your first location-gated smart contract + Your first verified spatial computation in 5 minutes diff --git a/quickstart.mdx b/quickstart.mdx index 19445d3..bbeddec 100644 --- a/quickstart.mdx +++ b/quickstart.mdx @@ -1,341 +1,135 @@ --- title: "Quickstart" -description: "Build a location-gated smart contract" +description: "Your first verified spatial computation in 5 minutes" --- - - **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](https://collective.flashbots.net/t/towards-stronger-location-proofs/5323) to provide evidence-backed location claims — these are still in development and will integrate with the flow shown here. - - -## What You'll Learn - - - - Create a location attestation for the Eiffel Tower - - - Use Astral to verify proximity - - - Use EAS resolvers to trigger onchain actions based on the policy result - - - -## Prerequisites - -```bash -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 - -```typescript -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. - -```typescript -// 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](https://github.com/DecentralizedGeo/location-protocol-spec/tree/v0.2-draft) schema (verification in progress — see [Location Records](/concepts/location-attestations#schema)). - 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](#prerequisite-deploy-resolver-and-register-schema) below — the `RESOLVER_SCHEMA_UID` from that step is required here. + **Research preview** — Astral is under active development and not yet production-ready. APIs may change. We're building in public and welcome feedback. -When the user is ready to claim, submit their location to Astral for policy evaluation. +This guide takes you from zero to a verified spatial computation. You'll call the API, understand the response, use the SDK, and optionally submit the result onchain. - - **Location source matters**: The `userCoords` below comes from GPS, which is spoofable. We're working on [Location Proof plugins](https://collective.flashbots.net/t/towards-stronger-location-proofs/5323) to provide evidence-backed location claims — these are still in development. - +## Step 1: Call the API -```typescript -// User's location (from GPS or location proof plugin) -const userCoords = { - type: 'Point', - coordinates: [2.2951, 48.8580] -}; +Compute the distance between the Eiffel Tower and a point across the Seine: -// Create user's location attestation -const userLocation = await astral.location.onchain.create({ - location: userCoords -}); +```bash +curl -X POST https://api.astral.global/compute/distance \ + -H "Content-Type: application/json" \ + -d '{ + "from": { "type": "Point", "coordinates": [2.2945, 48.8584] }, + "to": { "type": "Point", "coordinates": [2.3522, 48.8566] }, + "chainId": 84532 + }' +``` -// 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 +That's it. You just ran a verified spatial computation. + +## Step 2: Understand the response + +```json +{ + "result": 4520.37, + "operation": "distance", + "inputRefs": [ + "0xabc123...input_a_hash", + "0xdef456...input_b_hash" + ], + "signature": "0x7f8e9d...tee_signature", + "delegatedAttestation": { + "schema": "0x...", + "data": "0x...", + "signature": "0x...", + "attester": "0x...", + "recipient": "0x..." } -); - -// 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](https://turfjs.org/) for instant client-side distance calculations before submitting to Astral for verified computation. - +- **`result`**: The distance in meters between the two points. +- **`operation`**: The spatial operation that was performed. +- **`inputRefs`**: Hashes of the input geographic features, so you can verify which inputs were used. +- **`signature`**: The TEE's cryptographic signature over the result. +- **`delegatedAttestation`**: A pre-signed EAS attestation for optional onchain submission. ---- - -## 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: - -```solidity -// 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"); +The signature proves this result came from Astral's TEE. The inputRefs let you verify which geographic features were used. Together, they make the result independently verifiable. - // 5. Mint NFT atomically - hasMinted[attestation.recipient] = true; - _mint(attestation.recipient, nextTokenId++); +## Step 3: Use the SDK - return true; - } - - function onRevoke(Attestation calldata, uint256) - internal pure override returns (bool) - { - return false; - } -} +```bash +npm install @decentralized-geo/astral-sdk ``` ---- - -## 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. - - ```typescript -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"; +import { AstralSDK } from '@decentralized-geo/astral-sdk'; -const tx = await schemaRegistry.register({ - schema, - resolverAddress: await resolver.getAddress(), - revocable: false +const astral = new AstralSDK({ + chainId: 84532, + apiUrl: 'https://api.astral.global' }); -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() +// Distance between two points +const distance = await astral.compute.distance({ + from: { type: 'Point', coordinates: [2.2945, 48.8584] }, + to: { type: 'Point', coordinates: [2.3522, 48.8566] }, + chainId: 84532 +}); +console.log(`Distance: ${distance.result} meters`); + +// Is a point inside a polygon? +const inside = await astral.compute.contains({ + container: { + type: 'Polygon', + coordinates: [[[2.28, 48.85], [2.30, 48.85], [2.30, 48.87], [2.28, 48.87], [2.28, 48.85]]] + }, + geometry: { type: 'Point', coordinates: [2.2945, 48.8584] }, + chainId: 84532 +}); +console.log(`Inside: ${inside.result}`); ``` ---- +## Step 4 (optional): Submit onchain -## The Complete Flow +If you want the result available to smart contracts, submit it as an EAS attestation: ```typescript -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 wallet = new ethers.Wallet(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: - -```typescript -// Create offchain attestation (no gas) -const userLocation = await astral.location.offchain.create({ - location: { type: 'Point', coordinates: [2.2951, 48.8580] } +const result = await astral.compute.distance({ + from: eiffelTowerUID, + to: userLocationUID, + chainId: 84532, + schema: RESOLVER_SCHEMA_UID, + recipient: wallet.address }); -// 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 } -); +// Submit to EAS — triggers your resolver contract +const tx = await astral.eas.submitDelegated(result.delegatedAttestation); +console.log('Onchain:', tx.hash); ``` ---- +For the full blockchain flow — deploying resolvers, registering schemas — see [Blockchain integration](/guides/blockchain-integration). -## Next Steps +## Next steps - - Understand the building blocks + + Understand geographic features, stamps, and credibility scores - - Explore what you can build + + Walk through common workflows step by step - - Full SDK documentation + + Full API documentation - - Dive into the API details + + TypeScript SDK documentation From 88e95f15edf5a5211afaf4c05dd219adfb000fcd Mon Sep 17 00:00:00 2001 From: John X Date: Mon, 16 Feb 2026 21:25:12 -0500 Subject: [PATCH 04/17] docs: add and reorganize core concept pages New pages: - geocomputation.mdx (from geospatial-operations, terminology updated) - geographic-features.mdx (from location-attestations, provenance section) - result-format.mdx (from policy-attestations, universal framing) - privacy-model.mdx (new: TEE privacy guarantees and limitations) Edited: - verifiable-computation.mdx (trust model content extracted) - location-proofs.mdx (terminology: geocomputation, signed result) Original files preserved for reference. --- concepts/geocomputation.mdx | 233 ++++++++++++++++++++++++++++ concepts/geographic-features.mdx | 193 +++++++++++++++++++++++ concepts/location-proofs.mdx | 32 ++-- concepts/privacy-model.mdx | 64 ++++++++ concepts/result-format.mdx | 201 ++++++++++++++++++++++++ concepts/verifiable-computation.mdx | 146 +++-------------- 6 files changed, 731 insertions(+), 138 deletions(-) create mode 100644 concepts/geocomputation.mdx create mode 100644 concepts/geographic-features.mdx create mode 100644 concepts/privacy-model.mdx create mode 100644 concepts/result-format.mdx diff --git a/concepts/geocomputation.mdx b/concepts/geocomputation.mdx new file mode 100644 index 0000000..3621156 --- /dev/null +++ b/concepts/geocomputation.mdx @@ -0,0 +1,233 @@ +--- +title: "Geocomputation" +description: "Spatial computations with proof of correct execution" +--- + +**Research Preview** — APIs may change. [GitHub](https://github.com/AstralProtocol) + +# Geocomputation + +Astral provides verifiable spatial computations backed by **PostGIS** — the gold standard in geospatial databases with 20+ years of production use. + +All operations return [signed results](/concepts/result-format) with proof of correct execution — results that can be used offchain or submitted onchain to trigger smart contract logic. + + + PostGIS uses [GEOS](https://libgeos.org/) (Geometry Engine Open Source) under the hood for geometry operations. GEOS is the C++ library that powers most professional geospatial software. + + +## Available Operations + +### Measurements + +Operations that return **numeric** results (as [NumericResults](/concepts/result-format#numericpolicyattestation)): + +| Operation | Description | Unit | Geometry Types | PostGIS Function | +|-----------|-------------|------|----------------|------------------| +| `distance` | Nearest distance between two geometries | meters | Any | `ST_Distance` | +| `area` | Area of a polygon | square meters | Polygon, MultiPolygon | `ST_Area` | +| `length` | Length of a line | meters | LineString, MultiLineString | `ST_Length` | + +### Predicates + +Operations that return **boolean** results (as [BooleanResults](/concepts/result-format#booleanpolicyattestation)): + +| Operation | Description | PostGIS Function | +|-----------|-------------|------------------| +| `contains` | Is geometry B **entirely** inside geometry A? | `ST_Contains` | +| `within` | Is geometry within a specified radius of target? | `ST_DWithin` | +| `intersects` | Do geometries share any space? | `ST_Intersects` | + +## Operation Examples + + + **Code snippets need testing** — Verify against actual implementation before use. + + +### Distance + +Calculate the nearest distance between two geometries in meters. For polygons, this returns the minimum distance between their boundaries. + +```typescript +const result = await astral.compute.distance({ + from: userLocationUID, + to: landmarkUID, + chainId: 84532, + schema: SCHEMA_UID +}); + +console.log(result.result); // 523.45 (meters) +console.log(result.units); // "meters" +``` + +### Contains + +Check if a geometry is **entirely** inside a container geometry. + +```typescript +const result = await astral.compute.contains({ + container: sfBayAreaPolygonUID, // Container polygon + geometry: userLocationUID, // Geometry to check + chainId: 84532, + schema: SCHEMA_UID +}); + +console.log(result.result); // true or false +``` + +### Within + +Check if a geometry is within a specified radius of a target — essentially a **radius check**. + +```typescript +const result = await astral.compute.within({ + geometry: userLocationUID, // Geometry to check + target: landmarkUID, // Target location + radius: 500, // Radius in meters + chainId: 84532, + schema: SCHEMA_UID +}); + +console.log(result.result); // true if within 500m +``` + +### Intersects + +Check if two geometries share any space (overlap, touch, or cross). + +```typescript +const result = await astral.compute.intersects({ + geometry1: polygon1UID, + geometry2: polygon2UID, + chainId: 84532, + schema: SCHEMA_UID +}); + +console.log(result.result); // true if they share space +``` + +### Area + +Calculate the area of a polygon (must be Polygon or MultiPolygon). + +```typescript +const result = await astral.compute.area({ + geometry: propertyBoundaryUID, + chainId: 84532, + schema: SCHEMA_UID +}); + +console.log(result.result); // 5432.10 (square meters) +console.log(result.units); // "square_meters" +``` + +### Length + +Calculate the length of a line (must be LineString or MultiLineString). + +```typescript +const result = await astral.compute.length({ + geometry: routeUID, + chainId: 84532, + schema: SCHEMA_UID +}); + +console.log(result.result); // 2345.67 (meters) +console.log(result.units); // "meters" +``` + +## Units + +All measurements use **metric units only**: + +| Measurement | Unit | +|-------------|------| +| Distance | meters | +| Length | meters | +| Area | square meters | +| Radius (in `within`) | meters | + + + No unit conversion options are provided. Developers should convert client-side if needed. + + +## Precision + +Results are stored with centimeter precision to enable **deterministic, reproducible** computation: + +| Type | Precision | Storage | +|------|-----------|---------| +| Distance/Length | 0.01m | Scaled integer (cm) | +| Area | 0.0001 m² | Scaled integer (cm²) | + +``` +523.45 meters → stored as 52345 (centimeters) +1234.5678 m² → stored as 12345678 (cm²) +``` + + + We're actively testing the reliability of deterministic geospatial computation with this precision approach. This is an area of ongoing research in the beta implementation. + + +## Compute Options + +All operations require these parameters: + +```typescript +interface ComputeOptions { + chainId: number; // Target chain for result signing + schema: string; // EAS schema UID (required) + recipient?: string; // Result recipient address +} +``` + + + **`chainId` is required.** The service needs to know which chain to sign the result for. + + + + We only support official EAS deployments on supported chains. See the SDK overview for the chain list. + + +## Future Operations + +Post-MVP, we plan to add: + +| Operation | Type | Description | +|-----------|------|-------------| +| `disjoint` | Predicate | Do geometries not touch? | +| `buffer` | Transformation | Create buffer zone around geometry | +| `centroid` | Transformation | Find center point | +| `union` | Transformation | Merge geometries | +| `intersection` | Transformation | Overlapping area | + +We're also exploring **nested/composable operations** — combining multiple predicates in a single result, such as "is inside region A AND within 500m of landmark B". + +## Client-Side Operations with Turf.js + +We recommend **[Turf.js](https://turfjs.org/)** for client-side geospatial operations. Use Turf.js for instant UX feedback, and Astral for verifiable results: + +```typescript +import * as turf from '@turf/turf'; + +// Client-side: instant UX feedback (unverified, but instant) +const localDistance = turf.distance(point1, point2); +showUI(`${localDistance}km away`); + +// Verifiable: signed result for onchain use +const attestedResult = await astral.compute.distance({ + from: uid1, + to: uid2, + chainId: 84532, + schema: SCHEMA_UID +}); +await astral.eas.submitDelegated(attestedResult.delegatedAttestation); +``` + +| Use Case | Tool | Why | +|----------|------|-----| +| Real-time UI feedback | Turf.js | Instant, free, runs client-side | +| Verifiable computation | Astral | Signed result for onchain actions | + + + Learn about the signed outputs of geocomputation + diff --git a/concepts/geographic-features.mdx b/concepts/geographic-features.mdx new file mode 100644 index 0000000..e308191 --- /dev/null +++ b/concepts/geographic-features.mdx @@ -0,0 +1,193 @@ +--- +title: "Geographic features" +description: "What the system operates on — the inputs to geocomputation and verification" +--- + +**Research Preview** — APIs may change. [GitHub](https://github.com/AstralProtocol) + +# Geographic features + +Geographic features are what Astral operates on. Every API call starts with geographic features — points, polygons, lines, or references to previously verified locations. The provenance of a feature matters: a raw coordinate and a verified location are both valid inputs, but they carry different trust properties. + +## Supported Input Types + +Astral currently supports four ways to pass geographic features: + +| # | Input Type | Description | +|---|-----------|-------------| +| 1 | **Raw GeoJSON** | Unsigned geometry for reference locations or prototyping | +| 2 | **Attested location (UID)** | EAS attestation with cryptographic proof of origin, referenced by UID | +| 3 | **Attested location (UID + URI)** | Offchain attestation referenced by UID and storage URI | +| 4 | **Inline attestation** | Full signed attestation object passed directly | + +We plan to expand support for other feature formats in the future, including AT Protocol location lexicon records and other standards. + +## Attested Locations + +Attested locations are **signed spatial records** stored as EAS attestations. They contain: + +- **Geometry**: The spatial data (GeoJSON format) +- **Metadata**: Optional descriptive information +- **Signature**: Cryptographic proof of the attester's identity + + + **Code snippets need testing** — Verify against actual implementation before use. + + +```typescript +// Creating an attested location +const landmark = await astral.location.create({ + type: 'Point', + coordinates: [2.2945, 48.8584] +}, { + chainId: 84532, + submitOnchain: true, + metadata: { + name: "Eiffel Tower", + description: "Iconic Paris landmark" + } +}); + +console.log(landmark.uid); // 0xabc123... +``` + +## Provenance + +The provenance of a geographic feature determines how much trust you can place in it. + +**Raw GeoJSON** is fine for reference geometries — official boundaries, well-known landmarks, administrative regions. These are publicly verifiable facts that don't need cryptographic proof of origin. Raw GeoJSON is also useful during prototyping when you want to test computations without creating attestations first. However, raw coordinates are unverified for user positions. A signed result using raw GeoJSON proves "Astral computed the relationship between inputs A and B" but does not prove **who provided** those geometries. + +**Attested locations** from location proofs carry evidence of where someone actually was. They include cryptographic signatures tying the location claim to a specific identity. When a signed result references attested locations as inputs, the full chain of trust is preserved: who claimed to be where, when they claimed it, and what the computation determined. + +The right choice depends on your use case. A geofencing check against a city boundary can safely use raw GeoJSON for the boundary and an attested location for the user's position. + +## Storage Options + +For blockchain integrators, see [Blockchain integration](/guides/blockchain-integration) for details on onchain vs offchain storage. + + + + - Stored on EAS contracts + - Referenced by UID alone + - Higher gas cost + - Permanent, immutable + + + - Stored on IPFS, servers, etc. + - Referenced by UID + URI + - No gas cost to create + - EIP-712 signed + + + +### Onchain + +```typescript +// Create onchain attestation +const feature = await astral.location.create(geojson, { + submitOnchain: true +}); + +// Reference by UID only +await astral.compute.distance(feature.uid, otherUID); +``` + +### Offchain + +```typescript +// Create offchain attestation +const feature = await astral.location.create(geojson, { + submitOnchain: false, + storage: 'ipfs' // or 'arweave', 'https', etc. +}); + +// Reference by UID + URI +await astral.compute.distance( + { uid: feature.uid, uri: feature.uri }, + otherUID +); +``` + +## Schema + +Attested locations follow the [Location Protocol v0.2](https://github.com/DecentralizedGeo/location-protocol-spec/tree/v0.2-draft) schema: + +For blockchain integrators, see [Blockchain integration](/guides/blockchain-integration) for details on onchain vs offchain storage. + + + **Verification in progress** — We are actively verifying that deployed schemas conform to Location Protocol v0.2. There may be inconsistencies between the documentation, deployed schemas, and the spec. See [GitHub issue #11](https://github.com/AstralProtocol/astral-location-services/issues/11) for status. + + +| Field | Type | Description | +|-------|------|-------------| +| `lp_version` | string | Location Protocol version (e.g., "0.2") | +| `location_type` | string | The type of location data (e.g., "GeoJSON") | +| `location` | bytes | Encoded location data | +| `srs` | string | Spatial reference system as OGC URI | + +## Using Geographic Features + +Geographic features serve as **inputs** to geocomputation: + +```typescript +// As direct UID (onchain attestation) +const result = await astral.compute.distance( + '0xabc123...', // UID of first feature + '0xdef456...' // UID of second feature +); + +// As UID + URI (offchain attestation) +const result = await astral.compute.contains( + { uid: '0xabc123...', uri: 'ipfs://Qm...' }, + userLocationUID +); + +// As inline attestation object +const result = await astral.compute.within( + { attestation: signedOffchainAttestation }, + landmarkUID, + 500 +); +``` + +## Raw GeoJSON Alternative + +You can also use **raw GeoJSON** without creating an attestation first: + +```typescript +// Raw GeoJSON as input +const result = await astral.compute.contains( + { + type: 'Polygon', + coordinates: [[[...]]] // SF Bay Area boundary + }, + userLocationUID +); +``` + +## Fetching Geographic Features + +```typescript +// Get by UID +const feature = await astral.location.get('0xabc123...'); +console.log(feature.geometry); // { type: 'Point', coordinates: [...] } + +// Query with filters +const features = await astral.location.query({ + bbox: [-122.5, 37.7, -122.4, 37.8], // Bounding box + limit: 100 +}); +``` + +## Coordinate System + +Attested locations honor the `srs` field, using OGC URIs per [Location Protocol v0.2](https://github.com/DecentralizedGeo/location-protocol-spec/tree/v0.2-draft): + +- **Default**: `http://www.opengis.net/def/crs/OGC/1.3/CRS84` (WGS84, lon/lat order) +- **Custom**: Any OGC CRS URI supported by PostGIS + +Coordinates should be in **[longitude, latitude]** order (GeoJSON standard). + + + Learn about the spatial computations you can perform + diff --git a/concepts/location-proofs.mdx b/concepts/location-proofs.mdx index 29156b3..d04a8b8 100644 --- a/concepts/location-proofs.mdx +++ b/concepts/location-proofs.mdx @@ -3,9 +3,7 @@ title: "Location Proofs" description: "Evidence-based verification of location claims" --- - - **Research Preview** — Under active development. - +**Research preview** — APIs may change. [GitHub](https://github.com/AstralProtocol) # Location Proofs @@ -94,9 +92,9 @@ Single-stamp proofs are valid. Multi-stamp proofs from independent systems enabl 2. **Assess stamps against claim** — Does the observed location support the asserted location? 3. **Assess cross-correlation** — Do multiple stamps corroborate each other? -The output is a **Credibility Assessment**. +The output is a **Credibility Assessment** (credibility scores). -## Credibility Assessment +## Credibility assessment Verification outputs a structured assessment, not just a yes/no: @@ -111,9 +109,9 @@ interface CredibilityAssessment { } ``` - - **Confidence ≠ Probability.** A confidence of 0.8 does NOT mean "80% probability the claim is true." It means "the evidence is reasonably strong." Calibrating to true probability is future work. - + + **Confidence is not probability.** A confidence of 0.8 does not mean "80% probability the claim is true." It means "the evidence is reasonably strong." Calibrating to true probability is future work. + ### Multi-Factor Proofs @@ -254,22 +252,22 @@ Can be onchain or offchain — no requirement to submit onchain. --- -## Relationship to Geospatial Operations +## Relationship to geocomputation -Location Verification is **upstream** of [Geospatial Operations](/concepts/geospatial-operations): +Location verification is **upstream** of [geocomputation](/concepts/geocomputation): -| Location Verification | Geospatial Operations | -|----------------------|----------------------| +| Location verification | Geocomputation | +|----------------------|----------------| | Evaluates evidence credibility | Computes spatial relationships | | Input: location proof (claim + stamps) | Input: locations (claimed or verified) | -| Output: credibility assessment | Output: policy attestation | +| Output: credibility scores | Output: signed result | | "How confident are we that X was at L?" | "Is A inside B?" | Verified location proofs can serve as **trusted inputs** to geospatial operations, increasing confidence in computed results. -### Integration with Compute +### Integration with geocomputation -Verified Location Proofs can be used as **inputs** to Geospatial Operations: +Verified location proofs can be used as **inputs** to geocomputation: ```typescript // Verify user's location @@ -284,6 +282,6 @@ const result = await astral.compute.contains({ }); ``` - - Learn about spatial computations on location data + + Learn about the signed outputs of geocomputation diff --git a/concepts/privacy-model.mdx b/concepts/privacy-model.mdx new file mode 100644 index 0000000..d771cc7 --- /dev/null +++ b/concepts/privacy-model.mdx @@ -0,0 +1,64 @@ +--- +title: "Privacy model" +description: "What the operator can see and what stays private" +--- + +**Research preview** — APIs may change. [GitHub](https://github.com/AstralProtocol) + +# Privacy model + +Astral runs spatial computations inside a Trusted Execution Environment (TEE). The TEE accepts encrypted inputs, decrypts them inside the enclave, computes a result, and returns only the signed result. The operator — whoever runs the Astral service — never sees raw location data. + +This is a meaningful privacy guarantee even before zero-knowledge proofs. Your exact coordinates are processed but never exposed outside the enclave. + +## What stays private + +- **Raw input coordinates** — the exact lat/lng values you submit +- **Exact geometries** — polygon boundaries, line paths, and other spatial data used in the computation + +These values exist only inside the TEE during computation and are discarded after the result is signed. + +## What is visible + +- **The signed result** — a boolean (true/false) or numeric value (distance, area, length) +- **Operation type** — which geocomputation was performed (e.g., `contains`, `distance`) +- **Input references** — hashes of the inputs, not the raw data itself + +Anyone who sees the signed result knows *what question was asked* and *what the answer was*, but not *what the exact inputs were*. + +## Information leakage from results + +The result itself may reveal information. If you ask "is this point inside this polygon?" and the answer is true, an observer learns something about the point's location. This is inherent to the computation, not a limitation of the privacy model. + +The amount of leakage depends on the operation and the specificity of the inputs: + +| Operation | What the result reveals | +|-----------|------------------------| +| `contains` (true) | The point is somewhere inside the polygon | +| `within` (true, 500m) | The point is within 500m of the target | +| `distance` (exact value) | The precise distance between two geometries | + +More specific operations leak more. A `contains` check against a country-sized polygon reveals less than a `within` check with a 10-meter radius. + +## Current guarantees vs future enhancements + +The TEE provides **computational privacy** today. Your raw inputs are processed inside hardware-isolated memory that the operator cannot inspect, even with physical access to the machine. + +Zero-knowledge proofs are a future enhancement that would allow verification without any trusted hardware. With ZK, a verifier could confirm the computation was correct without the inputs ever entering a trusted enclave — removing the need to trust the TEE manufacturer's security claims. + +| | TEE (today) | ZK (future) | +|---|---|---| +| Raw inputs hidden from operator | Yes | Yes | +| No trusted hardware required | No | Yes | +| Verification without re-execution | No | Yes | +| Production-ready | Yes | Not yet | + +## Limitations + +- **TEE trust assumption** — you are trusting that the TEE hardware (Intel SGX / AMD SEV) correctly isolates the enclave. Side-channel attacks on TEEs are an active area of security research. +- **Result leakage** — as described above, the result itself carries information about the inputs. +- **Input references** — hashed input references are visible. If an observer knows the possible input space, they could attempt to match hashes. + + + The architecture behind verifiable spatial computation + diff --git a/concepts/result-format.mdx b/concepts/result-format.mdx new file mode 100644 index 0000000..9222ee0 --- /dev/null +++ b/concepts/result-format.mdx @@ -0,0 +1,201 @@ +--- +title: "Result format" +description: "How signed results are structured, decoded, and verified" +--- + +**Research Preview** — APIs may change. [GitHub](https://github.com/AstralProtocol) + +# Result format + +When you call a compute operation, Astral returns a signed result — a cryptographically signed record of the computation, its inputs, and its output. This is the universal format that any integrator (agent, backend, smart contract) uses to consume and verify Astral's results. + + + Signed results are produced by the Astral signing key, held exclusively inside the TEE (trusted execution environment). This is how you know the computation was performed correctly — only code running inside the TEE can produce valid signatures. + + +## What is a signed result? + +When you call a compute operation, Astral returns a signed result: + + + **Code snippets need testing** — Verify against actual implementation before use. + + +```typescript +const result = await astral.compute.within({ + geometry: userLocationUID, + target: landmarkUID, + radius: 500, + chainId: 84532, + schema: SCHEMA_UID, + recipient: userAddress +}); + +// result is a PolicyAttestationResult +console.log(result.result); // true +console.log(result.operation); // "within" +console.log(result.attestation); // { uid, schema, signature, ... } +``` + +The signed result proves: +- The computation was performed by the Astral signing key +- The inputs were exactly as specified +- The result is accurate + +## Schema Types + +Signed results use per-result-type schemas. The SDK auto-selects based on the operation. + +### BooleanPolicyAttestation + +For predicates (`contains`, `within`, `intersects`): + +```solidity +// Schema: "bool result, bytes32[] inputRefs, uint64 timestamp, string operation" +struct BooleanPolicyAttestation { + bool result; // The boolean result + bytes32[] inputRefs; // Input references (UIDs or hashes) + uint64 timestamp; // When computation was performed + string operation; // "contains", "within", "intersects" +} +``` + +### NumericPolicyAttestation + +For measurements (`distance`, `length`, `area`): + +```solidity +// Schema: "uint256 result, string units, bytes32[] inputRefs, uint64 timestamp, string operation" +struct NumericPolicyAttestation { + uint256 result; // Scaled integer (centimeters) + string units; // "meters" or "square_meters" + bytes32[] inputRefs; // Input references + uint64 timestamp; // When computation was performed + string operation; // "distance", "length", "area" +} +``` + +### GeometryPolicyAttestation (Future) + +For transformations (`buffer`, `centroid`, `union`): + +```solidity +// Schema: "bytes geometry, string geometryType, bytes32[] inputRefs, uint64 timestamp, string operation" +struct GeometryPolicyAttestation { + bytes geometry; // Encoded geometry result + string geometryType; // "Point", "Polygon", etc. + bytes32[] inputRefs; // Input references + uint64 timestamp; // When computation was performed + string operation; // "buffer", "centroid", "union" +} +``` + +## Input References + +The `inputRefs` array contains a `bytes32` reference for each input: + +| Input Type | Reference Value | +|------------|-----------------| +| Location record UID | The UID itself | +| Offchain attestation | The attestation UID | +| Raw GeoJSON | `keccak256(abi.encode(geojson))` | + +This enables verification that specific inputs were used: + +```solidity +// In your resolver +(bool result, bytes32[] memory inputRefs, , ) = abi.decode(...); + +// Verify the expected location was checked +require(inputRefs[1] == EXPECTED_LANDMARK_UID, "Wrong location checked"); +``` + + + For raw GeoJSON, the hash allows verification if the original geometry is known, but does not reveal the geometry itself. + + +## SDK Return Object + +```typescript +interface PolicyAttestationResult { + // Decoded result (convenience) + result: T; // boolean | number | GeoJSON.Geometry + units?: string; // For measurements + operation: string; // Operation name + timestamp: number; // Unix timestamp + inputRefs: string[]; // Input references + + // Full attestation data + attestation: { + uid: string; // EAS UID (if submitted onchain) + schema: string; // Schema UID + attester: string; // Astral signer address + recipient: string; // Developer-specified recipient + data: string; // ABI-encoded attestation data + signature: string; // EIP-712 signature + }; + + // For delegated onchain submission + delegatedAttestation: { + signature: string; // Astral's signature + attester: string; // Astral's address + deadline: number; // Signature expiry + }; +} +``` + +## Using Signed Results + +### Offchain + +Use the result directly in your application: + +```typescript +const result = await astral.compute.within({ + geometry: uid1, + target: uid2, + radius: 500, + chainId: 84532, + schema: SCHEMA_UID +}); + +if (result.result) { + // User is nearby — enable feature + enableFeature(); +} +``` + +### Onchain + +Signed results can be submitted onchain via EAS delegated attestations to trigger smart contract logic. + +For the full blockchain integration flow, see [Blockchain integration](/guides/blockchain-integration). + +## Result Scaling + +Numeric results are stored as scaled integers for determinism: + +| Original | Stored | To Recover | +|----------|--------|------------| +| 523.45 meters | 52345 | divide by 100 | +| 1234.5678 m² | 12345678 | divide by 10000 | + +```solidity +// In your resolver +(uint256 resultCm, , , , ) = abi.decode(...); +uint256 meters = resultCm / 100; +``` + +## Result Expiry + +The `delegatedAttestation.deadline` indicates when the signature expires. Submissions after the deadline will fail. + +```typescript +if (Date.now() / 1000 < result.delegatedAttestation.deadline) { + await astral.eas.submitDelegated(result.delegatedAttestation); +} +``` + + + Learn about verifiable location claims + diff --git a/concepts/verifiable-computation.mdx b/concepts/verifiable-computation.mdx index dd20eaa..69ba7d2 100644 --- a/concepts/verifiable-computation.mdx +++ b/concepts/verifiable-computation.mdx @@ -1,37 +1,35 @@ --- -title: "Verifiable Computation" -description: "How EigenCompute provides trust in geospatial operations" +title: "Verifiable computation" +description: "What 'verifiable' means and how it works" --- - - **Research Preview** — Under active development. - +**Research Preview** — APIs may change. [GitHub](https://github.com/AstralProtocol) -# Verifiable Computation +# Verifiable computation -Astral Location Services run in a **Trusted Execution Environment (TEE)** via EigenCompute, providing cryptographic guarantees that computations were performed correctly. +Astral runs spatial computations inside a Trusted Execution Environment (TEE). The TEE guarantees that computations execute correctly, inputs aren't tampered with, and results are signed by a key that only the TEE holds. This is what makes the results verifiable — not just signed, but provably computed. -## The Verification Pipeline +## The verification pipeline -Building trustworthy location-based smart contracts requires verification at multiple stages: +Verifiable spatial computation requires verification at multiple stages: ```mermaid graph LR - A["1. Location Verification
(Where is the user?)
In development"] --> B["2. Geospatial Computation
(Policy evaluation)
✓ Astral Location Services"] - B --> C["3. Onchain Verification
(Smart contract logic)
✓ EAS Resolvers"] + A["1. Location verification
(Where is the entity?)
In development"] --> B["2. Spatial computation
(What spatial relationships hold?)
✓ Astral geocomputation"] + B --> C["3. Result delivery
(Signed result goes wherever you need it —
an agent, a database, a smart contract)"] ``` -**Astral Location Services solve the middle step** — verifiable geospatial computation. Given location inputs, we can provably check spatial relationships and produce signed attestations for onchain use. +**Astral solves the middle step** — verifiable geocomputation. Given location inputs, the system provably checks spatial relationships and produces signed results. - **What about step 1?** Verifying *where* a user actually is remains an open problem. GPS is spoofable. We're developing the [Location Proof framework](https://collective.flashbots.net/t/towards-stronger-location-proofs/5323) to address this. Location proofs will plug into Astral Location Services to complete the pipeline. + **What about step 1?** Verifying *where* an entity actually is remains an open problem. GPS is spoofable. We're developing the [Location Proof framework](https://collective.flashbots.net/t/towards-stronger-location-proofs/5323) to address this. Location proofs will plug into Astral to complete the pipeline. -## Why Verification Matters +## Why verification matters Without verification, you're trusting a black box. Anyone could claim "the user is nearby" without proof. -With verifiable computation, the signed attestation proves the result came from executing specific code on specific inputs inside a TEE. Smart contracts can verify that the Astral signing key performed the computation correctly, not just that someone *claims* it did. +With verifiable computation, the signed result proves the output came from executing specific code on specific inputs inside a TEE. Downstream consumers — whether smart contracts, agents, or applications — can verify that the Astral signing key performed the computation correctly, not just that someone *claims* it did. ## EigenCompute @@ -41,7 +39,7 @@ With verifiable computation, the signed attestation proves the result came from Code runs in a hardware-isolated environment that even the operator can't tamper with - + Hardware-generated proof that specific code executed on specific inputs @@ -52,111 +50,7 @@ With verifiable computation, the signed attestation proves the result came from -## The Execution Model - -```mermaid -flowchart TB - subgraph TEE["EigenCompute TEE Environment"] - subgraph Container["Docker Container"] - subgraph Engine["Astral Geospatial Policy Engine"] - E1["Validates input attestations"] - E2["Executes PostGIS queries"] - E3["Signs results with TEE-held key"] - end - subgraph DB["PostgreSQL + PostGIS"] - D1["All spatial computations"] - D2["Ephemeral, no persistent state"] - end - Engine --> DB - end - G1["TEE guarantees:"] - G2["• Code hasn't been modified"] - G3["• Inputs weren't tampered with"] - G4["• Outputs came from executing that code"] - end -``` - - - Verify EigenCompute details against the [official EigenCompute documentation](https://blog.eigencloud.xyz/eigencloud-brings-verifiable-ai-to-mass-market-with-eigenai-and-eigencompute-launches/). Hardware attestation (remote attestation) specifics may vary. - - -## Verifiability Properties - -| Property | How It's Achieved | -|----------|-------------------| -| **Input Integrity** | Attestation signatures verified at TEE boundary before processing | -| **Execution Integrity** | TEE ensures code runs as deployed, can't be modified | -| **Output Authenticity** | Signing key held inside TEE, can't be extracted | -| **Determinism** | Stateless model + fixed precision = same inputs → same outputs | - -## The Astral Signing Key - -The service holds a signing key **inside** the TEE: - -- Key is generated within the TEE or securely provisioned -- Cannot be extracted by the operator -- All Policy Attestations are signed with this key -- Resolver contracts verify `attestation.attester` matches the known Astral signer - -```solidity -// In your resolver -require(attestation.attester == ASTRAL_SIGNER, "Not from Astral"); -``` - -## Trust Model - -### Current (MVP) - -Why can you trust the computation results? - -- **Open source code**: The compute service code is public and auditable -- **TEE execution**: Code runs inside EigenCompute's trusted execution environment -- **Remote attestation**: EigenCompute provides hardware attestation that the expected code is running -- **Deterministic operations**: Same inputs always produce same outputs (additional testing in progress) - -### Future Enhancements - - - - Multiple independent operators run the computation. Results must match to be accepted. No single operator can lie. - - - Cryptographic proof that the computation was correct. Verifiable by anyone without trusting the prover. - - - Multi-party computation for attestation signing. No single party holds the full key. - - - -## Why This Matters - -Consider a location-gated NFT. The question is: how do we check "is the user within 500m of the Eiffel Tower?" in a way that smart contracts can trust? - -**The computation problem (what Astral Location Services solve):** - -If we have location data for the user and the Eiffel Tower, how do we verifiably check if they're close enough? The geospatial policy engine runs the computation in a TEE and produces a signed attestation: - -``` -Location A + Location B → TEE (PostGIS) → Signed attestation: "distance = 450m" -``` - -**The input problem (location proofs, still being developed):** - -Even with verifiable computation, users can lie about their location. GPS is spoofable. The attestation proves the *computation* was correct, not that the *inputs* were honest. - -This is why we're developing the [Location Proof framework](https://collective.flashbots.net/t/towards-stronger-location-proofs/5323) — to provide evidence-based location claims that can feed into Astral Location Services for a fully verifiable pipeline. - - - **Be upfront with your users**: Until location proof plugins are integrated, location verification relies on trusting the user's GPS. Astral Location Services provides verifiable *computation*, not verifiable *location*. - - -## Why Build the Geospatial Layer First? - -Location proofs become useful when they can trigger onchain actions through geospatial policies. Astral Location Services apply those policies — checking if a location is inside a boundary, within range of a target, or satisfies other spatial constraints. - -By building the geospatial policy infrastructure now, we can experiment with use cases and iterate while developing the [Location Proof plugins](https://collective.flashbots.net/t/towards-stronger-location-proofs/5323). As proof mechanisms mature, they plug directly into the existing pipeline. - -## Determinism Guarantees +## Determinism guarantees To ensure reproducibility: @@ -172,6 +66,16 @@ const result2 = await astral.compute.distance(uid1, uid2); result1.result === result2.result // Always true ``` +## Why build the geocomputation layer first? + +Location proofs become useful when they can trigger actions through spatial policies. Astral applies those policies — checking if a location is inside a boundary, within range of a target, or satisfies other spatial constraints. + +By building the geocomputation infrastructure now, we can experiment with use cases and iterate while developing the [Location Proof plugins](https://collective.flashbots.net/t/towards-stronger-location-proofs/5323). As proof mechanisms mature, they plug directly into the existing pipeline. + +--- + +For a detailed accounting of what exactly is verified and what trust assumptions remain, see the [Trust Model](/trust-model/architecture). + See the full architecture From f3801f26c9b457481a4c1a45eedd3e77a7afc982 Mon Sep 17 00:00:00 2001 From: John X Date: Mon, 16 Feb 2026 21:25:22 -0500 Subject: [PATCH 05/17] docs: add guide pages for all integration paths New guides: - local-development: Docker, env setup, --env-file startup - calling-the-api: request/response format, input types, errors - blockchain-integration: EAS resolvers, delegated attestations - agent-integration: Python/TS examples, decision-making patterns - building-plugins: PoL plugin interface, Wi-Fi example - verifying-location-proofs: claims, stamps, credibility scores blockchain-integration merges content from eas-resolvers, policy-attestations onchain sections, and location-attestations storage options. --- guides/agent-integration.mdx | 181 +++++++++++++ guides/blockchain-integration.mdx | 387 +++++++++++++++++++++++++++ guides/building-plugins.mdx | 284 ++++++++++++++++++++ guides/calling-the-api.mdx | 216 +++++++++++++++ guides/local-development.mdx | 149 +++++++++++ guides/verifying-location-proofs.mdx | 248 +++++++++++++++++ 6 files changed, 1465 insertions(+) create mode 100644 guides/agent-integration.mdx create mode 100644 guides/blockchain-integration.mdx create mode 100644 guides/building-plugins.mdx create mode 100644 guides/calling-the-api.mdx create mode 100644 guides/local-development.mdx create mode 100644 guides/verifying-location-proofs.mdx diff --git a/guides/agent-integration.mdx b/guides/agent-integration.mdx new file mode 100644 index 0000000..44d82d2 --- /dev/null +++ b/guides/agent-integration.mdx @@ -0,0 +1,181 @@ +--- +title: "Agent integration" +description: "Using Astral as a spatial oracle in agent workflows" +--- + +**Research preview** — APIs may change. [GitHub](https://github.com/AstralProtocol) + +# Agent integration + +Autonomous agents need spatial reasoning — is this delivery within the geofence? How far is the nearest facility? Does this route intersect a restricted zone? Astral provides verified spatial answers that agents can use in decision-making. + +## The pattern + +The integration pattern is straightforward: the agent needs a spatial answer, calls Astral, gets a signed result, and uses the result in its decision logic. + +```mermaid +graph LR + A[Agent needs spatial answer] --> B[Calls Astral API] + B --> C[Gets signed result] + C --> D[Uses result in decision] +``` + +The signed result provides an audit trail. Anyone reviewing the agent's decisions can verify that the spatial reasoning was based on correctly computed data, not fabricated or hallucinated. + +## Raw HTTP from any framework + +Astral is a REST API, so any language or framework that can make HTTP requests works. No SDK required. + +### Python + +```python +import httpx + +response = httpx.post("https://api.astral.global/compute/distance", json={ + "from": {"type": "Point", "coordinates": [2.2945, 48.8584]}, + "to": {"type": "Point", "coordinates": [2.3522, 48.8566]}, + "chainId": 84532 +}) + +result = response.json() +distance_meters = result["result"] +``` + +### TypeScript + +```typescript +const response = await fetch('https://api.astral.global/compute/distance', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + from: { type: 'Point', coordinates: [2.2945, 48.8584] }, + to: { type: 'Point', coordinates: [2.3522, 48.8566] }, + chainId: 84532 + }) +}); + +const result = await response.json(); +const distanceMeters = result.result; +``` + +## Using the result in decisions + +Once the agent has a signed spatial answer, it can branch on the result: + +```python +result = response.json() + +if result["result"] < 500: + # Courier is within 500 meters of the delivery address + proceed_with_delivery_confirmation(result) +else: + # Too far — wait or reroute + schedule_retry(result["result"]) +``` + +For boolean operations like containment or intersection, the result is `true` or `false`: + +```python +response = httpx.post("https://api.astral.global/compute/contains", json={ + "container": geofence_polygon, + "geometry": {"type": "Point", "coordinates": agent_location}, + "chainId": 84532 +}) + +result = response.json() + +if result["result"]: + # Agent is inside the geofence + execute_geofenced_action() +else: + # Agent is outside — restricted + log_boundary_violation() +``` + +## Example: delivery verification agent + +Here is a complete agent workflow that checks whether a courier has arrived at the delivery address. The agent polls the courier's location and confirms delivery when the courier is close enough. + +```python +import httpx +import time + +ASTRAL_URL = "https://api.astral.global" +DELIVERY_RADIUS_METERS = 100 + +def check_delivery_proximity( + courier_coords: list[float], + destination_coords: list[float], + chain_id: int = 84532 +) -> dict: + """Check if courier is within delivery radius of destination.""" + response = httpx.post(f"{ASTRAL_URL}/compute/distance", json={ + "from": {"type": "Point", "coordinates": courier_coords}, + "to": {"type": "Point", "coordinates": destination_coords}, + "chainId": chain_id + }) + response.raise_for_status() + return response.json() + + +def delivery_agent( + courier_coords: list[float], + destination_coords: list[float] +) -> dict: + """ + Delivery verification agent. + Checks proximity and returns a decision with the signed result. + """ + result = check_delivery_proximity(courier_coords, destination_coords) + distance = result["result"] + + if distance <= DELIVERY_RADIUS_METERS: + return { + "decision": "confirm_delivery", + "distance_meters": distance, + "signed_result": result, + "reason": f"Courier is {distance:.1f}m from destination (within {DELIVERY_RADIUS_METERS}m threshold)" + } + + return { + "decision": "wait", + "distance_meters": distance, + "signed_result": result, + "reason": f"Courier is {distance:.1f}m from destination (need to be within {DELIVERY_RADIUS_METERS}m)" + } + + +# Run the agent +decision = delivery_agent( + courier_coords=[2.3522, 48.8566], + destination_coords=[2.3525, 48.8568] +) + +print(f"Decision: {decision['decision']}") +print(f"Reason: {decision['reason']}") +``` + +The `signed_result` in the decision object contains the full Astral response, including the cryptographic signature. This means the decision is auditable — anyone can verify the spatial computation that led to it. + +## Why verified answers matter for agents + +When an agent makes a decision based on spatial data, the signed result provides an audit trail. Anyone can verify that the agent's spatial reasoning was based on correctly computed data. + +This matters for several reasons: + +- **Accountability** — if an agent approves a delivery payout based on location proximity, the signed result proves the distance was computed correctly on the stated inputs. +- **Dispute resolution** — when a decision is challenged, the signed result is independent evidence. It does not depend on trusting the agent's own logs. +- **Composability** — signed results can be submitted onchain to trigger smart contract logic, bridging the agent's offchain reasoning with onchain actions. + +Without verified computation, an agent's spatial claims are self-reported. With Astral, they are independently verifiable. + +## Next steps + + + + Full details on request format and error handling + + + Submit agent decisions onchain via EAS + + diff --git a/guides/blockchain-integration.mdx b/guides/blockchain-integration.mdx new file mode 100644 index 0000000..788297d --- /dev/null +++ b/guides/blockchain-integration.mdx @@ -0,0 +1,387 @@ +--- +title: "Blockchain integration" +description: "Submit verified spatial results onchain via EAS" +--- + +**Research Preview** — Smart contract patterns need audit. [GitHub](https://github.com/AstralProtocol) + +# Blockchain integration + +Astral's signed results can be submitted onchain as EAS attestations, making them available to smart contracts. This guide covers the full blockchain integration flow. + +## What is EAS? + +The [Ethereum Attestation Service](https://docs.attest.org/) (EAS) is an open protocol for making onchain and offchain attestations. Attestations are structured, signed claims — "entity X attests that Y is true." + +EAS supports two storage modes: + +- **Onchain attestations** — stored directly in EAS contracts, referenced by UID +- **Offchain attestations** — signed with EIP-712, stored on IPFS or other storage, verifiable without gas costs + +Astral uses EAS to package signed spatial results as attestations that smart contracts can consume and act on. + +## The pattern + +The core flow: compute a spatial result, sign it, submit it onchain, and let a resolver contract react. + +```mermaid +sequenceDiagram + participant User + participant SDK as Astral SDK + participant Engine as Geospatial Policy Engine + participant EAS as EAS Contracts + participant Resolver as Your Resolver + + User->>SDK: compute.within(...) + SDK->>Engine: Request computation + Engine-->>SDK: Signed attestation + SDK->>EAS: Submit delegated attestation + EAS->>Resolver: onAttest() callback + Resolver->>Resolver: Verify + Execute logic + Resolver-->>EAS: return true/false +``` + +## Delegated attestation flow + +Astral uses EAS's **delegated attestation** pattern to separate signing from submission: + +```mermaid +sequenceDiagram + participant Dev as Developer App + participant SDK as Astral SDK + participant Svc as Compute Service + participant EAS as EAS Contracts + participant Res as Resolver + + Dev->>SDK: compute.within(...) + SDK->>Svc: POST /compute/within + Svc->>Svc: Execute PostGIS operation + Svc->>Svc: Sign attestation + Svc-->>SDK: Signed attestation + delegated sig + SDK->>EAS: Submit with Astral's signature + EAS->>Res: onAttest() callback + Res->>Res: Execute business logic +``` + +The delegated attestation pattern means: + +- **Astral signs** the attestation data offchain (inside the TEE) +- **Developer submits** with Astral's signature (pays gas) +- **EAS verifies** the signature and records Astral as attester +- **Resolver contracts** can verify `attestation.attester == astralSigner` + +```typescript +const result = await astral.compute.within({ + geometry: uid1, + target: uid2, + radius: 500, + chainId: 84532, + schema: RESOLVER_SCHEMA_UID, + recipient: userAddress +}); + +// Submit to EAS — triggers your resolver +const tx = await astral.eas.submitDelegated(result.delegatedAttestation); +await tx.wait(); +``` + +The `delegatedAttestation.deadline` indicates when the signature expires. Submissions after the deadline will fail: + +```typescript +if (Date.now() / 1000 < result.delegatedAttestation.deadline) { + await astral.eas.submitDelegated(result.delegatedAttestation); +} +``` + +## Writing a resolver contract + +In EAS, a **resolver** is a smart contract that gets called whenever an attestation is made against a specific schema. This enables: + +- **Validation** — accept or reject attestations based on custom logic +- **Side effects** — execute actions atomically with attestation creation +- **Composability** — combine attestations with any onchain logic + +### Basic resolver (LocationGatedAction) + + + **About the Astral signer**: The `astralSigner` address is the key that signs attestations inside the TEE. Signer management (multisig, key rotation, etc.) is on the roadmap. See [Security](/resources/security) for more details. + + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@eas/contracts/resolver/SchemaResolver.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract LocationGatedAction is SchemaResolver, Ownable { + address public astralSigner; + + constructor(IEAS eas, address _astralSigner) SchemaResolver(eas) { + astralSigner = _astralSigner; + } + + function onAttest( + Attestation calldata attestation, + uint256 /* value */ + ) internal override returns (bool) { + // 1. Verify from Astral + require(attestation.attester == astralSigner, "Not from Astral"); + + // 2. Decode policy result (BooleanPolicyAttestation) + ( + bool result, + bytes32[] memory inputRefs, + uint64 timestamp, + string memory operation + ) = abi.decode( + attestation.data, + (bool, bytes32[], uint64, string) + ); + + // 3. Execute business logic + require(result, "Policy check failed"); + _executeAction(attestation.recipient); + + return true; + } + + function onRevoke(Attestation calldata, uint256) + internal pure override returns (bool) + { + return false; // Don't allow revocation + } + + function _executeAction(address recipient) internal virtual { + // Override in child contracts + } + + // Owner-controlled signer update for key rotation + function updateAstralSigner(address newSigner) external onlyOwner { + astralSigner = newSigner; + } +} +``` + +### Common patterns + +#### NFT minting + +```solidity +contract LocationNFT is LocationGatedAction, ERC721 { + mapping(address => bool) public hasMinted; + uint256 public nextTokenId = 1; + + function _executeAction(address recipient) internal override { + require(!hasMinted[recipient], "Already minted"); + hasMinted[recipient] = true; + _mint(recipient, nextTokenId++); + } +} +``` + +#### Token distribution + +```solidity +contract LocationAirdrop is LocationGatedAction { + IERC20 public token; + uint256 public amount; + + function _executeAction(address recipient) internal override { + token.transfer(recipient, amount); + } +} +``` + +#### Access control + +```solidity +contract LocationGate is LocationGatedAction { + mapping(address => bool) public hasAccess; + + function _executeAction(address recipient) internal override { + hasAccess[recipient] = true; + } + + modifier onlyVerified() { + require(hasAccess[msg.sender], "Location not verified"); + _; + } +} +``` + +#### Numeric policies (distance-based) + +Use numeric attestations (distance, area, length) for more sophisticated logic like [spatial demurrage](https://www.johnx.co/notes/spatial-demurrage) — where transfer fees vary based on distance from a target location. + +```solidity +// Decode numeric policy attestation +( + uint256 distanceCm, + string memory units, + bytes32[] memory inputRefs, + uint64 timestamp, + string memory operation +) = abi.decode(attestation.data, (uint256, string, bytes32[], uint64, string)); + +// Apply distance-based fee (example: 0.1% per km from target) +uint256 distanceKm = distanceCm / 100000; +uint256 fee = (amount * distanceKm) / 1000; +``` + +### Decoding attestation data + +#### Boolean policies + +```solidity +( + bool result, + bytes32[] memory inputRefs, + uint64 timestamp, + string memory operation +) = abi.decode( + attestation.data, + (bool, bytes32[], uint64, string) +); +``` + +#### Numeric policies + +```solidity +( + uint256 result, // Scaled integer (centimeters) + string memory units, // "meters" or "square_meters" + bytes32[] memory inputRefs, + uint64 timestamp, + string memory operation +) = abi.decode( + attestation.data, + (uint256, string, bytes32[], uint64, string) +); + +// Convert back to meters +uint256 meters = result / 100; +``` + +## Registering your schema + +Before submitting attestations, register your schema with EAS and point it at your resolver: + +```typescript +import { SchemaRegistry } from '@ethereum-attestation-service/eas-sdk'; + +const schemaRegistry = new SchemaRegistry(SCHEMA_REGISTRY_ADDRESS); + +// Boolean policy schema +const boolSchema = "bool result,bytes32[] inputRefs,uint64 timestamp,string operation"; + +const tx = await schemaRegistry.connect(signer).register({ + schema: boolSchema, + resolverAddress: yourResolver.address, + revocable: false +}); + +const receipt = await tx.wait(); +const schemaUID = receipt.logs[0].args.uid; +``` + +## Onchain vs offchain location records + +Location attestations — the spatial inputs to computations — can be stored onchain or offchain: + + + + - Stored on EAS contracts + - Referenced by UID alone + - Higher gas cost + - Permanent, immutable + + + - Stored on IPFS, servers, etc. + - Referenced by UID + URI + - No gas cost to create + - EIP-712 signed + + + +### Onchain + +```typescript +// Create onchain attestation +const location = await astral.location.create(geojson, { + submitOnchain: true +}); + +// Reference by UID only +await astral.compute.distance(location.uid, otherUID); +``` + +### Offchain + +```typescript +// Create offchain attestation +const location = await astral.location.create(geojson, { + submitOnchain: false, + storage: 'ipfs' // or 'arweave', 'https', etc. +}); + +// Reference by UID + URI +await astral.compute.distance( + { uid: location.uid, uri: location.uri }, + otherUID +); +``` + +## Chain configuration + +Astral supports EAS on multiple EVM-compatible chains. See [Schema registry](/resources/schemas) for schema UIDs by chain. + +## Verification best practices + + + + Always check `attestation.attester == astralSigner`: + ```solidity + require(attestation.attester == astralSigner, "Not from Astral"); + ``` + + + Prevent replay of old attestations: + ```solidity + require(timestamp > block.timestamp - 1 hours, "Attestation too old"); + ``` + + + Ensure the right locations were checked: + ```solidity + require(inputRefs[1] == EXPECTED_LANDMARK_UID, "Wrong location"); + ``` + + + Prevent reuse of attestations: + ```solidity + mapping(bytes32 => bool) public usedAttestations; + + function onAttest(...) { + bytes32 attUid = keccak256(abi.encode(attestation)); + require(!usedAttestations[attUid], "Already used"); + usedAttestations[attUid] = true; + } + ``` + + + +## Key rotation + +Resolver contracts should support updating the Astral signer address. For the research preview, a simple owner-controlled approach works: + +```solidity +function updateAstralSigner(address newSigner) external onlyOwner { + emit SignerUpdated(astralSigner, newSigner); + astralSigner = newSigner; +} +``` + + + For production deployments, you may want the owner to be a multisig. We plan to provide more graceful key rotation mechanisms in future releases. + diff --git a/guides/building-plugins.mdx b/guides/building-plugins.mdx new file mode 100644 index 0000000..9fc1f7a --- /dev/null +++ b/guides/building-plugins.mdx @@ -0,0 +1,284 @@ +--- +title: "Building verification plugins" +description: "Connect a new proof-of-location system to Astral" +--- + +**Research preview** — APIs may change. [GitHub](https://github.com/AstralProtocol) + +# Building verification plugins + +A plugin connects a proof-of-location (PoL) system to Astral's verification framework. Plugins collect signals from a PoL system, produce location stamps, and verify those stamps. + + + The plugin interface is under active development. The patterns shown here reflect the current design direction, but specifics may change as we iterate on the verification framework. + + +## What is a plugin? + +Proof-of-location systems vary widely — hardware attestation, network triangulation, sensor fusion, institutional records. A plugin is a standardized adapter that translates a specific PoL system's output into the common stamp format that Astral can verify and cross-correlate. + +Each plugin handles three responsibilities: + +1. **Collect signals** from the PoL system (GPS readings, network measurements, device attestations) +2. **Create stamps** from those signals (structured evidence artifacts) +3. **Verify stamps** for authenticity and structural integrity + +## Plugin interface + +```typescript +interface LocationProofPlugin { + name: string; + version: string; + + // Collect raw signals from the PoL system + collectSignals(config: PluginConfig): Promise; + + // Process signals into a location stamp + createStamp(signals: Signal[]): Promise; + + // Verify a stamp's authenticity and structure + verifyStamp(stamp: LocationStamp): Promise; +} + +interface PluginConfig { + timeout: number; + [key: string]: unknown; +} + +interface Signal { + type: string; + value: unknown; + timestamp: number; + source: string; +} + +interface StampVerificationResult { + valid: boolean; + errors: string[]; + details: Record; +} +``` + +## How stamps work + +A stamp is a signed artifact from a PoL system. It encodes the system's conclusion about where and when an event occurred, along with the raw signals that support that conclusion. + +Stamps follow the [Location Protocol](https://github.com/DecentralizedGeo/location-protocol-spec) format: + +```typescript +interface LocationStamp { + // Location data (LP v0.2) + lpVersion: string; + locationType: string; + location: LocationData; // Where evidence indicates the subject was + srs: string; + + // Temporal footprint + temporalFootprint: { start: number; end: number }; + + // Plugin identification + plugin: string; // "proofmode", "witnesschain", etc. + pluginVersion: string; + + // Evidence and signatures + signals: Record; + signatures: Signature[]; +} +``` + +The distinction between a stamp's location and a claim's location is important. The stamp records where the PoL system *observed* the subject. The claim records where the subject *asserts* they were. Verification compares the two. + +## Implementation guide + +Here is a step-by-step walkthrough for building a hypothetical plugin that uses Wi-Fi access point data. + +### Step 1: Define signal collection + +```typescript +import type { LocationProofPlugin, PluginConfig, Signal } from '@decentralized-geo/astral-sdk'; + +const wifiPlugin: LocationProofPlugin = { + name: 'wifi-triangulation', + version: '0.1.0', + + async collectSignals(config: PluginConfig): Promise { + const networks = await scanNearbyNetworks(config.timeout); + + return networks.map((network) => ({ + type: 'wifi-ap', + value: { + bssid: network.bssid, + ssid: network.ssid, + rssi: network.signalStrength, + frequency: network.frequency + }, + timestamp: Date.now() / 1000, + source: 'device-wifi-scan' + })); + }, + + // ... continued below +}; +``` + +### Step 2: Create stamps from signals + +```typescript + async createStamp(signals: Signal[]): Promise { + // Triangulate position from Wi-Fi signals + const position = triangulateFromAccessPoints( + signals.map((s) => s.value as WifiSignal) + ); + + return { + lpVersion: '0.2', + locationType: 'geojson-point', + location: { + type: 'Point', + coordinates: [position.longitude, position.latitude] + }, + srs: 'http://www.opengis.net/def/crs/OGC/1.3/CRS84', + temporalFootprint: { + start: Math.min(...signals.map((s) => s.timestamp)), + end: Math.max(...signals.map((s) => s.timestamp)) + }, + plugin: 'wifi-triangulation', + pluginVersion: '0.1.0', + signals: { + accessPoints: signals.map((s) => s.value), + triangulationMethod: 'weighted-centroid' + } + }; + }, +``` + +### Step 3: Implement stamp verification + +```typescript + async verifyStamp(stamp: LocationStamp): Promise { + const errors: string[] = []; + + // Check plugin identification + if (stamp.plugin !== 'wifi-triangulation') { + errors.push(`Expected plugin 'wifi-triangulation', got '${stamp.plugin}'`); + } + + // Verify signal structure + const signals = stamp.signals as { accessPoints?: unknown[] }; + if (!signals.accessPoints || signals.accessPoints.length < 3) { + errors.push('Wi-Fi triangulation requires at least 3 access points'); + } + + // Verify temporal bounds are reasonable + const duration = stamp.temporalFootprint.end - stamp.temporalFootprint.start; + if (duration > 60) { + errors.push('Signal collection window exceeds 60 seconds'); + } + + // Verify signatures + const signaturesValid = await verifyStampSignatures(stamp); + if (!signaturesValid) { + errors.push('Stamp signatures are invalid'); + } + + return { + valid: errors.length === 0, + errors, + details: { + accessPointCount: signals.accessPoints?.length ?? 0, + collectionDuration: duration, + signaturesChecked: stamp.signatures.length + } + }; + } +``` + +## Registration + +Register your plugin with the Astral SDK so it can be used in stamp collection and verification: + +```typescript +import { AstralSDK } from '@decentralized-geo/astral-sdk'; + +const astral = new AstralSDK({ chainId: 84532 }); + +astral.plugins.register(wifiPlugin); + +// Now you can collect stamps using your plugin +const signals = await astral.stamps.collect({ + name: 'wifi-triangulation', + version: '0.1.0', + config: { timeout: 5000 } +}); + +const stamp = await astral.stamps.create( + { name: 'wifi-triangulation', version: '0.1.0', config: {} }, + signals +); +``` + +## Testing + +Test each plugin responsibility independently: + +```typescript +import { describe, it, expect } from 'vitest'; + +describe('wifi-triangulation plugin', () => { + it('collects signals from nearby networks', async () => { + const signals = await wifiPlugin.collectSignals({ timeout: 5000 }); + + expect(signals.length).toBeGreaterThan(0); + for (const signal of signals) { + expect(signal.type).toBe('wifi-ap'); + expect(signal.timestamp).toBeGreaterThan(0); + } + }); + + it('creates a valid stamp from signals', async () => { + const signals = mockWifiSignals(5); + const stamp = await wifiPlugin.createStamp(signals); + + expect(stamp.lpVersion).toBe('0.2'); + expect(stamp.plugin).toBe('wifi-triangulation'); + expect(stamp.location.type).toBe('Point'); + expect(stamp.location.coordinates).toHaveLength(2); + }); + + it('rejects stamps with fewer than 3 access points', async () => { + const stamp = createMockStamp({ accessPointCount: 2 }); + const result = await wifiPlugin.verifyStamp(stamp); + + expect(result.valid).toBe(false); + expect(result.errors).toContainEqual( + expect.stringContaining('at least 3 access points') + ); + }); +}); +``` + +## Existing plugins + +Two plugins are currently in development: + + + + Device attestation and sensor fusion. Uses Secure Enclave (iOS) or hardware keystore (Android) to sign location observations. + + + Infrastructure verification using UDP latency triangulation and a challenger network. Trust derives from speed-of-light constraints and cryptoeconomic incentives. + + + +Each plugin documents its own threat model and trust assumptions. When building a new plugin, you should clearly document what an attacker would need to do to forge a stamp from your system. + +## Next steps + + + + Understand how stamps combine into verifiable proofs + + + Submit proofs and understand credibility scores + + diff --git a/guides/calling-the-api.mdx b/guides/calling-the-api.mdx new file mode 100644 index 0000000..46e92e2 --- /dev/null +++ b/guides/calling-the-api.mdx @@ -0,0 +1,216 @@ +--- +title: "Calling the API" +description: "Request format, authentication, and response handling" +--- + +**Research preview** — APIs may change. [GitHub](https://github.com/AstralProtocol) + +# Calling the API + +This guide covers the mechanics of making requests to Astral — how to format inputs, what comes back, and how to handle errors. + +## Base URL + +| Environment | URL | +|-------------|-----| +| Production | `https://api.astral.global` | +| Local development | `http://localhost:3004` | + +See [Local development](/guides/local-development) for setup instructions. + +## Request format + +All compute endpoints accept `POST` requests with a JSON body. The required fields depend on the operation, but every request needs a `chainId` to identify which chain to sign for. + +### Distance, length + +```json +{ + "from": { "type": "Point", "coordinates": [2.2945, 48.8584] }, + "to": { "type": "Point", "coordinates": [2.3522, 48.8566] }, + "chainId": 84532 +} +``` + +### Contains, intersects + +```json +{ + "container": { + "type": "Polygon", + "coordinates": [[[2.28, 48.85], [2.30, 48.85], [2.30, 48.87], [2.28, 48.87], [2.28, 48.85]]] + }, + "geometry": { "type": "Point", "coordinates": [2.2945, 48.8584] }, + "chainId": 84532 +} +``` + +### Optional fields + +| Field | Description | +|-------|-------------| +| `schema` | EAS schema UID — required if you plan to submit the result onchain | +| `recipient` | Ethereum address to set as the attestation recipient | + +## Geographic feature inputs + +You can pass geographic features in four formats: + +### Raw GeoJSON + +A GeoJSON geometry object directly in the request: + +```json +{ + "from": { "type": "Point", "coordinates": [2.2945, 48.8584] } +} +``` + +### UID string + +A reference to an onchain location attestation: + +```json +{ + "from": "0xabc123...def456" +} +``` + +### UID + URI (offchain attestation) + +A reference to an offchain attestation stored on IPFS or another location: + +```json +{ + "from": { "uid": "0xabc123...", "uri": "ipfs://Qm..." } +} +``` + +### Inline signed attestation + +A full offchain attestation object: + +```json +{ + "from": { "attestation": { "uid": "0x...", "schema": "0x...", "data": "0x..." } } +} +``` + +## Authentication + +No authentication is currently required. Rate limits apply by IP address (100 requests/hour for unauthenticated clients). Wallet-based authentication with higher limits is planned. + +## Response format + +A successful response includes the computed result, proof of computation, and a pre-signed attestation for optional onchain submission. + +```json +{ + "result": 4520.37, + "units": "meters", + "operation": "distance", + "timestamp": 1706400000, + "inputRefs": [ + "0xabc123...input_a_hash", + "0xdef456...input_b_hash" + ], + "attestation": { + "uid": "0x...", + "schema": "0x...", + "attester": "0x...", + "recipient": "0x...", + "data": "0x...", + "signature": "0x..." + }, + "delegatedAttestation": { + "signature": "0x7f8e9d...tee_signature", + "attester": "0x590fdb53ed3f0B52694876d42367192a5336700F", + "deadline": 1706403600 + } +} +``` + +Here is what each field means: + +- **`result`** — The computed answer. A number for distance/area/length, a boolean for contains/within/intersects. +- **`units`** — Unit of measurement (for numeric results). `"meters"` or `"square_meters"`. +- **`operation`** — The spatial operation that was performed. +- **`timestamp`** — Unix timestamp of when the computation ran. +- **`inputRefs`** — Hashes of the input geographic features. These let you verify which inputs were used in the computation. +- **`attestation`** — The full EAS attestation data, including the TEE's cryptographic signature. +- **`delegatedAttestation`** — A pre-signed attestation ready for onchain submission via EAS. The `deadline` indicates when the signature expires. + +## Error handling + +Errors follow [RFC 7807](https://tools.ietf.org/html/rfc7807) (Problem Details for HTTP APIs): + +```json +{ + "type": "https://api.astral.global/errors/invalid-input", + "title": "Invalid Input", + "status": 400, + "detail": "Field 'from' must be a valid GeoJSON geometry or attestation UID" +} +``` + +### Common error types + +| Type | Status | What it means | +|------|--------|---------------| +| `invalid-input` | 400 | Bad request data, missing fields, or invalid geometry | +| `attestation-not-found` | 404 | The UID does not exist on the specified chain | +| `verification-failed` | 401 | Signature verification failed | +| `computation-error` | 500 | The PostGIS operation failed | +| `rate-limited` | 429 | Too many requests — wait and retry | + +### Handling errors in code + +```typescript +const response = await fetch('https://api.astral.global/compute/distance', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + from: { type: 'Point', coordinates: [2.2945, 48.8584] }, + to: { type: 'Point', coordinates: [2.3522, 48.8566] }, + chainId: 84532 + }) +}); + +if (!response.ok) { + const error = await response.json(); + console.error(`${error.title}: ${error.detail}`); + return; +} + +const result = await response.json(); +console.log(`Distance: ${result.result} ${result.units}`); +``` + +## Full example: curl + +```bash +curl -X POST https://api.astral.global/compute/contains \ + -H "Content-Type: application/json" \ + -d '{ + "container": { + "type": "Polygon", + "coordinates": [[[2.28, 48.85], [2.30, 48.85], [2.30, 48.87], [2.28, 48.87], [2.28, 48.85]]] + }, + "geometry": { "type": "Point", "coordinates": [2.2945, 48.8584] }, + "chainId": 84532 + }' +``` + +## Next steps + + + + Use Astral as a spatial oracle in agent workflows + + + Submit signed results onchain via EAS + + + Full endpoint documentation + + diff --git a/guides/local-development.mdx b/guides/local-development.mdx new file mode 100644 index 0000000..dc0d89c --- /dev/null +++ b/guides/local-development.mdx @@ -0,0 +1,149 @@ +--- +title: "Local development" +description: "Run Astral locally for development and testing" +--- + +**Research preview** — APIs may change. [GitHub](https://github.com/AstralProtocol) + +# Local development + +This guide walks you through running Astral on your machine. By the end, you will have a working local instance that responds to geocomputation requests. + +## Prerequisites + +- **Node.js 20+** — the service uses the `--env-file` flag, which requires Node 20 +- **Docker** — for PostgreSQL with PostGIS +- **pnpm** — the monorepo uses pnpm workspaces + +## Clone and install + +```bash +git clone https://github.com/AstralProtocol/astral-location-services.git +cd astral-location-services +pnpm install +``` + +## Start PostgreSQL + PostGIS + +The repo includes a development Docker Compose file that runs PostgreSQL with PostGIS: + +```bash +docker compose -f docker-compose.dev.yml up -d +``` + +Verify the database is running: + +```bash +docker compose -f docker-compose.dev.yml ps +``` + +### Port conflicts + +If port 5432 is already in use by another PostgreSQL instance, create a `docker-compose.override.yml` that maps to a different port (e.g., 5434:5432) and update your `DATABASE_URL` accordingly: + +```yaml +# docker-compose.override.yml +services: + postgres: + ports: + - "5434:5432" +``` + +Then set `DATABASE_URL=postgresql://postgres:postgres@localhost:5434/astral` in your environment file. + +## Environment setup + +```bash +cp packages/astral-service/.env.example packages/astral-service/.env.local +``` + +Edit `.env.local` with your configuration. You will need a `SIGNER_PRIVATE_KEY` — any Ethereum private key works for development. You can generate one with `openssl rand -hex 32`. + +The important fields: + +| Variable | Description | Example | +|----------|-------------|---------| +| `DATABASE_URL` | PostgreSQL connection string | `postgresql://postgres:postgres@localhost:5432/astral` | +| `PORT` | HTTP server port | `3004` | +| `SIGNER_PRIVATE_KEY` | Ethereum private key for signing results | `0xac0974bec...` | +| `CHAIN_ID` | Default chain ID | `84532` (Base Sepolia) | + +## Start the service + + +The service does not use dotenv. You must pass the env file explicitly using Node's `--env-file` flag. + + +```bash +node --env-file=packages/astral-service/.env.local --import tsx packages/astral-service/src/index.ts +``` + +The `npm run dev` script may not work reliably. The command above is the most reliable way to start the service. + +You should see output indicating the server is listening on the configured port. + +## Health check + +Confirm the service is running: + +```bash +curl http://localhost:3004/health +``` + +A successful response means the service is up and connected to the database. + +## Smoke test + +Run a distance computation between the Eiffel Tower and a point across the Seine: + +```bash +curl -X POST http://localhost:3004/compute/distance \ + -H "Content-Type: application/json" \ + -d '{ + "from": { "type": "Point", "coordinates": [2.2945, 48.8584] }, + "to": { "type": "Point", "coordinates": [2.3522, 48.8566] }, + "chainId": 84532 + }' +``` + +The response includes the distance in meters, a cryptographic signature, and input references. If you see a `result` field with a numeric value, everything is working. + +## Platform notes + + + + PostGIS Docker images may need an explicit platform flag. If the container fails to start, add `platform: linux/amd64` to the postgres service in your compose file: + + ```yaml + services: + postgres: + platform: linux/amd64 + image: postgis/postgis:16-3.4 + ``` + + This runs under Rosetta emulation, which is slower but functional. + + + + These ports may collide with other local services: + + | Port | Used by | Common conflict | + |------|---------|----------------| + | 5432 | PostgreSQL | Other Postgres instances, Homebrew Postgres | + | 3004 | Astral service | Other dev servers | + | 3000-3003 | Other monorepo packages | Next.js, React dev servers | + + Use the `docker-compose.override.yml` approach for database port conflicts, and change `PORT` in `.env.local` for service port conflicts. + + + +## Next steps + + + + Learn the request format and response structure + + + Your first verified spatial computation + + diff --git a/guides/verifying-location-proofs.mdx b/guides/verifying-location-proofs.mdx new file mode 100644 index 0000000..9a29551 --- /dev/null +++ b/guides/verifying-location-proofs.mdx @@ -0,0 +1,248 @@ +--- +title: "Verifying location proofs" +description: "Submit location proofs and understand credibility scores" +--- + +**Research preview** — APIs may change. [GitHub](https://github.com/AstralProtocol) + +# Verifying location proofs + +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. + +## What is a location proof? + +A location proof has two parts: + +- **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. + +## Creating a location claim + +A claim follows the [Location Protocol](https://github.com/DecentralizedGeo/location-protocol-spec) format and includes the asserted location, time bounds, and spatial uncertainty: + +```typescript +const claim = { + lpVersion: '0.2', + locationType: 'geojson-point', + location: { type: 'Point', coordinates: [-122.4194, 37.7749] }, + srs: 'http://www.opengis.net/def/crs/OGC/1.3/CRS84', + subject: { scheme: 'eth-address', value: '0x1234...abcd' }, + radius: 100, + time: { start: Date.now() / 1000 - 60, end: Date.now() / 1000 }, + eventType: 'presence' +}; +``` + +Key fields: + +| Field | Description | +|-------|-------------| +| `location` | 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. + + +## Collecting stamps + +Stamps are evidence from proof-of-location plugins. Each plugin collects signals from its PoL system and produces a stamp: + +```typescript +import { AstralSDK } from '@decentralized-geo/astral-sdk'; + +const astral = new AstralSDK({ chainId: 84532 }); + +// Collect signals from ProofMode (device attestation) +const proofmodeSignals = await astral.stamps.collect({ + name: 'proofmode', + version: '0.1.0', + config: { timeout: 5000 } +}); + +// Create and sign the stamp +const unsignedStamp = await astral.stamps.create( + { name: 'proofmode', version: '0.1.0', config: {} }, + proofmodeSignals +); +const stamp1 = await astral.stamps.sign(unsignedStamp, deviceSigner); +``` + +For stronger verification, collect stamps from multiple independent systems: + +```typescript +// Collect from a second independent system +const witnessSignals = await astral.stamps.collect({ + name: 'witnesschain', + version: '0.1.0', + config: { timeout: 10000 } +}); + +const unsignedWitnessStamp = await astral.stamps.create( + { name: 'witnesschain', version: '0.1.0', config: {} }, + witnessSignals +); +const stamp2 = await astral.stamps.sign(unsignedWitnessStamp, nodeSigner); +``` + +## Bundling into a proof + +Combine the claim and stamps into a location proof: + +```typescript +const proof = astral.proofs.create(claim, [stamp1, stamp2]); +``` + +A single-stamp proof is valid. Multiple stamps from independent systems enable cross-correlation, which increases confidence. + +## Submitting to the verify API + +Submit the proof for verification: + +```typescript +const result = await astral.verify.proof(proof); +``` + +Or via raw HTTP: + +```bash +curl -X POST https://api.astral.global/verify/proof \ + -H "Content-Type: application/json" \ + -d '{ + "claim": { + "lpVersion": "0.2", + "locationType": "geojson-point", + "location": { "type": "Point", "coordinates": [-122.4194, 37.7749] }, + "srs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84", + "subject": { "scheme": "eth-address", "value": "0x1234...abcd" }, + "radius": 100, + "time": { "start": 1706399940, "end": 1706400000 }, + "eventType": "presence" + }, + "stamps": [ ... ] + }' +``` + +## Understanding credibility scores + +The verification result is a structured credibility assessment, not a simple pass/fail: + +```json +{ + "credibility": { + "confidence": 0.85, + "stampResults": [ + { + "plugin": "proofmode", + "valid": true, + "spatialAccuracy": 0.9, + "temporalIntegrity": 0.8, + "forgeryResistance": 0.7 + }, + { + "plugin": "witnesschain", + "valid": true, + "spatialAccuracy": 0.85, + "temporalIntegrity": 0.9, + "forgeryResistance": 0.8 + } + ], + "correlation": { + "independence": 0.95, + "agreement": 0.92 + } + }, + "uid": "0xabc123...", + "attester": "0x590fdb53...", + "timestamp": 1706400000 +} +``` + +### Credibility dimensions + +Credibility scores are a multidimensional assessment. Each stamp is evaluated along several dimensions: + +| Dimension | What it measures | +|-----------|-----------------| +| **Spatial accuracy** | How precisely the stamp's observed location matches the claimed location | +| **Temporal integrity** | Whether the stamp's timestamps are consistent and within the claimed time window | +| **Forgery resistance** | How difficult it would be to fabricate this stamp (depends on the PoL system's threat model) | +| **Source independence** | Whether the stamp comes from a system that is independent of other stamps in the proof | + +### Overall confidence + +The top-level `confidence` score (0 to 1) is a summary of the per-stamp assessments and cross-correlation analysis. It reflects the overall strength of evidence for the claim. + + + **Confidence is not probability.** A confidence of 0.85 does not mean "85% chance the claim is true." It means the evidence is strong. Calibrating confidence scores to true probabilities is future work. + + +### Cross-correlation + +When a proof includes multiple stamps, the verification engine analyzes their relationship: + +- **Independence** — are the stamps from truly independent systems? Two stamps from the same underlying data source do not add much. +- **Agreement** — do the stamps agree on location and time? Independent stamps that corroborate each other significantly boost confidence. + +## Multi-factor proofs + +Multiple stamps from independent systems increase confidence because an attacker would need to compromise multiple unrelated systems simultaneously: + +```typescript +// Single stamp: moderate confidence +const singleResult = await astral.verify.proof( + astral.proofs.create(claim, [proofmodeStamp]) +); +// singleResult.credibility.confidence → 0.7 + +// Multi-stamp from independent systems: higher confidence +const multiResult = await astral.verify.proof( + astral.proofs.create(claim, [proofmodeStamp, witnessStamp]) +); +// multiResult.credibility.confidence → 0.9 +// multiResult.credibility.correlation.independence → 0.95 +``` + +The improvement comes from source independence. Redundant stamps from the same system do not meaningfully increase confidence, but they do not decrease it either. + +### Choosing the right level of evidence + +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. + +## Using verified proofs as compute inputs + +Verified location proofs can serve as trusted inputs to geocomputation operations. This connects the verification pipeline to the spatial reasoning pipeline: + +```typescript +const verifiedProof = await astral.verify.proof(proof); + +// Use the verified proof as input to a spatial operation +const result = await astral.compute.contains({ + container: approvedZonePolygonUID, + geometry: { verifiedProof: verifiedProof.uid }, + chainId: 84532, + schema: SCHEMA_UID +}); +``` + +## Next steps + + + + Deeper dive into claims, stamps, and the verification model + + + Connect a new proof-of-location system to Astral + + From 3e8ee20dc0f43ce25359671a79c15bab03d28f91 Mon Sep 17 00:00:00 2001 From: John X Date: Mon, 16 Feb 2026 21:25:31 -0500 Subject: [PATCH 06/17] docs: add use case pages - delivery-verification: escrow + proximity resolver - parametric-insurance: crop insurance with distance threshold - geofence-compliance: drone corridor monitoring - onchain-attestation: NFT minting, token distribution patterns Each page uses story framing with concrete code examples. delivery-verification relocated from guides/ with terminology updates. --- use-cases/delivery-verification.mdx | 333 ++++++++++++++++++++++++++++ use-cases/geofence-compliance.mdx | 159 +++++++++++++ use-cases/onchain-attestation.mdx | 255 +++++++++++++++++++++ use-cases/parametric-insurance.mdx | 224 +++++++++++++++++++ 4 files changed, 971 insertions(+) create mode 100644 use-cases/delivery-verification.mdx create mode 100644 use-cases/geofence-compliance.mdx create mode 100644 use-cases/onchain-attestation.mdx create mode 100644 use-cases/parametric-insurance.mdx diff --git a/use-cases/delivery-verification.mdx b/use-cases/delivery-verification.mdx new file mode 100644 index 0000000..25e3ebf --- /dev/null +++ b/use-cases/delivery-verification.mdx @@ -0,0 +1,333 @@ +--- +title: "Delivery verification" +description: "Escrow that releases when the courier arrives" +--- + +**Research preview** — APIs may change. [GitHub](https://github.com/AstralProtocol) + +# Delivery verification + +You're building a delivery platform. Buyers lock payment in escrow. When the courier arrives at the delivery address, the escrow releases automatically. No manual confirmation, no disputes about whether the courier actually showed up. + + + **About location verification**: This guide uses GPS coordinates as input. GPS is spoofable. Astral is developing [location proof plugins](https://collective.flashbots.net/t/towards-stronger-location-proofs/5323) for stronger verification — these are still in development. + + +## How it works + +1. Buyer creates a delivery order and locks payment in an escrow contract +2. The delivery address is registered as a location record onchain +3. When the courier arrives, their app checks proximity using `compute.within` +4. Astral returns a signed result confirming the courier is within range +5. The signed result is submitted onchain, triggering the escrow to release funds + +## The escrow contract + +The resolver contract holds funds and releases them when it receives a valid signed result proving the courier arrived. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@eas/contracts/resolver/SchemaResolver.sol"; +import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; + +contract DeliveryEscrow is SchemaResolver, ReentrancyGuard { + address public astralSigner; + + struct Delivery { + address buyer; + address courier; + bytes32 destinationUID; // Location record of delivery address + uint256 amount; + uint256 radius; // Acceptable radius in meters + uint256 deadline; + bool completed; + bool refunded; + } + + mapping(bytes32 => Delivery) public deliveries; + mapping(bytes32 => bool) public usedAttestations; + + event DeliveryCreated(bytes32 indexed deliveryId, address buyer, uint256 amount); + event DeliveryCompleted(bytes32 indexed deliveryId, address courier); + event DeliveryRefunded(bytes32 indexed deliveryId, address buyer); + + constructor(IEAS eas, address _astralSigner) SchemaResolver(eas) { + astralSigner = _astralSigner; + } + + // Buyer creates delivery escrow + function createDelivery( + bytes32 deliveryId, + address courier, + bytes32 destinationUID, + uint256 radiusMeters, + uint256 deadline + ) external payable { + require(msg.value > 0, "Must send payment"); + require(deliveries[deliveryId].buyer == address(0), "Already exists"); + require(deadline > block.timestamp, "Invalid deadline"); + + deliveries[deliveryId] = Delivery({ + buyer: msg.sender, + courier: courier, + destinationUID: destinationUID, + amount: msg.value, + radius: radiusMeters * 100, // Convert to cm + deadline: deadline, + completed: false, + refunded: false + }); + + emit DeliveryCreated(deliveryId, msg.sender, msg.value); + } + + function onAttest( + Attestation calldata attestation, + uint256 /*value*/ + ) internal override returns (bool) { + require(attestation.attester == astralSigner, "Not from Astral"); + require(!usedAttestations[attestation.uid], "Already used"); + usedAttestations[attestation.uid] = true; + + // Decode the signed result + ( + bool isWithinRadius, + bytes32[] memory inputRefs, + uint64 timestamp, + string memory operation + ) = abi.decode( + attestation.data, + (bool, bytes32[], uint64, string) + ); + + // Verify operation type + require( + keccak256(bytes(operation)) == keccak256(bytes("within")), + "Wrong operation" + ); + + // Extract delivery ID from recipient field + bytes32 deliveryId = bytes32(uint256(uint160(attestation.recipient))); + Delivery storage delivery = deliveries[deliveryId]; + + require(delivery.buyer != address(0), "Delivery not found"); + require(!delivery.completed, "Already completed"); + require(!delivery.refunded, "Already refunded"); + require(block.timestamp <= delivery.deadline, "Deadline passed"); + + // Verify correct destination was checked + require(inputRefs.length >= 2, "Invalid inputs"); + require(inputRefs[1] == delivery.destinationUID, "Wrong destination"); + + // Verify courier is within radius + require(isWithinRadius, "Not at delivery location"); + + // Verify timestamp is recent + require(timestamp > block.timestamp - 30 minutes, "Result too old"); + + // Complete delivery + delivery.completed = true; + + // Release funds to courier + (bool success, ) = delivery.courier.call{value: delivery.amount}(""); + require(success, "Transfer failed"); + + emit DeliveryCompleted(deliveryId, delivery.courier); + return true; + } + + // Buyer can refund after deadline + function refund(bytes32 deliveryId) external nonReentrant { + Delivery storage delivery = deliveries[deliveryId]; + + require(msg.sender == delivery.buyer, "Not buyer"); + require(!delivery.completed, "Already completed"); + require(!delivery.refunded, "Already refunded"); + require(block.timestamp > delivery.deadline, "Deadline not passed"); + + delivery.refunded = true; + + (bool success, ) = delivery.buyer.call{value: delivery.amount}(""); + require(success, "Refund failed"); + + emit DeliveryRefunded(deliveryId, delivery.buyer); + } + + function onRevoke(Attestation calldata, uint256) internal pure override returns (bool) { + return false; + } +} +``` + +## Buyer flow + +The buyer creates a delivery order by registering the destination onchain and locking payment. + +```typescript +import { AstralSDK } from '@decentralized-geo/astral-sdk'; +import { ethers } from 'ethers'; + +async function createDeliveryOrder( + destinationCoords: [number, number], + courierAddress: string, + paymentAmount: bigint, + wallet: Signer +) { + const astral = new AstralSDK({ chainId: 84532, signer: wallet }); + const escrow = new Contract(ESCROW_ADDRESS, ESCROW_ABI, wallet); + + // Create destination location record + const destination = await astral.location.onchain.create({ + location: { type: 'Point', coordinates: destinationCoords }, + memo: "Delivery Address" + }); + + // Generate delivery ID + const deliveryId = ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode( + ['address', 'address', 'uint256'], + [await wallet.getAddress(), courierAddress, Date.now()] + )); + + // Create escrow + const tx = await escrow.createDelivery( + deliveryId, + courierAddress, + destination.uid, + 100, // 100 meter radius + Math.floor(Date.now() / 1000) + 86400, // 24 hour deadline + { value: paymentAmount } + ); + + await tx.wait(); + + return { + deliveryId, + destinationUID: destination.uid + }; +} +``` + +## Courier flow + +When the courier arrives, their app checks proximity and submits the signed result to release payment. + +```typescript +async function confirmDelivery( + deliveryId: string, + destinationUID: string, + wallet: Signer +) { + const astral = new AstralSDK({ chainId: 84532, signer: wallet }); + + // Get current location + const coords = await getCurrentLocation(); + + // Create location record + const courierLocation = await astral.location.onchain.create({ + location: { type: 'Point', coordinates: coords } + }); + + // Prove within radius of destination + const proof = await astral.compute.within( + courierLocation.uid, + destinationUID, + 100, // Must match contract radius + { + schema: SCHEMA_UID, + recipient: deliveryId // Pass delivery ID + } + ); + + if (!proof.result) { + throw new Error('Not close enough to delivery address'); + } + + // Submit signed result — triggers payment release + const tx = await astral.compute.submit(proof.delegatedAttestation); + await tx.wait(); + + return { success: true, transactionHash: tx.hash }; +} +``` + +## Mobile integration + +```typescript +// React Native example +import Geolocation from '@react-native-community/geolocation'; + +function DeliveryConfirmButton({ deliveryId, destinationUID }) { + const [status, setStatus] = useState<'idle' | 'checking' | 'confirming' | 'done'>('idle'); + + const handleConfirm = async () => { + setStatus('checking'); + + Geolocation.getCurrentPosition( + async (position) => { + const coords: [number, number] = [ + position.coords.longitude, + position.coords.latitude + ]; + + try { + setStatus('confirming'); + await confirmDelivery(deliveryId, destinationUID, wallet); + setStatus('done'); + Alert.alert('Success', 'Delivery confirmed! Payment released.'); + } catch (error) { + setStatus('idle'); + Alert.alert('Error', error.message); + } + }, + (error) => { + setStatus('idle'); + Alert.alert('Location Error', 'Could not get your location'); + }, + { enableHighAccuracy: true } + ); + }; + + return ( +