@framers/agentos-ext-anchor-providers 1.1.0

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 (65) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +353 -0
  3. package/dist/index.d.ts +48 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +44 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/providers/EthereumProvider.d.ts +36 -0
  8. package/dist/providers/EthereumProvider.d.ts.map +1 -0
  9. package/dist/providers/EthereumProvider.js +66 -0
  10. package/dist/providers/EthereumProvider.js.map +1 -0
  11. package/dist/providers/OpenTimestampsProvider.d.ts +28 -0
  12. package/dist/providers/OpenTimestampsProvider.d.ts.map +1 -0
  13. package/dist/providers/OpenTimestampsProvider.js +72 -0
  14. package/dist/providers/OpenTimestampsProvider.js.map +1 -0
  15. package/dist/providers/RekorProvider.d.ts +28 -0
  16. package/dist/providers/RekorProvider.d.ts.map +1 -0
  17. package/dist/providers/RekorProvider.js +68 -0
  18. package/dist/providers/RekorProvider.js.map +1 -0
  19. package/dist/providers/SolanaProvider.d.ts +62 -0
  20. package/dist/providers/SolanaProvider.d.ts.map +1 -0
  21. package/dist/providers/SolanaProvider.js +256 -0
  22. package/dist/providers/SolanaProvider.js.map +1 -0
  23. package/dist/providers/WormSnapshotProvider.d.ts +35 -0
  24. package/dist/providers/WormSnapshotProvider.d.ts.map +1 -0
  25. package/dist/providers/WormSnapshotProvider.js +64 -0
  26. package/dist/providers/WormSnapshotProvider.js.map +1 -0
  27. package/dist/register.d.ts +31 -0
  28. package/dist/register.d.ts.map +1 -0
  29. package/dist/register.js +47 -0
  30. package/dist/register.js.map +1 -0
  31. package/dist/types.d.ts +20 -0
  32. package/dist/types.d.ts.map +1 -0
  33. package/dist/types.js +15 -0
  34. package/dist/types.js.map +1 -0
  35. package/dist/utils/http-client.d.ts +23 -0
  36. package/dist/utils/http-client.d.ts.map +1 -0
  37. package/dist/utils/http-client.js +53 -0
  38. package/dist/utils/http-client.js.map +1 -0
  39. package/dist/utils/index.d.ts +4 -0
  40. package/dist/utils/index.d.ts.map +1 -0
  41. package/dist/utils/index.js +3 -0
  42. package/dist/utils/index.js.map +1 -0
  43. package/dist/utils/serialization.d.ts +16 -0
  44. package/dist/utils/serialization.d.ts.map +1 -0
  45. package/dist/utils/serialization.js +30 -0
  46. package/dist/utils/serialization.js.map +1 -0
  47. package/manifest.json +100 -0
  48. package/package.json +103 -0
  49. package/src/index.ts +59 -0
  50. package/src/providers/EthereumProvider.ts +87 -0
  51. package/src/providers/OpenTimestampsProvider.ts +86 -0
  52. package/src/providers/RekorProvider.ts +82 -0
  53. package/src/providers/SolanaProvider.ts +439 -0
  54. package/src/providers/WormSnapshotProvider.ts +84 -0
  55. package/src/register.ts +69 -0
  56. package/src/types.ts +27 -0
  57. package/src/utils/http-client.ts +75 -0
  58. package/src/utils/index.ts +3 -0
  59. package/src/utils/serialization.ts +33 -0
  60. package/test/integration.spec.ts +175 -0
  61. package/test/providers.spec.ts +163 -0
  62. package/test/register.spec.ts +69 -0
  63. package/test/types.spec.ts +55 -0
  64. package/test/utils.spec.ts +225 -0
  65. package/tsconfig.json +21 -0
@@ -0,0 +1,225 @@
1
+ /**
2
+ * @file utils.spec.ts
3
+ * @description Tests for shared utility functions: canonicalization, hashing, and HTTP retry.
4
+ */
5
+
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7
+ import { canonicalizeAnchor, hashCanonicalAnchor } from '../src/utils/serialization.js';
8
+ import { fetchWithRetry } from '../src/utils/http-client.js';
9
+ import type { AnchorRecord } from '@framers/agentos';
10
+
11
+ // =============================================================================
12
+ // Test Fixtures
13
+ // =============================================================================
14
+
15
+ function createMockAnchor(overrides?: Partial<AnchorRecord>): AnchorRecord {
16
+ return {
17
+ id: 'anchor-001',
18
+ merkleRoot: 'abc123def456',
19
+ sequenceFrom: 1,
20
+ sequenceTo: 10,
21
+ eventCount: 10,
22
+ timestamp: '2025-01-15T00:00:00.000Z',
23
+ signature: 'mock-signature-base64',
24
+ ...overrides,
25
+ };
26
+ }
27
+
28
+ // =============================================================================
29
+ // canonicalizeAnchor
30
+ // =============================================================================
31
+
32
+ describe('canonicalizeAnchor', () => {
33
+ it('should produce valid JSON with sorted keys', () => {
34
+ const anchor = createMockAnchor();
35
+ const canonical = canonicalizeAnchor(anchor);
36
+ const parsed = JSON.parse(canonical);
37
+
38
+ expect(Object.keys(parsed)).toEqual([
39
+ 'eventCount',
40
+ 'id',
41
+ 'merkleRoot',
42
+ 'sequenceFrom',
43
+ 'sequenceTo',
44
+ 'signature',
45
+ 'timestamp',
46
+ ]);
47
+ });
48
+
49
+ it('should produce deterministic output for the same anchor', () => {
50
+ const anchor = createMockAnchor();
51
+ const a = canonicalizeAnchor(anchor);
52
+ const b = canonicalizeAnchor(anchor);
53
+ expect(a).toBe(b);
54
+ });
55
+
56
+ it('should exclude externalRef and providerResults', () => {
57
+ const anchor = createMockAnchor({
58
+ externalRef: 'rekor:12345',
59
+ providerResults: [{ providerId: 'rekor', success: true }],
60
+ });
61
+ const canonical = canonicalizeAnchor(anchor);
62
+ expect(canonical).not.toContain('externalRef');
63
+ expect(canonical).not.toContain('providerResults');
64
+ });
65
+
66
+ it('should include all required fields', () => {
67
+ const anchor = createMockAnchor();
68
+ const canonical = canonicalizeAnchor(anchor);
69
+ const parsed = JSON.parse(canonical);
70
+
71
+ expect(parsed.id).toBe('anchor-001');
72
+ expect(parsed.merkleRoot).toBe('abc123def456');
73
+ expect(parsed.sequenceFrom).toBe(1);
74
+ expect(parsed.sequenceTo).toBe(10);
75
+ expect(parsed.eventCount).toBe(10);
76
+ expect(parsed.signature).toBe('mock-signature-base64');
77
+ expect(parsed.timestamp).toBe('2025-01-15T00:00:00.000Z');
78
+ });
79
+
80
+ it('should produce different output for different anchors', () => {
81
+ const a = canonicalizeAnchor(createMockAnchor({ id: 'anchor-A' }));
82
+ const b = canonicalizeAnchor(createMockAnchor({ id: 'anchor-B' }));
83
+ expect(a).not.toBe(b);
84
+ });
85
+ });
86
+
87
+ // =============================================================================
88
+ // hashCanonicalAnchor
89
+ // =============================================================================
90
+
91
+ describe('hashCanonicalAnchor', () => {
92
+ it('should produce a 64-character hex string (SHA-256)', async () => {
93
+ const anchor = createMockAnchor();
94
+ const hash = await hashCanonicalAnchor(anchor);
95
+ expect(hash).toMatch(/^[0-9a-f]{64}$/);
96
+ });
97
+
98
+ it('should produce deterministic hashes', async () => {
99
+ const anchor = createMockAnchor();
100
+ const a = await hashCanonicalAnchor(anchor);
101
+ const b = await hashCanonicalAnchor(anchor);
102
+ expect(a).toBe(b);
103
+ });
104
+
105
+ it('should produce different hashes for different anchors', async () => {
106
+ const a = await hashCanonicalAnchor(createMockAnchor({ id: 'anchor-A' }));
107
+ const b = await hashCanonicalAnchor(createMockAnchor({ id: 'anchor-B' }));
108
+ expect(a).not.toBe(b);
109
+ });
110
+
111
+ it('should be sensitive to field changes', async () => {
112
+ const base = createMockAnchor();
113
+ const modified = createMockAnchor({ merkleRoot: 'different-root' });
114
+ const a = await hashCanonicalAnchor(base);
115
+ const b = await hashCanonicalAnchor(modified);
116
+ expect(a).not.toBe(b);
117
+ });
118
+ });
119
+
120
+ // =============================================================================
121
+ // fetchWithRetry
122
+ // =============================================================================
123
+
124
+ describe('fetchWithRetry', () => {
125
+ let fetchMock: ReturnType<typeof vi.fn>;
126
+
127
+ beforeEach(() => {
128
+ fetchMock = vi.fn();
129
+ vi.stubGlobal('fetch', fetchMock);
130
+ });
131
+
132
+ afterEach(() => {
133
+ vi.unstubAllGlobals();
134
+ });
135
+
136
+ it('should return response on successful first attempt', async () => {
137
+ const mockResponse = new Response('ok', { status: 200 });
138
+ fetchMock.mockResolvedValue(mockResponse);
139
+
140
+ const response = await fetchWithRetry('https://example.com/api', {}, {
141
+ retries: 3,
142
+ retryDelayMs: 10,
143
+ });
144
+
145
+ expect(response.status).toBe(200);
146
+ expect(fetchMock).toHaveBeenCalledTimes(1);
147
+ });
148
+
149
+ it('should return 4xx errors without retrying', async () => {
150
+ const mockResponse = new Response('not found', { status: 404 });
151
+ fetchMock.mockResolvedValue(mockResponse);
152
+
153
+ const response = await fetchWithRetry('https://example.com/api', {}, {
154
+ retries: 3,
155
+ retryDelayMs: 10,
156
+ });
157
+
158
+ expect(response.status).toBe(404);
159
+ expect(fetchMock).toHaveBeenCalledTimes(1);
160
+ });
161
+
162
+ it('should retry on 5xx errors', async () => {
163
+ const fail = new Response('error', { status: 500, statusText: 'Internal Server Error' });
164
+ const success = new Response('ok', { status: 200 });
165
+
166
+ fetchMock
167
+ .mockResolvedValueOnce(fail)
168
+ .mockResolvedValueOnce(fail)
169
+ .mockResolvedValueOnce(success);
170
+
171
+ const response = await fetchWithRetry('https://example.com/api', {}, {
172
+ retries: 3,
173
+ retryDelayMs: 10,
174
+ });
175
+
176
+ expect(response.status).toBe(200);
177
+ expect(fetchMock).toHaveBeenCalledTimes(3);
178
+ });
179
+
180
+ it('should retry on network errors', async () => {
181
+ const success = new Response('ok', { status: 200 });
182
+
183
+ fetchMock
184
+ .mockRejectedValueOnce(new Error('Network error'))
185
+ .mockResolvedValueOnce(success);
186
+
187
+ const response = await fetchWithRetry('https://example.com/api', {}, {
188
+ retries: 3,
189
+ retryDelayMs: 10,
190
+ });
191
+
192
+ expect(response.status).toBe(200);
193
+ expect(fetchMock).toHaveBeenCalledTimes(2);
194
+ });
195
+
196
+ it('should throw after exhausting all retries', async () => {
197
+ const fail = new Response('error', { status: 503, statusText: 'Service Unavailable' });
198
+ fetchMock.mockResolvedValue(fail);
199
+
200
+ await expect(
201
+ fetchWithRetry('https://example.com/api', {}, {
202
+ retries: 2,
203
+ retryDelayMs: 10,
204
+ }),
205
+ ).rejects.toThrow('HTTP 503');
206
+ });
207
+
208
+ it('should use default values when options not provided', async () => {
209
+ const mockResponse = new Response('ok', { status: 200 });
210
+ fetchMock.mockResolvedValue(mockResponse);
211
+
212
+ const response = await fetchWithRetry('https://example.com/api');
213
+ expect(response.status).toBe(200);
214
+ });
215
+
216
+ it('should pass AbortSignal for timeout', async () => {
217
+ const mockResponse = new Response('ok', { status: 200 });
218
+ fetchMock.mockResolvedValue(mockResponse);
219
+
220
+ await fetchWithRetry('https://example.com/api', {}, { timeoutMs: 5000, retries: 0 });
221
+
222
+ const callInit = fetchMock.mock.calls[0][1];
223
+ expect(callInit.signal).toBeDefined();
224
+ });
225
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "lib": ["ES2022"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "declarationMap": true,
14
+ "sourceMap": true,
15
+ "resolveJsonModule": true,
16
+ "moduleResolution": "Bundler",
17
+ "types": ["node"]
18
+ },
19
+ "include": ["src/**/*"],
20
+ "exclude": ["node_modules", "dist", "test"]
21
+ }