@hauska-sdk/vda 0.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.
- package/CHANGELOG.md +21 -0
- package/README.md +655 -0
- package/dist/VDASDK.d.ts +158 -0
- package/dist/VDASDK.d.ts.map +1 -0
- package/dist/VDASDK.js +284 -0
- package/dist/VDASDK.js.map +1 -0
- package/dist/access-pass.d.ts +96 -0
- package/dist/access-pass.d.ts.map +1 -0
- package/dist/access-pass.js +137 -0
- package/dist/access-pass.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/minting.d.ts +101 -0
- package/dist/minting.d.ts.map +1 -0
- package/dist/minting.js +104 -0
- package/dist/minting.js.map +1 -0
- package/dist/ownership-transfer.d.ts +50 -0
- package/dist/ownership-transfer.d.ts.map +1 -0
- package/dist/ownership-transfer.js +74 -0
- package/dist/ownership-transfer.js.map +1 -0
- package/dist/ownership.d.ts +72 -0
- package/dist/ownership.d.ts.map +1 -0
- package/dist/ownership.js +138 -0
- package/dist/ownership.js.map +1 -0
- package/dist/search.d.ts +59 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +79 -0
- package/dist/search.js.map +1 -0
- package/dist/types.d.ts +144 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +33 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +203 -0
- package/dist/validation.js.map +1 -0
- package/package.json +73 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog - @hauska-sdk/vda
|
|
2
|
+
|
|
3
|
+
All notable changes to the VDA SDK will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - 2025-02-03
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Initial release of CNS Protocol VDA SDK
|
|
9
|
+
- VDA (Verified Digital Asset) minting with metadata
|
|
10
|
+
- Access pass creation and management
|
|
11
|
+
- Ownership verification
|
|
12
|
+
- Ownership transfer
|
|
13
|
+
- Access pass revocation
|
|
14
|
+
- Cross-spoke search (real-estate, healthcare, architect, etc.)
|
|
15
|
+
- Universal metadata support (address, legalDesc, patientId, api14)
|
|
16
|
+
- PostgreSQL storage adapter support
|
|
17
|
+
- Automatic wallet creation integration
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
[0.1.0]: https://github.com/hauska-sdk/hauska-sdk/releases/tag/@hauska-sdk/vda@0.1.0
|
package/README.md
ADDED
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
# @hauska-sdk/vda
|
|
2
|
+
|
|
3
|
+
CNS Protocol VDA SDK - Verified Digital Assets management with cross-spoke search and access control.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @hauska-sdk/vda
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { VDASDK } from "@hauska-sdk/vda";
|
|
15
|
+
import { PostgreSQLStorageAdapter } from "@hauska-sdk/adapters-storage-postgres";
|
|
16
|
+
import { WalletManager } from "@hauska-sdk/wallet";
|
|
17
|
+
import { Pool } from "pg";
|
|
18
|
+
|
|
19
|
+
// Initialize SDK
|
|
20
|
+
const pool = new Pool({
|
|
21
|
+
connectionString: process.env.DATABASE_URL,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const sdk = new VDASDK({
|
|
25
|
+
storageAdapter: new PostgreSQLStorageAdapter({
|
|
26
|
+
pool,
|
|
27
|
+
autoMigrate: true,
|
|
28
|
+
}),
|
|
29
|
+
walletManager: new WalletManager(),
|
|
30
|
+
logHook: (level, message, data) => {
|
|
31
|
+
console.log(`[${level}] ${message}`, data);
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Mint a VDA
|
|
36
|
+
const vda = await sdk.mint({
|
|
37
|
+
assetType: "deed",
|
|
38
|
+
address: "123 Main St, Schertz, TX 78154",
|
|
39
|
+
ownerWallet: "0x...",
|
|
40
|
+
spoke: "real-estate",
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Create an access pass
|
|
44
|
+
const accessPass = await sdk.createAccessPass({
|
|
45
|
+
grantorWallet: "0x...",
|
|
46
|
+
recipientWallet: "0x...",
|
|
47
|
+
accessibleVDAs: [vda.id],
|
|
48
|
+
permissions: ["view", "download"],
|
|
49
|
+
expiry: Date.now() + 3600000, // 1 hour
|
|
50
|
+
spoke: "real-estate",
|
|
51
|
+
address: "123 Main St",
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Verify ownership
|
|
55
|
+
const verification = await sdk.verifyOwnership(vda.id, "0x...");
|
|
56
|
+
if (verification.hasAccess) {
|
|
57
|
+
// Grant access to resource
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Features
|
|
62
|
+
|
|
63
|
+
- ✅ **VDA Minting** - Create verified digital assets with universal metadata
|
|
64
|
+
- ✅ **Access Pass Management** - Create, revoke, and verify time-bound access passes
|
|
65
|
+
- ✅ **Ownership Verification** - Check direct ownership and access pass permissions
|
|
66
|
+
- ✅ **Ownership Transfer** - Transfer VDA ownership between wallets
|
|
67
|
+
- ✅ **Cross-Spoke Search** - Search VDAs across all spokes by universal metadata keys
|
|
68
|
+
- ✅ **Automatic Wallet Creation** - Seamless wallet management
|
|
69
|
+
- ✅ **Logging Hooks** - Optional monitoring and debugging support
|
|
70
|
+
|
|
71
|
+
## Universal Metadata Schema
|
|
72
|
+
|
|
73
|
+
VDAs use a universal metadata schema that enables cross-spoke search. At least one universal metadata key is required:
|
|
74
|
+
|
|
75
|
+
### Universal Metadata Keys
|
|
76
|
+
|
|
77
|
+
- **`address`** - Physical address (911 format: "123 Main St, Schertz, TX 78154")
|
|
78
|
+
- Used by: real-estate, architect, municipal spokes
|
|
79
|
+
- Enables cross-spoke property search
|
|
80
|
+
|
|
81
|
+
- **`legalDesc`** - Legal description (e.g., "Lot 5, Block 3, Schertz Heights")
|
|
82
|
+
- Used by: real-estate, architect, municipal spokes
|
|
83
|
+
- Alternative to address for property identification
|
|
84
|
+
|
|
85
|
+
- **`patientId`** - Patient identifier
|
|
86
|
+
- Used by: healthcare spoke
|
|
87
|
+
- Enables patient record aggregation across systems
|
|
88
|
+
|
|
89
|
+
- **`api14`** - API well number (format: "42-123-12345-00-00")
|
|
90
|
+
- Used by: oil-gas spoke
|
|
91
|
+
- Standard identifier for oil & gas wells
|
|
92
|
+
|
|
93
|
+
### Spoke Types
|
|
94
|
+
|
|
95
|
+
- `real-estate` - Real estate properties, deeds, titles
|
|
96
|
+
- `healthcare` - Patient records, medical documents
|
|
97
|
+
- `oil-gas` - Well logs, permits, leases
|
|
98
|
+
- `architect` - Blueprints, building plans
|
|
99
|
+
- `municipal` - Permits, licenses, city records
|
|
100
|
+
|
|
101
|
+
### Asset Types
|
|
102
|
+
|
|
103
|
+
- `deed` - Property deed
|
|
104
|
+
- `blueprint` - Architectural blueprint
|
|
105
|
+
- `permit` - Building or operational permit
|
|
106
|
+
- `contract` - Legal contract
|
|
107
|
+
- `health-record` - Medical record
|
|
108
|
+
- `well-log` - Oil & gas well log
|
|
109
|
+
- `access-pass` - Time-bound access pass
|
|
110
|
+
- `invoice` - Invoice document
|
|
111
|
+
|
|
112
|
+
## API Reference
|
|
113
|
+
|
|
114
|
+
### VDASDK
|
|
115
|
+
|
|
116
|
+
Main SDK class that provides all VDA functionality.
|
|
117
|
+
|
|
118
|
+
#### Constructor
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
new VDASDK(config: VDASDKConfig)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Configuration:**
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
interface VDASDKConfig {
|
|
128
|
+
storageAdapter: VDAStorageAdapter; // Required
|
|
129
|
+
walletManager?: WalletManager; // Optional, auto-created if not provided
|
|
130
|
+
logHook?: LogHook; // Optional logging hook
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### Methods
|
|
135
|
+
|
|
136
|
+
##### `mint(params: MintVDAParams): Promise<VDA>`
|
|
137
|
+
|
|
138
|
+
Mint a new VDA.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
const vda = await sdk.mint({
|
|
142
|
+
assetType: "deed",
|
|
143
|
+
address: "123 Main St, Schertz, TX 78154",
|
|
144
|
+
ownerWallet: "0x...", // Optional if userId/password provided
|
|
145
|
+
spoke: "real-estate",
|
|
146
|
+
// Optional: userId, password for auto-wallet creation
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
##### `getVDA(vdaId: string): Promise<VDA | null>`
|
|
151
|
+
|
|
152
|
+
Get a VDA by ID.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
const vda = await sdk.getVDA("vda-id");
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
##### `listVDAsByOwner(wallet: string): Promise<VDA[]>`
|
|
159
|
+
|
|
160
|
+
List all VDAs owned by a wallet.
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
const vdas = await sdk.listVDAsByOwner("0x...");
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
##### `createAccessPass(params: CreateAccessPassParams): Promise<VDA>`
|
|
167
|
+
|
|
168
|
+
Create an access pass VDA.
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const accessPass = await sdk.createAccessPass({
|
|
172
|
+
grantorWallet: "0x...", // Must own all accessible VDAs
|
|
173
|
+
recipientWallet: "0x...", // Optional if userId/password provided
|
|
174
|
+
accessibleVDAs: ["vda-1", "vda-2"],
|
|
175
|
+
permissions: ["view", "download"],
|
|
176
|
+
expiry: Date.now() + 3600000, // Must be in the future
|
|
177
|
+
spoke: "real-estate",
|
|
178
|
+
address: "123 Main St", // Universal metadata
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
##### `revokeAccessPass(accessPassId: string, grantorWallet: string): Promise<VDA>`
|
|
183
|
+
|
|
184
|
+
Revoke an access pass.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
const revoked = await sdk.revokeAccessPass(
|
|
188
|
+
accessPassId,
|
|
189
|
+
grantorWallet
|
|
190
|
+
);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
##### `verifyOwnership(vdaId: string, wallet: string, requiredPermissions?: string[]): Promise<OwnershipVerificationResult>`
|
|
194
|
+
|
|
195
|
+
Verify ownership of a VDA.
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
const result = await sdk.verifyOwnership(
|
|
199
|
+
vdaId,
|
|
200
|
+
wallet,
|
|
201
|
+
["view", "download"] // Optional permissions to check
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
if (result.hasAccess) {
|
|
205
|
+
console.log(`Access type: ${result.accessType}`); // "direct" or "access-pass"
|
|
206
|
+
if (result.accessPass) {
|
|
207
|
+
console.log(`Access via pass: ${result.accessPass.id}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
##### `transferOwnership(params: TransferOwnershipParams): Promise<VDA>`
|
|
213
|
+
|
|
214
|
+
Transfer ownership of a VDA.
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
const updatedVDA = await sdk.transferOwnership({
|
|
218
|
+
vdaId: "vda-id",
|
|
219
|
+
currentOwner: "0x...", // Must match current owner
|
|
220
|
+
newOwner: "0x...",
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
##### `searchByAddress(address: string, options?: SearchOptions): Promise<SearchResult>`
|
|
225
|
+
|
|
226
|
+
Search VDAs by address (supports partial matching).
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
const result = await sdk.searchByAddress("Main St", {
|
|
230
|
+
limit: 10,
|
|
231
|
+
offset: 0,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
console.log(`Found ${result.total} VDAs`);
|
|
235
|
+
console.log(`Showing ${result.results.length} results`);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
##### `searchByPatientId(patientId: string, options?: SearchOptions): Promise<SearchResult>`
|
|
239
|
+
|
|
240
|
+
Search VDAs by patient ID (exact match).
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
const result = await sdk.searchByPatientId("patient-123", {
|
|
244
|
+
limit: 20,
|
|
245
|
+
offset: 0,
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
##### `searchByAPI14(api14: string, options?: SearchOptions): Promise<SearchResult>`
|
|
250
|
+
|
|
251
|
+
Search VDAs by API14 (exact match).
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
const result = await sdk.searchByAPI14("42-123-12345-00-00");
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
##### `searchUniversal(query: string, options?: SearchOptions): Promise<SearchResult>`
|
|
258
|
+
|
|
259
|
+
Search VDAs across all spokes by any universal metadata key. Automatically detects query type.
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// Automatically detects API14 format
|
|
263
|
+
const result1 = await sdk.searchUniversal("42-123-12345-00-00");
|
|
264
|
+
|
|
265
|
+
// Tries patient ID first, falls back to address
|
|
266
|
+
const result2 = await sdk.searchUniversal("patient-123");
|
|
267
|
+
|
|
268
|
+
// Searches by address
|
|
269
|
+
const result3 = await sdk.searchUniversal("123 Main St");
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Access Pass Usage Guide
|
|
273
|
+
|
|
274
|
+
Access passes are time-bound, revocable permissions that grant access to VDAs. They are themselves VDAs with `assetType: "access-pass"`.
|
|
275
|
+
|
|
276
|
+
### Creating Access Passes
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// Create an access pass for a data room
|
|
280
|
+
const dataRoomVDA = await sdk.mint({
|
|
281
|
+
assetType: "deed",
|
|
282
|
+
address: "123 Main St",
|
|
283
|
+
ownerWallet: propertyOwnerWallet,
|
|
284
|
+
spoke: "real-estate",
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const accessPass = await sdk.createAccessPass({
|
|
288
|
+
grantorWallet: propertyOwnerWallet, // Must own dataRoomVDA
|
|
289
|
+
recipientWallet: buyerWallet,
|
|
290
|
+
accessibleVDAs: [dataRoomVDA.id],
|
|
291
|
+
permissions: ["view", "download"], // What the recipient can do
|
|
292
|
+
expiry: Date.now() + 7 * 24 * 3600000, // 7 days
|
|
293
|
+
spoke: "real-estate",
|
|
294
|
+
address: "123 Main St",
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Verifying Access
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// Check if wallet has access to a VDA
|
|
302
|
+
const verification = await sdk.verifyOwnership(
|
|
303
|
+
dataRoomVDA.id,
|
|
304
|
+
buyerWallet,
|
|
305
|
+
["view"] // Required permission
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
if (verification.hasAccess) {
|
|
309
|
+
if (verification.accessType === "direct") {
|
|
310
|
+
console.log("User owns this VDA");
|
|
311
|
+
} else if (verification.accessType === "access-pass") {
|
|
312
|
+
console.log("User has access via access pass");
|
|
313
|
+
console.log(`Access pass expires: ${verification.accessPass?.metadata.expiry}`);
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
console.log(`Access denied: ${verification.reason}`);
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Revoking Access Passes
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
// Revoke an access pass before it expires
|
|
324
|
+
const revoked = await sdk.revokeAccessPass(
|
|
325
|
+
accessPass.id,
|
|
326
|
+
propertyOwnerWallet // Must be the grantor
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
// Access is immediately revoked
|
|
330
|
+
const verification = await sdk.verifyOwnership(
|
|
331
|
+
dataRoomVDA.id,
|
|
332
|
+
buyerWallet
|
|
333
|
+
);
|
|
334
|
+
// verification.hasAccess === false
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Access Pass Permissions
|
|
338
|
+
|
|
339
|
+
- `view` - Can view the VDA and associated documents
|
|
340
|
+
- `download` - Can download documents
|
|
341
|
+
- `write` - Can modify the VDA
|
|
342
|
+
- `annotate` - Can add annotations
|
|
343
|
+
|
|
344
|
+
## Code Examples
|
|
345
|
+
|
|
346
|
+
### Real Estate: Property Listing
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
// Mint a property deed
|
|
350
|
+
const property = await sdk.mint({
|
|
351
|
+
assetType: "deed",
|
|
352
|
+
address: "123 Main St, Schertz, TX 78154",
|
|
353
|
+
legalDesc: "Lot 5, Block 3, Schertz Heights",
|
|
354
|
+
ownerWallet: ownerWallet,
|
|
355
|
+
spoke: "real-estate",
|
|
356
|
+
ipfsCid: "Qm...", // Link to property documents on IPFS
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// Create a data room access pass for potential buyers
|
|
360
|
+
const dataRoomPass = await sdk.createAccessPass({
|
|
361
|
+
grantorWallet: ownerWallet,
|
|
362
|
+
recipientWallet: buyerWallet,
|
|
363
|
+
accessibleVDAs: [property.id],
|
|
364
|
+
permissions: ["view", "download"],
|
|
365
|
+
expiry: Date.now() + 30 * 24 * 3600000, // 30 days
|
|
366
|
+
spoke: "real-estate",
|
|
367
|
+
address: "123 Main St, Schertz, TX 78154",
|
|
368
|
+
});
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Healthcare: Patient Record Management
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
// Mint a patient record
|
|
375
|
+
const patientRecord = await sdk.mint({
|
|
376
|
+
assetType: "health-record",
|
|
377
|
+
patientId: "PATIENT-12345",
|
|
378
|
+
ownerWallet: patientWallet,
|
|
379
|
+
spoke: "healthcare",
|
|
380
|
+
ipfsCid: "Qm...", // Encrypted medical records
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// Grant doctor access
|
|
384
|
+
const doctorAccess = await sdk.createAccessPass({
|
|
385
|
+
grantorWallet: patientWallet,
|
|
386
|
+
recipientWallet: doctorWallet,
|
|
387
|
+
accessibleVDAs: [patientRecord.id],
|
|
388
|
+
permissions: ["view", "annotate"],
|
|
389
|
+
expiry: Date.now() + 24 * 3600000, // 24 hours
|
|
390
|
+
spoke: "healthcare",
|
|
391
|
+
patientId: "PATIENT-12345",
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// Search all records for a patient
|
|
395
|
+
const allRecords = await sdk.searchByPatientId("PATIENT-12345");
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Oil & Gas: Well Log Management
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
// Mint a well log
|
|
402
|
+
const wellLog = await sdk.mint({
|
|
403
|
+
assetType: "well-log",
|
|
404
|
+
api14: "42-123-12345-00-00",
|
|
405
|
+
ownerWallet: operatorWallet,
|
|
406
|
+
spoke: "oil-gas",
|
|
407
|
+
ipfsCid: "Qm...", // Well log data
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// Grant regulator access
|
|
411
|
+
const regulatorAccess = await sdk.createAccessPass({
|
|
412
|
+
grantorWallet: operatorWallet,
|
|
413
|
+
recipientWallet: regulatorWallet,
|
|
414
|
+
accessibleVDAs: [wellLog.id],
|
|
415
|
+
permissions: ["view"],
|
|
416
|
+
expiry: Date.now() + 365 * 24 * 3600000, // 1 year
|
|
417
|
+
spoke: "oil-gas",
|
|
418
|
+
api14: "42-123-12345-00-00",
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Search by API14
|
|
422
|
+
const wellData = await sdk.searchByAPI14("42-123-12345-00-00");
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Cross-Spoke Search
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
// Search for all assets at a specific address across all spokes
|
|
429
|
+
const allAssets = await sdk.searchByAddress("123 Main St, Schertz, TX 78154");
|
|
430
|
+
|
|
431
|
+
// Results may include:
|
|
432
|
+
// - Real estate deeds
|
|
433
|
+
// - Architectural blueprints
|
|
434
|
+
// - Municipal permits
|
|
435
|
+
// - All from different spokes but same address
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## Express.js Integration
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
import express from "express";
|
|
442
|
+
import { VDASDK } from "@hauska-sdk/vda";
|
|
443
|
+
import { PostgreSQLStorageAdapter } from "@hauska-sdk/adapters-storage-postgres";
|
|
444
|
+
|
|
445
|
+
const app = express();
|
|
446
|
+
const sdk = new VDASDK({
|
|
447
|
+
storageAdapter: new PostgreSQLStorageAdapter({
|
|
448
|
+
pool: new Pool({ connectionString: process.env.DATABASE_URL }),
|
|
449
|
+
autoMigrate: true,
|
|
450
|
+
}),
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// Mint a VDA
|
|
454
|
+
app.post("/api/vdas", async (req, res) => {
|
|
455
|
+
try {
|
|
456
|
+
const vda = await sdk.mint({
|
|
457
|
+
assetType: req.body.assetType,
|
|
458
|
+
address: req.body.address,
|
|
459
|
+
ownerWallet: req.body.ownerWallet,
|
|
460
|
+
spoke: req.body.spoke,
|
|
461
|
+
});
|
|
462
|
+
res.json(vda);
|
|
463
|
+
} catch (error) {
|
|
464
|
+
res.status(400).json({ error: error.message });
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// Verify access before serving document
|
|
469
|
+
app.get("/api/documents/:vdaId", async (req, res) => {
|
|
470
|
+
const wallet = req.headers["x-wallet-address"] as string;
|
|
471
|
+
|
|
472
|
+
const verification = await sdk.verifyOwnership(
|
|
473
|
+
req.params.vdaId,
|
|
474
|
+
wallet,
|
|
475
|
+
["view"]
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
if (!verification.hasAccess) {
|
|
479
|
+
return res.status(403).json({ error: verification.reason });
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Fetch and serve document from IPFS
|
|
483
|
+
// ...
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// Create access pass
|
|
487
|
+
app.post("/api/access-passes", async (req, res) => {
|
|
488
|
+
try {
|
|
489
|
+
const accessPass = await sdk.createAccessPass({
|
|
490
|
+
grantorWallet: req.body.grantorWallet,
|
|
491
|
+
recipientWallet: req.body.recipientWallet,
|
|
492
|
+
accessibleVDAs: req.body.accessibleVDAs,
|
|
493
|
+
permissions: req.body.permissions,
|
|
494
|
+
expiry: req.body.expiry,
|
|
495
|
+
spoke: req.body.spoke,
|
|
496
|
+
address: req.body.address,
|
|
497
|
+
});
|
|
498
|
+
res.json(accessPass);
|
|
499
|
+
} catch (error) {
|
|
500
|
+
res.status(400).json({ error: error.message });
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
## Troubleshooting
|
|
506
|
+
|
|
507
|
+
### Common Issues
|
|
508
|
+
|
|
509
|
+
#### "Invalid VDA metadata: At least one universal metadata key is required"
|
|
510
|
+
|
|
511
|
+
**Problem:** VDA is missing required universal metadata (address, legalDesc, patientId, or api14).
|
|
512
|
+
|
|
513
|
+
**Solution:** Provide at least one universal metadata key:
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
// ❌ Missing universal metadata
|
|
517
|
+
await sdk.mint({
|
|
518
|
+
assetType: "deed",
|
|
519
|
+
ownerWallet: "0x...",
|
|
520
|
+
spoke: "real-estate",
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
// ✅ Include address or legalDesc
|
|
524
|
+
await sdk.mint({
|
|
525
|
+
assetType: "deed",
|
|
526
|
+
address: "123 Main St",
|
|
527
|
+
ownerWallet: "0x...",
|
|
528
|
+
spoke: "real-estate",
|
|
529
|
+
});
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
#### "Grantor does not own the following VDAs"
|
|
533
|
+
|
|
534
|
+
**Problem:** Trying to create an access pass for VDAs the grantor doesn't own.
|
|
535
|
+
|
|
536
|
+
**Solution:** Verify grantor owns all accessible VDAs:
|
|
537
|
+
|
|
538
|
+
```typescript
|
|
539
|
+
// Check ownership first
|
|
540
|
+
const verification = await sdk.verifyOwnership(
|
|
541
|
+
vdaId,
|
|
542
|
+
grantorWallet
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
if (verification.hasAccess && verification.accessType === "direct") {
|
|
546
|
+
// Grantor owns it, can create access pass
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
#### "Expiry must be in the future"
|
|
551
|
+
|
|
552
|
+
**Problem:** Access pass expiry is set to a past timestamp.
|
|
553
|
+
|
|
554
|
+
**Solution:** Ensure expiry is in the future:
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
// ❌ Past expiry
|
|
558
|
+
expiry: Date.now() - 1000
|
|
559
|
+
|
|
560
|
+
// ✅ Future expiry
|
|
561
|
+
expiry: Date.now() + 3600000 // 1 hour from now
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
#### "Access pass is already revoked"
|
|
565
|
+
|
|
566
|
+
**Problem:** Trying to revoke an already-revoked access pass.
|
|
567
|
+
|
|
568
|
+
**Solution:** Check revocation status before revoking:
|
|
569
|
+
|
|
570
|
+
```typescript
|
|
571
|
+
const accessPass = await sdk.getVDA(accessPassId);
|
|
572
|
+
if (accessPass?.metadata.revoked) {
|
|
573
|
+
// Already revoked
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
await sdk.revokeAccessPass(accessPassId, grantorWallet);
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
#### Search returns no results
|
|
580
|
+
|
|
581
|
+
**Problem:** Search query doesn't match stored addresses.
|
|
582
|
+
|
|
583
|
+
**Solution:**
|
|
584
|
+
- For address search, use partial matching (e.g., "Main St" instead of full address)
|
|
585
|
+
- Ensure addresses are normalized (case-insensitive)
|
|
586
|
+
- Check that VDAs were indexed correctly
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
// ✅ Partial matching works
|
|
590
|
+
const results = await sdk.searchByAddress("Main St");
|
|
591
|
+
|
|
592
|
+
// ✅ Exact match also works
|
|
593
|
+
const results = await sdk.searchByAddress("123 Main St, Schertz, TX 78154");
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
#### "VDA not found" after transfer
|
|
597
|
+
|
|
598
|
+
**Problem:** VDA ID doesn't exist or was deleted.
|
|
599
|
+
|
|
600
|
+
**Solution:** Verify VDA exists before transfer:
|
|
601
|
+
|
|
602
|
+
```typescript
|
|
603
|
+
const vda = await sdk.getVDA(vdaId);
|
|
604
|
+
if (!vda) {
|
|
605
|
+
throw new Error("VDA not found");
|
|
606
|
+
}
|
|
607
|
+
await sdk.transferOwnership({ vdaId, currentOwner, newOwner });
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
### Performance Tips
|
|
611
|
+
|
|
612
|
+
1. **Use pagination for large result sets:**
|
|
613
|
+
```typescript
|
|
614
|
+
const result = await sdk.searchByAddress("Main St", {
|
|
615
|
+
limit: 50,
|
|
616
|
+
offset: 0,
|
|
617
|
+
});
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
2. **Index frequently searched fields:** The PostgreSQL adapter automatically creates indexes on `address`, `patient_id`, `api14`, and `owner_wallet`.
|
|
621
|
+
|
|
622
|
+
3. **Batch operations:** When creating multiple VDAs, use `Promise.all()` for parallel execution:
|
|
623
|
+
```typescript
|
|
624
|
+
const vdas = await Promise.all(
|
|
625
|
+
addresses.map(addr => sdk.mint({ address: addr, ... }))
|
|
626
|
+
);
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
## Storage Adapters
|
|
630
|
+
|
|
631
|
+
### PostgreSQL (Production)
|
|
632
|
+
|
|
633
|
+
```typescript
|
|
634
|
+
import { PostgreSQLStorageAdapter } from "@hauska-sdk/adapters-storage-postgres";
|
|
635
|
+
import { Pool } from "pg";
|
|
636
|
+
|
|
637
|
+
const adapter = new PostgreSQLStorageAdapter({
|
|
638
|
+
pool: new Pool({
|
|
639
|
+
connectionString: process.env.DATABASE_URL,
|
|
640
|
+
}),
|
|
641
|
+
autoMigrate: true, // Automatically creates tables
|
|
642
|
+
});
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
### Mock (Testing)
|
|
646
|
+
|
|
647
|
+
```typescript
|
|
648
|
+
import { MockStorageAdapter } from "@hauska-sdk/adapters-storage-mock";
|
|
649
|
+
|
|
650
|
+
const adapter = new MockStorageAdapter();
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
## License
|
|
654
|
+
|
|
655
|
+
MIT
|