@meddleware/token-deployer-ui 0.0.8 → 0.0.9
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/.github/workflows/node-ci.yml +9 -0
- package/.github/workflows/npm-publish.yml +16 -2
- package/.htmlvalidate.mjs +30 -0
- package/CLAUDE.md +1 -1
- package/eslint.config.ts +63 -0
- package/package.json +31 -10
- package/postcss.config.js +8 -0
- 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 +65 -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 +6 -1
- package/src/composables/useWalrusRelay.ts +1 -1
- package/src/lib/buildPublishTx.ts +1 -1
- package/src/lib/generatePackage.ts +6 -1
- package/src/lib/listMyTokens.ts +45 -0
- package/src/lib/validation.ts +8 -4
- package/src/lib/walrus-constants.ts +1 -1
- package/src/main.ts +8 -3
- package/src/styles.css +13 -5
- package/stylelint.config.js +33 -0
- package/test-results/.last-run.json +1 -1
- package/typedoc.json +2 -2
- package/src/components/WalrusIconUpload.vue +0 -205
package/src/styles.css
CHANGED
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
Token variables come from @meddleware/design-tokens, imported before this.
|
|
3
3
|
Component utility classes live in component-styles.css. */
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
justify-
|
|
5
|
+
/* The colour-mode control is the page header's first grid item, aligned to the inline end. */
|
|
6
|
+
.theme-toggle {
|
|
7
|
+
justify-self: end;
|
|
8
8
|
margin-bottom: 0.75rem;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
.page-footer {
|
|
12
|
+
margin-top: 3rem;
|
|
13
|
+
color: var(--muted);
|
|
14
|
+
font-size: 0.9rem;
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
body {
|
|
12
18
|
margin: 0;
|
|
13
19
|
background: var(--bg);
|
|
@@ -21,12 +27,14 @@ body {
|
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
main {
|
|
24
|
-
max-width:
|
|
30
|
+
max-width: var(--mw-tool-content-max);
|
|
25
31
|
margin: 0 auto;
|
|
26
32
|
padding: 1.5rem 1.25rem 4rem;
|
|
27
33
|
}
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
/* A single-column grid so the colour-mode control can sit at the inline end without a wrapper. */
|
|
36
|
+
.page-header {
|
|
37
|
+
display: grid;
|
|
30
38
|
padding-bottom: 1.5rem;
|
|
31
39
|
border-bottom: 1px solid var(--border);
|
|
32
40
|
margin-bottom: 1.5rem;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Stylelint over the library's CSS and the <style> blocks of its SFCs.
|
|
2
|
+
// postcss-html provides the custom syntax needed to lint Vue single-file components.
|
|
3
|
+
export default {
|
|
4
|
+
extends: ['stylelint-config-standard'],
|
|
5
|
+
overrides: [{ files: ['**/*.vue'], customSyntax: 'postcss-html' }],
|
|
6
|
+
rules: {
|
|
7
|
+
// The design system leans heavily on custom properties and deliberate
|
|
8
|
+
// ordering; these standard rules are too noisy for a token-driven codebase.
|
|
9
|
+
'custom-property-empty-line-before': null,
|
|
10
|
+
'declaration-empty-line-before': null,
|
|
11
|
+
'comment-empty-line-before': null,
|
|
12
|
+
'rule-empty-line-before': null,
|
|
13
|
+
'at-rule-empty-line-before': null,
|
|
14
|
+
'no-descending-specificity': null,
|
|
15
|
+
// BEM-style class names (block__element--modifier) are intentional.
|
|
16
|
+
'selector-class-pattern': null,
|
|
17
|
+
// Some presentational SFCs use compact single-line multi-declaration rules.
|
|
18
|
+
'declaration-block-single-line-max-declarations': null,
|
|
19
|
+
// Preserve camelCase CSS keyword values (e.g. text-rendering: optimizeLegibility).
|
|
20
|
+
'value-keyword-case': null,
|
|
21
|
+
// Vue SFC scoped-style pseudo-classes.
|
|
22
|
+
'selector-pseudo-class-no-unknown': [
|
|
23
|
+
true,
|
|
24
|
+
{ ignorePseudoClasses: ['deep', 'slotted', 'global'] },
|
|
25
|
+
],
|
|
26
|
+
// Private panel vars use a leading-underscore convention (--_bg, --_text).
|
|
27
|
+
'custom-property-pattern': null,
|
|
28
|
+
// Vendor-prefixed properties are emitted by autoprefixer at build time; the
|
|
29
|
+
// hand-written -webkit-/-moz- font-smoothing pair is intentional.
|
|
30
|
+
'property-no-vendor-prefix': null,
|
|
31
|
+
'value-no-vendor-prefix': null,
|
|
32
|
+
},
|
|
33
|
+
}
|
package/typedoc.json
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"src/lib/github.ts",
|
|
14
14
|
"src/lib/walrus.ts",
|
|
15
15
|
"src/lib/walrus-constants.ts",
|
|
16
|
-
"src/composables/
|
|
17
|
-
"src/composables/
|
|
16
|
+
"src/composables/useAccessGate.ts",
|
|
17
|
+
"src/composables/useWalrusRelay.ts"
|
|
18
18
|
],
|
|
19
19
|
"out": "docs/api",
|
|
20
20
|
"readme": "none",
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { ref, onMounted, computed } from 'vue'
|
|
3
|
-
import { useWallet } from '@meddleware/wallet-adapter'
|
|
4
|
-
import { buildDeployExecutor } from '../lib/deployExecutor.js'
|
|
5
|
-
import { useWalrusRelay } from '../composables/useWalrusRelay.js'
|
|
6
|
-
import { ICON_EPOCHS } from '../lib/walrus-constants.js'
|
|
7
|
-
import { ICON_MAX_BYTES, validateIconFile } from '../config.js'
|
|
8
|
-
import type { WalrusNetwork } from '../lib/walrus.js'
|
|
9
|
-
import type { Network } from '../lib/types.js'
|
|
10
|
-
import { UiNotice } from '@meddleware/ui'
|
|
11
|
-
// Vite serves the Walrus wasm as an asset URL (lightweight import — just the URL).
|
|
12
|
-
import walrusWasmUrl from '@mysten/walrus-wasm/web/walrus_wasm_bg.wasm?url'
|
|
13
|
-
|
|
14
|
-
const props = defineProps<{ network: WalrusNetwork; connected: boolean }>()
|
|
15
|
-
const emit = defineEmits<{ (e: 'uploaded', url: string): void }>()
|
|
16
|
-
|
|
17
|
-
const { account } = useWallet()
|
|
18
|
-
const {
|
|
19
|
-
selectedRelayHost,
|
|
20
|
-
availableRelays,
|
|
21
|
-
estimatedCost,
|
|
22
|
-
fileSizeBytes,
|
|
23
|
-
checkOperatorRelayHealth,
|
|
24
|
-
} = useWalrusRelay(props.network)
|
|
25
|
-
|
|
26
|
-
const uploading = ref(false)
|
|
27
|
-
const status = ref('')
|
|
28
|
-
const error = ref<string | null>(null)
|
|
29
|
-
const fileName = ref('')
|
|
30
|
-
let bytes: Uint8Array | null = null
|
|
31
|
-
|
|
32
|
-
const maxSizeLabel = `${Math.round(ICON_MAX_BYTES / 1024)} KB`
|
|
33
|
-
const ICON_YEARS = Math.round(ICON_EPOCHS / 26)
|
|
34
|
-
|
|
35
|
-
onMounted(() => {
|
|
36
|
-
checkOperatorRelayHealth()
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
const showPublicRelayNotice = computed(() => availableRelays.value.length > 1)
|
|
40
|
-
|
|
41
|
-
const showFreeRelayTip = computed(() => {
|
|
42
|
-
const publicHost = availableRelays.value.find((r) => r.isPublic)?.host
|
|
43
|
-
return showPublicRelayNotice.value && selectedRelayHost.value !== publicHost
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
function onFile(e: Event): void {
|
|
47
|
-
error.value = null
|
|
48
|
-
bytes = null
|
|
49
|
-
fileName.value = ''
|
|
50
|
-
const f = (e.target as HTMLInputElement).files?.[0]
|
|
51
|
-
if (!f) return
|
|
52
|
-
|
|
53
|
-
// UX/cost guard only — the authoritative cap is the edge (relay) body limit.
|
|
54
|
-
const invalid = validateIconFile(f)
|
|
55
|
-
if (invalid) {
|
|
56
|
-
error.value = invalid
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
fileName.value = f.name
|
|
61
|
-
fileSizeBytes.value = f.size
|
|
62
|
-
const reader = new FileReader()
|
|
63
|
-
reader.onload = () => {
|
|
64
|
-
bytes = new Uint8Array(reader.result as ArrayBuffer)
|
|
65
|
-
}
|
|
66
|
-
reader.readAsArrayBuffer(f)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function onRelayChange(newHost: string): void {
|
|
70
|
-
selectedRelayHost.value = newHost
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async function upload(): Promise<void> {
|
|
74
|
-
if (!bytes) {
|
|
75
|
-
error.value = 'Choose an image first.'
|
|
76
|
-
return
|
|
77
|
-
}
|
|
78
|
-
if (!account.value) {
|
|
79
|
-
error.value = 'Connect your wallet first.'
|
|
80
|
-
return
|
|
81
|
-
}
|
|
82
|
-
uploading.value = true
|
|
83
|
-
error.value = null
|
|
84
|
-
try {
|
|
85
|
-
// Lazily load the heavy @mysten/walrus code and Sui client only on demand.
|
|
86
|
-
const { createWalrusClient, createBlobUploadFlow, walrusBlobUrl } =
|
|
87
|
-
await import('../lib/walrus.js')
|
|
88
|
-
const { getReadClient } = await import('../lib/readClient.js')
|
|
89
|
-
const executor = await buildDeployExecutor(props.network as Network)
|
|
90
|
-
const suiClient = getReadClient(props.network as Network)
|
|
91
|
-
const client = createWalrusClient(props.network, {
|
|
92
|
-
wasmUrl: walrusWasmUrl,
|
|
93
|
-
uploadRelayHost: selectedRelayHost.value,
|
|
94
|
-
})
|
|
95
|
-
const flow = createBlobUploadFlow(client, bytes)
|
|
96
|
-
|
|
97
|
-
status.value = 'Encoding…'
|
|
98
|
-
await flow.encode()
|
|
99
|
-
|
|
100
|
-
// Disclose the relay fee before asking for a signature. `calculateUploadRelayTip`
|
|
101
|
-
// takes the UNENCODED size and throws if the tip exceeds the client max guard.
|
|
102
|
-
let feeNote = ''
|
|
103
|
-
try {
|
|
104
|
-
const tipMist = await client.walrus.calculateUploadRelayTip({ size: bytes.length })
|
|
105
|
-
const tipSui = Number(tipMist) / 1e9
|
|
106
|
-
feeNote = tipMist > 0n ? ` Relay fee ≈ ${tipSui.toFixed(4)} SUI + storage (WAL).` : ''
|
|
107
|
-
} catch {
|
|
108
|
-
// Non-fatal for disclosure; the register step surfaces any real tip error.
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
status.value = `Registering blob (approve in wallet)…${feeNote}`
|
|
112
|
-
const regTx = flow.register({
|
|
113
|
-
owner: account.value.address,
|
|
114
|
-
epochs: ICON_EPOCHS,
|
|
115
|
-
deletable: false,
|
|
116
|
-
})
|
|
117
|
-
// Set sender and build with the Sui client to resolve CoinWithBalance intents
|
|
118
|
-
regTx.setSenderIfNotSet(account.value.address)
|
|
119
|
-
await regTx.build({ client: suiClient })
|
|
120
|
-
const reg = await executor.signAndExecute(regTx)
|
|
121
|
-
await executor.waitForTransaction(reg.digest)
|
|
122
|
-
|
|
123
|
-
status.value = 'Uploading to the relay…'
|
|
124
|
-
await flow.upload({ digest: reg.digest })
|
|
125
|
-
|
|
126
|
-
status.value = 'Certifying (approve in wallet)…'
|
|
127
|
-
const certTx = flow.certify()
|
|
128
|
-
// Set sender and build the certify transaction with the Sui client
|
|
129
|
-
certTx.setSenderIfNotSet(account.value.address)
|
|
130
|
-
await certTx.build({ client: suiClient })
|
|
131
|
-
const cert = await executor.signAndExecute(certTx)
|
|
132
|
-
await executor.waitForTransaction(cert.digest)
|
|
133
|
-
|
|
134
|
-
const blob = await flow.getBlob()
|
|
135
|
-
emit('uploaded', walrusBlobUrl(props.network, blob.blobId))
|
|
136
|
-
status.value = 'Uploaded ✓ — URL filled in above.'
|
|
137
|
-
} catch (e) {
|
|
138
|
-
error.value = e instanceof Error ? e.message : String(e)
|
|
139
|
-
status.value = ''
|
|
140
|
-
} finally {
|
|
141
|
-
uploading.value = false
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
</script>
|
|
145
|
-
|
|
146
|
-
<template>
|
|
147
|
-
<div>
|
|
148
|
-
<label for="walrus-file" class="hint" style="display: block; font-weight: 600">
|
|
149
|
-
…or upload an image to Walrus
|
|
150
|
-
</label>
|
|
151
|
-
<div class="row">
|
|
152
|
-
<input
|
|
153
|
-
id="walrus-file"
|
|
154
|
-
type="file"
|
|
155
|
-
accept="image/*"
|
|
156
|
-
aria-label="Choose an image to upload to Walrus"
|
|
157
|
-
aria-describedby="walrus-help"
|
|
158
|
-
style="width: auto"
|
|
159
|
-
@change="onFile"
|
|
160
|
-
/>
|
|
161
|
-
<button type="button" :disabled="uploading || !connected || !fileName" @click="upload">
|
|
162
|
-
<span v-if="uploading" class="spinner" aria-hidden="true"></span>
|
|
163
|
-
Upload to Walrus
|
|
164
|
-
</button>
|
|
165
|
-
</div>
|
|
166
|
-
|
|
167
|
-
<!-- Relay selection (shown only if multiple relays available) -->
|
|
168
|
-
<fieldset v-if="fileName && availableRelays.length > 1" style="margin: 0.75rem 0">
|
|
169
|
-
<legend>Upload relay</legend>
|
|
170
|
-
<div style="display: flex; flex-direction: column; gap: 0.5rem">
|
|
171
|
-
<label
|
|
172
|
-
v-for="option in availableRelays"
|
|
173
|
-
:key="option.host"
|
|
174
|
-
style="display: flex; align-items: center; gap: 0.5rem"
|
|
175
|
-
>
|
|
176
|
-
<input
|
|
177
|
-
type="radio"
|
|
178
|
-
:value="option.host"
|
|
179
|
-
:checked="selectedRelayHost === option.host"
|
|
180
|
-
@change="(e) => onRelayChange((e.target as HTMLInputElement).value)"
|
|
181
|
-
/>
|
|
182
|
-
<span>{{ option.label }}</span>
|
|
183
|
-
</label>
|
|
184
|
-
</div>
|
|
185
|
-
</fieldset>
|
|
186
|
-
|
|
187
|
-
<!-- Cost estimation (shown after file selection) -->
|
|
188
|
-
<UiNotice v-if="fileName && estimatedCost" style="margin: 0.75rem 0">
|
|
189
|
-
<strong>Estimated cost:</strong> {{ estimatedCost.label }}
|
|
190
|
-
</UiNotice>
|
|
191
|
-
|
|
192
|
-
<!-- Public relay notice -->
|
|
193
|
-
<p v-if="showFreeRelayTip" class="hint" style="margin: 0.75rem 0">
|
|
194
|
-
Tip: select the public relay above to avoid the relay fee.
|
|
195
|
-
</p>
|
|
196
|
-
|
|
197
|
-
<p id="walrus-help" class="hint" aria-live="polite">
|
|
198
|
-
Max {{ maxSizeLabel }} · PNG/JPEG/WebP/SVG. Requires <strong>WAL</strong> (storage) and
|
|
199
|
-
<strong>SUI</strong> (gas + a small relay fee) in your wallet; two wallet approvals. Blob
|
|
200
|
-
stored for ~{{ ICON_YEARS }} years.
|
|
201
|
-
<span v-if="status">{{ status }}</span>
|
|
202
|
-
</p>
|
|
203
|
-
<UiNotice v-if="error" type="error">{{ error }}</UiNotice>
|
|
204
|
-
</div>
|
|
205
|
-
</template>
|