@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
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
|
package/.env.production
ADDED
|
@@ -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
package/.oxlintrc.json
ADDED
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.
|