@meddleware/token-deployer-ui 0.0.21 → 0.0.23

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.21",
3
+ "version": "0.0.23",
4
4
  "type": "module",
5
5
  "license": "0BSD",
6
6
  "repository": {
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@meddleware/design-tokens": "^0.1.7",
52
- "@meddleware/sui-token-client": "^0.0.2",
52
+ "@meddleware/sui-token-client": "^0.0.5",
53
53
  "@meddleware/ui": "^0.1.28",
54
54
  "@meddleware/walrus-client": "^0.0.22",
55
55
  "@meddleware/walrus-relay": "^0.1.23",
@@ -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,11 @@ 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 { deployToken } from '@meddleware/sui-token-client/deploy'
23
- import type { DeployStep } from '@meddleware/sui-token-client/deploy'
24
- import { buildDeployExecutor } from '../lib/deployExecutor.js'
23
+ import { useDeploy } from '../composables/useDeploy.js'
25
24
  import { FEE_MIST, FEE_TREASURY, PUBLISH_GAS_BUDGET, isFeeConfigured } from '../config.js'
26
- import { extractErrorMessage } from '../lib/errors.js'
27
- import type { TokenNetwork as Network, PublishResult } from '@meddleware/sui-token-client'
25
+ import type { TokenNetwork as Network } from '@meddleware/sui-token-client'
28
26
 
29
- type Step = 'identity' | 'token' | 'settings' | 'review' | 'deploying' | 'done'
27
+ type Step = 'identity' | 'token' | 'settings' | 'review' | 'deploying' | 'incomplete' | 'published' | 'done'
30
28
 
31
29
  const STEPS: StepperStep[] = [
32
30
  { id: 'identity', label: 'Identity' },
@@ -51,9 +49,21 @@ const activeTab = ref('deploy')
51
49
 
52
50
  const form = reactive(emptyForm())
53
51
  const step = ref<Step>('identity')
54
- const deployStep = ref<DeployStep | null>(null)
55
- const deployError = ref<string | null>(null)
56
- const result = ref<PublishResult | null>(null)
52
+ const flow = useDeploy()
53
+ const deployStep = flow.step
54
+ const result = flow.result
55
+ // A wallet message ("Connect a wallet first.") or the flow's error.
56
+ const walletError = ref<string | null>(null)
57
+ const deployError = computed(() => walletError.value ?? flow.error.value)
58
+ // The flow decides the step once it leaves `deploying`: a failure before publishing returns to the
59
+ // review; anything after a publish never does (a second deploy would publish a second coin).
60
+ watch(flow.phase, (phase) => {
61
+ if (phase === 'deploying') step.value = 'deploying'
62
+ else if (phase === 'done') step.value = 'done'
63
+ else if (phase === 'incomplete') step.value = 'incomplete'
64
+ else if (phase === 'published') step.value = 'published'
65
+ else if (phase === 'failed') step.value = 'review'
66
+ })
57
67
  const termsAccepted = ref(localStorage.getItem('sui-deployer:terms-accepted') === '1')
58
68
  const showTerms = ref(false)
59
69
 
@@ -96,6 +106,8 @@ const stepIndex = computed((): number => {
96
106
  settings: 2,
97
107
  review: 3,
98
108
  deploying: 3,
109
+ incomplete: 3,
110
+ published: 3,
99
111
  done: 4,
100
112
  }
101
113
  return map[step.value]
@@ -157,35 +169,25 @@ function onTermsCancel(): void {
157
169
  async function confirmDeploy(): Promise<void> {
158
170
  const sender = account.value?.address
159
171
  if (!sender) {
160
- deployError.value = 'Connect a wallet first.'
172
+ walletError.value = 'Connect a wallet first.'
161
173
  return
162
174
  }
163
- deployError.value = null
164
- step.value = 'deploying'
165
- deployStep.value = null
166
- try {
167
- const net = network.value as Network
168
- const feeConfigured = isFeeConfigured(net)
169
- result.value = await deployToken({
170
- config: config.value,
171
- network: net,
172
- sender,
173
- feeMist: feeConfigured ? FEE_MIST : 0n,
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
- }
175
+ walletError.value = null
176
+ const net = network.value as Network
177
+ const feeConfigured = isFeeConfigured(net)
178
+ await flow.deploy({
179
+ config: config.value,
180
+ network: net,
181
+ sender,
182
+ feeMist: feeConfigured ? FEE_MIST : 0n,
183
+ feeTreasury: FEE_TREASURY[net],
184
+ gasBudget: PUBLISH_GAS_BUDGET,
185
+ })
184
186
  }
185
187
 
186
188
  function restart(): void {
187
- result.value = null
188
- deployError.value = null
189
+ flow.reset()
190
+ walletError.value = null
189
191
  step.value = 'identity'
190
192
  }
191
193
  </script>
@@ -231,6 +233,25 @@ function restart(): void {
231
233
  @confirm="confirmDeploy"
232
234
  />
233
235
  <DeployProgress v-if="step === 'deploying'" :step="deployStep" />
236
+ <section v-if="step === 'incomplete' && flow.pending.value" class="deploy-incomplete" aria-labelledby="incomplete-h">
237
+ <h2 id="incomplete-h">Your coin is published — finish its setup</h2>
238
+ <p>
239
+ <code>{{ flow.pending.value.result.coinType }}</code> exists on {{ flow.pending.value.result.network }},
240
+ but the second transaction did not complete: the currency is not registered, the initial supply
241
+ is not minted and your supply and metadata choices are not applied yet. The caps are in your
242
+ wallet.
243
+ </p>
244
+ <p>Do not deploy again — that would publish a second coin. Sign the setup transaction instead.</p>
245
+ <UiButton variant="primary" @click="flow.finish()">Finish setup (sign again)</UiButton>
246
+ </section>
247
+ <section v-if="step === 'published'" class="deploy-incomplete" aria-labelledby="published-h">
248
+ <h2 id="published-h">A coin was published, but the result could not be read</h2>
249
+ <p>
250
+ Publish transaction <code>{{ flow.publishDigest.value }}</code>. Check it in an explorer before
251
+ doing anything else; do not deploy again.
252
+ </p>
253
+ <UiButton variant="ghost" @click="restart">Start a new token</UiButton>
254
+ </section>
234
255
  <ResultPanel
235
256
  v-if="step === 'done' && result"
236
257
  :result="result"
@@ -252,4 +273,8 @@ function restart(): void {
252
273
  .deployer-tabs {
253
274
  margin: 0 0 1rem;
254
275
  }
276
+
277
+ .deploy-incomplete {
278
+ margin: 1rem 0;
279
+ }
255
280
  </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
+ }