epistery 1.3.0 → 1.3.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.
- package/client/wallet.js +253 -183
- package/client/witness.js +4 -4
- package/dist/epistery.d.ts.map +1 -1
- package/dist/epistery.js +4 -1
- package/dist/epistery.js.map +1 -1
- package/index.mjs +32 -3
- package/package.json +1 -1
- package/src/epistery.ts +15 -12
package/client/wallet.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Wallet - Base class for client wallets
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Handles wallet creation, persistence, and signing for Epistery
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -17,17 +17,17 @@ export class Wallet {
|
|
|
17
17
|
return {
|
|
18
18
|
address: this.address,
|
|
19
19
|
publicKey: this.publicKey,
|
|
20
|
-
source: this.source
|
|
20
|
+
source: this.source,
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
// Factory method to create appropriate wallet type from saved data
|
|
25
25
|
static async fromJSON(data, ethers) {
|
|
26
|
-
if (data.source ===
|
|
26
|
+
if (data.source === "web3") {
|
|
27
27
|
return await Web3Wallet.fromJSON(data, ethers);
|
|
28
|
-
} else if (data.source ===
|
|
28
|
+
} else if (data.source === "local") {
|
|
29
29
|
return await BrowserWallet.fromJSON(data, ethers);
|
|
30
|
-
} else if (data.source ===
|
|
30
|
+
} else if (data.source === "rivet") {
|
|
31
31
|
return await RivetWallet.fromJSON(data, ethers);
|
|
32
32
|
}
|
|
33
33
|
throw new Error(`Unknown wallet source: ${data.source}`);
|
|
@@ -35,11 +35,11 @@ export class Wallet {
|
|
|
35
35
|
|
|
36
36
|
// Abstract methods - must be implemented by subclasses
|
|
37
37
|
async sign(message) {
|
|
38
|
-
throw new Error(
|
|
38
|
+
throw new Error("sign() must be implemented by subclass");
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
static async create(ethers) {
|
|
42
|
-
throw new Error(
|
|
42
|
+
throw new Error("create() must be implemented by subclass");
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -47,7 +47,7 @@ export class Wallet {
|
|
|
47
47
|
export class Web3Wallet extends Wallet {
|
|
48
48
|
constructor() {
|
|
49
49
|
super();
|
|
50
|
-
this.source =
|
|
50
|
+
this.source = "web3";
|
|
51
51
|
this.signer = null;
|
|
52
52
|
this.provider = null;
|
|
53
53
|
}
|
|
@@ -64,7 +64,7 @@ export class Web3Wallet extends Wallet {
|
|
|
64
64
|
const wallet = new Web3Wallet();
|
|
65
65
|
wallet.address = data.address;
|
|
66
66
|
wallet.publicKey = data.publicKey;
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
// Attempt to reconnect to Web3 provider
|
|
69
69
|
await wallet.reconnectWeb3(ethers);
|
|
70
70
|
return wallet;
|
|
@@ -72,7 +72,7 @@ export class Web3Wallet extends Wallet {
|
|
|
72
72
|
|
|
73
73
|
static async create(ethers) {
|
|
74
74
|
const wallet = new Web3Wallet();
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
if (await wallet.connectWeb3(ethers)) {
|
|
77
77
|
return wallet;
|
|
78
78
|
}
|
|
@@ -81,22 +81,22 @@ export class Web3Wallet extends Wallet {
|
|
|
81
81
|
|
|
82
82
|
async connectWeb3(ethers) {
|
|
83
83
|
try {
|
|
84
|
-
if (typeof window !==
|
|
84
|
+
if (typeof window !== "undefined" && (window.ethereum || window.web3)) {
|
|
85
85
|
const provider = window.ethereum || window.web3.currentProvider;
|
|
86
|
-
|
|
86
|
+
|
|
87
87
|
// Request account access
|
|
88
|
-
const accounts = await provider.request({
|
|
89
|
-
method:
|
|
88
|
+
const accounts = await provider.request({
|
|
89
|
+
method: "eth_requestAccounts",
|
|
90
90
|
});
|
|
91
|
-
|
|
91
|
+
|
|
92
92
|
if (accounts && accounts.length > 0) {
|
|
93
93
|
this.address = accounts[0];
|
|
94
94
|
this.provider = new ethers.providers.Web3Provider(provider);
|
|
95
95
|
this.signer = this.provider.getSigner();
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
// Get public key from first signature
|
|
98
98
|
this.publicKey = await this.derivePublicKeyPlaceholder();
|
|
99
|
-
|
|
99
|
+
|
|
100
100
|
return true;
|
|
101
101
|
}
|
|
102
102
|
}
|
|
@@ -108,11 +108,11 @@ export class Web3Wallet extends Wallet {
|
|
|
108
108
|
|
|
109
109
|
async reconnectWeb3(ethers) {
|
|
110
110
|
try {
|
|
111
|
-
if (typeof window !==
|
|
111
|
+
if (typeof window !== "undefined" && (window.ethereum || window.web3)) {
|
|
112
112
|
const provider = window.ethereum || window.web3.currentProvider;
|
|
113
113
|
this.provider = new ethers.providers.Web3Provider(provider);
|
|
114
114
|
this.signer = this.provider.getSigner();
|
|
115
|
-
|
|
115
|
+
|
|
116
116
|
// Verify the address matches what we have stored
|
|
117
117
|
const currentAddress = await this.signer.getAddress();
|
|
118
118
|
if (currentAddress.toLowerCase() !== this.address.toLowerCase()) {
|
|
@@ -128,22 +128,26 @@ export class Web3Wallet extends Wallet {
|
|
|
128
128
|
|
|
129
129
|
async sign(message, ethers) {
|
|
130
130
|
if (!this.signer) {
|
|
131
|
-
throw new Error(
|
|
131
|
+
throw new Error("Web3 signer not available");
|
|
132
132
|
}
|
|
133
|
-
|
|
133
|
+
|
|
134
134
|
const signature = await this.signer.signMessage(message);
|
|
135
|
-
|
|
135
|
+
|
|
136
136
|
// Always update public key from signature for Web3 wallets
|
|
137
137
|
if (ethers) {
|
|
138
|
-
this.publicKey = await this.derivePublicKeyFromSignature(
|
|
138
|
+
this.publicKey = await this.derivePublicKeyFromSignature(
|
|
139
|
+
message,
|
|
140
|
+
signature,
|
|
141
|
+
ethers,
|
|
142
|
+
);
|
|
139
143
|
}
|
|
140
|
-
|
|
144
|
+
|
|
141
145
|
return signature;
|
|
142
146
|
}
|
|
143
147
|
|
|
144
148
|
async derivePublicKeyPlaceholder() {
|
|
145
149
|
// Placeholder until we get a real signature
|
|
146
|
-
return `0x04${this.address.slice(2)}${
|
|
150
|
+
return `0x04${this.address.slice(2)}${"0".repeat(64)}`;
|
|
147
151
|
}
|
|
148
152
|
|
|
149
153
|
async derivePublicKeyFromSignature(message, signature, ethers) {
|
|
@@ -151,7 +155,7 @@ export class Web3Wallet extends Wallet {
|
|
|
151
155
|
const messageHash = ethers.utils.hashMessage(message);
|
|
152
156
|
return ethers.utils.recoverPublicKey(messageHash, signature);
|
|
153
157
|
} catch (error) {
|
|
154
|
-
console.error(
|
|
158
|
+
console.error("Failed to derive public key from signature:", error);
|
|
155
159
|
return this.derivePublicKeyPlaceholder();
|
|
156
160
|
}
|
|
157
161
|
}
|
|
@@ -161,7 +165,7 @@ export class Web3Wallet extends Wallet {
|
|
|
161
165
|
export class BrowserWallet extends Wallet {
|
|
162
166
|
constructor() {
|
|
163
167
|
super();
|
|
164
|
-
this.source =
|
|
168
|
+
this.source = "local";
|
|
165
169
|
this.mnemonic = null;
|
|
166
170
|
this.privateKey = null;
|
|
167
171
|
this.signer = null;
|
|
@@ -171,7 +175,7 @@ export class BrowserWallet extends Wallet {
|
|
|
171
175
|
return {
|
|
172
176
|
...super.toJSON(),
|
|
173
177
|
mnemonic: this.mnemonic,
|
|
174
|
-
privateKey: this.privateKey
|
|
178
|
+
privateKey: this.privateKey,
|
|
175
179
|
};
|
|
176
180
|
}
|
|
177
181
|
|
|
@@ -181,33 +185,33 @@ export class BrowserWallet extends Wallet {
|
|
|
181
185
|
wallet.publicKey = data.publicKey;
|
|
182
186
|
wallet.mnemonic = data.mnemonic;
|
|
183
187
|
wallet.privateKey = data.privateKey;
|
|
184
|
-
|
|
188
|
+
|
|
185
189
|
// Recreate the signer
|
|
186
190
|
if (wallet.mnemonic) {
|
|
187
191
|
wallet.signer = ethers.Wallet.fromMnemonic(wallet.mnemonic);
|
|
188
192
|
}
|
|
189
|
-
|
|
193
|
+
|
|
190
194
|
return wallet;
|
|
191
195
|
}
|
|
192
196
|
|
|
193
197
|
static async create(ethers) {
|
|
194
198
|
const wallet = new BrowserWallet();
|
|
195
|
-
|
|
199
|
+
|
|
196
200
|
// Generate new wallet
|
|
197
201
|
const ethersWallet = ethers.Wallet.createRandom();
|
|
198
|
-
|
|
202
|
+
|
|
199
203
|
wallet.address = ethersWallet.address;
|
|
200
|
-
wallet.mnemonic = ethersWallet.mnemonic?.phrase ||
|
|
204
|
+
wallet.mnemonic = ethersWallet.mnemonic?.phrase || "";
|
|
201
205
|
wallet.publicKey = ethersWallet.publicKey;
|
|
202
206
|
wallet.privateKey = ethersWallet.privateKey;
|
|
203
207
|
wallet.signer = ethersWallet;
|
|
204
|
-
|
|
208
|
+
|
|
205
209
|
return wallet;
|
|
206
210
|
}
|
|
207
211
|
|
|
208
212
|
async sign(message) {
|
|
209
213
|
if (!this.signer) {
|
|
210
|
-
throw new Error(
|
|
214
|
+
throw new Error("Browser wallet signer not available");
|
|
211
215
|
}
|
|
212
216
|
|
|
213
217
|
return await this.signer.signMessage(message);
|
|
@@ -218,9 +222,9 @@ export class BrowserWallet extends Wallet {
|
|
|
218
222
|
export class RivetWallet extends Wallet {
|
|
219
223
|
constructor() {
|
|
220
224
|
super();
|
|
221
|
-
this.source =
|
|
225
|
+
this.source = "rivet";
|
|
222
226
|
this.keyId = null;
|
|
223
|
-
this.type =
|
|
227
|
+
this.type = "Browser"; // Browser, Contract, or Web3
|
|
224
228
|
this.label = null;
|
|
225
229
|
this.provider = null;
|
|
226
230
|
this.createdAt = null;
|
|
@@ -243,7 +247,7 @@ export class RivetWallet extends Wallet {
|
|
|
243
247
|
encryptedPrivateKey: this.encryptedPrivateKey,
|
|
244
248
|
contractAddress: this.contractAddress,
|
|
245
249
|
rivetAddress: this.rivetAddress,
|
|
246
|
-
associations: this.associations
|
|
250
|
+
associations: this.associations,
|
|
247
251
|
};
|
|
248
252
|
}
|
|
249
253
|
|
|
@@ -252,7 +256,7 @@ export class RivetWallet extends Wallet {
|
|
|
252
256
|
wallet.address = data.address;
|
|
253
257
|
wallet.publicKey = data.publicKey;
|
|
254
258
|
wallet.keyId = data.keyId;
|
|
255
|
-
wallet.type = data.type ||
|
|
259
|
+
wallet.type = data.type || "Browser";
|
|
256
260
|
wallet.label = data.label;
|
|
257
261
|
wallet.provider = data.provider;
|
|
258
262
|
wallet.createdAt = data.createdAt;
|
|
@@ -270,14 +274,17 @@ export class RivetWallet extends Wallet {
|
|
|
270
274
|
|
|
271
275
|
try {
|
|
272
276
|
// Generate unique keyId
|
|
273
|
-
wallet.keyId =
|
|
277
|
+
wallet.keyId =
|
|
278
|
+
"rivet-" + Date.now() + "-" + Math.random().toString(36).substr(2, 9);
|
|
274
279
|
wallet.createdAt = Date.now();
|
|
275
280
|
wallet.lastUpdated = Date.now();
|
|
276
|
-
wallet.label =
|
|
281
|
+
wallet.label = "Browser Wallet";
|
|
277
282
|
|
|
278
283
|
// Check if Web Crypto API is available
|
|
279
284
|
if (!window.crypto || !window.crypto.subtle) {
|
|
280
|
-
console.warn(
|
|
285
|
+
console.warn(
|
|
286
|
+
"Web Crypto API not available, falling back to extractable keys",
|
|
287
|
+
);
|
|
281
288
|
// Fallback to regular ethers wallet
|
|
282
289
|
const ethersWallet = ethers.Wallet.createRandom();
|
|
283
290
|
wallet.address = ethersWallet.address;
|
|
@@ -289,11 +296,11 @@ export class RivetWallet extends Wallet {
|
|
|
289
296
|
// Generate non-extractable AES-GCM key for encrypting the secp256k1 private key
|
|
290
297
|
const masterKey = await crypto.subtle.generateKey(
|
|
291
298
|
{
|
|
292
|
-
name:
|
|
293
|
-
length: 256
|
|
299
|
+
name: "AES-GCM",
|
|
300
|
+
length: 256,
|
|
294
301
|
},
|
|
295
302
|
false, // non-extractable!
|
|
296
|
-
[
|
|
303
|
+
["encrypt", "decrypt"],
|
|
297
304
|
);
|
|
298
305
|
|
|
299
306
|
// Store master key in IndexedDB (non-extractable CryptoKey)
|
|
@@ -310,39 +317,41 @@ export class RivetWallet extends Wallet {
|
|
|
310
317
|
|
|
311
318
|
const encryptedBuffer = await crypto.subtle.encrypt(
|
|
312
319
|
{
|
|
313
|
-
name:
|
|
314
|
-
iv: iv
|
|
320
|
+
name: "AES-GCM",
|
|
321
|
+
iv: iv,
|
|
315
322
|
},
|
|
316
323
|
masterKey,
|
|
317
|
-
privateKeyBytes
|
|
324
|
+
privateKeyBytes,
|
|
318
325
|
);
|
|
319
326
|
|
|
320
327
|
// Store encrypted private key and IV
|
|
321
328
|
wallet.encryptedPrivateKey = JSON.stringify({
|
|
322
329
|
encrypted: ethers.utils.hexlify(new Uint8Array(encryptedBuffer)),
|
|
323
|
-
iv: ethers.utils.hexlify(iv)
|
|
330
|
+
iv: ethers.utils.hexlify(iv),
|
|
324
331
|
});
|
|
325
332
|
|
|
326
333
|
return wallet;
|
|
327
334
|
} catch (error) {
|
|
328
|
-
console.error(
|
|
335
|
+
console.error("Failed to create rivet wallet:", error);
|
|
329
336
|
throw error;
|
|
330
337
|
}
|
|
331
338
|
}
|
|
332
339
|
|
|
333
340
|
/**
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
341
|
+
* Signs a message only (client-side)
|
|
342
|
+
*
|
|
343
|
+
* @param {object} message - blob of data to sign
|
|
344
|
+
* @param {ethers} ethers - ethers.js instance
|
|
345
|
+
* @returns {Promise<string>} Signed message as hex string
|
|
346
|
+
*/
|
|
340
347
|
async sign(message, ethers) {
|
|
341
348
|
try {
|
|
342
349
|
// Retrieve master key from IndexedDB
|
|
343
350
|
const masterKey = await RivetWallet.getMasterKey(this.keyId);
|
|
344
351
|
if (!masterKey) {
|
|
345
|
-
throw new Error(
|
|
352
|
+
throw new Error(
|
|
353
|
+
"Master key not found - rivet may have been created in a different browser context",
|
|
354
|
+
);
|
|
346
355
|
}
|
|
347
356
|
|
|
348
357
|
// Decrypt the private key
|
|
@@ -352,11 +361,11 @@ export class RivetWallet extends Wallet {
|
|
|
352
361
|
|
|
353
362
|
const decryptedBuffer = await crypto.subtle.decrypt(
|
|
354
363
|
{
|
|
355
|
-
name:
|
|
356
|
-
iv: ivBytes
|
|
364
|
+
name: "AES-GCM",
|
|
365
|
+
iv: ivBytes,
|
|
357
366
|
},
|
|
358
367
|
masterKey,
|
|
359
|
-
encryptedBytes
|
|
368
|
+
encryptedBytes,
|
|
360
369
|
);
|
|
361
370
|
|
|
362
371
|
const privateKey = ethers.utils.hexlify(new Uint8Array(decryptedBuffer));
|
|
@@ -367,7 +376,7 @@ export class RivetWallet extends Wallet {
|
|
|
367
376
|
|
|
368
377
|
return signature;
|
|
369
378
|
} catch (error) {
|
|
370
|
-
console.error(
|
|
379
|
+
console.error("Failed to sign message with rivet:", error);
|
|
371
380
|
throw error;
|
|
372
381
|
}
|
|
373
382
|
}
|
|
@@ -384,11 +393,13 @@ export class RivetWallet extends Wallet {
|
|
|
384
393
|
*/
|
|
385
394
|
async signTransaction(unsignedTx, ethers) {
|
|
386
395
|
try {
|
|
387
|
-
console.log(
|
|
396
|
+
console.log("RivetWallet: Signing transaction");
|
|
388
397
|
|
|
389
398
|
const masterKey = await RivetWallet.getMasterKey(this.keyId);
|
|
390
399
|
if (!masterKey) {
|
|
391
|
-
throw new Error(
|
|
400
|
+
throw new Error(
|
|
401
|
+
"Master key not found - rivet may have been created in a different browser context",
|
|
402
|
+
);
|
|
392
403
|
}
|
|
393
404
|
|
|
394
405
|
const { encrypted, iv } = JSON.parse(this.encryptedPrivateKey);
|
|
@@ -397,11 +408,11 @@ export class RivetWallet extends Wallet {
|
|
|
397
408
|
|
|
398
409
|
const decryptedBuffer = await crypto.subtle.decrypt(
|
|
399
410
|
{
|
|
400
|
-
name:
|
|
401
|
-
iv: ivBytes
|
|
411
|
+
name: "AES-GCM",
|
|
412
|
+
iv: ivBytes,
|
|
402
413
|
},
|
|
403
414
|
masterKey,
|
|
404
|
-
encryptedBytes
|
|
415
|
+
encryptedBytes,
|
|
405
416
|
);
|
|
406
417
|
|
|
407
418
|
const privateKey = ethers.utils.hexlify(new Uint8Array(decryptedBuffer));
|
|
@@ -413,16 +424,15 @@ export class RivetWallet extends Wallet {
|
|
|
413
424
|
// Validate that our address matches
|
|
414
425
|
const addressToValidate = this.rivetAddress || this.address;
|
|
415
426
|
if (signer.address.toLowerCase() !== addressToValidate.toLowerCase()) {
|
|
416
|
-
throw new Error(
|
|
427
|
+
throw new Error("Decrypted key does not match rivet address");
|
|
417
428
|
}
|
|
418
429
|
|
|
419
430
|
const signedTx = await signer.signTransaction(unsignedTx);
|
|
420
431
|
|
|
421
|
-
console.log(
|
|
432
|
+
console.log("RivetWallet: Transaction signed successfully");
|
|
422
433
|
return signedTx;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
console.error('Failed to sign transaction with rivet:', error);
|
|
434
|
+
} catch (error) {
|
|
435
|
+
console.error("Failed to sign transaction with rivet:", error);
|
|
426
436
|
throw error;
|
|
427
437
|
}
|
|
428
438
|
}
|
|
@@ -430,14 +440,14 @@ export class RivetWallet extends Wallet {
|
|
|
430
440
|
// IndexedDB operations for storing non-extractable CryptoKey
|
|
431
441
|
static async storeMasterKey(keyId, masterKey) {
|
|
432
442
|
return new Promise((resolve, reject) => {
|
|
433
|
-
const request = indexedDB.open(
|
|
443
|
+
const request = indexedDB.open("EpisteryRivets", 1);
|
|
434
444
|
|
|
435
445
|
request.onerror = () => reject(request.error);
|
|
436
446
|
|
|
437
447
|
request.onsuccess = () => {
|
|
438
448
|
const db = request.result;
|
|
439
|
-
const transaction = db.transaction([
|
|
440
|
-
const store = transaction.objectStore(
|
|
449
|
+
const transaction = db.transaction(["masterKeys"], "readwrite");
|
|
450
|
+
const store = transaction.objectStore("masterKeys");
|
|
441
451
|
|
|
442
452
|
const putRequest = store.put({ keyId, masterKey });
|
|
443
453
|
|
|
@@ -454,8 +464,8 @@ export class RivetWallet extends Wallet {
|
|
|
454
464
|
|
|
455
465
|
request.onupgradeneeded = (event) => {
|
|
456
466
|
const db = event.target.result;
|
|
457
|
-
if (!db.objectStoreNames.contains(
|
|
458
|
-
db.createObjectStore(
|
|
467
|
+
if (!db.objectStoreNames.contains("masterKeys")) {
|
|
468
|
+
db.createObjectStore("masterKeys", { keyPath: "keyId" });
|
|
459
469
|
}
|
|
460
470
|
};
|
|
461
471
|
});
|
|
@@ -463,14 +473,14 @@ export class RivetWallet extends Wallet {
|
|
|
463
473
|
|
|
464
474
|
static async getMasterKey(keyId) {
|
|
465
475
|
return new Promise((resolve, reject) => {
|
|
466
|
-
const request = indexedDB.open(
|
|
476
|
+
const request = indexedDB.open("EpisteryRivets", 1);
|
|
467
477
|
|
|
468
478
|
request.onerror = () => reject(request.error);
|
|
469
479
|
|
|
470
480
|
request.onsuccess = () => {
|
|
471
481
|
const db = request.result;
|
|
472
|
-
const transaction = db.transaction([
|
|
473
|
-
const store = transaction.objectStore(
|
|
482
|
+
const transaction = db.transaction(["masterKeys"], "readonly");
|
|
483
|
+
const store = transaction.objectStore("masterKeys");
|
|
474
484
|
|
|
475
485
|
const getRequest = store.get(keyId);
|
|
476
486
|
|
|
@@ -487,8 +497,8 @@ export class RivetWallet extends Wallet {
|
|
|
487
497
|
|
|
488
498
|
request.onupgradeneeded = (event) => {
|
|
489
499
|
const db = event.target.result;
|
|
490
|
-
if (!db.objectStoreNames.contains(
|
|
491
|
-
db.createObjectStore(
|
|
500
|
+
if (!db.objectStoreNames.contains("masterKeys")) {
|
|
501
|
+
db.createObjectStore("masterKeys", { keyPath: "keyId" });
|
|
492
502
|
}
|
|
493
503
|
};
|
|
494
504
|
});
|
|
@@ -504,24 +514,30 @@ export class RivetWallet extends Wallet {
|
|
|
504
514
|
* @param {string} domain - Domain context for the deployment
|
|
505
515
|
* @returns {Promise<string>} Contract address
|
|
506
516
|
*/
|
|
507
|
-
async deployIdentityContract(ethers, providerConfig, domain =
|
|
517
|
+
async deployIdentityContract(ethers, providerConfig, domain = "localhost") {
|
|
508
518
|
try {
|
|
509
519
|
// Get rootPath from Witness singleton
|
|
510
|
-
const rootPath =
|
|
520
|
+
const rootPath =
|
|
521
|
+
(typeof Witness !== "undefined" && Witness.instance?.rootPath) || "..";
|
|
511
522
|
|
|
512
523
|
// Step 1: Prepare unsigned deployment transaction (server funds the wallet)
|
|
513
|
-
const prepareResponse = await fetch(
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
524
|
+
const prepareResponse = await fetch(
|
|
525
|
+
`${rootPath}/epistery/identity/prepare-deploy`,
|
|
526
|
+
{
|
|
527
|
+
method: "POST",
|
|
528
|
+
headers: { "Content-Type": "application/json" },
|
|
529
|
+
body: JSON.stringify({
|
|
530
|
+
clientAddress: this.address,
|
|
531
|
+
domain: domain,
|
|
532
|
+
}),
|
|
533
|
+
},
|
|
534
|
+
);
|
|
521
535
|
|
|
522
536
|
if (!prepareResponse.ok) {
|
|
523
537
|
const error = await prepareResponse.json();
|
|
524
|
-
throw new Error(
|
|
538
|
+
throw new Error(
|
|
539
|
+
`Failed to prepare deployment: ${error.error || prepareResponse.statusText}`,
|
|
540
|
+
);
|
|
525
541
|
}
|
|
526
542
|
|
|
527
543
|
const { unsignedTransaction, metadata } = await prepareResponse.json();
|
|
@@ -530,34 +546,40 @@ export class RivetWallet extends Wallet {
|
|
|
530
546
|
const signedTx = await this.signTransaction(unsignedTransaction, ethers);
|
|
531
547
|
|
|
532
548
|
// Step 3: Submit signed transaction to blockchain
|
|
533
|
-
const submitResponse = await fetch(
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
549
|
+
const submitResponse = await fetch(
|
|
550
|
+
`${rootPath}/epistery/data/submit-signed`,
|
|
551
|
+
{
|
|
552
|
+
method: "POST",
|
|
553
|
+
headers: { "Content-Type": "application/json" },
|
|
554
|
+
body: JSON.stringify({
|
|
555
|
+
signedTransaction: signedTx,
|
|
556
|
+
operation: "deployIdentityContract",
|
|
557
|
+
metadata: metadata,
|
|
558
|
+
}),
|
|
559
|
+
},
|
|
560
|
+
);
|
|
542
561
|
|
|
543
562
|
if (!submitResponse.ok) {
|
|
544
563
|
const error = await submitResponse.json();
|
|
545
|
-
throw new Error(
|
|
564
|
+
throw new Error(
|
|
565
|
+
`Failed to submit deployment: ${error.error || submitResponse.statusText}`,
|
|
566
|
+
);
|
|
546
567
|
}
|
|
547
568
|
|
|
548
569
|
const receipt = await submitResponse.json();
|
|
549
570
|
|
|
550
571
|
if (!receipt.contractAddress) {
|
|
551
|
-
throw new Error(
|
|
572
|
+
throw new Error(
|
|
573
|
+
"Contract deployment succeeded but no contract address in receipt",
|
|
574
|
+
);
|
|
552
575
|
}
|
|
553
576
|
|
|
554
577
|
// Upgrade this rivet to use the contract
|
|
555
578
|
this.upgradeToContract(receipt.contractAddress);
|
|
556
579
|
|
|
557
580
|
return receipt.contractAddress;
|
|
558
|
-
|
|
559
581
|
} catch (error) {
|
|
560
|
-
console.error(
|
|
582
|
+
console.error("Failed to deploy IdentityContract:", error);
|
|
561
583
|
throw error;
|
|
562
584
|
}
|
|
563
585
|
}
|
|
@@ -576,7 +598,7 @@ export class RivetWallet extends Wallet {
|
|
|
576
598
|
targetRivetAddress, // Token is bound to this specific address
|
|
577
599
|
inviterRivetAddress: this.rivetAddress || this.address,
|
|
578
600
|
timestamp: Date.now(),
|
|
579
|
-
expiresAt: Date.now() + 3600000 // 1 hour
|
|
601
|
+
expiresAt: Date.now() + 3600000, // 1 hour
|
|
580
602
|
};
|
|
581
603
|
|
|
582
604
|
// Sign the payload to prove this is a legitimate invitation
|
|
@@ -585,7 +607,7 @@ export class RivetWallet extends Wallet {
|
|
|
585
607
|
|
|
586
608
|
const token = {
|
|
587
609
|
payload,
|
|
588
|
-
signature
|
|
610
|
+
signature,
|
|
589
611
|
};
|
|
590
612
|
|
|
591
613
|
// Return base64-encoded token
|
|
@@ -607,15 +629,21 @@ export class RivetWallet extends Wallet {
|
|
|
607
629
|
|
|
608
630
|
// Verify token hasn't expired
|
|
609
631
|
if (Date.now() > payload.expiresAt) {
|
|
610
|
-
throw new Error(
|
|
632
|
+
throw new Error("Join token has expired");
|
|
611
633
|
}
|
|
612
634
|
|
|
613
635
|
// Get current rivet address
|
|
614
636
|
const myRivetAddress = this.rivetAddress || this.address;
|
|
615
637
|
|
|
616
638
|
// SECURITY: Verify this token was generated for THIS rivet's address
|
|
617
|
-
if (
|
|
618
|
-
|
|
639
|
+
if (
|
|
640
|
+
payload.targetRivetAddress.toLowerCase() !==
|
|
641
|
+
myRivetAddress.toLowerCase()
|
|
642
|
+
) {
|
|
643
|
+
throw new Error(
|
|
644
|
+
"This join token was not generated for your rivet address. Token is bound to: " +
|
|
645
|
+
payload.targetRivetAddress,
|
|
646
|
+
);
|
|
619
647
|
}
|
|
620
648
|
|
|
621
649
|
// The contract we're joining (from the token)
|
|
@@ -624,21 +652,24 @@ export class RivetWallet extends Wallet {
|
|
|
624
652
|
// Verify the signature from the inviter
|
|
625
653
|
const message = JSON.stringify(payload);
|
|
626
654
|
const recoveredAddress = ethers.utils.verifyMessage(message, signature);
|
|
627
|
-
if (
|
|
628
|
-
|
|
655
|
+
if (
|
|
656
|
+
recoveredAddress.toLowerCase() !==
|
|
657
|
+
payload.inviterRivetAddress.toLowerCase()
|
|
658
|
+
) {
|
|
659
|
+
throw new Error("Invalid join token signature");
|
|
629
660
|
}
|
|
630
661
|
|
|
631
662
|
// Upgrade this rivet to use the contract as its identity
|
|
632
663
|
this.upgradeToContract(targetContract);
|
|
633
664
|
|
|
634
|
-
console.log(
|
|
665
|
+
console.log("Rivet ready to join identity contract:", myRivetAddress);
|
|
635
666
|
|
|
636
667
|
return {
|
|
637
668
|
contractAddress: targetContract,
|
|
638
|
-
myRivetAddress: myRivetAddress
|
|
669
|
+
myRivetAddress: myRivetAddress,
|
|
639
670
|
};
|
|
640
671
|
} catch (error) {
|
|
641
|
-
console.error(
|
|
672
|
+
console.error("Failed to accept join token:", error);
|
|
642
673
|
throw error;
|
|
643
674
|
}
|
|
644
675
|
}
|
|
@@ -651,32 +682,32 @@ export class RivetWallet extends Wallet {
|
|
|
651
682
|
static generateRivetName() {
|
|
652
683
|
// Detect browser
|
|
653
684
|
const userAgent = navigator.userAgent.toLowerCase();
|
|
654
|
-
let browser =
|
|
655
|
-
|
|
656
|
-
if (userAgent.includes(
|
|
657
|
-
browser =
|
|
658
|
-
} else if (userAgent.includes(
|
|
659
|
-
browser =
|
|
660
|
-
} else if (userAgent.includes(
|
|
661
|
-
browser =
|
|
662
|
-
} else if (userAgent.includes(
|
|
663
|
-
browser =
|
|
664
|
-
} else if (userAgent.includes(
|
|
665
|
-
browser =
|
|
685
|
+
let browser = "unknown";
|
|
686
|
+
|
|
687
|
+
if (userAgent.includes("chrome") && !userAgent.includes("edg")) {
|
|
688
|
+
browser = "chrome";
|
|
689
|
+
} else if (userAgent.includes("firefox")) {
|
|
690
|
+
browser = "firefox";
|
|
691
|
+
} else if (userAgent.includes("safari") && !userAgent.includes("chrome")) {
|
|
692
|
+
browser = "safari";
|
|
693
|
+
} else if (userAgent.includes("edg")) {
|
|
694
|
+
browser = "edge";
|
|
695
|
+
} else if (userAgent.includes("opr") || userAgent.includes("opera")) {
|
|
696
|
+
browser = "opera";
|
|
666
697
|
}
|
|
667
698
|
|
|
668
699
|
// Detect OS
|
|
669
|
-
let os =
|
|
670
|
-
if (userAgent.includes(
|
|
671
|
-
os =
|
|
672
|
-
} else if (userAgent.includes(
|
|
673
|
-
os =
|
|
674
|
-
} else if (userAgent.includes(
|
|
675
|
-
os =
|
|
676
|
-
} else if (userAgent.includes(
|
|
677
|
-
os =
|
|
678
|
-
} else if (userAgent.includes(
|
|
679
|
-
os =
|
|
700
|
+
let os = "unknown";
|
|
701
|
+
if (userAgent.includes("win")) {
|
|
702
|
+
os = "windows";
|
|
703
|
+
} else if (userAgent.includes("mac")) {
|
|
704
|
+
os = "macos";
|
|
705
|
+
} else if (userAgent.includes("linux")) {
|
|
706
|
+
os = "linux";
|
|
707
|
+
} else if (userAgent.includes("android")) {
|
|
708
|
+
os = "android";
|
|
709
|
+
} else if (userAgent.includes("iphone") || userAgent.includes("ipad")) {
|
|
710
|
+
os = "ios";
|
|
680
711
|
}
|
|
681
712
|
|
|
682
713
|
// Get hostname
|
|
@@ -695,18 +726,23 @@ export class RivetWallet extends Wallet {
|
|
|
695
726
|
try {
|
|
696
727
|
// Normalize URL - ensure it has a protocol
|
|
697
728
|
let normalizedURL = url.trim();
|
|
698
|
-
if (
|
|
729
|
+
if (
|
|
730
|
+
!normalizedURL.startsWith("http://") &&
|
|
731
|
+
!normalizedURL.startsWith("https://")
|
|
732
|
+
) {
|
|
699
733
|
normalizedURL = `https://${normalizedURL}`;
|
|
700
734
|
}
|
|
701
735
|
|
|
702
736
|
// Remove trailing slash if present
|
|
703
|
-
normalizedURL = normalizedURL.replace(/\/$/,
|
|
737
|
+
normalizedURL = normalizedURL.replace(/\/$/, "");
|
|
704
738
|
|
|
705
739
|
// Fetch epistery status from the site
|
|
706
740
|
const response = await fetch(`${normalizedURL}/.well-known/epistery`);
|
|
707
741
|
|
|
708
742
|
if (!response.ok) {
|
|
709
|
-
throw new Error(
|
|
743
|
+
throw new Error(
|
|
744
|
+
`Failed to fetch epistery info from ${normalizedURL}. Status: ${response.status}`,
|
|
745
|
+
);
|
|
710
746
|
}
|
|
711
747
|
|
|
712
748
|
const data = await response.json();
|
|
@@ -715,13 +751,17 @@ export class RivetWallet extends Wallet {
|
|
|
715
751
|
const rivetAddress = data.client?.walletAddress;
|
|
716
752
|
|
|
717
753
|
if (!rivetAddress) {
|
|
718
|
-
throw new Error(
|
|
754
|
+
throw new Error(
|
|
755
|
+
`No rivet address found at ${normalizedURL}. The site may not have Epistery enabled or no rivet is connected.`,
|
|
756
|
+
);
|
|
719
757
|
}
|
|
720
758
|
|
|
721
759
|
return rivetAddress;
|
|
722
760
|
} catch (error) {
|
|
723
|
-
console.error(
|
|
724
|
-
throw new Error(
|
|
761
|
+
console.error("Failed to get rivet address from URL:", error);
|
|
762
|
+
throw new Error(
|
|
763
|
+
`Unable to get rivet address from "${url}": ${error.message}`,
|
|
764
|
+
);
|
|
725
765
|
}
|
|
726
766
|
}
|
|
727
767
|
|
|
@@ -735,34 +775,46 @@ export class RivetWallet extends Wallet {
|
|
|
735
775
|
* @param {string} domain - Domain context for the transaction
|
|
736
776
|
* @returns {Promise<void>}
|
|
737
777
|
*/
|
|
738
|
-
async addRivetToContract(
|
|
778
|
+
async addRivetToContract(
|
|
779
|
+
rivetAddressToAdd,
|
|
780
|
+
ethers,
|
|
781
|
+
providerConfig,
|
|
782
|
+
rivetName = "",
|
|
783
|
+
domain = "localhost",
|
|
784
|
+
) {
|
|
739
785
|
try {
|
|
740
786
|
if (!this.contractAddress) {
|
|
741
|
-
throw new Error(
|
|
787
|
+
throw new Error("This rivet is not part of an identity contract");
|
|
742
788
|
}
|
|
743
789
|
|
|
744
790
|
// Get rootPath from Witness singleton
|
|
745
|
-
const rootPath =
|
|
791
|
+
const rootPath =
|
|
792
|
+
(typeof Witness !== "undefined" && Witness.instance?.rootPath) || "..";
|
|
746
793
|
|
|
747
794
|
// Generate name if not provided (empty string is considered "not provided")
|
|
748
795
|
const finalRivetName = rivetName || RivetWallet.generateRivetName();
|
|
749
796
|
|
|
750
797
|
// Step 1: Prepare unsigned transaction (server funds the wallet)
|
|
751
|
-
const prepareResponse = await fetch(
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
798
|
+
const prepareResponse = await fetch(
|
|
799
|
+
`${rootPath}/identity/prepare-add-rivet`,
|
|
800
|
+
{
|
|
801
|
+
method: "POST",
|
|
802
|
+
headers: { "Content-Type": "application/json" },
|
|
803
|
+
body: JSON.stringify({
|
|
804
|
+
signerAddress: this.rivetAddress || this.address,
|
|
805
|
+
contractAddress: this.contractAddress,
|
|
806
|
+
rivetAddressToAdd: rivetAddressToAdd,
|
|
807
|
+
rivetName: finalRivetName,
|
|
808
|
+
domain: domain,
|
|
809
|
+
}),
|
|
810
|
+
},
|
|
811
|
+
);
|
|
762
812
|
|
|
763
813
|
if (!prepareResponse.ok) {
|
|
764
814
|
const error = await prepareResponse.json();
|
|
765
|
-
throw new Error(
|
|
815
|
+
throw new Error(
|
|
816
|
+
`Failed to prepare add rivet: ${error.error || prepareResponse.statusText}`,
|
|
817
|
+
);
|
|
766
818
|
}
|
|
767
819
|
|
|
768
820
|
const { unsignedTransaction, metadata } = await prepareResponse.json();
|
|
@@ -771,28 +823,37 @@ export class RivetWallet extends Wallet {
|
|
|
771
823
|
const signedTx = await this.signTransaction(unsignedTransaction, ethers);
|
|
772
824
|
|
|
773
825
|
// Step 3: Submit signed transaction to blockchain
|
|
774
|
-
const submitResponse = await fetch(
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
826
|
+
const submitResponse = await fetch(
|
|
827
|
+
`${rootPath}/epistery/data/submit-signed`,
|
|
828
|
+
{
|
|
829
|
+
method: "POST",
|
|
830
|
+
headers: { "Content-Type": "application/json" },
|
|
831
|
+
body: JSON.stringify({
|
|
832
|
+
signedTransaction: signedTx,
|
|
833
|
+
operation: "addRivetToContract",
|
|
834
|
+
metadata: metadata,
|
|
835
|
+
}),
|
|
836
|
+
},
|
|
837
|
+
);
|
|
783
838
|
|
|
784
839
|
if (!submitResponse.ok) {
|
|
785
840
|
const error = await submitResponse.json();
|
|
786
|
-
throw new Error(
|
|
841
|
+
throw new Error(
|
|
842
|
+
`Failed to submit add rivet: ${error.error || submitResponse.statusText}`,
|
|
843
|
+
);
|
|
787
844
|
}
|
|
788
845
|
|
|
789
846
|
const receipt = await submitResponse.json();
|
|
790
847
|
|
|
791
|
-
console.log(
|
|
848
|
+
console.log(
|
|
849
|
+
"Rivet added to identity contract:",
|
|
850
|
+
rivetAddressToAdd,
|
|
851
|
+
"with name:",
|
|
852
|
+
finalRivetName,
|
|
853
|
+
);
|
|
792
854
|
return receipt;
|
|
793
|
-
|
|
794
855
|
} catch (error) {
|
|
795
|
-
console.error(
|
|
856
|
+
console.error("Failed to add rivet to contract:", error);
|
|
796
857
|
throw error;
|
|
797
858
|
}
|
|
798
859
|
}
|
|
@@ -806,20 +867,27 @@ export class RivetWallet extends Wallet {
|
|
|
806
867
|
async getRivetsInContract(ethers, providerConfig) {
|
|
807
868
|
try {
|
|
808
869
|
if (!this.contractAddress) {
|
|
809
|
-
throw new Error(
|
|
870
|
+
throw new Error("This rivet is not part of an identity contract");
|
|
810
871
|
}
|
|
811
872
|
|
|
812
873
|
// Get rootPath from Witness singleton
|
|
813
|
-
const rootPath =
|
|
874
|
+
const rootPath =
|
|
875
|
+
(typeof Witness !== "undefined" && Witness.instance?.rootPath) || "..";
|
|
814
876
|
|
|
815
877
|
const provider = new ethers.providers.JsonRpcProvider(providerConfig.rpc);
|
|
816
878
|
|
|
817
879
|
// Load contract artifact
|
|
818
|
-
const response = await fetch(
|
|
880
|
+
const response = await fetch(
|
|
881
|
+
`${rootPath}/epistery/artifacts/IdentityContract.json`,
|
|
882
|
+
);
|
|
819
883
|
const artifact = await response.json();
|
|
820
884
|
|
|
821
885
|
// Connect to the identity contract (read-only, no signer needed)
|
|
822
|
-
const contract = new ethers.Contract(
|
|
886
|
+
const contract = new ethers.Contract(
|
|
887
|
+
this.contractAddress,
|
|
888
|
+
artifact.abi,
|
|
889
|
+
provider,
|
|
890
|
+
);
|
|
823
891
|
|
|
824
892
|
try {
|
|
825
893
|
// Try to call getRivetsWithNames() (new contract version)
|
|
@@ -828,21 +896,23 @@ export class RivetWallet extends Wallet {
|
|
|
828
896
|
// Combine addresses and names into objects
|
|
829
897
|
return addresses.map((address, index) => ({
|
|
830
898
|
address: address,
|
|
831
|
-
name: names[index] ||
|
|
899
|
+
name: names[index] || "Unnamed Rivet",
|
|
832
900
|
}));
|
|
833
901
|
} catch (error) {
|
|
834
902
|
// Fallback to getRivets() for old contracts that don't have names
|
|
835
|
-
console.warn(
|
|
903
|
+
console.warn(
|
|
904
|
+
"Contract does not support getRivetsWithNames(), falling back to getRivets()",
|
|
905
|
+
);
|
|
836
906
|
const addresses = await contract.getRivets();
|
|
837
907
|
|
|
838
908
|
// Return addresses with default names
|
|
839
909
|
return addresses.map((address) => ({
|
|
840
910
|
address: address,
|
|
841
|
-
name:
|
|
911
|
+
name: "Unnamed Rivet (old contract)",
|
|
842
912
|
}));
|
|
843
913
|
}
|
|
844
914
|
} catch (error) {
|
|
845
|
-
console.error(
|
|
915
|
+
console.error("Failed to get rivets from contract:", error);
|
|
846
916
|
throw error;
|
|
847
917
|
}
|
|
848
918
|
}
|
|
@@ -854,18 +924,18 @@ export class RivetWallet extends Wallet {
|
|
|
854
924
|
*/
|
|
855
925
|
upgradeToContract(contractAddress) {
|
|
856
926
|
if (this.contractAddress) {
|
|
857
|
-
throw new Error(
|
|
927
|
+
throw new Error("Rivet is already using an identity contract");
|
|
858
928
|
}
|
|
859
929
|
|
|
860
930
|
this.rivetAddress = this.address; // Save original rivet address
|
|
861
931
|
this.address = contractAddress; // Present contract address
|
|
862
932
|
this.contractAddress = contractAddress;
|
|
863
|
-
this.type =
|
|
933
|
+
this.type = "Contract";
|
|
864
934
|
this.lastUpdated = Date.now();
|
|
865
935
|
}
|
|
866
936
|
}
|
|
867
937
|
|
|
868
938
|
// Expose RivetWallet globally for browser access
|
|
869
|
-
if (typeof window !==
|
|
939
|
+
if (typeof window !== "undefined") {
|
|
870
940
|
window.RivetWallet = RivetWallet;
|
|
871
941
|
}
|