create-dubhe 1.2.0-pre.124 → 1.2.0-pre.125

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 (104) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/101/sui-template/packages/contracts/src/dubhe/Move.lock +0 -6
  5. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/systems/dapp_system.move +82 -42
  6. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/scene_session_test.move +269 -0
  7. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/typed_scene_test.move +25 -9
  8. package/templates/contract/sui-template/src/dubhe/Move.lock +0 -6
  9. package/templates/contract/sui-template/src/dubhe/sources/systems/dapp_system.move +82 -42
  10. package/templates/contract/sui-template/src/dubhe/sources/tests/scene_session_test.move +269 -0
  11. package/templates/contract/sui-template/src/dubhe/sources/tests/typed_scene_test.move +25 -9
  12. package/templates/nextjs/sui-card-duel/.gitignore_ +51 -0
  13. package/templates/nextjs/sui-card-duel/README.md +213 -0
  14. package/templates/nextjs/sui-card-duel/docker-compose.yml +34 -0
  15. package/templates/nextjs/sui-card-duel/mprocs.yaml +17 -0
  16. package/templates/nextjs/sui-card-duel/package.json +43 -0
  17. package/templates/nextjs/sui-card-duel/packages/client/.gitignore_ +48 -0
  18. package/templates/nextjs/sui-card-duel/packages/client/.prettierignore +1 -0
  19. package/templates/nextjs/sui-card-duel/packages/client/.prettierrc.json +8 -0
  20. package/templates/nextjs/sui-card-duel/packages/client/eslint.config.mjs +22 -0
  21. package/templates/nextjs/sui-card-duel/packages/client/next.config.mjs +8 -0
  22. package/templates/nextjs/sui-card-duel/packages/client/package.json +47 -0
  23. package/templates/nextjs/sui-card-duel/packages/client/postcss.config.mjs +8 -0
  24. package/templates/nextjs/sui-card-duel/packages/client/src/app/admin/ArenaPanel.tsx +307 -0
  25. package/templates/nextjs/sui-card-duel/packages/client/src/app/admin/page.tsx +622 -0
  26. package/templates/nextjs/sui-card-duel/packages/client/src/app/brawl/[id]/page.tsx +559 -0
  27. package/templates/nextjs/sui-card-duel/packages/client/src/app/collection/page.tsx +202 -0
  28. package/templates/nextjs/sui-card-duel/packages/client/src/app/components/CardView.tsx +66 -0
  29. package/templates/nextjs/sui-card-duel/packages/client/src/app/components/GameShell.tsx +294 -0
  30. package/templates/nextjs/sui-card-duel/packages/client/src/app/components/icons.tsx +101 -0
  31. package/templates/nextjs/sui-card-duel/packages/client/src/app/duel/[id]/page.tsx +477 -0
  32. package/templates/nextjs/sui-card-duel/packages/client/src/app/favicon.ico +0 -0
  33. package/templates/nextjs/sui-card-duel/packages/client/src/app/globals.css +85 -0
  34. package/templates/nextjs/sui-card-duel/packages/client/src/app/hooks/useGame.ts +313 -0
  35. package/templates/nextjs/sui-card-duel/packages/client/src/app/layout.tsx +24 -0
  36. package/templates/nextjs/sui-card-duel/packages/client/src/app/leaderboard/page.tsx +123 -0
  37. package/templates/nextjs/sui-card-duel/packages/client/src/app/lib/game.ts +77 -0
  38. package/templates/nextjs/sui-card-duel/packages/client/src/app/lib/scenes.ts +226 -0
  39. package/templates/nextjs/sui-card-duel/packages/client/src/app/market/page.tsx +418 -0
  40. package/templates/nextjs/sui-card-duel/packages/client/src/app/page.tsx +427 -0
  41. package/templates/nextjs/sui-card-duel/packages/client/src/app/providers.tsx +69 -0
  42. package/templates/nextjs/sui-card-duel/packages/client/tailwind.config.ts +21 -0
  43. package/templates/nextjs/sui-card-duel/packages/client/tsconfig.json +30 -0
  44. package/templates/nextjs/sui-card-duel/packages/contracts/.gitignore_ +51 -0
  45. package/templates/nextjs/sui-card-duel/packages/contracts/.prettierignore +1 -0
  46. package/templates/nextjs/sui-card-duel/packages/contracts/.prettierrc.json +8 -0
  47. package/templates/nextjs/sui-card-duel/packages/contracts/Dockerfile +5 -0
  48. package/templates/nextjs/sui-card-duel/packages/contracts/Pub.localnet.toml +13 -0
  49. package/templates/nextjs/sui-card-duel/packages/contracts/card_duel.lock.json +66 -0
  50. package/templates/nextjs/sui-card-duel/packages/contracts/docker-compose.yml +34 -0
  51. package/templates/nextjs/sui-card-duel/packages/contracts/dubhe.config.ts +150 -0
  52. package/templates/nextjs/sui-card-duel/packages/contracts/logs/combined.log +31383 -0
  53. package/templates/nextjs/sui-card-duel/packages/contracts/package.json +49 -0
  54. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/config.ts +17 -0
  55. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-brawl.ts +270 -0
  56. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-duel.ts +272 -0
  57. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-lobby.ts +177 -0
  58. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-market.ts +219 -0
  59. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-session.ts +342 -0
  60. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/query-dapp-revenue.ts +95 -0
  61. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/seed.ts +92 -0
  62. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/withdraw-dapp-revenue.ts +56 -0
  63. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/Move.toml +15 -0
  64. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/dapp_key.move +46 -0
  65. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/enums/card_kind.move +47 -0
  66. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/enums/match_state.move +37 -0
  67. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/enums/rarity.move +37 -0
  68. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/error.move +126 -0
  69. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/genesis.move +33 -0
  70. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/init_test.move +23 -0
  71. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/objects/arena.move +137 -0
  72. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/permits/brawl_permit.move +125 -0
  73. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/permits/duel_permit.move +125 -0
  74. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/battle_state.move +255 -0
  75. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/card.move +282 -0
  76. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/deck.move +64 -0
  77. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/game_config.move +267 -0
  78. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/gold.move +205 -0
  79. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/profile.move +255 -0
  80. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/scenes/brawl.move +313 -0
  81. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/scenes/duel.move +313 -0
  82. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/user_storage_init.move +26 -0
  83. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/scripts/deploy_hook.move +32 -0
  84. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/scripts/migrate.move +7 -0
  85. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/arena_system.move +57 -0
  86. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/brawl_system.move +559 -0
  87. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/card_system.move +146 -0
  88. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/duel_system.move +515 -0
  89. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/market_system.move +115 -0
  90. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/player_system.move +37 -0
  91. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/tests/brawl_tests.move +385 -0
  92. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/tests/duel_tests.move +656 -0
  93. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/tests/test_helpers.move +31 -0
  94. package/templates/nextjs/sui-card-duel/packages/contracts/sui-indexer-schema.graphql +5973 -0
  95. package/templates/nextjs/sui-card-duel/pnpm-workspace.yaml +2 -0
  96. package/templates/nextjs/sui-farm/packages/client/src/app/market/page.tsx +4 -13
  97. package/templates/nextjs/sui-farm/packages/client/src/app/page.tsx +73 -130
  98. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/permits/world.move +12 -9
  99. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/systems/world_system.move +1 -1
  100. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/Move.lock +0 -6
  101. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/systems/dapp_system.move +82 -42
  102. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/scene_session_test.move +269 -0
  103. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/typed_scene_test.move +25 -9
  104. package/templates/nextjs/sui-farm/packages/client/src/app/hooks/useSessionKey.ts +0 -248
package/dist/cli.js CHANGED
@@ -62,7 +62,7 @@ var CHAINS = [
62
62
  // package.json
63
63
  var package_default = {
64
64
  name: "create-dubhe",
65
- version: "1.2.0-pre.124",
65
+ version: "1.2.0-pre.125",
66
66
  repository: {
67
67
  type: "git",
68
68
  url: "https://github.com/0xobelisk/dubhe.git"
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bin/cli.ts","../src/config/chains.ts","../package.json","../src/exists.ts"],"sourcesContent":["import { fileURLToPath } from 'node:url';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport glob from 'fast-glob';\nimport yargsInteractive from 'yargs-interactive';\nimport { CHAINS } from '../config/chains';\nimport packageJson from '../../package.json';\nimport { exists } from '../exists';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst cwd = process.cwd();\n\nconst init = async () => {\n // Prepare chain options\n const chainChoices = CHAINS.map(({ title, description, value }) => ({\n name: `${title} - ${description}`,\n value\n }));\n\n // Step 1: Choose project name and chain\n const firstStep = await yargsInteractive()\n .usage('$0 [args]')\n .interactive({\n interactive: { default: true },\n projectName: {\n describe: 'Name your project',\n type: 'input'\n },\n chain: {\n describe: 'Pick your chain',\n type: 'list',\n choices: chainChoices.map((c) => c.value)\n },\n dubheVersion: {\n describe: 'The version of Dubhe packages to use, defaults to latest',\n type: 'input',\n default: packageJson.version\n }\n });\n\n const { projectName, chain, dubheVersion } = firstStep;\n if (!projectName) throw new Error('No project name provided.');\n\n // Get available templates based on the selected chain\n const selectedChain = CHAINS.find((c) => c.value === chain);\n if (!selectedChain) {\n throw new Error('Invalid chain selection');\n }\n\n // Prepare platform options\n const platformChoices = selectedChain.supportedTemplates.map(({ title, description, value }) => ({\n name: `${title} - ${description}`,\n value\n }));\n\n // Step 2: Choose platform\n const secondStep = await yargsInteractive()\n .usage('$0 [args]')\n .interactive({\n interactive: { default: true },\n platform: {\n describe: 'Pick your platform',\n type: 'list',\n choices: platformChoices.map((c) => c.value)\n }\n });\n\n const { platform } = secondStep;\n\n const selectedTemplate = selectedChain.supportedTemplates.find((t) => t.value === platform);\n if (!selectedTemplate) {\n throw new Error('Invalid platform selection');\n }\n\n const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent);\n const pkgManager = pkgInfo ? pkgInfo.name : 'npm';\n\n const sourceDir = path.join(\n __dirname,\n '..',\n 'templates',\n selectedTemplate.path.replace('{chain}', chain)\n );\n\n if (!(await exists(sourceDir))) {\n throw new Error(`Template directory not found: ${sourceDir}`);\n }\n\n const destDir = path.join(process.cwd(), projectName);\n if (await exists(destDir)) {\n throw new Error(`Target directory \"${destDir}\" already exists.`);\n }\n\n const files = await glob('**/*', { cwd: sourceDir, dot: true });\n\n for (const filename of files) {\n const sourceFile = path.join(sourceDir, filename);\n const destFile = path.join(destDir, filename);\n\n await fs.mkdir(path.dirname(destFile), { recursive: true });\n\n if (/package\\.json$/.test(sourceFile)) {\n const source = await fs.readFile(sourceFile, 'utf-8');\n await fs.writeFile(destFile, source.replaceAll(/{{dubhe-version}}/g, dubheVersion), 'utf-8');\n } else if (/\\.gitignore_$/.test(sourceFile)) {\n await fs.copyFile(sourceFile, destFile.replace(/_$/, ''));\n } else {\n await fs.copyFile(sourceFile, destFile);\n }\n }\n\n const cdProjectName = path.relative(cwd, destDir);\n\n const styles = {\n success: '\\x1b[32m%s\\x1b[0m',\n info: '\\x1b[36m%s\\x1b[0m',\n command: '\\x1b[33m%s\\x1b[0m',\n separator: '\\x1b[90m%s\\x1b[0m'\n };\n\n console.log('\\n' + '='.repeat(60));\n console.log(styles.success, '🎉 Project creation successful!');\n console.log(styles.info, `📁 Project location: ${destDir}`);\n console.log(styles.separator, '-'.repeat(60));\n console.log(styles.info, 'Next steps:\\n');\n\n if (destDir !== cwd) {\n console.log(\n styles.command,\n ` cd ${cdProjectName.includes(' ') ? `\"${cdProjectName}\"` : cdProjectName}`\n );\n }\n\n const actualTemplate = selectedTemplate.value;\n\n switch (actualTemplate) {\n case '101':\n case 'web':\n console.log(styles.command, ` ${pkgManager} install`);\n console.log(styles.command, ` ${pkgManager} dubhe doctor`);\n console.log(styles.command, ` ${pkgManager} run dev`);\n break;\n case 'contract':\n console.log(styles.command, ` ${pkgManager} install`);\n console.log(styles.command, ` ${pkgManager} dubhe doctor`);\n break;\n }\n\n console.log(styles.separator, '\\n' + '='.repeat(60) + '\\n');\n};\n\nfunction pkgFromUserAgent(userAgent: string | undefined) {\n if (!userAgent) return undefined;\n const pkgSpec = userAgent.split(' ')[0];\n const pkgSpecArr = pkgSpec.split('/');\n return {\n name: pkgSpecArr[0],\n version: pkgSpecArr[1]\n };\n}\n\ninit().catch((e) => {\n console.error(e);\n});\n","interface Template {\n title: string;\n description: string;\n value: string;\n path: string;\n}\n\ninterface Chain {\n title: string;\n description: string;\n value: string;\n supportedTemplates: Template[];\n}\n\nconst TEMPLATES = {\n QUICK_START: {\n title: '101',\n description: 'Quick start',\n value: '101',\n path: '101/{chain}-template'\n },\n WEB: {\n title: 'Web',\n description: 'Web template',\n value: 'web',\n path: 'nextjs/{chain}-template'\n },\n CONTRACT: {\n title: 'Contract',\n description: 'Contract template',\n value: 'contract',\n path: 'contract/{chain}-template'\n }\n} as const;\n\nexport const CHAINS: Chain[] = [\n {\n title: 'sui',\n description: 'Sui',\n value: 'sui',\n supportedTemplates: [TEMPLATES.QUICK_START, TEMPLATES.WEB, TEMPLATES.CONTRACT]\n },\n {\n title: 'aptos',\n description: 'Aptos',\n value: 'aptos',\n supportedTemplates: [TEMPLATES.QUICK_START, TEMPLATES.WEB, TEMPLATES.CONTRACT]\n },\n {\n title: 'rooch',\n description: 'Rooch',\n value: 'rooch',\n supportedTemplates: [TEMPLATES.QUICK_START]\n },\n {\n title: 'initia',\n description: 'Initia',\n value: 'initia',\n supportedTemplates: [TEMPLATES.QUICK_START]\n },\n {\n title: 'movement',\n description: 'Movement',\n value: 'movement',\n supportedTemplates: [TEMPLATES.QUICK_START]\n }\n];\n","{\n \"name\": \"create-dubhe\",\n \"version\": \"1.2.0-pre.124\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/0xobelisk/dubhe.git\"\n },\n \"license\": \"MIT\",\n \"author\": \"team@obelisk.build\",\n \"type\": \"module\",\n \"bin\": \"bin/cli.js\",\n \"files\": [\n \"bin\",\n \"dist\",\n \"templates\"\n ],\n \"scripts\": {\n \"build\": \"pnpm run type-check && pnpm run build:js\",\n \"build:js\": \"tsup && pnpm run copy-templates\",\n \"clean\": \"pnpm run clean:js\",\n \"clean:js\": \"shx rm -rf dist\",\n \"cocos-js-build\": \"cd cocos-lib-builder && npm i && browserify aptos.js -p esmify > ../templates/cocos/aptos-template/assets/lib/dubhe.js && browserify sui.js -p esmify > ../templates/cocos/sui-template/assets/lib/dubhe.js\",\n \"copy-templates\": \"tsx ./scripts/copy-templates.ts\",\n \"dev\": \"tsup --watch\",\n \"format\": \"prettier --write .\",\n \"format:check\": \"prettier --check .\",\n \"lint\": \"eslint . --ext .ts\",\n \"type-check\": \"tsc --noEmit\",\n \"validate\": \"pnpm format:check && pnpm lint && pnpm type-check\"\n },\n \"dependencies\": {\n \"browser-resolve\": \"^2.0.0\",\n \"browser-sync\": \"^2.29.3\",\n \"fast-glob\": \"^3.3.3\",\n \"yargs-interactive\": \"^3.0.1\"\n },\n \"devDependencies\": {\n \"@types/cross-spawn\": \"^6.0.2\",\n \"@types/minimist\": \"^1.2.2\",\n \"@types/prompts\": \"^2.4.3\",\n \"@types/yargs-interactive\": \"^2.1.6\",\n \"browserify\": \"^17.0.0\",\n \"cross-spawn\": \"^7.0.3\",\n \"eslint\": \"^9.0.0\",\n \"eslint-config-prettier\": \"^9.1.0\",\n \"esmify\": \"^2.1.1\",\n \"kolorist\": \"^1.7.0\",\n \"prettier\": \"3.3.3\",\n \"prompts\": \"^2.4.2\",\n \"typescript\": \"^5.8.3\"\n },\n \"engines\": {\n \"node\": \">=22.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n }\n}\n","import fs from 'node:fs/promises';\n\nexport async function exists(path: string) {\n try {\n await fs.access(path);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":";AAAA,SAAS,qBAAqB;AAC9B,OAAOA,SAAQ;AACf,OAAO,UAAU;AACjB,OAAO,UAAU;AACjB,OAAO,sBAAsB;;;ACU7B,IAAM,YAAY;AAAA,EAChB,aAAa;AAAA,IACX,OAAO;AAAA,IACP,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,EACR;AAAA,EACA,KAAK;AAAA,IACH,OAAO;AAAA,IACP,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,EACR;AAAA,EACA,UAAU;AAAA,IACR,OAAO;AAAA,IACP,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,EACR;AACF;AAEO,IAAM,SAAkB;AAAA,EAC7B;AAAA,IACE,OAAO;AAAA,IACP,aAAa;AAAA,IACb,OAAO;AAAA,IACP,oBAAoB,CAAC,UAAU,aAAa,UAAU,KAAK,UAAU,QAAQ;AAAA,EAC/E;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,aAAa;AAAA,IACb,OAAO;AAAA,IACP,oBAAoB,CAAC,UAAU,aAAa,UAAU,KAAK,UAAU,QAAQ;AAAA,EAC/E;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,aAAa;AAAA,IACb,OAAO;AAAA,IACP,oBAAoB,CAAC,UAAU,WAAW;AAAA,EAC5C;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,aAAa;AAAA,IACb,OAAO;AAAA,IACP,oBAAoB,CAAC,UAAU,WAAW;AAAA,EAC5C;AAAA,EACA;AAAA,IACE,OAAO;AAAA,IACP,aAAa;AAAA,IACb,OAAO;AAAA,IACP,oBAAoB,CAAC,UAAU,WAAW;AAAA,EAC5C;AACF;;;AClEA;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,YAAc;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,EACT;AAAA,EACA,SAAW;AAAA,EACX,QAAU;AAAA,EACV,MAAQ;AAAA,EACR,KAAO;AAAA,EACP,OAAS;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,SAAW;AAAA,IACT,OAAS;AAAA,IACT,YAAY;AAAA,IACZ,OAAS;AAAA,IACT,YAAY;AAAA,IACZ,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,IAClB,KAAO;AAAA,IACP,QAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,MAAQ;AAAA,IACR,cAAc;AAAA,IACd,UAAY;AAAA,EACd;AAAA,EACA,cAAgB;AAAA,IACd,mBAAmB;AAAA,IACnB,gBAAgB;AAAA,IAChB,aAAa;AAAA,IACb,qBAAqB;AAAA,EACvB;AAAA,EACA,iBAAmB;AAAA,IACjB,sBAAsB;AAAA,IACtB,mBAAmB;AAAA,IACnB,kBAAkB;AAAA,IAClB,4BAA4B;AAAA,IAC5B,YAAc;AAAA,IACd,eAAe;AAAA,IACf,QAAU;AAAA,IACV,0BAA0B;AAAA,IAC1B,QAAU;AAAA,IACV,UAAY;AAAA,IACZ,UAAY;AAAA,IACZ,SAAW;AAAA,IACX,YAAc;AAAA,EAChB;AAAA,EACA,SAAW;AAAA,IACT,MAAQ;AAAA,EACV;AAAA,EACA,eAAiB;AAAA,IACf,QAAU;AAAA,EACZ;AACF;;;ACzDA,OAAO,QAAQ;AAEf,eAAsB,OAAOC,OAAc;AACzC,MAAI;AACF,UAAM,GAAG,OAAOA,KAAI;AACpB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AHAA,IAAM,aAAa,cAAc,YAAY,GAAG;AAChD,IAAM,YAAY,KAAK,QAAQ,UAAU;AAEzC,IAAM,MAAM,QAAQ,IAAI;AAExB,IAAM,OAAO,YAAY;AAEvB,QAAM,eAAe,OAAO,IAAI,CAAC,EAAE,OAAO,aAAa,MAAM,OAAO;AAAA,IAClE,MAAM,GAAG,KAAK,MAAM,WAAW;AAAA,IAC/B;AAAA,EACF,EAAE;AAGF,QAAM,YAAY,MAAM,iBAAiB,EACtC,MAAM,WAAW,EACjB,YAAY;AAAA,IACX,aAAa,EAAE,SAAS,KAAK;AAAA,IAC7B,aAAa;AAAA,MACX,UAAU;AAAA,MACV,MAAM;AAAA,IACR;AAAA,IACA,OAAO;AAAA,MACL,UAAU;AAAA,MACV,MAAM;AAAA,MACN,SAAS,aAAa,IAAI,CAAC,MAAM,EAAE,KAAK;AAAA,IAC1C;AAAA,IACA,cAAc;AAAA,MACZ,UAAU;AAAA,MACV,MAAM;AAAA,MACN,SAAS,gBAAY;AAAA,IACvB;AAAA,EACF,CAAC;AAEH,QAAM,EAAE,aAAa,OAAO,aAAa,IAAI;AAC7C,MAAI,CAAC,YAAa,OAAM,IAAI,MAAM,2BAA2B;AAG7D,QAAM,gBAAgB,OAAO,KAAK,CAAC,MAAM,EAAE,UAAU,KAAK;AAC1D,MAAI,CAAC,eAAe;AAClB,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AAGA,QAAM,kBAAkB,cAAc,mBAAmB,IAAI,CAAC,EAAE,OAAO,aAAa,MAAM,OAAO;AAAA,IAC/F,MAAM,GAAG,KAAK,MAAM,WAAW;AAAA,IAC/B;AAAA,EACF,EAAE;AAGF,QAAM,aAAa,MAAM,iBAAiB,EACvC,MAAM,WAAW,EACjB,YAAY;AAAA,IACX,aAAa,EAAE,SAAS,KAAK;AAAA,IAC7B,UAAU;AAAA,MACR,UAAU;AAAA,MACV,MAAM;AAAA,MACN,SAAS,gBAAgB,IAAI,CAAC,MAAM,EAAE,KAAK;AAAA,IAC7C;AAAA,EACF,CAAC;AAEH,QAAM,EAAE,SAAS,IAAI;AAErB,QAAM,mBAAmB,cAAc,mBAAmB,KAAK,CAAC,MAAM,EAAE,UAAU,QAAQ;AAC1F,MAAI,CAAC,kBAAkB;AACrB,UAAM,IAAI,MAAM,4BAA4B;AAAA,EAC9C;AAEA,QAAM,UAAU,iBAAiB,QAAQ,IAAI,qBAAqB;AAClE,QAAM,aAAa,UAAU,QAAQ,OAAO;AAE5C,QAAM,YAAY,KAAK;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IACA,iBAAiB,KAAK,QAAQ,WAAW,KAAK;AAAA,EAChD;AAEA,MAAI,CAAE,MAAM,OAAO,SAAS,GAAI;AAC9B,UAAM,IAAI,MAAM,iCAAiC,SAAS,EAAE;AAAA,EAC9D;AAEA,QAAM,UAAU,KAAK,KAAK,QAAQ,IAAI,GAAG,WAAW;AACpD,MAAI,MAAM,OAAO,OAAO,GAAG;AACzB,UAAM,IAAI,MAAM,qBAAqB,OAAO,mBAAmB;AAAA,EACjE;AAEA,QAAM,QAAQ,MAAM,KAAK,QAAQ,EAAE,KAAK,WAAW,KAAK,KAAK,CAAC;AAE9D,aAAW,YAAY,OAAO;AAC5B,UAAM,aAAa,KAAK,KAAK,WAAW,QAAQ;AAChD,UAAM,WAAW,KAAK,KAAK,SAAS,QAAQ;AAE5C,UAAMC,IAAG,MAAM,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAE1D,QAAI,iBAAiB,KAAK,UAAU,GAAG;AACrC,YAAM,SAAS,MAAMA,IAAG,SAAS,YAAY,OAAO;AACpD,YAAMA,IAAG,UAAU,UAAU,OAAO,WAAW,sBAAsB,YAAY,GAAG,OAAO;AAAA,IAC7F,WAAW,gBAAgB,KAAK,UAAU,GAAG;AAC3C,YAAMA,IAAG,SAAS,YAAY,SAAS,QAAQ,MAAM,EAAE,CAAC;AAAA,IAC1D,OAAO;AACL,YAAMA,IAAG,SAAS,YAAY,QAAQ;AAAA,IACxC;AAAA,EACF;AAEA,QAAM,gBAAgB,KAAK,SAAS,KAAK,OAAO;AAEhD,QAAM,SAAS;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA,IACT,WAAW;AAAA,EACb;AAEA,UAAQ,IAAI,OAAO,IAAI,OAAO,EAAE,CAAC;AACjC,UAAQ,IAAI,OAAO,SAAS,wCAAiC;AAC7D,UAAQ,IAAI,OAAO,MAAM,+BAAwB,OAAO,EAAE;AAC1D,UAAQ,IAAI,OAAO,WAAW,IAAI,OAAO,EAAE,CAAC;AAC5C,UAAQ,IAAI,OAAO,MAAM,eAAe;AAExC,MAAI,YAAY,KAAK;AACnB,YAAQ;AAAA,MACN,OAAO;AAAA,MACP,QAAQ,cAAc,SAAS,GAAG,IAAI,IAAI,aAAa,MAAM,aAAa;AAAA,IAC5E;AAAA,EACF;AAEA,QAAM,iBAAiB,iBAAiB;AAExC,UAAQ,gBAAgB;AAAA,IACtB,KAAK;AAAA,IACL,KAAK;AACH,cAAQ,IAAI,OAAO,SAAS,KAAK,UAAU,UAAU;AACrD,cAAQ,IAAI,OAAO,SAAS,KAAK,UAAU,eAAe;AAC1D,cAAQ,IAAI,OAAO,SAAS,KAAK,UAAU,UAAU;AACrD;AAAA,IACF,KAAK;AACH,cAAQ,IAAI,OAAO,SAAS,KAAK,UAAU,UAAU;AACrD,cAAQ,IAAI,OAAO,SAAS,KAAK,UAAU,eAAe;AAC1D;AAAA,EACJ;AAEA,UAAQ,IAAI,OAAO,WAAW,OAAO,IAAI,OAAO,EAAE,IAAI,IAAI;AAC5D;AAEA,SAAS,iBAAiB,WAA+B;AACvD,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,UAAU,UAAU,MAAM,GAAG,EAAE,CAAC;AACtC,QAAM,aAAa,QAAQ,MAAM,GAAG;AACpC,SAAO;AAAA,IACL,MAAM,WAAW,CAAC;AAAA,IAClB,SAAS,WAAW,CAAC;AAAA,EACvB;AACF;AAEA,KAAK,EAAE,MAAM,CAAC,MAAM;AAClB,UAAQ,MAAM,CAAC;AACjB,CAAC;","names":["fs","path","fs"]}
1
+ {"version":3,"sources":["../src/bin/cli.ts","../src/config/chains.ts","../package.json","../src/exists.ts"],"sourcesContent":["import { fileURLToPath } from 'node:url';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport glob from 'fast-glob';\nimport yargsInteractive from 'yargs-interactive';\nimport { CHAINS } from '../config/chains';\nimport packageJson from '../../package.json';\nimport { exists } from '../exists';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst cwd = process.cwd();\n\nconst init = async () => {\n // Prepare chain options\n const chainChoices = CHAINS.map(({ title, description, value }) => ({\n name: `${title} - ${description}`,\n value\n }));\n\n // Step 1: Choose project name and chain\n const firstStep = await yargsInteractive()\n .usage('$0 [args]')\n .interactive({\n interactive: { default: true },\n projectName: {\n describe: 'Name your project',\n type: 'input'\n },\n chain: {\n describe: 'Pick your chain',\n type: 'list',\n choices: chainChoices.map((c) => c.value)\n },\n dubheVersion: {\n describe: 'The version of Dubhe packages to use, defaults to latest',\n type: 'input',\n default: packageJson.version\n }\n });\n\n const { projectName, chain, dubheVersion } = firstStep;\n if (!projectName) throw new Error('No project name provided.');\n\n // Get available templates based on the selected chain\n const selectedChain = CHAINS.find((c) => c.value === chain);\n if (!selectedChain) {\n throw new Error('Invalid chain selection');\n }\n\n // Prepare platform options\n const platformChoices = selectedChain.supportedTemplates.map(({ title, description, value }) => ({\n name: `${title} - ${description}`,\n value\n }));\n\n // Step 2: Choose platform\n const secondStep = await yargsInteractive()\n .usage('$0 [args]')\n .interactive({\n interactive: { default: true },\n platform: {\n describe: 'Pick your platform',\n type: 'list',\n choices: platformChoices.map((c) => c.value)\n }\n });\n\n const { platform } = secondStep;\n\n const selectedTemplate = selectedChain.supportedTemplates.find((t) => t.value === platform);\n if (!selectedTemplate) {\n throw new Error('Invalid platform selection');\n }\n\n const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent);\n const pkgManager = pkgInfo ? pkgInfo.name : 'npm';\n\n const sourceDir = path.join(\n __dirname,\n '..',\n 'templates',\n selectedTemplate.path.replace('{chain}', chain)\n );\n\n if (!(await exists(sourceDir))) {\n throw new Error(`Template directory not found: ${sourceDir}`);\n }\n\n const destDir = path.join(process.cwd(), projectName);\n if (await exists(destDir)) {\n throw new Error(`Target directory \"${destDir}\" already exists.`);\n }\n\n const files = await glob('**/*', { cwd: sourceDir, dot: true });\n\n for (const filename of files) {\n const sourceFile = path.join(sourceDir, filename);\n const destFile = path.join(destDir, filename);\n\n await fs.mkdir(path.dirname(destFile), { recursive: true });\n\n if (/package\\.json$/.test(sourceFile)) {\n const source = await fs.readFile(sourceFile, 'utf-8');\n await fs.writeFile(destFile, source.replaceAll(/{{dubhe-version}}/g, dubheVersion), 'utf-8');\n } else if (/\\.gitignore_$/.test(sourceFile)) {\n await fs.copyFile(sourceFile, destFile.replace(/_$/, ''));\n } else {\n await fs.copyFile(sourceFile, destFile);\n }\n }\n\n const cdProjectName = path.relative(cwd, destDir);\n\n const styles = {\n success: '\\x1b[32m%s\\x1b[0m',\n info: '\\x1b[36m%s\\x1b[0m',\n command: '\\x1b[33m%s\\x1b[0m',\n separator: '\\x1b[90m%s\\x1b[0m'\n };\n\n console.log('\\n' + '='.repeat(60));\n console.log(styles.success, '🎉 Project creation successful!');\n console.log(styles.info, `📁 Project location: ${destDir}`);\n console.log(styles.separator, '-'.repeat(60));\n console.log(styles.info, 'Next steps:\\n');\n\n if (destDir !== cwd) {\n console.log(\n styles.command,\n ` cd ${cdProjectName.includes(' ') ? `\"${cdProjectName}\"` : cdProjectName}`\n );\n }\n\n const actualTemplate = selectedTemplate.value;\n\n switch (actualTemplate) {\n case '101':\n case 'web':\n console.log(styles.command, ` ${pkgManager} install`);\n console.log(styles.command, ` ${pkgManager} dubhe doctor`);\n console.log(styles.command, ` ${pkgManager} run dev`);\n break;\n case 'contract':\n console.log(styles.command, ` ${pkgManager} install`);\n console.log(styles.command, ` ${pkgManager} dubhe doctor`);\n break;\n }\n\n console.log(styles.separator, '\\n' + '='.repeat(60) + '\\n');\n};\n\nfunction pkgFromUserAgent(userAgent: string | undefined) {\n if (!userAgent) return undefined;\n const pkgSpec = userAgent.split(' ')[0];\n const pkgSpecArr = pkgSpec.split('/');\n return {\n name: pkgSpecArr[0],\n version: pkgSpecArr[1]\n };\n}\n\ninit().catch((e) => {\n console.error(e);\n});\n","interface Template {\n title: string;\n description: string;\n value: string;\n path: string;\n}\n\ninterface Chain {\n title: string;\n description: string;\n value: string;\n supportedTemplates: Template[];\n}\n\nconst TEMPLATES = {\n QUICK_START: {\n title: '101',\n description: 'Quick start',\n value: '101',\n path: '101/{chain}-template'\n },\n WEB: {\n title: 'Web',\n description: 'Web template',\n value: 'web',\n path: 'nextjs/{chain}-template'\n },\n CONTRACT: {\n title: 'Contract',\n description: 'Contract template',\n value: 'contract',\n path: 'contract/{chain}-template'\n }\n} as const;\n\nexport const CHAINS: Chain[] = [\n {\n title: 'sui',\n description: 'Sui',\n value: 'sui',\n supportedTemplates: [TEMPLATES.QUICK_START, TEMPLATES.WEB, TEMPLATES.CONTRACT]\n },\n {\n title: 'aptos',\n description: 'Aptos',\n value: 'aptos',\n supportedTemplates: [TEMPLATES.QUICK_START, TEMPLATES.WEB, TEMPLATES.CONTRACT]\n },\n {\n title: 'rooch',\n description: 'Rooch',\n value: 'rooch',\n supportedTemplates: [TEMPLATES.QUICK_START]\n },\n {\n title: 'initia',\n description: 'Initia',\n value: 'initia',\n supportedTemplates: [TEMPLATES.QUICK_START]\n },\n {\n title: 'movement',\n description: 'Movement',\n value: 'movement',\n supportedTemplates: [TEMPLATES.QUICK_START]\n }\n];\n","{\n \"name\": \"create-dubhe\",\n \"version\": \"1.2.0-pre.125\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/0xobelisk/dubhe.git\"\n },\n \"license\": \"MIT\",\n \"author\": \"team@obelisk.build\",\n \"type\": \"module\",\n \"bin\": \"bin/cli.js\",\n \"files\": [\n \"bin\",\n \"dist\",\n \"templates\"\n ],\n \"scripts\": {\n \"build\": \"pnpm run type-check && pnpm run build:js\",\n \"build:js\": \"tsup && pnpm run copy-templates\",\n \"clean\": \"pnpm run clean:js\",\n \"clean:js\": \"shx rm -rf dist\",\n \"cocos-js-build\": \"cd cocos-lib-builder && npm i && browserify aptos.js -p esmify > ../templates/cocos/aptos-template/assets/lib/dubhe.js && browserify sui.js -p esmify > ../templates/cocos/sui-template/assets/lib/dubhe.js\",\n \"copy-templates\": \"tsx ./scripts/copy-templates.ts\",\n \"dev\": \"tsup --watch\",\n \"format\": \"prettier --write .\",\n \"format:check\": \"prettier --check .\",\n \"lint\": \"eslint . --ext .ts\",\n \"type-check\": \"tsc --noEmit\",\n \"validate\": \"pnpm format:check && pnpm lint && pnpm type-check\"\n },\n \"dependencies\": {\n \"browser-resolve\": \"^2.0.0\",\n \"browser-sync\": \"^2.29.3\",\n \"fast-glob\": \"^3.3.3\",\n \"yargs-interactive\": \"^3.0.1\"\n },\n \"devDependencies\": {\n \"@types/cross-spawn\": \"^6.0.2\",\n \"@types/minimist\": \"^1.2.2\",\n \"@types/prompts\": \"^2.4.3\",\n \"@types/yargs-interactive\": \"^2.1.6\",\n \"browserify\": \"^17.0.0\",\n \"cross-spawn\": \"^7.0.3\",\n \"eslint\": \"^9.0.0\",\n \"eslint-config-prettier\": \"^9.1.0\",\n \"esmify\": \"^2.1.1\",\n \"kolorist\": \"^1.7.0\",\n \"prettier\": \"3.3.3\",\n \"prompts\": \"^2.4.2\",\n \"typescript\": \"^5.8.3\"\n },\n \"engines\": {\n \"node\": \">=22.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n }\n}\n","import fs from 'node:fs/promises';\n\nexport async function exists(path: string) {\n try {\n await fs.access(path);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"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","names":["fs","path","fs"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-dubhe",
3
- "version": "1.2.0-pre.124",
3
+ "version": "1.2.0-pre.125",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/0xobelisk/dubhe.git"
@@ -39,9 +39,3 @@ source = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-f
39
39
  use_environment = "testnet"
40
40
  manifest_digest = "7AFB66695545775FBFBB2D3078ADFD084244D5002392E837FDE21D9EA1C6D01C"
41
41
  deps = { MoveStdlib = "MoveStdlib" }
42
-
43
- [env.mainnet]
44
- chain-id = "35834a8a"
45
- original-published-id = "0x6cb54e8bbcf59cc976584330689073500af755b12d85bf4f56d7eea9f232b8aa"
46
- latest-published-id = "0x6cb54e8bbcf59cc976584330689073500af755b12d85bf4f56d7eea9f232b8aa"
47
- published-version = "1"
@@ -278,9 +278,10 @@ public fun delete_field<DappKey: copy + drop>(
278
278
  //
279
279
  // Reactive writes allow one participant to modify another participant's UserStorage
280
280
  // within a shared scene context. Four-layer security:
281
- // 1. ctx.sender() must be from.canonical_owner (only owner can initiate)
282
- // 2. from must be a registered scene participant
283
- // 3. target must be a registered scene participant
281
+ // 1. ctx.sender() must be authorized to write `from` (canonical owner or its
282
+ // active session key sessions are delegated proxies for in-scene actions)
283
+ // 2. from's canonical owner must be a registered scene participant
284
+ // 3. target's canonical owner must be a registered scene participant
284
285
  // 4. scene must be active (not expired)
285
286
  //
286
287
  // Write fees are charged to the initiator (`from`) under the initiator-pays model.
@@ -304,8 +305,8 @@ public fun set_record_reactive<DappKey: copy + drop, PermType>(
304
305
  let scene_id = dapp_service::scene_permit_id(permit);
305
306
  let meta = dapp_service::scene_permit_meta(permit);
306
307
 
307
- // 1. Sender must be the initiator's canonical owner.
308
- error::not_canonical_owner(ctx.sender() == dapp_service::canonical_owner(from));
308
+ // 1. Sender must be authorized to write `from` (canonical owner or active session key).
309
+ error::no_permission(dapp_service::is_write_authorized(from, ctx.sender(), ctx.epoch_timestamp_ms()));
309
310
  // 2. Initiator must be a scene participant (O(1) DF lookup).
310
311
  error::not_scene_participant(dapp_service::is_scene_participant(scene_id, dapp_service::canonical_owner(from)));
311
312
  // 3. Target must be a scene participant (O(1) DF lookup).
@@ -343,7 +344,7 @@ public fun set_field_reactive<DappKey: copy + drop, PermType>(
343
344
  let scene_id = dapp_service::scene_permit_id(permit);
344
345
  let meta = dapp_service::scene_permit_meta(permit);
345
346
 
346
- error::not_canonical_owner(ctx.sender() == dapp_service::canonical_owner(from));
347
+ error::no_permission(dapp_service::is_write_authorized(from, ctx.sender(), ctx.epoch_timestamp_ms()));
347
348
  error::not_scene_participant(dapp_service::is_scene_participant(scene_id, dapp_service::canonical_owner(from)));
348
349
  error::not_scene_participant(dapp_service::is_scene_participant(scene_id, dapp_service::canonical_owner(target)));
349
350
  error::scene_expired(dapp_service::is_scene_active(meta, ctx.epoch_timestamp_ms()));
@@ -504,19 +505,27 @@ public fun remove_object_field<DappKey: copy + drop, ObjType, T: store + copy +
504
505
 
505
506
  /// Remove and return a native-typed field from a permit-bound SceneStorage Bag.
506
507
  /// Emits a Dubhe_Scene_DeleteField event so off-chain indexers stay in sync.
508
+ /// The caller identifies as `user_storage`'s canonical owner; the transaction may be
509
+ /// signed by the canonical owner or its active session key (is_write_authorized).
507
510
  public fun remove_scene_field<DappKey: copy + drop, PermType, SceneType, T: store + copy + drop>(
508
- _auth: DappKey,
509
- permit: &ScenePermit<PermType>,
510
- storage: &mut SceneStorage<SceneType>,
511
- field_name: vector<u8>,
512
- ctx: &TxContext,
511
+ _auth: DappKey,
512
+ permit: &ScenePermit<PermType>,
513
+ storage: &mut SceneStorage<SceneType>,
514
+ user_storage: &UserStorage,
515
+ field_name: vector<u8>,
516
+ ctx: &TxContext,
513
517
  ): T {
514
518
  let dapp_key_str = type_info::get_type_name_string<DappKey>();
515
519
  error::dapp_key_mismatch(dapp_service::scene_storage_dapp_key(storage) == dapp_key_str);
516
520
  error::dapp_key_mismatch(dapp_service::scene_permit_dapp_key(permit) == dapp_key_str);
521
+ error::dapp_key_mismatch(dapp_service::user_storage_dapp_key(user_storage) == dapp_key_str);
517
522
  assert_scene_storage_bound_to_permit(permit, storage);
518
- error::scene_expired(dapp_service::is_scene_active(dapp_service::scene_permit_meta(permit), ctx.epoch_timestamp_ms()));
519
- error::not_scene_participant(dapp_service::is_participant_in_scene_permit(permit, ctx.sender()));
523
+ let now_ms = ctx.epoch_timestamp_ms();
524
+ error::scene_expired(dapp_service::is_scene_active(dapp_service::scene_permit_meta(permit), now_ms));
525
+ error::no_permission(dapp_service::is_write_authorized(user_storage, ctx.sender(), now_ms));
526
+ error::not_scene_participant(dapp_service::is_participant_in_scene_permit(
527
+ permit, dapp_service::canonical_owner(user_storage)
528
+ ));
520
529
 
521
530
  let scene_type = *dapp_service::scene_storage_type(storage);
522
531
  let scene_id = sui::object::uid_to_address(dapp_service::scene_storage_id(storage));
@@ -729,20 +738,28 @@ public fun share_scene_storage<DappKey: copy + drop, SceneType>(
729
738
  }
730
739
 
731
740
  /// Write a native-typed field into a permit-bound SceneStorage Bag and emit an event.
741
+ /// The caller identifies as `user_storage`'s canonical owner; the transaction may be
742
+ /// signed by the canonical owner or its active session key (is_write_authorized).
732
743
  public fun set_scene_field<DappKey: copy + drop, PermType, SceneType, T: store + copy + drop>(
733
- _auth: DappKey,
734
- permit: &ScenePermit<PermType>,
735
- storage: &mut SceneStorage<SceneType>,
736
- field_name: vector<u8>,
737
- value: T,
738
- ctx: &TxContext,
744
+ _auth: DappKey,
745
+ permit: &ScenePermit<PermType>,
746
+ storage: &mut SceneStorage<SceneType>,
747
+ user_storage: &UserStorage,
748
+ field_name: vector<u8>,
749
+ value: T,
750
+ ctx: &TxContext,
739
751
  ) {
740
752
  let dapp_key_str = type_info::get_type_name_string<DappKey>();
741
753
  error::dapp_key_mismatch(dapp_service::scene_storage_dapp_key(storage) == dapp_key_str);
742
754
  error::dapp_key_mismatch(dapp_service::scene_permit_dapp_key(permit) == dapp_key_str);
755
+ error::dapp_key_mismatch(dapp_service::user_storage_dapp_key(user_storage) == dapp_key_str);
743
756
  assert_scene_storage_bound_to_permit(permit, storage);
744
- error::scene_expired(dapp_service::is_scene_active(dapp_service::scene_permit_meta(permit), ctx.epoch_timestamp_ms()));
745
- error::not_scene_participant(dapp_service::is_participant_in_scene_permit(permit, ctx.sender()));
757
+ let now_ms = ctx.epoch_timestamp_ms();
758
+ error::scene_expired(dapp_service::is_scene_active(dapp_service::scene_permit_meta(permit), now_ms));
759
+ error::no_permission(dapp_service::is_write_authorized(user_storage, ctx.sender(), now_ms));
760
+ error::not_scene_participant(dapp_service::is_participant_in_scene_permit(
761
+ permit, dapp_service::canonical_owner(user_storage)
762
+ ));
746
763
 
747
764
  let event_bytes = sui::bcs::to_bytes(&value);
748
765
  dapp_service::set_scene_field(storage, field_name, value);
@@ -829,19 +846,25 @@ public fun destroy_scene_permit<DappKey: copy + drop, PermType>(
829
846
  }
830
847
 
831
848
  /// Helper: accept a scene invitation for a ScenePermit-backed scene.
832
- /// Moves ctx.sender() from the invitees list to confirmed participants.
849
+ /// Moves the caller's canonical owner from the invitees list to confirmed
850
+ /// participants. The transaction may be signed by the canonical owner or its
851
+ /// active session key (is_write_authorized) — the registered identity is
852
+ /// always the canonical owner address.
833
853
  /// Guards: scene must be active AND the invitation window must not have expired.
834
854
  public fun accept_scene_permit_invitation<DappKey: copy + drop, PermType>(
835
- _auth: DappKey,
836
- permit: &mut ScenePermit<PermType>,
837
- ctx: &TxContext,
855
+ _auth: DappKey,
856
+ permit: &mut ScenePermit<PermType>,
857
+ user_storage: &UserStorage,
858
+ ctx: &TxContext,
838
859
  ) {
839
860
  let dapp_key_str = type_info::get_type_name_string<DappKey>();
840
861
  error::dapp_key_mismatch(dapp_service::scene_permit_dapp_key(permit) == dapp_key_str);
862
+ error::dapp_key_mismatch(dapp_service::user_storage_dapp_key(user_storage) == dapp_key_str);
841
863
 
842
864
  // Check both permit activity and invitation window before mutating.
843
865
  // The immutable borrow of `meta` is released at the end of this block.
844
866
  let now_ms = ctx.epoch_timestamp_ms();
867
+ error::no_permission(dapp_service::is_write_authorized(user_storage, ctx.sender(), now_ms));
845
868
  {
846
869
  let meta = dapp_service::scene_permit_meta(permit);
847
870
  error::scene_expired(dapp_service::is_scene_active(meta, now_ms));
@@ -851,54 +874,71 @@ public fun accept_scene_permit_invitation<DappKey: copy + drop, PermType>(
851
874
  };
852
875
  };
853
876
 
854
- dapp_service::accept_invitation_in_scene_permit(permit, ctx.sender());
877
+ let owner = dapp_service::canonical_owner(user_storage);
878
+ dapp_service::accept_invitation_in_scene_permit(permit, owner);
855
879
  dubhe_events::emit_scene_permit_accept(
856
880
  dapp_key_str,
857
881
  *dapp_service::scene_permit_type(permit),
858
882
  sui::object::uid_to_address(dapp_service::scene_permit_id(permit)),
859
- ctx.sender(),
883
+ owner,
860
884
  );
861
885
  }
862
886
 
863
- /// Helper: add the caller as a confirmed participant in a ScenePermit.
887
+ /// Helper: add the caller's canonical owner as a confirmed participant in a
888
+ /// ScenePermit. The transaction may be signed by the canonical owner or its
889
+ /// active session key (is_write_authorized) — the registered identity is
890
+ /// always the canonical owner address.
864
891
  /// The permit must still be active — joining an expired permit is meaningless and
865
892
  /// wastes gas on a DF write that can never be used for reactive writes.
866
893
  public fun join_scene_permit<DappKey: copy + drop, PermType>(
867
- _auth: DappKey,
868
- permit: &mut ScenePermit<PermType>,
869
- ctx: &TxContext,
894
+ _auth: DappKey,
895
+ permit: &mut ScenePermit<PermType>,
896
+ user_storage: &UserStorage,
897
+ ctx: &TxContext,
870
898
  ) {
871
899
  let dapp_key_str = type_info::get_type_name_string<DappKey>();
872
900
  error::dapp_key_mismatch(dapp_service::scene_permit_dapp_key(permit) == dapp_key_str);
873
- error::scene_expired(dapp_service::is_scene_active(dapp_service::scene_permit_meta(permit), ctx.epoch_timestamp_ms()));
874
- let was_participant = dapp_service::is_participant_in_scene_permit(permit, ctx.sender());
875
- dapp_service::add_participant_in_scene_permit(permit, ctx.sender());
901
+ error::dapp_key_mismatch(dapp_service::user_storage_dapp_key(user_storage) == dapp_key_str);
902
+ let now_ms = ctx.epoch_timestamp_ms();
903
+ error::scene_expired(dapp_service::is_scene_active(dapp_service::scene_permit_meta(permit), now_ms));
904
+ error::no_permission(dapp_service::is_write_authorized(user_storage, ctx.sender(), now_ms));
905
+ let owner = dapp_service::canonical_owner(user_storage);
906
+ let was_participant = dapp_service::is_participant_in_scene_permit(permit, owner);
907
+ dapp_service::add_participant_in_scene_permit(permit, owner);
876
908
  if (!was_participant) {
877
909
  dubhe_events::emit_scene_permit_join(
878
910
  dapp_key_str,
879
911
  *dapp_service::scene_permit_type(permit),
880
912
  sui::object::uid_to_address(dapp_service::scene_permit_id(permit)),
881
- ctx.sender(),
913
+ owner,
882
914
  );
883
915
  };
884
916
  }
885
917
 
886
- /// Helper: remove the caller from participants in a SceneStorage scene.
918
+ /// Helper: remove the caller's canonical owner from participants in a
919
+ /// SceneStorage scene. The transaction may be signed by the canonical owner or
920
+ /// its active session key (is_write_authorized).
887
921
  public fun leave_scene_permit<DappKey: copy + drop, PermType>(
888
- _auth: DappKey,
889
- permit: &mut ScenePermit<PermType>,
890
- ctx: &TxContext,
922
+ _auth: DappKey,
923
+ permit: &mut ScenePermit<PermType>,
924
+ user_storage: &UserStorage,
925
+ ctx: &TxContext,
891
926
  ) {
892
927
  let dapp_key_str = type_info::get_type_name_string<DappKey>();
893
928
  error::dapp_key_mismatch(dapp_service::scene_permit_dapp_key(permit) == dapp_key_str);
894
- let was_participant = dapp_service::is_participant_in_scene_permit(permit, ctx.sender());
895
- dapp_service::remove_participant_in_scene_permit(permit, ctx.sender());
929
+ error::dapp_key_mismatch(dapp_service::user_storage_dapp_key(user_storage) == dapp_key_str);
930
+ error::no_permission(dapp_service::is_write_authorized(
931
+ user_storage, ctx.sender(), ctx.epoch_timestamp_ms()
932
+ ));
933
+ let owner = dapp_service::canonical_owner(user_storage);
934
+ let was_participant = dapp_service::is_participant_in_scene_permit(permit, owner);
935
+ dapp_service::remove_participant_in_scene_permit(permit, owner);
896
936
  if (was_participant) {
897
937
  dubhe_events::emit_scene_permit_leave(
898
938
  dapp_key_str,
899
939
  *dapp_service::scene_permit_type(permit),
900
940
  sui::object::uid_to_address(dapp_service::scene_permit_id(permit)),
901
- ctx.sender(),
941
+ owner,
902
942
  );
903
943
  };
904
944
  }
@@ -0,0 +1,269 @@
1
+ /// Unit tests — Session keys acting in scenes
2
+ ///
3
+ /// Sessions are delegated proxies: a transaction signed by an active session
4
+ /// key must be able to perform in-scene actions on behalf of its canonical
5
+ /// owner, while the on-chain identity (permit participants, scene events)
6
+ /// always resolves to the canonical owner address.
7
+ ///
8
+ /// Covers:
9
+ /// reactive writes:
10
+ /// session key may initiate set_record_reactive / set_field_reactive
11
+ /// expired session is rejected
12
+ /// stranger (neither owner nor session) is rejected
13
+ /// permit participation:
14
+ /// join_scene_permit registers the canonical owner, not the session address
15
+ /// accept_scene_permit_invitation moves the canonical owner to participants
16
+ /// leave_scene_permit removes the canonical owner
17
+ /// unauthorized sender cannot join on someone else's behalf
18
+ /// scene field writes:
19
+ /// session key may set_scene_field / remove_scene_field
20
+ /// non-participant canonical owner is rejected
21
+ #[test_only]
22
+ module dubhe::scene_session_test;
23
+
24
+ use dubhe::dapp_service::{Self, UserStorage, ScenePermit};
25
+ use dubhe::dapp_system;
26
+ use sui::bcs::to_bytes;
27
+ use sui::tx_context;
28
+
29
+ public struct SceneSessionKey has copy, drop {}
30
+
31
+ const OWNER: address = @0xA1;
32
+ const SESSION: address = @0xA2;
33
+ const PEER: address = @0xB1;
34
+ const OTHER: address = @0xC1;
35
+
36
+ const NOW: u64 = 1_000;
37
+ const SESSION_EXPIRES: u64 = 10_000;
38
+
39
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
40
+
41
+ fun make_us(owner: address, ctx: &mut TxContext): UserStorage {
42
+ dapp_service::create_user_storage_for_testing<SceneSessionKey>(owner, ctx)
43
+ }
44
+
45
+ /// UserStorage owned by `owner` with an active session key bound to SESSION.
46
+ fun make_us_with_session(owner: address, ctx: &mut TxContext): UserStorage {
47
+ let mut us = make_us(owner, ctx);
48
+ dapp_service::set_session_key_for_testing(&mut us, SESSION, SESSION_EXPIRES);
49
+ us
50
+ }
51
+
52
+ fun make_permit(
53
+ participants: vector<address>,
54
+ ctx: &mut TxContext,
55
+ ): ScenePermit<SceneSessionKey> {
56
+ dapp_service::create_scene_permit_for_testing<SceneSessionKey, SceneSessionKey>(
57
+ participants, std::option::none(), std::option::none(), ctx,
58
+ )
59
+ }
60
+
61
+ /// Transaction context whose sender is `sender` at epoch time NOW.
62
+ fun ctx_for(sender: address): TxContext {
63
+ tx_context::new_from_hint(sender, 0, 0, NOW, 0)
64
+ }
65
+
66
+ fun key_for(name: vector<u8>): vector<vector<u8>> { vector[name] }
67
+
68
+ // ═══════════════════════════════════════════════════════════════════════════════
69
+ // Reactive writes initiated by a session key
70
+ // ═══════════════════════════════════════════════════════════════════════════════
71
+
72
+ #[test]
73
+ fun test_session_can_initiate_reactive_write() {
74
+ let mut ctx = ctx_for(SESSION);
75
+
76
+ let permit = make_permit(vector[OWNER, PEER], &mut ctx);
77
+ let mut from = make_us_with_session(OWNER, &mut ctx);
78
+ let mut target = make_us(PEER, &mut ctx);
79
+
80
+ dapp_system::set_record_reactive<SceneSessionKey, SceneSessionKey>(
81
+ SceneSessionKey {}, &permit, &mut from, &mut target,
82
+ key_for(b"hp"), vector[b"v"], vector[to_bytes(&100u64)], &mut ctx,
83
+ );
84
+ dapp_system::set_field_reactive<SceneSessionKey, SceneSessionKey>(
85
+ SceneSessionKey {}, &permit, &mut from, &mut target,
86
+ key_for(b"hp"), b"v", to_bytes(&80u64), &mut ctx,
87
+ );
88
+
89
+ assert!(dapp_service::has_user_record<SceneSessionKey>(&target, key_for(b"hp")), 0);
90
+ // Fees are charged to the initiator's storage, not the target's.
91
+ assert!(dapp_service::write_count(&from) == 2, 1);
92
+ assert!(dapp_service::write_count(&target) == 0, 2);
93
+
94
+ dapp_service::destroy_user_storage(from);
95
+ dapp_service::destroy_user_storage(target);
96
+ dapp_service::destroy_scene_permit_for_testing(permit);
97
+ }
98
+
99
+ #[test]
100
+ #[expected_failure]
101
+ fun test_expired_session_reactive_aborts() {
102
+ // Session expires exactly at SESSION_EXPIRES — a transaction at that time is rejected.
103
+ let mut ctx = tx_context::new_from_hint(SESSION, 0, 0, SESSION_EXPIRES, 0);
104
+
105
+ let permit = make_permit(vector[OWNER, PEER], &mut ctx);
106
+ let mut from = make_us_with_session(OWNER, &mut ctx);
107
+ let mut target = make_us(PEER, &mut ctx);
108
+
109
+ dapp_system::set_record_reactive<SceneSessionKey, SceneSessionKey>(
110
+ SceneSessionKey {}, &permit, &mut from, &mut target,
111
+ key_for(b"hp"), vector[b"v"], vector[to_bytes(&100u64)], &mut ctx,
112
+ );
113
+
114
+ dapp_service::destroy_user_storage(from);
115
+ dapp_service::destroy_user_storage(target);
116
+ dapp_service::destroy_scene_permit_for_testing(permit);
117
+ }
118
+
119
+ #[test]
120
+ #[expected_failure]
121
+ fun test_stranger_reactive_aborts() {
122
+ let mut ctx = ctx_for(OTHER);
123
+
124
+ let permit = make_permit(vector[OWNER, PEER], &mut ctx);
125
+ let mut from = make_us_with_session(OWNER, &mut ctx);
126
+ let mut target = make_us(PEER, &mut ctx);
127
+
128
+ dapp_system::set_record_reactive<SceneSessionKey, SceneSessionKey>(
129
+ SceneSessionKey {}, &permit, &mut from, &mut target,
130
+ key_for(b"hp"), vector[b"v"], vector[to_bytes(&100u64)], &mut ctx,
131
+ );
132
+
133
+ dapp_service::destroy_user_storage(from);
134
+ dapp_service::destroy_user_storage(target);
135
+ dapp_service::destroy_scene_permit_for_testing(permit);
136
+ }
137
+
138
+ // ═══════════════════════════════════════════════════════════════════════════════
139
+ // Permit participation through a session key registers the canonical owner
140
+ // ═══════════════════════════════════════════════════════════════════════════════
141
+
142
+ #[test]
143
+ fun test_session_join_registers_canonical_owner() {
144
+ let mut ctx = ctx_for(SESSION);
145
+
146
+ let mut permit = make_permit(vector[], &mut ctx);
147
+ let us = make_us_with_session(OWNER, &mut ctx);
148
+
149
+ dapp_system::join_scene_permit<SceneSessionKey, SceneSessionKey>(
150
+ SceneSessionKey {}, &mut permit, &us, &ctx,
151
+ );
152
+
153
+ assert!(dapp_service::is_participant_in_scene_permit(&permit, OWNER), 0);
154
+ assert!(!dapp_service::is_participant_in_scene_permit(&permit, SESSION), 1);
155
+
156
+ dapp_service::destroy_user_storage(us);
157
+ dapp_service::destroy_scene_permit_for_testing(permit);
158
+ }
159
+
160
+ #[test]
161
+ fun test_session_accept_invitation_registers_canonical_owner() {
162
+ let mut ctx = ctx_for(SESSION);
163
+
164
+ let mut permit = dapp_service::create_scene_permit_with_invitations_for_testing<
165
+ SceneSessionKey, SceneSessionKey
166
+ >(vector[OWNER], std::option::none(), std::option::none(), &mut ctx);
167
+ let us = make_us_with_session(OWNER, &mut ctx);
168
+
169
+ dapp_system::accept_scene_permit_invitation<SceneSessionKey, SceneSessionKey>(
170
+ SceneSessionKey {}, &mut permit, &us, &ctx,
171
+ );
172
+
173
+ assert!(dapp_service::is_participant_in_scene_permit(&permit, OWNER), 0);
174
+ assert!(!dapp_service::is_participant_in_scene_permit(&permit, SESSION), 1);
175
+
176
+ dapp_service::destroy_user_storage(us);
177
+ dapp_service::destroy_scene_permit_for_testing(permit);
178
+ }
179
+
180
+ #[test]
181
+ fun test_session_leave_removes_canonical_owner() {
182
+ let mut ctx = ctx_for(SESSION);
183
+
184
+ let mut permit = make_permit(vector[OWNER], &mut ctx);
185
+ let us = make_us_with_session(OWNER, &mut ctx);
186
+
187
+ dapp_system::leave_scene_permit<SceneSessionKey, SceneSessionKey>(
188
+ SceneSessionKey {}, &mut permit, &us, &ctx,
189
+ );
190
+
191
+ assert!(!dapp_service::is_participant_in_scene_permit(&permit, OWNER), 0);
192
+
193
+ dapp_service::destroy_user_storage(us);
194
+ dapp_service::destroy_scene_permit_for_testing(permit);
195
+ }
196
+
197
+ #[test]
198
+ #[expected_failure]
199
+ fun test_join_unauthorized_sender_aborts() {
200
+ // OTHER is neither the canonical owner nor the session key of `us`.
201
+ let mut ctx = ctx_for(OTHER);
202
+
203
+ let mut permit = make_permit(vector[], &mut ctx);
204
+ let us = make_us_with_session(OWNER, &mut ctx);
205
+
206
+ dapp_system::join_scene_permit<SceneSessionKey, SceneSessionKey>(
207
+ SceneSessionKey {}, &mut permit, &us, &ctx,
208
+ );
209
+
210
+ dapp_service::destroy_user_storage(us);
211
+ dapp_service::destroy_scene_permit_for_testing(permit);
212
+ }
213
+
214
+ // ═══════════════════════════════════════════════════════════════════════════════
215
+ // Scene field writes through a session key
216
+ // ═══════════════════════════════════════════════════════════════════════════════
217
+
218
+ #[test]
219
+ fun test_session_set_and_remove_scene_field() {
220
+ let mut ctx = ctx_for(SESSION);
221
+
222
+ let ds = dapp_service::create_dapp_storage_for_testing<SceneSessionKey>(&mut ctx);
223
+ let permit = make_permit(vector[OWNER], &mut ctx);
224
+ let us = make_us_with_session(OWNER, &mut ctx);
225
+
226
+ let mut scene = dapp_system::new_typed_scene_with_permit<
227
+ SceneSessionKey, SceneSessionKey, SceneSessionKey
228
+ >(SceneSessionKey {}, &ds, &permit, b"battle", &mut ctx);
229
+
230
+ dapp_system::set_scene_field<SceneSessionKey, SceneSessionKey, SceneSessionKey, u64>(
231
+ SceneSessionKey {}, &permit, &mut scene, &us, b"hp", 100u64, &ctx,
232
+ );
233
+ assert!(dapp_system::get_scene_field<SceneSessionKey, u64>(&scene, b"hp") == 100, 0);
234
+
235
+ let removed: u64 = dapp_system::remove_scene_field<
236
+ SceneSessionKey, SceneSessionKey, SceneSessionKey, u64
237
+ >(SceneSessionKey {}, &permit, &mut scene, &us, b"hp", &ctx);
238
+ assert!(removed == 100, 1);
239
+
240
+ dapp_system::destroy_typed_scene<SceneSessionKey, SceneSessionKey>(SceneSessionKey {}, scene);
241
+ dapp_service::destroy_user_storage(us);
242
+ dapp_service::destroy_scene_permit_for_testing(permit);
243
+ dapp_system::destroy_dapp_storage(ds);
244
+ }
245
+
246
+ #[test]
247
+ #[expected_failure]
248
+ fun test_scene_field_non_participant_aborts() {
249
+ // `us` belongs to OTHER who never joined the permit — write must abort
250
+ // even though OTHER signed the transaction themselves.
251
+ let mut ctx = ctx_for(OTHER);
252
+
253
+ let ds = dapp_service::create_dapp_storage_for_testing<SceneSessionKey>(&mut ctx);
254
+ let permit = make_permit(vector[OWNER], &mut ctx);
255
+ let us = make_us(OTHER, &mut ctx);
256
+
257
+ let mut scene = dapp_system::new_typed_scene_with_permit<
258
+ SceneSessionKey, SceneSessionKey, SceneSessionKey
259
+ >(SceneSessionKey {}, &ds, &permit, b"battle", &mut ctx);
260
+
261
+ dapp_system::set_scene_field<SceneSessionKey, SceneSessionKey, SceneSessionKey, u64>(
262
+ SceneSessionKey {}, &permit, &mut scene, &us, b"hp", 100u64, &ctx,
263
+ );
264
+
265
+ dapp_system::destroy_typed_scene<SceneSessionKey, SceneSessionKey>(SceneSessionKey {}, scene);
266
+ dapp_service::destroy_user_storage(us);
267
+ dapp_service::destroy_scene_permit_for_testing(permit);
268
+ dapp_system::destroy_dapp_storage(ds);
269
+ }