@meddleware/token-deployer-ui 0.0.1
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/.claude/move-language-reference/SKILL.md +445 -0
- package/.claude/skills/move-language-reference/SKILL.md +445 -0
- package/.claude/skills/sui-move-bootcamp/SKILL.md +128 -0
- package/.claude/sui-move-bootcamp/SKILL.md +128 -0
- package/.editorconfig +8 -0
- package/.env.example +75 -0
- package/.env.production +7 -0
- package/.gitattributes +1 -0
- package/.github/workflows/ci.yml +34 -0
- package/.github/workflows/token-deployer-e2e.yml +64 -0
- package/.oxfmtrc.json +5 -0
- package/.oxlintrc.json +10 -0
- package/.vscode/extensions.json +9 -0
- package/AGENTS.md +39 -0
- package/CLAUDE.md +159 -0
- package/LICENSE +121 -0
- package/README.md +234 -0
- package/env.d.ts +13 -0
- package/index.html +13 -0
- package/package.json +67 -0
- package/playwright.config.ts +30 -0
- package/public/CNAME +1 -0
- package/public/_headers +12 -0
- package/public/_redirects +1 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +5 -0
- package/public/og-image.png +0 -0
- package/public/robots.txt +3 -0
- package/public/sitemap.xml +8 -0
- package/scripts/e2e-browser.mjs +15 -0
- package/scripts/e2e-deploy.mjs +222 -0
- package/scripts/e2e-localnet.mjs +65 -0
- package/scripts/e2e-walrus-browser.mjs +115 -0
- package/scripts/gen-og-image.mjs +31 -0
- package/scripts/publish-localnet.mjs +55 -0
- package/scripts/regen-template.sh +45 -0
- package/scripts/sync-template-src.sh +36 -0
- package/scripts/verify-bytecode.mjs +49 -0
- package/src/App.vue +41 -0
- package/src/__tests__/App.spec.js +11 -0
- package/src/components/AppNotice.vue +15 -0
- package/src/components/ColorModeControl.vue +81 -0
- package/src/components/ConfigForm.vue +192 -0
- package/src/components/DeployProgress.vue +46 -0
- package/src/components/FieldHint.vue +79 -0
- package/src/components/FormField.vue +28 -0
- package/src/components/FormSelect.vue +43 -0
- package/src/components/GithubPush.vue +100 -0
- package/src/components/IconPicker.vue +410 -0
- package/src/components/LicensePicker.vue +182 -0
- package/src/components/ResultPanel.vue +88 -0
- package/src/components/ReviewPanel.vue +69 -0
- package/src/components/TermsDialog.vue +81 -0
- package/src/components/TokenDeployerView.vue +115 -0
- package/src/components/WalletBar.vue +108 -0
- package/src/components/WalrusBlobBrowser.vue +188 -0
- package/src/components/WalrusIconUpload.vue +205 -0
- package/src/composables/useAccessGate.ts +122 -0
- package/src/composables/useColorMode.ts +76 -0
- package/src/composables/useWalrusRelay.ts +21 -0
- package/src/config.ts +185 -0
- package/src/index.ts +1 -0
- package/src/lib/accessGate.ts +141 -0
- package/src/lib/buildPublishTx.ts +192 -0
- package/src/lib/deploy.ts +168 -0
- package/src/lib/deployExecutor.ts +61 -0
- package/src/lib/errors.ts +21 -0
- package/src/lib/form.ts +75 -0
- package/src/lib/generatePackage.ts +312 -0
- package/src/lib/github.ts +146 -0
- package/src/lib/licenses.ts +122 -0
- package/src/lib/readClient.ts +17 -0
- package/src/lib/template.ts +131 -0
- package/src/lib/types.ts +69 -0
- package/src/lib/validation.ts +172 -0
- package/src/lib/walrus-constants.ts +15 -0
- package/src/lib/walrus.ts +144 -0
- package/src/main.ts +154 -0
- package/src/move-template/sui_token_template.mv +0 -0
- package/src/move-template/template.ts +31 -0
- package/src/styles.css +384 -0
- package/src/template-src/files.json +10 -0
- package/test-results/.last-run.json +4 -0
- package/tests/accessGate.test.ts +108 -0
- package/tests/buildPublishTx.test.ts +243 -0
- package/tests/deploy.test.ts +81 -0
- package/tests/errors.test.ts +32 -0
- package/tests/generatePackage.test.ts +182 -0
- package/tests/github.test.ts +61 -0
- package/tests/licenses.test.ts +78 -0
- package/tests/template.test.ts +93 -0
- package/tests/templateArtifact.test.ts +61 -0
- package/tests/templateParity.test.ts +55 -0
- package/tests/useAccessGate.test.ts +101 -0
- package/tests/useWallet.test.ts +74 -0
- package/tests/useWalrusRelayGating.test.ts +30 -0
- package/tests/validation.test.ts +117 -0
- package/tests/walrusUpload.test.ts +93 -0
- package/tests/walrus_url_patch.test.ts +26 -0
- package/tsconfig.json +19 -0
- package/typedoc.json +34 -0
- package/vite.config.ts +65 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
|
|
3
|
+
// Capture the options handed to `walrus(...)` and the SuiGrpcClient so we can
|
|
4
|
+
// assert the relay host + tip guard are wired, without a live network. `vi.mock`
|
|
5
|
+
// is hoisted, so the mock fns are declared via `vi.hoisted`.
|
|
6
|
+
const { walrusMock, grpcCtor, extendMock } = vi.hoisted(() => {
|
|
7
|
+
const extendMock = vi.fn<(ext: unknown) => { extendedWith: unknown }>((ext: unknown) => ({ extendedWith: ext }))
|
|
8
|
+
return {
|
|
9
|
+
walrusMock: vi.fn<(_cfg?: unknown) => { __walrusExtension: boolean }>((_cfg?: unknown) => ({ __walrusExtension: true })),
|
|
10
|
+
extendMock,
|
|
11
|
+
// `src/lib/walrus.ts` calls `new SuiGrpcClient(...)`, so the mock must be newable — an
|
|
12
|
+
// arrow function is not a constructor (`new arrowFn()` throws). Use a function expression.
|
|
13
|
+
grpcCtor: vi.fn<() => { $extend: typeof extendMock }>(function () {
|
|
14
|
+
return { $extend: extendMock }
|
|
15
|
+
}),
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
vi.mock('@mysten/walrus', () => ({ walrus: walrusMock }))
|
|
19
|
+
vi.mock('@mysten/sui/grpc', () => ({ SuiGrpcClient: grpcCtor }))
|
|
20
|
+
|
|
21
|
+
import { createWalrusClient, walrusBlobUrl } from '../src/lib/walrus.js'
|
|
22
|
+
import {
|
|
23
|
+
WALRUS_RELAY_HOSTS,
|
|
24
|
+
WALRUS_MAX_TIP_MIST,
|
|
25
|
+
validateIconFile,
|
|
26
|
+
ICON_MAX_BYTES,
|
|
27
|
+
} from '../src/config.js'
|
|
28
|
+
|
|
29
|
+
type WalrusCfg = { wasmUrl?: string; uploadRelay: { host: string; sendTip: { max: number } } }
|
|
30
|
+
|
|
31
|
+
describe('createWalrusClient', () => {
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
walrusMock.mockClear()
|
|
34
|
+
grpcCtor.mockClear()
|
|
35
|
+
extendMock.mockClear()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('defaults to the configured relay host + max-tip guard', () => {
|
|
39
|
+
createWalrusClient('testnet', 'wasm-url')
|
|
40
|
+
expect(walrusMock).toHaveBeenCalledTimes(1)
|
|
41
|
+
const cfg = walrusMock.mock.calls[0][0] as unknown as WalrusCfg
|
|
42
|
+
expect(cfg.uploadRelay.host).toBe(WALRUS_RELAY_HOSTS.testnet)
|
|
43
|
+
expect(cfg.uploadRelay.sendTip.max).toBe(Number(WALRUS_MAX_TIP_MIST))
|
|
44
|
+
// The bare-string arg is treated as the WASM URL (back-compat).
|
|
45
|
+
expect(cfg.wasmUrl).toBe('wasm-url')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('honours an explicit relay host + max-tip override', () => {
|
|
49
|
+
createWalrusClient('mainnet', {
|
|
50
|
+
uploadRelayHost: 'https://my.relay.example',
|
|
51
|
+
uploadRelayMaxTipMist: 123n,
|
|
52
|
+
})
|
|
53
|
+
const cfg = walrusMock.mock.calls[0][0] as unknown as WalrusCfg
|
|
54
|
+
expect(cfg.uploadRelay.host).toBe('https://my.relay.example')
|
|
55
|
+
expect(cfg.uploadRelay.sendTip.max).toBe(123)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('never sets a tip address/kind (the relay declares those via /v1/tip-config)', () => {
|
|
59
|
+
createWalrusClient('testnet')
|
|
60
|
+
const cfg = walrusMock.mock.calls[0][0] as unknown as WalrusCfg & {
|
|
61
|
+
uploadRelay: { sendTip: Record<string, unknown> }
|
|
62
|
+
}
|
|
63
|
+
expect(cfg.uploadRelay.sendTip).not.toHaveProperty('address')
|
|
64
|
+
expect(cfg.uploadRelay.sendTip).not.toHaveProperty('kind')
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe('walrusBlobUrl', () => {
|
|
69
|
+
it('points at the network aggregator raw-blob route', () => {
|
|
70
|
+
expect(walrusBlobUrl('testnet', 'BLOB123')).toContain('/v1/blobs/BLOB123')
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('validateIconFile (UX/cost gate — not a security control)', () => {
|
|
75
|
+
it('accepts an in-spec PNG', () => {
|
|
76
|
+
expect(validateIconFile({ type: 'image/png', size: 10 * 1024 })).toBeNull()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('rejects a disallowed MIME type', () => {
|
|
80
|
+
const msg = validateIconFile({ type: 'application/pdf', size: 1024 })
|
|
81
|
+
expect(msg).toMatch(/Unsupported image type/)
|
|
82
|
+
expect(msg).toContain('pdf')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('rejects a file over the size cap', () => {
|
|
86
|
+
const msg = validateIconFile({ type: 'image/png', size: ICON_MAX_BYTES + 1 })
|
|
87
|
+
expect(msg).toMatch(/too large/)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('accepts a file exactly at the cap', () => {
|
|
91
|
+
expect(validateIconFile({ type: 'image/webp', size: ICON_MAX_BYTES })).toBeNull()
|
|
92
|
+
})
|
|
93
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import init, { deserialize } from '@mysten/move-bytecode-template'
|
|
3
|
+
import { patchTemplateModule } from '../src/lib/template.js'
|
|
4
|
+
|
|
5
|
+
const asStr = (data: number[]) => Buffer.from(data.slice(1)).toString('utf8')
|
|
6
|
+
interface Decoded { constant_pool: { type_: unknown; data: number[] }[] }
|
|
7
|
+
|
|
8
|
+
describe('Walrus blob URL patching', () => {
|
|
9
|
+
it('patches a 99-char Walrus blob URL correctly', async () => {
|
|
10
|
+
await (init as unknown as () => Promise<unknown>)()
|
|
11
|
+
const BLOB_URL = 'https://aggregator.walrus-testnet.walrus.space/v1/blobs/OVB5WTWEqkOktRXHIXTBybPAERD0wfORn8NQ1a2lbFQ'
|
|
12
|
+
const bytes = await patchTemplateModule({
|
|
13
|
+
moduleName: 'mycoin', structName: 'MYCOIN',
|
|
14
|
+
symbol: 'MYC', name: 'My Coin', description: 'A test',
|
|
15
|
+
iconUrl: BLOB_URL, decimals: 9,
|
|
16
|
+
})
|
|
17
|
+
// Valid magic bytes
|
|
18
|
+
expect(Buffer.from(bytes.slice(0, 4)).toString('hex')).toBe('a11ceb0b')
|
|
19
|
+
// Round-trips through deserialize
|
|
20
|
+
const back = deserialize(bytes) as unknown as Decoded
|
|
21
|
+
// Icon URL constant present
|
|
22
|
+
const iconEntry = back.constant_pool.find(c => c.type_ !== 'U8' && asStr(c.data).startsWith('https://aggregator'))
|
|
23
|
+
expect(iconEntry).toBeTruthy()
|
|
24
|
+
expect(asStr(iconEntry!.data)).toBe(BLOB_URL)
|
|
25
|
+
})
|
|
26
|
+
})
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "esnext",
|
|
4
|
+
"lib": ["esnext", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "esnext",
|
|
6
|
+
"moduleResolution": "Bundler",
|
|
7
|
+
"resolveJsonModule": true,
|
|
8
|
+
"isolatedModules": true,
|
|
9
|
+
"allowSyntheticDefaultImports": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"strict": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"jsx": "preserve",
|
|
16
|
+
"types": ["node", "vite/client"]
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*.ts", "src/**/*.vue", "tests/**/*.ts", "env.d.ts"]
|
|
19
|
+
}
|
package/typedoc.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://typedoc.org/schema.json",
|
|
3
|
+
"entryPoints": [
|
|
4
|
+
"src/config.ts",
|
|
5
|
+
"src/lib/types.ts",
|
|
6
|
+
"src/lib/validation.ts",
|
|
7
|
+
"src/lib/form.ts",
|
|
8
|
+
"src/lib/template.ts",
|
|
9
|
+
"src/lib/buildPublishTx.ts",
|
|
10
|
+
"src/lib/deploy.ts",
|
|
11
|
+
"src/lib/generatePackage.ts",
|
|
12
|
+
"src/lib/licenses.ts",
|
|
13
|
+
"src/lib/github.ts",
|
|
14
|
+
"src/lib/walrus.ts",
|
|
15
|
+
"src/lib/walrus-constants.ts",
|
|
16
|
+
"src/composables/useWallet.ts",
|
|
17
|
+
"src/composables/useSuiClient.ts"
|
|
18
|
+
],
|
|
19
|
+
"out": "docs/api",
|
|
20
|
+
"readme": "none",
|
|
21
|
+
"excludeInternal": true,
|
|
22
|
+
"excludePrivate": true,
|
|
23
|
+
"excludeExternals": true,
|
|
24
|
+
"includeVersion": true,
|
|
25
|
+
"skipErrorChecking": true,
|
|
26
|
+
"categorizeByGroup": true,
|
|
27
|
+
"validation": {
|
|
28
|
+
"invalidLink": false,
|
|
29
|
+
"notExported": false
|
|
30
|
+
},
|
|
31
|
+
"navigationLinks": {
|
|
32
|
+
"Repository": "https://github.com/meddleware"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { defineConfig, loadEnv } from 'vite'
|
|
2
|
+
import vue from '@vitejs/plugin-vue'
|
|
3
|
+
|
|
4
|
+
const ZERO_ADDR = '0x' + '0'.repeat(64)
|
|
5
|
+
|
|
6
|
+
// Networks a production build must have a real (non-zero) treasury for. Mirrors
|
|
7
|
+
// SELECTABLE_NETWORKS in src/config.ts — kept as a literal here to avoid importing
|
|
8
|
+
// app code into the build config.
|
|
9
|
+
const REQUIRED_TREASURY_NETWORKS = ['TESTNET', 'MAINNET'] as const
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Fail a production build when a selectable network's fee treasury is still the
|
|
13
|
+
* zero address, so revenue can never silently burn to 0x0. Bypass for dev/e2e
|
|
14
|
+
* builds with VITE_ALLOW_UNSET_TREASURY=1 (or VITE_E2E=1).
|
|
15
|
+
*/
|
|
16
|
+
function assertTreasuryConfigured(env: Record<string, string>): void {
|
|
17
|
+
if (env.VITE_ALLOW_UNSET_TREASURY === '1' || env.VITE_E2E === '1') return
|
|
18
|
+
const unset = REQUIRED_TREASURY_NETWORKS.filter((n) => {
|
|
19
|
+
const v = env[`VITE_FEE_TREASURY_${n}`]
|
|
20
|
+
return !v || v === ZERO_ADDR
|
|
21
|
+
})
|
|
22
|
+
if (unset.length > 0) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Refusing to build: fee treasury unset (zero address) for ${unset.join(', ')}. ` +
|
|
25
|
+
`Set VITE_FEE_TREASURY_${unset[0]} (see .env.production/.env.example), ` +
|
|
26
|
+
`or set VITE_ALLOW_UNSET_TREASURY=1 for a dev/e2e build.`,
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// The move-bytecode-template package ships a .wasm that must be served as an
|
|
32
|
+
// asset; `wasm` is excluded from dep-optimization so its URL import resolves.
|
|
33
|
+
export default defineConfig(({ command, mode }) => {
|
|
34
|
+
const env = loadEnv(mode, process.cwd(), '')
|
|
35
|
+
if (command === 'build' && mode === 'production') assertTreasuryConfigured(env)
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
base: "/",
|
|
39
|
+
plugins: [vue()],
|
|
40
|
+
optimizeDeps: {
|
|
41
|
+
exclude: ['@mysten/move-bytecode-template'],
|
|
42
|
+
},
|
|
43
|
+
build: {
|
|
44
|
+
target: 'esnext',
|
|
45
|
+
rollupOptions: {
|
|
46
|
+
output: {
|
|
47
|
+
// Pin the Sui SDK into its own long-lived vendor chunk so app changes
|
|
48
|
+
// don't bust its cache. Matched by module id (not package entry, which
|
|
49
|
+
// @mysten/sui does not expose). @mysten/walrus + move-bytecode-template
|
|
50
|
+
// are explicitly left out so they stay in their lazy dynamic chunks.
|
|
51
|
+
manualChunks(id: string) {
|
|
52
|
+
if (id.includes('@mysten/walrus') || id.includes('move-bytecode-template')) return
|
|
53
|
+
if (
|
|
54
|
+
id.includes('@mysten/sui') ||
|
|
55
|
+
id.includes('@mysten/bcs') ||
|
|
56
|
+
id.includes('@mysten/wallet-standard')
|
|
57
|
+
) {
|
|
58
|
+
return 'vendor-sui'
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
})
|