@framers/agentos-ext-anchor-providers 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/LICENSE +98 -0
  2. package/README.md +38 -16
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/providers/EthereumProvider.d.ts +48 -4
  7. package/dist/providers/EthereumProvider.d.ts.map +1 -1
  8. package/dist/providers/EthereumProvider.js +261 -27
  9. package/dist/providers/EthereumProvider.js.map +1 -1
  10. package/dist/providers/OpenTimestampsProvider.d.ts +26 -2
  11. package/dist/providers/OpenTimestampsProvider.d.ts.map +1 -1
  12. package/dist/providers/OpenTimestampsProvider.js +141 -26
  13. package/dist/providers/OpenTimestampsProvider.js.map +1 -1
  14. package/dist/providers/RekorProvider.d.ts +32 -3
  15. package/dist/providers/RekorProvider.d.ts.map +1 -1
  16. package/dist/providers/RekorProvider.js +212 -25
  17. package/dist/providers/RekorProvider.js.map +1 -1
  18. package/dist/providers/SolanaProvider.d.ts +58 -4
  19. package/dist/providers/SolanaProvider.d.ts.map +1 -1
  20. package/dist/providers/SolanaProvider.js +310 -25
  21. package/dist/providers/SolanaProvider.js.map +1 -1
  22. package/dist/providers/WormSnapshotProvider.d.ts +30 -2
  23. package/dist/providers/WormSnapshotProvider.d.ts.map +1 -1
  24. package/dist/providers/WormSnapshotProvider.js +176 -27
  25. package/dist/providers/WormSnapshotProvider.js.map +1 -1
  26. package/dist/register.d.ts.map +1 -1
  27. package/dist/register.js +1 -0
  28. package/dist/register.js.map +1 -1
  29. package/dist/types.d.ts.map +1 -1
  30. package/dist/types.js +1 -0
  31. package/dist/types.js.map +1 -1
  32. package/dist/utils/http-client.d.ts.map +1 -1
  33. package/dist/utils/http-client.js +1 -0
  34. package/dist/utils/http-client.js.map +1 -1
  35. package/dist/utils/index.d.ts.map +1 -1
  36. package/dist/utils/index.js +1 -0
  37. package/dist/utils/index.js.map +1 -1
  38. package/dist/utils/serialization.d.ts.map +1 -1
  39. package/dist/utils/serialization.js +1 -0
  40. package/dist/utils/serialization.js.map +1 -1
  41. package/manifest.json +5 -5
  42. package/package.json +26 -21
  43. package/CHANGELOG.md +0 -16
  44. package/src/index.ts +0 -59
  45. package/src/providers/EthereumProvider.ts +0 -87
  46. package/src/providers/OpenTimestampsProvider.ts +0 -86
  47. package/src/providers/RekorProvider.ts +0 -82
  48. package/src/providers/SolanaProvider.ts +0 -439
  49. package/src/providers/WormSnapshotProvider.ts +0 -84
  50. package/src/register.ts +0 -69
  51. package/src/types.ts +0 -27
  52. package/src/utils/http-client.ts +0 -75
  53. package/src/utils/index.ts +0 -3
  54. package/src/utils/serialization.ts +0 -33
  55. package/test/integration.spec.ts +0 -175
  56. package/test/providers.spec.ts +0 -163
  57. package/test/register.spec.ts +0 -69
  58. package/test/types.spec.ts +0 -55
  59. package/test/utils.spec.ts +0 -225
  60. package/tsconfig.json +0 -21
package/src/register.ts DELETED
@@ -1,69 +0,0 @@
1
- /**
2
- * @file register.ts
3
- * @description Registers all anchor provider factories with the core AgentOS factory.
4
- * Call `registerExtensionProviders()` once at startup to enable config-driven
5
- * provider creation via `createAnchorProvider()`.
6
- *
7
- * @module @framers/agentos-ext-anchor-providers
8
- */
9
-
10
- import { registerAnchorProviderFactory } from '@framers/agentos';
11
- import { WormSnapshotProvider } from './providers/WormSnapshotProvider.js';
12
- import type { WormSnapshotProviderConfig } from './providers/WormSnapshotProvider.js';
13
- import { RekorProvider } from './providers/RekorProvider.js';
14
- import type { RekorProviderConfig } from './providers/RekorProvider.js';
15
- import { OpenTimestampsProvider } from './providers/OpenTimestampsProvider.js';
16
- import type { OpenTimestampsProviderConfig } from './providers/OpenTimestampsProvider.js';
17
- import { EthereumProvider } from './providers/EthereumProvider.js';
18
- import type { EthereumProviderConfig } from './providers/EthereumProvider.js';
19
- import { SolanaProvider } from './providers/SolanaProvider.js';
20
- import type { SolanaProviderConfig } from './providers/SolanaProvider.js';
21
-
22
- let registered = false;
23
-
24
- /**
25
- * Register all anchor provider factories with the core AgentOS provider registry.
26
- * Call once at application startup, before creating any AnchorProvider via config.
27
- *
28
- * Safe to call multiple times — subsequent calls are no-ops.
29
- *
30
- * @example
31
- * ```typescript
32
- * import { registerExtensionProviders } from '@framers/agentos-ext-anchor-providers';
33
- * import { createAnchorProvider } from '@framers/agentos';
34
- *
35
- * // Register at startup
36
- * registerExtensionProviders();
37
- *
38
- * // Now config-driven creation works:
39
- * const provider = createAnchorProvider({
40
- * type: 'rekor',
41
- * options: { serverUrl: 'https://rekor.sigstore.dev' },
42
- * });
43
- * ```
44
- */
45
- export function registerExtensionProviders(): void {
46
- if (registered) return;
47
-
48
- registerAnchorProviderFactory('worm-snapshot', (opts) =>
49
- new WormSnapshotProvider(opts as unknown as WormSnapshotProviderConfig),
50
- );
51
-
52
- registerAnchorProviderFactory('rekor', (opts) =>
53
- new RekorProvider(opts as unknown as RekorProviderConfig),
54
- );
55
-
56
- registerAnchorProviderFactory('opentimestamps', (opts) =>
57
- new OpenTimestampsProvider(opts as unknown as OpenTimestampsProviderConfig),
58
- );
59
-
60
- registerAnchorProviderFactory('ethereum', (opts) =>
61
- new EthereumProvider(opts as unknown as EthereumProviderConfig),
62
- );
63
-
64
- registerAnchorProviderFactory('solana', (opts) =>
65
- new SolanaProvider(opts as unknown as SolanaProviderConfig),
66
- );
67
-
68
- registered = true;
69
- }
package/src/types.ts DELETED
@@ -1,27 +0,0 @@
1
- /**
2
- * @file types.ts
3
- * @description Shared types for anchor provider implementations.
4
- */
5
-
6
- /**
7
- * Base configuration shared by all anchor providers.
8
- */
9
- export interface BaseProviderConfig {
10
- /** Request timeout in milliseconds. Default: 30000. */
11
- timeoutMs?: number;
12
- /** Number of retries on transient failures. Default: 3. */
13
- retries?: number;
14
- /** Base delay between retries in milliseconds. Default: 1000. */
15
- retryDelayMs?: number;
16
- }
17
-
18
- /**
19
- * Resolve a base config with defaults.
20
- */
21
- export function resolveBaseConfig(config?: Partial<BaseProviderConfig>): Required<BaseProviderConfig> {
22
- return {
23
- timeoutMs: config?.timeoutMs ?? 30_000,
24
- retries: config?.retries ?? 3,
25
- retryDelayMs: config?.retryDelayMs ?? 1_000,
26
- };
27
- }
@@ -1,75 +0,0 @@
1
- /**
2
- * @file http-client.ts
3
- * @description Shared HTTP fetch wrapper with retry and timeout for anchor providers.
4
- */
5
-
6
- export interface HttpRequestOptions {
7
- /** Request timeout in milliseconds. */
8
- timeoutMs?: number;
9
- /** Number of retries on transient failures (5xx, network errors). */
10
- retries?: number;
11
- /** Base delay between retries in milliseconds (exponential backoff). */
12
- retryDelayMs?: number;
13
- }
14
-
15
- const DEFAULT_TIMEOUT_MS = 30_000;
16
- const DEFAULT_RETRIES = 3;
17
- const DEFAULT_RETRY_DELAY_MS = 1_000;
18
-
19
- /**
20
- * Fetch with exponential backoff retry on transient failures.
21
- *
22
- * @param url - The URL to fetch.
23
- * @param init - Standard RequestInit options.
24
- * @param options - Retry and timeout configuration.
25
- * @returns The fetch Response.
26
- * @throws On non-transient errors or after all retries exhausted.
27
- */
28
- export async function fetchWithRetry(
29
- url: string,
30
- init: RequestInit = {},
31
- options?: HttpRequestOptions,
32
- ): Promise<Response> {
33
- const maxRetries = options?.retries ?? DEFAULT_RETRIES;
34
- const baseDelay = options?.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
35
- const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
36
-
37
- let lastError: Error | undefined;
38
-
39
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
40
- try {
41
- const controller = new AbortController();
42
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
43
-
44
- const response = await fetch(url, {
45
- ...init,
46
- signal: controller.signal,
47
- });
48
-
49
- clearTimeout(timeout);
50
-
51
- // Don't retry client errors (4xx), only server errors (5xx)
52
- if (response.ok || (response.status >= 400 && response.status < 500)) {
53
- return response;
54
- }
55
-
56
- // Server error — retry
57
- lastError = new Error(`HTTP ${response.status}: ${response.statusText}`);
58
- } catch (err) {
59
- lastError = err instanceof Error ? err : new Error(String(err));
60
-
61
- // Don't retry aborted requests (timeout)
62
- if (lastError.name === 'AbortError') {
63
- lastError = new Error(`Request timed out after ${timeoutMs}ms`);
64
- }
65
- }
66
-
67
- // Exponential backoff before next retry
68
- if (attempt < maxRetries) {
69
- const delay = baseDelay * Math.pow(2, attempt);
70
- await new Promise(resolve => setTimeout(resolve, delay));
71
- }
72
- }
73
-
74
- throw lastError ?? new Error('Request failed after all retries');
75
- }
@@ -1,3 +0,0 @@
1
- export { fetchWithRetry } from './http-client.js';
2
- export type { HttpRequestOptions } from './http-client.js';
3
- export { canonicalizeAnchor, hashCanonicalAnchor } from './serialization.js';
@@ -1,33 +0,0 @@
1
- /**
2
- * @file serialization.ts
3
- * @description Deterministic serialization for anchor records before external publishing.
4
- */
5
-
6
- import type { AnchorRecord } from '@framers/agentos';
7
- import { createHash } from 'crypto';
8
-
9
- /**
10
- * Serialize an AnchorRecord to a canonical JSON string with sorted keys.
11
- * Ensures all providers hash/publish the same byte representation.
12
- */
13
- export function canonicalizeAnchor(anchor: AnchorRecord): string {
14
- const canonical = {
15
- eventCount: anchor.eventCount,
16
- id: anchor.id,
17
- merkleRoot: anchor.merkleRoot,
18
- sequenceFrom: anchor.sequenceFrom,
19
- sequenceTo: anchor.sequenceTo,
20
- signature: anchor.signature,
21
- timestamp: anchor.timestamp,
22
- };
23
- return JSON.stringify(canonical);
24
- }
25
-
26
- /**
27
- * Compute a SHA-256 hex digest of the canonical anchor representation.
28
- * Used by providers as the hash to publish externally.
29
- */
30
- export async function hashCanonicalAnchor(anchor: AnchorRecord): Promise<string> {
31
- const canonical = canonicalizeAnchor(anchor);
32
- return createHash('sha256').update(canonical, 'utf8').digest('hex');
33
- }
@@ -1,175 +0,0 @@
1
- /**
2
- * @file integration.spec.ts
3
- * @description End-to-end integration tests verifying that the extension's
4
- * registerExtensionProviders() correctly hooks into the core
5
- * createAnchorProvider() factory for all provider types.
6
- *
7
- * Uses vi.resetModules() + dynamic import to get a fresh register.ts module
8
- * per test (bypassing the idempotent `registered` flag).
9
- */
10
-
11
- import { describe, it, expect, vi, beforeEach } from 'vitest';
12
- import type { AnchorRecord } from '@framers/agentos';
13
-
14
- // Use vi.hoisted() so the mock fn is available before vi.mock hoisting
15
- const { mockRegister, factories } = vi.hoisted(() => {
16
- const factories = new Map<string, (opts: any) => any>();
17
- const mockRegister = vi.fn((type: string, factory: (opts: any) => any) => {
18
- factories.set(type, factory);
19
- });
20
- return { mockRegister, factories };
21
- });
22
-
23
- vi.mock('@framers/agentos', () => ({
24
- registerAnchorProviderFactory: mockRegister,
25
- }));
26
-
27
- function createMockAnchor(): AnchorRecord {
28
- return {
29
- id: 'anchor-int-001',
30
- merkleRoot: 'abc123',
31
- sequenceFrom: 1,
32
- sequenceTo: 5,
33
- eventCount: 5,
34
- timestamp: '2025-01-15T00:00:00.000Z',
35
- signature: 'sig-base64',
36
- };
37
- }
38
-
39
- async function freshRegister(): Promise<void> {
40
- // Reset module cache so register.ts `registered` flag starts false
41
- vi.resetModules();
42
- // Re-mock since resetModules clears mocks too
43
- vi.doMock('@framers/agentos', () => ({
44
- registerAnchorProviderFactory: mockRegister,
45
- }));
46
- const mod = await import('../src/register.js');
47
- mod.registerExtensionProviders();
48
- }
49
-
50
- describe('Extension ↔ Core Integration', () => {
51
- beforeEach(async () => {
52
- mockRegister.mockClear();
53
- factories.clear();
54
- await freshRegister();
55
- });
56
-
57
- it('should register all 5 provider types with the core factory', () => {
58
- expect(factories.has('worm-snapshot')).toBe(true);
59
- expect(factories.has('rekor')).toBe(true);
60
- expect(factories.has('opentimestamps')).toBe(true);
61
- expect(factories.has('ethereum')).toBe(true);
62
- expect(factories.has('solana')).toBe(true);
63
- });
64
-
65
- // ---------------------------------------------------------------------------
66
- // Factory-created provider identity
67
- // ---------------------------------------------------------------------------
68
-
69
- it('worm-snapshot factory should create correct provider', async () => {
70
- const { WormSnapshotProvider } = await import('../src/providers/WormSnapshotProvider.js');
71
- const factory = factories.get('worm-snapshot')!;
72
- const provider = factory({ bucket: 'test-bucket', region: 'us-east-1' });
73
- expect(provider).toBeInstanceOf(WormSnapshotProvider);
74
- expect(provider.id).toBe('worm-snapshot');
75
- expect(provider.proofLevel).toBe('externally-archived');
76
- });
77
-
78
- it('rekor factory should create RekorProvider with custom server', async () => {
79
- const { RekorProvider } = await import('../src/providers/RekorProvider.js');
80
- const factory = factories.get('rekor')!;
81
- const provider = factory({ serverUrl: 'https://custom.rekor.dev' });
82
- expect(provider).toBeInstanceOf(RekorProvider);
83
- expect(provider.id).toBe('rekor');
84
- expect(provider.proofLevel).toBe('publicly-auditable');
85
- });
86
-
87
- it('opentimestamps factory should create OpenTimestampsProvider', async () => {
88
- const { OpenTimestampsProvider } = await import('../src/providers/OpenTimestampsProvider.js');
89
- const factory = factories.get('opentimestamps')!;
90
- const provider = factory({});
91
- expect(provider).toBeInstanceOf(OpenTimestampsProvider);
92
- expect(provider.id).toBe('opentimestamps');
93
- expect(provider.proofLevel).toBe('publicly-timestamped');
94
- });
95
-
96
- it('ethereum factory should create EthereumProvider with chain ID', async () => {
97
- const { EthereumProvider } = await import('../src/providers/EthereumProvider.js');
98
- const factory = factories.get('ethereum')!;
99
- const provider = factory({ rpcUrl: 'https://eth.test', chainId: 11155111 });
100
- expect(provider).toBeInstanceOf(EthereumProvider);
101
- expect(provider.id).toBe('ethereum');
102
- expect(provider.proofLevel).toBe('publicly-timestamped');
103
- });
104
-
105
- it('solana factory should create SolanaProvider', async () => {
106
- const { SolanaProvider } = await import('../src/providers/SolanaProvider.js');
107
- const factory = factories.get('solana')!;
108
- const provider = factory({ rpcUrl: 'https://solana.test', programId: '11111111111111111111111111111111' });
109
- expect(provider).toBeInstanceOf(SolanaProvider);
110
- expect(provider.id).toBe('solana');
111
- expect(provider.proofLevel).toBe('publicly-timestamped');
112
- });
113
-
114
- // ---------------------------------------------------------------------------
115
- // Factory-created providers behavior (stubs)
116
- // ---------------------------------------------------------------------------
117
-
118
- it('all factory-created providers should return failure from stub publish()', async () => {
119
- const anchor = createMockAnchor();
120
-
121
- const configs: [string, Record<string, unknown>, RegExp][] = [
122
- ['worm-snapshot', { bucket: 'b', region: 'us-east-1' }, /not yet implemented/i],
123
- ['rekor', {}, /not yet implemented/i],
124
- ['opentimestamps', {}, /not yet implemented/i],
125
- ['ethereum', { rpcUrl: 'https://eth.test' }, /not yet implemented/i],
126
- ['solana', { rpcUrl: 'https://solana.test', programId: '11111111111111111111111111111111' }, /missing signer configuration/i],
127
- ];
128
-
129
- for (const [type, opts, expectedError] of configs) {
130
- const factory = factories.get(type)!;
131
- const provider = factory(opts);
132
- const result = await provider.publish(anchor);
133
- expect(result.success).toBe(false);
134
- expect(result.error).toMatch(expectedError);
135
- expect(result.providerId).toBe(type);
136
- }
137
- });
138
-
139
- it('all factory-created providers should return false from verify()', async () => {
140
- const anchor = createMockAnchor();
141
-
142
- const configs: [string, Record<string, unknown>][] = [
143
- ['worm-snapshot', { bucket: 'b', region: 'us-east-1' }],
144
- ['rekor', {}],
145
- ['opentimestamps', {}],
146
- ['ethereum', { rpcUrl: 'https://eth.test' }],
147
- ['solana', { rpcUrl: 'https://solana.test', programId: '11111111111111111111111111111111' }],
148
- ];
149
-
150
- for (const [type, opts] of configs) {
151
- const factory = factories.get(type)!;
152
- const provider = factory(opts);
153
- const valid = await provider.verify(anchor);
154
- expect(valid).toBe(false);
155
- }
156
- });
157
-
158
- // ---------------------------------------------------------------------------
159
- // Default config values
160
- // ---------------------------------------------------------------------------
161
-
162
- it('rekor factory should use default server URL when not provided', async () => {
163
- const { RekorProvider } = await import('../src/providers/RekorProvider.js');
164
- const factory = factories.get('rekor')!;
165
- const provider = factory(undefined);
166
- expect(provider).toBeInstanceOf(RekorProvider);
167
- });
168
-
169
- it('worm-snapshot factory should apply default retention config', () => {
170
- const factory = factories.get('worm-snapshot')!;
171
- const provider = factory({ bucket: 'b', region: 'r' });
172
- expect(provider.id).toBe('worm-snapshot');
173
- expect(provider.name).toBe('WORM Snapshot (S3 Object Lock)');
174
- });
175
- });
@@ -1,163 +0,0 @@
1
- /**
2
- * @file providers.spec.ts
3
- * @description Tests for all anchor provider implementations.
4
- */
5
-
6
- import { describe, it, expect } from 'vitest';
7
- import { WormSnapshotProvider } from '../src/providers/WormSnapshotProvider.js';
8
- import { RekorProvider } from '../src/providers/RekorProvider.js';
9
- import { OpenTimestampsProvider } from '../src/providers/OpenTimestampsProvider.js';
10
- import { EthereumProvider } from '../src/providers/EthereumProvider.js';
11
- import { SolanaProvider } from '../src/providers/SolanaProvider.js';
12
- import type { AnchorRecord } from '@framers/agentos';
13
-
14
- function createMockAnchor(overrides?: Partial<AnchorRecord>): AnchorRecord {
15
- return {
16
- id: 'anchor-001',
17
- merkleRoot: 'abc123def456',
18
- sequenceFrom: 1,
19
- sequenceTo: 10,
20
- eventCount: 10,
21
- timestamp: '2025-01-15T00:00:00.000Z',
22
- signature: 'mock-signature-base64',
23
- ...overrides,
24
- };
25
- }
26
-
27
- // =============================================================================
28
- // WormSnapshotProvider
29
- // =============================================================================
30
-
31
- describe('WormSnapshotProvider', () => {
32
- it('should have correct id, name, and proofLevel', () => {
33
- const provider = new WormSnapshotProvider({ bucket: 'test', region: 'us-east-1' });
34
- expect(provider.id).toBe('worm-snapshot');
35
- expect(provider.name).toBe('WORM Snapshot (S3 Object Lock)');
36
- expect(provider.proofLevel).toBe('externally-archived');
37
- });
38
-
39
- it('should return failure from stub publish()', async () => {
40
- const provider = new WormSnapshotProvider({ bucket: 'test', region: 'us-east-1' });
41
- const result = await provider.publish(createMockAnchor());
42
- expect(result.providerId).toBe('worm-snapshot');
43
- expect(result.success).toBe(false);
44
- expect(result.error).toContain('not yet implemented');
45
- });
46
-
47
- it('should return false from stub verify()', async () => {
48
- const provider = new WormSnapshotProvider({ bucket: 'test', region: 'us-east-1' });
49
- const valid = await provider.verify(createMockAnchor({ externalRef: 's3://bucket/key' }));
50
- expect(valid).toBe(false);
51
- });
52
- });
53
-
54
- // =============================================================================
55
- // RekorProvider
56
- // =============================================================================
57
-
58
- describe('RekorProvider', () => {
59
- it('should have correct id, name, and proofLevel', () => {
60
- const provider = new RekorProvider();
61
- expect(provider.id).toBe('rekor');
62
- expect(provider.name).toBe('Sigstore Rekor Transparency Log');
63
- expect(provider.proofLevel).toBe('publicly-auditable');
64
- });
65
-
66
- it('should use default server URL when none provided', () => {
67
- const provider = new RekorProvider();
68
- expect(provider).toBeDefined();
69
- });
70
-
71
- it('should return failure from stub publish()', async () => {
72
- const provider = new RekorProvider();
73
- const result = await provider.publish(createMockAnchor());
74
- expect(result.providerId).toBe('rekor');
75
- expect(result.success).toBe(false);
76
- expect(result.error).toContain('not yet implemented');
77
- });
78
-
79
- it('should return false from stub verify() without externalRef', async () => {
80
- const provider = new RekorProvider();
81
- const valid = await provider.verify(createMockAnchor());
82
- expect(valid).toBe(false);
83
- });
84
- });
85
-
86
- // =============================================================================
87
- // OpenTimestampsProvider
88
- // =============================================================================
89
-
90
- describe('OpenTimestampsProvider', () => {
91
- it('should have correct id, name, and proofLevel', () => {
92
- const provider = new OpenTimestampsProvider();
93
- expect(provider.id).toBe('opentimestamps');
94
- expect(provider.name).toBe('OpenTimestamps (Bitcoin)');
95
- expect(provider.proofLevel).toBe('publicly-timestamped');
96
- });
97
-
98
- it('should return failure from stub publish()', async () => {
99
- const provider = new OpenTimestampsProvider();
100
- const result = await provider.publish(createMockAnchor());
101
- expect(result.providerId).toBe('opentimestamps');
102
- expect(result.success).toBe(false);
103
- expect(result.error).toContain('not yet implemented');
104
- });
105
-
106
- it('should accept custom calendar URLs', () => {
107
- const provider = new OpenTimestampsProvider({
108
- calendarUrls: ['https://custom.calendar.org'],
109
- });
110
- expect(provider).toBeDefined();
111
- });
112
- });
113
-
114
- // =============================================================================
115
- // EthereumProvider
116
- // =============================================================================
117
-
118
- describe('EthereumProvider', () => {
119
- it('should have correct id, name, and proofLevel', () => {
120
- const provider = new EthereumProvider({ rpcUrl: 'https://eth.example.com' });
121
- expect(provider.id).toBe('ethereum');
122
- expect(provider.name).toBe('Ethereum On-Chain Anchor');
123
- expect(provider.proofLevel).toBe('publicly-timestamped');
124
- });
125
-
126
- it('should return failure from stub publish()', async () => {
127
- const provider = new EthereumProvider({ rpcUrl: 'https://eth.example.com' });
128
- const result = await provider.publish(createMockAnchor());
129
- expect(result.providerId).toBe('ethereum');
130
- expect(result.success).toBe(false);
131
- expect(result.error).toContain('not yet implemented');
132
- });
133
-
134
- it('should accept chain ID and contract address', () => {
135
- const provider = new EthereumProvider({
136
- rpcUrl: 'https://eth.example.com',
137
- chainId: 11155111,
138
- contractAddress: '0x1234567890abcdef',
139
- });
140
- expect(provider).toBeDefined();
141
- });
142
- });
143
-
144
- // =============================================================================
145
- // SolanaProvider
146
- // =============================================================================
147
-
148
- describe('SolanaProvider', () => {
149
- it('should have correct id, name, and proofLevel', () => {
150
- const provider = new SolanaProvider({ rpcUrl: 'https://solana.test', programId: '11111111111111111111111111111111' });
151
- expect(provider.id).toBe('solana');
152
- expect(provider.name).toBe('Solana On-Chain Anchor (Wunderland)');
153
- expect(provider.proofLevel).toBe('publicly-timestamped');
154
- });
155
-
156
- it('should return failure from publish() when signer config is missing', async () => {
157
- const provider = new SolanaProvider({ rpcUrl: 'https://solana.test', programId: '11111111111111111111111111111111' });
158
- const result = await provider.publish(createMockAnchor());
159
- expect(result.providerId).toBe('solana');
160
- expect(result.success).toBe(false);
161
- expect(result.error).toContain('Missing signer configuration');
162
- });
163
- });
@@ -1,69 +0,0 @@
1
- /**
2
- * @file register.spec.ts
3
- * @description Tests for the extension provider registration with core AgentOS factory.
4
- */
5
-
6
- import { describe, it, expect, vi, beforeEach } from 'vitest';
7
-
8
- // Use vi.hoisted() so the mock fn is available before vi.mock hoisting
9
- const mockRegister = vi.hoisted(() => vi.fn());
10
- vi.mock('@framers/agentos', () => ({
11
- registerAnchorProviderFactory: mockRegister,
12
- }));
13
-
14
- // Import after mocking
15
- import { registerExtensionProviders } from '../src/register.js';
16
-
17
- describe('registerExtensionProviders', () => {
18
- beforeEach(() => {
19
- mockRegister.mockClear();
20
- });
21
-
22
- it('should register all 5 provider factories', async () => {
23
- // Re-import to reset the `registered` flag
24
- const mod = await import('../src/register.js');
25
-
26
- // Force reset by accessing internals — in a fresh module this is not needed
27
- // but vitest caches modules, so we call it knowing it may be idempotent
28
- mod.registerExtensionProviders();
29
-
30
- // Verify at least 4 registrations happened (first call registers all 4)
31
- const types = mockRegister.mock.calls.map((call: unknown[]) => call[0]);
32
- expect(types).toContain('worm-snapshot');
33
- expect(types).toContain('rekor');
34
- expect(types).toContain('opentimestamps');
35
- expect(types).toContain('ethereum');
36
- expect(types).toContain('solana');
37
- });
38
-
39
- it('should register factories that create correct provider instances', () => {
40
- // Find the rekor factory call
41
- const rekorCall = mockRegister.mock.calls.find((call: unknown[]) => call[0] === 'rekor');
42
- if (rekorCall) {
43
- const factory = rekorCall[1] as (opts: any) => any;
44
- const provider = factory({ serverUrl: 'https://custom.rekor.dev' });
45
- expect(provider.id).toBe('rekor');
46
- expect(provider.proofLevel).toBe('publicly-auditable');
47
- }
48
- });
49
-
50
- it('should register factories that create providers with options', () => {
51
- const ethCall = mockRegister.mock.calls.find((call: unknown[]) => call[0] === 'ethereum');
52
- if (ethCall) {
53
- const factory = ethCall[1] as (opts: any) => any;
54
- const provider = factory({ rpcUrl: 'https://eth.test', chainId: 5 });
55
- expect(provider.id).toBe('ethereum');
56
- expect(provider.proofLevel).toBe('publicly-timestamped');
57
- }
58
- });
59
-
60
- it('should register WormSnapshot factory with config', () => {
61
- const wormCall = mockRegister.mock.calls.find((call: unknown[]) => call[0] === 'worm-snapshot');
62
- if (wormCall) {
63
- const factory = wormCall[1] as (opts: any) => any;
64
- const provider = factory({ bucket: 'test-bucket', region: 'us-west-2' });
65
- expect(provider.id).toBe('worm-snapshot');
66
- expect(provider.proofLevel).toBe('externally-archived');
67
- }
68
- });
69
- });
@@ -1,55 +0,0 @@
1
- /**
2
- * @file types.spec.ts
3
- * @description Tests for shared types and config resolution.
4
- */
5
-
6
- import { describe, it, expect } from 'vitest';
7
- import { resolveBaseConfig } from '../src/types.js';
8
- import type { BaseProviderConfig } from '../src/types.js';
9
-
10
- describe('resolveBaseConfig', () => {
11
- it('should return defaults when no config provided', () => {
12
- const resolved = resolveBaseConfig();
13
- expect(resolved.timeoutMs).toBe(30_000);
14
- expect(resolved.retries).toBe(3);
15
- expect(resolved.retryDelayMs).toBe(1_000);
16
- });
17
-
18
- it('should return defaults when empty object provided', () => {
19
- const resolved = resolveBaseConfig({});
20
- expect(resolved.timeoutMs).toBe(30_000);
21
- expect(resolved.retries).toBe(3);
22
- expect(resolved.retryDelayMs).toBe(1_000);
23
- });
24
-
25
- it('should override specific fields', () => {
26
- const config: Partial<BaseProviderConfig> = {
27
- timeoutMs: 5000,
28
- retries: 1,
29
- };
30
- const resolved = resolveBaseConfig(config);
31
- expect(resolved.timeoutMs).toBe(5000);
32
- expect(resolved.retries).toBe(1);
33
- expect(resolved.retryDelayMs).toBe(1_000); // default
34
- });
35
-
36
- it('should override all fields', () => {
37
- const config: BaseProviderConfig = {
38
- timeoutMs: 10_000,
39
- retries: 5,
40
- retryDelayMs: 500,
41
- };
42
- const resolved = resolveBaseConfig(config);
43
- expect(resolved.timeoutMs).toBe(10_000);
44
- expect(resolved.retries).toBe(5);
45
- expect(resolved.retryDelayMs).toBe(500);
46
- });
47
-
48
- it('should return a Required<BaseProviderConfig>', () => {
49
- const resolved = resolveBaseConfig();
50
- // All fields should be defined (not undefined)
51
- expect(typeof resolved.timeoutMs).toBe('number');
52
- expect(typeof resolved.retries).toBe('number');
53
- expect(typeof resolved.retryDelayMs).toBe('number');
54
- });
55
- });