@meddleware/token-deployer-ui 0.0.22 → 0.0.24
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meddleware/token-deployer-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "0BSD",
|
|
6
6
|
"repository": {
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@meddleware/design-tokens": "^0.1.7",
|
|
52
|
-
"@meddleware/sui-token-client": "^0.0.
|
|
52
|
+
"@meddleware/sui-token-client": "^0.0.5",
|
|
53
53
|
"@meddleware/ui": "^0.1.28",
|
|
54
|
-
"@meddleware/walrus-client": "^0.0.
|
|
55
|
-
"@meddleware/walrus-relay": "^0.1.
|
|
54
|
+
"@meddleware/walrus-client": "^0.0.24",
|
|
55
|
+
"@meddleware/walrus-relay": "^0.1.25",
|
|
56
56
|
"@mysten/move-bytecode-template": "~0.4.1",
|
|
57
57
|
"@mysten/sui": "^2.33.1",
|
|
58
58
|
"@mysten/walrus": "~1.2.32",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@meddleware/access-gate-client": "^0.0.3",
|
|
66
|
-
"@meddleware/wallet-adapter": "^0.0.
|
|
66
|
+
"@meddleware/wallet-adapter": "^0.0.13",
|
|
67
67
|
"@playwright/test": "^1.56.0",
|
|
68
68
|
"@types/node": "~24.12.2",
|
|
69
69
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
@@ -6,7 +6,7 @@ import { getReadClient } from '../wallet.js'
|
|
|
6
6
|
import { useWalrusRelay } from '../composables/useWalrusRelay.js'
|
|
7
7
|
import { useAccessGate } from '@meddleware/walrus-relay'
|
|
8
8
|
// The flow subpath carries no wasm; it loads the Walrus client lazily when an upload starts.
|
|
9
|
-
import { consumeStorageKey, createGatedAccess, runBlobUpload } from '@meddleware/walrus-client/flow'
|
|
9
|
+
import { browserStorage, consumeStorageKey, createGatedAccess, runBlobUpload } from '@meddleware/walrus-client/flow'
|
|
10
10
|
import { ICON_EPOCHS } from '../lib/walrus-constants.js'
|
|
11
11
|
import { ACCESS_GATE, ICON_MAX_BYTES, WALRUS_MAX_TIP_MIST, WALRUS_RPC_URLS, validateIconFile } from '../config.js'
|
|
12
12
|
import type { WalrusNetwork } from '../config.js'
|
|
@@ -157,7 +157,7 @@ async function upload(): Promise<void> {
|
|
|
157
157
|
const access =
|
|
158
158
|
gated && gate && nftId
|
|
159
159
|
? createGatedAccess({
|
|
160
|
-
storage:
|
|
160
|
+
storage: browserStorage(),
|
|
161
161
|
key: consumeStorageKey(walrusNet, gate.gateId, address),
|
|
162
162
|
relayHost: selectedRelayHost.value,
|
|
163
163
|
address,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, reactive, ref } from 'vue'
|
|
2
|
+
import { computed, reactive, ref, watch } from 'vue'
|
|
3
3
|
import { useWallet, useNetwork, WalletGuard } from '@meddleware/wallet-adapter'
|
|
4
4
|
import ConfigForm, { type ConfigFormStep } from './ConfigForm.vue'
|
|
5
5
|
import ReviewPanel from './ReviewPanel.vue'
|
|
@@ -9,6 +9,7 @@ import TermsDialog from './TermsDialog.vue'
|
|
|
9
9
|
import MyTokens from './MyTokens.vue'
|
|
10
10
|
import {
|
|
11
11
|
AppTabNav,
|
|
12
|
+
UiButton,
|
|
12
13
|
UiNotice,
|
|
13
14
|
UiStepper,
|
|
14
15
|
UiTabPanel,
|
|
@@ -19,14 +20,12 @@ import {
|
|
|
19
20
|
import { emptyForm, toTokenConfig } from '../lib/form.js'
|
|
20
21
|
import { validateForm } from '../lib/validation.js'
|
|
21
22
|
import type { FormErrors } from '../lib/validation.js'
|
|
22
|
-
import {
|
|
23
|
-
import
|
|
24
|
-
import { buildDeployExecutor } from '../lib/deployExecutor.js'
|
|
23
|
+
import { useDeploy } from '../composables/useDeploy.js'
|
|
24
|
+
import { browserStorage } from '@meddleware/walrus-client/flow'
|
|
25
25
|
import { FEE_MIST, FEE_TREASURY, PUBLISH_GAS_BUDGET, isFeeConfigured } from '../config.js'
|
|
26
|
-
import {
|
|
27
|
-
import type { TokenNetwork as Network, PublishResult } from '@meddleware/sui-token-client'
|
|
26
|
+
import type { TokenNetwork as Network } from '@meddleware/sui-token-client'
|
|
28
27
|
|
|
29
|
-
type Step = 'identity' | 'token' | 'settings' | 'review' | 'deploying' | 'done'
|
|
28
|
+
type Step = 'identity' | 'token' | 'settings' | 'review' | 'deploying' | 'incomplete' | 'published' | 'done'
|
|
30
29
|
|
|
31
30
|
const STEPS: StepperStep[] = [
|
|
32
31
|
{ id: 'identity', label: 'Identity' },
|
|
@@ -51,10 +50,24 @@ const activeTab = ref('deploy')
|
|
|
51
50
|
|
|
52
51
|
const form = reactive(emptyForm())
|
|
53
52
|
const step = ref<Step>('identity')
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const result =
|
|
57
|
-
|
|
53
|
+
const flow = useDeploy()
|
|
54
|
+
const deployStep = flow.step
|
|
55
|
+
const result = flow.result
|
|
56
|
+
// A wallet message ("Connect a wallet first.") or the flow's error.
|
|
57
|
+
const walletError = ref<string | null>(null)
|
|
58
|
+
const deployError = computed(() => walletError.value ?? flow.error.value)
|
|
59
|
+
// The flow decides the step once it leaves `deploying`: a failure before publishing returns to the
|
|
60
|
+
// review; anything after a publish never does (a second deploy would publish a second coin).
|
|
61
|
+
watch(flow.phase, (phase) => {
|
|
62
|
+
if (phase === 'deploying') step.value = 'deploying'
|
|
63
|
+
else if (phase === 'done') step.value = 'done'
|
|
64
|
+
else if (phase === 'incomplete') step.value = 'incomplete'
|
|
65
|
+
else if (phase === 'published') step.value = 'published'
|
|
66
|
+
else if (phase === 'failed') step.value = 'review'
|
|
67
|
+
})
|
|
68
|
+
// Storage that never throws: with site data blocked the terms are simply asked again next visit.
|
|
69
|
+
const storage = browserStorage()
|
|
70
|
+
const termsAccepted = ref(storage.getItem('sui-deployer:terms-accepted') === '1')
|
|
58
71
|
const showTerms = ref(false)
|
|
59
72
|
|
|
60
73
|
const errors = computed(() => validateForm(form))
|
|
@@ -96,6 +109,8 @@ const stepIndex = computed((): number => {
|
|
|
96
109
|
settings: 2,
|
|
97
110
|
review: 3,
|
|
98
111
|
deploying: 3,
|
|
112
|
+
incomplete: 3,
|
|
113
|
+
published: 3,
|
|
99
114
|
done: 4,
|
|
100
115
|
}
|
|
101
116
|
return map[step.value]
|
|
@@ -144,7 +159,7 @@ function goBack(): void {
|
|
|
144
159
|
}
|
|
145
160
|
|
|
146
161
|
function onTermsAccept(): void {
|
|
147
|
-
|
|
162
|
+
storage.setItem('sui-deployer:terms-accepted', '1')
|
|
148
163
|
termsAccepted.value = true
|
|
149
164
|
showTerms.value = false
|
|
150
165
|
step.value = 'review'
|
|
@@ -157,35 +172,25 @@ function onTermsCancel(): void {
|
|
|
157
172
|
async function confirmDeploy(): Promise<void> {
|
|
158
173
|
const sender = account.value?.address
|
|
159
174
|
if (!sender) {
|
|
160
|
-
|
|
175
|
+
walletError.value = 'Connect a wallet first.'
|
|
161
176
|
return
|
|
162
177
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
feeTreasury: FEE_TREASURY[net],
|
|
175
|
-
gasBudget: PUBLISH_GAS_BUDGET,
|
|
176
|
-
executor: await buildDeployExecutor(net),
|
|
177
|
-
onStep: (s) => (deployStep.value = s),
|
|
178
|
-
})
|
|
179
|
-
step.value = 'done'
|
|
180
|
-
} catch (e) {
|
|
181
|
-
deployError.value = extractErrorMessage(e)
|
|
182
|
-
step.value = 'review'
|
|
183
|
-
}
|
|
178
|
+
walletError.value = null
|
|
179
|
+
const net = network.value as Network
|
|
180
|
+
const feeConfigured = isFeeConfigured(net)
|
|
181
|
+
await flow.deploy({
|
|
182
|
+
config: config.value,
|
|
183
|
+
network: net,
|
|
184
|
+
sender,
|
|
185
|
+
feeMist: feeConfigured ? FEE_MIST : 0n,
|
|
186
|
+
feeTreasury: FEE_TREASURY[net],
|
|
187
|
+
gasBudget: PUBLISH_GAS_BUDGET,
|
|
188
|
+
})
|
|
184
189
|
}
|
|
185
190
|
|
|
186
191
|
function restart(): void {
|
|
187
|
-
|
|
188
|
-
|
|
192
|
+
flow.reset()
|
|
193
|
+
walletError.value = null
|
|
189
194
|
step.value = 'identity'
|
|
190
195
|
}
|
|
191
196
|
</script>
|
|
@@ -231,6 +236,25 @@ function restart(): void {
|
|
|
231
236
|
@confirm="confirmDeploy"
|
|
232
237
|
/>
|
|
233
238
|
<DeployProgress v-if="step === 'deploying'" :step="deployStep" />
|
|
239
|
+
<section v-if="step === 'incomplete' && flow.pending.value" class="deploy-incomplete" aria-labelledby="incomplete-h">
|
|
240
|
+
<h2 id="incomplete-h">Your coin is published — finish its setup</h2>
|
|
241
|
+
<p>
|
|
242
|
+
<code>{{ flow.pending.value.result.coinType }}</code> exists on {{ flow.pending.value.result.network }},
|
|
243
|
+
but the second transaction did not complete: the currency is not registered, the initial supply
|
|
244
|
+
is not minted and your supply and metadata choices are not applied yet. The caps are in your
|
|
245
|
+
wallet.
|
|
246
|
+
</p>
|
|
247
|
+
<p>Do not deploy again — that would publish a second coin. Sign the setup transaction instead.</p>
|
|
248
|
+
<UiButton variant="primary" @click="flow.finish()">Finish setup (sign again)</UiButton>
|
|
249
|
+
</section>
|
|
250
|
+
<section v-if="step === 'published'" class="deploy-incomplete" aria-labelledby="published-h">
|
|
251
|
+
<h2 id="published-h">A coin was published, but the result could not be read</h2>
|
|
252
|
+
<p>
|
|
253
|
+
Publish transaction <code>{{ flow.publishDigest.value }}</code>. Check it in an explorer before
|
|
254
|
+
doing anything else; do not deploy again.
|
|
255
|
+
</p>
|
|
256
|
+
<UiButton variant="ghost" @click="restart">Start a new token</UiButton>
|
|
257
|
+
</section>
|
|
234
258
|
<ResultPanel
|
|
235
259
|
v-if="step === 'done' && result"
|
|
236
260
|
:result="result"
|
|
@@ -252,4 +276,8 @@ function restart(): void {
|
|
|
252
276
|
.deployer-tabs {
|
|
253
277
|
margin: 0 0 1rem;
|
|
254
278
|
}
|
|
279
|
+
|
|
280
|
+
.deploy-incomplete {
|
|
281
|
+
margin: 1rem 0;
|
|
282
|
+
}
|
|
255
283
|
</style>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
import {
|
|
3
|
+
DeployIncompleteError,
|
|
4
|
+
DeployUnconfirmedError,
|
|
5
|
+
PublishedError,
|
|
6
|
+
deployToken,
|
|
7
|
+
finalizeToken,
|
|
8
|
+
type DeployArgs,
|
|
9
|
+
type DeployStep,
|
|
10
|
+
type Executor,
|
|
11
|
+
type PendingFinalize,
|
|
12
|
+
} from '@meddleware/sui-token-client/deploy'
|
|
13
|
+
import type { PublishResult, TokenNetwork } from '@meddleware/sui-token-client'
|
|
14
|
+
import { buildDeployExecutor } from '../lib/deployExecutor.js'
|
|
15
|
+
import { extractErrorMessage } from '../lib/errors.js'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Where a deploy stands:
|
|
19
|
+
* - `failed` — nothing was published; the user may review and try again.
|
|
20
|
+
* - `incomplete` — the coin is published but its setup (second signature) is not finished. The UI
|
|
21
|
+
* must offer {@link finish}, never a fresh deploy, which would publish a second coin and charge the
|
|
22
|
+
* fee again.
|
|
23
|
+
* - `published` — a coin was published but the outcome could not be read; the UI shows the
|
|
24
|
+
* transaction and offers no deploy or retry.
|
|
25
|
+
* - `done` — finished (`error` may still say the last step was not confirmed).
|
|
26
|
+
*/
|
|
27
|
+
export type DeployPhase = 'idle' | 'deploying' | 'failed' | 'incomplete' | 'published' | 'done'
|
|
28
|
+
|
|
29
|
+
/** The deploy flow's state machine, with the wallet executor injectable for tests. */
|
|
30
|
+
export function useDeploy(executorFor: (network: TokenNetwork) => Promise<Executor> = buildDeployExecutor) {
|
|
31
|
+
const phase = ref<DeployPhase>('idle')
|
|
32
|
+
const step = ref<DeployStep | null>(null)
|
|
33
|
+
const error = ref<string | null>(null)
|
|
34
|
+
const result = ref<PublishResult | null>(null)
|
|
35
|
+
const pending = ref<PendingFinalize | null>(null)
|
|
36
|
+
/** The publish transaction, once one executed (shown when its outcome is unclear). */
|
|
37
|
+
const publishDigest = ref<string | null>(null)
|
|
38
|
+
|
|
39
|
+
/** Route a failure: anything after the publish executed never goes back to a fresh deploy. */
|
|
40
|
+
function onFailure(e: unknown): void {
|
|
41
|
+
error.value = extractErrorMessage(e)
|
|
42
|
+
if (e instanceof DeployUnconfirmedError) {
|
|
43
|
+
result.value = e.result
|
|
44
|
+
pending.value = null
|
|
45
|
+
phase.value = 'done'
|
|
46
|
+
} else if (e instanceof DeployIncompleteError) {
|
|
47
|
+
pending.value = e.pending
|
|
48
|
+
publishDigest.value = e.publishDigest
|
|
49
|
+
phase.value = 'incomplete'
|
|
50
|
+
} else if (e instanceof PublishedError) {
|
|
51
|
+
publishDigest.value = e.publishDigest
|
|
52
|
+
phase.value = 'published'
|
|
53
|
+
} else if (pending.value) {
|
|
54
|
+
phase.value = 'incomplete'
|
|
55
|
+
} else {
|
|
56
|
+
phase.value = 'failed'
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function deploy(args: Omit<DeployArgs, 'executor' | 'onStep'>): Promise<void> {
|
|
61
|
+
phase.value = 'deploying'
|
|
62
|
+
step.value = null
|
|
63
|
+
error.value = null
|
|
64
|
+
try {
|
|
65
|
+
result.value = await deployToken({
|
|
66
|
+
...args,
|
|
67
|
+
executor: await executorFor(args.network),
|
|
68
|
+
onStep: (s) => (step.value = s),
|
|
69
|
+
})
|
|
70
|
+
phase.value = 'done'
|
|
71
|
+
} catch (e) {
|
|
72
|
+
onFailure(e)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Finish a published coin's setup: one more signature, on the network it was published to. */
|
|
77
|
+
async function finish(): Promise<void> {
|
|
78
|
+
const p = pending.value
|
|
79
|
+
if (!p) return
|
|
80
|
+
phase.value = 'deploying'
|
|
81
|
+
error.value = null
|
|
82
|
+
try {
|
|
83
|
+
result.value = await finalizeToken({
|
|
84
|
+
pending: p,
|
|
85
|
+
executor: await executorFor(p.result.network),
|
|
86
|
+
onStep: (s) => (step.value = s),
|
|
87
|
+
})
|
|
88
|
+
pending.value = null
|
|
89
|
+
phase.value = 'done'
|
|
90
|
+
} catch (e) {
|
|
91
|
+
onFailure(e)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function reset(): void {
|
|
96
|
+
phase.value = 'idle'
|
|
97
|
+
step.value = null
|
|
98
|
+
error.value = null
|
|
99
|
+
result.value = null
|
|
100
|
+
pending.value = null
|
|
101
|
+
publishDigest.value = null
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { phase, step, error, result, pending, publishDigest, deploy, finish, reset }
|
|
105
|
+
}
|