@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
package/.env.example ADDED
@@ -0,0 +1,75 @@
1
+ # Copy to .env (or set in the Cloudflare Pages dashboard) before deploying.
2
+ # All are optional; sensible public defaults are used when unset.
3
+ #
4
+ # Canonical domain: sui-token-deployer.meddleware.co.uk (k8s SPA, primary)
5
+ # GitHub Pages mirror: meddleware.github.io/vault/token-deployer-ui/
6
+
7
+ # Operator fee (MIST) split from the user's gas coin into your treasury.
8
+ # Default 1 SUI (1000000000 MIST) — trivial, ~7.5x under competitor pricing.
9
+ VITE_FEE_MIST=1000000000
10
+
11
+ # Operator treasury addresses per network (REQUIRED for the fee to be collected).
12
+ # The committed operator defaults live in `.env.production` (auto-loaded by
13
+ # `vite build`). A production build FAILS if a selectable network's treasury is
14
+ # still the zero address, so revenue can never silently burn — set both below or
15
+ # in `.env.production`. Set VITE_ALLOW_UNSET_TREASURY=1 to bypass (dev/e2e only).
16
+ VITE_FEE_TREASURY_TESTNET=0xe6b2810abfc5a6f37a375f73e3ba76cfc37584196e453255ad3c9ca2f0ede0ed
17
+ VITE_FEE_TREASURY_MAINNET=0xe6b2810abfc5a6f37a375f73e3ba76cfc37584196e453255ad3c9ca2f0ede0ed
18
+
19
+ # Optional RPC overrides. Set a paid/private endpoint for production to avoid public
20
+ # rate limits. NOTE: the official testnet fullnode (fullnode.testnet.sui.io) is gRPC-only
21
+ # and 404s for JSON-RPC — use a JSON-RPC-capable endpoint for testnet (the app defaults to
22
+ # https://sui-testnet-rpc.publicnode.com). The same VITE_RPC_TESTNET is what the real-chain
23
+ # e2e (npm run e2e:testnet) builds against; point E2E_RPC at the same endpoint.
24
+ # VITE_RPC_TESTNET=https://sui-testnet-rpc.publicnode.com
25
+ # VITE_RPC_MAINNET=https://fullnode.mainnet.sui.io:443
26
+
27
+ # Optional publish gas budget (MIST).
28
+ # VITE_PUBLISH_GAS_BUDGET=500000000
29
+
30
+ # --- Walrus icon upload -----------------------------------------------------
31
+ # Operator's own upload relay per network. Set these to a relay YOU run (the
32
+ # stock mysten/walrus-upload-relay container) to collect the native per-upload
33
+ # tip as revenue. Unset → the public Mysten relay is used (uploads still work
34
+ # but tip Mysten, not you). See the relay runbook in README.md / CLAUDE.md.
35
+ # VITE_WALRUS_RELAY_TESTNET=https://sui-walrus-relay-testnet.meddleware.co.uk
36
+ # VITE_WALRUS_RELAY_MAINNET=https://sui-walrus-relay.meddleware.co.uk
37
+
38
+ # gRPC fullnode for the Walrus icon-upload client (a SuiGrpcClient). This is a
39
+ # *gRPC* endpoint and is SEPARATE from VITE_RPC_* (the JSON-RPC deploy endpoints) —
40
+ # the two protocols are not interchangeable. Defaults to the public gRPC fullnodes
41
+ # (rate-limited); set your own for production to avoid limits.
42
+ # VITE_WALRUS_RPC_TESTNET=https://fullnode.testnet.sui.io:443
43
+ # VITE_WALRUS_RPC_MAINNET=https://fullnode.mainnet.sui.io:443
44
+
45
+ # Client-side max relay tip (MIST). COUPLING: the Walrus SDK hard-fails an upload
46
+ # if your relay's tip exceeds this, so keep it comfortably ABOVE the relay tip.
47
+ # Default 0.05 SUI. Raising the relay tip past this needs a frontend redeploy.
48
+ # VITE_WALRUS_MAX_TIP_MIST=50000000
49
+
50
+ # NFT-gated relay access (OPTIONAL). If your operator relay runs behind an
51
+ # `nft-gate` gateway, set these to require holders of the access NFT. When UNSET,
52
+ # the relay behaves exactly as before (no gating). Values come from the
53
+ # access_gate publish (`.env.<network>`: ACCESS_GATE_PACKAGE_ID, ACCESS_GATE_GATE_ID,
54
+ # and ACCESS_NFT_TYPE or SOULBOUND_NFT_TYPE). Set all three (PACKAGE/ID/NFT_TYPE)
55
+ # to enable. Per-network isolation is automatic (the NFT type embeds the package id).
56
+ # VITE_ACCESS_GATE_PACKAGE_TESTNET=0x…
57
+ # VITE_ACCESS_GATE_ID_TESTNET=0x…
58
+ # VITE_ACCESS_GATE_NFT_TYPE_TESTNET=0x…::access_gate::SoulboundAccessNFT
59
+ # VITE_ACCESS_GATE_SOULBOUND_TESTNET=true # selects consume vs consume_soulbound
60
+ # VITE_ACCESS_GATE_PRICE_MIST_TESTNET=0 # display + purchase price
61
+ # VITE_ACCESS_GATE_PACKAGE_MAINNET=0x…
62
+ # VITE_ACCESS_GATE_ID_MAINNET=0x…
63
+ # VITE_ACCESS_GATE_NFT_TYPE_MAINNET=0x…::access_gate::SoulboundAccessNFT
64
+ # VITE_ACCESS_GATE_SOULBOUND_MAINNET=true
65
+ # VITE_ACCESS_GATE_PRICE_MIST_MAINNET=0
66
+
67
+ # Icon constraints (UX/cost guard only — the authoritative cap is the edge body
68
+ # limit in front of the relay). Default 100 KiB; PNG/JPEG/WebP/SVG.
69
+ # VITE_ICON_MAX_BYTES=102400
70
+ # VITE_ICON_ALLOWED_TYPES=image/png,image/jpeg,image/webp,image/svg+xml
71
+
72
+ # Public site origin for canonical/OpenGraph/JSON-LD URLs (index.html). Committed
73
+ # defaults live in .env.production / .env.development; a staging deploy should set
74
+ # this so it never claims the production URL.
75
+ # VITE_PUBLIC_URL=https://tokens.meddleware.co.uk
@@ -0,0 +1,7 @@
1
+ # Committed defaults for the standalone SPA production build (auto-loaded by `vite build`).
2
+ # These are public Sui addresses — not secrets — so they are safe to commit.
3
+ # Override at deploy time via Cloudflare Pages environment variables (takes precedence).
4
+ # Local dev: copy .env.example to .env.local and edit there (never commit .env.local).
5
+
6
+ VITE_FEE_TREASURY_TESTNET=0xe6b2810abfc5a6f37a375f73e3ba76cfc37584196e453255ad3c9ca2f0ede0ed
7
+ VITE_FEE_TREASURY_MAINNET=0xe6b2810abfc5a6f37a375f73e3ba76cfc37584196e453255ad3c9ca2f0ede0ed
package/.gitattributes ADDED
@@ -0,0 +1 @@
1
+ * text=auto eol=lf
@@ -0,0 +1,34 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ ci:
11
+ name: Lint, type-check, test, build
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - uses: actions/checkout@v7.0.1
16
+
17
+ - uses: actions/setup-node@v6.5.0
18
+ with:
19
+ node-version: '24'
20
+
21
+ - name: Install dependencies
22
+ run: npm ci
23
+
24
+ - name: Audit dependencies
25
+ run: npm audit --audit-level=high
26
+
27
+ - name: Type-check
28
+ run: npm run type-check
29
+
30
+ - name: Unit tests
31
+ run: npm test
32
+
33
+ - name: Build (dev — treasury guard bypassed for CI)
34
+ run: VITE_ALLOW_UNSET_TREASURY=1 npm run build
@@ -0,0 +1,64 @@
1
+ # Real-chain deploy e2e for blockchain/sui/apps/token-deployer-ui.
2
+ #
3
+ # MANUAL ONLY (workflow_dispatch). This workflow publishes a REAL token and spends REAL SUI, so
4
+ # it is never triggered on push or pull_request — it must be started by hand and mainnet requires
5
+ # an explicit confirmation input. The automatic gates for the package remain `npm test` (Vitest)
6
+ # and `npm run test:e2e` (Cypress, fully mocked), which should live in a separate always-on CI job.
7
+ #
8
+ # Secrets required (repository or environment secrets):
9
+ # SUI_PRIV_TESTNET / SUI_PRIV_MAINNET — bech32 suiprivkey1… for a funded deployer key.
10
+ # FEE_TREASURY_TESTNET / FEE_TREASURY_MAINNET — operator treasury address (fee recipient).
11
+ # RPC_TESTNET / RPC_MAINNET (optional) — JSON-RPC endpoint override (public testnet is gRPC-only).
12
+
13
+ name: token-deployer real-chain e2e
14
+
15
+ on:
16
+ workflow_dispatch:
17
+ inputs:
18
+ network:
19
+ description: Target network (mainnet publishes a REAL token and spends REAL SUI)
20
+ required: true
21
+ default: testnet
22
+ type: choice
23
+ options:
24
+ - testnet
25
+ - mainnet
26
+ confirm_mainnet:
27
+ description: Type "1" to confirm a mainnet run (ignored for testnet)
28
+ required: false
29
+ default: ""
30
+
31
+ defaults:
32
+ run:
33
+ working-directory: blockchain/sui/apps/token-deployer-ui
34
+
35
+ jobs:
36
+ deploy-e2e:
37
+ runs-on: ubuntu-latest
38
+ steps:
39
+ - uses: actions/checkout@v4
40
+
41
+ - uses: actions/setup-node@v4
42
+ with:
43
+ node-version: 22
44
+
45
+ - name: Install dependencies
46
+ run: npm ci
47
+
48
+ - name: Install Playwright Chromium
49
+ run: npx playwright install --with-deps chromium
50
+
51
+ - name: Run real-chain deploy e2e
52
+ env:
53
+ # Select the funded key + treasury + RPC for the chosen network.
54
+ SUI_PRIV: ${{ inputs.network == 'mainnet' && secrets.SUI_PRIV_MAINNET || secrets.SUI_PRIV_TESTNET }}
55
+ VITE_FEE_TREASURY_TESTNET: ${{ secrets.FEE_TREASURY_TESTNET }}
56
+ VITE_FEE_TREASURY_MAINNET: ${{ secrets.FEE_TREASURY_MAINNET }}
57
+ VITE_RPC_TESTNET: ${{ secrets.RPC_TESTNET }}
58
+ VITE_RPC_MAINNET: ${{ secrets.RPC_MAINNET }}
59
+ E2E_RPC: ${{ inputs.network == 'mainnet' && secrets.RPC_MAINNET || secrets.RPC_TESTNET }}
60
+ E2E_FEE_ADDR: ${{ inputs.network == 'mainnet' && secrets.FEE_TREASURY_MAINNET || secrets.FEE_TREASURY_TESTNET }}
61
+ # Mainnet fail-safe: only proceeds when the operator typed "1".
62
+ E2E_MAINNET_CONFIRM: ${{ inputs.network == 'mainnet' && inputs.confirm_mainnet || '' }}
63
+ # CHROME_PATH intentionally unset — the harness falls back to Playwright's bundled Chromium.
64
+ run: npm run "e2e:${{ inputs.network }}"
package/.oxfmtrc.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "./node_modules/oxfmt/configuration_schema.json",
3
+ "semi": false,
4
+ "singleQuote": true
5
+ }
package/.oxlintrc.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "plugins": ["eslint", "unicorn", "oxc", "vue", "vitest"],
4
+ "env": {
5
+ "browser": true
6
+ },
7
+ "categories": {
8
+ "correctness": "error"
9
+ }
10
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "recommendations": [
3
+ "Vue.volar",
4
+ "vitest.explorer",
5
+ "dbaeumer.vscode-eslint",
6
+ "EditorConfig.EditorConfig",
7
+ "oxc.oxc-vscode"
8
+ ]
9
+ }
package/AGENTS.md ADDED
@@ -0,0 +1,39 @@
1
+ # AGENTS.md — token-deployer-ui
2
+
3
+ Browser app for permissionless Sui token deployment. See [CLAUDE.md](CLAUDE.md) for full architecture and invariant documentation.
4
+
5
+ ## Responsibilities
6
+
7
+ - Bytecode patching of the pre-compiled Move template (`src/lib/template.ts`)
8
+ - PTB construction for two-phase publish + finalize (`src/lib/buildPublishTx.ts`)
9
+ - Source package generation and zip download (`src/lib/generatePackage.ts`)
10
+ - Walrus icon upload via the operator relay (`@meddleware/sui-walrus`)
11
+ - NFT-gate ownership check and access-proof attachment when `VITE_ACCESS_GATE_*` is configured
12
+
13
+ ## Non-responsibilities
14
+
15
+ - On-chain accounting or financial enforcement — the contracts handle that
16
+ - Any backend process; no server dependency
17
+ - Custody of keys or user funds
18
+
19
+ ## Testing
20
+
21
+ Three distinct categories — do not collapse:
22
+
23
+ 1. **Unit tests** (`npm test`, Vitest): validation, template patching, PTB builders, generatePackage, deploy orchestration.
24
+ 2. **Mocked-RPC e2e** (`npm run test:e2e`, Playwright, Chromium + Firefox): UI flow with fetch mock + mock wallet. Specs live in `integrations/token-deployer/e2e/`.
25
+ 3. **Real-chain e2e** (`npm run e2e:localnet|testnet|mainnet`): publishes a real token; **never run in automatic CI**; gated by `E2E_MAINNET_CONFIRM=1` on mainnet.
26
+
27
+ ## Key invariants
28
+
29
+ - **Template parity:** `.mv` bytes and `files.json` must derive from the same `sui-token-template` commit. Verify with `npm run verify:template`; regenerate with `npm run regen:template && npm run sync:template`.
30
+ - **Fee integrity:** treasury fee is split from gas inside the publish PTB. The vite prod build fails if `VITE_FEE_TREASURY` is unset (`assertTreasuryConfigured`).
31
+ - **Injection safety:** validation in `validation.ts` + independent re-assertion in `generatePackage.ts` and `template.ts`. Never remove a downstream guard.
32
+ - **No wallet-standard proxying:** Sui wallet objects must be wrapped in `markRaw()` before storing in Vue reactive state (private-field getters crash under Vue's Proxy).
33
+
34
+ ## Key paths
35
+
36
+ - E2e specs: `e2e/` (mocked-RPC Playwright suite; `testDir` in `playwright.config.ts`)
37
+ - Move template: `@meddleware/sui-token-template` (`optionalDependencies` in `package.json`; local checkout at `../sui-token-template/` if working from the workspace)
38
+ - NFT gate client: `@meddleware/nft-gate-client` (`repos/nft-gate-client/` in the workspace)
39
+ - CI workflow: `.github/workflows/token-deployer-e2e.yml`
package/CLAUDE.md ADDED
@@ -0,0 +1,159 @@
1
+ # CLAUDE.md — `token-deployer-ui`
2
+
3
+ `@meddleware/token-deployer-ui` is a Vue 3 + Vite browser app that lets any user deploy
4
+ their own Sui coin fully client-side (their wallet signs + pays gas + a trivial fee to
5
+ the operator treasury) and download a matching source package. It is the browser
6
+ equivalent of the `@meddleware/sui-token-template` Move package (from which it derives
7
+ its pre-compiled bytecode and source templates). See the repo-root
8
+ [CLAUDE.md](../../../../CLAUDE.md) for context and [README.md](README.md) for the
9
+ user/operator guide.
10
+
11
+ ## Dual app + library pattern
12
+
13
+ This package ships as both a **standalone SPA** and an **embeddable library component**:
14
+
15
+ - **Standalone SPA** (`npm run dev` / `npm run build`): `src/main.ts` mounts `App.vue`, which
16
+ is a thin shell that renders `WalletBar` + `TokenDeployerView`. All deploy logic lives in
17
+ `TokenDeployerView` — `App.vue` contains no state.
18
+ - **Library component** (consumed by `@meddleware/dashboard`): `src/index.ts` exports
19
+ `TokenDeployerView`, which is imported via the `exports["."]` entry in `package.json`. The
20
+ dashboard's Vite build resolves TypeScript source directly — no separate lib build step.
21
+ The dashboard supplies the wallet connection via `@meddleware/wallet-adapter`'s shared
22
+ module singleton; `TokenDeployerView` reads `useWallet()` / `useNetwork()` from it and is
23
+ wrapped in `WalletGuard` so it shows a connect prompt when no wallet is connected.
24
+
25
+ The pattern mirrors `@meddleware/walrus-ui`, `@meddleware/seal-ui`, and
26
+ `@meddleware/access-gate-ui`. See the dashboard `CLAUDE.md` for the invariant: every tool view
27
+ must **not** render its own wallet bar, header, or footer when embedded.
28
+
29
+ **Deploy executor note:** The wallet-adapter `Executor` only returns `{ digest }` — insufficient
30
+ for the deploy flow, which requires `objectChanges` to extract the packageId, coin type,
31
+ TreasuryCap, and MetadataCap. `src/lib/deployExecutor.ts` uses the wallet-adapter singleton for
32
+ wallet connection state, but builds its own `SuiJsonRpcClient` (`@mysten/sui/jsonRpc`) to execute
33
+ with `showEffects + showObjectChanges`. This is intentional — do not replace it with the
34
+ wallet-adapter `buildExecutor`.
35
+
36
+ ## Architecture (the money/parity paths matter most)
37
+
38
+ - **Bytecode patching** — [src/lib/template.ts](src/lib/template.ts) deserialises
39
+ one pre-compiled template module ([src/move-template/](src/move-template/)),
40
+ renames the module/OTW-struct identifiers in place, and overwrites five DISTINCT
41
+ named constants (decimals, symbol, name, description, iconUrl), then re-serialises.
42
+ Binary format v7 cannot be round-tripped by `update_identifiers`/`update_constants`
43
+ ("missing field version"); `deserialize`→mutate JSON→`serialize` is the path.
44
+ - **Two-phase publish** — TreasuryCap/MetadataCap are created in `init()` and sent
45
+ to the sender, so they are NOT `tx.publish` results. Publish PTB
46
+ ([src/lib/buildPublishTx.ts](src/lib/buildPublishTx.ts)) = publish + UpgradeCap
47
+ policy + fee split from gas. A finalize PTB (only when needed —
48
+ `needsFinalize`) mints supply and applies supply/metadata policies.
49
+ - **Orchestration** — [src/lib/deploy.ts](src/lib/deploy.ts) sequences patch →
50
+ publish → wait → (finalize) via an injectable `Executor` (so it is unit-testable;
51
+ the real one is built from the wallet in
52
+ [src/composables/useWallet.ts](src/composables/useWallet.ts)).
53
+ - **Package generation** — [src/lib/generatePackage.ts](src/lib/generatePackage.ts)
54
+ substitutes the same inputs into the canonical template text
55
+ ([src/template-src/files.json](src/template-src/files.json)) and zips it.
56
+
57
+ ## Invariants (do not break)
58
+
59
+ 1. **Bytecode ↔ source parity.** The shipped `.mv` and `files.json` must both derive
60
+ from the same `sui-token-template` commit. Enforced by
61
+ [tests/templateArtifact.test.ts](tests/templateArtifact.test.ts) (defaults present
62
+ in the `.mv`) and [tests/templateParity.test.ts](tests/templateParity.test.ts)
63
+ (`files.json` == canonical sources). After changing the template run
64
+ `npm run regen:template && npm run sync:template && npm run verify:template`.
65
+ 2. **Injection safety is layered.** `validation.ts` gates the form, but
66
+ `generatePackage.ts` (`assertSafeConfig`) and `template.ts` (`patchVecConstant`)
67
+ re-assert `SAFE_TEXT`/`MOVE_IDENT`/length/keyword rules independently. Never
68
+ remove a downstream guard on the assumption the form already checked.
69
+ 3. **Fee integrity.** The fee is split from `tx.gas` to the config treasury inside
70
+ the publish PTB. The vite production build fails on a zero-address treasury
71
+ (`assertTreasuryConfigured` in [vite.config.ts](vite.config.ts)).
72
+ 4. **No backend / no custody / no endorsement.** Keep all financial truth on-chain;
73
+ the app only previews and constructs transactions. Do not add a server dependency.
74
+ 5. **Lazy heavy deps.** `@mysten/walrus` and `@mysten/move-bytecode-template` (wasm)
75
+ are loaded on demand (dynamic import / `?url`). Keep the shared `ICON_EPOCHS`
76
+ constant in [src/lib/walrus-constants.ts](src/lib/walrus-constants.ts) so the
77
+ eagerly-rendered widget doesn't pull the Walrus chunk into the main bundle.
78
+
79
+ ## Walrus icons
80
+
81
+ Icons are RAW blobs (not quilts) so `GET /v1/blobs/<id>` renders the exact image;
82
+ an upload relay is required from browsers. `ICON_EPOCHS = 53` (Walrus's
83
+ `max_epochs_ahead`; a larger single reservation aborts). Longer retention needs
84
+ `extendBlobLifetime` (no keeper — operator step). Verified on testnet via
85
+ `scripts/e2e-walrus-browser.mjs`.
86
+
87
+ ## Walrus relay economics & deferred decisions
88
+
89
+ The icon upload can charge users per upload via Walrus's **native relay tip** —
90
+ no custom backend. This section records the decisions and their rationale so they
91
+ survive independently of any planning doc. (Verified against walrus SDK 1.1.7 and
92
+ the relay source `crates/walrus-upload-relay`.)
93
+
94
+ - **Native `linear` tip is enforced on-chain — chosen for launch.** `register`
95
+ bakes an auth payload `blobDigest ‖ SHA256(nonce) ‖ size` **plus** a tip transfer
96
+ to the operator's address into the *same* tx the user signs. The relay verifies
97
+ that on-chain tip (amount checked against the *actual received body length*, bound
98
+ by nonce/size) **before** the expensive storage-node fan-out. Stripping or lowering
99
+ the tip → the relay rejects the upload; no operator cost. This refutes an earlier
100
+ finding that the tip "could be removed" — it is enforced now.
101
+ - **The relay does no on-chain tx** (README L27) → the operator never pays gas.
102
+ Storage (WAL) is paid by the **user** at registration; read/serve bandwidth is the
103
+ separate public **aggregator**. The tip only needs to cover the relay's
104
+ ingress + fan-out egress + RPC lookups.
105
+ - **NFT / usage-ticket per-wallet gating is IMPLEMENTED (2026-08-05)** via the standalone
106
+ `nft-gate` project: an `nft-gate` gateway fronts the operator relay and admits only
107
+ holders of an `access_gate` NFT. The app gates the operator-relay option on ownership
108
+ (`useAccessGate` → `getOwnedObjects`), offers a permissionless purchase CTA, and attaches
109
+ a wallet-signed access proof (`sui:signPersonalMessage` → `uploadRelayAuthToken`) when the
110
+ operator relay is used. Unset `VITE_ACCESS_GATE_*` → no gating (unchanged behaviour). The
111
+ tip and the NFT are **independent levers** (tip = per-upload cost; NFT = access/abuse).
112
+ - **Per-wallet rate limiting** — previously rejected at the edge (which can't resolve the
113
+ wallet), it is now enforced by the `nft-gate` gateway *after* verification. Keep a per-IP
114
+ edge limit as a coarse pre-auth control.
115
+ - **Autonomous price-fed tip tuner DEFERRED** — the relay reads its tip only at
116
+ **startup** (no hot-reload/SIGHUP), so adjusting = rewrite YAML + restart. That's a
117
+ keeper with a liveness dependency. For launch use a **single static tip with a fat
118
+ SUI-denominated margin**: icons are cents-scale, so a large margin costs users
119
+ nothing and absorbs *both* SUI/fiat drift and storage-node retry amplification in
120
+ one lever. A later tuner (read Pyth on-chain, clamp to `[min,max]`, rolling-restart)
121
+ must stay under the client `max` headroom below.
122
+ - **Client `WALRUS_MAX_TIP_MIST` ↔ relay-tip COUPLING** — the SDK **throws** if the
123
+ relay's tip exceeds this client ceiling ([config.ts](src/config.ts) documents it).
124
+ Set it with generous headroom above the relay tip; raising the relay tip past it
125
+ needs a frontend redeploy.
126
+ - **Client-side icon size/type limits are UX only — NOT security.** The authoritative
127
+ cap is a hard request-body limit at the edge (the relay otherwise buffers up to a
128
+ hardcoded 1 GiB body into RAM *before* the tip check — the one real abuse vector:
129
+ ingress/RAM/RPC on rejected uploads). See the relay runbook in [README.md](README.md).
130
+
131
+ ## Testing
132
+
133
+ Three distinct categories — do not collapse them (repo convention):
134
+
135
+ - **Pure unit (Vitest, `npm test`):** validation, licenses, github, template patch, PTB
136
+ builders, generatePackage, deploy orchestration, provenance/parity.
137
+ - **Mocked-RPC UI e2e (Playwright, `npm run test:e2e`):** wallet/form/publish flow against the
138
+ `VITE_E2E` fetch mock + mock wallet in [src/main.ts](src/main.ts) (tree-shaken from prod). Specs
139
+ live in `e2e/` (`playwright.config.ts` builds with `VITE_E2E=1` + previews). **Migrated from Cypress
140
+ 2026-08-07** (Cypress dropped — it exits SIGILL in the sandbox; Playwright is the one browser-e2e
141
+ tool now, shared with the real-chain harness). Run `npm run e2e:install` once, then `npm run test:e2e`.
142
+ Cannot reach real confirmation, the result panel, or the source download — those need a real chain
143
+ (see below). This is the automatic gate alongside `npm test`.
144
+ - **Real-chain deploy e2e (Playwright, NOT in `npm test`):** `scripts/e2e-deploy.mjs` — the
145
+ single network-parametrized harness (`E2E_NETWORK=localnet|testnet|mainnet`) driven by
146
+ `npm run e2e:localnet|e2e:testnet|e2e:mainnet`. Injects a wallet-standard wallet backed by a
147
+ node keypair, does a REAL publish, waits for real confirmation, checks the result panel,
148
+ downloads the zip, and verifies coin type + fee on-chain. Two axes are distinct: `E2E_NETWORK`
149
+ (physical chain) vs the app network (testnet/mainnet UI mode; localnet borrows testnet mode with
150
+ the RPC pointed at the local node). Mainnet is fail-safe: aborts unless `E2E_MAINNET_CONFIRM=1`.
151
+ `scripts/e2e-browser.mjs` is a thin `E2E_NETWORK=localnet` wrapper; `scripts/e2e-walrus-browser.mjs`
152
+ (testnet icon upload) uses the E2E-only `window.__getSuiClient` hook in `src/main.ts`. Testnet/
153
+ mainnet are **manual only** — never in automatic CI (see the README launch runbook). The
154
+ `.github/workflows/token-deployer-e2e.yml` `workflow_dispatch` job is the gated manual runner.
155
+
156
+ ## Docs
157
+
158
+ TSDoc on every exported symbol in `src/lib`, `src/composables`, and `src/config.ts`;
159
+ `npm run docs` renders a TypeDoc site to `docs/api/` (config: [typedoc.json](typedoc.json)).
package/LICENSE ADDED
@@ -0,0 +1,121 @@
1
+ Creative Commons Legal Code
2
+
3
+ CC0 1.0 Universal
4
+
5
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12
+ HEREUNDER.
13
+
14
+ Statement of Purpose
15
+
16
+ The laws of most jurisdictions throughout the world automatically confer
17
+ exclusive Copyright and Related Rights (defined below) upon the creator
18
+ and subsequent owner(s) (each and all, an "owner") of an original work of
19
+ authorship and/or a database (each, a "Work").
20
+
21
+ Certain owners wish to permanently relinquish those rights to a Work for
22
+ the purpose of contributing to a commons of creative, cultural and
23
+ scientific works ("Commons") that the public can reliably and without fear
24
+ of later claims of infringement build upon, modify, incorporate in other
25
+ works, reuse and redistribute as freely as possible in any form whatsoever
26
+ and for any purposes, including without limitation commercial purposes.
27
+ These owners may contribute to the Commons to promote the ideal of a free
28
+ culture and the further production of creative, cultural and scientific
29
+ works, or to gain reputation or greater distribution for their Work in
30
+ part through the use and efforts of others.
31
+
32
+ For these and/or other purposes and motivations, and without any
33
+ expectation of additional consideration or compensation, the person
34
+ associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35
+ is an owner of Copyright and Related Rights in the Work, voluntarily
36
+ elects to apply CC0 to the Work and publicly distribute the Work under its
37
+ terms, with knowledge of his or her Copyright and Related Rights in the
38
+ Work and the meaning and intended legal effect of CC0 on those rights.
39
+
40
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
41
+ protected by copyright and related or neighboring rights ("Copyright and
42
+ Related Rights"). Copyright and Related Rights include, but are not
43
+ limited to, the following:
44
+
45
+ i. the right to reproduce, adapt, distribute, perform, display,
46
+ communicate, and translate a Work;
47
+ ii. moral rights retained by the original author(s) and/or performer(s);
48
+ iii. publicity and privacy rights pertaining to a person's image or
49
+ likeness depicted in a Work;
50
+ iv. rights protecting against unfair competition in regards to a Work,
51
+ subject to the limitations in paragraph 4(a), below;
52
+ v. rights protecting the extraction, dissemination, use and reuse of data
53
+ in a Work;
54
+ vi. database rights (such as those arising under Directive 96/9/EC of the
55
+ European Parliament and of the Council of 11 March 1996 on the legal
56
+ protection of databases, and under any national implementation
57
+ thereof, including any amended or successor version of such
58
+ directive); and
59
+ vii. other similar, equivalent or corresponding rights throughout the
60
+ world based on applicable law or treaty, and any national
61
+ implementations thereof.
62
+
63
+ 2. Waiver. To the greatest extent permitted by, but not in contravention
64
+ of, applicable law, Affirmer hereby overtly, fully, permanently,
65
+ irrevocably and unconditionally waives, abandons, and surrenders all of
66
+ Affirmer's Copyright and Related Rights and associated claims and causes
67
+ of action, whether now known or unknown (including existing as well as
68
+ future claims and causes of action), in the Work (i) in all territories
69
+ worldwide, (ii) for the maximum duration provided by applicable law or
70
+ treaty (including future time extensions), (iii) in any current or future
71
+ medium and for any number of copies, and (iv) for any purpose whatsoever,
72
+ including without limitation commercial, advertising or promotional
73
+ purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74
+ member of the public at large and to the detriment of Affirmer's heirs and
75
+ successors, fully intending that such Waiver shall not be subject to
76
+ revocation, rescission, cancellation, termination, or any other legal or
77
+ equitable action to disrupt the quiet enjoyment of the Work by the public
78
+ as contemplated by Affirmer's express Statement of Purpose.
79
+
80
+ 3. Public License Fallback. Should any part of the Waiver for any reason
81
+ be judged legally invalid or ineffective under applicable law, then the
82
+ Waiver shall be preserved to the maximum extent permitted taking into
83
+ account Affirmer's express Statement of Purpose. In addition, to the
84
+ extent the Waiver is so judged Affirmer hereby grants to each affected
85
+ person a royalty-free, non transferable, non sublicensable, non exclusive,
86
+ irrevocable and unconditional license to exercise Affirmer's Copyright and
87
+ Related Rights in the Work (i) in all territories worldwide, (ii) for the
88
+ maximum duration provided by applicable law or treaty (including future
89
+ time extensions), (iii) in any current or future medium and for any number
90
+ of copies, and (iv) for any purpose whatsoever, including without
91
+ limitation commercial, advertising or promotional purposes (the
92
+ "License"). The License shall be deemed effective as of the date CC0 was
93
+ applied by Affirmer to the Work. Should any part of the License for any
94
+ reason be judged legally invalid or ineffective under applicable law, such
95
+ partial invalidity or ineffectiveness shall not invalidate the remainder
96
+ of the License, and in such case Affirmer hereby affirms that he or she
97
+ will not (i) exercise any of his or her remaining Copyright and Related
98
+ Rights in the Work or (ii) assert any associated claims and causes of
99
+ action with respect to the Work, in either case contrary to Affirmer's
100
+ express Statement of Purpose.
101
+
102
+ 4. Limitations and Disclaimers.
103
+
104
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
105
+ surrendered, licensed or otherwise affected by this document.
106
+ b. Affirmer offers the Work as-is and makes no representations or
107
+ warranties of any kind concerning the Work, express, implied,
108
+ statutory or otherwise, including without limitation warranties of
109
+ title, merchantability, fitness for a particular purpose, non
110
+ infringement, or the absence of latent or other defects, accuracy, or
111
+ the present or absence of errors, whether or not discoverable, all to
112
+ the greatest extent permissible under applicable law.
113
+ c. Affirmer disclaims responsibility for clearing rights of other persons
114
+ that may apply to the Work or any use thereof, including without
115
+ limitation any person's Copyright and Related Rights in the Work.
116
+ Further, Affirmer disclaims responsibility for obtaining any necessary
117
+ consents, permissions or other rights required for any use of the
118
+ Work.
119
+ d. Affirmer understands and acknowledges that Creative Commons is not a
120
+ party to this document and has no duty or obligation with respect to
121
+ this CC0 or use of the Work.