@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.
Files changed (103) hide show
  1. package/.claude/move-language-reference/SKILL.md +445 -0
  2. package/.claude/skills/move-language-reference/SKILL.md +445 -0
  3. package/.claude/skills/sui-move-bootcamp/SKILL.md +128 -0
  4. package/.claude/sui-move-bootcamp/SKILL.md +128 -0
  5. package/.editorconfig +8 -0
  6. package/.env.example +75 -0
  7. package/.env.production +7 -0
  8. package/.gitattributes +1 -0
  9. package/.github/workflows/ci.yml +34 -0
  10. package/.github/workflows/token-deployer-e2e.yml +64 -0
  11. package/.oxfmtrc.json +5 -0
  12. package/.oxlintrc.json +10 -0
  13. package/.vscode/extensions.json +9 -0
  14. package/AGENTS.md +39 -0
  15. package/CLAUDE.md +159 -0
  16. package/LICENSE +121 -0
  17. package/README.md +234 -0
  18. package/env.d.ts +13 -0
  19. package/index.html +13 -0
  20. package/package.json +67 -0
  21. package/playwright.config.ts +30 -0
  22. package/public/CNAME +1 -0
  23. package/public/_headers +12 -0
  24. package/public/_redirects +1 -0
  25. package/public/favicon.ico +0 -0
  26. package/public/favicon.svg +5 -0
  27. package/public/og-image.png +0 -0
  28. package/public/robots.txt +3 -0
  29. package/public/sitemap.xml +8 -0
  30. package/scripts/e2e-browser.mjs +15 -0
  31. package/scripts/e2e-deploy.mjs +222 -0
  32. package/scripts/e2e-localnet.mjs +65 -0
  33. package/scripts/e2e-walrus-browser.mjs +115 -0
  34. package/scripts/gen-og-image.mjs +31 -0
  35. package/scripts/publish-localnet.mjs +55 -0
  36. package/scripts/regen-template.sh +45 -0
  37. package/scripts/sync-template-src.sh +36 -0
  38. package/scripts/verify-bytecode.mjs +49 -0
  39. package/src/App.vue +41 -0
  40. package/src/__tests__/App.spec.js +11 -0
  41. package/src/components/AppNotice.vue +15 -0
  42. package/src/components/ColorModeControl.vue +81 -0
  43. package/src/components/ConfigForm.vue +192 -0
  44. package/src/components/DeployProgress.vue +46 -0
  45. package/src/components/FieldHint.vue +79 -0
  46. package/src/components/FormField.vue +28 -0
  47. package/src/components/FormSelect.vue +43 -0
  48. package/src/components/GithubPush.vue +100 -0
  49. package/src/components/IconPicker.vue +410 -0
  50. package/src/components/LicensePicker.vue +182 -0
  51. package/src/components/ResultPanel.vue +88 -0
  52. package/src/components/ReviewPanel.vue +69 -0
  53. package/src/components/TermsDialog.vue +81 -0
  54. package/src/components/TokenDeployerView.vue +115 -0
  55. package/src/components/WalletBar.vue +108 -0
  56. package/src/components/WalrusBlobBrowser.vue +188 -0
  57. package/src/components/WalrusIconUpload.vue +205 -0
  58. package/src/composables/useAccessGate.ts +122 -0
  59. package/src/composables/useColorMode.ts +76 -0
  60. package/src/composables/useWalrusRelay.ts +21 -0
  61. package/src/config.ts +185 -0
  62. package/src/index.ts +1 -0
  63. package/src/lib/accessGate.ts +141 -0
  64. package/src/lib/buildPublishTx.ts +192 -0
  65. package/src/lib/deploy.ts +168 -0
  66. package/src/lib/deployExecutor.ts +61 -0
  67. package/src/lib/errors.ts +21 -0
  68. package/src/lib/form.ts +75 -0
  69. package/src/lib/generatePackage.ts +312 -0
  70. package/src/lib/github.ts +146 -0
  71. package/src/lib/licenses.ts +122 -0
  72. package/src/lib/readClient.ts +17 -0
  73. package/src/lib/template.ts +131 -0
  74. package/src/lib/types.ts +69 -0
  75. package/src/lib/validation.ts +172 -0
  76. package/src/lib/walrus-constants.ts +15 -0
  77. package/src/lib/walrus.ts +144 -0
  78. package/src/main.ts +154 -0
  79. package/src/move-template/sui_token_template.mv +0 -0
  80. package/src/move-template/template.ts +31 -0
  81. package/src/styles.css +384 -0
  82. package/src/template-src/files.json +10 -0
  83. package/test-results/.last-run.json +4 -0
  84. package/tests/accessGate.test.ts +108 -0
  85. package/tests/buildPublishTx.test.ts +243 -0
  86. package/tests/deploy.test.ts +81 -0
  87. package/tests/errors.test.ts +32 -0
  88. package/tests/generatePackage.test.ts +182 -0
  89. package/tests/github.test.ts +61 -0
  90. package/tests/licenses.test.ts +78 -0
  91. package/tests/template.test.ts +93 -0
  92. package/tests/templateArtifact.test.ts +61 -0
  93. package/tests/templateParity.test.ts +55 -0
  94. package/tests/useAccessGate.test.ts +101 -0
  95. package/tests/useWallet.test.ts +74 -0
  96. package/tests/useWalrusRelayGating.test.ts +30 -0
  97. package/tests/validation.test.ts +117 -0
  98. package/tests/walrusUpload.test.ts +93 -0
  99. package/tests/walrus_url_patch.test.ts +26 -0
  100. package/tsconfig.json +19 -0
  101. package/typedoc.json +34 -0
  102. package/vite.config.ts +65 -0
  103. 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
+ })
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'vitest/config'
2
+ import vue from '@vitejs/plugin-vue'
3
+
4
+ export default defineConfig({
5
+ plugins: [vue()],
6
+ test: {
7
+ environment: 'jsdom',
8
+ include: ['tests/**/*.test.ts'],
9
+ },
10
+ })