@beclab/olaresid 0.1.5 → 0.1.6

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