@beclab/olaresid 0.1.4 → 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.
- package/dist/business/index.d.ts +3 -3
- package/dist/business/index.d.ts.map +1 -1
- package/dist/business/index.js +49 -64
- package/dist/business/index.js.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/crypto-utils.d.ts +32 -4
- package/dist/utils/crypto-utils.d.ts.map +1 -1
- package/dist/utils/crypto-utils.js +117 -30
- package/dist/utils/crypto-utils.js.map +1 -1
- package/examples/crypto-utilities.ts +3 -3
- package/examples/ed25519-jwk.ts +1 -1
- package/examples/encoding-utils.ts +96 -0
- package/examples/frontend-demo/.dockerignore +40 -0
- package/examples/frontend-demo/index.html +13 -0
- package/examples/frontend-demo/package-lock.json +5304 -0
- package/examples/frontend-demo/package.json +32 -0
- package/examples/frontend-demo/src/App.vue +1156 -0
- package/examples/frontend-demo/src/main.ts +5 -0
- package/examples/frontend-demo/src/style.css +323 -0
- package/examples/frontend-demo/tsconfig.json +24 -0
- package/examples/frontend-demo/webpack.config.js +86 -0
- package/examples/generate-mnemonic.ts +3 -3
- package/examples/register-subdomain.ts +4 -3
- package/examples/transfer-domain.ts +1 -1
- package/examples/wallet-management.ts +8 -8
- package/package.json +1 -3
- package/src/business/index.ts +46 -58
- package/src/cli.ts +3 -3
- package/src/index.ts +6 -1
- package/src/utils/crypto-utils.ts +134 -32
- package/examples/quasar-demo/.eslintrc.js +0 -23
- package/examples/quasar-demo/.quasar/app.js +0 -43
- package/examples/quasar-demo/.quasar/client-entry.js +0 -38
- package/examples/quasar-demo/.quasar/client-prefetch.js +0 -130
- package/examples/quasar-demo/.quasar/quasar-user-options.js +0 -16
- package/examples/quasar-demo/README.md +0 -49
- package/examples/quasar-demo/index.html +0 -11
- package/examples/quasar-demo/package-lock.json +0 -6407
- package/examples/quasar-demo/package.json +0 -36
- package/examples/quasar-demo/quasar.config.js +0 -73
- package/examples/quasar-demo/src/App.vue +0 -13
- package/examples/quasar-demo/src/css/app.scss +0 -1
- package/examples/quasar-demo/src/layouts/MainLayout.vue +0 -21
- package/examples/quasar-demo/src/pages/IndexPage.vue +0 -905
- package/examples/quasar-demo/src/router/index.ts +0 -25
- package/examples/quasar-demo/src/router/routes.ts +0 -11
- 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>
|