@meddleware/token-deployer-ui 0.0.10 → 0.0.11

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 (87) hide show
  1. package/README.md +8 -5
  2. package/package.json +19 -8
  3. package/src/App.vue +1 -1
  4. package/src/component-styles.css +39 -57
  5. package/src/components/GithubPush.vue +2 -2
  6. package/src/components/IconPicker.vue +44 -75
  7. package/src/components/LicensePicker.vue +2 -2
  8. package/src/components/ResultPanel.vue +2 -2
  9. package/src/components/ReviewPanel.vue +11 -3
  10. package/src/components/TokenDeployerView.vue +52 -48
  11. package/src/components/WalletBar.vue +12 -1
  12. package/src/components/WalrusBlobBrowser.vue +4 -4
  13. package/src/composables/useWalrusRelay.ts +1 -2
  14. package/src/config.ts +19 -17
  15. package/src/lib/buildPublishTx.ts +0 -10
  16. package/src/lib/deploy.ts +9 -3
  17. package/src/lib/deployExecutor.ts +18 -45
  18. package/src/lib/template.ts +3 -0
  19. package/src/lib/walrus-constants.ts +2 -2
  20. package/src/main.ts +4 -6
  21. package/src/styles.css +22 -0
  22. package/.claude/move-language-reference/SKILL.md +0 -445
  23. package/.claude/skills/move-language-reference/SKILL.md +0 -445
  24. package/.claude/skills/sui-move-bootcamp/SKILL.md +0 -128
  25. package/.claude/sui-move-bootcamp/SKILL.md +0 -128
  26. package/.editorconfig +0 -8
  27. package/.env.example +0 -72
  28. package/.gitattributes +0 -1
  29. package/.github/workflows/docker-publish.yml +0 -348
  30. package/.github/workflows/e2e-realchain.yml +0 -64
  31. package/.github/workflows/node-ci.yml +0 -43
  32. package/.github/workflows/npm-publish.yml +0 -83
  33. package/.htmlvalidate.mjs +0 -30
  34. package/.oxfmtrc.json +0 -5
  35. package/.oxlintrc.json +0 -10
  36. package/.vscode/extensions.json +0 -9
  37. package/AGENTS.md +0 -39
  38. package/CLAUDE.md +0 -166
  39. package/Dockerfile +0 -96
  40. package/eslint.config.ts +0 -63
  41. package/index.html +0 -22
  42. package/playwright.config.ts +0 -30
  43. package/postcss.config.js +0 -8
  44. package/public/CNAME +0 -1
  45. package/public/_headers +0 -12
  46. package/public/_redirects +0 -1
  47. package/public/favicon.ico +0 -0
  48. package/public/favicon.svg +0 -5
  49. package/public/og-image.png +0 -0
  50. package/public/robots.txt +0 -3
  51. package/public/sitemap.xml +0 -8
  52. package/scripts/e2e-browser.mjs +0 -15
  53. package/scripts/e2e-deploy.mjs +0 -227
  54. package/scripts/e2e-localnet.mjs +0 -67
  55. package/scripts/e2e-walrus-browser.mjs +0 -112
  56. package/scripts/gen-og-image.mjs +0 -31
  57. package/scripts/publish-localnet.mjs +0 -56
  58. package/scripts/regen-template.sh +0 -45
  59. package/scripts/sync-template-src.sh +0 -36
  60. package/scripts/verify-bytecode.mjs +0 -49
  61. package/src/__tests__/App.spec.js +0 -11
  62. package/src/composables/useAccessGate.ts +0 -127
  63. package/src/lib/accessGate.ts +0 -155
  64. package/src/lib/walrus.ts +0 -155
  65. package/stylelint.config.js +0 -33
  66. package/test-results/.last-run.json +0 -4
  67. package/tests/accessGate.test.ts +0 -113
  68. package/tests/buildPublishTx.test.ts +0 -243
  69. package/tests/deploy.test.ts +0 -149
  70. package/tests/errors.test.ts +0 -32
  71. package/tests/generatePackage.test.ts +0 -193
  72. package/tests/github.test.ts +0 -61
  73. package/tests/licenses.test.ts +0 -81
  74. package/tests/listMyTokens.test.ts +0 -30
  75. package/tests/template.test.ts +0 -93
  76. package/tests/templateArtifact.test.ts +0 -61
  77. package/tests/templateParity.test.ts +0 -55
  78. package/tests/useAccessGate.test.ts +0 -97
  79. package/tests/useWallet.test.ts +0 -74
  80. package/tests/useWalrusRelayGating.test.ts +0 -30
  81. package/tests/validation.test.ts +0 -117
  82. package/tests/walrusUpload.test.ts +0 -93
  83. package/tests/walrus_url_patch.test.ts +0 -26
  84. package/tsconfig.json +0 -19
  85. package/typedoc.json +0 -34
  86. package/vite.config.ts +0 -73
  87. package/vitest.config.ts +0 -10
@@ -1,227 +0,0 @@
1
- // Network-parametrized headless-browser deploy e2e. Loads the built app, injects a
2
- // wallet-standard wallet backed by a node-held keypair, drives the full UI, and performs a
3
- // REAL publish against the target chain — then verifies coin type + operator fee on-chain.
4
- //
5
- // This is the single source of truth for the real-chain deploy path; `e2e-browser.mjs` is a
6
- // thin localnet wrapper over it. It covers exactly what the mocked Cypress suite cannot: real
7
- // confirmation, the result panel, the source-zip download, and on-chain verification.
8
- //
9
- // Two axes matter and are DISTINCT:
10
- // - E2E_NETWORK = the physical chain the RPC talks to: localnet | testnet | mainnet.
11
- // - APP_NETWORK = the network the app runs in (its only UI options are testnet/mainnet;
12
- // it drives the `sui:<net>` chain label and which fee treasury applies).
13
- // localnet is run as APP_NETWORK=testnet with the testnet RPC overridden to the local node,
14
- // so the app believes it is on testnet while publishing to 127.0.0.1:9000. mainnet is the
15
- // only case that changes the in-UI network selector.
16
- //
17
- // The npm scripts (e2e:localnet / e2e:testnet / e2e:mainnet) build the app with the matching
18
- // VITE_RPC_* / VITE_FEE_TREASURY_* envs and serve dist on APP_URL before invoking this runner.
19
-
20
- import { readFileSync, existsSync } from 'node:fs'
21
- import { chromium } from 'playwright'
22
- import { SuiGrpcClient } from '@mysten/sui/grpc'
23
- import { requestSuiFromFaucetV2 } from '@mysten/sui/faucet'
24
- import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'
25
- import { Transaction } from '@mysten/sui/transactions'
26
- import { toBase64 } from '@mysten/sui/utils'
27
-
28
- const ZERO_ADDR = '0x' + '0'.repeat(64)
29
-
30
- const NETWORK = process.env.E2E_NETWORK || 'localnet'
31
- if (!['localnet', 'testnet', 'mainnet'].includes(NETWORK)) {
32
- fail(`E2E_NETWORK must be localnet|testnet|mainnet (got "${NETWORK}")`)
33
- }
34
-
35
- // The app only ever runs in testnet or mainnet mode; localnet borrows testnet mode.
36
- const APP_NETWORK = NETWORK === 'mainnet' ? 'mainnet' : 'testnet'
37
-
38
- const DEFAULT_RPC = {
39
- localnet: 'http://127.0.0.1:9000',
40
- testnet: 'https://fullnode.testnet.sui.io:443',
41
- mainnet: 'https://fullnode.mainnet.sui.io:443',
42
- }
43
- const RPC = process.env.E2E_RPC || DEFAULT_RPC[NETWORK]
44
-
45
- const APP_URL = process.env.APP_URL || 'http://localhost:4173'
46
- const FAUCET = process.env.E2E_FAUCET || 'http://127.0.0.1:9123'
47
- const FEE_ADDR = process.env.E2E_FEE_ADDR || (NETWORK === 'localnet' ? '0xfee' + '0'.repeat(61) : '')
48
- const EXPECT_FEE_MIST = BigInt(process.env.E2E_FEE_MIST || '1000000000') // 1 SUI, matches VITE_FEE_MIST default
49
- const RESULT_TIMEOUT = Number(process.env.E2E_RESULT_TIMEOUT || '60000')
50
-
51
- function fail(msg) {
52
- console.error('E2E FAIL:', msg)
53
- process.exit(1)
54
- }
55
-
56
- // ---------------------------------------------------------------------------
57
- // Mainnet is fail-safe by default: it must never publish a real token — and spend real
58
- // SUI — by accident or in CI. Require an explicit opt-in and abort cleanly otherwise.
59
- // ---------------------------------------------------------------------------
60
- if (NETWORK === 'mainnet' && process.env.E2E_MAINNET_CONFIRM !== '1') {
61
- console.log(
62
- 'e2e-deploy: mainnet run is gated. Set E2E_MAINNET_CONFIRM=1 to publish a REAL token on mainnet.\n' +
63
- 'Skipping (this is the expected, safe default).',
64
- )
65
- process.exit(0)
66
- }
67
-
68
- // ---------------------------------------------------------------------------
69
- // Keypair + funding. localnet mints a throwaway key from the faucet; testnet/mainnet use an
70
- // externally funded key supplied via SUI_PRIV (never a faucet on a real network).
71
- // ---------------------------------------------------------------------------
72
- let keypair
73
- if (NETWORK === 'localnet') {
74
- keypair = new Ed25519Keypair()
75
- } else {
76
- if (!process.env.SUI_PRIV) {
77
- fail(
78
- `SUI_PRIV is required for a ${NETWORK} run (bech32 suiprivkey1… — export the funded key with ` +
79
- `\`sui keytool export --key-identity <addr>\`). Never funds from a faucet on ${NETWORK}.`,
80
- )
81
- }
82
- keypair = Ed25519Keypair.fromSecretKey(process.env.SUI_PRIV)
83
- }
84
- const address = keypair.toSuiAddress()
85
-
86
- // Node-side gRPC client points at the PHYSICAL RPC; its `network` label is the app network so
87
- // the built transaction's chain identifier matches what the wallet/app sign under.
88
- const client = new SuiGrpcClient({ baseUrl: RPC, network: APP_NETWORK })
89
-
90
- /** Total SUI balance (MIST) of `owner`. */
91
- async function suiBalance(owner) {
92
- const { balance } = await client.getBalance({ owner })
93
- return BigInt(balance.balance)
94
- }
95
-
96
- const EXPECT_FEE = Boolean(FEE_ADDR) && FEE_ADDR !== ZERO_ADDR
97
-
98
- async function main() {
99
- console.log(`e2e-deploy: E2E_NETWORK=${NETWORK} APP_NETWORK=${APP_NETWORK} RPC=${RPC}`)
100
-
101
- if (NETWORK === 'localnet') {
102
- await requestSuiFromFaucetV2({ host: FAUCET, recipient: address })
103
- for (let i = 0; i < 25; i++) {
104
- if ((await suiBalance(address)) > 0n) break
105
- await new Promise((r) => setTimeout(r, 1000))
106
- }
107
- }
108
-
109
- const balance = await suiBalance(address)
110
- console.log('sender:', address, 'balance:', balance.toString(), 'MIST')
111
- if (balance === 0n) fail(`sender ${address} has no SUI on ${NETWORK}`)
112
-
113
- const feeBefore = EXPECT_FEE ? await suiBalance(FEE_ADDR) : 0n
114
-
115
- // Browser resolution: explicit CHROME_PATH → a locally-installed Chrome → Playwright's
116
- // bundled Chromium (CI installs it via `npx playwright install chromium`).
117
- const chromePath =
118
- process.env.CHROME_PATH ||
119
- (existsSync('/usr/bin/google-chrome') ? '/usr/bin/google-chrome' : undefined)
120
- const browser = await chromium.launch({ headless: true, executablePath: chromePath })
121
- const page = await browser.newPage()
122
- page.on('console', (m) => m.type() === 'error' && console.log('[browser error]', m.text()))
123
- page.on('pageerror', (e) => console.log('[pageerror]', e.message))
124
-
125
- // Node-side signer: build + sign the transaction the wallet forwards.
126
- await page.exposeFunction('__mockSign', async (txJson) => {
127
- const tx = Transaction.from(txJson)
128
- const bytes = await tx.build({ client })
129
- const { signature } = await keypair.signTransaction(bytes)
130
- return { bytes: toBase64(bytes), signature }
131
- })
132
-
133
- // Inject a wallet-standard wallet before app scripts run. The account advertises the app
134
- // network's chain so wallet-standard's required-feature/chain checks are satisfied.
135
- await page.addInitScript(
136
- ({ address, pubkey, chain }) => {
137
- const account = {
138
- address,
139
- publicKey: new Uint8Array(pubkey),
140
- chains: [chain],
141
- features: ['sui:signTransaction'],
142
- label: 'Mock',
143
- }
144
- const wallet = {
145
- version: '1.0.0',
146
- name: 'Mock Test Wallet',
147
- icon: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLz4=',
148
- chains: [chain],
149
- accounts: [account],
150
- features: {
151
- 'standard:connect': { version: '1.0.0', connect: async () => ({ accounts: [account] }) },
152
- 'standard:events': { version: '1.0.0', on: () => () => {} },
153
- 'sui:signTransaction': {
154
- version: '2.0.0',
155
- signTransaction: async (input) => {
156
- const txJson = await input.transaction.toJSON()
157
- return await window.__mockSign(txJson)
158
- },
159
- },
160
- },
161
- }
162
- const callback = ({ register }) => register(wallet)
163
- window.dispatchEvent(new CustomEvent('wallet-standard:register-wallet', { detail: callback }))
164
- window.addEventListener('wallet-standard:app-ready', (e) => callback(e.detail))
165
- },
166
- { address, pubkey: Array.from(keypair.getPublicKey().toRawBytes()), chain: `sui:${APP_NETWORK}` },
167
- )
168
-
169
- await page.goto(APP_URL, { waitUntil: 'networkidle' })
170
-
171
- // Connect: open the dialog, select the app network if it differs from the testnet default,
172
- // then pick the injected wallet.
173
- await page.getByRole('button', { name: 'Connect Wallet', exact: true }).click()
174
- if (APP_NETWORK !== 'testnet') {
175
- await page.locator('#dialog-network-select').selectOption(APP_NETWORK)
176
- }
177
- await page.getByRole('button', { name: /Mock Test Wallet/ }).click()
178
- await page.getByText(/Mock Test Wallet ·/).waitFor({ timeout: 10000 })
179
- console.log('wallet connected in UI')
180
-
181
- // Fill the wizard: identity → token → settings (defaults) → terms (first run) → review.
182
- await page.locator('#package').fill('browser_coin')
183
- await page.locator('#module').fill('browsercoin')
184
- await page.getByRole('button', { name: 'Next', exact: true }).click()
185
- await page.locator('#name').fill('Browser Coin')
186
- await page.locator('#symbol').fill('BRWC')
187
- await page.locator('#description').fill('Deployed via headless browser e2e')
188
- await page.locator('#decimals').fill('6')
189
- await page.getByRole('button', { name: 'Next', exact: true }).click()
190
- await page.getByRole('button', { name: /Review & deploy/ }).click()
191
- await page.getByRole('button', { name: /I understand — continue/ }).click()
192
- await page.getByRole('button', { name: /Confirm & deploy/ }).click()
193
- console.log('confirmed; deploying (real publish + confirmation)…')
194
-
195
- // Wait for the result screen — this is the confirmation the mocked suite cannot reach.
196
- await page.getByRole('heading', { name: /is live on/ }).waitFor({ timeout: RESULT_TIMEOUT })
197
- const coinType = await page.locator('dd.mono').first().innerText()
198
- console.log('UI result coinType:', coinType)
199
-
200
- // Verify the in-browser source-package download (generatePackage + zip + SPDX license fetch).
201
- const [download] = await Promise.all([
202
- page.waitForEvent('download', { timeout: 30000 }),
203
- page.getByRole('button', { name: /Download source package/ }).click(),
204
- ])
205
- const zipBuf = readFileSync(await download.path())
206
- const isZip = zipBuf[0] === 0x50 && zipBuf[1] === 0x4b
207
- console.log('download:', download.suggestedFilename(), zipBuf.length, 'bytes, zip:', isZip)
208
- if (!isZip) fail('downloaded file is not a zip')
209
-
210
- await browser.close()
211
-
212
- // Independent on-chain verification.
213
- if (!coinType.includes('::browsercoin::BROWSERCOIN')) fail(`unexpected coin type: ${coinType}`)
214
-
215
- if (EXPECT_FEE) {
216
- const feeAfter = await suiBalance(FEE_ADDR)
217
- const feeDelta = feeAfter - feeBefore
218
- console.log('fee delivered this run:', feeDelta.toString(), 'MIST (to', FEE_ADDR + ')')
219
- if (feeDelta !== EXPECT_FEE_MIST) fail(`expected ${EXPECT_FEE_MIST} MIST fee this run, got ${feeDelta}`)
220
- } else {
221
- console.log('no treasury configured — fee assertion skipped (app charges no fee)')
222
- }
223
-
224
- console.log(`\nDEPLOY E2E PASS ✓ (${NETWORK}: injected wallet, real publish, result panel, zip${EXPECT_FEE ? ', fee delivered' : ''})`)
225
- }
226
-
227
- main().catch((e) => fail(e.message))
@@ -1,67 +0,0 @@
1
- // End-to-end: patch -> publish (fee + immutable) -> finalize (mint + fixed supply
2
- // + frozen metadata) against localnet, using the actual app lib.
3
- import init from '@mysten/move-bytecode-template';
4
- import { SuiGrpcClient } from '@mysten/sui/grpc';
5
- import { requestSuiFromFaucetV2 } from '@mysten/sui/faucet';
6
- import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
7
- import { patchTemplateModule } from '../src/lib/template.ts';
8
- import { buildPublishTransaction, buildFinalizeTransaction, extractPublishResult, needsFinalize } from '../src/lib/buildPublishTx.ts';
9
- import { toSuiTxResult } from '../src/lib/deploy.ts';
10
-
11
- await (init)();
12
- const client = new SuiGrpcClient({ baseUrl: 'http://127.0.0.1:9000', network: 'localnet' });
13
- const balanceOf = async (owner, coinType) => BigInt((await client.getBalance({ owner, coinType })).balance.balance);
14
- const include = { effects: true, objectTypes: true };
15
- const kp = new Ed25519Keypair(); const sender = kp.toSuiAddress();
16
- const feeRecipient = new Ed25519Keypair().toSuiAddress();
17
- await requestSuiFromFaucetV2({ host: 'http://127.0.0.1:9123', recipient: sender });
18
- for (let i=0;i<25;i++){ if((await balanceOf(sender))>0n)break; await new Promise(r=>setTimeout(r,1000)); }
19
-
20
- const config = {
21
- packageName:'fixed_token', moduleName:'fixedtok', structName:'FIXEDTOK',
22
- symbol:'FIX', name:'Fixed Token', description:'Fixed supply token', iconUrl:'', decimals:6,
23
- initialSupply: 1000n, supplyPolicy:'fixed', metadataPolicy:'frozen', packagePolicy:'immutable',
24
- recipient: sender, license:'MIT', packageDescription:'', projectName:'',
25
- };
26
-
27
- const moduleBytes = await patchTemplateModule(config);
28
- const feeMist = 250000000n;
29
- const pubTx = buildPublishTransaction({ moduleBytes, sender, feeMist, feeRecipient, gasBudget: 500000000n, packagePolicy: config.packagePolicy });
30
- const pub = toSuiTxResult(await client.signAndExecuteTransaction({ signer: kp, transaction: pubTx, include }));
31
- console.log('publish status:', pub.effects?.status?.status);
32
- if (pub.effects?.status?.status !== 'success') { console.log(JSON.stringify(pub.effects?.status)); process.exit(1); }
33
-
34
- await client.waitForTransaction({ digest: pub.digest });
35
-
36
- const result = extractPublishResult(pub.objectChanges ?? [], { network:'localnet', digest: pub.digest, feeRecipient, feeMist });
37
- console.log('coinType:', result.coinType);
38
- console.log('caps:', { t: !!result.treasuryCapId, m: !!result.metadataCapId, upgrade: result.upgradeCapId ?? '(burned=immutable)' });
39
-
40
- // fee arrived?
41
- const feeBal = await balanceOf(feeRecipient);
42
- console.log('fee recipient balance:', feeBal.toString(), feeBal === feeMist ? 'OK' : 'MISMATCH');
43
-
44
- // finalize
45
- console.log('needsFinalize:', needsFinalize(config, sender));
46
- const finTx = buildFinalizeTransaction({ config, coinType: result.coinType, treasuryCapId: result.treasuryCapId, metadataCapId: result.metadataCapId, sender, gasBudget: 500000000n });
47
- const fin = toSuiTxResult(await client.signAndExecuteTransaction({ signer: kp, transaction: finTx, include }));
48
- await client.waitForTransaction({ digest: fin.digest });
49
- console.log('finalize status:', fin.effects?.status?.status);
50
- if (fin.effects?.status?.status !== 'success') { console.log(JSON.stringify(fin.effects?.status)); process.exit(1); }
51
-
52
- // minted coin present?
53
- const total = await balanceOf(sender, result.coinType);
54
- const expected = 1000n * 10n**6n;
55
- console.log('minted balance:', total.toString(), total===expected ? 'OK (1000 * 10^6)' : 'MISMATCH exp '+expected);
56
-
57
- // treasury cap frozen (immutable owner)?
58
- const { object: tcap } = await client.getObject({ objectId: result.treasuryCapId });
59
- const owner = tcap.owner;
60
- console.log('treasuryCap owner after fixed-supply freeze:', JSON.stringify(owner));
61
-
62
- const ok = result.coinType.endsWith('::fixedtok::FIXEDTOK')
63
- && feeBal === feeMist
64
- && !result.upgradeCapId
65
- && total === expected;
66
- console.log('\nE2E', ok ? 'PASS ✓' : 'FAIL ✗');
67
- process.exit(ok ? 0 : 1);
@@ -1,112 +0,0 @@
1
- // Headless browser verification of the Walrus icon-upload widget on TESTNET.
2
- // Injects a wallet-standard wallet backed by the FUNDED keypair (SUI_PRIV env),
3
- // drives the widget's file upload, and asserts the icon field is filled with a
4
- // working aggregator URL that serves the exact uploaded image.
5
- //
6
- // Prereq: build the app + serve on APP_URL, and export SUI_PRIV for the funded
7
- // testnet address (with SUI + WAL).
8
-
9
- import { readFileSync } from 'node:fs'
10
- import { chromium } from 'playwright'
11
- import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'
12
- import { toBase64 } from '@mysten/sui/utils'
13
-
14
- const APP_URL = process.env.APP_URL || 'http://localhost:4173'
15
- const ICON = '/tmp/test-icon.svg'
16
-
17
- const keypair = Ed25519Keypair.fromSecretKey(process.env.SUI_PRIV)
18
- const address = keypair.toSuiAddress()
19
-
20
- function fail(m) {
21
- console.error('WALRUS BROWSER E2E FAIL:', m)
22
- process.exit(1)
23
- }
24
-
25
- async function main() {
26
- console.log('signer:', address)
27
- const browser = await chromium.launch({
28
- headless: true,
29
- executablePath: process.env.CHROME_PATH || '/usr/bin/google-chrome',
30
- // Bypass CORS so the in-browser tx.build() can hit the testnet gRPC endpoint.
31
- args: ['--disable-web-security', '--disable-features=IsolateOrigins,site-per-process'],
32
- })
33
- const page = await browser.newPage()
34
- page.on('pageerror', (e) => console.log('[pageerror]', e.message))
35
-
36
- // Node just signs raw bytes; the browser builds the tx (resolving CoinWithBalance).
37
- await page.exposeFunction('__mockSignBytes', async (byteArr) => {
38
- const bytes = new Uint8Array(byteArr)
39
- const { signature } = await keypair.signTransaction(bytes)
40
- return { bytes: toBase64(bytes), signature }
41
- })
42
-
43
- await page.addInitScript(
44
- ({ address, pubkey }) => {
45
- const account = { address, publicKey: new Uint8Array(pubkey), chains: ['sui:testnet'], features: ['sui:signTransaction'], label: 'Mock' }
46
- const wallet = {
47
- version: '1.0.0', name: 'Mock Test Wallet',
48
- icon: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciLz4=',
49
- chains: ['sui:testnet'], accounts: [account],
50
- features: {
51
- 'standard:connect': { version: '1.0.0', connect: async () => ({ accounts: [account] }) },
52
- 'standard:events': { version: '1.0.0', on: () => () => {} },
53
- 'sui:signTransaction': {
54
- version: '2.0.0',
55
- signTransaction: async (input) => {
56
- // A real wallet sets the sender from the connected account; do the same.
57
- const client = window.__getSuiClient('testnet')
58
- input.transaction.setSenderIfNotSet(input.account.address)
59
- const bytes = await input.transaction.build({ client })
60
- return window.__mockSignBytes(Array.from(bytes))
61
- },
62
- },
63
- },
64
- }
65
- const cb = ({ register }) => register(wallet)
66
- window.dispatchEvent(new CustomEvent('wallet-standard:register-wallet', { detail: cb }))
67
- window.addEventListener('wallet-standard:app-ready', (e) => cb(e.detail))
68
- },
69
- { address, pubkey: Array.from(keypair.getPublicKey().toRawBytes()) },
70
- )
71
-
72
- await page.goto(APP_URL, { waitUntil: 'networkidle' })
73
- await page.getByRole('button', { name: /Connect Mock Test Wallet/ }).click()
74
- await page.getByText(/Mock Test Wallet ·/).waitFor({ timeout: 10000 })
75
- console.log('wallet connected')
76
-
77
- // Drive the widget: choose the file + click upload.
78
- await page.locator('#walrus-file').setInputFiles(ICON)
79
- await page.getByRole('button', { name: /Upload to Walrus/ }).click()
80
- console.log('uploading to Walrus (2 wallet approvals, relay)…')
81
-
82
- // Wait for the icon field to be populated with an aggregator URL.
83
- // (arg must precede options in waitForFunction.)
84
- try {
85
- await page.waitForFunction(
86
- () => (document.querySelector('#icon') ?? {}).value?.includes('/v1/blobs/'),
87
- undefined,
88
- { timeout: 180000, polling: 1000 },
89
- )
90
- } catch (e) {
91
- const status = await page.locator('.hint[aria-live]').allInnerTexts().catch(() => [])
92
- const err = await page.locator('.field-error').allInnerTexts().catch(() => [])
93
- console.log('widget status:', JSON.stringify(status))
94
- console.log('widget errors:', JSON.stringify(err))
95
- throw e
96
- }
97
- const url = await page.locator('#icon').inputValue()
98
- console.log('icon URL:', url)
99
- await browser.close()
100
-
101
- if (!url.includes('aggregator.walrus-testnet') || !url.includes('/v1/blobs/')) fail(`unexpected URL: ${url}`)
102
- const res = await fetch(url)
103
- const body = new Uint8Array(await res.arrayBuffer())
104
- const expected = new Uint8Array(readFileSync(ICON))
105
- const match = body.length === expected.length && body.every((b, i) => b === expected[i])
106
- console.log('aggregator serves', body.length, 'bytes, matches uploaded image:', match)
107
- if (!match) fail('aggregator content does not match uploaded image')
108
-
109
- console.log('\nWALRUS BROWSER E2E PASS ✓ (widget upload -> renderable icon URL on testnet)')
110
- }
111
-
112
- main().catch((e) => fail(e.message))
@@ -1,31 +0,0 @@
1
- // Regenerates public/og-image.png (1200x630 social card) via headless Chrome.
2
- // Run from the app dir: `node scripts/gen-og-image.mjs`. Re-run if branding changes.
3
- import { chromium } from 'playwright'
4
- import { writeFileSync } from 'node:fs'
5
- import { fileURLToPath } from 'node:url'
6
-
7
- const OUT = fileURLToPath(new URL('../public/og-image.png', import.meta.url))
8
- const html = `<!doctype html><html><head><meta charset="utf-8"><style>
9
- *{margin:0;box-sizing:border-box;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif}
10
- body{width:1200px;height:630px;background:linear-gradient(135deg,#0b1120 0%,#111c33 60%,#0e2a3f 100%);color:#f4f7fb;display:flex;flex-direction:column;justify-content:center;padding:80px}
11
- .badge{display:inline-block;font-size:26px;letter-spacing:.12em;text-transform:uppercase;color:#6ea8fe;border:2px solid #2b3a55;border-radius:999px;padding:10px 26px;width:max-content;margin-bottom:36px}
12
- h1{font-size:82px;line-height:1.04;font-weight:800;letter-spacing:-.02em;margin-bottom:28px}
13
- h1 .accent{color:#6ea8fe}
14
- p{font-size:34px;line-height:1.35;color:#aebfd6;max-width:960px}
15
- .row{display:flex;gap:18px;margin-top:52px}
16
- .chip{font-size:24px;color:#cfe0f7;background:#16233d;border:1px solid #26365a;border-radius:10px;padding:12px 20px}
17
- .url{position:absolute;top:84px;right:80px;font-size:26px;color:#6ea8fe;font-weight:600}
18
- </style></head><body>
19
- <div class="badge">Sui &middot; client-side</div>
20
- <h1>Create your own <span class="accent">Sui coin</span><br>in the browser</h1>
21
- <p>Your wallet signs and pays gas &mdash; nothing is compiled or signed on a server. Keep the full, verifiable source package.</p>
22
- <div class="row"><div class="chip">No server</div><div class="chip">No custody</div><div class="chip">Full source included</div></div>
23
- <div class="url">sui-token-deployer.meddleware.co.uk</div>
24
- </body></html>`
25
-
26
- const browser = await chromium.launch({ executablePath: process.env.CHROME_PATH || '/usr/bin/google-chrome' })
27
- const page = await browser.newPage({ viewport: { width: 1200, height: 630 }, deviceScaleFactor: 1 })
28
- await page.setContent(html, { waitUntil: 'networkidle' })
29
- writeFileSync(OUT, await page.screenshot({ type: 'png' }))
30
- await browser.close()
31
- console.log('wrote', OUT)
@@ -1,56 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import { bcs } from '@mysten/bcs';
3
- import * as tpl from '@mysten/move-bytecode-template';
4
- import { SuiGrpcClient } from '@mysten/sui/grpc';
5
- import { requestSuiFromFaucetV2 } from '@mysten/sui/faucet';
6
- import { Transaction } from '@mysten/sui/transactions';
7
- import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
8
- import { toBase64 } from '@mysten/sui/utils';
9
- import { toSuiTxResult } from '../src/lib/deploy.ts';
10
-
11
- const init = tpl.default ?? tpl.init;
12
- try { if (typeof init === 'function') await init(); } catch {}
13
-
14
- const client = new SuiGrpcClient({ baseUrl: 'http://127.0.0.1:9000', network: 'localnet' });
15
-
16
- const orig = new Uint8Array(readFileSync(new URL('../src/move-template/sui_token_template.mv', import.meta.url)));
17
- const json = tpl.deserialize(orig);
18
- const rename = { sui_token_template: 'mycoin', SUI_TOKEN_TEMPLATE: 'MYCOIN' };
19
- json.identifiers = json.identifiers.map((id) => rename[id] ?? id);
20
- const vec = (s) => Array.from(bcs.string().serialize(s).toBytes());
21
- const patch = (cur, nw) => { const c = json.constant_pool.find((k)=>JSON.stringify(k.data)===JSON.stringify(vec(cur))); c.data = vec(nw); };
22
- json.constant_pool.find((c)=>c.type_==='U8').data = [6];
23
- patch('TEMPLATE_SYMBOL','MYC'); patch('TEMPLATE_NAME','My Coin');
24
- patch('TEMPLATE_DESCRIPTION','A great coin'); patch('TEMPLATE_ICON_URL','https://x/y.svg');
25
- const moduleBytes = new Uint8Array(tpl.serialize(json));
26
- console.log('identifiers sorted?', JSON.stringify(json.identifiers) === JSON.stringify([...json.identifiers].sort()));
27
-
28
- const kp = new Ed25519Keypair();
29
- const addr = kp.toSuiAddress();
30
- console.log('address:', addr);
31
- await requestSuiFromFaucetV2({ host: 'http://127.0.0.1:9123', recipient: addr });
32
- let bal = '0';
33
- for (let i=0;i<25;i++){ const { balance } = await client.getBalance({ owner: addr }); if (BigInt(balance.balance) > 0n){ bal=balance.balance; break;} await new Promise(r=>setTimeout(r,1000)); }
34
- console.log('balance:', bal);
35
-
36
- const tx = new Transaction();
37
- tx.setSender(addr);
38
- const [upgradeCap] = tx.publish({ modules: [toBase64(moduleBytes)], dependencies: ['0x1', '0x2'] });
39
- tx.transferObjects([upgradeCap], addr);
40
- tx.setGasBudget(500000000);
41
-
42
- try {
43
- const res = toSuiTxResult(await client.signAndExecuteTransaction({ signer: kp, transaction: tx, include: { effects: true, objectTypes: true } }));
44
- const status = res.effects?.status?.status;
45
- console.log('\nstatus:', status);
46
- if (status !== 'success') { console.log('ERR:', JSON.stringify(res.effects?.status)); process.exit(1); }
47
- for (const c of (res.objectChanges ?? [])) {
48
- if (c.type==='published') console.log('packageId:', c.packageId);
49
- if (c.type==='created') console.log(' created:', c.objectType);
50
- }
51
- console.log('\nPUBLISH PASS - verifier accepted the in-place patched module');
52
- } catch (e) {
53
- console.log('\nPUBLISH FAIL:', e.message);
54
- process.exit(2);
55
- }
56
-
@@ -1,45 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- # Recompiles the canonical sui-token-template Move module and refreshes the
4
- # bytecode artifact the deployer ships (src/move-template/*). Run this whenever
5
- # blockchain/sui/sui-token-template/sources/sui_token_template.move changes, so
6
- # the shipped bytecode stays in lock-step with the downloadable source generator.
7
- #
8
- # IMPORTANT: use `sui move build` (NOT `sui move test`) — the test profile emits
9
- # instrumented bytecode (magic 0xDEADC0DE) that is not publishable.
10
-
11
- HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
- APP_DIR="$HERE/.."
13
- # The canonical template ships as the published @meddleware/sui-token-template
14
- # package; resolve it from node_modules. A local checkout can be pointed at via
15
- # SUI_TOKEN_TEMPLATE_DIR (e.g. during template co-development before publish).
16
- TEMPLATE_DIR="${SUI_TOKEN_TEMPLATE_DIR:-$(cd "$APP_DIR" && node -e "process.stdout.write(require('path').dirname(require.resolve('@meddleware/sui-token-template/package.json')))" 2>/dev/null || true)}"
17
- if [[ -z "${TEMPLATE_DIR:-}" || ! -f "$TEMPLATE_DIR/Move.toml" ]]; then
18
- echo "ERROR: sui-token-template sources not found. Install @meddleware/sui-token-template or set SUI_TOKEN_TEMPLATE_DIR to a local checkout." >&2
19
- exit 1
20
- fi
21
-
22
- echo "Building $TEMPLATE_DIR (testnet build-env)…"
23
- ( cd "$TEMPLATE_DIR" && rm -rf build && sui move build --build-env testnet >/dev/null )
24
-
25
- MV="$TEMPLATE_DIR/build/sui_token_template/bytecode_modules/sui_token_template.mv"
26
- MAGIC="$(head -c 4 "$MV" | xxd -p)"
27
- if [[ "$MAGIC" != "a11ceb0b" ]]; then
28
- echo "ERROR: unexpected bytecode magic $MAGIC (expected a11ceb0b)" >&2
29
- exit 1
30
- fi
31
-
32
- cp "$MV" "$APP_DIR/src/move-template/sui_token_template.mv"
33
- B64="$(base64 -w0 "$MV")"
34
-
35
- # Refresh only the base64 literal in template.ts, preserving the rest.
36
- node - "$APP_DIR/src/move-template/template.ts" "$B64" <<'NODE'
37
- const fs = require('node:fs');
38
- const [file, b64] = process.argv.slice(2);
39
- let s = fs.readFileSync(file, 'utf8');
40
- s = s.replace(/export const TEMPLATE_MODULE_B64 =\n\s*'[^']*';/, `export const TEMPLATE_MODULE_B64 =\n '${b64}';`);
41
- fs.writeFileSync(file, s);
42
- console.log('updated TEMPLATE_MODULE_B64 (%d base64 chars)', b64.length);
43
- NODE
44
-
45
- echo "Done. Verify the DEFAULT_* constants in template.ts still match the source."
@@ -1,36 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- # Copies the canonical sui-token-template text files into the app as a single
4
- # JSON module (src/template-src/files.json), so the downloadable-package
5
- # generator stays the single source of truth with the CLI generator. Re-run
6
- # whenever the template's source/docs/scripts change.
7
-
8
- HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
- APP_DIR="$HERE/.."
10
- # Canonical template = the published @meddleware/sui-token-template package
11
- # (resolved from node_modules); override with SUI_TOKEN_TEMPLATE_DIR for a local checkout.
12
- T="${SUI_TOKEN_TEMPLATE_DIR:-$(cd "$APP_DIR" && node -e "process.stdout.write(require('path').dirname(require.resolve('@meddleware/sui-token-template/package.json')))" 2>/dev/null || true)}"
13
- if [[ -z "${T:-}" || ! -f "$T/Move.toml" ]]; then
14
- echo "ERROR: sui-token-template sources not found. Install @meddleware/sui-token-template or set SUI_TOKEN_TEMPLATE_DIR to a local checkout." >&2
15
- exit 1
16
- fi
17
- OUT="$APP_DIR/src/template-src/files.json"
18
- mkdir -p "$APP_DIR/src/template-src"
19
-
20
- node - "$T" "$OUT" <<'NODE'
21
- const fs = require('node:fs');
22
- const [T, OUT] = process.argv.slice(2);
23
- const read = (p) => fs.readFileSync(`${T}/${p}`, 'utf8');
24
- const files = {
25
- 'Move.toml': read('Move.toml'),
26
- 'source.move': read('sources/sui_token_template.move'),
27
- 'scripts/publish.sh': read('templates/publish.sh'),
28
- '.gitignore': read('templates/gitignore'),
29
- 'README.md': read('templates/README.md'),
30
- 'deployments.md': read('templates/deployments.md'),
31
- 'CLAUDE.md': read('templates/CLAUDE.md'),
32
- 'AGENTS.md': read('templates/AGENTS.md'),
33
- };
34
- fs.writeFileSync(OUT, JSON.stringify(files, null, 2) + '\n');
35
- console.log('wrote %s (%d files)', OUT, Object.keys(files).length);
36
- NODE
@@ -1,49 +0,0 @@
1
- // v7-safe patch via direct JSON mutation (deserialize -> mutate -> serialize),
2
- // bypassing update_identifiers/update_constants which cannot re-serialize v7.
3
- import { readFileSync } from 'node:fs';
4
- import { bcs } from '@mysten/bcs';
5
- import * as tpl from '@mysten/move-bytecode-template';
6
-
7
- const init = tpl.default ?? tpl.init;
8
- try { if (typeof init === 'function') await init(); } catch {}
9
-
10
- const orig = new Uint8Array(readFileSync(new URL('../src/move-template/sui_token_template.mv', import.meta.url)));
11
- const json = tpl.deserialize(orig);
12
-
13
- // 1) rename identifiers in place (indices preserved => all handle refs stay valid)
14
- const rename = { sui_token_template: 'mycoin', SUI_TOKEN_TEMPLATE: 'MYCOIN' };
15
- json.identifiers = json.identifiers.map((id) => rename[id] ?? id);
16
-
17
- // 2) patch constant_pool by matching the known default values
18
- const vec = (s) => Array.from(bcs.string().serialize(s).toBytes()); // uleb len + utf8 bytes (== vector<u8>)
19
- const patchByCurrent = (currentStr, newStr) => {
20
- const cur = vec(currentStr);
21
- const c = json.constant_pool.find((k) => JSON.stringify(k.data) === JSON.stringify(cur));
22
- if (!c) throw new Error('constant not found: ' + currentStr);
23
- c.data = vec(newStr);
24
- };
25
- // decimals (U8)
26
- json.constant_pool.find((c) => c.type_ === 'U8').data = [6];
27
- patchByCurrent('TEMPLATE_SYMBOL', 'MYC');
28
- patchByCurrent('TEMPLATE_NAME', 'My Coin');
29
- patchByCurrent('TEMPLATE_DESCRIPTION', 'A great coin');
30
- patchByCurrent('TEMPLATE_ICON_URL', 'https://x/y.svg');
31
-
32
- const out = new Uint8Array(tpl.serialize(json));
33
- const magic = Buffer.from(out.slice(0, 4)).toString('hex');
34
- console.log('patched bytes=%d magic=%s (orig=%d)', out.length, magic, orig.length);
35
-
36
- // re-deserialize the patched module to confirm structural validity
37
- const back = tpl.deserialize(out);
38
- const asStr = (d) => Buffer.from(d.slice(1)).toString('utf8');
39
- console.log('identifiers now include:', back.identifiers.filter((i) => ['mycoin','MYCOIN','sui_token_template','SUI_TOKEN_TEMPLATE'].includes(i)));
40
- console.log('constants now:', back.constant_pool.map((c) => c.type_==='U8'?`u8=${c.data[0]}`:asStr(c.data)));
41
-
42
- const ok = magic === 'a11ceb0b'
43
- && back.identifiers.includes('mycoin') && back.identifiers.includes('MYCOIN')
44
- && !back.identifiers.includes('sui_token_template') && !back.identifiers.includes('SUI_TOKEN_TEMPLATE')
45
- && back.constant_pool.some((c) => c.type_==='U8' && c.data[0]===6)
46
- && ['MYC','My Coin','A great coin','https://x/y.svg'].every((v) => back.constant_pool.some((c)=>c.type_!=='U8'&&asStr(c.data)===v))
47
- && !back.constant_pool.some((c)=>c.type_!=='U8'&&asStr(c.data).startsWith('TEMPLATE_'));
48
- console.log('\nJSON-MUTATION ROUND-TRIP', ok ? 'PASS ✓' : 'FAIL ✗');
49
- process.exit(ok ? 0 : 1);
@@ -1,11 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
-
3
- import { mount } from '@vue/test-utils'
4
- import App from '../App.vue'
5
-
6
- describe('App', () => {
7
- it('mounts renders properly', () => {
8
- const wrapper = mount(App)
9
- expect(wrapper.text()).toContain('You did it!')
10
- })
11
- })