@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
package/scripts/e2e-deploy.mjs
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
import { readFileSync, existsSync } from 'node:fs'
|
|
21
21
|
import { chromium } from 'playwright'
|
|
22
|
-
import {
|
|
22
|
+
import { SuiGrpcClient } from '@mysten/sui/grpc'
|
|
23
23
|
import { requestSuiFromFaucetV2 } from '@mysten/sui/faucet'
|
|
24
24
|
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'
|
|
25
25
|
import { Transaction } from '@mysten/sui/transactions'
|
|
@@ -37,7 +37,7 @@ const APP_NETWORK = NETWORK === 'mainnet' ? 'mainnet' : 'testnet'
|
|
|
37
37
|
|
|
38
38
|
const DEFAULT_RPC = {
|
|
39
39
|
localnet: 'http://127.0.0.1:9000',
|
|
40
|
-
testnet: 'https://
|
|
40
|
+
testnet: 'https://fullnode.testnet.sui.io:443',
|
|
41
41
|
mainnet: 'https://fullnode.mainnet.sui.io:443',
|
|
42
42
|
}
|
|
43
43
|
const RPC = process.env.E2E_RPC || DEFAULT_RPC[NETWORK]
|
|
@@ -83,9 +83,15 @@ if (NETWORK === 'localnet') {
|
|
|
83
83
|
}
|
|
84
84
|
const address = keypair.toSuiAddress()
|
|
85
85
|
|
|
86
|
-
// Node-side client points at the PHYSICAL RPC; its `network` label is the app network so
|
|
87
|
-
// built transaction's chain identifier matches what the wallet/app sign under.
|
|
88
|
-
const client = new
|
|
86
|
+
// Node-side gRPC client points at the PHYSICAL RPC; its `network` label is the app network so
|
|
87
|
+
// the built transaction's chain identifier matches what the wallet/app sign under.
|
|
88
|
+
const client = new SuiGrpcClient({ baseUrl: RPC, network: APP_NETWORK })
|
|
89
|
+
|
|
90
|
+
/** Total SUI balance (MIST) of `owner`. */
|
|
91
|
+
async function suiBalance(owner) {
|
|
92
|
+
const { balance } = await client.getBalance({ owner })
|
|
93
|
+
return BigInt(balance.balance)
|
|
94
|
+
}
|
|
89
95
|
|
|
90
96
|
const EXPECT_FEE = Boolean(FEE_ADDR) && FEE_ADDR !== ZERO_ADDR
|
|
91
97
|
|
|
@@ -95,19 +101,16 @@ async function main() {
|
|
|
95
101
|
if (NETWORK === 'localnet') {
|
|
96
102
|
await requestSuiFromFaucetV2({ host: FAUCET, recipient: address })
|
|
97
103
|
for (let i = 0; i < 25; i++) {
|
|
98
|
-
|
|
99
|
-
if (BigInt(b.totalBalance) > 0n) break
|
|
104
|
+
if ((await suiBalance(address)) > 0n) break
|
|
100
105
|
await new Promise((r) => setTimeout(r, 1000))
|
|
101
106
|
}
|
|
102
107
|
}
|
|
103
108
|
|
|
104
|
-
const balance =
|
|
109
|
+
const balance = await suiBalance(address)
|
|
105
110
|
console.log('sender:', address, 'balance:', balance.toString(), 'MIST')
|
|
106
111
|
if (balance === 0n) fail(`sender ${address} has no SUI on ${NETWORK}`)
|
|
107
112
|
|
|
108
|
-
const feeBefore = EXPECT_FEE
|
|
109
|
-
? BigInt((await client.getBalance({ owner: FEE_ADDR })).totalBalance)
|
|
110
|
-
: 0n
|
|
113
|
+
const feeBefore = EXPECT_FEE ? await suiBalance(FEE_ADDR) : 0n
|
|
111
114
|
|
|
112
115
|
// Browser resolution: explicit CHROME_PATH → a locally-installed Chrome → Playwright's
|
|
113
116
|
// bundled Chromium (CI installs it via `npx playwright install chromium`).
|
|
@@ -167,7 +170,7 @@ async function main() {
|
|
|
167
170
|
|
|
168
171
|
// Connect: open the dialog, select the app network if it differs from the testnet default,
|
|
169
172
|
// then pick the injected wallet.
|
|
170
|
-
await page.getByRole('button', { name: 'Connect Wallet' }).click()
|
|
173
|
+
await page.getByRole('button', { name: 'Connect Wallet', exact: true }).click()
|
|
171
174
|
if (APP_NETWORK !== 'testnet') {
|
|
172
175
|
await page.locator('#dialog-network-select').selectOption(APP_NETWORK)
|
|
173
176
|
}
|
|
@@ -175,15 +178,17 @@ async function main() {
|
|
|
175
178
|
await page.getByText(/Mock Test Wallet ·/).waitFor({ timeout: 10000 })
|
|
176
179
|
console.log('wallet connected in UI')
|
|
177
180
|
|
|
178
|
-
// Fill the
|
|
181
|
+
// Fill the wizard: identity → token → settings (defaults) → terms (first run) → review.
|
|
182
|
+
await page.locator('#package').fill('browser_coin')
|
|
183
|
+
await page.locator('#module').fill('browsercoin')
|
|
184
|
+
await page.getByRole('button', { name: 'Next', exact: true }).click()
|
|
179
185
|
await page.locator('#name').fill('Browser Coin')
|
|
180
186
|
await page.locator('#symbol').fill('BRWC')
|
|
181
187
|
await page.locator('#description').fill('Deployed via headless browser e2e')
|
|
182
188
|
await page.locator('#decimals').fill('6')
|
|
183
|
-
await page.
|
|
184
|
-
await page.locator('#module').fill('browsercoin')
|
|
185
|
-
|
|
189
|
+
await page.getByRole('button', { name: 'Next', exact: true }).click()
|
|
186
190
|
await page.getByRole('button', { name: /Review & deploy/ }).click()
|
|
191
|
+
await page.getByRole('button', { name: /I understand — continue/ }).click()
|
|
187
192
|
await page.getByRole('button', { name: /Confirm & deploy/ }).click()
|
|
188
193
|
console.log('confirmed; deploying (real publish + confirmation)…')
|
|
189
194
|
|
|
@@ -208,7 +213,7 @@ async function main() {
|
|
|
208
213
|
if (!coinType.includes('::browsercoin::BROWSERCOIN')) fail(`unexpected coin type: ${coinType}`)
|
|
209
214
|
|
|
210
215
|
if (EXPECT_FEE) {
|
|
211
|
-
const feeAfter =
|
|
216
|
+
const feeAfter = await suiBalance(FEE_ADDR)
|
|
212
217
|
const feeDelta = feeAfter - feeBefore
|
|
213
218
|
console.log('fee delivered this run:', feeDelta.toString(), 'MIST (to', FEE_ADDR + ')')
|
|
214
219
|
if (feeDelta !== EXPECT_FEE_MIST) fail(`expected ${EXPECT_FEE_MIST} MIST fee this run, got ${feeDelta}`)
|
package/scripts/e2e-localnet.mjs
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
// End-to-end: patch -> publish (fee + immutable) -> finalize (mint + fixed supply
|
|
2
2
|
// + frozen metadata) against localnet, using the actual app lib.
|
|
3
3
|
import init from '@mysten/move-bytecode-template';
|
|
4
|
-
import {
|
|
4
|
+
import { SuiGrpcClient } from '@mysten/sui/grpc';
|
|
5
5
|
import { requestSuiFromFaucetV2 } from '@mysten/sui/faucet';
|
|
6
6
|
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
|
|
7
7
|
import { patchTemplateModule } from '../src/lib/template.ts';
|
|
8
8
|
import { buildPublishTransaction, buildFinalizeTransaction, extractPublishResult, needsFinalize } from '../src/lib/buildPublishTx.ts';
|
|
9
|
+
import { toSuiTxResult } from '../src/lib/deploy.ts';
|
|
9
10
|
|
|
10
11
|
await (init)();
|
|
11
|
-
const client = new
|
|
12
|
+
const client = new SuiGrpcClient({ baseUrl: 'http://127.0.0.1:9000', network: 'localnet' });
|
|
13
|
+
const balanceOf = async (owner, coinType) => BigInt((await client.getBalance({ owner, coinType })).balance.balance);
|
|
14
|
+
const include = { effects: true, objectTypes: true };
|
|
12
15
|
const kp = new Ed25519Keypair(); const sender = kp.toSuiAddress();
|
|
13
16
|
const feeRecipient = new Ed25519Keypair().toSuiAddress();
|
|
14
17
|
await requestSuiFromFaucetV2({ host: 'http://127.0.0.1:9123', recipient: sender });
|
|
15
|
-
for (let i=0;i<25;i++){
|
|
18
|
+
for (let i=0;i<25;i++){ if((await balanceOf(sender))>0n)break; await new Promise(r=>setTimeout(r,1000)); }
|
|
16
19
|
|
|
17
20
|
const config = {
|
|
18
21
|
packageName:'fixed_token', moduleName:'fixedtok', structName:'FIXEDTOK',
|
|
@@ -24,7 +27,7 @@ const config = {
|
|
|
24
27
|
const moduleBytes = await patchTemplateModule(config);
|
|
25
28
|
const feeMist = 250000000n;
|
|
26
29
|
const pubTx = buildPublishTransaction({ moduleBytes, sender, feeMist, feeRecipient, gasBudget: 500000000n, packagePolicy: config.packagePolicy });
|
|
27
|
-
const pub = await client.signAndExecuteTransaction({ signer: kp, transaction: pubTx,
|
|
30
|
+
const pub = toSuiTxResult(await client.signAndExecuteTransaction({ signer: kp, transaction: pubTx, include }));
|
|
28
31
|
console.log('publish status:', pub.effects?.status?.status);
|
|
29
32
|
if (pub.effects?.status?.status !== 'success') { console.log(JSON.stringify(pub.effects?.status)); process.exit(1); }
|
|
30
33
|
|
|
@@ -35,30 +38,29 @@ console.log('coinType:', result.coinType);
|
|
|
35
38
|
console.log('caps:', { t: !!result.treasuryCapId, m: !!result.metadataCapId, upgrade: result.upgradeCapId ?? '(burned=immutable)' });
|
|
36
39
|
|
|
37
40
|
// fee arrived?
|
|
38
|
-
const feeBal = await
|
|
39
|
-
console.log('fee recipient balance:', feeBal.
|
|
41
|
+
const feeBal = await balanceOf(feeRecipient);
|
|
42
|
+
console.log('fee recipient balance:', feeBal.toString(), feeBal === feeMist ? 'OK' : 'MISMATCH');
|
|
40
43
|
|
|
41
44
|
// finalize
|
|
42
45
|
console.log('needsFinalize:', needsFinalize(config, sender));
|
|
43
46
|
const finTx = buildFinalizeTransaction({ config, coinType: result.coinType, treasuryCapId: result.treasuryCapId, metadataCapId: result.metadataCapId, sender, gasBudget: 500000000n });
|
|
44
|
-
const fin = await client.signAndExecuteTransaction({ signer: kp, transaction: finTx,
|
|
47
|
+
const fin = toSuiTxResult(await client.signAndExecuteTransaction({ signer: kp, transaction: finTx, include }));
|
|
45
48
|
await client.waitForTransaction({ digest: fin.digest });
|
|
46
49
|
console.log('finalize status:', fin.effects?.status?.status);
|
|
47
50
|
if (fin.effects?.status?.status !== 'success') { console.log(JSON.stringify(fin.effects?.status)); process.exit(1); }
|
|
48
51
|
|
|
49
52
|
// minted coin present?
|
|
50
|
-
const
|
|
51
|
-
const total = coins.data.reduce((a,c)=>a+BigInt(c.balance), 0n);
|
|
53
|
+
const total = await balanceOf(sender, result.coinType);
|
|
52
54
|
const expected = 1000n * 10n**6n;
|
|
53
55
|
console.log('minted balance:', total.toString(), total===expected ? 'OK (1000 * 10^6)' : 'MISMATCH exp '+expected);
|
|
54
56
|
|
|
55
57
|
// treasury cap frozen (immutable owner)?
|
|
56
|
-
const tcap = await client.getObject({
|
|
57
|
-
const owner = tcap.
|
|
58
|
+
const { object: tcap } = await client.getObject({ objectId: result.treasuryCapId });
|
|
59
|
+
const owner = tcap.owner;
|
|
58
60
|
console.log('treasuryCap owner after fixed-supply freeze:', JSON.stringify(owner));
|
|
59
61
|
|
|
60
62
|
const ok = result.coinType.endsWith('::fixedtok::FIXEDTOK')
|
|
61
|
-
&& feeBal
|
|
63
|
+
&& feeBal === feeMist
|
|
62
64
|
&& !result.upgradeCapId
|
|
63
65
|
&& total === expected;
|
|
64
66
|
console.log('\nE2E', ok ? 'PASS ✓' : 'FAIL ✗');
|
|
@@ -8,15 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
import { readFileSync } from 'node:fs'
|
|
10
10
|
import { chromium } from 'playwright'
|
|
11
|
-
import { SuiJsonRpcClient } from '@mysten/sui/jsonRpc'
|
|
12
11
|
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'
|
|
13
12
|
import { toBase64 } from '@mysten/sui/utils'
|
|
14
13
|
|
|
15
14
|
const APP_URL = process.env.APP_URL || 'http://localhost:4173'
|
|
16
|
-
const RPC = process.env.RPC || 'https://sui-testnet-rpc.publicnode.com'
|
|
17
15
|
const ICON = '/tmp/test-icon.svg'
|
|
18
16
|
|
|
19
|
-
const _client = new SuiJsonRpcClient({ url: RPC, network: 'testnet' })
|
|
20
17
|
const keypair = Ed25519Keypair.fromSecretKey(process.env.SUI_PRIV)
|
|
21
18
|
const address = keypair.toSuiAddress()
|
|
22
19
|
|
|
@@ -30,7 +27,7 @@ async function main() {
|
|
|
30
27
|
const browser = await chromium.launch({
|
|
31
28
|
headless: true,
|
|
32
29
|
executablePath: process.env.CHROME_PATH || '/usr/bin/google-chrome',
|
|
33
|
-
// Bypass CORS so the in-browser tx.build() can hit the testnet
|
|
30
|
+
// Bypass CORS so the in-browser tx.build() can hit the testnet gRPC endpoint.
|
|
34
31
|
args: ['--disable-web-security', '--disable-features=IsolateOrigins,site-per-process'],
|
|
35
32
|
})
|
|
36
33
|
const page = await browser.newPage()
|
package/scripts/gen-og-image.mjs
CHANGED
|
@@ -14,13 +14,13 @@ h1 .accent{color:#6ea8fe}
|
|
|
14
14
|
p{font-size:34px;line-height:1.35;color:#aebfd6;max-width:960px}
|
|
15
15
|
.row{display:flex;gap:18px;margin-top:52px}
|
|
16
16
|
.chip{font-size:24px;color:#cfe0f7;background:#16233d;border:1px solid #26365a;border-radius:10px;padding:12px 20px}
|
|
17
|
-
.url{position:absolute;
|
|
17
|
+
.url{position:absolute;top:84px;right:80px;font-size:26px;color:#6ea8fe;font-weight:600}
|
|
18
18
|
</style></head><body>
|
|
19
19
|
<div class="badge">Sui · client-side</div>
|
|
20
20
|
<h1>Create your own <span class="accent">Sui coin</span><br>in the browser</h1>
|
|
21
21
|
<p>Your wallet signs and pays gas — nothing is compiled or signed on a server. Keep the full, verifiable source package.</p>
|
|
22
22
|
<div class="row"><div class="chip">No server</div><div class="chip">No custody</div><div class="chip">Full source included</div></div>
|
|
23
|
-
<div class="url">
|
|
23
|
+
<div class="url">sui-token-deployer.meddleware.co.uk</div>
|
|
24
24
|
</body></html>`
|
|
25
25
|
|
|
26
26
|
const browser = await chromium.launch({ executablePath: process.env.CHROME_PATH || '/usr/bin/google-chrome' })
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { bcs } from '@mysten/bcs';
|
|
3
3
|
import * as tpl from '@mysten/move-bytecode-template';
|
|
4
|
-
import {
|
|
4
|
+
import { SuiGrpcClient } from '@mysten/sui/grpc';
|
|
5
5
|
import { requestSuiFromFaucetV2 } from '@mysten/sui/faucet';
|
|
6
6
|
import { Transaction } from '@mysten/sui/transactions';
|
|
7
7
|
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
|
|
8
8
|
import { toBase64 } from '@mysten/sui/utils';
|
|
9
|
+
import { toSuiTxResult } from '../src/lib/deploy.ts';
|
|
9
10
|
|
|
10
11
|
const init = tpl.default ?? tpl.init;
|
|
11
12
|
try { if (typeof init === 'function') await init(); } catch {}
|
|
12
13
|
|
|
13
|
-
const client = new
|
|
14
|
+
const client = new SuiGrpcClient({ baseUrl: 'http://127.0.0.1:9000', network: 'localnet' });
|
|
14
15
|
|
|
15
16
|
const orig = new Uint8Array(readFileSync(new URL('../src/move-template/sui_token_template.mv', import.meta.url)));
|
|
16
17
|
const json = tpl.deserialize(orig);
|
|
@@ -29,7 +30,7 @@ const addr = kp.toSuiAddress();
|
|
|
29
30
|
console.log('address:', addr);
|
|
30
31
|
await requestSuiFromFaucetV2({ host: 'http://127.0.0.1:9123', recipient: addr });
|
|
31
32
|
let bal = '0';
|
|
32
|
-
for (let i=0;i<25;i++){ const
|
|
33
|
+
for (let i=0;i<25;i++){ const { balance } = await client.getBalance({ owner: addr }); if (BigInt(balance.balance) > 0n){ bal=balance.balance; break;} await new Promise(r=>setTimeout(r,1000)); }
|
|
33
34
|
console.log('balance:', bal);
|
|
34
35
|
|
|
35
36
|
const tx = new Transaction();
|
|
@@ -39,7 +40,7 @@ tx.transferObjects([upgradeCap], addr);
|
|
|
39
40
|
tx.setGasBudget(500000000);
|
|
40
41
|
|
|
41
42
|
try {
|
|
42
|
-
const res = await client.signAndExecuteTransaction({ signer: kp, transaction: tx,
|
|
43
|
+
const res = toSuiTxResult(await client.signAndExecuteTransaction({ signer: kp, transaction: tx, include: { effects: true, objectTypes: true } }));
|
|
43
44
|
const status = res.effects?.status?.status;
|
|
44
45
|
console.log('\nstatus:', status);
|
|
45
46
|
if (status !== 'success') { console.log('ERR:', JSON.stringify(res.effects?.status)); process.exit(1); }
|
package/src/App.vue
CHANGED
|
@@ -9,12 +9,14 @@ const { mode: colorMode, set: setColorMode } = useColorMode('system')
|
|
|
9
9
|
<template>
|
|
10
10
|
<a class="skip-link" href="#main-content">Skip to main content</a>
|
|
11
11
|
<main id="main-content">
|
|
12
|
-
<
|
|
13
|
-
<ColorModeControl
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
<header class="page-header">
|
|
13
|
+
<ColorModeControl
|
|
14
|
+
class="theme-toggle"
|
|
15
|
+
:model-value="colorMode"
|
|
16
|
+
@update:model-value="setColorMode"
|
|
17
|
+
/>
|
|
16
18
|
<h1>Create your own Sui coin</h1>
|
|
17
|
-
<p class="lede">
|
|
19
|
+
<p v-once class="lede">
|
|
18
20
|
Deploy a coin on Sui directly from your browser. Your wallet signs and pays gas — nothing is
|
|
19
21
|
compiled or signed on a server — and you keep the full, verifiable source package.
|
|
20
22
|
</p>
|
|
@@ -24,7 +26,7 @@ const { mode: colorMode, set: setColorMode } = useColorMode('system')
|
|
|
24
26
|
|
|
25
27
|
<TokenDeployerView />
|
|
26
28
|
|
|
27
|
-
<footer v-once
|
|
29
|
+
<footer v-once class="page-footer">
|
|
28
30
|
<p>
|
|
29
31
|
Client-side token deployment. This tool never takes custody of your token or keys. Symbol,
|
|
30
32
|
name, and decimals are permanent once published.
|
package/src/component-styles.css
CHANGED
|
@@ -77,12 +77,16 @@ button:disabled {
|
|
|
77
77
|
flex-wrap: wrap;
|
|
78
78
|
align-items: center;
|
|
79
79
|
}
|
|
80
|
+
/* A row of form/navigation actions below a block of content. */
|
|
81
|
+
.actions {
|
|
82
|
+
margin-top: 1rem;
|
|
83
|
+
}
|
|
80
84
|
.grid2 {
|
|
81
85
|
display: grid;
|
|
82
86
|
grid-template-columns: 1fr;
|
|
83
87
|
gap: 0 1rem;
|
|
84
88
|
}
|
|
85
|
-
@media (
|
|
89
|
+
@media (width >= 520px) {
|
|
86
90
|
.grid2 {
|
|
87
91
|
grid-template-columns: 1fr 1fr;
|
|
88
92
|
}
|
|
@@ -144,18 +148,6 @@ details > fieldset {
|
|
|
144
148
|
background: var(--border);
|
|
145
149
|
}
|
|
146
150
|
|
|
147
|
-
.visually-hidden {
|
|
148
|
-
position: absolute;
|
|
149
|
-
width: 1px;
|
|
150
|
-
height: 1px;
|
|
151
|
-
padding: 0;
|
|
152
|
-
margin: -1px;
|
|
153
|
-
overflow: hidden;
|
|
154
|
-
clip: rect(0 0 0 0);
|
|
155
|
-
white-space: nowrap;
|
|
156
|
-
border: 0;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
151
|
.skip-link {
|
|
160
152
|
position: absolute;
|
|
161
153
|
left: 0.5rem;
|
|
@@ -217,31 +209,6 @@ details > fieldset {
|
|
|
217
209
|
}
|
|
218
210
|
}
|
|
219
211
|
|
|
220
|
-
dialog {
|
|
221
|
-
background: var(--surface);
|
|
222
|
-
border: 1px solid var(--border);
|
|
223
|
-
border-radius: var(--radius);
|
|
224
|
-
padding: 1.5rem;
|
|
225
|
-
width: min(420px, 90vw);
|
|
226
|
-
max-height: 90vh;
|
|
227
|
-
overflow-y: auto;
|
|
228
|
-
color: var(--text);
|
|
229
|
-
}
|
|
230
|
-
dialog::backdrop {
|
|
231
|
-
background: rgba(0, 0, 0, 0.45);
|
|
232
|
-
-webkit-backdrop-filter: blur(2px);
|
|
233
|
-
backdrop-filter: blur(2px);
|
|
234
|
-
}
|
|
235
|
-
.dialog-header {
|
|
236
|
-
display: flex;
|
|
237
|
-
justify-content: space-between;
|
|
238
|
-
align-items: center;
|
|
239
|
-
margin-bottom: 1.25rem;
|
|
240
|
-
}
|
|
241
|
-
.dialog-header h2 {
|
|
242
|
-
margin: 0;
|
|
243
|
-
font-size: 1.1rem;
|
|
244
|
-
}
|
|
245
212
|
.wallet-list {
|
|
246
213
|
display: flex;
|
|
247
214
|
flex-direction: column;
|
|
@@ -43,7 +43,6 @@ function onBlur(key: string): void {
|
|
|
43
43
|
|
|
44
44
|
<template>
|
|
45
45
|
<form novalidate @submit.prevent="emit('next')">
|
|
46
|
-
|
|
47
46
|
<!-- Step 1: Identity -->
|
|
48
47
|
<template v-if="formStep === 'identity'">
|
|
49
48
|
<fieldset>
|
|
@@ -57,18 +56,32 @@ function onBlur(key: string): void {
|
|
|
57
56
|
</UiFieldHint>
|
|
58
57
|
</template>
|
|
59
58
|
<template #default="{ attrs }">
|
|
60
|
-
<input
|
|
59
|
+
<input
|
|
60
|
+
type="text"
|
|
61
|
+
v-bind="attrs"
|
|
62
|
+
v-model="form.packageName"
|
|
63
|
+
placeholder="my_token"
|
|
64
|
+
autocomplete="off"
|
|
65
|
+
@blur="onBlur('packageName')"
|
|
66
|
+
/>
|
|
61
67
|
</template>
|
|
62
68
|
</UiFormField>
|
|
63
69
|
<UiFormField id="module" label="Module name" :error="visibleError('moduleName')">
|
|
64
70
|
<template #label-suffix>
|
|
65
71
|
<UiFieldHint field-id="module-name">
|
|
66
|
-
The Move module inside the package (lowercase, e.g. <b>mytoken</b>). The coin type
|
|
67
|
-
its one-time-witness struct are derived from this name, so it is permanent too.
|
|
72
|
+
The Move module inside the package (lowercase, e.g. <b>mytoken</b>). The coin type
|
|
73
|
+
and its one-time-witness struct are derived from this name, so it is permanent too.
|
|
68
74
|
</UiFieldHint>
|
|
69
75
|
</template>
|
|
70
76
|
<template #default="{ attrs }">
|
|
71
|
-
<input
|
|
77
|
+
<input
|
|
78
|
+
type="text"
|
|
79
|
+
v-bind="attrs"
|
|
80
|
+
v-model="form.moduleName"
|
|
81
|
+
placeholder="mytoken"
|
|
82
|
+
autocomplete="off"
|
|
83
|
+
@blur="onBlur('moduleName')"
|
|
84
|
+
/>
|
|
72
85
|
</template>
|
|
73
86
|
</UiFormField>
|
|
74
87
|
</div>
|
|
@@ -78,8 +91,8 @@ function onBlur(key: string): void {
|
|
|
78
91
|
</template>
|
|
79
92
|
<template v-else>
|
|
80
93
|
Coin type will be <span class="mono">{{ coinTypePreview }}</span
|
|
81
|
-
>. The witness struct <span class="mono">{{ structPreview }}</span> is derived from
|
|
82
|
-
|
|
94
|
+
>. The witness struct <span class="mono">{{ structPreview }}</span> is derived from the
|
|
95
|
+
module name (a Sui requirement).
|
|
83
96
|
</template>
|
|
84
97
|
</p>
|
|
85
98
|
</fieldset>
|
|
@@ -94,22 +107,35 @@ function onBlur(key: string): void {
|
|
|
94
107
|
<UiFormField id="name" label="Token name" :error="visibleError('name')">
|
|
95
108
|
<template #label-suffix>
|
|
96
109
|
<UiFieldHint field-id="token-name">
|
|
97
|
-
The human-readable display name shown in wallets and explorers (e.g.
|
|
98
|
-
Editable later if you keep the Updatable metadata policy.
|
|
110
|
+
The human-readable display name shown in wallets and explorers (e.g.
|
|
111
|
+
<b>My Token</b>). Editable later if you keep the Updatable metadata policy.
|
|
99
112
|
</UiFieldHint>
|
|
100
113
|
</template>
|
|
101
114
|
<template #default="{ attrs }">
|
|
102
|
-
<input
|
|
115
|
+
<input
|
|
116
|
+
type="text"
|
|
117
|
+
v-bind="attrs"
|
|
118
|
+
v-model="form.name"
|
|
119
|
+
autocomplete="off"
|
|
120
|
+
@blur="onBlur('name')"
|
|
121
|
+
/>
|
|
103
122
|
</template>
|
|
104
123
|
</UiFormField>
|
|
105
124
|
<UiFormField id="symbol" label="Symbol" :error="visibleError('symbol')">
|
|
106
125
|
<template #label-suffix>
|
|
107
126
|
<UiFieldHint field-id="token-symbol">
|
|
108
|
-
The short ticker shown in wallets (e.g. <b>MYT</b>), typically 3–5 uppercase
|
|
127
|
+
The short ticker shown in wallets (e.g. <b>MYT</b>), typically 3–5 uppercase
|
|
128
|
+
letters.
|
|
109
129
|
</UiFieldHint>
|
|
110
130
|
</template>
|
|
111
131
|
<template #default="{ attrs }">
|
|
112
|
-
<input
|
|
132
|
+
<input
|
|
133
|
+
type="text"
|
|
134
|
+
v-bind="attrs"
|
|
135
|
+
v-model="form.symbol"
|
|
136
|
+
autocomplete="off"
|
|
137
|
+
@blur="onBlur('symbol')"
|
|
138
|
+
/>
|
|
113
139
|
</template>
|
|
114
140
|
</UiFormField>
|
|
115
141
|
</div>
|
|
@@ -118,8 +144,8 @@ function onBlur(key: string): void {
|
|
|
118
144
|
<template #label-suffix>
|
|
119
145
|
<span class="hint">(optional)</span>
|
|
120
146
|
<UiFieldHint field-id="token-description">
|
|
121
|
-
A short blurb stored in the coin metadata and shown in some wallets/explorers.
|
|
122
|
-
|
|
147
|
+
A short blurb stored in the coin metadata and shown in some wallets/explorers. Leave
|
|
148
|
+
blank if you don't need one.
|
|
123
149
|
</UiFieldHint>
|
|
124
150
|
</template>
|
|
125
151
|
<template #default="{ attrs }">
|
|
@@ -136,7 +162,13 @@ function onBlur(key: string): void {
|
|
|
136
162
|
</UiFieldHint>
|
|
137
163
|
</template>
|
|
138
164
|
<template #default="{ attrs }">
|
|
139
|
-
<input
|
|
165
|
+
<input
|
|
166
|
+
type="text"
|
|
167
|
+
v-bind="attrs"
|
|
168
|
+
v-model="form.decimals"
|
|
169
|
+
inputmode="numeric"
|
|
170
|
+
@blur="onBlur('decimals')"
|
|
171
|
+
/>
|
|
140
172
|
</template>
|
|
141
173
|
</UiFormField>
|
|
142
174
|
|
|
@@ -158,14 +190,21 @@ function onBlur(key: string): void {
|
|
|
158
190
|
<UiFormField id="supply" label="Initial supply" :error="visibleError('initialSupply')">
|
|
159
191
|
<template #label-suffix>
|
|
160
192
|
<UiFieldHint field-id="supply">
|
|
161
|
-
The number of whole tokens to mint and send to the recipient at deployment.
|
|
162
|
-
|
|
163
|
-
Enter <b>0</b> or leave blank for no initial supply.
|
|
164
|
-
|
|
193
|
+
The number of whole tokens to mint and send to the recipient at deployment. Uses your
|
|
194
|
+
chosen decimal precision — with 9 decimals, 1 token = 1,000,000,000 base units
|
|
195
|
+
on-chain. Enter <b>0</b> or leave blank for no initial supply. You can mint more later
|
|
196
|
+
if you choose the Mintable supply policy.
|
|
165
197
|
</UiFieldHint>
|
|
166
198
|
</template>
|
|
167
199
|
<template #default="{ attrs }">
|
|
168
|
-
<input
|
|
200
|
+
<input
|
|
201
|
+
type="text"
|
|
202
|
+
v-bind="attrs"
|
|
203
|
+
v-model="form.initialSupply"
|
|
204
|
+
inputmode="numeric"
|
|
205
|
+
placeholder="0"
|
|
206
|
+
@blur="onBlur('initialSupply')"
|
|
207
|
+
/>
|
|
169
208
|
</template>
|
|
170
209
|
</UiFormField>
|
|
171
210
|
|
|
@@ -204,8 +243,8 @@ function onBlur(key: string): void {
|
|
|
204
243
|
<UiFormField id="package-policy" label="Package code">
|
|
205
244
|
<template #label-suffix>
|
|
206
245
|
<UiFieldHint field-id="package-policy">
|
|
207
|
-
<b>Immutable</b>: the UpgradeCap is burned — the on-chain code is sealed.
|
|
208
|
-
|
|
246
|
+
<b>Immutable</b>: the UpgradeCap is burned — the on-chain code is sealed. Recommended
|
|
247
|
+
for trust and auditability.<br />
|
|
209
248
|
<b>Upgradeable</b>: you keep the UpgradeCap and can push code changes to the package
|
|
210
249
|
after deployment.
|
|
211
250
|
</UiFieldHint>
|
|
@@ -221,14 +260,21 @@ function onBlur(key: string): void {
|
|
|
221
260
|
<UiFormField id="recipient" label="Recipient" :error="visibleError('recipient')">
|
|
222
261
|
<template #label-suffix>
|
|
223
262
|
<UiFieldHint field-id="recipient">
|
|
224
|
-
The Sui address that receives the TreasuryCap, MetadataCap, UpgradeCap (if kept),
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
263
|
+
The Sui address that receives the TreasuryCap, MetadataCap, UpgradeCap (if kept), and
|
|
264
|
+
any initial supply minted at deployment. Leave blank to send everything to the
|
|
265
|
+
connected wallet. Enter a different address to separate the deployer wallet from the
|
|
266
|
+
custody wallet.
|
|
228
267
|
</UiFieldHint>
|
|
229
268
|
</template>
|
|
230
269
|
<template #default="{ attrs }">
|
|
231
|
-
<input
|
|
270
|
+
<input
|
|
271
|
+
type="text"
|
|
272
|
+
v-bind="attrs"
|
|
273
|
+
v-model="form.recipient"
|
|
274
|
+
placeholder="0x…"
|
|
275
|
+
autocomplete="off"
|
|
276
|
+
@blur="onBlur('recipient')"
|
|
277
|
+
/>
|
|
232
278
|
</template>
|
|
233
279
|
</UiFormField>
|
|
234
280
|
|
|
@@ -236,7 +282,7 @@ function onBlur(key: string): void {
|
|
|
236
282
|
</fieldset>
|
|
237
283
|
</template>
|
|
238
284
|
|
|
239
|
-
<div class="row
|
|
285
|
+
<div class="row actions">
|
|
240
286
|
<button v-if="formStep !== 'identity'" type="button" @click="emit('back')">Back</button>
|
|
241
287
|
<button type="submit" class="primary" :disabled="!canProceed">
|
|
242
288
|
{{ formStep === 'settings' ? 'Review & deploy' : 'Next' }}
|
|
@@ -25,9 +25,7 @@ function getState(i: number): 'done' | 'active' | 'todo' {
|
|
|
25
25
|
return 'todo'
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const steps = computed(() =>
|
|
29
|
-
ORDER.map((s, i) => ({ ...s, state: getState(i) }))
|
|
30
|
-
)
|
|
28
|
+
const steps = computed(() => ORDER.map((s, i) => ({ ...s, state: getState(i) })))
|
|
31
29
|
</script>
|
|
32
30
|
|
|
33
31
|
<template>
|
|
@@ -38,7 +36,7 @@ const steps = computed(() =>
|
|
|
38
36
|
<span v-if="s.state === 'active'" class="spinner" aria-hidden="true"></span>
|
|
39
37
|
<span v-else-if="s.state === 'done'" aria-hidden="true">✓ </span>
|
|
40
38
|
{{ s.label }}
|
|
41
|
-
<span v-if="s.state === 'active'" class="visually-hidden">(in progress)</span>
|
|
39
|
+
<span v-if="s.state === 'active'" class="mw-visually-hidden">(in progress)</span>
|
|
42
40
|
</li>
|
|
43
41
|
</ol>
|
|
44
42
|
<p class="hint">Keep this tab open and approve the wallet prompts.</p>
|
|
@@ -56,7 +56,7 @@ async function push(): Promise<void> {
|
|
|
56
56
|
{{ open ? '▾' : '▸' }} Create a GitHub repo (optional)
|
|
57
57
|
</button>
|
|
58
58
|
|
|
59
|
-
<div v-if="open" class="card
|
|
59
|
+
<div v-if="open" class="card github-push__form">
|
|
60
60
|
<p v-once class="hint">
|
|
61
61
|
Uses your <strong>own</strong> GitHub token — the repo is created in your account and this
|
|
62
62
|
site's servers are never involved. The token is sent only to
|
|
@@ -67,24 +67,23 @@ async function push(): Promise<void> {
|
|
|
67
67
|
</p>
|
|
68
68
|
|
|
69
69
|
<label for="gh-repo">Repository name</label>
|
|
70
|
-
<input id="gh-repo" v-model="repoName" autocomplete="off" />
|
|
70
|
+
<input id="gh-repo" v-model="repoName" type="text" autocomplete="off" />
|
|
71
71
|
|
|
72
72
|
<label for="gh-token">GitHub token</label>
|
|
73
73
|
<input
|
|
74
74
|
id="gh-token"
|
|
75
75
|
v-model="token"
|
|
76
76
|
type="password"
|
|
77
|
-
autocomplete="
|
|
77
|
+
autocomplete="new-password"
|
|
78
78
|
spellcheck="false"
|
|
79
79
|
placeholder="github_pat_… or ghp_…"
|
|
80
80
|
/>
|
|
81
81
|
|
|
82
|
-
<label
|
|
83
|
-
<input
|
|
84
|
-
private
|
|
82
|
+
<label class="github-push__private">
|
|
83
|
+
<input v-model="isPrivate" type="checkbox" /> Make the repository private
|
|
85
84
|
</label>
|
|
86
85
|
|
|
87
|
-
<div class="row
|
|
86
|
+
<div class="row github-push__actions">
|
|
88
87
|
<button type="button" class="primary" :disabled="busy || !token || !repoName" @click="push">
|
|
89
88
|
<span v-if="busy" class="spinner" aria-hidden="true"></span> Create repo & push
|
|
90
89
|
</button>
|
|
@@ -98,3 +97,19 @@ async function push(): Promise<void> {
|
|
|
98
97
|
</div>
|
|
99
98
|
</div>
|
|
100
99
|
</template>
|
|
100
|
+
|
|
101
|
+
<style scoped>
|
|
102
|
+
.github-push__form {
|
|
103
|
+
margin-top: 0.5rem;
|
|
104
|
+
}
|
|
105
|
+
.github-push__private {
|
|
106
|
+
margin-top: 0.75rem;
|
|
107
|
+
font-weight: 400;
|
|
108
|
+
}
|
|
109
|
+
.github-push__private input {
|
|
110
|
+
width: auto;
|
|
111
|
+
}
|
|
112
|
+
.github-push__actions {
|
|
113
|
+
margin-top: 0.75rem;
|
|
114
|
+
}
|
|
115
|
+
</style>
|