@meddleware/token-deployer-ui 0.0.8 → 0.0.10
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/.env.example +8 -11
- package/.github/workflows/e2e-realchain.yml +1 -1
- package/.github/workflows/node-ci.yml +9 -0
- package/.github/workflows/npm-publish.yml +16 -2
- package/.htmlvalidate.mjs +30 -0
- package/CLAUDE.md +14 -7
- package/Dockerfile +1 -1
- package/LICENSE +11 -118
- package/README.md +3 -5
- package/eslint.config.ts +63 -0
- package/index.html +11 -2
- package/package.json +32 -10
- package/postcss.config.js +8 -0
- package/public/og-image.png +0 -0
- package/public/robots.txt +1 -1
- package/public/sitemap.xml +1 -1
- package/scripts/e2e-deploy.mjs +22 -17
- package/scripts/e2e-localnet.mjs +14 -12
- package/scripts/e2e-walrus-browser.mjs +1 -4
- package/scripts/gen-og-image.mjs +2 -2
- package/scripts/publish-localnet.mjs +5 -4
- package/src/App.vue +8 -6
- package/src/component-styles.css +5 -38
- package/src/components/ConfigForm.vue +74 -28
- package/src/components/DeployProgress.vue +2 -4
- package/src/components/GithubPush.vue +22 -7
- package/src/components/IconPicker.vue +103 -42
- package/src/components/LicensePicker.vue +66 -66
- package/src/components/MyTokens.vue +125 -0
- package/src/components/ResultPanel.vue +2 -4
- package/src/components/ReviewPanel.vue +1 -1
- package/src/components/TermsDialog.vue +28 -27
- package/src/components/TokenDeployerView.vue +109 -40
- package/src/components/WalletBar.vue +67 -78
- package/src/components/WalrusBlobBrowser.vue +33 -25
- package/src/composables/useAccessGate.ts +7 -2
- package/src/composables/useWalrusRelay.ts +1 -1
- package/src/config.ts +10 -13
- package/src/lib/accessGate.ts +28 -14
- package/src/lib/buildPublishTx.ts +1 -1
- package/src/lib/deploy.ts +36 -0
- package/src/lib/deployExecutor.ts +14 -12
- package/src/lib/generatePackage.ts +18 -13
- package/src/lib/licenses.ts +4 -3
- package/src/lib/listMyTokens.ts +41 -0
- package/src/lib/readClient.ts +15 -7
- package/src/lib/types.ts +1 -1
- package/src/lib/validation.ts +8 -4
- package/src/lib/walrus-constants.ts +1 -1
- package/src/lib/walrus.ts +22 -11
- package/src/main.ts +46 -58
- package/src/styles.css +13 -5
- package/src/template-src/files.json +7 -7
- package/stylelint.config.js +33 -0
- package/test-results/.last-run.json +1 -1
- package/tests/accessGate.test.ts +26 -21
- package/tests/deploy.test.ts +70 -2
- package/tests/generatePackage.test.ts +11 -0
- package/tests/licenses.test.ts +6 -3
- package/tests/listMyTokens.test.ts +30 -0
- package/tests/useAccessGate.test.ts +15 -19
- package/typedoc.json +2 -2
- package/vite.config.ts +9 -1
- package/src/components/WalrusIconUpload.vue +0 -205
|
@@ -116,11 +116,16 @@ function onFile(e: Event): void {
|
|
|
116
116
|
const f = (e.target as HTMLInputElement).files?.[0]
|
|
117
117
|
if (!f) return
|
|
118
118
|
const invalid = validateIconFile(f)
|
|
119
|
-
if (invalid) {
|
|
119
|
+
if (invalid) {
|
|
120
|
+
uploadError.value = invalid
|
|
121
|
+
return
|
|
122
|
+
}
|
|
120
123
|
fileName.value = f.name
|
|
121
124
|
fileSizeBytes.value = f.size
|
|
122
125
|
const reader = new FileReader()
|
|
123
|
-
reader.onload = () => {
|
|
126
|
+
reader.onload = () => {
|
|
127
|
+
bytes = new Uint8Array(reader.result as ArrayBuffer)
|
|
128
|
+
}
|
|
124
129
|
reader.readAsArrayBuffer(f)
|
|
125
130
|
}
|
|
126
131
|
|
|
@@ -129,14 +134,19 @@ function onRelayChange(newHost: string): void {
|
|
|
129
134
|
}
|
|
130
135
|
|
|
131
136
|
async function upload(): Promise<void> {
|
|
132
|
-
if (!bytes) {
|
|
133
|
-
|
|
137
|
+
if (!bytes) {
|
|
138
|
+
uploadError.value = 'Choose an image first.'
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
if (!account.value) {
|
|
142
|
+
uploadError.value = 'Connect your wallet first.'
|
|
143
|
+
return
|
|
144
|
+
}
|
|
134
145
|
uploading.value = true
|
|
135
146
|
uploadError.value = null
|
|
136
147
|
try {
|
|
137
|
-
const { createWalrusClient, createBlobUploadFlow, walrusBlobUrl } =
|
|
138
|
-
'../lib/walrus.js'
|
|
139
|
-
)
|
|
148
|
+
const { createWalrusClient, createBlobUploadFlow, walrusBlobUrl } =
|
|
149
|
+
await import('../lib/walrus.js')
|
|
140
150
|
const executor = await buildDeployExecutor(props.network as Network)
|
|
141
151
|
const suiClient = getReadClient(props.network as Network)
|
|
142
152
|
|
|
@@ -232,11 +242,21 @@ function onBrowseSelect(url: string): void {
|
|
|
232
242
|
v-model="tab"
|
|
233
243
|
:options="[
|
|
234
244
|
{ id: 'url', label: 'URL' },
|
|
235
|
-
{
|
|
236
|
-
|
|
245
|
+
{
|
|
246
|
+
id: 'upload',
|
|
247
|
+
label: 'Upload to Walrus',
|
|
248
|
+
disabled: !isWalrusNetwork,
|
|
249
|
+
title: isWalrusNetwork ? undefined : 'Not available on localnet',
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
id: 'browse',
|
|
253
|
+
label: 'My Walrus blobs',
|
|
254
|
+
disabled: !isWalrusNetwork,
|
|
255
|
+
title: isWalrusNetwork ? undefined : 'Not available on localnet',
|
|
256
|
+
},
|
|
237
257
|
]"
|
|
238
258
|
aria-label="Icon source"
|
|
239
|
-
|
|
259
|
+
class="icon-source"
|
|
240
260
|
/>
|
|
241
261
|
|
|
242
262
|
<!-- URL panel -->
|
|
@@ -263,7 +283,7 @@ function onBrowseSelect(url: string): void {
|
|
|
263
283
|
accept="image/*"
|
|
264
284
|
aria-label="Choose an image to upload to Walrus"
|
|
265
285
|
aria-describedby="walrus-help"
|
|
266
|
-
|
|
286
|
+
class="icon-file"
|
|
267
287
|
@change="onFile"
|
|
268
288
|
/>
|
|
269
289
|
<button type="button" :disabled="uploading || !connected || !fileName" @click="upload">
|
|
@@ -272,17 +292,20 @@ function onBrowseSelect(url: string): void {
|
|
|
272
292
|
</button>
|
|
273
293
|
</div>
|
|
274
294
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
295
|
+
<!-- The help button sits beside the label, not inside it (a label may only contain its control). -->
|
|
296
|
+
<p class="permanent-toggle">
|
|
297
|
+
<label>
|
|
298
|
+
<input v-model="permanent" type="checkbox" />
|
|
299
|
+
Permanent blob <span class="hint">(cannot be deleted)</span>
|
|
300
|
+
</label>
|
|
278
301
|
<UiFieldHint field-id="blob-permanent">
|
|
279
302
|
A permanent blob cannot be deleted once registered, even within its storage period.
|
|
280
303
|
Deletable blobs can be removed early by the owner to reclaim unused storage fees (WAL).
|
|
281
304
|
Permanent is the safer default for a token icon that should remain available indefinitely.
|
|
282
305
|
</UiFieldHint>
|
|
283
|
-
</
|
|
306
|
+
</p>
|
|
284
307
|
|
|
285
|
-
<fieldset v-if="fileName && availableRelays.length > 1"
|
|
308
|
+
<fieldset v-if="fileName && availableRelays.length > 1" class="relay-choice">
|
|
286
309
|
<legend>
|
|
287
310
|
Upload relay
|
|
288
311
|
<UiFieldHint field-id="upload-relay">
|
|
@@ -292,47 +315,47 @@ function onBrowseSelect(url: string): void {
|
|
|
292
315
|
prefer.
|
|
293
316
|
</UiFieldHint>
|
|
294
317
|
</legend>
|
|
295
|
-
<
|
|
296
|
-
<
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
@change="(e) => onRelayChange((e.target as HTMLInputElement).value)"
|
|
306
|
-
/>
|
|
307
|
-
<span>{{ option.label }}</span>
|
|
308
|
-
</label>
|
|
309
|
-
</div>
|
|
318
|
+
<label v-for="option in availableRelays" :key="option.host">
|
|
319
|
+
<input
|
|
320
|
+
type="radio"
|
|
321
|
+
name="upload-relay"
|
|
322
|
+
:value="option.host"
|
|
323
|
+
:checked="selectedRelayHost === option.host"
|
|
324
|
+
@change="(e) => onRelayChange((e.target as HTMLInputElement).value)"
|
|
325
|
+
/>
|
|
326
|
+
<span>{{ option.label }}</span>
|
|
327
|
+
</label>
|
|
310
328
|
</fieldset>
|
|
311
329
|
|
|
312
|
-
<UiNotice v-if="fileName && estimatedCost"
|
|
330
|
+
<UiNotice v-if="fileName && estimatedCost" class="icon-notice">
|
|
313
331
|
<strong>Estimated cost:</strong> {{ estimatedCost.label }}
|
|
314
332
|
</UiNotice>
|
|
315
333
|
|
|
316
334
|
<!-- NFT-gated relay: the operator relay is live but this wallet lacks the access NFT.
|
|
317
335
|
The public relay is still available; purchasing unlocks the operator relay. -->
|
|
318
|
-
<UiNotice v-if="fileName && purchaseAccessAvailable"
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
336
|
+
<UiNotice v-if="fileName && purchaseAccessAvailable" class="icon-notice">
|
|
337
|
+
The operator relay requires an access NFT. You can keep using the public relay, or purchase
|
|
338
|
+
access to support this app’s relay.
|
|
339
|
+
<button
|
|
340
|
+
type="button"
|
|
341
|
+
class="icon-notice__cta"
|
|
342
|
+
:disabled="purchasing || !connected"
|
|
343
|
+
@click="purchaseAccess"
|
|
344
|
+
>
|
|
324
345
|
{{ purchasing ? 'Purchasing…' : 'Purchase relay access' }}
|
|
325
346
|
</button>
|
|
326
347
|
</UiNotice>
|
|
327
348
|
|
|
328
|
-
<p v-if="showFreeRelayTip" class="hint
|
|
349
|
+
<p v-if="showFreeRelayTip" class="hint icon-notice">
|
|
329
350
|
Tip: select the public relay above to avoid the relay fee.
|
|
330
351
|
</p>
|
|
331
352
|
|
|
332
353
|
<p id="walrus-help" class="upload-note" aria-live="polite">
|
|
333
354
|
Max {{ maxSizeLabel }} · PNG/JPEG/WebP/SVG. Requires <strong>WAL</strong> (storage) and
|
|
334
|
-
<strong>SUI</strong> (gas + relay fee) in your wallet; two approvals. Stored for
|
|
335
|
-
|
|
355
|
+
<strong>SUI</strong> (gas + relay fee) in your wallet; two approvals. Stored for ~{{
|
|
356
|
+
ICON_YEARS
|
|
357
|
+
}}
|
|
358
|
+
years. <span v-if="status">{{ status }}</span>
|
|
336
359
|
</p>
|
|
337
360
|
<UiNotice v-if="uploadError" type="error">{{ uploadError }}</UiNotice>
|
|
338
361
|
</div>
|
|
@@ -345,6 +368,37 @@ function onBrowseSelect(url: string): void {
|
|
|
345
368
|
</template>
|
|
346
369
|
|
|
347
370
|
<style scoped>
|
|
371
|
+
.icon-source {
|
|
372
|
+
margin-bottom: 0.75rem;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.icon-file {
|
|
376
|
+
width: auto;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.relay-choice {
|
|
380
|
+
display: flex;
|
|
381
|
+
flex-direction: column;
|
|
382
|
+
gap: 0.5rem;
|
|
383
|
+
margin: 0.5rem 0;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.relay-choice label {
|
|
387
|
+
display: flex;
|
|
388
|
+
align-items: center;
|
|
389
|
+
gap: 0.5rem;
|
|
390
|
+
margin: 0;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.icon-notice {
|
|
394
|
+
margin: 0.5rem 0;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.icon-notice__cta {
|
|
398
|
+
display: block;
|
|
399
|
+
margin-top: 0.5rem;
|
|
400
|
+
}
|
|
401
|
+
|
|
348
402
|
.icon-panel {
|
|
349
403
|
padding-top: 0.5rem;
|
|
350
404
|
}
|
|
@@ -355,8 +409,15 @@ function onBrowseSelect(url: string): void {
|
|
|
355
409
|
gap: 0.4rem;
|
|
356
410
|
/* Only span the content — otherwise the row stretches the full field width. */
|
|
357
411
|
width: fit-content;
|
|
358
|
-
margin
|
|
412
|
+
margin: 0.5rem 0 0;
|
|
359
413
|
font-size: 0.875rem;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.permanent-toggle label {
|
|
417
|
+
display: inline-flex;
|
|
418
|
+
align-items: center;
|
|
419
|
+
gap: 0.4rem;
|
|
420
|
+
margin: 0;
|
|
360
421
|
cursor: pointer;
|
|
361
422
|
}
|
|
362
423
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { computed, onMounted, ref } from 'vue'
|
|
3
3
|
import { fetchLicenseList, fetchLicenseText, NO_LICENSE } from '../lib/licenses.js'
|
|
4
4
|
import type { SpdxLicense } from '../lib/licenses.js'
|
|
5
|
-
import {
|
|
5
|
+
import { UiDialog, UiFieldHint, UiFormField, UiNotice, UiSelect } from '@meddleware/ui'
|
|
6
6
|
|
|
7
7
|
const props = defineProps<{ modelValue: string }>()
|
|
8
8
|
const emit = defineEmits<{
|
|
@@ -46,7 +46,7 @@ onMounted(async () => {
|
|
|
46
46
|
})
|
|
47
47
|
|
|
48
48
|
// License view modal
|
|
49
|
-
const
|
|
49
|
+
const licenseOpen = ref(false)
|
|
50
50
|
const licenseText = ref<string | null>(null)
|
|
51
51
|
const licenseLoading = ref(false)
|
|
52
52
|
const licenseError = ref<string | null>(null)
|
|
@@ -63,86 +63,86 @@ async function openLicenseModal(): Promise<void> {
|
|
|
63
63
|
licenseLoading.value = false
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
licenseOpen.value = true
|
|
67
67
|
}
|
|
68
68
|
</script>
|
|
69
69
|
|
|
70
70
|
<template>
|
|
71
|
-
<
|
|
72
|
-
<label
|
|
73
|
-
License
|
|
71
|
+
<UiFormField id="license-select" label="License">
|
|
72
|
+
<template #label-suffix>
|
|
74
73
|
<UiFieldHint field-id="license-select">
|
|
75
74
|
Applies only to the downloadable Move source package — embedded in the LICENSE file and
|
|
76
|
-
source headers. Has <b>no effect on the on-chain token</b> or its transferability.
|
|
77
|
-
|
|
75
|
+
source headers. Has <b>no effect on the on-chain token</b> or its transferability. Choose
|
|
76
|
+
<b>0BSD</b> (default) for a minimal permissive licence, or <b>CC0-1.0</b> for public domain.
|
|
77
|
+
Choose <b>None</b> to retain all rights.
|
|
78
78
|
</UiFieldHint>
|
|
79
|
-
</
|
|
80
|
-
<
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
79
|
+
</template>
|
|
80
|
+
<template #default="{ attrs }">
|
|
81
|
+
<span class="license-row">
|
|
82
|
+
<UiSelect
|
|
83
|
+
v-bind="attrs"
|
|
84
|
+
v-model="selectModel"
|
|
85
|
+
:disabled="loading || Boolean(error)"
|
|
86
|
+
aria-describedby="license-load-status"
|
|
87
|
+
>
|
|
88
|
+
<option :value="NO_LICENSE.id" :title="NO_LICENSE.name">{{ NO_LICENSE.id }}</option>
|
|
89
|
+
<optgroup v-if="popular.length" label="Popular">
|
|
90
|
+
<option
|
|
91
|
+
v-for="l in popular"
|
|
92
|
+
:key="'p-' + l.id"
|
|
93
|
+
:value="l.id"
|
|
94
|
+
:title="`${l.id} — ${l.name}`"
|
|
95
|
+
>
|
|
96
|
+
{{ l.id }}
|
|
97
|
+
</option>
|
|
98
|
+
</optgroup>
|
|
99
|
+
<optgroup v-if="all.length" label="All licenses">
|
|
100
|
+
<option
|
|
101
|
+
v-for="l in allExceptNone"
|
|
102
|
+
:key="l.id"
|
|
103
|
+
:value="l.id"
|
|
104
|
+
:title="`${l.id} — ${l.name}`"
|
|
105
|
+
>
|
|
106
|
+
{{ l.id }}
|
|
107
|
+
</option>
|
|
108
|
+
</optgroup>
|
|
109
|
+
</UiSelect>
|
|
110
|
+
<button type="button" class="view-btn" :disabled="loading" @click="openLicenseModal">
|
|
111
|
+
View
|
|
112
|
+
</button>
|
|
113
|
+
</span>
|
|
114
|
+
</template>
|
|
115
|
+
</UiFormField>
|
|
116
|
+
<p id="license-load-status" class="hint" aria-live="polite">
|
|
117
|
+
<span v-if="loading"
|
|
118
|
+
><span class="spinner" aria-hidden="true"></span> Loading licenses from SPDX…</span
|
|
119
|
+
>
|
|
120
|
+
<span v-else-if="error" class="field-error">Couldn't load the license list: {{ error }}</span>
|
|
121
|
+
</p>
|
|
114
122
|
|
|
115
|
-
<
|
|
116
|
-
<
|
|
117
|
-
<h2>{{ currentLicenseName }}</h2>
|
|
118
|
-
<button
|
|
119
|
-
type="button"
|
|
120
|
-
aria-label="Close"
|
|
121
|
-
style="background: none; border: none; font-size: 1.5rem; padding: 0; cursor: pointer; color: var(--text)"
|
|
122
|
-
@click="licenseDialogEl?.close()"
|
|
123
|
-
>×</button>
|
|
124
|
-
</div>
|
|
125
|
-
<div v-if="licenseLoading" style="padding: 1rem 0">
|
|
123
|
+
<UiDialog v-model:open="licenseOpen" :title="currentLicenseName" width="min(680px, 92vw)">
|
|
124
|
+
<p v-if="licenseLoading" role="status">
|
|
126
125
|
<span class="spinner" aria-hidden="true"></span> Loading…
|
|
127
|
-
</
|
|
126
|
+
</p>
|
|
128
127
|
<UiNotice v-else-if="licenseError" type="error">{{ licenseError }}</UiNotice>
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
</
|
|
128
|
+
<p v-else-if="modelValue === NO_LICENSE.id">
|
|
129
|
+
No LICENSE file will be generated. All rights are reserved by the author.
|
|
130
|
+
</p>
|
|
132
131
|
<template v-else>
|
|
133
|
-
<p
|
|
132
|
+
<p class="license-spdx">
|
|
134
133
|
<a
|
|
135
134
|
:href="`https://spdx.org/licenses/${modelValue}.html`"
|
|
136
135
|
target="_blank"
|
|
137
136
|
rel="noopener noreferrer"
|
|
138
|
-
|
|
137
|
+
>View on SPDX ↗</a
|
|
138
|
+
>
|
|
139
139
|
</p>
|
|
140
140
|
<pre class="license-text">{{ licenseText }}</pre>
|
|
141
141
|
</template>
|
|
142
|
-
<
|
|
143
|
-
<button type="button" @click="
|
|
144
|
-
</
|
|
145
|
-
</
|
|
142
|
+
<template #actions="{ close }">
|
|
143
|
+
<button type="button" @click="close()">Close</button>
|
|
144
|
+
</template>
|
|
145
|
+
</UiDialog>
|
|
146
146
|
</template>
|
|
147
147
|
|
|
148
148
|
<style scoped>
|
|
@@ -161,8 +161,8 @@ async function openLicenseModal(): Promise<void> {
|
|
|
161
161
|
white-space: nowrap;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
.license-
|
|
165
|
-
|
|
164
|
+
.license-spdx {
|
|
165
|
+
margin: 0;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
.license-text {
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Lists the coins the connected wallet has deployed (owned TreasuryCap<T> objects). Read-only,
|
|
3
|
+
// mirroring access-gate's "My Gates". Loads on mount + whenever the connected address changes.
|
|
4
|
+
import { computed, ref, watch } from 'vue'
|
|
5
|
+
import {
|
|
6
|
+
CopyableAddress,
|
|
7
|
+
ExplorerLink,
|
|
8
|
+
UiNotice,
|
|
9
|
+
suiExplorerUrl,
|
|
10
|
+
type SuiNetwork,
|
|
11
|
+
} from '@meddleware/ui'
|
|
12
|
+
import { listMyTokens, type DeployedToken } from '../lib/listMyTokens.js'
|
|
13
|
+
import type { Network } from '../lib/types.js'
|
|
14
|
+
|
|
15
|
+
const props = defineProps<{ owner: string | null; network: Network }>()
|
|
16
|
+
|
|
17
|
+
// SuiVision has no localnet explorer, so only build object links on public networks.
|
|
18
|
+
const explorerNetwork = computed<SuiNetwork | null>(() =>
|
|
19
|
+
props.network === 'localnet' ? null : props.network,
|
|
20
|
+
)
|
|
21
|
+
function objectHref(id: string): string | null {
|
|
22
|
+
return explorerNetwork.value ? suiExplorerUrl('object', id, explorerNetwork.value) : null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const tokens = ref<DeployedToken[]>([])
|
|
26
|
+
const loading = ref(false)
|
|
27
|
+
const error = ref<string | null>(null)
|
|
28
|
+
|
|
29
|
+
async function load(): Promise<void> {
|
|
30
|
+
if (!props.owner) {
|
|
31
|
+
tokens.value = []
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
loading.value = true
|
|
35
|
+
error.value = null
|
|
36
|
+
try {
|
|
37
|
+
tokens.value = await listMyTokens(props.owner, props.network)
|
|
38
|
+
} catch (e) {
|
|
39
|
+
error.value = e instanceof Error ? e.message : String(e)
|
|
40
|
+
tokens.value = []
|
|
41
|
+
} finally {
|
|
42
|
+
loading.value = false
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
watch(() => props.owner, load, { immediate: true })
|
|
47
|
+
defineExpose({ reload: load })
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<template>
|
|
51
|
+
<div class="my-tokens">
|
|
52
|
+
<p v-if="loading" class="hint">Loading your deployed tokens…</p>
|
|
53
|
+
<UiNotice v-else-if="error" type="error">{{ error }}</UiNotice>
|
|
54
|
+
<p v-else-if="!tokens.length" class="hint">
|
|
55
|
+
No deployed tokens found for this wallet. Deploy one from the <strong>Deploy</strong> tab.
|
|
56
|
+
</p>
|
|
57
|
+
<ul v-else class="my-tokens__list">
|
|
58
|
+
<li v-for="t in tokens" :key="t.treasuryCapId" class="my-tokens__item">
|
|
59
|
+
<div class="my-tokens__label">{{ t.label }}</div>
|
|
60
|
+
<dl class="my-tokens__meta">
|
|
61
|
+
<dt>Coin type</dt>
|
|
62
|
+
<dd><CopyableAddress :address="t.coinType" label="Copy coin type" /></dd>
|
|
63
|
+
<dt>Package</dt>
|
|
64
|
+
<dd>
|
|
65
|
+
<CopyableAddress :address="t.packageId" label="Copy package id">
|
|
66
|
+
<ExplorerLink
|
|
67
|
+
v-if="objectHref(t.packageId)"
|
|
68
|
+
:href="objectHref(t.packageId)!"
|
|
69
|
+
:value="t.packageId"
|
|
70
|
+
/>
|
|
71
|
+
</CopyableAddress>
|
|
72
|
+
</dd>
|
|
73
|
+
<dt>Treasury cap</dt>
|
|
74
|
+
<dd>
|
|
75
|
+
<CopyableAddress :address="t.treasuryCapId" label="Copy treasury cap id">
|
|
76
|
+
<ExplorerLink
|
|
77
|
+
v-if="objectHref(t.treasuryCapId)"
|
|
78
|
+
:href="objectHref(t.treasuryCapId)!"
|
|
79
|
+
:value="t.treasuryCapId"
|
|
80
|
+
/>
|
|
81
|
+
</CopyableAddress>
|
|
82
|
+
</dd>
|
|
83
|
+
</dl>
|
|
84
|
+
</li>
|
|
85
|
+
</ul>
|
|
86
|
+
</div>
|
|
87
|
+
</template>
|
|
88
|
+
|
|
89
|
+
<style scoped>
|
|
90
|
+
.my-tokens__list {
|
|
91
|
+
list-style: none;
|
|
92
|
+
margin: 0;
|
|
93
|
+
padding: 0;
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
gap: 0.75rem;
|
|
97
|
+
}
|
|
98
|
+
.my-tokens__item {
|
|
99
|
+
border: 1px solid var(--border);
|
|
100
|
+
border-radius: var(--radius);
|
|
101
|
+
padding: 0.75rem 0.9rem;
|
|
102
|
+
background: var(--surface);
|
|
103
|
+
}
|
|
104
|
+
.my-tokens__label {
|
|
105
|
+
font-weight: 600;
|
|
106
|
+
margin-bottom: 0.4rem;
|
|
107
|
+
}
|
|
108
|
+
.my-tokens__meta {
|
|
109
|
+
display: grid;
|
|
110
|
+
grid-template-columns: auto 1fr;
|
|
111
|
+
gap: 0.2rem 0.75rem;
|
|
112
|
+
margin: 0;
|
|
113
|
+
font-size: 0.8rem;
|
|
114
|
+
}
|
|
115
|
+
.my-tokens__meta dt {
|
|
116
|
+
color: var(--muted);
|
|
117
|
+
white-space: nowrap;
|
|
118
|
+
}
|
|
119
|
+
.my-tokens__meta dd {
|
|
120
|
+
margin: 0;
|
|
121
|
+
min-width: 0;
|
|
122
|
+
font-family: var(--mw-font-mono);
|
|
123
|
+
font-size: 0.72rem;
|
|
124
|
+
}
|
|
125
|
+
</style>
|
|
@@ -72,11 +72,9 @@ async function downloadPackage(): Promise<void> {
|
|
|
72
72
|
</div>
|
|
73
73
|
<UiNotice v-if="downloadError" type="error">{{ downloadError }}</UiNotice>
|
|
74
74
|
|
|
75
|
-
<
|
|
76
|
-
<GithubPush :config="config" :result="result" />
|
|
77
|
-
</div>
|
|
75
|
+
<GithubPush class="actions" :config="config" :result="result" />
|
|
78
76
|
|
|
79
|
-
<p v-once class="hint
|
|
77
|
+
<p v-once class="hint actions">
|
|
80
78
|
The download is the full, buildable Move package (matching this on-chain deployment) — your
|
|
81
79
|
TreasuryCap grants minting rights; keep it safe. This tool takes no custody of your token.
|
|
82
80
|
</p>
|
|
@@ -61,7 +61,7 @@ const feeSui = Number(FEE_MIST) / 1e9
|
|
|
61
61
|
token or caps.
|
|
62
62
|
</p>
|
|
63
63
|
|
|
64
|
-
<div class="row
|
|
64
|
+
<div class="row actions">
|
|
65
65
|
<button type="button" @click="$emit('back')">Back</button>
|
|
66
66
|
<button type="button" class="primary" @click="$emit('confirm')">Confirm & deploy</button>
|
|
67
67
|
</div>
|
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
2
|
+
// Pre-deploy terms, in the shared UiDialog. Closing by any route (Cancel, Escape, backdrop,
|
|
3
|
+
// close button) counts as cancel; only the "continue" action accepts — UiDialog's return value
|
|
4
|
+
// tells them apart.
|
|
5
|
+
import { UiDialog } from '@meddleware/ui'
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
defineProps<{ open: boolean }>()
|
|
5
8
|
const emit = defineEmits<{
|
|
6
9
|
(e: 'accept'): void
|
|
7
10
|
(e: 'cancel'): void
|
|
8
11
|
}>()
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
(v) => (v ? el.value?.showModal() : el.value?.close()),
|
|
15
|
-
)
|
|
13
|
+
function onClose(returnValue: string): void {
|
|
14
|
+
if (returnValue === 'accept') emit('accept')
|
|
15
|
+
else emit('cancel')
|
|
16
|
+
}
|
|
16
17
|
</script>
|
|
17
18
|
|
|
18
19
|
<template>
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
</div>
|
|
23
|
-
<ul>
|
|
20
|
+
<UiDialog :open="open" title="Before you deploy" @close="onClose">
|
|
21
|
+
<!-- A list: each item is a discrete, parallel term (announced with an item count). -->
|
|
22
|
+
<ul class="terms">
|
|
24
23
|
<li>
|
|
25
24
|
All operations — bytecode compilation, transaction construction, and signing — take place
|
|
26
25
|
entirely <strong>client-side</strong>, meaning on your computer. No code runs on our
|
|
@@ -48,34 +47,36 @@ watch(
|
|
|
48
47
|
arising from your use of this tool.
|
|
49
48
|
</li>
|
|
50
49
|
</ul>
|
|
51
|
-
<p
|
|
50
|
+
<p class="terms__confirm">
|
|
52
51
|
By continuing, you confirm that you have read and accept these terms.
|
|
53
52
|
</p>
|
|
54
|
-
<
|
|
55
|
-
<button type="button" class="primary" @click="
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
<template #actions="{ close }">
|
|
54
|
+
<button type="button" class="primary" @click="close('accept')">
|
|
55
|
+
I understand — continue
|
|
56
|
+
</button>
|
|
57
|
+
<button type="button" @click="close('cancel')">Cancel</button>
|
|
58
|
+
</template>
|
|
59
|
+
</UiDialog>
|
|
59
60
|
</template>
|
|
60
61
|
|
|
61
62
|
<style scoped>
|
|
62
|
-
|
|
63
|
+
.terms {
|
|
63
64
|
padding-left: 1.25rem;
|
|
64
|
-
margin: 0
|
|
65
|
+
margin: 0;
|
|
65
66
|
line-height: 1.6;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
.terms li {
|
|
69
70
|
margin-bottom: 0.6rem;
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
|
|
73
|
+
.terms li:last-child {
|
|
73
74
|
margin-bottom: 0;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
.terms__confirm {
|
|
78
|
+
margin: 0;
|
|
79
|
+
font-size: 0.9rem;
|
|
80
|
+
color: var(--muted);
|
|
80
81
|
}
|
|
81
82
|
</style>
|