@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.
|
|
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.
|
|
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 {
|
|
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 {
|
|
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
|
|
55
|
-
const
|
|
56
|
-
const result =
|
|
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
|
-
|
|
172
|
+
walletError.value = 'Connect a wallet first.'
|
|
161
173
|
return
|
|
162
174
|
}
|
|
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
|
-
}
|
|
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
|
-
|
|
188
|
-
|
|
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
|
+
}
|