@beclab/olaresid 0.1.5 → 0.1.7

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 (51) hide show
  1. package/README.md +10 -0
  2. package/dist/business/index.d.ts +3 -3
  3. package/dist/business/index.d.ts.map +1 -1
  4. package/dist/business/index.js +31 -37
  5. package/dist/business/index.js.map +1 -1
  6. package/dist/cli.js +3 -4
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1 -6
  11. package/dist/index.js.map +1 -1
  12. package/dist/utils/crypto-utils.d.ts +4 -32
  13. package/dist/utils/crypto-utils.d.ts.map +1 -1
  14. package/dist/utils/crypto-utils.js +32 -95
  15. package/dist/utils/crypto-utils.js.map +1 -1
  16. package/examples/crypto-utilities.ts +3 -3
  17. package/examples/ed25519-jwk.ts +1 -1
  18. package/examples/frontend-demo/index.html +13 -0
  19. package/examples/frontend-demo/package-lock.json +5370 -0
  20. package/examples/frontend-demo/package.json +33 -0
  21. package/examples/frontend-demo/src/App.vue +1211 -0
  22. package/examples/frontend-demo/src/main.ts +8 -0
  23. package/examples/frontend-demo/src/style.css +341 -0
  24. package/examples/frontend-demo/tsconfig.json +24 -0
  25. package/examples/frontend-demo/webpack.config.js +87 -0
  26. package/examples/generate-mnemonic.ts +3 -3
  27. package/examples/register-subdomain.ts +1 -1
  28. package/examples/transfer-domain.ts +1 -1
  29. package/package.json +2 -1
  30. package/src/business/index.ts +23 -34
  31. package/src/cli.ts +3 -4
  32. package/src/index.ts +1 -6
  33. package/src/utils/crypto-utils.ts +34 -112
  34. package/examples/encoding-utils.ts +0 -96
  35. package/examples/quasar-demo/.eslintrc.js +0 -23
  36. package/examples/quasar-demo/.quasar/app.js +0 -43
  37. package/examples/quasar-demo/.quasar/client-entry.js +0 -38
  38. package/examples/quasar-demo/.quasar/client-prefetch.js +0 -130
  39. package/examples/quasar-demo/.quasar/quasar-user-options.js +0 -16
  40. package/examples/quasar-demo/README.md +0 -49
  41. package/examples/quasar-demo/index.html +0 -11
  42. package/examples/quasar-demo/package-lock.json +0 -6407
  43. package/examples/quasar-demo/package.json +0 -36
  44. package/examples/quasar-demo/quasar.config.js +0 -73
  45. package/examples/quasar-demo/src/App.vue +0 -13
  46. package/examples/quasar-demo/src/css/app.scss +0 -1
  47. package/examples/quasar-demo/src/layouts/MainLayout.vue +0 -21
  48. package/examples/quasar-demo/src/pages/IndexPage.vue +0 -905
  49. package/examples/quasar-demo/src/router/index.ts +0 -25
  50. package/examples/quasar-demo/src/router/routes.ts +0 -11
  51. package/examples/quasar-demo/tsconfig.json +0 -28
@@ -0,0 +1,1211 @@
1
+ <template>
2
+ <div class="app">
3
+ <header>
4
+ <h1>🔑 OlaresID SDK Demo</h1>
5
+ <p class="subtitle">Complete SDK functionality demonstration</p>
6
+ </header>
7
+
8
+ <main>
9
+ <!-- MetaMask Connection -->
10
+ <section class="card">
11
+ <h2>🦊 MetaMask Connection</h2>
12
+
13
+ <div
14
+ v-if="currentNetwork"
15
+ class="network-badge"
16
+ :class="networkClass"
17
+ >
18
+ {{ currentNetwork }} ({{ currentChainId }})
19
+ </div>
20
+
21
+ <div
22
+ v-if="currentNetwork.startsWith('Unsupported')"
23
+ class="warning"
24
+ >
25
+ ⚠️ Please switch to Optimism Mainnet or OP Sepolia Testnet
26
+ <div class="button-grid-small" style="margin-top: 0.75rem">
27
+ <button @click="switchToMainnet" class="btn-link">
28
+ Switch to OP Mainnet
29
+ </button>
30
+ <button @click="switchToTestnet" class="btn-link">
31
+ Switch to OP Testnet
32
+ </button>
33
+ </div>
34
+ </div>
35
+
36
+ <div v-if="!walletConnected">
37
+ <button
38
+ @click="connectMetaMask"
39
+ :disabled="connecting"
40
+ class="btn-primary"
41
+ >
42
+ {{
43
+ connecting ? 'Connecting...' : '🦊 Connect MetaMask'
44
+ }}
45
+ </button>
46
+ </div>
47
+ <div v-else class="connected-info">
48
+ <div class="success-badge">
49
+ ✅ Connected: {{ walletAddress }}
50
+ </div>
51
+ <button @click="disconnectWallet" class="btn-secondary">
52
+ Disconnect
53
+ </button>
54
+ </div>
55
+ </section>
56
+
57
+ <!-- Domain Input - Top Level -->
58
+ <section class="card domain-input-section">
59
+ <h2>🏷️ Domain Configuration</h2>
60
+ <p class="section-description">
61
+ Enter your domain name to manage its settings below
62
+ </p>
63
+ <div class="form-group">
64
+ <label class="input-label">Domain Name</label>
65
+ <input
66
+ v-model="domainName"
67
+ placeholder="e.g., alice.olares.com or 1.com"
68
+ :disabled="!walletConnected"
69
+ class="input input-large"
70
+ />
71
+ </div>
72
+ <div v-if="!domainName && walletConnected" class="info-message">
73
+ 👆 Please enter a domain name to access management features
74
+ </div>
75
+ </section>
76
+
77
+ <!-- Domain Management Area - Only show when domain is entered -->
78
+ <div v-if="domainName" class="management-area">
79
+ <!-- Domain Info Demo -->
80
+ <section class="card subsection">
81
+ <h3>📋 Domain Info</h3>
82
+ <div class="button-grid">
83
+ <button
84
+ @click="getMetaInfo"
85
+ :disabled="
86
+ !domainName ||
87
+ !walletConnected ||
88
+ loading.metaInfo
89
+ "
90
+ class="btn-primary"
91
+ >
92
+ {{
93
+ loading.metaInfo
94
+ ? 'Loading...'
95
+ : 'Get Meta Info'
96
+ }}
97
+ </button>
98
+ <button
99
+ @click="getOwner"
100
+ :disabled="
101
+ !domainName || !walletConnected || loading.owner
102
+ "
103
+ class="btn-primary"
104
+ >
105
+ {{ loading.owner ? 'Loading...' : 'Get Owner' }}
106
+ </button>
107
+ <button
108
+ @click="checkIsOwner"
109
+ :disabled="
110
+ !domainName ||
111
+ !walletConnected ||
112
+ loading.isOwner
113
+ "
114
+ class="btn-secondary"
115
+ >
116
+ {{ loading.isOwner ? 'Loading...' : 'Is Owner?' }}
117
+ </button>
118
+ </div>
119
+ <div v-if="results.domainContext" class="result">
120
+ <pre>{{ results.domainContext }}</pre>
121
+ </div>
122
+ </section>
123
+
124
+ <!-- IP Address Management -->
125
+ <section class="card subsection">
126
+ <h3>🌐 IP Address Management</h3>
127
+ <div class="form-group">
128
+ <input
129
+ v-model="ipAddress"
130
+ placeholder="e.g., 192.168.1.100"
131
+ :disabled="!walletConnected"
132
+ class="input"
133
+ />
134
+ </div>
135
+ <div class="button-grid">
136
+ <button
137
+ @click="setIP"
138
+ :disabled="
139
+ !domainName ||
140
+ !ipAddress ||
141
+ !walletConnected ||
142
+ loading.setIP
143
+ "
144
+ class="btn-success"
145
+ >
146
+ {{ loading.setIP ? 'Setting...' : 'Set IP' }}
147
+ </button>
148
+ <button
149
+ @click="getIP"
150
+ :disabled="
151
+ !domainName || !walletConnected || loading.getIP
152
+ "
153
+ class="btn-primary"
154
+ >
155
+ {{ loading.getIP ? 'Loading...' : 'Get IP' }}
156
+ </button>
157
+ <button
158
+ @click="removeIP"
159
+ :disabled="
160
+ !domainName ||
161
+ !walletConnected ||
162
+ loading.removeIP
163
+ "
164
+ class="btn-danger"
165
+ >
166
+ {{ loading.removeIP ? 'Removing...' : 'Remove IP' }}
167
+ </button>
168
+ </div>
169
+ <div v-if="results.ip" class="result success">
170
+ <pre>{{ results.ip }}</pre>
171
+ </div>
172
+ </section>
173
+
174
+ <!-- RSA Key Management -->
175
+ <section class="card subsection">
176
+ <h3>🔐 RSA Key Management</h3>
177
+ <div class="button-grid">
178
+ <button
179
+ @click="generateRSAKeyPair"
180
+ :disabled="loading.generateRSA"
181
+ class="btn-purple"
182
+ >
183
+ {{
184
+ loading.generateRSA
185
+ ? 'Generating...'
186
+ : '🔑 Generate RSA Key Pair'
187
+ }}
188
+ </button>
189
+ </div>
190
+
191
+ <div v-if="rsaKeyPair" class="rsa-keys">
192
+ <div class="key-display">
193
+ <strong>Public Key (PEM):</strong>
194
+ <pre
195
+ >{{
196
+ rsaKeyPair.publicKey.substring(0, 100)
197
+ }}...</pre
198
+ >
199
+ </div>
200
+ <div class="key-display">
201
+ <strong>Private Key (PEM):</strong>
202
+ <pre
203
+ >{{
204
+ rsaKeyPair.privateKey.substring(0, 100)
205
+ }}...</pre
206
+ >
207
+ </div>
208
+ </div>
209
+
210
+ <div class="button-grid">
211
+ <button
212
+ @click="setRSAPublicKey"
213
+ :disabled="
214
+ !domainName ||
215
+ !rsaKeyPair ||
216
+ !walletConnected ||
217
+ loading.setRSA
218
+ "
219
+ class="btn-success"
220
+ >
221
+ {{
222
+ loading.setRSA
223
+ ? 'Setting...'
224
+ : 'Set RSA Public Key'
225
+ }}
226
+ </button>
227
+ <button
228
+ @click="getRSAPublicKey"
229
+ :disabled="
230
+ !domainName ||
231
+ !walletConnected ||
232
+ loading.getRSA
233
+ "
234
+ class="btn-primary"
235
+ >
236
+ {{
237
+ loading.getRSA
238
+ ? 'Loading...'
239
+ : 'Get RSA Public Key'
240
+ }}
241
+ </button>
242
+ <button
243
+ @click="removeRSAPublicKey"
244
+ :disabled="
245
+ !domainName ||
246
+ !walletConnected ||
247
+ loading.removeRSA
248
+ "
249
+ class="btn-danger"
250
+ >
251
+ {{
252
+ loading.removeRSA
253
+ ? 'Removing...'
254
+ : 'Remove RSA Key'
255
+ }}
256
+ </button>
257
+ </div>
258
+
259
+ <div v-if="results.rsa" class="result purple">
260
+ <pre>{{ results.rsa }}</pre>
261
+ </div>
262
+ </section>
263
+ </div>
264
+
265
+ <!-- Toast Messages -->
266
+ <div v-if="toast.show" class="toast" :class="toast.type">
267
+ {{ toast.message }}
268
+ </div>
269
+ </main>
270
+ </div>
271
+ </template>
272
+
273
+ <script setup lang="ts">
274
+ import { ref, onMounted, reactive } from 'vue';
275
+ import * as OlaresIDModule from '@beclab/olaresid';
276
+ import { ethers } from 'ethers';
277
+
278
+ const OlaresID = (OlaresIDModule as any).default || OlaresIDModule;
279
+ const { createRsaKeyPair } = OlaresIDModule as any;
280
+
281
+ declare global {
282
+ interface Window {
283
+ ethereum?: any;
284
+ }
285
+ }
286
+
287
+ // State
288
+ const domainName = ref('1.com');
289
+ const ipAddress = ref('192.168.1.100');
290
+ const rsaKeyPair = ref<any>(null);
291
+
292
+ // Loading states
293
+ const loading = reactive({
294
+ metaInfo: false,
295
+ owner: false,
296
+ isOwner: false,
297
+ setIP: false,
298
+ getIP: false,
299
+ removeIP: false,
300
+ generateRSA: false,
301
+ setRSA: false,
302
+ getRSA: false,
303
+ removeRSA: false
304
+ });
305
+
306
+ // Results
307
+ const results = reactive({
308
+ domainContext: '',
309
+ ip: '',
310
+ rsa: ''
311
+ });
312
+
313
+ // MetaMask state
314
+ const walletConnected = ref(false);
315
+ const walletAddress = ref('');
316
+ const connecting = ref(false);
317
+ const currentNetwork = ref('');
318
+ const currentChainId = ref('');
319
+ const networkClass = ref('');
320
+
321
+ // Toast state
322
+ const toast = reactive({
323
+ show: false,
324
+ message: '',
325
+ type: 'info'
326
+ });
327
+
328
+ // Initialize OlaresID
329
+ let olaresId = OlaresID.createTestnet();
330
+
331
+ onMounted(async () => {
332
+ await checkMetaMaskConnection();
333
+
334
+ if (typeof window.ethereum !== 'undefined') {
335
+ window.ethereum.on('chainChanged', handleChainChanged);
336
+ window.ethereum.on('accountsChanged', handleAccountsChanged);
337
+ }
338
+ });
339
+
340
+ const showToast = (
341
+ message: string,
342
+ type: 'success' | 'error' | 'info' = 'info'
343
+ ) => {
344
+ toast.message = message;
345
+ toast.type = type;
346
+ toast.show = true;
347
+ setTimeout(() => {
348
+ toast.show = false;
349
+ }, 3000);
350
+ };
351
+
352
+ const handleChainChanged = async (chainId: string) => {
353
+ console.log('Chain changed to:', chainId);
354
+ currentChainId.value = chainId;
355
+ currentNetwork.value = getNetworkName(chainId);
356
+
357
+ if (!isSupportedNetwork(chainId)) {
358
+ networkClass.value = 'unsupported';
359
+ if (walletConnected.value) {
360
+ olaresId.clearSigner();
361
+ }
362
+ showToast(
363
+ `⚠️ Unsupported network detected. Please switch to Optimism Mainnet or OP Sepolia Testnet.`,
364
+ 'error'
365
+ );
366
+ return;
367
+ }
368
+
369
+ await reinitializeOlaresId();
370
+ showToast(`✅ Network switched to ${currentNetwork.value}`, 'success');
371
+ };
372
+
373
+ const handleAccountsChanged = async (accounts: string[]) => {
374
+ console.log('Accounts changed:', accounts);
375
+ if (accounts.length === 0) {
376
+ disconnectWallet();
377
+ } else if (accounts[0] !== walletAddress.value) {
378
+ walletAddress.value = accounts[0];
379
+ await setSignerToOlaresId();
380
+ }
381
+ };
382
+
383
+ const getNetworkName = (chainId: string): string => {
384
+ const networks: Record<string, string> = {
385
+ '0xa': 'Optimism Mainnet',
386
+ '0xaa37dc': 'OP Sepolia Testnet'
387
+ };
388
+ return networks[chainId] || `Unsupported Network (Chain ID: ${chainId})`;
389
+ };
390
+
391
+ const isMainnet = (chainId: string): boolean => {
392
+ return chainId === '0xa';
393
+ };
394
+
395
+ const isSupportedNetwork = (chainId: string): boolean => {
396
+ return chainId === '0xa' || chainId === '0xaa37dc';
397
+ };
398
+
399
+ const reinitializeOlaresId = async () => {
400
+ if (!isSupportedNetwork(currentChainId.value)) {
401
+ currentNetwork.value = getNetworkName(currentChainId.value);
402
+ networkClass.value = 'unsupported';
403
+ return;
404
+ }
405
+
406
+ const isMainnetNetwork = isMainnet(currentChainId.value);
407
+
408
+ try {
409
+ if (isMainnetNetwork) {
410
+ olaresId = OlaresID.createMainnet();
411
+ currentNetwork.value = 'Optimism Mainnet';
412
+ networkClass.value = 'mainnet';
413
+ } else {
414
+ olaresId = OlaresID.createTestnet();
415
+ currentNetwork.value = 'OP Sepolia Testnet';
416
+ networkClass.value = 'testnet';
417
+ }
418
+
419
+ if (walletConnected.value && walletAddress.value) {
420
+ await setSignerToOlaresId();
421
+ console.log(
422
+ 'OlaresID reinitialized with new network and signer set'
423
+ );
424
+ } else {
425
+ console.log('OlaresID reinitialized with new network');
426
+ }
427
+ } catch (err) {
428
+ console.error('Error reinitializing OlaresID:', err);
429
+ showToast('Failed to reinitialize OlaresID', 'error');
430
+ }
431
+ };
432
+
433
+ const checkMetaMaskConnection = async () => {
434
+ if (typeof window.ethereum !== 'undefined') {
435
+ try {
436
+ const chainId = await window.ethereum.request({
437
+ method: 'eth_chainId'
438
+ });
439
+ currentChainId.value = chainId;
440
+ await reinitializeOlaresId();
441
+
442
+ const accounts = await window.ethereum.request({
443
+ method: 'eth_accounts'
444
+ });
445
+ if (accounts.length > 0) {
446
+ walletAddress.value = accounts[0];
447
+ walletConnected.value = true;
448
+ await setSignerToOlaresId();
449
+ }
450
+ } catch (err) {
451
+ console.error('Error checking MetaMask connection:', err);
452
+ }
453
+ }
454
+ };
455
+
456
+ const connectMetaMask = async () => {
457
+ if (typeof window.ethereum === 'undefined') {
458
+ showToast(
459
+ 'MetaMask not detected. Please install MetaMask extension.',
460
+ 'error'
461
+ );
462
+ return;
463
+ }
464
+
465
+ connecting.value = true;
466
+
467
+ try {
468
+ const chainId = await window.ethereum.request({
469
+ method: 'eth_chainId'
470
+ });
471
+ currentChainId.value = chainId;
472
+
473
+ if (!isSupportedNetwork(chainId)) {
474
+ showToast(
475
+ 'Unsupported network. Please switch to Optimism Mainnet or OP Sepolia Testnet.',
476
+ 'error'
477
+ );
478
+ await reinitializeOlaresId();
479
+ connecting.value = false;
480
+ return;
481
+ }
482
+
483
+ await reinitializeOlaresId();
484
+
485
+ const accounts = await window.ethereum.request({
486
+ method: 'eth_requestAccounts'
487
+ });
488
+
489
+ if (accounts.length > 0) {
490
+ walletAddress.value = accounts[0];
491
+ walletConnected.value = true;
492
+ await setSignerToOlaresId();
493
+ showToast(
494
+ `Connected: ${walletAddress.value.substring(
495
+ 0,
496
+ 6
497
+ )}...${walletAddress.value.substring(38)}`,
498
+ 'success'
499
+ );
500
+ }
501
+ } catch (err: any) {
502
+ console.error('MetaMask connection error:', err);
503
+ showToast(`Failed to connect: ${err.message}`, 'error');
504
+ } finally {
505
+ connecting.value = false;
506
+ }
507
+ };
508
+
509
+ const setSignerToOlaresId = async () => {
510
+ try {
511
+ const provider = new ethers.BrowserProvider(window.ethereum);
512
+ const signer = await provider.getSigner();
513
+ olaresId.setSigner(signer);
514
+ console.log('Signer set to OlaresID');
515
+ } catch (err) {
516
+ console.error('Error setting signer:', err);
517
+ throw err;
518
+ }
519
+ };
520
+
521
+ const disconnectWallet = () => {
522
+ walletConnected.value = false;
523
+ walletAddress.value = '';
524
+ olaresId.clearSigner();
525
+ showToast('Wallet disconnected', 'info');
526
+ };
527
+
528
+ const switchToMainnet = async () => {
529
+ try {
530
+ await window.ethereum.request({
531
+ method: 'wallet_switchEthereumChain',
532
+ params: [{ chainId: '0xa' }] // OP Mainnet
533
+ });
534
+ } catch (error: any) {
535
+ if (error.code === 4902) {
536
+ try {
537
+ await window.ethereum.request({
538
+ method: 'wallet_addEthereumChain',
539
+ params: [
540
+ {
541
+ chainId: '0xa',
542
+ chainName: 'Optimism',
543
+ nativeCurrency: {
544
+ name: 'Ethereum',
545
+ symbol: 'ETH',
546
+ decimals: 18
547
+ },
548
+ rpcUrls: ['https://optimism-rpc.publicnode.com'],
549
+ blockExplorerUrls: [
550
+ 'https://optimistic.etherscan.io'
551
+ ]
552
+ }
553
+ ]
554
+ });
555
+ } catch (addError) {
556
+ showToast('Failed to add Optimism network', 'error');
557
+ }
558
+ } else {
559
+ showToast('Failed to switch network', 'error');
560
+ }
561
+ }
562
+ };
563
+
564
+ const switchToTestnet = async () => {
565
+ try {
566
+ await window.ethereum.request({
567
+ method: 'wallet_switchEthereumChain',
568
+ params: [{ chainId: '0xaa37dc' }] // OP Sepolia
569
+ });
570
+ } catch (error: any) {
571
+ if (error.code === 4902) {
572
+ try {
573
+ await window.ethereum.request({
574
+ method: 'wallet_addEthereumChain',
575
+ params: [
576
+ {
577
+ chainId: '0xaa37dc',
578
+ chainName: 'OP Sepolia',
579
+ nativeCurrency: {
580
+ name: 'Ethereum',
581
+ symbol: 'ETH',
582
+ decimals: 18
583
+ },
584
+ rpcUrls: ['https://sepolia.optimism.io'],
585
+ blockExplorerUrls: [
586
+ 'https://sepolia-optimistic.etherscan.io'
587
+ ]
588
+ }
589
+ ]
590
+ });
591
+ } catch (addError) {
592
+ showToast('Failed to add OP Sepolia network', 'error');
593
+ }
594
+ } else {
595
+ showToast('Failed to switch network', 'error');
596
+ }
597
+ }
598
+ };
599
+
600
+ // Domain Info Methods
601
+ const getMetaInfo = async () => {
602
+ loading.metaInfo = true;
603
+ results.domainContext = '';
604
+ try {
605
+ const domain = olaresId.domain(domainName.value);
606
+ const metaInfo = await domain.getMetaInfo();
607
+ results.domainContext = `Meta Info:
608
+ ID: ${metaInfo.id}
609
+ Name: ${metaInfo.name}
610
+ DID: ${metaInfo.did}
611
+ Note: ${metaInfo.note || 'N/A'}
612
+ Allow Subdomain: ${metaInfo.allowSubdomain}`;
613
+ showToast('Meta info retrieved!', 'success');
614
+ } catch (err: any) {
615
+ showToast(`Failed: ${err.message}`, 'error');
616
+ } finally {
617
+ loading.metaInfo = false;
618
+ }
619
+ };
620
+
621
+ const getOwner = async () => {
622
+ loading.owner = true;
623
+ results.domainContext = '';
624
+ try {
625
+ const domain = olaresId.domain(domainName.value);
626
+ const owner = await domain.getOwner();
627
+ results.domainContext = `Owner Address:\n${owner}`;
628
+ showToast('Owner retrieved!', 'success');
629
+ } catch (err: any) {
630
+ showToast(`Failed: ${err.message}`, 'error');
631
+ } finally {
632
+ loading.owner = false;
633
+ }
634
+ };
635
+
636
+ const checkIsOwner = async () => {
637
+ loading.isOwner = true;
638
+ results.domainContext = '';
639
+ try {
640
+ const domain = olaresId.domain(domainName.value);
641
+ const isOwner = await domain.isOwner();
642
+ results.domainContext = `Is Owner: ${isOwner ? 'Yes ✅' : 'No ❌'}`;
643
+ showToast(
644
+ isOwner ? 'You are the owner!' : 'You are not the owner',
645
+ isOwner ? 'success' : 'info'
646
+ );
647
+ } catch (err: any) {
648
+ showToast(`Failed: ${err.message}`, 'error');
649
+ } finally {
650
+ loading.isOwner = false;
651
+ }
652
+ };
653
+
654
+ // IP Methods
655
+ const setIP = async () => {
656
+ loading.setIP = true;
657
+ results.ip = '';
658
+ try {
659
+ const domain = olaresId.domain(domainName.value);
660
+ const result = await domain.setIP(ipAddress.value);
661
+ if (result.success) {
662
+ results.ip = `IP Set Successfully!
663
+ Transaction Hash: ${result.transactionHash}
664
+ Gas Used: ${result.gasUsed?.toString()}`;
665
+ showToast('IP address set!', 'success');
666
+ } else {
667
+ throw new Error(result.error || 'Failed to set IP');
668
+ }
669
+ } catch (err: any) {
670
+ showToast(`Failed: ${err.message}`, 'error');
671
+ } finally {
672
+ loading.setIP = false;
673
+ }
674
+ };
675
+
676
+ const getIP = async () => {
677
+ loading.getIP = true;
678
+ results.ip = '';
679
+ try {
680
+ const domain = olaresId.domain(domainName.value);
681
+ const ip = await domain.getIP();
682
+ results.ip = ip ? `IP Address: ${ip}` : 'No IP address set';
683
+ showToast('IP retrieved!', 'success');
684
+ } catch (err: any) {
685
+ showToast(`Failed: ${err.message}`, 'error');
686
+ } finally {
687
+ loading.getIP = false;
688
+ }
689
+ };
690
+
691
+ const removeIP = async () => {
692
+ loading.removeIP = true;
693
+ results.ip = '';
694
+ try {
695
+ const domain = olaresId.domain(domainName.value);
696
+ const result = await domain.removeIP();
697
+ if (result.success) {
698
+ results.ip = `IP Removed Successfully!\nTransaction Hash: ${result.transactionHash}`;
699
+ showToast('IP address removed!', 'success');
700
+ } else {
701
+ throw new Error(result.error || 'Failed to remove IP');
702
+ }
703
+ } catch (err: any) {
704
+ showToast(`Failed: ${err.message}`, 'error');
705
+ } finally {
706
+ loading.removeIP = false;
707
+ }
708
+ };
709
+
710
+ // RSA Methods
711
+ const generateRSAKeyPair = async () => {
712
+ loading.generateRSA = true;
713
+ results.rsa = '';
714
+ try {
715
+ const keyPair = await createRsaKeyPair();
716
+ rsaKeyPair.value = {
717
+ publicKey: keyPair.rsaPublicKey,
718
+ privateKey: keyPair.rsaPrivateKey
719
+ };
720
+ results.rsa = 'RSA Key Pair Generated Successfully!';
721
+ showToast('RSA key pair generated!', 'success');
722
+ } catch (err: any) {
723
+ showToast(`Failed: ${err.message}`, 'error');
724
+ } finally {
725
+ loading.generateRSA = false;
726
+ }
727
+ };
728
+
729
+ const setRSAPublicKey = async () => {
730
+ loading.setRSA = true;
731
+ results.rsa = '';
732
+ try {
733
+ const domain = olaresId.domain(domainName.value);
734
+ const result = await domain.setRSAPublicKey(rsaKeyPair.value.publicKey);
735
+ if (result.success) {
736
+ results.rsa = `RSA Public Key Set Successfully!
737
+ Transaction Hash: ${result.transactionHash}
738
+ Gas Used: ${result.gasUsed?.toString()}`;
739
+ showToast('RSA public key set!', 'success');
740
+ } else {
741
+ throw new Error(result.error || 'Failed to set RSA key');
742
+ }
743
+ } catch (err: any) {
744
+ showToast(`Failed: ${err.message}`, 'error');
745
+ } finally {
746
+ loading.setRSA = false;
747
+ }
748
+ };
749
+
750
+ const getRSAPublicKey = async () => {
751
+ loading.getRSA = true;
752
+ results.rsa = '';
753
+ try {
754
+ const domain = olaresId.domain(domainName.value);
755
+ const key = await domain.getRSAPublicKey();
756
+ results.rsa = key
757
+ ? `RSA Public Key (PEM):\n${key}`
758
+ : 'No RSA public key set';
759
+ showToast('RSA key retrieved!', 'success');
760
+ } catch (err: any) {
761
+ showToast(`Failed: ${err.message}`, 'error');
762
+ } finally {
763
+ loading.getRSA = false;
764
+ }
765
+ };
766
+
767
+ const removeRSAPublicKey = async () => {
768
+ loading.removeRSA = true;
769
+ results.rsa = '';
770
+ try {
771
+ const domain = olaresId.domain(domainName.value);
772
+ const result = await domain.removeRSAPublicKey();
773
+ if (result.success) {
774
+ results.rsa = `RSA Public Key Removed Successfully!\nTransaction Hash: ${result.transactionHash}`;
775
+ showToast('RSA key removed!', 'success');
776
+ } else {
777
+ throw new Error(result.error || 'Failed to remove RSA key');
778
+ }
779
+ } catch (err: any) {
780
+ showToast(`Failed: ${err.message}`, 'error');
781
+ } finally {
782
+ loading.removeRSA = false;
783
+ }
784
+ };
785
+ </script>
786
+
787
+ <style scoped>
788
+ * {
789
+ box-sizing: border-box;
790
+ }
791
+
792
+ .app {
793
+ min-height: 100vh;
794
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
795
+ padding: 2rem 1rem;
796
+ }
797
+
798
+ header {
799
+ text-align: center;
800
+ color: white;
801
+ margin-bottom: 2rem;
802
+ }
803
+
804
+ header h1 {
805
+ font-size: 2.5rem;
806
+ margin: 0 0 0.5rem 0;
807
+ }
808
+
809
+ .subtitle {
810
+ font-size: 1.1rem;
811
+ opacity: 0.9;
812
+ margin: 0;
813
+ }
814
+
815
+ main {
816
+ max-width: 900px;
817
+ margin: 0 auto;
818
+ display: flex;
819
+ flex-direction: column;
820
+ gap: 1.5rem;
821
+ }
822
+
823
+ .card {
824
+ background: white;
825
+ border-radius: 12px;
826
+ padding: 2rem;
827
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
828
+ }
829
+
830
+ .card h2 {
831
+ margin: 0 0 1.5rem 0;
832
+ color: #333;
833
+ font-size: 1.5rem;
834
+ }
835
+
836
+ .network-badge {
837
+ display: inline-block;
838
+ padding: 0.5rem 1rem;
839
+ border-radius: 20px;
840
+ font-weight: 600;
841
+ margin-bottom: 1rem;
842
+ }
843
+
844
+ .network-badge.mainnet {
845
+ background: #ff9800;
846
+ color: white;
847
+ }
848
+
849
+ .network-badge.testnet {
850
+ background: #2196f3;
851
+ color: white;
852
+ }
853
+
854
+ .network-badge.unsupported {
855
+ background: #f44336;
856
+ color: white;
857
+ }
858
+
859
+ .warning {
860
+ background: #fff3cd;
861
+ color: #856404;
862
+ padding: 0.75rem;
863
+ border-radius: 8px;
864
+ margin-bottom: 1rem;
865
+ border: 1px solid #ffc107;
866
+ }
867
+
868
+ .connected-info {
869
+ display: flex;
870
+ flex-direction: column;
871
+ gap: 1rem;
872
+ }
873
+
874
+ .success-badge {
875
+ background: #d4edda;
876
+ color: #155724;
877
+ padding: 0.75rem;
878
+ border-radius: 8px;
879
+ border: 1px solid #c3e6cb;
880
+ font-weight: 500;
881
+ }
882
+
883
+ .form-group {
884
+ margin-bottom: 1rem;
885
+ }
886
+
887
+ .input {
888
+ width: 100%;
889
+ padding: 0.875rem;
890
+ border: 2px solid #e0e0e0;
891
+ border-radius: 8px;
892
+ font-size: 1rem;
893
+ transition: border-color 0.3s;
894
+ }
895
+
896
+ .input:focus {
897
+ outline: none;
898
+ border-color: #667eea;
899
+ }
900
+
901
+ .input:disabled {
902
+ background: #f5f5f5;
903
+ cursor: not-allowed;
904
+ }
905
+
906
+ /* Domain Input Section - Top Level */
907
+ .domain-input-section {
908
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
909
+ color: white;
910
+ border: 3px solid #667eea;
911
+ }
912
+
913
+ .domain-input-section h2 {
914
+ color: white;
915
+ margin-bottom: 0.5rem;
916
+ }
917
+
918
+ .section-description {
919
+ color: rgba(255, 255, 255, 0.9);
920
+ font-size: 0.95rem;
921
+ margin-bottom: 1rem;
922
+ }
923
+
924
+ .input-label {
925
+ display: block;
926
+ font-weight: 600;
927
+ margin-bottom: 0.5rem;
928
+ color: white;
929
+ font-size: 1rem;
930
+ }
931
+
932
+ .input-large {
933
+ font-size: 1.1rem;
934
+ padding: 1rem;
935
+ background: white;
936
+ color: #333;
937
+ }
938
+
939
+ .info-message {
940
+ background: rgba(255, 255, 255, 0.2);
941
+ padding: 0.75rem;
942
+ border-radius: 8px;
943
+ margin-top: 0.75rem;
944
+ text-align: center;
945
+ font-size: 0.95rem;
946
+ color: white;
947
+ border: 1px dashed rgba(255, 255, 255, 0.5);
948
+ }
949
+
950
+ /* Management Area - Container for subsections */
951
+ .management-area {
952
+ margin-top: 2rem;
953
+ padding-left: 1.5rem;
954
+ border-left: 4px solid #667eea;
955
+ position: relative;
956
+ }
957
+
958
+ .management-area::before {
959
+ content: '';
960
+ position: absolute;
961
+ left: -2px;
962
+ top: 0;
963
+ bottom: 0;
964
+ width: 4px;
965
+ background: linear-gradient(180deg, #667eea 0%, transparent 100%);
966
+ }
967
+
968
+ /* Subsection - Child cards */
969
+ .subsection {
970
+ position: relative;
971
+ margin-bottom: 1.5rem;
972
+ border-left: 3px solid #e0e0e0;
973
+ padding-left: 1.5rem;
974
+ margin-left: -1.5rem;
975
+ }
976
+
977
+ .subsection::before {
978
+ content: '';
979
+ position: absolute;
980
+ left: -1.5rem;
981
+ top: 2rem;
982
+ width: 1rem;
983
+ height: 2px;
984
+ background: #e0e0e0;
985
+ }
986
+
987
+ .subsection h3 {
988
+ font-size: 1.25rem;
989
+ color: #555;
990
+ margin-bottom: 1rem;
991
+ font-weight: 600;
992
+ }
993
+
994
+ .subsection:hover {
995
+ border-left-color: #667eea;
996
+ }
997
+
998
+ .subsection:hover::before {
999
+ background: #667eea;
1000
+ }
1001
+
1002
+ .button-grid {
1003
+ display: grid;
1004
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
1005
+ gap: 0.75rem;
1006
+ margin-bottom: 1rem;
1007
+ }
1008
+
1009
+ .button-grid-small {
1010
+ display: flex;
1011
+ gap: 0.5rem;
1012
+ margin-bottom: 1rem;
1013
+ flex-wrap: wrap;
1014
+ }
1015
+
1016
+ button {
1017
+ padding: 0.875rem 1.5rem;
1018
+ border: none;
1019
+ border-radius: 8px;
1020
+ font-size: 1rem;
1021
+ font-weight: 600;
1022
+ cursor: pointer;
1023
+ transition: all 0.3s;
1024
+ min-width: 150px;
1025
+ width: 100%;
1026
+ }
1027
+
1028
+ button:disabled {
1029
+ opacity: 0.5;
1030
+ cursor: not-allowed;
1031
+ }
1032
+
1033
+ .btn-primary {
1034
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1035
+ color: white;
1036
+ }
1037
+
1038
+ .btn-primary:hover:not(:disabled) {
1039
+ transform: translateY(-2px);
1040
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
1041
+ }
1042
+
1043
+ .btn-secondary {
1044
+ background: #f0f0f0;
1045
+ color: #333;
1046
+ }
1047
+
1048
+ .btn-secondary:hover:not(:disabled) {
1049
+ background: #e0e0e0;
1050
+ }
1051
+
1052
+ .btn-success {
1053
+ background: #4caf50;
1054
+ color: white;
1055
+ }
1056
+
1057
+ .btn-success:hover:not(:disabled) {
1058
+ background: #45a049;
1059
+ box-shadow: 0 5px 15px rgba(76, 175, 80, 0.4);
1060
+ }
1061
+
1062
+ .btn-danger {
1063
+ background: #f44336;
1064
+ color: white;
1065
+ }
1066
+
1067
+ .btn-danger:hover:not(:disabled) {
1068
+ background: #da190b;
1069
+ box-shadow: 0 5px 15px rgba(244, 67, 54, 0.4);
1070
+ }
1071
+
1072
+ .btn-purple {
1073
+ background: #9c27b0;
1074
+ color: white;
1075
+ }
1076
+
1077
+ .btn-purple:hover:not(:disabled) {
1078
+ background: #7b1fa2;
1079
+ box-shadow: 0 5px 15px rgba(156, 39, 176, 0.4);
1080
+ }
1081
+
1082
+ .btn-link {
1083
+ background: transparent;
1084
+ color: #667eea;
1085
+ border: 1px solid #667eea;
1086
+ padding: 0.5rem 1rem;
1087
+ font-size: 0.9rem;
1088
+ }
1089
+
1090
+ .btn-link:hover:not(:disabled) {
1091
+ background: #667eea;
1092
+ color: white;
1093
+ }
1094
+
1095
+ .result {
1096
+ background: #f5f7fa;
1097
+ padding: 1rem;
1098
+ border-radius: 8px;
1099
+ margin-top: 1rem;
1100
+ border-left: 4px solid #667eea;
1101
+ }
1102
+
1103
+ .result.success {
1104
+ background: #d4edda;
1105
+ border-left-color: #4caf50;
1106
+ }
1107
+
1108
+ .result.purple {
1109
+ background: #f3e5f5;
1110
+ border-left-color: #9c27b0;
1111
+ }
1112
+
1113
+ .result pre {
1114
+ margin: 0;
1115
+ font-family: 'Courier New', monospace;
1116
+ font-size: 0.875rem;
1117
+ white-space: pre-wrap;
1118
+ word-break: break-all;
1119
+ }
1120
+
1121
+ .rsa-keys {
1122
+ background: #f3e5f5;
1123
+ padding: 1rem;
1124
+ border-radius: 8px;
1125
+ margin-bottom: 1rem;
1126
+ }
1127
+
1128
+ .key-display {
1129
+ margin-bottom: 1rem;
1130
+ }
1131
+
1132
+ .key-display:last-child {
1133
+ margin-bottom: 0;
1134
+ }
1135
+
1136
+ .key-display strong {
1137
+ display: block;
1138
+ margin-bottom: 0.5rem;
1139
+ color: #7b1fa2;
1140
+ }
1141
+
1142
+ .key-display pre {
1143
+ background: white;
1144
+ padding: 0.75rem;
1145
+ border-radius: 4px;
1146
+ margin: 0;
1147
+ font-size: 0.75rem;
1148
+ overflow-x: auto;
1149
+ }
1150
+
1151
+ .toast {
1152
+ position: fixed;
1153
+ bottom: 2rem;
1154
+ right: 2rem;
1155
+ padding: 1rem 1.5rem;
1156
+ border-radius: 8px;
1157
+ color: white;
1158
+ font-weight: 500;
1159
+ box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
1160
+ animation: slideIn 0.3s ease-out;
1161
+ max-width: 400px;
1162
+ z-index: 1000;
1163
+ }
1164
+
1165
+ .toast.success {
1166
+ background: #4caf50;
1167
+ }
1168
+
1169
+ .toast.error {
1170
+ background: #f44336;
1171
+ }
1172
+
1173
+ .toast.info {
1174
+ background: #2196f3;
1175
+ }
1176
+
1177
+ @keyframes slideIn {
1178
+ from {
1179
+ transform: translateX(400px);
1180
+ opacity: 0;
1181
+ }
1182
+ to {
1183
+ transform: translateX(0);
1184
+ opacity: 1;
1185
+ }
1186
+ }
1187
+
1188
+ @media (max-width: 768px) {
1189
+ .app {
1190
+ padding: 1rem 0.5rem;
1191
+ }
1192
+
1193
+ header h1 {
1194
+ font-size: 2rem;
1195
+ }
1196
+
1197
+ .card {
1198
+ padding: 1.5rem;
1199
+ }
1200
+
1201
+ .button-grid {
1202
+ grid-template-columns: 1fr;
1203
+ }
1204
+
1205
+ .toast {
1206
+ left: 1rem;
1207
+ right: 1rem;
1208
+ bottom: 1rem;
1209
+ }
1210
+ }
1211
+ </style>