@proofchain/sdk 1.2.0 → 2.0.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.
package/package.json CHANGED
@@ -1,12 +1,7 @@
1
1
  {
2
2
  "name": "@proofchain/sdk",
3
- "version": "1.2.0",
4
- "description": "TypeScript SDK for the ProofChain Attestation Service API",
5
- "repository": {
6
- "type": "git",
7
- "url": "https://github.com/clivewatts/attestify.git"
8
- },
9
- "homepage": "https://proofchain.co.za",
3
+ "version": "2.0.0",
4
+ "description": "Official JavaScript/TypeScript SDK for ProofChain - blockchain-anchored document attestation",
10
5
  "main": "dist/index.js",
11
6
  "module": "dist/index.mjs",
12
7
  "types": "dist/index.d.ts",
@@ -17,32 +12,61 @@
17
12
  "types": "./dist/index.d.ts"
18
13
  }
19
14
  },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
20
  "scripts": {
21
- "build": "tsup src/index.ts --format cjs,esm --dts",
21
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
22
22
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
23
- "test": "vitest",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest",
24
25
  "lint": "eslint src --ext .ts",
26
+ "typecheck": "tsc --noEmit",
25
27
  "prepublishOnly": "npm run build"
26
28
  },
27
29
  "keywords": [
30
+ "proofchain",
31
+ "blockchain",
28
32
  "attestation",
29
- "merkle",
30
33
  "ipfs",
31
- "web3",
32
- "blockchain"
34
+ "proof",
35
+ "verification",
36
+ "documents",
37
+ "certificates",
38
+ "web3"
33
39
  ],
34
- "author": "Clive Watts <MegaWatts Solutions>",
35
- "license": "SEE LICENSE IN LICENSE",
36
- "dependencies": {
37
- "ethers": "^6.9.0"
40
+ "author": "ProofChain <support@proofchain.co.za>",
41
+ "license": "MIT",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/proofchain/proofchain-js.git"
45
+ },
46
+ "homepage": "https://proofchain.co.za",
47
+ "bugs": {
48
+ "url": "https://github.com/proofchain/proofchain-js/issues"
49
+ },
50
+ "engines": {
51
+ "node": ">=16.0.0"
38
52
  },
39
53
  "devDependencies": {
40
54
  "@types/node": "^20.10.0",
55
+ "eslint": "^8.55.0",
41
56
  "tsup": "^8.0.1",
42
- "typescript": "^5.3.3",
43
- "vitest": "^1.1.0"
57
+ "typescript": "^5.3.0",
58
+ "vitest": "^1.0.0"
44
59
  },
45
60
  "peerDependencies": {
46
- "ethers": "^6.0.0"
61
+ "typescript": ">=4.7.0"
62
+ },
63
+ "peerDependenciesMeta": {
64
+ "typescript": {
65
+ "optional": true
66
+ }
67
+ },
68
+ "publishConfig": {
69
+ "access": "public",
70
+ "registry": "https://registry.npmjs.org/"
47
71
  }
48
72
  }
package/src/client.ts DELETED
@@ -1,312 +0,0 @@
1
- import type {
2
- AttestationClientConfig,
3
- RequestOptions,
4
- HealthResponse,
5
- ChallengeRequest,
6
- ChallengeResponse,
7
- VerifyRequest,
8
- AuthResponse,
9
- EventCreate,
10
- Event,
11
- EventsListResponse,
12
- EventQuery,
13
- MerkleTree,
14
- MerkleProof,
15
- VerifyProofRequest,
16
- VerifyProofResponse,
17
- LinkWalletRequest,
18
- Wallet,
19
- WalletStatus,
20
- Quest,
21
- QuestWithProgress,
22
- UserQuestProgress,
23
- QuestStepCompletionResult,
24
- } from './types';
25
-
26
- export class AttestationClientError extends Error {
27
- constructor(
28
- message: string,
29
- public status: number,
30
- public body?: unknown
31
- ) {
32
- super(message);
33
- this.name = 'AttestationClientError';
34
- }
35
- }
36
-
37
- export class AttestationClient {
38
- private baseUrl: string;
39
- private accessToken?: string;
40
- private timeout: number;
41
-
42
- constructor(config: AttestationClientConfig) {
43
- this.baseUrl = config.baseUrl.replace(/\/$/, '');
44
- this.accessToken = config.accessToken;
45
- this.timeout = config.timeout ?? 30000;
46
- }
47
-
48
- // ============ Internal Methods ============
49
-
50
- private async request<T>(
51
- method: string,
52
- path: string,
53
- body?: unknown,
54
- options?: RequestOptions
55
- ): Promise<T> {
56
- const url = `${this.baseUrl}${path}`;
57
- const controller = new AbortController();
58
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
59
-
60
- const headers: Record<string, string> = {
61
- 'Content-Type': 'application/json',
62
- ...options?.headers,
63
- };
64
-
65
- if (this.accessToken) {
66
- headers['Authorization'] = `Bearer ${this.accessToken}`;
67
- }
68
-
69
- try {
70
- const response = await fetch(url, {
71
- method,
72
- headers,
73
- body: body ? JSON.stringify(body) : undefined,
74
- signal: options?.signal ?? controller.signal,
75
- });
76
-
77
- clearTimeout(timeoutId);
78
-
79
- if (!response.ok) {
80
- const errorBody = await response.json().catch(() => null);
81
- throw new AttestationClientError(
82
- `Request failed: ${response.status} ${response.statusText}`,
83
- response.status,
84
- errorBody
85
- );
86
- }
87
-
88
- return response.json();
89
- } catch (error) {
90
- clearTimeout(timeoutId);
91
- if (error instanceof AttestationClientError) throw error;
92
- throw new AttestationClientError(
93
- `Request failed: ${(error as Error).message}`,
94
- 0
95
- );
96
- }
97
- }
98
-
99
- // ============ Auth Methods ============
100
-
101
- setAccessToken(token: string): void {
102
- this.accessToken = token;
103
- }
104
-
105
- clearAccessToken(): void {
106
- this.accessToken = undefined;
107
- }
108
-
109
- // ============ Health ============
110
-
111
- async health(): Promise<HealthResponse> {
112
- return this.request<HealthResponse>('GET', '/health');
113
- }
114
-
115
- // ============ Authentication ============
116
-
117
- async requestChallenge(walletAddress: string): Promise<ChallengeResponse> {
118
- return this.request<ChallengeResponse>('POST', '/auth/web3/challenge', {
119
- wallet_address: walletAddress,
120
- } as ChallengeRequest);
121
- }
122
-
123
- async verifySignature(
124
- walletAddress: string,
125
- signature: string,
126
- message: string
127
- ): Promise<AuthResponse> {
128
- const response = await this.request<AuthResponse>('POST', '/auth/web3/verify', {
129
- wallet_address: walletAddress,
130
- signature,
131
- message,
132
- } as VerifyRequest);
133
-
134
- // Auto-set the access token
135
- if (response.access_token) {
136
- this.setAccessToken(response.access_token);
137
- }
138
-
139
- return response;
140
- }
141
-
142
- // ============ Events ============
143
-
144
- async createEvent(event: EventCreate): Promise<Event> {
145
- return this.request<Event>('POST', '/events', event);
146
- }
147
-
148
- async getEvents(
149
- userId: string,
150
- options?: {
151
- limit?: number;
152
- offset?: number;
153
- eventType?: string;
154
- fetchData?: boolean;
155
- }
156
- ): Promise<EventsListResponse> {
157
- const params = new URLSearchParams();
158
- if (options?.limit) params.set('limit', options.limit.toString());
159
- if (options?.offset) params.set('offset', options.offset.toString());
160
- if (options?.eventType) params.set('event_type', options.eventType);
161
- if (options?.fetchData) params.set('fetch_data', 'true');
162
-
163
- const query = params.toString();
164
- return this.request<EventsListResponse>(
165
- 'GET',
166
- `/data/events/${userId}${query ? `?${query}` : ''}`
167
- );
168
- }
169
-
170
- async queryEvents(userId: string, query: EventQuery): Promise<EventsListResponse> {
171
- return this.request<EventsListResponse>(
172
- 'POST',
173
- `/data/events/${userId}/query`,
174
- query
175
- );
176
- }
177
-
178
- // ============ Merkle Tree ============
179
-
180
- async rebuildMerkleTree(userId: string): Promise<MerkleTree> {
181
- return this.request<MerkleTree>('POST', `/merkle/rebuild/${userId}`);
182
- }
183
-
184
- async getMerkleRoot(userId: string): Promise<MerkleTree> {
185
- return this.request<MerkleTree>('GET', `/merkle/root/${userId}`);
186
- }
187
-
188
- async getMerkleProof(userId: string, ipfsHash: string): Promise<MerkleProof> {
189
- return this.request<MerkleProof>('GET', `/merkle/proof/${userId}/${ipfsHash}`);
190
- }
191
-
192
- async verifyMerkleProof(
193
- leaf: string,
194
- proof: string[],
195
- root: string
196
- ): Promise<VerifyProofResponse> {
197
- return this.request<VerifyProofResponse>('POST', '/merkle/verify', {
198
- leaf,
199
- proof,
200
- root,
201
- } as VerifyProofRequest);
202
- }
203
-
204
- // ============ Wallets ============
205
-
206
- async linkWallet(request: LinkWalletRequest): Promise<Wallet> {
207
- return this.request<Wallet>('POST', '/wallet/link', request);
208
- }
209
-
210
- async getWalletStatus(userId: string): Promise<WalletStatus> {
211
- return this.request<WalletStatus>('GET', `/wallet/status/${userId}`);
212
- }
213
-
214
- // ============ Quests ============
215
-
216
- /**
217
- * List available quests for a user
218
- * @param userId - End user external ID
219
- * @param options - Filter options
220
- */
221
- async getAvailableQuests(
222
- userId: string,
223
- options?: { category?: string; limit?: number }
224
- ): Promise<QuestWithProgress[]> {
225
- const params = new URLSearchParams();
226
- params.set('user_id', userId);
227
- if (options?.category) params.set('category', options.category);
228
- if (options?.limit) params.set('limit', options.limit.toString());
229
-
230
- return this.request<QuestWithProgress[]>(
231
- 'GET',
232
- `/quests/available?${params.toString()}`
233
- );
234
- }
235
-
236
- /**
237
- * Get a specific quest by ID with all steps
238
- * @param questId - Quest ID
239
- */
240
- async getQuest(questId: string): Promise<Quest> {
241
- return this.request<Quest>('GET', `/quests/${questId}`);
242
- }
243
-
244
- /**
245
- * Start a quest for a user
246
- * @param questId - Quest ID
247
- * @param userId - End user external ID
248
- */
249
- async startQuest(questId: string, userId: string): Promise<UserQuestProgress> {
250
- return this.request<UserQuestProgress>(
251
- 'POST',
252
- `/quests/${questId}/start?user_id=${encodeURIComponent(userId)}`,
253
- {}
254
- );
255
- }
256
-
257
- /**
258
- * Get user's progress on a specific quest
259
- * @param questId - Quest ID
260
- * @param userId - End user external ID
261
- */
262
- async getQuestProgress(questId: string, userId: string): Promise<UserQuestProgress> {
263
- return this.request<UserQuestProgress>(
264
- 'GET',
265
- `/quests/${questId}/progress/${encodeURIComponent(userId)}`
266
- );
267
- }
268
-
269
- /**
270
- * Get all quest progress for a user
271
- * @param userId - End user external ID
272
- * @param status - Optional status filter
273
- */
274
- async getUserQuestProgress(
275
- userId: string,
276
- status?: string
277
- ): Promise<UserQuestProgress[]> {
278
- const params = new URLSearchParams();
279
- if (status) params.set('status', status);
280
- const query = params.toString();
281
-
282
- return this.request<UserQuestProgress[]>(
283
- 'GET',
284
- `/quests/user/${encodeURIComponent(userId)}/progress${query ? `?${query}` : ''}`
285
- );
286
- }
287
-
288
- /**
289
- * Complete or increment a quest step
290
- * @param questId - Quest ID
291
- * @param userId - End user external ID
292
- * @param stepIndex - Step index (0-based)
293
- * @param options - Completion options
294
- */
295
- async completeQuestStep(
296
- questId: string,
297
- userId: string,
298
- stepIndex: number,
299
- options?: { count?: number; value?: string }
300
- ): Promise<QuestStepCompletionResult> {
301
- const params = new URLSearchParams();
302
- if (options?.count) params.set('count', options.count.toString());
303
- if (options?.value) params.set('value', options.value);
304
- const query = params.toString();
305
-
306
- return this.request<QuestStepCompletionResult>(
307
- 'POST',
308
- `/quests/${questId}/progress/${encodeURIComponent(userId)}/step/${stepIndex}/complete${query ? `?${query}` : ''}`,
309
- {}
310
- );
311
- }
312
- }
package/src/contract.ts DELETED
@@ -1,212 +0,0 @@
1
- import { ethers, type Signer, type Provider, type ContractTransactionResponse } from 'ethers';
2
-
3
- // ABI for AttestationRegistry contract
4
- const ATTESTATION_REGISTRY_ABI = [
5
- // Read functions
6
- 'function attestationRoots(address wallet) view returns (bytes32 merkleRoot, uint256 eventCount, uint256 lastUpdated, string latestIpfsHash)',
7
- 'function getRoot(address wallet) view returns (bytes32 root, uint256 eventCount, uint256 lastUpdated, string latestIpfsHash)',
8
- 'function getHistoryCount(address wallet) view returns (uint256 count)',
9
- 'function getHistoricalRoot(address wallet, uint256 index) view returns (bytes32 root, uint256 eventCount, uint256 timestamp)',
10
- 'function verifyAttestation(address wallet, bytes32 leaf, bytes32[] proof) view returns (bool valid)',
11
- 'function computeLeaf(string ipfsHash, string eventType, uint256 timestamp) pure returns (bytes32 leaf)',
12
- 'function getWalletByIpfsHash(string ipfsHash) view returns (address wallet)',
13
- 'function authorizedUpdaters(address) view returns (bool)',
14
- 'function owner() view returns (address)',
15
-
16
- // Write functions
17
- 'function updateRoot(address wallet, bytes32 newRoot, uint256 eventCount, string latestIpfsHash)',
18
- 'function batchUpdateRoots(address[] wallets, bytes32[] roots, uint256[] eventCounts, string[] ipfsHashes)',
19
- 'function setAuthorizedUpdater(address updater, bool authorized)',
20
-
21
- // Events
22
- 'event RootUpdated(address indexed wallet, bytes32 indexed newRoot, bytes32 indexed previousRoot, uint256 eventCount, string latestIpfsHash)',
23
- 'event AttestationVerified(address indexed wallet, bytes32 indexed leaf, string ipfsHash, bool valid)',
24
- 'event UpdaterAuthorized(address indexed updater, bool authorized)',
25
- ];
26
-
27
- export interface AttestationRoot {
28
- merkleRoot: string;
29
- eventCount: bigint;
30
- lastUpdated: bigint;
31
- latestIpfsHash: string;
32
- }
33
-
34
- export interface RootHistory {
35
- merkleRoot: string;
36
- eventCount: bigint;
37
- timestamp: bigint;
38
- }
39
-
40
- export interface RootUpdatedEvent {
41
- wallet: string;
42
- newRoot: string;
43
- previousRoot: string;
44
- eventCount: bigint;
45
- latestIpfsHash: string;
46
- }
47
-
48
- export class AttestationRegistryContract {
49
- private contract: ethers.Contract;
50
- private signer?: Signer;
51
-
52
- constructor(
53
- contractAddress: string,
54
- signerOrProvider: Signer | Provider
55
- ) {
56
- this.contract = new ethers.Contract(
57
- contractAddress,
58
- ATTESTATION_REGISTRY_ABI,
59
- signerOrProvider
60
- );
61
-
62
- if ('getAddress' in signerOrProvider) {
63
- this.signer = signerOrProvider as Signer;
64
- }
65
- }
66
-
67
- // ============ Read Functions ============
68
-
69
- async getRoot(wallet: string): Promise<AttestationRoot> {
70
- const [merkleRoot, eventCount, lastUpdated, latestIpfsHash] =
71
- await this.contract.getRoot(wallet);
72
- return {
73
- merkleRoot,
74
- eventCount,
75
- lastUpdated,
76
- latestIpfsHash,
77
- };
78
- }
79
-
80
- async getHistoryCount(wallet: string): Promise<bigint> {
81
- return this.contract.getHistoryCount(wallet);
82
- }
83
-
84
- async getHistoricalRoot(wallet: string, index: number): Promise<RootHistory> {
85
- const [merkleRoot, eventCount, timestamp] =
86
- await this.contract.getHistoricalRoot(wallet, index);
87
- return { merkleRoot, eventCount, timestamp };
88
- }
89
-
90
- async verifyAttestation(
91
- wallet: string,
92
- leaf: string,
93
- proof: string[]
94
- ): Promise<boolean> {
95
- return this.contract.verifyAttestation(wallet, leaf, proof);
96
- }
97
-
98
- async computeLeaf(
99
- ipfsHash: string,
100
- eventType: string,
101
- timestamp: number
102
- ): Promise<string> {
103
- return this.contract.computeLeaf(ipfsHash, eventType, timestamp);
104
- }
105
-
106
- async getWalletByIpfsHash(ipfsHash: string): Promise<string> {
107
- return this.contract.getWalletByIpfsHash(ipfsHash);
108
- }
109
-
110
- async isAuthorizedUpdater(address: string): Promise<boolean> {
111
- return this.contract.authorizedUpdaters(address);
112
- }
113
-
114
- async owner(): Promise<string> {
115
- return this.contract.owner();
116
- }
117
-
118
- // ============ Write Functions ============
119
-
120
- async updateRoot(
121
- wallet: string,
122
- newRoot: string,
123
- eventCount: number,
124
- latestIpfsHash: string
125
- ): Promise<ContractTransactionResponse> {
126
- if (!this.signer) {
127
- throw new Error('Signer required for write operations');
128
- }
129
- return this.contract.updateRoot(wallet, newRoot, eventCount, latestIpfsHash);
130
- }
131
-
132
- async batchUpdateRoots(
133
- wallets: string[],
134
- roots: string[],
135
- eventCounts: number[],
136
- ipfsHashes: string[]
137
- ): Promise<ContractTransactionResponse> {
138
- if (!this.signer) {
139
- throw new Error('Signer required for write operations');
140
- }
141
- return this.contract.batchUpdateRoots(wallets, roots, eventCounts, ipfsHashes);
142
- }
143
-
144
- async setAuthorizedUpdater(
145
- updater: string,
146
- authorized: boolean
147
- ): Promise<ContractTransactionResponse> {
148
- if (!this.signer) {
149
- throw new Error('Signer required for write operations');
150
- }
151
- return this.contract.setAuthorizedUpdater(updater, authorized);
152
- }
153
-
154
- // ============ Event Listeners ============
155
-
156
- onRootUpdated(
157
- callback: (event: RootUpdatedEvent) => void,
158
- filter?: { wallet?: string }
159
- ): void {
160
- const eventFilter = this.contract.filters.RootUpdated(filter?.wallet);
161
- this.contract.on(eventFilter, (wallet, newRoot, previousRoot, eventCount, latestIpfsHash) => {
162
- callback({
163
- wallet,
164
- newRoot,
165
- previousRoot,
166
- eventCount,
167
- latestIpfsHash,
168
- });
169
- });
170
- }
171
-
172
- removeAllListeners(): void {
173
- this.contract.removeAllListeners();
174
- }
175
-
176
- // ============ Utilities ============
177
-
178
- static computeLeafOffchain(
179
- ipfsHash: string,
180
- eventType: string,
181
- timestamp: number
182
- ): string {
183
- return ethers.keccak256(
184
- ethers.solidityPacked(
185
- ['string', 'string', 'uint256'],
186
- [ipfsHash, eventType, timestamp]
187
- )
188
- );
189
- }
190
-
191
- static verifyProofOffchain(
192
- leaf: string,
193
- proof: string[],
194
- root: string
195
- ): boolean {
196
- let computedHash = leaf;
197
-
198
- for (const proofElement of proof) {
199
- if (computedHash < proofElement) {
200
- computedHash = ethers.keccak256(
201
- ethers.solidityPacked(['bytes32', 'bytes32'], [computedHash, proofElement])
202
- );
203
- } else {
204
- computedHash = ethers.keccak256(
205
- ethers.solidityPacked(['bytes32', 'bytes32'], [proofElement, computedHash])
206
- );
207
- }
208
- }
209
-
210
- return computedHash === root;
211
- }
212
- }
package/src/index.ts DELETED
@@ -1,65 +0,0 @@
1
- /**
2
- * Copyright (c) 2024-2025 Clive Watts / MegaWatts Solutions
3
- * All Rights Reserved.
4
- *
5
- * PROPRIETARY AND CONFIDENTIAL
6
- * This software is the exclusive property of MegaWatts Solutions.
7
- * Unauthorized copying, distribution, or use is strictly prohibited.
8
- * See LICENSE file for full terms.
9
- */
10
-
11
- // Client
12
- export { AttestationClient, AttestationClientError } from './client';
13
-
14
- // Contract
15
- export {
16
- AttestationRegistryContract,
17
- type AttestationRoot,
18
- type RootHistory,
19
- type RootUpdatedEvent,
20
- } from './contract';
21
-
22
- // Types
23
- export type {
24
- // Config
25
- AttestationClientConfig,
26
- RequestOptions,
27
-
28
- // Requests
29
- ChallengeRequest,
30
- VerifyRequest,
31
- EventCreate,
32
- EventQuery,
33
- VerifyProofRequest,
34
- LinkWalletRequest,
35
-
36
- // Responses
37
- HealthResponse,
38
- ChallengeResponse,
39
- AuthResponse,
40
- Event,
41
- EventsListResponse,
42
- MerkleTree,
43
- MerkleProof,
44
- VerifyProofResponse,
45
- Wallet,
46
- WalletStatus,
47
-
48
- // Quests
49
- Quest,
50
- QuestStep,
51
- QuestWithProgress,
52
- UserQuestProgress,
53
- QuestStepCompletionResult,
54
- RewardInfo,
55
- } from './types';
56
-
57
- // Convenience factory
58
- import { AttestationClient as Client } from './client';
59
-
60
- export function createClient(baseUrl: string, accessToken?: string): Client {
61
- return new Client({
62
- baseUrl,
63
- accessToken,
64
- });
65
- }