> ## Documentation Index
> Fetch the complete documentation index at: https://docs.astral.global/llms.txt
> Use this file to discover all available pages before exploring further.

# Building Verification Plugins

> Connect a new proof-of-location system to Astral

<Note>**Research Preview** — APIs may change. [GitHub](https://github.com/AstralProtocol)</Note>

# 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.

<Info>
  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.
</Info>

## 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 theme={null}
type Runtime = 'react-native' | 'node' | 'browser';

interface LocationProofPlugin {
  name: string;
  version: string;
  runtimes: Runtime[];
  requiredCapabilities: string[];
  description: string;

  // Each method is optional — a plugin implements the stages it supports
  collect?(options?: CollectOptions): Promise<RawSignals>;
  create?(signals: RawSignals): Promise<UnsignedLocationStamp>;
  sign?(stamp: UnsignedLocationStamp, signer?: StampSigner): Promise<LocationStamp>;
  verify?(stamp: LocationStamp): Promise<StampVerificationResult>;
}

interface RawSignals {
  plugin: string;
  timestamp: number;             // Unix seconds
  data: Record<string, unknown>; // plugin-specific signal data
}

interface StampVerificationResult {
  valid: boolean;
  signaturesValid: boolean;
  structureValid: boolean;
  signalsConsistent: boolean;
  details: Record<string, unknown>;
}
```

## 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 theme={null}
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<string, unknown>;
  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 theme={null}
import type {
  LocationProofPlugin,
  RawSignals,
  UnsignedLocationStamp,
  LocationStamp,
  StampVerificationResult,
} from '@decentralized-geo/astral-sdk';

const wifiPlugin: LocationProofPlugin = {
  name: 'wifi-triangulation',
  version: '0.1.0',
  runtimes: ['node', 'browser'],
  requiredCapabilities: ['wifi-scan'],
  description: 'Wi-Fi triangulation via nearby access points',

  // `collect` returns a single RawSignals object: { plugin, timestamp, data }
  async collect(): Promise<RawSignals> {
    const networks = await scanNearbyNetworks();

    return {
      plugin: 'wifi-triangulation',
      timestamp: Date.now() / 1000,
      data: {
        accessPoints: networks.map((network) => ({
          bssid: network.bssid,
          ssid: network.ssid,
          rssi: network.signalStrength,
          frequency: network.frequency,
        })),
      },
    };
  },

  // ... continued below
};
```

### Step 2: Create stamps from signals

```typescript theme={null}
  // `create` transforms RawSignals into an unsigned stamp
  async create(signals: RawSignals): Promise<UnsignedLocationStamp> {
    const accessPoints = signals.data.accessPoints as WifiAccessPoint[];

    // Triangulate position from Wi-Fi signals
    const position = triangulateFromAccessPoints(accessPoints);

    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: signals.timestamp, end: signals.timestamp },
      plugin: 'wifi-triangulation',
      pluginVersion: '0.1.0',
      signals: {
        accessPoints,
        triangulationMethod: 'weighted-centroid'
      }
    };
  },
```

### Step 3: Implement stamp verification

```typescript theme={null}
  // `verify` returns a StampVerificationResult:
  // { valid, signaturesValid, structureValid, signalsConsistent, details }
  async verify(stamp: LocationStamp): Promise<StampVerificationResult> {
    const signals = stamp.signals as { accessPoints?: unknown[] };
    const accessPointCount = signals.accessPoints?.length ?? 0;

    // Structure: Wi-Fi triangulation needs at least 3 access points
    const structureValid =
      stamp.plugin === 'wifi-triangulation' && accessPointCount >= 3;

    // Signal consistency: collection window should be tight (≤ 60s)
    const duration = stamp.temporalFootprint.end - stamp.temporalFootprint.start;
    const signalsConsistent = duration <= 60;

    // Signatures
    const signaturesValid = await verifyStampSignatures(stamp);

    return {
      valid: structureValid && signalsConsistent && signaturesValid,
      signaturesValid,
      structureValid,
      signalsConsistent,
      details: {
        accessPointCount,
        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 theme={null}
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 (collect returns an array)
const signals = await astral.stamps.collect({ plugins: ['wifi-triangulation'] });

const unsigned = await astral.stamps.create(
  { plugin: 'wifi-triangulation' },
  signals[0]
);
const stamp = await astral.stamps.sign({ plugin: 'wifi-triangulation' }, unsigned, signer);
```

## Testing

Test each plugin responsibility independently:

```typescript theme={null}
import { describe, it, expect } from 'vitest';

describe('wifi-triangulation plugin', () => {
  it('collects signals from nearby networks', async () => {
    const signals = await wifiPlugin.collect();

    expect(signals.plugin).toBe('wifi-triangulation');
    expect(signals.timestamp).toBeGreaterThan(0);
    expect((signals.data.accessPoints as unknown[]).length).toBeGreaterThan(0);
  });

  it('creates a valid stamp from signals', async () => {
    const signals = mockWifiSignals(5);
    const stamp = await wifiPlugin.create(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.verify(stamp);

    expect(result.valid).toBe(false);
    expect(result.structureValid).toBe(false);
  });
});
```

## Existing plugins

ProofMode is the plugin that's working end to end today. The Verify service also includes experimental stamp-verification logic — `witnesschain`, `gpsd`, `geoclue`, `wifi-mls`, and `ip-geolocation` — with interfaces defined. We're keen to develop new ones with partners. Two are highlighted here:

<CardGroup cols={2}>
  <Card title="ProofMode" icon="mobile">
    Device attestation and sensor fusion. Uses Secure Enclave (iOS) or hardware keystore (Android) to sign location observations.
  </Card>

  <Card title="WitnessChain" icon="server">
    Infrastructure verification using UDP latency triangulation and a challenger network. Trust is intended to derive from speed-of-light constraints and cryptoeconomic incentives.
  </Card>
</CardGroup>

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

<CardGroup cols={2}>
  <Card title="Location proofs" icon="map-pin" href="/concepts/location-proofs">
    Understand how stamps combine into verifiable proofs
  </Card>

  <Card title="Verifying location proofs" icon="shield-check" href="/guides/verifying-location-proofs">
    Submit proofs and understand credibility scores
  </Card>
</CardGroup>
