Skip to main content

Anchor Providers

External anchor providers for the AgentOS provenance system. Extends the built-in signed hash chain with external tamper-evidence backends — WORM storage, transparency logs, and blockchain timestamping.

Proof Levels

Each provider advertises a proof level (ascending trust):

LevelMeaningProvider
verifiableLocal signed hash chain onlyBuilt-in (NoneProvider)
externally-archivedImmutable external archive with retention policyWormSnapshotProvider
publicly-auditableAppend-only public transparency logRekorProvider
publicly-timestampedBlockchain-anchored timestamp proofOpenTimestampsProvider, EthereumProvider, SolanaProvider

Installation

pnpm add @framers/agentos-ext-anchor-providers

Install the peer dependency for your chosen provider:

# For WORM Snapshot (S3 Object Lock)
pnpm add @aws-sdk/client-s3

# For Rekor (Sigstore Transparency Log)
pnpm add sigstore

# For OpenTimestamps (Bitcoin)
pnpm add opentimestamps

# For Ethereum (On-Chain Anchor)
pnpm add ethers

# For Solana (On-Chain Anchor)
pnpm add @solana/web3.js

# Optional (only if using base58 secret keys)
pnpm add bs58

Quick Start

Option 1: Config-Driven (via Registry)

Register all providers once at startup, then use the core factory:

import { registerExtensionProviders } from '@framers/agentos-ext-anchor-providers';
import { createAnchorProvider } from '@framers/agentos';

// Register once at startup
registerExtensionProviders();

// Create provider from config
const provider = createAnchorProvider({
type: 'rekor',
options: { serverUrl: 'https://rekor.sigstore.dev' },
});

Option 2: Direct Construction

Import and instantiate providers directly:

import { RekorProvider } from '@framers/agentos-ext-anchor-providers';

const provider = new RekorProvider({
serverUrl: 'https://rekor.sigstore.dev',
timeoutMs: 15000,
});

Using with AnchorManager

import { AnchorManager, profiles } from '@framers/agentos';
import { RekorProvider } from '@framers/agentos-ext-anchor-providers';

const provider = new RekorProvider();
const config = profiles.sealedAutonomous();

const anchorManager = new AnchorManager(
storageAdapter,
ledger,
keyManager,
config,
'', // table prefix
provider, // external anchor provider
);

Providers

WORM Snapshot Provider (S3 Object Lock)

Archives anchor records to S3 with Object Lock retention. Provides compliance-grade immutability — objects cannot be deleted or overwritten during the retention period.

Proof level: externally-archived Peer dependency: @aws-sdk/client-s3

import { WormSnapshotProvider } from '@framers/agentos-ext-anchor-providers';

const provider = new WormSnapshotProvider({
bucket: 'my-provenance-bucket', // Required: S3 bucket with Object Lock enabled
region: 'us-east-1', // Required: AWS region
keyPrefix: 'provenance/anchors/', // Default: 'provenance/anchors/'
retentionDays: 365, // Default: 365
retentionMode: 'COMPLIANCE', // 'GOVERNANCE' (default) or 'COMPLIANCE'
timeoutMs: 30000, // Default: 30000
retries: 3, // Default: 3
});

Rekor Provider (Sigstore Transparency Log)

Publishes anchor hashes to Sigstore Rekor, a publicly auditable append-only transparency log. Entries are permanently recorded and anyone can verify inclusion.

Proof level: publicly-auditable Peer dependency: sigstore

import { RekorProvider } from '@framers/agentos-ext-anchor-providers';

const provider = new RekorProvider({
serverUrl: 'https://rekor.sigstore.dev', // Default: public Rekor instance
timeoutMs: 30000,
retries: 3,
});

OpenTimestamps Provider (Bitcoin)

Creates OpenTimestamps proofs anchored to the Bitcoin blockchain. Proofs are initially pending and confirm after a Bitcoin block includes the calendar commitment (typically 1-2 hours).

Proof level: publicly-timestamped Peer dependency: opentimestamps

import { OpenTimestampsProvider } from '@framers/agentos-ext-anchor-providers';

const provider = new OpenTimestampsProvider({
calendarUrls: [ // Default: public OTS calendars
'https://a.pool.opentimestamps.org',
'https://b.pool.opentimestamps.org',
'https://a.pool.eternitywall.com',
],
timeoutMs: 30000,
retries: 3,
});

Ethereum Provider (On-Chain Anchor)

Publishes anchor Merkle roots as calldata in Ethereum transactions. Provides cryptographic proof of existence at a specific block height.

Proof level: publicly-timestamped Peer dependency: ethers

import { EthereumProvider } from '@framers/agentos-ext-anchor-providers';

const provider = new EthereumProvider({
rpcUrl: 'https://eth-mainnet.alchemyapi.io/v2/YOUR_KEY', // Required
signerPrivateKey: '0x...', // Required: hex private key
chainId: 1, // Default: 1 (mainnet)
contractAddress: '0x...', // Optional: anchor storage contract
gasLimit: 100000, // Optional: gas limit override
timeoutMs: 60000,
retries: 3,
});

Composite Usage

Use CompositeAnchorProvider from @framers/agentos to publish to multiple backends simultaneously:

import { CompositeAnchorProvider } from '@framers/agentos';
import { RekorProvider, WormSnapshotProvider } from '@framers/agentos-ext-anchor-providers';

const provider = new CompositeAnchorProvider([
new WormSnapshotProvider({ bucket: 'my-bucket', region: 'us-east-1' }),
new RekorProvider(),
]);

// Publishes to both WORM and Rekor in parallel
// Proof level: publicly-auditable (highest among children)

Or via config with the composite type:

registerExtensionProviders();

const provider = createAnchorProvider({
type: 'composite',
targets: [
{ type: 'worm-snapshot', options: { bucket: 'my-bucket', region: 'us-east-1' } },
{ type: 'rekor' },
],
});

Architecture

┌──────────────────────────────────────────────────────────────────┐
│ @framers/agentos (core) │
│ │
│ AnchorManager ──→ AnchorProvider interface │
│ │ ↑ │
│ │ ┌─────┴──────┐ │
│ │ │NoneProvider│ (built-in, local-only) │
│ │ └────────────┘ │
│ │ ┌──────────────────┐ │
│ │ │CompositeProvider │ (built-in, multi-target) │
│ │ └──────────────────┘ │
│ │ │
│ createAnchorProvider() ←─── registerAnchorProviderFactory() │
└──────────────────────┬───────────────────────────────────────────┘
│ (registry pattern)
┌──────────────────────▼───────────────────────────────────────────┐
│ @framers/agentos-ext-anchor-providers │
│ │
│ registerExtensionProviders() │
│ │ │
│ ├── WormSnapshotProvider (externally-archived) │
│ ├── RekorProvider (publicly-auditable) │
│ ├── OpenTimestampsProvider(publicly-timestamped) │
│ ├── EthereumProvider (publicly-timestamped) │
│ └── SolanaProvider (publicly-timestamped) │
└──────────────────────────────────────────────────────────────────┘

Writing Custom Providers

Implement the AnchorProvider interface from @framers/agentos:

import type { AnchorProvider, AnchorRecord, AnchorProviderResult, ProofLevel } from '@framers/agentos';

export class MyCustomProvider implements AnchorProvider {
readonly id = 'my-custom';
readonly name = 'My Custom Provider';
readonly proofLevel: ProofLevel = 'externally-archived';

async publish(anchor: AnchorRecord): Promise<AnchorProviderResult> {
try {
// Your publishing logic here
const ref = await publishToMyBackend(anchor);
return { providerId: this.id, success: true, externalRef: ref };
} catch (e) {
return { providerId: this.id, success: false, error: String(e) };
}
}

async verify(anchor: AnchorRecord): Promise<boolean> {
// Verify the anchor against the external record
return verifyAgainstMyBackend(anchor);
}

async dispose(): Promise<void> {
// Cleanup resources
}
}

Register it with the core factory:

import { registerAnchorProviderFactory } from '@framers/agentos';
import { MyCustomProvider } from './MyCustomProvider';

registerAnchorProviderFactory('my-custom', (opts) => new MyCustomProvider(opts));

Utilities

fetchWithRetry(url, init, options)

HTTP fetch wrapper with exponential backoff retry. Used internally by providers that communicate over HTTP.

import { fetchWithRetry } from '@framers/agentos-ext-anchor-providers';

const response = await fetchWithRetry('https://api.example.com/anchor', {
method: 'POST',
body: JSON.stringify(data),
}, { timeoutMs: 10000, retries: 3 });

canonicalizeAnchor(anchor)

Deterministic JSON serialization of an AnchorRecord with sorted keys. Ensures all providers hash the same byte representation.

hashCanonicalAnchor(anchor)

Computes SHA-256 hex digest of the canonical anchor representation.

Implementation Status

ProviderStatusRuntime requirements
OpenTimestampsProviderImplemented (weak verify)None — uses global fetch against public OTS calendars
WormSnapshotProviderImplemented@aws-sdk/client-s3 (dynamic import) + S3 bucket with Object Lock enabled
EthereumProviderImplementedethers v6 (dynamic import) + funded signer + RPC endpoint
RekorProviderImplemented (weak verify)publicKeyPem + signArtifact callback (Ed25519 sig over raw hash bytes)
SolanaProviderImplemented@solana/web3.js (dynamic import) + funded signer

All five providers return { success: true, externalRef, metadata: {...} } on a successful publish. Failed publishes return { success: false, error, metadata } — never throw — so a composite anchor strategy can fall through cleanly.

Verification strength

ProviderVerify behaviour
OpenTimestampsProviderWeak — re-POSTs the digest to a stored calendar URL and confirms the returned bytes hash to the stored attestation hash. For full Bitcoin-block proof, parse the stored base64 with javascript-opentimestamps and run its verify() against Bitcoin.
WormSnapshotProviderStrong — HeadObjectCommand confirms the object still exists, the Object Lock retention is still active, and the stored SHA-256 metadata matches the recomputed canonical anchor hash.
EthereumProviderStrong — fetches the transaction + receipt by hash, decodes calldata (or contract input via the anchor(bytes32) ABI), and asserts equality with the recomputed canonical anchor hash.
RekorProviderWeak — GETs the entry by UUID, decodes the body, and compares the stored hash with the recomputed canonical anchor hash. The inclusion proof against the signed tree head is not verified here; wire sigstore.verify(bundle) afterwards for compliance workflows.
SolanaProviderStrong — reads the on-chain account, decodes the manifest hash + content hash, and compares both against the recomputed canonical values.

Failure detection

Failed publishes set metadata with provider-specific diagnostics (bucket / region / chainId / serverUrl etc.). For SDK-missing failures the WORM provider sets metadata.sdkMissing: true so callers composing fallback strategies can distinguish "this provider can't run on this machine" from "transient runtime error":

const result = await provider.publish(anchor);
if (!result.success) {
if (result.metadata?.sdkMissing === true) continue; // fall through
throw new Error(`Anchor publish failed: ${result.error}`);
}

Testing

# Run all tests
pnpm test

# Run with coverage
pnpm test:coverage

# Watch mode
pnpm test:watch

Test Structure

FileDescription
test/providers.spec.tsUnit tests for all 4 providers — identity, stub behavior, config
test/register.spec.tsRegistration tests — verifies factories are registered with core
test/integration.spec.tsEnd-to-end integration — factory creation, provider identity, stub behavior
test/utils.spec.tsUtility tests — canonicalization, SHA-256 hashing, HTTP retry
test/types.spec.tsConfig resolution tests — default values, overrides
PackageDescriptionLink
@framers/agentosCore AgentOS framework with AnchorProvider interface, AnchorManager, registrypackages/agentos
@framers/agentos provenance typesAnchorProvider, ProofLevel, AnchorProviderResult interfacessrc/core/provenance/types.ts
@framers/agentos factorycreateAnchorProvider(), registerAnchorProviderFactory()src/core/provenance/anchoring/providers/createAnchorProvider.ts
@framers/agentos built-in providersNoneProvider, CompositeAnchorProvidersrc/core/provenance/anchoring/providers/
@framers/agentos AnchorManagerPeriodic anchoring with external provider supportsrc/core/provenance/anchoring/AnchorManager.ts
@framers/agentos profilessealedAuditable() convenience profilesrc/core/provenance/config/PolicyProfiles.ts

License

MIT