create-dubhe 1.2.0-pre.98 → 2.0.0

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 (361) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +16 -16
  4. package/templates/101/sui-template/docker-compose.testnet.yml +73 -0
  5. package/templates/101/sui-template/packages/client/src/app/page.tsx +545 -41
  6. package/templates/101/sui-template/packages/client/src/app/providers.tsx +12 -4
  7. package/templates/101/sui-template/packages/contracts/Pub.localnet.toml +4 -4
  8. package/templates/101/sui-template/packages/contracts/counter.lock.json +20 -0
  9. package/templates/101/sui-template/packages/contracts/deployment.ts +8 -3
  10. package/templates/101/sui-template/packages/contracts/dubhe.config.json +59 -13
  11. package/templates/101/sui-template/packages/contracts/dubhe.config.ts +1 -4
  12. package/templates/101/sui-template/packages/contracts/metadata.json +1015 -68
  13. package/templates/101/sui-template/packages/contracts/package.json +7 -4
  14. package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/dapp_key.move +45 -31
  15. package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/error.move +8 -0
  16. package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/genesis.move +30 -13
  17. package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/init_test.move +19 -14
  18. package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/resources/counter2.move +198 -0
  19. package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/resources/counter2withkey.move +90 -0
  20. package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/resources/value.move +30 -20
  21. package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/user_storage_init.move +26 -0
  22. package/templates/101/sui-template/packages/contracts/src/counter/sources/scripts/deploy_hook.move +6 -11
  23. package/templates/101/sui-template/packages/contracts/src/counter/sources/systems/counter.move +23 -11
  24. package/templates/101/sui-template/packages/contracts/src/counter/sources/tests/counter.move +15 -14
  25. package/templates/101/sui-template/packages/contracts/src/dubhe/Move.lock +33 -30
  26. package/templates/101/sui-template/packages/contracts/src/dubhe/Move.toml +8 -3
  27. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/dapp_key.move +45 -31
  28. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/error.move +170 -0
  29. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/genesis.move +16 -13
  30. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/init_test.move +8 -14
  31. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/core/account_key.move +1 -3
  32. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/core/dapp_service.move +1776 -137
  33. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/core/data_key.move +1 -20
  34. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/core/events.move +864 -25
  35. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/scripts/deploy_hook.move +15 -16
  36. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/scripts/migrate.move +0 -3
  37. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/systems/address_system.move +25 -17
  38. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/systems/dapp_system.move +2802 -343
  39. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/systems/utils.move +1 -1
  40. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/address.move +173 -55
  41. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/dapp_test.move +769 -0
  42. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/fee_test.move +2965 -0
  43. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/integration_test.move +303 -0
  44. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/listing_test.move +1422 -0
  45. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/scene_session_test.move +269 -0
  46. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/session_test.move +624 -0
  47. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/storage_test.move +700 -0
  48. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/typed_object_test.move +177 -0
  49. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/typed_scene_test.move +452 -0
  50. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/user_storage_test.move +160 -0
  51. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/utils/math.move +4 -3
  52. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/utils/type_info.move +2 -2
  53. package/templates/contract/sui-template/dubhe.config.ts +0 -1
  54. package/templates/contract/sui-template/package.json +3 -3
  55. package/templates/contract/sui-template/src/dubhe/Move.lock +33 -30
  56. package/templates/contract/sui-template/src/dubhe/Move.toml +8 -3
  57. package/templates/contract/sui-template/src/dubhe/sources/codegen/dapp_key.move +45 -31
  58. package/templates/contract/sui-template/src/dubhe/sources/codegen/error.move +170 -0
  59. package/templates/contract/sui-template/src/dubhe/sources/codegen/genesis.move +16 -13
  60. package/templates/contract/sui-template/src/dubhe/sources/codegen/init_test.move +8 -14
  61. package/templates/contract/sui-template/src/dubhe/sources/core/account_key.move +1 -3
  62. package/templates/contract/sui-template/src/dubhe/sources/core/dapp_service.move +1776 -137
  63. package/templates/contract/sui-template/src/dubhe/sources/core/data_key.move +1 -20
  64. package/templates/contract/sui-template/src/dubhe/sources/core/events.move +864 -25
  65. package/templates/contract/sui-template/src/dubhe/sources/scripts/deploy_hook.move +15 -16
  66. package/templates/contract/sui-template/src/dubhe/sources/scripts/migrate.move +0 -3
  67. package/templates/contract/sui-template/src/dubhe/sources/systems/address_system.move +25 -17
  68. package/templates/contract/sui-template/src/dubhe/sources/systems/dapp_system.move +2802 -343
  69. package/templates/contract/sui-template/src/dubhe/sources/systems/utils.move +1 -1
  70. package/templates/contract/sui-template/src/dubhe/sources/tests/address.move +173 -55
  71. package/templates/contract/sui-template/src/dubhe/sources/tests/dapp_test.move +769 -0
  72. package/templates/contract/sui-template/src/dubhe/sources/tests/fee_test.move +2965 -0
  73. package/templates/contract/sui-template/src/dubhe/sources/tests/integration_test.move +303 -0
  74. package/templates/contract/sui-template/src/dubhe/sources/tests/listing_test.move +1422 -0
  75. package/templates/contract/sui-template/src/dubhe/sources/tests/scene_session_test.move +269 -0
  76. package/templates/contract/sui-template/src/dubhe/sources/tests/session_test.move +624 -0
  77. package/templates/contract/sui-template/src/dubhe/sources/tests/storage_test.move +700 -0
  78. package/templates/contract/sui-template/src/dubhe/sources/tests/typed_object_test.move +177 -0
  79. package/templates/contract/sui-template/src/dubhe/sources/tests/typed_scene_test.move +452 -0
  80. package/templates/contract/sui-template/src/dubhe/sources/tests/user_storage_test.move +160 -0
  81. package/templates/contract/sui-template/src/dubhe/sources/utils/math.move +4 -3
  82. package/templates/contract/sui-template/src/dubhe/sources/utils/type_info.move +2 -2
  83. package/templates/contract/sui-template/src/template/Move.toml +12 -0
  84. package/templates/contract/sui-template/src/template/sources/codegen/dapp_key.move +47 -0
  85. package/templates/contract/sui-template/src/template/sources/codegen/genesis.move +34 -0
  86. package/templates/contract/sui-template/src/template/sources/codegen/init_test.move +23 -0
  87. package/templates/contract/sui-template/src/template/sources/codegen/user_storage_init.move +27 -0
  88. package/templates/contract/sui-template/src/template/sources/scripts/deploy_hook.move +13 -0
  89. package/templates/contract/sui-template/src/template/sources/scripts/migrate.move +7 -0
  90. package/templates/nextjs/sui-card-duel/.gitignore_ +51 -0
  91. package/templates/nextjs/sui-card-duel/README.md +213 -0
  92. package/templates/nextjs/sui-card-duel/docker-compose.yml +34 -0
  93. package/templates/nextjs/sui-card-duel/mprocs.yaml +17 -0
  94. package/templates/nextjs/sui-card-duel/package.json +43 -0
  95. package/templates/nextjs/sui-card-duel/packages/client/.gitignore_ +48 -0
  96. package/templates/nextjs/sui-card-duel/packages/client/.prettierignore +1 -0
  97. package/templates/nextjs/sui-card-duel/packages/client/.prettierrc.json +8 -0
  98. package/templates/nextjs/sui-card-duel/packages/client/eslint.config.mjs +22 -0
  99. package/templates/nextjs/sui-card-duel/packages/client/next.config.mjs +8 -0
  100. package/templates/nextjs/sui-card-duel/packages/client/package.json +47 -0
  101. package/templates/nextjs/sui-card-duel/packages/client/postcss.config.mjs +8 -0
  102. package/templates/nextjs/sui-card-duel/packages/client/src/app/admin/ArenaPanel.tsx +307 -0
  103. package/templates/nextjs/sui-card-duel/packages/client/src/app/admin/page.tsx +622 -0
  104. package/templates/nextjs/sui-card-duel/packages/client/src/app/brawl/[id]/page.tsx +559 -0
  105. package/templates/nextjs/sui-card-duel/packages/client/src/app/collection/page.tsx +202 -0
  106. package/templates/nextjs/sui-card-duel/packages/client/src/app/components/CardView.tsx +66 -0
  107. package/templates/nextjs/sui-card-duel/packages/client/src/app/components/GameShell.tsx +294 -0
  108. package/templates/nextjs/sui-card-duel/packages/client/src/app/components/icons.tsx +101 -0
  109. package/templates/nextjs/sui-card-duel/packages/client/src/app/duel/[id]/page.tsx +477 -0
  110. package/templates/nextjs/sui-card-duel/packages/client/src/app/favicon.ico +0 -0
  111. package/templates/nextjs/sui-card-duel/packages/client/src/app/globals.css +85 -0
  112. package/templates/nextjs/sui-card-duel/packages/client/src/app/hooks/useGame.ts +313 -0
  113. package/templates/nextjs/sui-card-duel/packages/client/src/app/layout.tsx +24 -0
  114. package/templates/nextjs/sui-card-duel/packages/client/src/app/leaderboard/page.tsx +123 -0
  115. package/templates/nextjs/sui-card-duel/packages/client/src/app/lib/game.ts +77 -0
  116. package/templates/nextjs/sui-card-duel/packages/client/src/app/lib/scenes.ts +226 -0
  117. package/templates/nextjs/sui-card-duel/packages/client/src/app/market/page.tsx +418 -0
  118. package/templates/nextjs/sui-card-duel/packages/client/src/app/page.tsx +427 -0
  119. package/templates/nextjs/sui-card-duel/packages/client/src/app/providers.tsx +69 -0
  120. package/templates/nextjs/sui-card-duel/packages/client/tailwind.config.ts +21 -0
  121. package/templates/nextjs/sui-card-duel/packages/client/tsconfig.json +30 -0
  122. package/templates/nextjs/sui-card-duel/packages/contracts/.gitignore_ +51 -0
  123. package/templates/nextjs/sui-card-duel/packages/contracts/.prettierignore +1 -0
  124. package/templates/nextjs/sui-card-duel/packages/contracts/.prettierrc.json +8 -0
  125. package/templates/nextjs/sui-card-duel/packages/contracts/Dockerfile +5 -0
  126. package/templates/nextjs/sui-card-duel/packages/contracts/Pub.localnet.toml +13 -0
  127. package/templates/nextjs/sui-card-duel/packages/contracts/card_duel.lock.json +66 -0
  128. package/templates/nextjs/sui-card-duel/packages/contracts/docker-compose.yml +34 -0
  129. package/templates/nextjs/sui-card-duel/packages/contracts/dubhe.config.ts +150 -0
  130. package/templates/nextjs/sui-card-duel/packages/contracts/logs/combined.log +31383 -0
  131. package/templates/nextjs/sui-card-duel/packages/contracts/package.json +49 -0
  132. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/config.ts +17 -0
  133. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-brawl.ts +270 -0
  134. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-duel.ts +272 -0
  135. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-lobby.ts +177 -0
  136. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-market.ts +219 -0
  137. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-session.ts +342 -0
  138. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/query-dapp-revenue.ts +95 -0
  139. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/seed.ts +92 -0
  140. package/templates/nextjs/sui-card-duel/packages/contracts/scripts/withdraw-dapp-revenue.ts +56 -0
  141. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/Move.toml +15 -0
  142. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/dapp_key.move +46 -0
  143. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/enums/card_kind.move +47 -0
  144. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/enums/match_state.move +37 -0
  145. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/enums/rarity.move +37 -0
  146. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/error.move +126 -0
  147. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/genesis.move +33 -0
  148. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/init_test.move +23 -0
  149. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/objects/arena.move +137 -0
  150. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/permits/brawl_permit.move +125 -0
  151. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/permits/duel_permit.move +125 -0
  152. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/battle_state.move +255 -0
  153. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/card.move +282 -0
  154. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/deck.move +64 -0
  155. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/game_config.move +267 -0
  156. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/gold.move +205 -0
  157. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/profile.move +255 -0
  158. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/scenes/brawl.move +313 -0
  159. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/scenes/duel.move +313 -0
  160. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/user_storage_init.move +26 -0
  161. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/scripts/deploy_hook.move +32 -0
  162. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/scripts/migrate.move +7 -0
  163. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/arena_system.move +57 -0
  164. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/brawl_system.move +559 -0
  165. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/card_system.move +146 -0
  166. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/duel_system.move +515 -0
  167. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/market_system.move +115 -0
  168. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/player_system.move +37 -0
  169. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/tests/brawl_tests.move +385 -0
  170. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/tests/duel_tests.move +656 -0
  171. package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/tests/test_helpers.move +31 -0
  172. package/templates/nextjs/sui-card-duel/packages/contracts/sui-indexer-schema.graphql +5973 -0
  173. package/templates/nextjs/sui-card-duel/pnpm-workspace.yaml +2 -0
  174. package/templates/nextjs/sui-farm/.gitignore_ +51 -0
  175. package/templates/nextjs/sui-farm/README.md +15 -0
  176. package/templates/nextjs/sui-farm/UPGRADE_NOTES.md +97 -0
  177. package/templates/nextjs/sui-farm/docker-compose.yml +34 -0
  178. package/templates/nextjs/sui-farm/mprocs.yaml +17 -0
  179. package/templates/nextjs/sui-farm/package.json +43 -0
  180. package/templates/nextjs/sui-farm/packages/client/.gitignore_ +48 -0
  181. package/templates/nextjs/sui-farm/packages/client/.prettierignore +1 -0
  182. package/templates/nextjs/sui-farm/packages/client/.prettierrc.json +8 -0
  183. package/templates/nextjs/sui-farm/packages/client/eslint.config.mjs +22 -0
  184. package/templates/nextjs/sui-farm/packages/client/next.config.mjs +8 -0
  185. package/templates/nextjs/sui-farm/packages/client/package.json +47 -0
  186. package/templates/nextjs/sui-farm/packages/client/postcss.config.mjs +8 -0
  187. package/templates/nextjs/sui-farm/packages/client/src/app/admin/page.tsx +618 -0
  188. package/templates/nextjs/sui-farm/packages/client/src/app/components/FarmLand.tsx +526 -0
  189. package/templates/nextjs/sui-farm/packages/client/src/app/components/FarmPlotCard.tsx +215 -0
  190. package/templates/nextjs/sui-farm/packages/client/src/app/components/PetAvatar.tsx +868 -0
  191. package/templates/nextjs/sui-farm/packages/client/src/app/components/PetPanel.tsx +1360 -0
  192. package/templates/nextjs/sui-farm/packages/client/src/app/components/PlantModal.tsx +147 -0
  193. package/templates/nextjs/sui-farm/packages/client/src/app/components/ProxyCard.tsx +684 -0
  194. package/templates/nextjs/sui-farm/packages/client/src/app/components/RanchLand.tsx +822 -0
  195. package/templates/nextjs/sui-farm/packages/client/src/app/components/ResourceHUD.tsx +125 -0
  196. package/templates/nextjs/sui-farm/packages/client/src/app/components/ShopPanel.tsx +232 -0
  197. package/templates/nextjs/sui-farm/packages/client/src/app/components/icons/GameIcons.tsx +574 -0
  198. package/templates/nextjs/sui-farm/packages/client/src/app/favicon.ico +0 -0
  199. package/templates/nextjs/sui-farm/packages/client/src/app/globals.css +423 -0
  200. package/templates/nextjs/sui-farm/packages/client/src/app/hooks/useWorldPermitId.ts +66 -0
  201. package/templates/nextjs/sui-farm/packages/client/src/app/layout.tsx +23 -0
  202. package/templates/nextjs/sui-farm/packages/client/src/app/leaderboard/page.tsx +185 -0
  203. package/templates/nextjs/sui-farm/packages/client/src/app/lib/crops.ts +97 -0
  204. package/templates/nextjs/sui-farm/packages/client/src/app/market/page.tsx +763 -0
  205. package/templates/nextjs/sui-farm/packages/client/src/app/page.tsx +1173 -0
  206. package/templates/nextjs/sui-farm/packages/client/src/app/providers.tsx +69 -0
  207. package/templates/nextjs/sui-farm/packages/client/src/app/state/index.tsx +5 -0
  208. package/templates/nextjs/sui-farm/packages/client/tailwind.config.ts +25 -0
  209. package/templates/nextjs/sui-farm/packages/client/tsconfig.json +30 -0
  210. package/templates/nextjs/sui-farm/packages/contracts/.gitignore_ +51 -0
  211. package/templates/nextjs/sui-farm/packages/contracts/.prettierignore +1 -0
  212. package/templates/nextjs/sui-farm/packages/contracts/.prettierrc.json +8 -0
  213. package/templates/nextjs/sui-farm/packages/contracts/Dockerfile +5 -0
  214. package/templates/nextjs/sui-farm/packages/contracts/Pub.localnet.toml +13 -0
  215. package/templates/nextjs/sui-farm/packages/contracts/counter.lock.json +14 -0
  216. package/templates/nextjs/sui-farm/packages/contracts/docker-compose.yml +34 -0
  217. package/templates/nextjs/sui-farm/packages/contracts/dubhe.config.ts +178 -0
  218. package/templates/nextjs/sui-farm/packages/contracts/harvest.lock.json +120 -0
  219. package/templates/nextjs/sui-farm/packages/contracts/package.json +48 -0
  220. package/templates/nextjs/sui-farm/packages/contracts/scripts/config.ts +17 -0
  221. package/templates/nextjs/sui-farm/packages/contracts/scripts/query-dapp-revenue.ts +95 -0
  222. package/templates/nextjs/sui-farm/packages/contracts/scripts/read-permit.ts +72 -0
  223. package/templates/nextjs/sui-farm/packages/contracts/scripts/seed.ts +85 -0
  224. package/templates/nextjs/sui-farm/packages/contracts/scripts/withdraw-dapp-revenue.ts +56 -0
  225. package/templates/nextjs/sui-farm/packages/contracts/src/counter/Move.toml +14 -0
  226. package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/codegen/dapp_key.move +31 -0
  227. package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/codegen/error.move +10 -0
  228. package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/codegen/genesis.move +33 -0
  229. package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/codegen/init_test.move +23 -0
  230. package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/codegen/resources/counter1.move +64 -0
  231. package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/codegen/resources/counter2.move +70 -0
  232. package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/codegen/user_storage_init.move +26 -0
  233. package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/scripts/deploy_hook.move +8 -0
  234. package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/scripts/migrate.move +8 -0
  235. package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/systems/counter.move +13 -0
  236. package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/tests/counter.move +28 -0
  237. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/Move.toml +14 -0
  238. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/dapp_key.move +46 -0
  239. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/enums/crop_type.move +47 -0
  240. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/enums/egg_type.move +42 -0
  241. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/enums/pet_rarity.move +37 -0
  242. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/enums/pet_species.move +52 -0
  243. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/error.move +110 -0
  244. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/genesis.move +33 -0
  245. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/init_test.move +23 -0
  246. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/permits/world.move +125 -0
  247. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/carrot.move +136 -0
  248. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/carrot_seed.move +80 -0
  249. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/common_egg.move +136 -0
  250. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/corn.move +136 -0
  251. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/corn_seed.move +80 -0
  252. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/crow_charges.move +162 -0
  253. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/crow_damage.move +207 -0
  254. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/farm_plot.move +261 -0
  255. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/gold.move +80 -0
  256. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/pet.move +467 -0
  257. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/pet_config.move +372 -0
  258. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/pet_hatch.move +162 -0
  259. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/pet_slot_index.move +83 -0
  260. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/pet_slots.move +64 -0
  261. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/profile.move +162 -0
  262. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/pumpkin.move +136 -0
  263. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/pumpkin_seed.move +80 -0
  264. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/rare_egg.move +136 -0
  265. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/scarecrow.move +64 -0
  266. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/season_config.move +197 -0
  267. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/season_stats.move +64 -0
  268. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/seasonal_egg.move +136 -0
  269. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/shop_config.move +267 -0
  270. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/trophy.move +245 -0
  271. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/wheat.move +136 -0
  272. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/wheat_seed.move +80 -0
  273. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/world_permit_id.move +64 -0
  274. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/user_storage_init.move +26 -0
  275. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/scripts/deploy_hook.move +45 -0
  276. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/scripts/migrate.move +7 -0
  277. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/systems/crow_system.move +96 -0
  278. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/systems/farm_system.move +182 -0
  279. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/systems/market_system.move +172 -0
  280. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/systems/pet_system.move +495 -0
  281. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/systems/season_system.move +70 -0
  282. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/systems/shop_system.move +112 -0
  283. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/systems/world_system.move +34 -0
  284. package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/tests/farm_tests.move +1192 -0
  285. package/templates/nextjs/sui-farm/pnpm-workspace.yaml +2 -0
  286. package/templates/nextjs/sui-template/packages/client/src/app/components/ProxyCard.tsx +683 -0
  287. package/templates/nextjs/sui-template/packages/client/src/app/page.tsx +510 -5
  288. package/templates/nextjs/sui-template/packages/client/src/app/providers.tsx +13 -5
  289. package/templates/nextjs/sui-template/packages/contracts/Pub.localnet.toml +4 -4
  290. package/templates/nextjs/sui-template/packages/contracts/counter.lock.json +14 -0
  291. package/templates/nextjs/sui-template/packages/contracts/deployment.ts +8 -3
  292. package/templates/nextjs/sui-template/packages/contracts/dubhe.config.json +9 -9
  293. package/templates/nextjs/sui-template/packages/contracts/dubhe.config.ts +0 -4
  294. package/templates/nextjs/sui-template/packages/contracts/metadata.json +180 -124
  295. package/templates/nextjs/sui-template/packages/contracts/package.json +8 -4
  296. package/templates/nextjs/sui-template/packages/contracts/src/counter/Move.toml +4 -0
  297. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/dapp_key.move +45 -31
  298. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/error.move +10 -0
  299. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/genesis.move +30 -13
  300. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/init_test.move +19 -14
  301. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/resources/counter1.move +30 -20
  302. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/resources/counter2.move +35 -24
  303. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/user_storage_init.move +26 -0
  304. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/scripts/deploy_hook.move +6 -10
  305. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/systems/counter.move +7 -20
  306. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/tests/counter.move +14 -27
  307. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/Move.lock +33 -30
  308. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/Move.toml +8 -3
  309. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/dapp_key.move +45 -31
  310. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/error.move +170 -0
  311. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/genesis.move +16 -13
  312. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/init_test.move +8 -14
  313. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/core/account_key.move +1 -3
  314. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/core/dapp_service.move +1776 -137
  315. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/core/data_key.move +1 -20
  316. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/core/events.move +864 -25
  317. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/scripts/deploy_hook.move +15 -16
  318. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/scripts/migrate.move +0 -3
  319. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/systems/address_system.move +25 -17
  320. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/systems/dapp_system.move +2802 -343
  321. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/systems/utils.move +1 -1
  322. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/address.move +173 -55
  323. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/dapp_test.move +769 -0
  324. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/fee_test.move +2965 -0
  325. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/integration_test.move +303 -0
  326. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/listing_test.move +1422 -0
  327. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/scene_session_test.move +269 -0
  328. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/session_test.move +624 -0
  329. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/storage_test.move +700 -0
  330. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/typed_object_test.move +177 -0
  331. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/typed_scene_test.move +452 -0
  332. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/user_storage_test.move +160 -0
  333. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/utils/math.move +4 -3
  334. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/utils/type_info.move +2 -2
  335. package/templates/101/sui-template/packages/contracts/src/counter/Move.lock +0 -41
  336. package/templates/101/sui-template/packages/contracts/src/counter/Published.toml +0 -9
  337. package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/errors.move +0 -12
  338. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/errors.move +0 -140
  339. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dapp_fee_config.move +0 -186
  340. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dapp_fee_state.move +0 -306
  341. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dapp_metadata.move +0 -396
  342. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dubhe_config.move +0 -246
  343. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/core/dapp_store.move +0 -29
  344. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/systems/session_system.move +0 -44
  345. package/templates/contract/sui-template/src/dubhe/sources/codegen/errors.move +0 -140
  346. package/templates/contract/sui-template/src/dubhe/sources/codegen/resources/dapp_fee_config.move +0 -186
  347. package/templates/contract/sui-template/src/dubhe/sources/codegen/resources/dapp_fee_state.move +0 -306
  348. package/templates/contract/sui-template/src/dubhe/sources/codegen/resources/dapp_metadata.move +0 -396
  349. package/templates/contract/sui-template/src/dubhe/sources/codegen/resources/dubhe_config.move +0 -246
  350. package/templates/contract/sui-template/src/dubhe/sources/core/dapp_store.move +0 -29
  351. package/templates/contract/sui-template/src/dubhe/sources/systems/session_system.move +0 -44
  352. package/templates/nextjs/sui-template/packages/contracts/src/counter/Move.lock +0 -47
  353. package/templates/nextjs/sui-template/packages/contracts/src/counter/Published.toml +0 -9
  354. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/errors.move +0 -12
  355. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/errors.move +0 -140
  356. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dapp_fee_config.move +0 -186
  357. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dapp_fee_state.move +0 -306
  358. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dapp_metadata.move +0 -396
  359. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dubhe_config.move +0 -246
  360. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/core/dapp_store.move +0 -29
  361. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/systems/session_system.move +0 -44
@@ -1,203 +1,1842 @@
1
1
  module dubhe::dapp_service {
2
- use std::ascii::String;
3
- use sui::object_table;
4
- use sui::object_table::ObjectTable;
2
+ use std::ascii::{String, string};
3
+ use std::type_name::{Self, TypeName};
4
+ use sui::bcs;
5
+ use sui::bag::{Self, Bag};
6
+ use sui::balance::{Self, Balance};
5
7
  use sui::dynamic_field;
6
- use dubhe::data_key::DataKey;
7
- use std::type_name;
8
- use dubhe::dubhe_events::{emit_store_set_record, emit_store_delete_record};
8
+ use dubhe::error;
9
+ use dubhe::dubhe_events::{
10
+ emit_store_set_record,
11
+ emit_store_set_field,
12
+ emit_store_delete_record,
13
+ emit_store_delete_field,
14
+ emit_object_created,
15
+ emit_scene_created,
16
+ emit_scene_permit_created,
17
+ emit_scene_permit_join,
18
+ emit_dapp_fee_state_updated,
19
+ emit_dapp_revenue_state_updated,
20
+ };
9
21
 
10
- /// Error codes
11
- const EInvalidKey: u64 = 2;
12
- const ENoPermissionPackageId: u64 = 6;
22
+ // ─── Error codes — all delegated to dubhe::error ──────────────────────────
13
23
 
24
+ // ─── UserStorage registry key ─────────────────────────────────────────────
25
+ //
26
+ // Stored as a dynamic field on DappStorage to track which addresses already
27
+ // have a UserStorage for a given DApp. Using a dedicated struct as the key
28
+ // prevents any collision with DApp game-data dynamic fields (which use
29
+ // vector<vector<u8>> keys).
14
30
 
15
- public struct AccountData has key, store { id: UID }
31
+ public struct UserStorageRegistryKey has copy, drop, store { owner: address }
16
32
 
17
- public struct AccountKey has copy, drop, store {
18
- account: String,
19
- dapp_key: String,
33
+ // ─── PermitMetadata authorization token for reactive writes ─────────────
34
+ //
35
+ // Embedded in every codegen-generated typed SceneStorage struct.
36
+ // Reactive write functions require a &ScenePermit<T> to verify that both
37
+ // the initiator and the target are registered participants and that the
38
+ // scene is still active.
39
+ //
40
+ // Participants are stored as dynamic fields on the scene object's UID
41
+ // (key = ParticipantKey { addr }, value = bool true). This gives O(1)
42
+ // join / leave / check instead of the old O(n) vector scan, and prevents
43
+ // unbounded vector growth regardless of participant count.
44
+ // PermitMetadata retains participant_count for max_participants enforcement.
45
+
46
+ /// Dynamic-field key marking a confirmed participant in a scene.
47
+ public struct ParticipantKey has copy, drop, store { addr: address }
48
+
49
+ public struct PermitMetadata has store, copy, drop {
50
+ expires_at: Option<u64>,
51
+ /// Addresses that have been invited but have not yet called accept_<scene>.
52
+ /// Used by create_<scene>_with_invitations + accept_<scene> flow to support
53
+ /// all wallet types (including zkLogin) without requiring off-chain signatures.
54
+ invitees: vector<address>,
55
+ /// Optional deadline for accepting invitations (epoch ms).
56
+ /// None = invitations never expire. Once passed, accept_<scene> aborts.
57
+ invites_expire_at: Option<u64>,
58
+ /// Maximum number of confirmed participants allowed in this scene.
59
+ /// None = unlimited. Enforced on participant add.
60
+ max_participants: Option<u64>,
61
+ /// Current confirmed participant count — updated by add/remove.
62
+ participant_count: u64,
20
63
  }
21
64
 
22
- public(package) fun new_account_key<DappKey: copy + drop>(account: String): AccountKey {
23
- AccountKey {
24
- account,
25
- dapp_key: type_name::get<DappKey>().into_string(),
65
+ public(package) fun new_scene_meta(
66
+ expires_at: Option<u64>,
67
+ max_participants: Option<u64>,
68
+ ): PermitMetadata {
69
+ PermitMetadata {
70
+ expires_at,
71
+ invitees: vector::empty(),
72
+ invites_expire_at: option::none(),
73
+ max_participants,
74
+ participant_count: 0,
26
75
  }
27
76
  }
28
77
 
29
- public(package) fun new_account_data(ctx: &mut TxContext): AccountData {
30
- AccountData { id: object::new(ctx) }
78
+ public(package) fun new_scene_meta_with_invitations(
79
+ invitees: vector<address>,
80
+ invites_expire_at: Option<u64>,
81
+ scene_expires_at: Option<u64>,
82
+ max_participants: Option<u64>,
83
+ ): PermitMetadata {
84
+ PermitMetadata {
85
+ expires_at: scene_expires_at,
86
+ invitees,
87
+ invites_expire_at,
88
+ max_participants,
89
+ participant_count: 0,
90
+ }
91
+ }
92
+
93
+ public fun scene_expires_at(meta: &PermitMetadata): Option<u64> {
94
+ meta.expires_at
95
+ }
96
+
97
+ public fun scene_invitees(meta: &PermitMetadata): &vector<address> {
98
+ &meta.invitees
31
99
  }
32
100
 
33
- /// Storage structure
101
+ public fun scene_invites_expire_at(meta: &PermitMetadata): Option<u64> {
102
+ meta.invites_expire_at
103
+ }
104
+
105
+ public fun scene_max_participants(meta: &PermitMetadata): Option<u64> {
106
+ meta.max_participants
107
+ }
108
+
109
+ public fun scene_participant_count(meta: &PermitMetadata): u64 {
110
+ meta.participant_count
111
+ }
112
+
113
+ public fun is_scene_invitee(meta: &PermitMetadata, addr: address): bool {
114
+ meta.invitees.contains(&addr)
115
+ }
116
+
117
+ /// O(1) participant check via dynamic field existence.
118
+ public fun is_scene_participant(id: &UID, addr: address): bool {
119
+ dynamic_field::exists_(id, ParticipantKey { addr })
120
+ }
121
+
122
+ /// Returns true if the scene is still active (not expired).
123
+ /// A scene with expires_at = None is considered permanently active.
124
+ public fun is_scene_active(meta: &PermitMetadata, now_ms: u64): bool {
125
+ if (option::is_none(&meta.expires_at)) { return true };
126
+ now_ms < *option::borrow(&meta.expires_at)
127
+ }
128
+
129
+ // ─── ObjectEntityId registry key ─────────────────────────────────────────
130
+ //
131
+ // Stored as a dynamic field on DappStorage to enforce entity_id uniqueness
132
+ // within a specific type_tag (e.g., b"guild", b"boss").
133
+ // Different type_tags can share the same entity_id value without conflict.
134
+
135
+ public struct ObjectEntityIdKey has copy, drop, store {
136
+ type_tag: vector<u8>,
137
+ entity_id: vector<u8>,
138
+ }
139
+
140
+ public(package) fun register_object_entity_id(
141
+ ds: &mut DappStorage,
142
+ type_tag: vector<u8>,
143
+ entity_id: vector<u8>,
144
+ object_id: address,
145
+ ) {
146
+ let key = ObjectEntityIdKey { type_tag, entity_id };
147
+ error::entity_id_already_exists(!dynamic_field::exists_(&ds.id, key));
148
+ dynamic_field::add(&mut ds.id, key, object_id);
149
+ }
150
+
151
+ public(package) fun unregister_object_entity_id(
152
+ ds: &mut DappStorage,
153
+ type_tag: vector<u8>,
154
+ entity_id: vector<u8>,
155
+ ) {
156
+ let key = ObjectEntityIdKey { type_tag, entity_id };
157
+ if (dynamic_field::exists_(&ds.id, key)) {
158
+ let _: address = dynamic_field::remove(&mut ds.id, key);
159
+ };
160
+ }
161
+
162
+ #[test_only]
163
+ public fun has_object_entity_id(
164
+ ds: &DappStorage,
165
+ type_tag: vector<u8>,
166
+ entity_id: vector<u8>,
167
+ ): bool {
168
+ dynamic_field::exists_(&ds.id, ObjectEntityIdKey { type_tag, entity_id })
169
+ }
170
+
171
+ #[test_only]
172
+ public fun get_object_entity_id(
173
+ ds: &DappStorage,
174
+ type_tag: vector<u8>,
175
+ entity_id: vector<u8>,
176
+ ): address {
177
+ let key = ObjectEntityIdKey { type_tag, entity_id };
178
+ error::entity_not_found(dynamic_field::exists_(&ds.id, key));
179
+ *dynamic_field::borrow<ObjectEntityIdKey, address>(&ds.id, key)
180
+ }
181
+
182
+ // ─── FrameworkFeeConfig ───────────────────────────────────────────────────
183
+
184
+ const MAX_FEE_HISTORY: u64 = 20;
185
+
186
+ /// Snapshot of a fee update stored in the rolling fee history.
187
+ /// Both fee components are recorded together so the history is self-contained.
188
+ public struct FeeHistoryEntry has store, copy, drop {
189
+ base_fee: u256,
190
+ bytes_fee: u256,
191
+ effective_from_ms: u64,
192
+ }
193
+
194
+ public struct FrameworkFeeConfig has store, drop {
195
+ /// Flat charge per write operation (applied to every write regardless of size).
196
+ base_fee_per_write: u256,
197
+ /// Per-byte charge applied to on-chain writes (offchain writes pay base_fee only).
198
+ bytes_fee_per_byte: u256,
199
+ /// Pending base_fee change (0 when no change is scheduled).
200
+ pending_base_fee: u256,
201
+ /// Pending bytes_fee change (0 when no change is scheduled).
202
+ pending_bytes_fee: u256,
203
+ /// When both pending fees become effective (ms). Shared across both components.
204
+ fee_effective_at_ms: u64,
205
+ treasury: address,
206
+ /// Pending treasury address for two-step rotation. @0x0 means no pending transfer.
207
+ pending_treasury: address,
208
+ fee_history: vector<FeeHistoryEntry>,
209
+ /// The coin type currently accepted for credit recharges.
210
+ /// None signals "not yet initialised" (deploy_hook hasn't run).
211
+ accepted_coin_type: Option<TypeName>,
212
+ /// Pending coin type after a propose_coin_type call. None = no change in flight.
213
+ pending_coin_type: Option<TypeName>,
214
+ /// Epoch-ms timestamp when pending_coin_type becomes committable (0 = no pending).
215
+ coin_type_effective_at_ms: u64,
216
+ }
217
+
218
+ // ─── FrameworkConfig — operational params managed by framework admin ──────
219
+ //
220
+ // Kept separate from FrameworkFeeConfig so that financial controls (treasury)
221
+ // and operational controls (admin) can be managed independently and rotated
222
+ // separately.
223
+
224
+ public struct FrameworkConfig has store, drop {
225
+ /// Default virtual free credit (MIST) automatically granted to every new DApp
226
+ /// at creation time. 25 SUI = 25_000_000_000 MIST. 0 disables auto-grant.
227
+ default_free_credit: u256,
228
+ /// Duration (ms) for which the default free credit is valid.
229
+ /// 0 = never expires. 6 months ≈ 15_778_800_000 ms.
230
+ default_free_credit_duration_ms: u64,
231
+ /// Framework admin address (manages operational params).
232
+ /// Distinct from treasury which manages financial operations.
233
+ admin: address,
234
+ /// Pending admin for two-step rotation. @0x0 means no pending transfer.
235
+ pending_admin: address,
236
+ /// Default write-fee DApp share (bps) assigned to newly created DApps.
237
+ /// Controls how write-operation fees are split between the DApp and the framework treasury.
238
+ /// Framework admin can override per-DApp with set_dapp_write_fee_share.
239
+ /// e.g. 3000 = 30% to DApp developer; remaining 70% to framework treasury.
240
+ default_write_fee_dapp_share_bps: u64,
241
+ /// Absolute ceiling on the per-DApp unsettled write limit.
242
+ /// DApp admins cannot set write_limit above this value.
243
+ /// Default 2_000; updatable by framework admin via set_framework_max_write_limit.
244
+ framework_max_write_limit: u64,
245
+ /// Global marketplace transaction fee in basis points (e.g. 300 = 3%).
246
+ /// Applied to every listing purchase across all DApps.
247
+ /// Framework admin can change via update_marketplace_fee.
248
+ marketplace_fee_bps: u64,
249
+ /// Of the marketplace fee, how many bps go to the DApp (remainder to framework).
250
+ /// e.g. 5000 = 50% of fee to DApp, 50% to framework treasury.
251
+ /// Framework admin can change via update_marketplace_dapp_share.
252
+ marketplace_dapp_share_bps: u64,
253
+ }
254
+
255
+ // ─── DappHub — global registry ────────────────────────────────────────────
256
+
34
257
  public struct DappHub has key, store {
35
- /// The unique identifier of the DappStore instance
36
- id: UID,
37
- /// Accounts
38
- accounts: ObjectTable<AccountKey, AccountData>,
258
+ id: UID,
259
+ fee_config: FrameworkFeeConfig,
260
+ config: FrameworkConfig,
261
+ /// Framework version used for upgrade version gating.
262
+ /// After a framework upgrade, call bump_framework_version to
263
+ /// increment this value; all version-gated functions using the
264
+ /// old FRAMEWORK_VERSION constant will then abort.
265
+ version: u64,
266
+ }
267
+
268
+ // ─── DappStorage — per-DApp shared object ─────────────────────────────────
269
+
270
+ public struct DappStorage has key, store {
271
+ id: UID,
272
+ dapp_key: String,
273
+ // ─── Metadata (stored directly, no dynamic field overhead) ──────────
274
+ name: String,
275
+ description: String,
276
+ website_url: String,
277
+ cover_url: vector<String>,
278
+ partners: vector<String>,
279
+ package_ids: vector<address>,
280
+ created_at: u64,
281
+ admin: address,
282
+ pending_admin: address,
283
+ version: u32,
284
+ paused: bool,
285
+ // ─── Fee / credit ────────────────────────────────────────────────────
286
+ /// Virtual free credit granted by framework admin (MIST, no SUI backing).
287
+ /// Consumed before credit_pool during settlement (free-first priority).
288
+ /// Set to 0 when exhausted or revoked.
289
+ free_credit: u256,
290
+ /// Expiry timestamp (epoch ms) for free_credit. 0 = never expires.
291
+ /// Expired free credit is treated as 0 in settlement and unsuspend checks.
292
+ free_credit_expires_at: u64,
293
+ credit_pool: u256,
294
+ /// Cumulative amount settled, used for off-chain analytics.
295
+ /// NOTE: the metric differs by settlement mode:
296
+ /// DAPP_SUBSIDIZES — sum of paid_used (credit_pool deductions, excludes free_credit).
297
+ /// USER_PAYS — sum of total user payment (fw + dapp portions combined).
298
+ total_settled: u256,
299
+ // ─── Per-DApp fee rates ───────────────────────────────────────────────
300
+ /// Flat charge per write operation (MIST). Copied from DappHub defaults
301
+ /// at creation time; updated via sync_dapp_fee.
302
+ base_fee_per_write: u256,
303
+ /// Per-byte charge for on-chain writes (MIST). Same lifecycle as above.
304
+ bytes_fee_per_byte: u256,
305
+ // ─── Settlement mode ─────────────────────────────────────────────────
306
+ /// 0 = DAPP_SUBSIDIZES (default), 1 = USER_PAYS.
307
+ /// Bidirectional switch: can be changed freely by the DApp admin.
308
+ settlement_mode: u8,
309
+ /// Write-fee DApp share (basis points).
310
+ /// Controls how write-operation fees are split between DApp and framework treasury.
311
+ /// In USER_PAYS mode: share_bps of total_cost goes to DApp revenue; remainder to treasury.
312
+ /// In DAPP_SUBSIDIZES mode: framework collects (10_000 - share_bps) / 10_000 of total_cost
313
+ /// from the DApp's credit pool; the DApp effectively retains its share.
314
+ /// Set exclusively by the framework admin via set_dapp_write_fee_share.
315
+ write_fee_dapp_share_bps: u64,
316
+ }
317
+
318
+ // ─── UserStorage — per-user shared key object ─────────────────────────────
319
+ //
320
+ // UserStorage is a shared object so that:
321
+ // 1. Canonical owner can write to it directly from their wallet.
322
+ // 2. An authorized session key (ephemeral frontend wallet) can write on
323
+ // their behalf without any object transfer.
324
+ // 3. Canonical owner can revoke or update the session key at any time
325
+ // because the shared object is always reachable by both parties.
326
+ //
327
+ // session_key == @0x0 → no active session (canonical owner only).
328
+ // session_expires_at → expiry deadline in epoch-clock ms (≈24h granularity
329
+ // on mainnet/testnet, ≈1h on devnet). In production,
330
+ // this is always >0 because activate_session enforces
331
+ // a minimum duration. The value 0 only appears in test
332
+ // helpers and means "no active session" when combined
333
+ // with session_key == @0x0.
334
+
335
+ public struct UserStorage has key {
336
+ id: UID,
337
+ dapp_key: String,
338
+ canonical_owner: address,
339
+ session_key: address,
340
+ session_expires_at: u64,
341
+ /// Total number of write operations (offchain + onchain). Incremented on
342
+ /// every set_record / set_field call regardless of offchain flag.
343
+ write_count: u64,
344
+ settled_count: u64,
345
+ /// Cumulative bytes of on-chain data written (offchain writes contribute 0).
346
+ /// Used together with write_count to compute the total settlement charge:
347
+ /// cost = base_fee × unsettled_writes + bytes_fee × unsettled_bytes
348
+ write_bytes: u256,
349
+ settled_bytes: u256,
350
+ /// Snapshot of the framework's effective write_limit at creation or last explicit sync.
351
+ /// set_record / set_field enforce unsettled_count < write_limit.
352
+ /// Call sync_user_write_limit to pick up changes made to DappHub.
353
+ write_limit: u64,
354
+ }
355
+
356
+ // ─── ObjectStorage — DApp-managed typed shared entity ─────────────────────
357
+ //
358
+ // A Framework-owned shared object that holds arbitrary key-value data for a
359
+ // single DApp entity (e.g. a guild, a boss, an item). The phantom type
360
+ // parameter ObjType (a DApp-package-local struct) distinguishes GuildStorage
361
+ // from BossStorage at the Move compiler level, preserving compile-time type
362
+ // safety while keeping the underlying struct in the framework package.
363
+ //
364
+ // Data is stored as BCS bytes (vector<u8>) in the Bag — the same model as
365
+ // UserStorage — enabling the framework to emit Dubhe_Object_SetField events
366
+ // for off-chain indexing.
367
+
368
+ public struct ObjectStorage<phantom ObjType> has key {
369
+ id: UID,
370
+ dapp_key: String, // used for dapp-key mismatch checks and event emission
371
+ object_type: vector<u8>, // human-readable type tag, e.g. b"guild"
372
+ entity_id: vector<u8>,
373
+ data: Bag, // key: vector<u8> field name → value: vector<u8> BCS bytes
374
+ }
375
+
376
+ // ─── ScenePermit / SceneStorage ───────────────────────────────────────────
377
+ //
378
+ // ScenePermit owns participant membership and lifecycle metadata for a
379
+ // session. SceneStorage is pure data storage, symmetric to ObjectStorage.
380
+ // A permit-bound SceneStorage records both the permit type tag and the
381
+ // concrete permit object id to prevent same-type session instances from
382
+ // authorizing each other.
383
+
384
+ public struct ScenePermit<phantom PermType> has key {
385
+ id: UID,
386
+ dapp_key: String,
387
+ permit_type: vector<u8>,
388
+ meta: PermitMetadata,
389
+ }
390
+
391
+ public struct SceneStorage<phantom SceneType> has key {
392
+ id: UID,
393
+ dapp_key: String,
394
+ scene_type: vector<u8>,
395
+ authorized_permit_id: Option<address>,
396
+ data: Bag,
397
+ }
398
+
399
+ // ─── ObjectStorage / SceneStorage accessors ───────────────────────────────
400
+
401
+ public fun object_storage_dapp_key<T>(s: &ObjectStorage<T>): String { s.dapp_key }
402
+ public fun object_storage_type<T>(s: &ObjectStorage<T>): &vector<u8> { &s.object_type }
403
+ public fun object_storage_entity_id<T>(s: &ObjectStorage<T>): &vector<u8> { &s.entity_id }
404
+ public fun object_storage_id<T>(s: &ObjectStorage<T>): &UID { &s.id }
405
+ public(package) fun object_storage_id_mut<T>(s: &mut ObjectStorage<T>): &mut UID { &mut s.id }
406
+
407
+ public fun scene_permit_dapp_key<T>(p: &ScenePermit<T>): String { p.dapp_key }
408
+ public fun scene_permit_type<T>(p: &ScenePermit<T>): &vector<u8> { &p.permit_type }
409
+ public fun scene_permit_meta<T>(p: &ScenePermit<T>): &PermitMetadata { &p.meta }
410
+ public(package) fun scene_permit_meta_mut<T>(p: &mut ScenePermit<T>): &mut PermitMetadata { &mut p.meta }
411
+ public fun scene_permit_id<T>(p: &ScenePermit<T>): &UID { &p.id }
412
+ public(package) fun scene_permit_id_mut<T>(p: &mut ScenePermit<T>): &mut UID { &mut p.id }
413
+
414
+ public fun scene_storage_dapp_key<T>(s: &SceneStorage<T>): String { s.dapp_key }
415
+ public fun scene_storage_type<T>(s: &SceneStorage<T>): &vector<u8> { &s.scene_type }
416
+ public fun scene_storage_authorized_permit_id<T>(s: &SceneStorage<T>): &Option<address> {
417
+ &s.authorized_permit_id
418
+ }
419
+ public fun scene_storage_id<T>(s: &SceneStorage<T>): &UID { &s.id }
420
+ public(package) fun scene_storage_id_mut<T>(s: &mut SceneStorage<T>): &mut UID { &mut s.id }
421
+
422
+ public fun dapp_storage_id(ds: &DappStorage): &UID { &ds.id }
423
+ public fun user_storage_id(us: &UserStorage): &UID { &us.id }
424
+
425
+ // ─── ScenePermit participant helpers ─────────────────────────────────────
426
+
427
+ public(package) fun accept_invitation_in_scene_permit<T>(
428
+ permit: &mut ScenePermit<T>,
429
+ addr: address,
430
+ ) {
431
+ let (found, idx) = permit.meta.invitees.index_of(&addr);
432
+ error::not_participant(found);
433
+ permit.meta.invitees.remove(idx);
434
+ add_participant_in_scene_permit(permit, addr);
435
+ }
436
+
437
+ public(package) fun add_participant_in_scene_permit<T>(
438
+ permit: &mut ScenePermit<T>,
439
+ addr: address,
440
+ ) {
441
+ if (dynamic_field::exists_(&permit.id, ParticipantKey { addr })) { return };
442
+ if (permit.meta.max_participants.is_some()) {
443
+ error::scene_full(
444
+ permit.meta.participant_count < *option::borrow(&permit.meta.max_participants)
445
+ );
446
+ };
447
+ dynamic_field::add(&mut permit.id, ParticipantKey { addr }, true);
448
+ permit.meta.participant_count = permit.meta.participant_count + 1;
449
+ }
450
+
451
+ public(package) fun remove_participant_in_scene_permit<T>(
452
+ permit: &mut ScenePermit<T>,
453
+ addr: address,
454
+ ) {
455
+ if (!dynamic_field::exists_(&permit.id, ParticipantKey { addr })) { return };
456
+ let _: bool = dynamic_field::remove(&mut permit.id, ParticipantKey { addr });
457
+ permit.meta.participant_count = permit.meta.participant_count - 1;
458
+ }
459
+
460
+ public fun is_participant_in_scene_permit<T>(
461
+ permit: &ScenePermit<T>,
462
+ addr: address,
463
+ ): bool {
464
+ dynamic_field::exists_(&permit.id, ParticipantKey { addr })
465
+ }
466
+
467
+ // ─── ObjectStorage CRUD (package-internal, called by dapp_system) ─────────
468
+
469
+ public(package) fun new_object_storage<ObjType>(
470
+ dapp_key: String,
471
+ object_type: vector<u8>,
472
+ entity_id: vector<u8>,
473
+ ctx: &mut TxContext,
474
+ ): ObjectStorage<ObjType> {
475
+ let storage = ObjectStorage<ObjType> {
476
+ id: object::new(ctx),
477
+ dapp_key,
478
+ object_type,
479
+ entity_id,
480
+ data: bag::new(ctx),
481
+ };
482
+ emit_object_created(
483
+ storage.dapp_key,
484
+ storage.object_type,
485
+ object::uid_to_address(&storage.id),
486
+ storage.entity_id,
487
+ );
488
+ storage
489
+ }
490
+
491
+ /// Create a ScenePermit with an initial participant list.
492
+ /// Participants are stored as Dynamic Fields on the permit's UID.
493
+ public(package) fun new_scene_permit_with_participants<PermType>(
494
+ dapp_key_str: String,
495
+ permit_type: vector<u8>,
496
+ participants: vector<address>,
497
+ expires_at: Option<u64>,
498
+ max_participants: Option<u64>,
499
+ ctx: &mut TxContext,
500
+ ): ScenePermit<PermType> {
501
+ let mut permit = ScenePermit<PermType> {
502
+ id: object::new(ctx),
503
+ dapp_key: dapp_key_str,
504
+ permit_type,
505
+ meta: new_scene_meta(expires_at, max_participants),
506
+ };
507
+ let mut i = 0;
508
+ let len = participants.length();
509
+ while (i < len) {
510
+ add_participant_in_scene_permit(&mut permit, *participants.borrow(i));
511
+ i = i + 1;
512
+ };
513
+ let permit_id = object::uid_to_address(&permit.id);
514
+ emit_scene_permit_created(
515
+ permit.dapp_key,
516
+ permit.permit_type,
517
+ permit_id,
518
+ permit.meta.expires_at,
519
+ permit.meta.invites_expire_at,
520
+ permit.meta.max_participants,
521
+ permit.meta.participant_count,
522
+ );
523
+ let mut j = 0;
524
+ while (j < len) {
525
+ emit_scene_permit_join(
526
+ permit.dapp_key,
527
+ permit.permit_type,
528
+ permit_id,
529
+ *participants.borrow(j),
530
+ );
531
+ j = j + 1;
532
+ };
533
+ permit
534
+ }
535
+
536
+ /// Create a ScenePermit with an invitation list (no confirmed participants yet).
537
+ public(package) fun new_scene_permit_with_invitations<PermType>(
538
+ dapp_key_str: String,
539
+ permit_type: vector<u8>,
540
+ invitees: vector<address>,
541
+ invites_expire_at: Option<u64>,
542
+ scene_expires_at: Option<u64>,
543
+ max_participants: Option<u64>,
544
+ ctx: &mut TxContext,
545
+ ): ScenePermit<PermType> {
546
+ let permit = ScenePermit<PermType> {
547
+ id: object::new(ctx),
548
+ dapp_key: dapp_key_str,
549
+ permit_type,
550
+ meta: new_scene_meta_with_invitations(
551
+ invitees, invites_expire_at, scene_expires_at, max_participants
552
+ ),
553
+ };
554
+ emit_scene_permit_created(
555
+ permit.dapp_key,
556
+ permit.permit_type,
557
+ object::uid_to_address(&permit.id),
558
+ permit.meta.expires_at,
559
+ permit.meta.invites_expire_at,
560
+ permit.meta.max_participants,
561
+ permit.meta.participant_count,
562
+ );
563
+ permit
564
+ }
565
+
566
+ /// Create a system-controlled SceneStorage with no permit authorization.
567
+ public(package) fun new_scene_storage_system<SceneType>(
568
+ dapp_key_str: String,
569
+ scene_type: vector<u8>,
570
+ ctx: &mut TxContext,
571
+ ): SceneStorage<SceneType> {
572
+ let storage = SceneStorage<SceneType> {
573
+ id: object::new(ctx),
574
+ dapp_key: dapp_key_str,
575
+ scene_type,
576
+ authorized_permit_id: option::none(),
577
+ data: bag::new(ctx),
578
+ };
579
+ emit_scene_created(
580
+ storage.dapp_key,
581
+ storage.scene_type,
582
+ object::uid_to_address(&storage.id),
583
+ b"system",
584
+ storage.authorized_permit_id,
585
+ );
586
+ storage
587
+ }
588
+
589
+ /// Create a SceneStorage bound to a concrete ScenePermit object.
590
+ public(package) fun new_scene_storage_with_permit<PermType, SceneType>(
591
+ dapp_key_str: String,
592
+ scene_type: vector<u8>,
593
+ permit: &ScenePermit<PermType>,
594
+ ctx: &mut TxContext,
595
+ ): SceneStorage<SceneType> {
596
+ let storage = SceneStorage<SceneType> {
597
+ id: object::new(ctx),
598
+ dapp_key: dapp_key_str,
599
+ scene_type,
600
+ authorized_permit_id: option::some(object::uid_to_address(scene_permit_id(permit))),
601
+ data: bag::new(ctx),
602
+ };
603
+ emit_scene_created(
604
+ storage.dapp_key,
605
+ storage.scene_type,
606
+ object::uid_to_address(&storage.id),
607
+ b"permit",
608
+ storage.authorized_permit_id,
609
+ );
610
+ storage
39
611
  }
40
612
 
41
- /// Create a new storage instance
613
+ /// Set (insert or overwrite) a native-typed field in an ObjectStorage Bag.
614
+ /// `T` must be `store + copy + drop` so Bag can hold it and bcs::to_bytes can encode it.
615
+ public(package) fun set_object_field<ObjType, T: store + copy + drop>(
616
+ storage: &mut ObjectStorage<ObjType>,
617
+ field_name: vector<u8>,
618
+ value: T,
619
+ ) {
620
+ if (bag::contains_with_type<vector<u8>, T>(&storage.data, field_name)) {
621
+ *bag::borrow_mut<vector<u8>, T>(&mut storage.data, field_name) = value;
622
+ } else {
623
+ bag::add(&mut storage.data, field_name, value);
624
+ }
625
+ }
626
+
627
+ /// Get a native-typed field from an ObjectStorage Bag. Aborts if not present.
628
+ public(package) fun get_object_field<ObjType, T: store + copy + drop>(
629
+ storage: &ObjectStorage<ObjType>,
630
+ field_name: vector<u8>,
631
+ ): T {
632
+ *bag::borrow<vector<u8>, T>(&storage.data, field_name)
633
+ }
634
+
635
+ /// Check if a native-typed field exists in an ObjectStorage Bag.
636
+ public(package) fun has_object_field<ObjType, T: store + copy + drop>(
637
+ storage: &ObjectStorage<ObjType>,
638
+ field_name: vector<u8>,
639
+ ): bool {
640
+ bag::contains_with_type<vector<u8>, T>(&storage.data, field_name)
641
+ }
642
+
643
+ /// Remove and return a native-typed field from an ObjectStorage Bag.
644
+ public(package) fun remove_object_field<ObjType, T: store + copy + drop>(
645
+ storage: &mut ObjectStorage<ObjType>,
646
+ field_name: vector<u8>,
647
+ ): T {
648
+ bag::remove<vector<u8>, T>(&mut storage.data, field_name)
649
+ }
650
+
651
+ /// Consume and destroy an ObjectStorage whose Bag is empty.
652
+ public(package) fun destroy_object_storage<ObjType>(storage: ObjectStorage<ObjType>) {
653
+ let ObjectStorage { id, dapp_key: _, object_type: _, entity_id: _, data } = storage;
654
+ bag::destroy_empty(data);
655
+ object::delete(id);
656
+ }
657
+
658
+ /// Set (insert or overwrite) a native-typed field in a SceneStorage Bag.
659
+ public(package) fun set_scene_field<SceneType, T: store + copy + drop>(
660
+ storage: &mut SceneStorage<SceneType>,
661
+ field_name: vector<u8>,
662
+ value: T,
663
+ ) {
664
+ if (bag::contains_with_type<vector<u8>, T>(&storage.data, field_name)) {
665
+ *bag::borrow_mut<vector<u8>, T>(&mut storage.data, field_name) = value;
666
+ } else {
667
+ bag::add(&mut storage.data, field_name, value);
668
+ }
669
+ }
670
+
671
+ /// Get a native-typed field from a SceneStorage Bag. Aborts if not present.
672
+ public(package) fun get_scene_field<SceneType, T: store + copy + drop>(
673
+ storage: &SceneStorage<SceneType>,
674
+ field_name: vector<u8>,
675
+ ): T {
676
+ *bag::borrow<vector<u8>, T>(&storage.data, field_name)
677
+ }
678
+
679
+ /// Check if a native-typed field exists in a SceneStorage Bag.
680
+ public(package) fun has_scene_field<SceneType, T: store + copy + drop>(
681
+ storage: &SceneStorage<SceneType>,
682
+ field_name: vector<u8>,
683
+ ): bool {
684
+ bag::contains_with_type<vector<u8>, T>(&storage.data, field_name)
685
+ }
686
+
687
+ /// Remove and return a native-typed field from a SceneStorage Bag.
688
+ public(package) fun remove_scene_field<SceneType, T: store + copy + drop>(
689
+ storage: &mut SceneStorage<SceneType>,
690
+ field_name: vector<u8>,
691
+ ): T {
692
+ bag::remove<vector<u8>, T>(&mut storage.data, field_name)
693
+ }
694
+
695
+ /// Consume and destroy a SceneStorage whose Bag is empty.
696
+ public(package) fun destroy_scene_storage<SceneType>(storage: SceneStorage<SceneType>) {
697
+ let SceneStorage {
698
+ id,
699
+ dapp_key: _,
700
+ scene_type: _,
701
+ authorized_permit_id: _,
702
+ data,
703
+ } = storage;
704
+ bag::destroy_empty(data);
705
+ object::delete(id);
706
+ }
707
+
708
+ /// Consume and destroy a ScenePermit whose participant DFs are empty.
709
+ public(package) fun destroy_scene_permit<PermType>(permit: ScenePermit<PermType>) {
710
+ let ScenePermit { id, dapp_key: _, permit_type: _, meta: _ } = permit;
711
+ object::delete(id);
712
+ }
713
+
714
+ // ─── Share wrappers ────────────────────────────────────────────────────────
715
+ //
716
+ // transfer::share_object is restricted to the module that defines the type.
717
+ // ObjectStorage, ScenePermit and SceneStorage are defined here, so these package-internal
718
+ // wrappers let dapp_system (same package, different module) share them.
719
+
720
+ /// Share a newly-created ObjectStorage shared object.
721
+ public(package) fun share_object_storage<ObjType>(storage: ObjectStorage<ObjType>) {
722
+ sui::transfer::share_object(storage);
723
+ }
724
+
725
+ /// Share a newly-created ScenePermit shared object.
726
+ public(package) fun share_scene_permit<PermType>(permit: ScenePermit<PermType>) {
727
+ sui::transfer::share_object(permit);
728
+ }
729
+
730
+ /// Share a newly-created SceneStorage shared object.
731
+ public(package) fun share_scene_storage<SceneType>(storage: SceneStorage<SceneType>) {
732
+ sui::transfer::share_object(storage);
733
+ }
734
+
735
+ // ─── Constructors ─────────────────────────────────────────────────────────
736
+
42
737
  public(package) fun new(ctx: &mut TxContext): DappHub {
43
738
  DappHub {
44
739
  id: object::new(ctx),
45
- accounts: object_table::new(ctx),
740
+ fee_config: FrameworkFeeConfig {
741
+ base_fee_per_write: 0,
742
+ bytes_fee_per_byte: 0,
743
+ pending_base_fee: 0,
744
+ pending_bytes_fee: 0,
745
+ fee_effective_at_ms: 0,
746
+ // @0x0 signals "not yet initialised"; deploy_hook::run sets the real
747
+ // treasury address via initialize_framework_fee on first genesis::run.
748
+ treasury: @0x0,
749
+ pending_treasury: @0x0,
750
+ fee_history: vector::empty(),
751
+ accepted_coin_type: option::none(),
752
+ pending_coin_type: option::none(),
753
+ coin_type_effective_at_ms: 0,
754
+ },
755
+ config: FrameworkConfig {
756
+ // New DApps automatically receive 25 SUI of free credit that never expires.
757
+ // 25 SUI = 25_000_000_000 MIST; duration_ms = 0 means no expiry.
758
+ default_free_credit: 25_000_000_000,
759
+ default_free_credit_duration_ms: 0,
760
+ admin: ctx.sender(),
761
+ pending_admin: @0x0,
762
+ // @0 signals "not yet initialised"; deploy_hook::run sets the real
763
+ // values via initialize_framework_fee on first genesis::run.
764
+ default_write_fee_dapp_share_bps: 0,
765
+ framework_max_write_limit: 2_000,
766
+ // Marketplace fee: 3% total, 50/50 split (1.5% framework, 1.5% DApp).
767
+ marketplace_fee_bps: 300,
768
+ marketplace_dapp_share_bps: 5_000,
769
+ },
770
+ version: 1,
46
771
  }
47
772
  }
48
773
 
49
- public(package) fun set_record<DappKey: copy + drop>(
50
- self: &mut DappHub,
51
- _: DappKey,
52
- key: vector<vector<u8>>,
53
- value: vector<vector<u8>>,
54
- account: String,
55
- offchain: bool,
56
- ctx: &mut TxContext,
774
+ public(package) fun new_dapp_storage<DappKey: copy + drop>(
775
+ name: String,
776
+ description: String,
777
+ package_ids: vector<address>,
778
+ created_at: u64,
779
+ admin: address,
780
+ free_credit: u256,
781
+ free_credit_expires_at: u64,
782
+ base_fee_per_write: u256,
783
+ bytes_fee_per_byte: u256,
784
+ settlement_mode: u8,
785
+ write_fee_dapp_share_bps: u64,
786
+ ctx: &mut TxContext,
787
+ ): DappStorage {
788
+ DappStorage {
789
+ id: object::new(ctx),
790
+ dapp_key: type_name::with_defining_ids<DappKey>().into_string(),
791
+ name,
792
+ description,
793
+ website_url: string(b""),
794
+ cover_url: vector::empty(),
795
+ partners: vector::empty(),
796
+ package_ids,
797
+ created_at,
798
+ admin,
799
+ pending_admin: @0x0,
800
+ version: 1,
801
+ paused: false,
802
+ free_credit,
803
+ free_credit_expires_at,
804
+ credit_pool: 0,
805
+ total_settled: 0,
806
+ base_fee_per_write,
807
+ bytes_fee_per_byte,
808
+ settlement_mode,
809
+ write_fee_dapp_share_bps,
810
+ }
811
+ }
812
+
813
+ public(package) fun new_user_storage<DappKey: copy + drop>(
814
+ owner: address,
815
+ write_limit: u64,
816
+ ctx: &mut TxContext,
817
+ ): UserStorage {
818
+ UserStorage {
819
+ id: object::new(ctx),
820
+ dapp_key: type_name::with_defining_ids<DappKey>().into_string(),
821
+ canonical_owner: owner,
822
+ session_key: @0x0,
823
+ session_expires_at: 0,
824
+ write_count: 0,
825
+ settled_count: 0,
826
+ write_bytes: 0,
827
+ settled_bytes: 0,
828
+ write_limit,
829
+ }
830
+ }
831
+
832
+ // ─── DappHub: fee config accessors ────────────────────────────────────────
833
+
834
+ public fun get_fee_config(dh: &DappHub): &FrameworkFeeConfig {
835
+ &dh.fee_config
836
+ }
837
+
838
+ public(package) fun get_fee_config_mut(dh: &mut DappHub): &mut FrameworkFeeConfig {
839
+ &mut dh.fee_config
840
+ }
841
+
842
+ public fun base_fee_per_write(cfg: &FrameworkFeeConfig): u256 { cfg.base_fee_per_write }
843
+ public fun bytes_fee_per_byte(cfg: &FrameworkFeeConfig): u256 { cfg.bytes_fee_per_byte }
844
+ public fun pending_base_fee(cfg: &FrameworkFeeConfig): u256 { cfg.pending_base_fee }
845
+ public fun pending_bytes_fee(cfg: &FrameworkFeeConfig): u256 { cfg.pending_bytes_fee }
846
+ public fun fee_effective_at_ms(cfg: &FrameworkFeeConfig): u64 { cfg.fee_effective_at_ms }
847
+ public fun treasury(cfg: &FrameworkFeeConfig): address { cfg.treasury }
848
+ public fun pending_treasury(cfg: &FrameworkFeeConfig): address { cfg.pending_treasury }
849
+
850
+ public fun accepted_coin_type(cfg: &FrameworkFeeConfig): &Option<TypeName> {
851
+ &cfg.accepted_coin_type
852
+ }
853
+ public fun pending_coin_type(cfg: &FrameworkFeeConfig): &Option<TypeName> {
854
+ &cfg.pending_coin_type
855
+ }
856
+ public fun coin_type_effective_at_ms(cfg: &FrameworkFeeConfig): u64 {
857
+ cfg.coin_type_effective_at_ms
858
+ }
859
+
860
+ public(package) fun set_base_fee_per_write(cfg: &mut FrameworkFeeConfig, fee: u256) {
861
+ cfg.base_fee_per_write = fee;
862
+ }
863
+ public(package) fun set_bytes_fee_per_byte(cfg: &mut FrameworkFeeConfig, fee: u256) {
864
+ cfg.bytes_fee_per_byte = fee;
865
+ }
866
+ public(package) fun set_pending_base_fee(cfg: &mut FrameworkFeeConfig, fee: u256) {
867
+ cfg.pending_base_fee = fee;
868
+ }
869
+ public(package) fun set_pending_bytes_fee(cfg: &mut FrameworkFeeConfig, fee: u256) {
870
+ cfg.pending_bytes_fee = fee;
871
+ }
872
+ public(package) fun set_fee_effective_at_ms(cfg: &mut FrameworkFeeConfig, ts: u64) {
873
+ cfg.fee_effective_at_ms = ts;
874
+ }
875
+ public(package) fun set_treasury(cfg: &mut FrameworkFeeConfig, addr: address) {
876
+ cfg.treasury = addr;
877
+ }
878
+ public(package) fun set_pending_treasury(cfg: &mut FrameworkFeeConfig, addr: address) {
879
+ cfg.pending_treasury = addr;
880
+ }
881
+
882
+ public(package) fun set_accepted_coin_type(cfg: &mut FrameworkFeeConfig, t: TypeName) {
883
+ cfg.accepted_coin_type = option::some(t);
884
+ }
885
+ public(package) fun set_pending_coin_type(cfg: &mut FrameworkFeeConfig, t: Option<TypeName>) {
886
+ cfg.pending_coin_type = t;
887
+ }
888
+ public(package) fun set_coin_type_effective_at_ms(cfg: &mut FrameworkFeeConfig, ms: u64) {
889
+ cfg.coin_type_effective_at_ms = ms;
890
+ }
891
+
892
+ public(package) fun push_fee_history(
893
+ cfg: &mut FrameworkFeeConfig,
894
+ base_fee: u256,
895
+ bytes_fee: u256,
896
+ ts: u64,
897
+ ) {
898
+ cfg.fee_history.push_back(FeeHistoryEntry {
899
+ base_fee,
900
+ bytes_fee,
901
+ effective_from_ms: ts,
902
+ });
903
+ if (cfg.fee_history.length() > MAX_FEE_HISTORY) {
904
+ cfg.fee_history.remove(0);
905
+ };
906
+ }
907
+
908
+ /// Return the full fee-change history ring buffer (most recent at the back).
909
+ /// Useful for off-chain explorers and billing tools that need to determine
910
+ /// the applicable fee rate for a historical write operation.
911
+ public fun fee_history(cfg: &FrameworkFeeConfig): &vector<FeeHistoryEntry> {
912
+ &cfg.fee_history
913
+ }
914
+ /// Base fee that was committed in this history entry (MIST per write).
915
+ public fun fee_history_base_fee(e: &FeeHistoryEntry): u256 { e.base_fee }
916
+ /// Bytes fee that was committed in this history entry (MIST per byte).
917
+ public fun fee_history_bytes_fee(e: &FeeHistoryEntry): u256 { e.bytes_fee }
918
+ /// Epoch-ms timestamp from which this history entry's rates became effective.
919
+ public fun fee_history_effective_from_ms(e: &FeeHistoryEntry): u64 { e.effective_from_ms }
920
+
921
+ public fun is_fee_config_initialized(dh: &DappHub): bool {
922
+ dh.fee_config.treasury != @0x0
923
+ }
924
+
925
+ // ─── DappHub: framework config accessors ─────────────────────────────────
926
+
927
+ public fun get_config(dh: &DappHub): &FrameworkConfig {
928
+ &dh.config
929
+ }
930
+
931
+ public(package) fun get_config_mut(dh: &mut DappHub): &mut FrameworkConfig {
932
+ &mut dh.config
933
+ }
934
+
935
+ public fun default_free_credit(cfg: &FrameworkConfig): u256 { cfg.default_free_credit }
936
+ public fun default_free_credit_duration_ms(cfg: &FrameworkConfig): u64 { cfg.default_free_credit_duration_ms }
937
+ public fun framework_admin(cfg: &FrameworkConfig): address { cfg.admin }
938
+ public fun pending_framework_admin(cfg: &FrameworkConfig): address { cfg.pending_admin }
939
+
940
+ public(package) fun set_default_free_credit(cfg: &mut FrameworkConfig, amount: u256, duration_ms: u64) {
941
+ cfg.default_free_credit = amount;
942
+ cfg.default_free_credit_duration_ms = duration_ms;
943
+ }
944
+ public(package) fun set_framework_admin(cfg: &mut FrameworkConfig, addr: address) {
945
+ cfg.admin = addr;
946
+ }
947
+ public(package) fun set_pending_framework_admin(cfg: &mut FrameworkConfig, addr: address) {
948
+ cfg.pending_admin = addr;
949
+ }
950
+
951
+ public fun default_write_fee_dapp_share_bps(cfg: &FrameworkConfig): u64 {
952
+ cfg.default_write_fee_dapp_share_bps
953
+ }
954
+
955
+ public(package) fun set_default_write_fee_dapp_share_bps(cfg: &mut FrameworkConfig, val: u64) {
956
+ cfg.default_write_fee_dapp_share_bps = val;
957
+ }
958
+
959
+ public fun framework_max_write_limit(cfg: &FrameworkConfig): u64 {
960
+ cfg.framework_max_write_limit
961
+ }
962
+
963
+ public(package) fun set_framework_max_write_limit_cfg(cfg: &mut FrameworkConfig, val: u64) {
964
+ cfg.framework_max_write_limit = val;
965
+ }
966
+
967
+ public fun marketplace_fee_bps(cfg: &FrameworkConfig): u64 {
968
+ cfg.marketplace_fee_bps
969
+ }
970
+
971
+ public fun marketplace_dapp_share_bps(cfg: &FrameworkConfig): u64 {
972
+ cfg.marketplace_dapp_share_bps
973
+ }
974
+
975
+ public(package) fun set_marketplace_fee_bps(cfg: &mut FrameworkConfig, val: u64) {
976
+ cfg.marketplace_fee_bps = val;
977
+ }
978
+
979
+ public(package) fun set_marketplace_dapp_share_bps(cfg: &mut FrameworkConfig, val: u64) {
980
+ cfg.marketplace_dapp_share_bps = val;
981
+ }
982
+
983
+
984
+ // ─── DappHub: version accessors ──────────────────────────────────────────
985
+
986
+ public fun framework_version(dh: &DappHub): u64 { dh.version }
987
+
988
+ public(package) fun set_framework_version(dh: &mut DappHub, v: u64) {
989
+ dh.version = v;
990
+ }
991
+
992
+ // ─── DappStorage: metadata accessors ─────────────────────────────────────
993
+
994
+ public fun dapp_storage_dapp_key(ds: &DappStorage): String { ds.dapp_key }
995
+ public fun dapp_name(ds: &DappStorage): String { ds.name }
996
+ public fun dapp_description(ds: &DappStorage): String { ds.description }
997
+ public fun dapp_website_url(ds: &DappStorage): String { ds.website_url }
998
+ public fun dapp_cover_url(ds: &DappStorage): vector<String> { ds.cover_url }
999
+ public fun dapp_partners(ds: &DappStorage): vector<String> { ds.partners }
1000
+ public fun dapp_package_ids(ds: &DappStorage): vector<address> { ds.package_ids }
1001
+ public fun dapp_created_at(ds: &DappStorage): u64 { ds.created_at }
1002
+ public fun dapp_admin(ds: &DappStorage): address { ds.admin }
1003
+ public fun dapp_pending_admin(ds: &DappStorage): address { ds.pending_admin }
1004
+ public fun dapp_version(ds: &DappStorage): u32 { ds.version }
1005
+ public fun dapp_paused(ds: &DappStorage): bool { ds.paused }
1006
+
1007
+ public(package) fun set_dapp_name(ds: &mut DappStorage, v: String) { ds.name = v; }
1008
+ public(package) fun set_dapp_description(ds: &mut DappStorage, v: String) { ds.description = v; }
1009
+ public(package) fun set_dapp_website_url(ds: &mut DappStorage, v: String) { ds.website_url = v; }
1010
+ public(package) fun set_dapp_cover_url(ds: &mut DappStorage, v: vector<String>) { ds.cover_url = v; }
1011
+ public(package) fun set_dapp_partners(ds: &mut DappStorage, v: vector<String>) { ds.partners = v; }
1012
+ public(package) fun set_dapp_package_ids(ds: &mut DappStorage, v: vector<address>) { ds.package_ids = v; }
1013
+ public(package) fun set_dapp_admin(ds: &mut DappStorage, v: address) { ds.admin = v; }
1014
+ public(package) fun set_dapp_pending_admin(ds: &mut DappStorage, v: address) { ds.pending_admin = v; }
1015
+ public(package) fun set_dapp_version(ds: &mut DappStorage, v: u32) { ds.version = v; }
1016
+ public(package) fun set_dapp_paused(ds: &mut DappStorage, v: bool) { ds.paused = v; }
1017
+
1018
+ // ─── DappStorage: fee/credit accessors ───────────────────────────────────
1019
+
1020
+ public fun free_credit(ds: &DappStorage): u256 { ds.free_credit }
1021
+ public fun free_credit_expires_at(ds: &DappStorage): u64 { ds.free_credit_expires_at }
1022
+ public fun credit_pool(ds: &DappStorage): u256 { ds.credit_pool }
1023
+ public fun total_settled(ds: &DappStorage): u256 { ds.total_settled }
1024
+
1025
+ /// Returns the usable free credit at the given timestamp.
1026
+ /// Returns 0 if the free credit has expired (expires_at != 0 and now >= expires_at).
1027
+ public fun effective_free_credit(ds: &DappStorage, now_ms: u64): u256 {
1028
+ let expires = ds.free_credit_expires_at;
1029
+ if (expires == 0 || now_ms < expires) { ds.free_credit } else { 0 }
1030
+ }
1031
+
1032
+ public(package) fun set_free_credit(ds: &mut DappStorage, amount: u256, expires_at: u64) {
1033
+ ds.free_credit = amount;
1034
+ ds.free_credit_expires_at = expires_at;
1035
+ }
1036
+
1037
+ public(package) fun deduct_free_credit(ds: &mut DappStorage, amount: u256) {
1038
+ ds.free_credit = ds.free_credit - amount;
1039
+ }
1040
+
1041
+ public(package) fun add_credit(ds: &mut DappStorage, amount: u256) {
1042
+ ds.credit_pool = ds.credit_pool + amount;
1043
+ }
1044
+
1045
+ public(package) fun deduct_credit(ds: &mut DappStorage, amount: u256) {
1046
+ ds.credit_pool = ds.credit_pool - amount;
1047
+ }
1048
+
1049
+ public(package) fun add_total_settled(ds: &mut DappStorage, count: u256) {
1050
+ ds.total_settled = ds.total_settled + count;
1051
+ }
1052
+
1053
+ // ─── DappStorage: per-DApp fee rate accessors ─────────────────────────────
1054
+
1055
+ public fun dapp_base_fee_per_write(ds: &DappStorage): u256 { ds.base_fee_per_write }
1056
+ public fun dapp_bytes_fee_per_byte(ds: &DappStorage): u256 { ds.bytes_fee_per_byte }
1057
+
1058
+ public(package) fun set_dapp_base_fee_per_write(ds: &mut DappStorage, fee: u256) {
1059
+ ds.base_fee_per_write = fee;
1060
+ }
1061
+ public(package) fun set_dapp_bytes_fee_per_byte(ds: &mut DappStorage, fee: u256) {
1062
+ ds.bytes_fee_per_byte = fee;
1063
+ }
1064
+
1065
+ // ─── DappStorage: settlement mode accessors ───────────────────────────────
1066
+
1067
+ public fun settlement_mode(ds: &DappStorage): u8 { ds.settlement_mode }
1068
+ public fun dapp_write_fee_share_bps(ds: &DappStorage): u64 { ds.write_fee_dapp_share_bps }
1069
+
1070
+ public(package) fun set_settlement_mode(ds: &mut DappStorage, mode: u8) {
1071
+ ds.settlement_mode = mode;
1072
+ }
1073
+ public(package) fun set_write_fee_dapp_share_bps(ds: &mut DappStorage, bps: u64) {
1074
+ ds.write_fee_dapp_share_bps = bps;
1075
+ }
1076
+
1077
+ // ─── DappRevenueKey — dynamic field key for DApp revenue balance ──────────
1078
+
1079
+ /// Key for the DApp revenue Balance<CoinType> stored as a dynamic field on DappStorage.
1080
+ public struct DappRevenueKey<phantom CoinType> has copy, drop, store {}
1081
+
1082
+ public(package) fun add_dapp_revenue<CoinType>(ds: &mut DappStorage, bal: Balance<CoinType>) {
1083
+ let key = DappRevenueKey<CoinType> {};
1084
+ if (!dynamic_field::exists_(&ds.id, key)) {
1085
+ dynamic_field::add(&mut ds.id, key, bal);
1086
+ } else {
1087
+ let stored: &mut Balance<CoinType> = dynamic_field::borrow_mut(&mut ds.id, key);
1088
+ balance::join(stored, bal);
1089
+ };
1090
+ }
1091
+
1092
+ public(package) fun take_dapp_revenue<CoinType>(ds: &mut DappStorage): Balance<CoinType> {
1093
+ let key = DappRevenueKey<CoinType> {};
1094
+ if (!dynamic_field::exists_(&ds.id, key)) {
1095
+ balance::zero<CoinType>()
1096
+ } else {
1097
+ let stored: &mut Balance<CoinType> = dynamic_field::borrow_mut(&mut ds.id, key);
1098
+ balance::withdraw_all(stored)
1099
+ }
1100
+ }
1101
+
1102
+ public fun dapp_revenue_balance<CoinType>(ds: &DappStorage): u64 {
1103
+ let key = DappRevenueKey<CoinType> {};
1104
+ if (!dynamic_field::exists_(&ds.id, key)) {
1105
+ 0
1106
+ } else {
1107
+ balance::value(dynamic_field::borrow<DappRevenueKey<CoinType>, Balance<CoinType>>(&ds.id, key))
1108
+ }
1109
+ }
1110
+
1111
+ // ─── UserStorage: accessors ───────────────────────────────────────────────
1112
+
1113
+ public fun user_storage_dapp_key(us: &UserStorage): String { us.dapp_key }
1114
+ public fun canonical_owner(us: &UserStorage): address { us.canonical_owner }
1115
+ public fun session_key(us: &UserStorage): address { us.session_key }
1116
+ public fun session_expires_at(us: &UserStorage): u64 { us.session_expires_at }
1117
+ public fun write_count(us: &UserStorage): u64 { us.write_count }
1118
+ public fun settled_count(us: &UserStorage): u64 { us.settled_count }
1119
+ public fun write_bytes(us: &UserStorage): u256 { us.write_bytes }
1120
+ public fun settled_bytes(us: &UserStorage): u256 { us.settled_bytes }
1121
+ public fun unsettled_count(us: &UserStorage): u64 { us.write_count - us.settled_count }
1122
+ public fun unsettled_bytes(us: &UserStorage): u256 { us.write_bytes - us.settled_bytes }
1123
+ public fun user_write_limit(us: &UserStorage): u64 { us.write_limit }
1124
+
1125
+ public(package) fun set_user_write_limit(us: &mut UserStorage, val: u64) {
1126
+ us.write_limit = val;
1127
+ }
1128
+
1129
+ /// Compute the monetary value of unsettled writes using the provided fee rates.
1130
+ /// Useful for off-chain monitoring tools and explorers.
1131
+ /// Note: the framework write-limit guard uses UserStorage.write_limit (a write count),
1132
+ /// not this monetary value. This function is informational only.
1133
+ public fun compute_unsettled_charge(
1134
+ us: &UserStorage,
1135
+ base_fee: u256,
1136
+ bytes_fee: u256,
1137
+ ): u256 {
1138
+ base_fee * ((us.write_count - us.settled_count) as u256)
1139
+ + bytes_fee * (us.write_bytes - us.settled_bytes)
1140
+ }
1141
+
1142
+ /// Returns true if `sender` is allowed to write to this UserStorage right now.
1143
+ ///
1144
+ /// Authorised callers:
1145
+ /// - canonical_owner: always allowed.
1146
+ /// - session_key: allowed when session_key != @0x0, sender matches, and
1147
+ /// the session has not yet expired.
1148
+ ///
1149
+ /// `now_ms` should be ctx.epoch_timestamp_ms() (≈24h granularity on mainnet/
1150
+ /// testnet, ≈1h on devnet). Session expiry is therefore a soft deadline with
1151
+ /// up to one epoch of tolerance. The canonical owner can always revoke early
1152
+ /// via deactivate_session.
1153
+ ///
1154
+ /// NOTE: In normal usage, session_expires_at is always > 0 because
1155
+ /// activate_session enforces a minimum duration (MIN_SESSION_DURATION_MS).
1156
+ /// The only way to reach session_expires_at == 0 with a non-zero session_key
1157
+ /// is via test helpers, which represents a "never expires" state used only in
1158
+ /// tests.
1159
+ public fun is_write_authorized(us: &UserStorage, sender: address, now_ms: u64): bool {
1160
+ if (sender == us.canonical_owner) { return true };
1161
+ if (us.session_key == @0x0) { return false };
1162
+ if (sender != us.session_key) { return false };
1163
+ if (us.session_expires_at > 0 && now_ms >= us.session_expires_at) { return false };
1164
+ true
1165
+ }
1166
+
1167
+ public(package) fun set_session_key(us: &mut UserStorage, key: address) {
1168
+ us.session_key = key;
1169
+ }
1170
+
1171
+ public(package) fun set_session_expires_at(us: &mut UserStorage, ts: u64) {
1172
+ us.session_expires_at = ts;
1173
+ }
1174
+
1175
+ /// Clear the active session (set key to @0x0 and expiry to 0).
1176
+ public(package) fun clear_session(us: &mut UserStorage) {
1177
+ us.session_key = @0x0;
1178
+ us.session_expires_at = 0;
1179
+ }
1180
+
1181
+ public(package) fun increment_write_count(us: &mut UserStorage) {
1182
+ us.write_count = us.write_count + 1;
1183
+ }
1184
+
1185
+ /// Accumulate `bytes` into write_bytes (called for on-chain writes only).
1186
+ public(package) fun add_write_bytes(us: &mut UserStorage, bytes: u256) {
1187
+ us.write_bytes = us.write_bytes + bytes;
1188
+ }
1189
+
1190
+ public(package) fun add_settled_count(us: &mut UserStorage, count: u64) {
1191
+ us.settled_count = us.settled_count + count;
1192
+ }
1193
+
1194
+ public(package) fun add_settled_bytes(us: &mut UserStorage, bytes: u256) {
1195
+ us.settled_bytes = us.settled_bytes + bytes;
1196
+ }
1197
+
1198
+ public(package) fun set_settled_to_write(us: &mut UserStorage) {
1199
+ us.settled_count = us.write_count;
1200
+ us.settled_bytes = us.write_bytes;
1201
+ }
1202
+
1203
+ // ─── Global record operations (stored on DappStorage dynamic fields) ──────
1204
+ //
1205
+ // Storage layout — per-field model:
1206
+ // sentinel dynamic_field key = record_key value type = bool
1207
+ // field data dynamic_field key = [record_key, field_name] value type = vector<u8>
1208
+ //
1209
+ // record_key = [TABLE_NAME, key_field0, key_field1, ...]
1210
+ //
1211
+ // This makes field access order-independent: reordering fields in
1212
+ // dubhe.config.ts has zero effect on stored data.
1213
+
1214
+ public(package) fun set_global_record<DappKey: copy + drop>(
1215
+ ds: &mut DappStorage,
1216
+ mut key: vector<vector<u8>>,
1217
+ field_names: vector<vector<u8>>,
1218
+ values: vector<vector<u8>>,
1219
+ offchain: bool,
57
1220
  ) {
58
- let dapp_key = type_name::get<DappKey>().into_string();
1221
+ let len = field_names.length();
1222
+ error::length_mismatch(len == values.length());
1223
+ let dapp_key_str = type_name::with_defining_ids<DappKey>().into_string();
59
1224
  if (offchain) {
60
- emit_store_set_record(dapp_key, account, key, value);
1225
+ emit_store_set_record(dapp_key_str, dapp_key_str, key, values);
61
1226
  return
62
1227
  };
63
- let account_key = new_account_key<DappKey>(account);
64
- if (!self.accounts.contains(account_key)) {
65
- let mut account_data = new_account_data(ctx);
66
- dynamic_field::add(&mut account_data.id, key, value);
67
- self.accounts.add(account_key, account_data);
68
- } else {
69
- let account_data = self.accounts.borrow_mut(account_key);
70
- if (dynamic_field::exists_(&account_data.id, key)) {
71
- *dynamic_field::borrow_mut(&mut account_data.id, key) = value;
1228
+ // Write sentinel to mark record as existing.
1229
+ if (!dynamic_field::exists_(&ds.id, key)) {
1230
+ dynamic_field::add(&mut ds.id, key, true);
1231
+ };
1232
+ // Write each field at [key..., field_name]. Mutate key in-place to avoid
1233
+ // allocating a separate field_key vector on every iteration.
1234
+ let mut i = 0u64;
1235
+ while (i < len) {
1236
+ let fv = *values.borrow(i);
1237
+ key.push_back(*field_names.borrow(i));
1238
+ if (dynamic_field::exists_(&ds.id, key)) {
1239
+ *dynamic_field::borrow_mut<vector<vector<u8>>, vector<u8>>(&mut ds.id, key) = fv;
72
1240
  } else {
73
- dynamic_field::add(&mut account_data.id, key, value);
1241
+ dynamic_field::add(&mut ds.id, key, fv);
74
1242
  };
1243
+ key.pop_back();
1244
+ i = i + 1;
75
1245
  };
76
- std::debug::print(&std::ascii::string(b"set_record"));
77
- std::debug::print(&key);
78
- std::debug::print(&value);
79
- emit_store_set_record(dapp_key, account, key, value);
1246
+ emit_store_set_record(dapp_key_str, dapp_key_str, key, values);
80
1247
  }
81
1248
 
82
- /// Set a field
83
- public(package) fun set_field<DappKey: copy + drop>(
84
- self: &mut DappHub,
85
- _: DappKey,
86
- key: vector<vector<u8>>,
87
- field_index: u8,
1249
+ /// Update a single named field within an existing record.
1250
+ /// Aborts with EInvalidKey if the sentinel (record) does not exist callers
1251
+ /// must call set_global_record first to create the record.
1252
+ public(package) fun set_global_field<DappKey: copy + drop>(
1253
+ ds: &mut DappStorage,
1254
+ mut key: vector<vector<u8>>,
1255
+ field_name: vector<u8>,
88
1256
  field_value: vector<u8>,
89
- account: String,
90
1257
  ) {
91
- let dapp_key = type_name::get<DappKey>().into_string();
92
- let account_key = new_account_key<DappKey>(account);
93
- let account_data = self.accounts.borrow_mut(account_key);
94
- let value = dynamic_field::borrow_mut<vector<vector<u8>>, vector<vector<u8>>>(&mut account_data.id, key);
95
- *value.borrow_mut(field_index as u64) = field_value;
96
- emit_store_set_record(dapp_key, account, key, *value)
1258
+ // Require sentinel to exist: prevent ghost fields that have no parent record.
1259
+ error::invalid_key(dynamic_field::exists_(&ds.id, key));
1260
+ let dapp_key_str = type_name::with_defining_ids<DappKey>().into_string();
1261
+ key.push_back(field_name);
1262
+ if (dynamic_field::exists_(&ds.id, key)) {
1263
+ *dynamic_field::borrow_mut<vector<vector<u8>>, vector<u8>>(&mut ds.id, key) = field_value;
1264
+ } else {
1265
+ dynamic_field::add(&mut ds.id, key, field_value);
1266
+ };
1267
+ key.pop_back();
1268
+ emit_store_set_field(dapp_key_str, dapp_key_str, key, field_name, field_value);
97
1269
  }
98
1270
 
99
- /// Get a record
100
- public fun get_record<DappKey: copy + drop>(
101
- self: &DappHub,
102
- account: String,
103
- key: vector<vector<u8>>
1271
+ #[allow(unused_type_parameter)]
1272
+ public fun get_global_field<DappKey: copy + drop>(
1273
+ ds: &DappStorage,
1274
+ mut key: vector<vector<u8>>,
1275
+ field_name: vector<u8>,
104
1276
  ): vector<u8> {
105
- let account_key = new_account_key<DappKey>(account);
106
- assert!(self.accounts.contains(account_key), EInvalidKey);
107
- let account_data = self.accounts.borrow(account_key);
108
- assert!(dynamic_field::exists_(&account_data.id, key), EInvalidKey);
109
- let value = dynamic_field::borrow(&account_data.id, key);
110
- std::debug::print(&key);
111
- std::debug::print(value);
112
- let mut result = vector::empty();
113
- let mut i = 0;
114
- while (i < vector::length(value)) {
115
- let value = vector::borrow(value, i);
116
- std::debug::print(value);
117
- std::debug::print(&account);
118
- vector::append(&mut result, *value);
1277
+ key.push_back(field_name);
1278
+ error::invalid_key(dynamic_field::exists_(&ds.id, key));
1279
+ *dynamic_field::borrow<vector<vector<u8>>, vector<u8>>(&ds.id, key)
1280
+ }
1281
+
1282
+ #[allow(unused_type_parameter)]
1283
+ public fun has_global_record<DappKey: copy + drop>(
1284
+ ds: &DappStorage,
1285
+ key: vector<vector<u8>>,
1286
+ ): bool {
1287
+ dynamic_field::exists_(&ds.id, key)
1288
+ }
1289
+
1290
+ public fun ensure_has_global_record<DappKey: copy + drop>(
1291
+ ds: &DappStorage,
1292
+ key: vector<vector<u8>>,
1293
+ ) {
1294
+ error::invalid_key(has_global_record<DappKey>(ds, key));
1295
+ }
1296
+
1297
+ #[allow(unused_type_parameter)]
1298
+ public fun ensure_has_not_global_record<DappKey: copy + drop>(
1299
+ ds: &DappStorage,
1300
+ key: vector<vector<u8>>,
1301
+ ) {
1302
+ error::invalid_key(!has_global_record<DappKey>(ds, key));
1303
+ }
1304
+
1305
+ /// Delete a record and all its named fields in a single call.
1306
+ /// Emits DeleteRecord, then removes each field dynamic field followed by the sentinel.
1307
+ ///
1308
+ /// IMPORTANT — orphaned-field warning:
1309
+ /// `field_names` must enumerate EVERY field name ever stored in this record across
1310
+ /// all schema versions. Missing field names leave orphaned dynamic fields.
1311
+ /// Always regenerate delete functions after a schema upgrade.
1312
+ public(package) fun delete_global_record<DappKey: copy + drop>(
1313
+ ds: &mut DappStorage,
1314
+ mut key: vector<vector<u8>>,
1315
+ field_names: vector<vector<u8>>,
1316
+ ) {
1317
+ let dapp_key_str = type_name::with_defining_ids<DappKey>().into_string();
1318
+ error::invalid_key(dynamic_field::exists_(&ds.id, key));
1319
+ emit_store_delete_record(dapp_key_str, dapp_key_str, key);
1320
+ // Remove each per-field dynamic field before the sentinel.
1321
+ let len = field_names.length();
1322
+ let mut i = 0u64;
1323
+ while (i < len) {
1324
+ key.push_back(*field_names.borrow(i));
1325
+ if (dynamic_field::exists_(&ds.id, key)) {
1326
+ let _: vector<u8> = dynamic_field::remove(&mut ds.id, key);
1327
+ };
1328
+ key.pop_back();
119
1329
  i = i + 1;
120
1330
  };
121
- result
1331
+ let _: bool = dynamic_field::remove(&mut ds.id, key);
122
1332
  }
123
1333
 
124
- /// Get a field
125
- public fun get_field<DappKey: copy + drop>(
126
- self: &DappHub,
127
- account: String,
128
- key: vector<vector<u8>>,
129
- field_index: u8
1334
+ /// Delete a single named field. Silently skips if the field does not exist.
1335
+ #[allow(unused_type_parameter)]
1336
+ public(package) fun delete_global_field<DappKey: copy + drop>(
1337
+ ds: &mut DappStorage,
1338
+ mut key: vector<vector<u8>>,
1339
+ field_name: vector<u8>,
1340
+ ) {
1341
+ let dapp_key_str = type_name::with_defining_ids<DappKey>().into_string();
1342
+ let account = dapp_key_str;
1343
+ key.push_back(field_name);
1344
+ if (dynamic_field::exists_(&ds.id, key)) {
1345
+ let _: vector<u8> = dynamic_field::remove(&mut ds.id, key);
1346
+ key.pop_back();
1347
+ emit_store_delete_field(dapp_key_str, account, key, field_name);
1348
+ } else {
1349
+ key.pop_back();
1350
+ };
1351
+ }
1352
+
1353
+ // ─── User record operations (stored on UserStorage dynamic fields) ─────────
1354
+ //
1355
+ // Same per-field layout as global records, but stored on UserStorage.id.
1356
+
1357
+ public(package) fun set_user_record<DappKey: copy + drop>(
1358
+ us: &mut UserStorage,
1359
+ mut key: vector<vector<u8>>,
1360
+ field_names: vector<vector<u8>>,
1361
+ values: vector<vector<u8>>,
1362
+ offchain: bool,
1363
+ ) {
1364
+ let len = field_names.length();
1365
+ error::length_mismatch(len == values.length());
1366
+ let dapp_key_str = type_name::with_defining_ids<DappKey>().into_string();
1367
+ let account = us.canonical_owner.to_ascii_string();
1368
+ if (offchain) {
1369
+ emit_store_set_record(dapp_key_str, account, key, values);
1370
+ return
1371
+ };
1372
+ if (!dynamic_field::exists_(&us.id, key)) {
1373
+ dynamic_field::add(&mut us.id, key, true);
1374
+ };
1375
+ let mut i = 0u64;
1376
+ while (i < len) {
1377
+ let fv = *values.borrow(i);
1378
+ key.push_back(*field_names.borrow(i));
1379
+ if (dynamic_field::exists_(&us.id, key)) {
1380
+ *dynamic_field::borrow_mut<vector<vector<u8>>, vector<u8>>(&mut us.id, key) = fv;
1381
+ } else {
1382
+ dynamic_field::add(&mut us.id, key, fv);
1383
+ };
1384
+ key.pop_back();
1385
+ i = i + 1;
1386
+ };
1387
+ emit_store_set_record(dapp_key_str, account, key, values);
1388
+ }
1389
+
1390
+ /// Update a single named field within an existing record.
1391
+ /// Aborts with EInvalidKey if the sentinel (record) does not exist.
1392
+ public(package) fun set_user_field<DappKey: copy + drop>(
1393
+ us: &mut UserStorage,
1394
+ mut key: vector<vector<u8>>,
1395
+ field_name: vector<u8>,
1396
+ field_value: vector<u8>,
1397
+ ) {
1398
+ // Require sentinel to exist: prevent ghost fields that have no parent record.
1399
+ error::invalid_key(dynamic_field::exists_(&us.id, key));
1400
+ let dapp_key_str = type_name::with_defining_ids<DappKey>().into_string();
1401
+ let account = us.canonical_owner.to_ascii_string();
1402
+ key.push_back(field_name);
1403
+ if (dynamic_field::exists_(&us.id, key)) {
1404
+ *dynamic_field::borrow_mut<vector<vector<u8>>, vector<u8>>(&mut us.id, key) = field_value;
1405
+ } else {
1406
+ dynamic_field::add(&mut us.id, key, field_value);
1407
+ };
1408
+ key.pop_back();
1409
+ emit_store_set_field(dapp_key_str, account, key, field_name, field_value);
1410
+ }
1411
+
1412
+ #[allow(unused_type_parameter)]
1413
+ public fun get_user_field<DappKey: copy + drop>(
1414
+ us: &UserStorage,
1415
+ mut key: vector<vector<u8>>,
1416
+ field_name: vector<u8>,
130
1417
  ): vector<u8> {
131
- let account_key = new_account_key<DappKey>(account);
132
- assert!(self.accounts.contains(account_key), EInvalidKey);
133
- let account_data = self.accounts.borrow(account_key);
134
- assert!(dynamic_field::exists_(&account_data.id, key), EInvalidKey);
135
- let value = dynamic_field::borrow(&account_data.id, key);
136
- let field = vector::borrow(value, field_index as u64);
137
- *field
138
- }
139
-
140
- public fun has_record<DappKey: copy + drop>(
141
- self: &DappHub,
142
- account: String,
143
- key: vector<vector<u8>>
1418
+ key.push_back(field_name);
1419
+ error::invalid_key(dynamic_field::exists_(&us.id, key));
1420
+ *dynamic_field::borrow<vector<vector<u8>>, vector<u8>>(&us.id, key)
1421
+ }
1422
+
1423
+ #[allow(unused_type_parameter)]
1424
+ public fun has_user_record<DappKey: copy + drop>(
1425
+ us: &UserStorage,
1426
+ key: vector<vector<u8>>,
144
1427
  ): bool {
145
- let account_key = new_account_key<DappKey>(account);
146
- if (!self.accounts.contains(account_key)) {
147
- return false
148
- };
149
- let account_data = self.accounts.borrow(account_key);
150
- dynamic_field::exists_(&account_data.id, key)
1428
+ dynamic_field::exists_(&us.id, key)
151
1429
  }
152
1430
 
153
- public(package) fun delete_record<DappKey: copy + drop>(
154
- self: &mut DappHub,
155
- _: DappKey,
1431
+ public fun ensure_has_user_record<DappKey: copy + drop>(
1432
+ us: &UserStorage,
156
1433
  key: vector<vector<u8>>,
157
- account: String,
158
- ): vector<vector<u8>> {
159
- let dapp_key = type_name::get<DappKey>().into_string();
160
- let account_key = new_account_key<DappKey>(account);
161
- assert!(self.accounts.contains(account_key), EInvalidKey);
162
- let account_data = self.accounts.borrow_mut(account_key);
163
- emit_store_delete_record(dapp_key, account, key);
164
- dynamic_field::remove(&mut account_data.id, key)
1434
+ ) {
1435
+ error::invalid_key(has_user_record<DappKey>(us, key));
165
1436
  }
166
1437
 
167
- public fun ensure_has_record<DappKey: copy + drop>(
168
- self: &DappHub,
169
- account: String,
170
- key: vector<vector<u8>>
1438
+ public fun ensure_has_not_user_record<DappKey: copy + drop>(
1439
+ us: &UserStorage,
1440
+ key: vector<vector<u8>>,
171
1441
  ) {
172
- assert!(has_record<DappKey>(self, account, key), EInvalidKey);
1442
+ error::invalid_key(!has_user_record<DappKey>(us, key));
173
1443
  }
174
1444
 
175
- public fun ensure_has_not_record<DappKey: copy + drop>(
176
- self: &DappHub,
177
- account: String,
178
- key: vector<vector<u8>>
1445
+ /// Delete a user record and all its named fields in a single call.
1446
+ ///
1447
+ /// IMPORTANT — orphaned-field warning:
1448
+ /// `field_names` must enumerate EVERY field name that was ever stored in this
1449
+ /// record across all schema versions. Fields not listed here are silently skipped
1450
+ /// and left as orphaned dynamic fields on the object, wasting storage and
1451
+ /// making future record recreation inconsistent.
1452
+ /// Always regenerate delete functions after a schema upgrade (new fields added).
1453
+ public(package) fun delete_user_record<DappKey: copy + drop>(
1454
+ us: &mut UserStorage,
1455
+ mut key: vector<vector<u8>>,
1456
+ field_names: vector<vector<u8>>,
179
1457
  ) {
180
- assert!(!has_record<DappKey>(self, account, key), EInvalidKey);
1458
+ let dapp_key_str = type_name::with_defining_ids<DappKey>().into_string();
1459
+ let account = us.canonical_owner.to_ascii_string();
1460
+ error::invalid_key(dynamic_field::exists_(&us.id, key));
1461
+ emit_store_delete_record(dapp_key_str, account, key);
1462
+ let len = field_names.length();
1463
+ let mut i = 0u64;
1464
+ while (i < len) {
1465
+ key.push_back(*field_names.borrow(i));
1466
+ if (dynamic_field::exists_(&us.id, key)) {
1467
+ let _: vector<u8> = dynamic_field::remove(&mut us.id, key);
1468
+ };
1469
+ key.pop_back();
1470
+ i = i + 1;
1471
+ };
1472
+ let _: bool = dynamic_field::remove(&mut us.id, key);
181
1473
  }
182
1474
 
183
- fun init(ctx: &mut TxContext) {
184
- sui::transfer::public_share_object(
185
- new(ctx)
1475
+ /// Delete a single named field. Silently skips if the field does not exist.
1476
+ #[allow(unused_type_parameter)]
1477
+ public(package) fun delete_user_field<DappKey: copy + drop>(
1478
+ us: &mut UserStorage,
1479
+ mut key: vector<vector<u8>>,
1480
+ field_name: vector<u8>,
1481
+ ) {
1482
+ let dapp_key_str = type_name::with_defining_ids<DappKey>().into_string();
1483
+ let account = us.canonical_owner.to_ascii_string();
1484
+ key.push_back(field_name);
1485
+ if (dynamic_field::exists_(&us.id, key)) {
1486
+ let _: vector<u8> = dynamic_field::remove(&mut us.id, key);
1487
+ key.pop_back();
1488
+ emit_store_delete_field(dapp_key_str, account, key, field_name);
1489
+ } else {
1490
+ key.pop_back();
1491
+ };
1492
+ }
1493
+
1494
+ // ─── Genesis registry (one-shot guard per DApp) ───────────────────────────
1495
+ //
1496
+ // Stored as a dynamic field on DappHub keyed by the phantom DappKey type.
1497
+ // Using a phantom type parameter (rather than a String) avoids the type-name
1498
+ // conversion and is checked at compile time.
1499
+ //
1500
+ // Prevents genesis::run → create_dapp from being called more than once per
1501
+ // DApp, even if someone rewrites genesis.move to drop the guard.
1502
+
1503
+ public struct DappGenesisKey<phantom DappKey> has copy, drop, store {}
1504
+
1505
+ /// Mark that genesis has been executed for the given DApp type.
1506
+ public(package) fun set_dapp_genesis_done<DappKey: copy + drop>(dh: &mut DappHub) {
1507
+ dynamic_field::add(&mut dh.id, DappGenesisKey<DappKey> {}, true);
1508
+ }
1509
+
1510
+ /// Returns true iff genesis has already been executed for the given DApp type.
1511
+ public fun is_dapp_genesis_done<DappKey: copy + drop>(dh: &DappHub): bool {
1512
+ dynamic_field::exists_(&dh.id, DappGenesisKey<DappKey> {})
1513
+ }
1514
+
1515
+ // ─── UserStorage registry (one-per-address enforcement) ──────────────────
1516
+
1517
+ /// Mark that `owner` has a UserStorage for this DApp.
1518
+ /// Called exactly once per address, inside create_user_storage.
1519
+ public(package) fun register_user_storage(ds: &mut DappStorage, owner: address) {
1520
+ dynamic_field::add(&mut ds.id, UserStorageRegistryKey { owner }, true);
1521
+ }
1522
+
1523
+ /// Returns true iff `owner` already has a registered UserStorage for this DApp.
1524
+ public fun has_registered_user_storage(ds: &DappStorage, owner: address): bool {
1525
+ dynamic_field::exists_(&ds.id, UserStorageRegistryKey { owner })
1526
+ }
1527
+
1528
+ // ─── Listing — marketplace protocol shared object ─────────────────────────
1529
+ //
1530
+ // A Listing holds a BCS-encoded item record taken atomically from a seller's
1531
+ // UserStorage. It is a shared object so any buyer can reference it.
1532
+ //
1533
+ // Key properties:
1534
+ // • No `copy` or `drop` — Move linear types guarantee exactly one owner.
1535
+ // • Consumed atomically on buy or cancel_listing — no data duplication.
1536
+ // • `listed_until: None` means the listing never auto-expires.
1537
+
1538
+ public struct Listing<phantom CoinType> has key {
1539
+ id: UID,
1540
+ /// Field values taken from seller's UserStorage (one inner vector per field, each BCS-encoded).
1541
+ record_data: vector<vector<u8>>,
1542
+ /// The resource table name this record belongs to (e.g. b"weapon").
1543
+ record_type: vector<u8>,
1544
+ /// The item's key tuple identifying the specific record slot.
1545
+ record_key: vector<vector<u8>>,
1546
+ /// The field names stored in this record (for restoring on cancel).
1547
+ field_names: vector<vector<u8>>,
1548
+ /// Seller address — gets item back on cancel or funds on buy.
1549
+ seller: address,
1550
+ /// Price in CoinType units.
1551
+ price: u64,
1552
+ /// Optional expiry (epoch ms). None = never auto-expires.
1553
+ listed_until: Option<u64>,
1554
+ /// The DApp this listing belongs to (type name string of DappKey).
1555
+ dapp_key: std::ascii::String,
1556
+ /// true for fungible resources; false for unique items.
1557
+ /// Guards restore_record to prevent it being called on fungible listings
1558
+ /// (which use cancel_fungible_listing for additive-merge semantics).
1559
+ is_fungible: bool,
1560
+ }
1561
+
1562
+ public(package) fun new_listing<CoinType>(
1563
+ record_data: vector<vector<u8>>,
1564
+ record_type: vector<u8>,
1565
+ record_key: vector<vector<u8>>,
1566
+ field_names: vector<vector<u8>>,
1567
+ seller: address,
1568
+ price: u64,
1569
+ listed_until: Option<u64>,
1570
+ dapp_key_str: std::ascii::String,
1571
+ is_fungible: bool,
1572
+ ctx: &mut TxContext,
1573
+ ): Listing<CoinType> {
1574
+ Listing {
1575
+ id: object::new(ctx),
1576
+ record_data,
1577
+ record_type,
1578
+ record_key,
1579
+ field_names,
1580
+ seller,
1581
+ price,
1582
+ listed_until,
1583
+ dapp_key: dapp_key_str,
1584
+ is_fungible,
1585
+ }
1586
+ }
1587
+
1588
+ public fun listing_record_data<CoinType>(l: &Listing<CoinType>): &vector<vector<u8>> { &l.record_data }
1589
+ public fun listing_record_type<CoinType>(l: &Listing<CoinType>): &vector<u8> { &l.record_type }
1590
+ public fun listing_record_key<CoinType>(l: &Listing<CoinType>): &vector<vector<u8>> { &l.record_key }
1591
+ public fun listing_field_names<CoinType>(l: &Listing<CoinType>): &vector<vector<u8>> { &l.field_names }
1592
+ public fun listing_id<CoinType>(l: &Listing<CoinType>): &UID { &l.id }
1593
+ public fun listing_seller<CoinType>(l: &Listing<CoinType>): address { l.seller }
1594
+ public fun listing_price<CoinType>(l: &Listing<CoinType>): u64 { l.price }
1595
+ public fun listing_listed_until<CoinType>(l: &Listing<CoinType>): Option<u64> { l.listed_until }
1596
+ public fun listing_dapp_key<CoinType>(l: &Listing<CoinType>): std::ascii::String { l.dapp_key }
1597
+ public fun listing_is_fungible<CoinType>(l: &Listing<CoinType>): bool { l.is_fungible }
1598
+
1599
+ public fun is_listing_expired<CoinType>(l: &Listing<CoinType>, now_ms: u64): bool {
1600
+ if (option::is_none(&l.listed_until)) { return false };
1601
+ now_ms >= *option::borrow(&l.listed_until)
1602
+ }
1603
+
1604
+ /// Destructure a Listing, returning all fields for further processing.
1605
+ /// Called by buy / cancel_listing / expire_listing entry functions.
1606
+ public(package) fun destroy_listing<CoinType>(l: Listing<CoinType>): (
1607
+ vector<vector<u8>>, vector<u8>, vector<vector<u8>>, vector<vector<u8>>,
1608
+ address, u64, Option<u64>, std::ascii::String,
1609
+ ) {
1610
+ let Listing {
1611
+ id,
1612
+ record_data,
1613
+ record_type,
1614
+ record_key,
1615
+ field_names,
1616
+ seller,
1617
+ price,
1618
+ listed_until,
1619
+ dapp_key,
1620
+ is_fungible: _,
1621
+ } = l;
1622
+ object::delete(id);
1623
+ (record_data, record_type, record_key, field_names, seller, price, listed_until, dapp_key)
1624
+ }
1625
+
1626
+ /// Share a freshly created Listing as a shared object.
1627
+ public(package) fun share_listing<CoinType>(l: Listing<CoinType>) {
1628
+ sui::transfer::share_object(l);
1629
+ }
1630
+
1631
+ // ─── Share helper ─────────────────────────────────────────────────────────
1632
+
1633
+ /// Publish UserStorage as a shared object.
1634
+ /// Must be called from the defining module since UserStorage lacks `store`.
1635
+ /// After sharing, any transaction can reference the object; write access is
1636
+ /// controlled by is_write_authorized.
1637
+ public(package) fun share_user_storage(us: UserStorage) {
1638
+ sui::transfer::share_object(us);
1639
+ }
1640
+
1641
+ // ─── Fee state snapshot ───────────────────────────────────────────────────
1642
+
1643
+ /// Emit a dedicated DappFeeStateUpdated event that snapshots the current
1644
+ /// credit-pool / fee-rate state of this DApp. The indexer handles this via
1645
+ /// the hardcoded non-schema-backed path (like marketplace / session events),
1646
+ /// so no entry in dubhe.config.json is required.
1647
+ /// Called by dapp_system after every operation that mutates credit/fee-rate state.
1648
+ public(package) fun emit_fee_state_record<DappKey: copy + drop>(ds: &DappStorage) {
1649
+ let dapp_key_str = type_name::with_defining_ids<DappKey>().into_string();
1650
+ emit_dapp_fee_state_updated(
1651
+ dapp_key_str,
1652
+ ds.base_fee_per_write,
1653
+ ds.bytes_fee_per_byte,
1654
+ ds.free_credit,
1655
+ ds.credit_pool,
1656
+ ds.total_settled,
186
1657
  );
187
1658
  }
188
1659
 
1660
+ /// Emit a dedicated DappRevenueStateUpdated event that snapshots the current
1661
+ /// pending-revenue balance of this DApp. The indexer handles this via the
1662
+ /// hardcoded non-schema-backed path.
1663
+ /// Called by dapp_system after every operation that changes the revenue balance.
1664
+ public(package) fun emit_revenue_state_record<DappKey: copy + drop, CoinType>(
1665
+ ds: &DappStorage,
1666
+ ) {
1667
+ let dapp_key_str = type_name::with_defining_ids<DappKey>().into_string();
1668
+ let coin_type_str = type_name::with_defining_ids<CoinType>().into_string();
1669
+ let revenue = dapp_revenue_balance<CoinType>(ds);
1670
+ emit_dapp_revenue_state_updated(dapp_key_str, revenue, coin_type_str);
1671
+ }
1672
+
1673
+ // ─── Module init ─────────────────────────────────────────────────────────
1674
+
1675
+ fun init(ctx: &mut TxContext) {
1676
+ // Share DappHub — this must happen before any other framework call.
1677
+ sui::transfer::public_share_object(new(ctx));
1678
+ }
1679
+
1680
+ // ─── Test helpers ─────────────────────────────────────────────────────────
1681
+
1682
+ #[test_only]
1683
+ use sui::sui::SUI;
1684
+
189
1685
  #[test_only]
190
1686
  public(package) fun create_dapp_hub_for_testing(ctx: &mut TxContext): DappHub {
191
1687
  DappHub {
192
1688
  id: object::new(ctx),
193
- accounts: object_table::new(ctx),
1689
+ fee_config: FrameworkFeeConfig {
1690
+ base_fee_per_write: 1000,
1691
+ bytes_fee_per_byte: 10,
1692
+ pending_base_fee: 0,
1693
+ pending_bytes_fee: 0,
1694
+ fee_effective_at_ms: 0,
1695
+ treasury: ctx.sender(),
1696
+ pending_treasury: @0x0,
1697
+ fee_history: vector::empty(),
1698
+ accepted_coin_type: option::some(type_name::with_defining_ids<SUI>()),
1699
+ pending_coin_type: option::none(),
1700
+ coin_type_effective_at_ms: 0,
1701
+ },
1702
+ config: FrameworkConfig {
1703
+ default_free_credit: 0,
1704
+ default_free_credit_duration_ms: 0,
1705
+ admin: ctx.sender(),
1706
+ pending_admin: @0x0,
1707
+ default_write_fee_dapp_share_bps: 3000,
1708
+ framework_max_write_limit: 2_000,
1709
+ marketplace_fee_bps: 300,
1710
+ marketplace_dapp_share_bps: 5_000,
1711
+ },
1712
+ version: 1,
1713
+ }
1714
+ }
1715
+
1716
+ #[test_only]
1717
+ public fun destroy_dapp_hub(dh: DappHub) {
1718
+ let DappHub { id, fee_config: _, config: _, version: _ } = dh;
1719
+ object::delete(id);
1720
+ }
1721
+
1722
+ // Alias for backwards-compat with existing tests
1723
+ #[test_only]
1724
+ public fun destroy(dh: DappHub) {
1725
+ destroy_dapp_hub(dh);
1726
+ }
1727
+
1728
+ /// Free-tier DappHub (both fees = 0) — for storage tests that do not test fee logic.
1729
+ #[test_only]
1730
+ public(package) fun create_free_dapp_hub_for_testing(ctx: &mut TxContext): DappHub {
1731
+ DappHub {
1732
+ id: object::new(ctx),
1733
+ fee_config: FrameworkFeeConfig {
1734
+ base_fee_per_write: 0,
1735
+ bytes_fee_per_byte: 0,
1736
+ pending_base_fee: 0,
1737
+ pending_bytes_fee: 0,
1738
+ fee_effective_at_ms: 0,
1739
+ treasury: ctx.sender(),
1740
+ pending_treasury: @0x0,
1741
+ fee_history: vector::empty(),
1742
+ accepted_coin_type: option::some(type_name::with_defining_ids<SUI>()),
1743
+ pending_coin_type: option::none(),
1744
+ coin_type_effective_at_ms: 0,
1745
+ },
1746
+ config: FrameworkConfig {
1747
+ default_free_credit: 0,
1748
+ default_free_credit_duration_ms: 0,
1749
+ admin: ctx.sender(),
1750
+ pending_admin: @0x0,
1751
+ default_write_fee_dapp_share_bps: 3000,
1752
+ framework_max_write_limit: 2_000,
1753
+ marketplace_fee_bps: 300,
1754
+ marketplace_dapp_share_bps: 5_000,
1755
+ },
1756
+ version: 1,
194
1757
  }
195
1758
  }
196
1759
 
197
1760
  #[test_only]
198
- public fun destroy(self: DappHub) {
199
- let DappHub { id, accounts } = self;
1761
+ public fun create_dapp_storage_for_testing<DappKey: copy + drop>(ctx: &mut TxContext): DappStorage {
1762
+ new_dapp_storage<DappKey>(
1763
+ string(b"Test DApp"),
1764
+ string(b""),
1765
+ vector::empty(),
1766
+ 0,
1767
+ ctx.sender(),
1768
+ 0,
1769
+ 0,
1770
+ 0,
1771
+ 0,
1772
+ 0,
1773
+ 0,
1774
+ ctx,
1775
+ )
1776
+ }
1777
+
1778
+ #[test_only]
1779
+ public fun destroy_dapp_storage(ds: DappStorage) {
1780
+ let DappStorage { id, .. } = ds;
1781
+ object::delete(id);
1782
+ }
1783
+
1784
+ #[test_only]
1785
+ public fun create_user_storage_for_testing<DappKey: copy + drop>(
1786
+ owner: address,
1787
+ ctx: &mut TxContext,
1788
+ ): UserStorage {
1789
+ new_user_storage<DappKey>(owner, 1_000, ctx)
1790
+ }
1791
+
1792
+ #[test_only]
1793
+ public fun set_session_key_for_testing(us: &mut UserStorage, key: address, expires_at: u64) {
1794
+ us.session_key = key;
1795
+ us.session_expires_at = expires_at;
1796
+ }
1797
+
1798
+ #[test_only]
1799
+ public fun destroy_user_storage(us: UserStorage) {
1800
+ let UserStorage { id, .. } = us;
200
1801
  object::delete(id);
201
- sui::transfer::public_freeze_object(accounts);
202
1802
  }
203
- }
1803
+
1804
+ #[test_only]
1805
+ public fun create_scene_permit_for_testing<DappKey: copy + drop, PermType>(
1806
+ participants: vector<address>,
1807
+ expires_at: std::option::Option<u64>,
1808
+ max_participants: std::option::Option<u64>,
1809
+ ctx: &mut TxContext,
1810
+ ): ScenePermit<PermType> {
1811
+ let mut permit = ScenePermit<PermType> {
1812
+ id: object::new(ctx),
1813
+ dapp_key: type_name::with_defining_ids<DappKey>().into_string(),
1814
+ permit_type: b"test",
1815
+ meta: new_scene_meta(expires_at, max_participants),
1816
+ };
1817
+ participants.do!(|addr| { add_participant_in_scene_permit(&mut permit, addr) });
1818
+ permit
1819
+ }
1820
+
1821
+ #[test_only]
1822
+ public fun create_scene_permit_with_invitations_for_testing<DappKey: copy + drop, PermType>(
1823
+ invitees: vector<address>,
1824
+ invites_expire_at: std::option::Option<u64>,
1825
+ expires_at: std::option::Option<u64>,
1826
+ ctx: &mut TxContext,
1827
+ ): ScenePermit<PermType> {
1828
+ ScenePermit<PermType> {
1829
+ id: object::new(ctx),
1830
+ dapp_key: type_name::with_defining_ids<DappKey>().into_string(),
1831
+ permit_type: b"test",
1832
+ meta: new_scene_meta_with_invitations(invitees, invites_expire_at, expires_at, option::none()),
1833
+ }
1834
+ }
1835
+
1836
+ #[test_only]
1837
+ public fun destroy_scene_permit_for_testing<PermType>(permit: ScenePermit<PermType>) {
1838
+ let ScenePermit { id, dapp_key: _, permit_type: _, meta: _ } = permit;
1839
+ object::delete(id);
1840
+ }
1841
+
1842
+ }