create-dubhe 1.2.0-pre.99 → 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 (360) 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 +4 -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 +4 -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/sources/codegen/dapp_key.move +45 -31
  297. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/error.move +10 -0
  298. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/genesis.move +30 -13
  299. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/init_test.move +19 -14
  300. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/resources/counter1.move +30 -20
  301. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/resources/counter2.move +35 -24
  302. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/user_storage_init.move +26 -0
  303. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/scripts/deploy_hook.move +6 -10
  304. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/systems/counter.move +7 -20
  305. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/tests/counter.move +14 -27
  306. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/Move.lock +33 -30
  307. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/Move.toml +4 -3
  308. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/dapp_key.move +45 -31
  309. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/error.move +170 -0
  310. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/genesis.move +16 -13
  311. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/init_test.move +8 -14
  312. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/core/account_key.move +1 -3
  313. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/core/dapp_service.move +1776 -137
  314. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/core/data_key.move +1 -20
  315. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/core/events.move +864 -25
  316. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/scripts/deploy_hook.move +15 -16
  317. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/scripts/migrate.move +0 -3
  318. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/systems/address_system.move +25 -17
  319. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/systems/dapp_system.move +2802 -343
  320. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/systems/utils.move +1 -1
  321. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/address.move +173 -55
  322. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/dapp_test.move +769 -0
  323. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/fee_test.move +2965 -0
  324. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/integration_test.move +303 -0
  325. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/listing_test.move +1422 -0
  326. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/scene_session_test.move +269 -0
  327. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/session_test.move +624 -0
  328. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/storage_test.move +700 -0
  329. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/typed_object_test.move +177 -0
  330. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/typed_scene_test.move +452 -0
  331. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/user_storage_test.move +160 -0
  332. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/utils/math.move +4 -3
  333. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/utils/type_info.move +2 -2
  334. package/templates/101/sui-template/packages/contracts/src/counter/Move.lock +0 -41
  335. package/templates/101/sui-template/packages/contracts/src/counter/Published.toml +0 -9
  336. package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/errors.move +0 -12
  337. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/errors.move +0 -140
  338. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dapp_fee_config.move +0 -186
  339. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dapp_fee_state.move +0 -306
  340. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dapp_metadata.move +0 -396
  341. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dubhe_config.move +0 -246
  342. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/core/dapp_store.move +0 -29
  343. package/templates/101/sui-template/packages/contracts/src/dubhe/sources/systems/session_system.move +0 -44
  344. package/templates/contract/sui-template/src/dubhe/sources/codegen/errors.move +0 -140
  345. package/templates/contract/sui-template/src/dubhe/sources/codegen/resources/dapp_fee_config.move +0 -186
  346. package/templates/contract/sui-template/src/dubhe/sources/codegen/resources/dapp_fee_state.move +0 -306
  347. package/templates/contract/sui-template/src/dubhe/sources/codegen/resources/dapp_metadata.move +0 -396
  348. package/templates/contract/sui-template/src/dubhe/sources/codegen/resources/dubhe_config.move +0 -246
  349. package/templates/contract/sui-template/src/dubhe/sources/core/dapp_store.move +0 -29
  350. package/templates/contract/sui-template/src/dubhe/sources/systems/session_system.move +0 -44
  351. package/templates/nextjs/sui-template/packages/contracts/src/counter/Move.lock +0 -47
  352. package/templates/nextjs/sui-template/packages/contracts/src/counter/Published.toml +0 -9
  353. package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/errors.move +0 -12
  354. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/errors.move +0 -140
  355. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dapp_fee_config.move +0 -186
  356. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dapp_fee_state.move +0 -306
  357. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dapp_metadata.move +0 -396
  358. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dubhe_config.move +0 -246
  359. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/core/dapp_store.move +0 -29
  360. package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/systems/session_system.move +0 -44
@@ -0,0 +1,1173 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useCallback } from 'react';
4
+ import {
5
+ ConnectButton,
6
+ useCurrentAccount,
7
+ useCurrentWallet,
8
+ useSignAndExecuteTransaction
9
+ } from '@mysten/dapp-kit';
10
+ import { useDubhe, useDubheTx, SessionInvalidatedError } from '@0xobelisk/react/sui';
11
+ import { Transaction } from '@0xobelisk/sui-client';
12
+ import { toast } from 'sonner';
13
+ import { motion } from 'framer-motion';
14
+
15
+ import { FarmLand } from './components/FarmLand';
16
+ import { RanchLand } from './components/RanchLand';
17
+ import { ShopPanel } from './components/ShopPanel';
18
+ import { ResourceHUD } from './components/ResourceHUD';
19
+ import {
20
+ PetPanel,
21
+ type PetData,
22
+ type PetHatchData,
23
+ type PetInventory
24
+ } from './components/PetPanel';
25
+ import { IconFarm, IconSprout } from './components/PetAvatar';
26
+ import { CROP_NONE } from './lib/crops';
27
+ import { DappHubId, DappStorageId, PackageId, FrameworkPackageId } from 'contracts/deployment';
28
+ import { useWorldPermitId } from './hooks/useWorldPermitId';
29
+
30
+ interface PlotData {
31
+ plotId: number;
32
+ cropType: number;
33
+ count: bigint;
34
+ plantedAt: bigint;
35
+ harvestAt: bigint;
36
+ }
37
+
38
+ interface PlayerState {
39
+ gold: bigint;
40
+ // Seeds (bought from shop, consumed when planting)
41
+ wheatSeed: bigint;
42
+ cornSeed: bigint;
43
+ carrotSeed: bigint;
44
+ pumpkinSeed: bigint;
45
+ // Crops (harvested output, sellable / listable on market)
46
+ wheat: bigint;
47
+ corn: bigint;
48
+ carrot: bigint;
49
+ pumpkin: bigint;
50
+ plotsOwned: number;
51
+ plots: (PlotData | null)[];
52
+ isRegistered: boolean;
53
+ }
54
+
55
+ // Unsettled write count threshold — settle_writes is prepended to the PTB
56
+ // once the user's unsettled count reaches this value (75% of the 2 000 hard limit).
57
+ const SETTLE_THRESHOLD = 5n; // TODO: restore to 1500n before production
58
+
59
+ const INITIAL_STATE: PlayerState = {
60
+ gold: BigInt(0),
61
+ wheatSeed: BigInt(0),
62
+ cornSeed: BigInt(0),
63
+ carrotSeed: BigInt(0),
64
+ pumpkinSeed: BigInt(0),
65
+ wheat: BigInt(0),
66
+ corn: BigInt(0),
67
+ carrot: BigInt(0),
68
+ pumpkin: BigInt(0),
69
+ plotsOwned: 0,
70
+ plots: Array(12).fill(null),
71
+ isRegistered: false
72
+ };
73
+
74
+ export default function FarmPage() {
75
+ const account = useCurrentAccount();
76
+ const { connectionStatus } = useCurrentWallet();
77
+ const { mutateAsync: signAndExecuteTransaction } = useSignAndExecuteTransaction();
78
+ const { contract, ecsWorld, dappStorageId, dappHubId, network, packageId } = useDubhe();
79
+ const { permitId: worldPermitId } = useWorldPermitId();
80
+
81
+ const [sessionBalance, setSessionBalance] = useState<number | null>(null);
82
+
83
+ // Use deployment.ts values as fallback when DubheProvider hasn't resolved yet
84
+ const hubId = dappHubId ?? DappHubId;
85
+ const storageId = dappStorageId ?? DappStorageId;
86
+
87
+ const [mainView, setMainView] = useState<'farm' | 'ranch'>('farm');
88
+ const [state, setState] = useState<PlayerState>(INITIAL_STATE);
89
+ const [balance, setBalance] = useState(0);
90
+ const [userStorageId, setUserStorageId] = useState<string | null>(null);
91
+ const [isLoading, setIsLoading] = useState(false);
92
+
93
+ // Main-wallet signer injected into the framework tx layer.
94
+ const signWithWallet = useCallback(
95
+ async (tx: Transaction) => {
96
+ const resp = await signAndExecuteTransaction({
97
+ transaction: tx.serialize() as any,
98
+ chain: `sui:${network ?? 'localnet'}`
99
+ });
100
+ return { digest: resp.digest };
101
+ },
102
+ [signAndExecuteTransaction, network]
103
+ );
104
+
105
+ // Framework tx layer: session key management (scoped to the connected main
106
+ // wallet) plus automatic settle_writes prepending at the threshold.
107
+ const dubheTx = useDubheTx({
108
+ owner: account?.address ?? null,
109
+ userStorageId,
110
+ signWithWallet,
111
+ settleThreshold: SETTLE_THRESHOLD
112
+ });
113
+ const {
114
+ isActive: sessionActive,
115
+ keypairLoading: sessionKeypairLoading,
116
+ minutesLeft: sessionMinutesLeft,
117
+ sessionAddress,
118
+ buildActivateTx,
119
+ confirmActivation,
120
+ signAndSend: sessionSignAndSend,
121
+ clearSession,
122
+ getSessionBalance,
123
+ buildFundSessionTx,
124
+ SESSION_DURATION_MS
125
+ } = dubheTx.session;
126
+
127
+ // ── Pet state ──────────────────────────────────────────────────────────────
128
+ const INITIAL_PET_INV: PetInventory = {
129
+ commonEgg: 0n,
130
+ rareEgg: 0n,
131
+ seasonalEgg: 0n,
132
+ slotsOwned: 1,
133
+ hatch: null,
134
+ activeSlots: Array(3).fill(null),
135
+ ranchPets: []
136
+ };
137
+ const [petInv, setPetInv] = useState<PetInventory>(INITIAL_PET_INV);
138
+
139
+ const inventory: Record<number, bigint> = {
140
+ 1: state.wheat,
141
+ 2: state.corn,
142
+ 3: state.carrot,
143
+ 4: state.pumpkin
144
+ };
145
+
146
+ const seedInventory: Record<number, bigint> = {
147
+ 1: state.wheatSeed,
148
+ 2: state.cornSeed,
149
+ 3: state.carrotSeed,
150
+ 4: state.pumpkinSeed
151
+ };
152
+
153
+ const isConnected = connectionStatus === 'connected';
154
+
155
+ // ── Helpers ────────────────────────────────────────────────────────────────
156
+
157
+ const fetchBalance = useCallback(async () => {
158
+ if (!account?.address || !contract) return;
159
+ try {
160
+ const b = await contract.balanceOf(account.address);
161
+ setBalance(Number(b.totalBalance) / 1_000_000_000);
162
+ } catch {}
163
+ }, [account?.address, contract]);
164
+
165
+ const fetchUserStorageId = useCallback(
166
+ async (addr: string) => {
167
+ if (!contract) return null;
168
+ try {
169
+ const id = await contract.getUserStorageId(addr);
170
+ setUserStorageId(id);
171
+ return id;
172
+ } catch {
173
+ setUserStorageId(null);
174
+ return null;
175
+ }
176
+ },
177
+ [contract]
178
+ );
179
+
180
+ // Query all game state via the ECS/indexer client.
181
+ // entityId = player wallet address (canonical owner address)
182
+ const fetchGameState = useCallback(
183
+ async (addr: string) => {
184
+ if (!ecsWorld) return;
185
+ try {
186
+ // Check registration by presence of profile resource
187
+ const profile = await ecsWorld
188
+ .getComponent<{ totalEarned: string; plotsOwned: number }>(addr, 'profile')
189
+ .catch(() => null);
190
+
191
+ if (!profile) {
192
+ setState((s) => ({ ...s, isRegistered: false }));
193
+ return;
194
+ }
195
+
196
+ // Fetch all single-key resources in parallel (seeds + crops)
197
+ const [
198
+ goldData,
199
+ wheatSeedData,
200
+ cornSeedData,
201
+ carrotSeedData,
202
+ pumpkinSeedData,
203
+ wheatData,
204
+ cornData,
205
+ carrotData,
206
+ pumpkinData
207
+ ] = await Promise.all([
208
+ ecsWorld.getComponent<{ amount: string }>(addr, 'gold').catch(() => null),
209
+ ecsWorld.getComponent<{ amount: string }>(addr, 'wheatSeed').catch(() => null),
210
+ ecsWorld.getComponent<{ amount: string }>(addr, 'cornSeed').catch(() => null),
211
+ ecsWorld.getComponent<{ amount: string }>(addr, 'carrotSeed').catch(() => null),
212
+ ecsWorld.getComponent<{ amount: string }>(addr, 'pumpkinSeed').catch(() => null),
213
+ ecsWorld.getComponent<{ amount: string }>(addr, 'wheat').catch(() => null),
214
+ ecsWorld.getComponent<{ amount: string }>(addr, 'corn').catch(() => null),
215
+ ecsWorld.getComponent<{ amount: string }>(addr, 'carrot').catch(() => null),
216
+ ecsWorld.getComponent<{ amount: string }>(addr, 'pumpkin').catch(() => null)
217
+ ]);
218
+
219
+ // farm_plot has composite key [entity_id, plot_id].
220
+ // getResources now accepts both camelCase and snake_case after the framework bug fix.
221
+ const plotsOwned = Number(profile?.plotsOwned ?? 1);
222
+ const plotResult = await ecsWorld
223
+ .getResources<{
224
+ entityId: string;
225
+ plotId: number;
226
+ cropType: number;
227
+ count: string;
228
+ plantedAt: string;
229
+ harvestAt: string;
230
+ }>('farmPlot', {
231
+ filters: { entityId: addr },
232
+ orderBy: [{ field: 'plotId', direction: 'ASC' }],
233
+ limit: 12
234
+ })
235
+ .catch(() => ({ items: [] as any[] }));
236
+
237
+ const plots: (PlotData | null)[] = Array(12).fill(null);
238
+ for (let i = 0; i < plotsOwned; i++) {
239
+ const p = plotResult.items.find((r) => Number(r.plotId) === i);
240
+ plots[i] = {
241
+ plotId: i,
242
+ cropType: Number(p?.cropType ?? 0),
243
+ count: BigInt(p?.count ?? 0),
244
+ plantedAt: BigInt(p?.plantedAt ?? 0),
245
+ harvestAt: BigInt(p?.harvestAt ?? 0)
246
+ };
247
+ }
248
+
249
+ setState({
250
+ gold: BigInt(goldData?.amount ?? 0),
251
+ wheatSeed: BigInt(wheatSeedData?.amount ?? 0),
252
+ cornSeed: BigInt(cornSeedData?.amount ?? 0),
253
+ carrotSeed: BigInt(carrotSeedData?.amount ?? 0),
254
+ pumpkinSeed: BigInt(pumpkinSeedData?.amount ?? 0),
255
+ wheat: BigInt(wheatData?.amount ?? 0),
256
+ corn: BigInt(cornData?.amount ?? 0),
257
+ carrot: BigInt(carrotData?.amount ?? 0),
258
+ pumpkin: BigInt(pumpkinData?.amount ?? 0),
259
+ plotsOwned,
260
+ plots,
261
+ isRegistered: true
262
+ });
263
+
264
+ // ── Pet inventory ──────────────────────────────────────────────────
265
+ const [cEgg, rEgg, sEgg, hatchData, petSlotsData, petResult, slotIndexResult] =
266
+ await Promise.all([
267
+ ecsWorld.getComponent<{ amount: string }>(addr, 'commonEgg').catch(() => null),
268
+ ecsWorld.getComponent<{ amount: string }>(addr, 'rareEgg').catch(() => null),
269
+ ecsWorld.getComponent<{ amount: string }>(addr, 'seasonalEgg').catch(() => null),
270
+ ecsWorld
271
+ .getComponent<{ eggType: number; hatchAt: string }>(addr, 'petHatch')
272
+ .catch(() => null),
273
+ ecsWorld.getComponent<{ slotsOwned: number }>(addr, 'petSlots').catch(() => null),
274
+ // All pets owned by the player (keyed by pet_id, no slot field)
275
+ ecsWorld
276
+ .getResources<{
277
+ petId: string;
278
+ species: number;
279
+ rarity: number;
280
+ level: number;
281
+ xp: number;
282
+ happiness: number;
283
+ satiety: number;
284
+ fedAt: string;
285
+ bornAt: string;
286
+ }>('pet', { filters: { entityId: addr }, limit: 50 })
287
+ .catch(() => ({ items: [] as any[] })),
288
+ // Active slot assignments: slot → pet_id
289
+ ecsWorld
290
+ .getResources<{ slot: number; petId: string }>('petSlotIndex', {
291
+ filters: { entityId: addr },
292
+ limit: 3
293
+ })
294
+ .catch(() => ({ items: [] as any[] }))
295
+ ]);
296
+
297
+ const slotsOwned = petSlotsData?.slotsOwned ?? 1;
298
+
299
+ // Build a map of all owned pets by petId — skip soft-deleted rows
300
+ const allPetsMap = new Map<string, PetData>();
301
+ petResult.items.forEach((p) => {
302
+ if ((p as any).isDeleted) return;
303
+ allPetsMap.set(String(p.petId), {
304
+ petId: String(p.petId),
305
+ species: Number(p.species),
306
+ rarity: Number(p.rarity),
307
+ level: Number(p.level),
308
+ xp: Number(p.xp),
309
+ happiness: Number(p.happiness),
310
+ satiety: Number(p.satiety),
311
+ fedAt: Number(p.fedAt),
312
+ bornAt: Number(p.bornAt)
313
+ });
314
+ });
315
+
316
+ // Build activeSlots — skip slot index rows that point to deleted pets
317
+ const activeSlots: (PetData | null)[] = Array(3).fill(null);
318
+ const activePetIds = new Set<string>();
319
+ slotIndexResult.items.forEach((s) => {
320
+ if ((s as any).isDeleted) return;
321
+ const slot = Number(s.slot);
322
+ const petId = String(s.petId);
323
+ const pet = allPetsMap.get(petId);
324
+ if (slot < 3 && pet) {
325
+ activeSlots[slot] = pet;
326
+ activePetIds.add(petId);
327
+ }
328
+ });
329
+
330
+ // Ranch = all pets NOT in any active slot
331
+ const ranchPets: PetData[] = [];
332
+ allPetsMap.forEach((pet) => {
333
+ if (!activePetIds.has(pet.petId)) ranchPets.push(pet);
334
+ });
335
+
336
+ // Guard against soft-deleted rows: the indexer marks deleted records with
337
+ // isDeleted=true but keeps them in the database. Treat as absent.
338
+ const hatch: PetHatchData | null =
339
+ hatchData && !(hatchData as any).isDeleted
340
+ ? { eggType: Number(hatchData.eggType), hatchAt: Number(hatchData.hatchAt) }
341
+ : null;
342
+
343
+ setPetInv({
344
+ commonEgg: BigInt(cEgg?.amount ?? 0),
345
+ rareEgg: BigInt(rEgg?.amount ?? 0),
346
+ seasonalEgg: BigInt(sEgg?.amount ?? 0),
347
+ slotsOwned,
348
+ hatch,
349
+ activeSlots,
350
+ ranchPets
351
+ });
352
+ } catch (err) {
353
+ console.error('fetchGameState error:', err);
354
+ }
355
+ },
356
+ [ecsWorld]
357
+ );
358
+ // Refresh everything for the current account
359
+ const refresh = useCallback(async () => {
360
+ if (!account?.address) return;
361
+ await fetchBalance();
362
+ await fetchUserStorageId(account.address);
363
+ // Refresh unsettled count + settlement mode so the framework tx layer
364
+ // knows when to prepend settle_writes (and which variant).
365
+ dubheTx.refreshSettleState().catch(() => {});
366
+ // Game state comes from the indexer keyed by wallet address (entityId = canonical owner address)
367
+ await fetchGameState(account.address);
368
+ // Refresh session wallet balance so the UI stays accurate
369
+ getSessionBalance()
370
+ .then(setSessionBalance)
371
+ .catch(() => {});
372
+ // eslint-disable-next-line react-hooks/exhaustive-deps
373
+ }, [
374
+ account?.address,
375
+ fetchBalance,
376
+ fetchUserStorageId,
377
+ fetchGameState,
378
+ getSessionBalance,
379
+ dubheTx.refreshSettleState
380
+ ]);
381
+
382
+ useEffect(() => {
383
+ if (isConnected && account?.address) {
384
+ refresh();
385
+ const interval = setInterval(refresh, 10000);
386
+ return () => clearInterval(interval);
387
+ } else {
388
+ setState(INITIAL_STATE);
389
+ setUserStorageId(null);
390
+ setBalance(0);
391
+ }
392
+ }, [isConnected, account?.address]);
393
+
394
+ // ── Transaction helpers ────────────────────────────────────────────────────
395
+
396
+ const explorerUrl = (digest: string) =>
397
+ contract?.getTxExplorerUrl(digest) ??
398
+ `https://suiscan.xyz/${network ?? 'testnet'}/tx/${digest}`;
399
+
400
+ const txToast = (msg: string, digest: string) =>
401
+ toast.success(msg, {
402
+ description: (
403
+ <a
404
+ href={explorerUrl(digest)}
405
+ target="_blank"
406
+ rel="noopener noreferrer"
407
+ className="text-xs text-blue-400 hover:text-blue-300 underline mt-1 inline-block"
408
+ >
409
+ View TX ↗
410
+ </a>
411
+ )
412
+ });
413
+
414
+ const checkPreconditions = () => {
415
+ if (!isConnected) {
416
+ toast.error('Please connect your wallet first');
417
+ return false;
418
+ }
419
+ if (balance === 0) {
420
+ toast.error('Your SUI balance is 0. Please top up before proceeding.');
421
+ return false;
422
+ }
423
+ return true;
424
+ };
425
+
426
+ // Always uses main wallet (for registration and session management).
427
+ const execTxWithMainWallet = async (
428
+ buildFn: (tx: Transaction) => void | Promise<void>,
429
+ successMsg: string,
430
+ onSuccess?: () => void
431
+ ) => {
432
+ if (!checkPreconditions()) return;
433
+ setIsLoading(true);
434
+ try {
435
+ const { digest } = await dubheTx.execTxWithMainWallet(buildFn);
436
+ txToast(successMsg, digest);
437
+ onSuccess?.();
438
+ setTimeout(refresh, 1500);
439
+ } catch (err: any) {
440
+ console.error('tx error:', err);
441
+ toast.error(`Transaction failed: ${err?.message ?? err}`);
442
+ } finally {
443
+ setIsLoading(false);
444
+ }
445
+ };
446
+
447
+ /**
448
+ * Execute a game action PTB.
449
+ * - If a session key is active: signs silently with the ephemeral keypair (no wallet popup).
450
+ * - Otherwise: falls back to main wallet via dapp-kit.
451
+ *
452
+ * The framework tx layer prepends `settle_writes` automatically once the
453
+ * user's unsettled write count reaches the configured threshold.
454
+ */
455
+ const execTx = async (
456
+ buildFn: (tx: Transaction) => void | Promise<void>,
457
+ successMsg: string,
458
+ onSuccess?: () => void
459
+ ) => {
460
+ if (!checkPreconditions()) return;
461
+ setIsLoading(true);
462
+ try {
463
+ const { digest } = await dubheTx.execTx(buildFn);
464
+ onSuccess?.();
465
+ txToast(successMsg, digest);
466
+ setTimeout(refresh, 1500);
467
+ } catch (err: any) {
468
+ if (err instanceof SessionInvalidatedError) {
469
+ toast.error('Session is no longer valid — please activate it again.');
470
+ } else {
471
+ console.error('tx error:', err);
472
+ toast.error(`Transaction failed: ${err?.message ?? err}`);
473
+ }
474
+ // Restore accurate unsettled count quickly so the next write
475
+ // correctly decides whether to prepend settle_writes again.
476
+ setTimeout(refresh, 500);
477
+ } finally {
478
+ setIsLoading(false);
479
+ }
480
+ };
481
+
482
+ // ── Session management actions ─────────────────────────────────────────────
483
+
484
+ const handleActivateSessionDirect = async () => {
485
+ if (!isConnected) {
486
+ toast.error('Please connect your wallet first');
487
+ return;
488
+ }
489
+ if (!userStorageId) {
490
+ toast.error('Create storage first');
491
+ return;
492
+ }
493
+ if (balance === 0) {
494
+ toast.error('Your SUI balance is 0');
495
+ return;
496
+ }
497
+
498
+ setIsLoading(true);
499
+ try {
500
+ const tx = buildActivateTx(userStorageId, SESSION_DURATION_MS);
501
+ await signAndExecuteTransaction(
502
+ { transaction: (tx as any).serialize(), chain: `sui:${network ?? 'localnet'}` },
503
+ {
504
+ onSuccess: (resp) => {
505
+ confirmActivation(SESSION_DURATION_MS);
506
+ txToast(
507
+ 'Session activated for 1 hour. No wallet popups for game actions!',
508
+ resp.digest
509
+ );
510
+ },
511
+ onError: (err) => toast.error(`Failed: ${err.message}`)
512
+ }
513
+ );
514
+ } catch (err: any) {
515
+ toast.error(`Error: ${err?.message ?? err}`);
516
+ } finally {
517
+ setIsLoading(false);
518
+ }
519
+ };
520
+
521
+ const handleDeactivateSession = async () => {
522
+ if (!userStorageId) {
523
+ clearSession();
524
+ return;
525
+ }
526
+ setIsLoading(true);
527
+ try {
528
+ if (sessionActive) {
529
+ // Session key can revoke itself — no main wallet needed
530
+ await sessionSignAndSend((tx) => {
531
+ tx.moveCall({
532
+ target: `${FrameworkPackageId}::dapp_system::deactivate_session`,
533
+ typeArguments: [`${packageId ?? PackageId}::dapp_key::DappKey`],
534
+ arguments: [tx.object(DappHubId), tx.object(userStorageId)]
535
+ });
536
+ });
537
+ }
538
+ clearSession();
539
+ toast.success('Session deactivated.');
540
+ } catch {
541
+ clearSession();
542
+ toast.success('Session cleared locally.');
543
+ } finally {
544
+ setIsLoading(false);
545
+ }
546
+ };
547
+
548
+ // ── Game actions ───────────────────────────────────────────────────────────
549
+
550
+ // Step 1: Create UserStorage (first time only — always main wallet)
551
+ const handleCreateStorage = () =>
552
+ execTxWithMainWallet(async (tx) => {
553
+ tx.moveCall({
554
+ target: `${packageId ?? PackageId}::user_storage_init::init_user_storage`,
555
+ arguments: [tx.object(hubId), tx.object(storageId)]
556
+ });
557
+ }, 'UserStorage created! Now register your farm.');
558
+
559
+ // Step 2: Register in the game world — always main wallet (one-time)
560
+ const handleRegister = () =>
561
+ execTxWithMainWallet(async (tx) => {
562
+ if (!userStorageId) throw new Error('UserStorage not found');
563
+ if (!worldPermitId) throw new Error('World permit not loaded yet — please wait a moment');
564
+ tx.moveCall({
565
+ target: `${packageId ?? PackageId}::world_system::register`,
566
+ arguments: [tx.object(storageId), tx.object(userStorageId), tx.object(worldPermitId)]
567
+ });
568
+ }, 'Welcome to Harvest! You received 50 gold.');
569
+
570
+ // Sui shared Clock object (0x6) — needed by any function that reads the current time.
571
+ const CLOCK = '0x6';
572
+
573
+ // Crop growth durations in milliseconds (must match farm_system.move constants)
574
+ const CROP_DURATION_MS: Record<number, number> = {
575
+ 1: 1 * 60 * 1000, // Wheat — 1 min
576
+ 2: 2 * 60 * 1000, // Corn — 2 min
577
+ 3: 4 * 60 * 1000, // Carrot — 4 min
578
+ 4: 5 * 60 * 1000 // Pumpkin — 5 min
579
+ };
580
+
581
+ // Yield per seed (must match farm_system.move crop_yield constants)
582
+ const CROP_YIELD: Record<number, number> = {
583
+ 1: 6, // Wheat
584
+ 2: 4, // Corn
585
+ 3: 3, // Carrot
586
+ 4: 3 // Pumpkin
587
+ };
588
+
589
+ const handlePlant = (plotId: number, cropType: number, _count: number) => {
590
+ // Optimistic update: show yield_per_seed plants immediately
591
+ const now = Date.now();
592
+ const harvestAt = now + (CROP_DURATION_MS[cropType] ?? 0);
593
+ const yieldCount = CROP_YIELD[cropType] ?? 1;
594
+ setState((s) => {
595
+ const plots = [...s.plots];
596
+ plots[plotId] = {
597
+ plotId,
598
+ cropType,
599
+ count: BigInt(yieldCount),
600
+ plantedAt: BigInt(now),
601
+ harvestAt: BigInt(harvestAt)
602
+ };
603
+ // Deduct 1 seed from the dedicated seed resource (not the crop resource)
604
+ const key =
605
+ cropType === 1
606
+ ? 'wheatSeed'
607
+ : cropType === 2
608
+ ? 'cornSeed'
609
+ : cropType === 3
610
+ ? 'carrotSeed'
611
+ : 'pumpkinSeed';
612
+ return { ...s, [key]: (s as any)[key] - BigInt(1), plots };
613
+ });
614
+
615
+ return execTx(async (tx) => {
616
+ if (!userStorageId) throw new Error('UserStorage not found');
617
+ tx.moveCall({
618
+ target: `${packageId ?? PackageId}::farm_system::plant`,
619
+ arguments: [
620
+ tx.object(storageId),
621
+ tx.object(userStorageId),
622
+ tx.pure.u8(plotId),
623
+ tx.pure.u8(cropType),
624
+ tx.object(CLOCK)
625
+ ]
626
+ });
627
+ }, 'Planted! Check back when ready to harvest.');
628
+ };
629
+
630
+ const handleHarvest = (plotId: number) => {
631
+ // Optimistic update: clear the plot immediately
632
+ const plot = state.plots[plotId];
633
+ const harvestedCropType = plot?.cropType ?? 0;
634
+ const harvestedCount = plot?.count ?? BigInt(0);
635
+ setState((s) => {
636
+ const plots = [...s.plots];
637
+ plots[plotId] = {
638
+ plotId,
639
+ cropType: 0,
640
+ count: BigInt(0),
641
+ plantedAt: BigInt(0),
642
+ harvestAt: BigInt(0)
643
+ };
644
+ const key =
645
+ harvestedCropType === 1
646
+ ? 'wheat'
647
+ : harvestedCropType === 2
648
+ ? 'corn'
649
+ : harvestedCropType === 3
650
+ ? 'carrot'
651
+ : 'pumpkin';
652
+ return { ...s, [key]: (s as any)[key] + harvestedCount, plots };
653
+ });
654
+
655
+ return execTx(async (tx) => {
656
+ if (!userStorageId) throw new Error('UserStorage not found');
657
+ tx.moveCall({
658
+ target: `${packageId ?? PackageId}::farm_system::harvest`,
659
+ arguments: [
660
+ tx.object(storageId),
661
+ tx.object(userStorageId),
662
+ tx.pure.u8(plotId),
663
+ tx.object(CLOCK)
664
+ ]
665
+ });
666
+ }, 'Harvested! Check your crop inventory.');
667
+ };
668
+
669
+ const handleBuySeeds = (cropType: number, count: number) =>
670
+ execTx(async (tx) => {
671
+ if (!userStorageId) throw new Error('UserStorage not found');
672
+ tx.moveCall({
673
+ target: `${packageId ?? PackageId}::shop_system::buy_seeds`,
674
+ arguments: [
675
+ tx.object(storageId),
676
+ tx.object(userStorageId),
677
+ tx.pure.u8(cropType),
678
+ tx.pure.u64(count)
679
+ ]
680
+ });
681
+ }, `Bought ${count} seed(s)!`);
682
+
683
+ const handleBuyPlot = () =>
684
+ execTx(async (tx) => {
685
+ if (!userStorageId) throw new Error('UserStorage not found');
686
+ tx.moveCall({
687
+ target: `${packageId ?? PackageId}::shop_system::buy_extra_plot`,
688
+ arguments: [tx.object(storageId), tx.object(userStorageId)]
689
+ });
690
+ }, 'New farm plot unlocked!');
691
+
692
+ const handleSellCrops = (cropType: number, amount: number) => {
693
+ const cropKey =
694
+ cropType === 1 ? 'wheat' : cropType === 2 ? 'corn' : cropType === 3 ? 'carrot' : 'pumpkin';
695
+ const sellPrices: Record<number, number> = { 1: 8, 2: 35, 3: 120, 4: 100 };
696
+ const earned = BigInt(sellPrices[cropType] ?? 0) * BigInt(amount);
697
+ // Optimistic update: deduct crops, add gold
698
+ setState((s) => ({
699
+ ...s,
700
+ [cropKey]: (s as any)[cropKey] - BigInt(amount),
701
+ gold: s.gold + earned
702
+ }));
703
+ return execTx(async (tx) => {
704
+ if (!userStorageId) throw new Error('UserStorage not found');
705
+ tx.moveCall({
706
+ target: `${packageId ?? PackageId}::shop_system::sell_crops`,
707
+ arguments: [
708
+ tx.object(storageId),
709
+ tx.object(userStorageId),
710
+ tx.pure.u8(cropType),
711
+ tx.pure.u64(amount)
712
+ ]
713
+ });
714
+ }, `Sold ${amount} crop(s) for ${Number(earned)}g!`);
715
+ };
716
+
717
+ // ── Pet handlers ───────────────────────────────────────────────────────────
718
+
719
+ const SUI_RANDOM = '0x8';
720
+
721
+ const handleBuyEgg = (eggType: number) =>
722
+ execTx(async (tx) => {
723
+ if (!userStorageId) throw new Error('UserStorage not found');
724
+ const fnName =
725
+ eggType === 1 ? 'buy_common_egg' : eggType === 2 ? 'buy_rare_egg' : 'buy_seasonal_egg';
726
+ tx.moveCall({
727
+ target: `${packageId ?? PackageId}::pet_system::${fnName}`,
728
+ arguments: [tx.object(storageId), tx.object(userStorageId), tx.pure.u64(1)]
729
+ });
730
+ }, 'Egg purchased!');
731
+
732
+ const handleStartHatch = (eggType: number) =>
733
+ execTx(async (tx) => {
734
+ if (!userStorageId) throw new Error('UserStorage not found');
735
+ tx.moveCall({
736
+ target: `${packageId ?? PackageId}::pet_system::start_hatch`,
737
+ arguments: [
738
+ tx.object(storageId),
739
+ tx.object(userStorageId),
740
+ tx.pure.u8(eggType),
741
+ tx.object(CLOCK)
742
+ ]
743
+ });
744
+ }, 'Egg placed in incubator!');
745
+
746
+ const handleOpenEgg = () =>
747
+ execTx(
748
+ async (tx) => {
749
+ if (!userStorageId) throw new Error('UserStorage not found');
750
+ tx.moveCall({
751
+ target: `${packageId ?? PackageId}::pet_system::open_egg`,
752
+ arguments: [
753
+ tx.object(storageId),
754
+ tx.object(userStorageId),
755
+ tx.object(SUI_RANDOM),
756
+ tx.object(CLOCK)
757
+ ]
758
+ });
759
+ },
760
+ 'New pet hatched!',
761
+ // Optimistic update: clear hatch record immediately so the button disappears
762
+ () => setPetInv((prev) => ({ ...prev, hatch: null }))
763
+ );
764
+
765
+ const handleFeedPet = (petId: string, cropType: number, amount: number) =>
766
+ execTx(async (tx) => {
767
+ if (!userStorageId) throw new Error('UserStorage not found');
768
+ tx.moveCall({
769
+ target: `${packageId ?? PackageId}::pet_system::feed_pet`,
770
+ arguments: [
771
+ tx.object(storageId),
772
+ tx.object(userStorageId),
773
+ tx.pure.address(petId),
774
+ tx.pure.u8(cropType),
775
+ tx.pure.u64(amount),
776
+ tx.object(CLOCK)
777
+ ]
778
+ });
779
+ }, 'Pet fed!');
780
+
781
+ const handleBuyPetSlot = () =>
782
+ execTx(async (tx) => {
783
+ if (!userStorageId) throw new Error('UserStorage not found');
784
+ tx.moveCall({
785
+ target: `${packageId ?? PackageId}::pet_system::buy_pet_slot`,
786
+ arguments: [tx.object(storageId), tx.object(userStorageId)]
787
+ });
788
+ }, 'New pet slot unlocked!');
789
+
790
+ const handleDismissPet = (petId: string) =>
791
+ execTx(async (tx) => {
792
+ if (!userStorageId) throw new Error('UserStorage not found');
793
+ tx.moveCall({
794
+ target: `${packageId ?? PackageId}::pet_system::dismiss_pet`,
795
+ arguments: [tx.object(storageId), tx.object(userStorageId), tx.pure.address(petId)]
796
+ });
797
+ }, 'Pet dismissed.');
798
+
799
+ const handleListPet = (petId: string, price: bigint) =>
800
+ execTxWithMainWallet(async (tx) => {
801
+ if (!userStorageId) throw new Error('UserStorage not found');
802
+ tx.moveCall({
803
+ target: `${packageId ?? PackageId}::pet_system::list_pet`,
804
+ arguments: [
805
+ tx.object(storageId),
806
+ tx.object(userStorageId),
807
+ tx.pure.address(petId),
808
+ tx.pure.u64(price)
809
+ ]
810
+ });
811
+ }, 'Pet listed on market!');
812
+
813
+ const handleAssignSlot = (petId: string, slot: number) =>
814
+ execTx(async (tx) => {
815
+ if (!userStorageId) throw new Error('UserStorage not found');
816
+ tx.moveCall({
817
+ target: `${packageId ?? PackageId}::pet_system::assign_slot`,
818
+ arguments: [
819
+ tx.object(storageId),
820
+ tx.object(userStorageId),
821
+ tx.pure.address(petId),
822
+ tx.pure.u8(slot)
823
+ ]
824
+ });
825
+ }, `Pet assigned to slot ${slot + 1}!`);
826
+
827
+ const handleUnassignSlot = (slot: number) =>
828
+ execTx(async (tx) => {
829
+ if (!userStorageId) throw new Error('UserStorage not found');
830
+ tx.moveCall({
831
+ target: `${packageId ?? PackageId}::pet_system::unassign_slot`,
832
+ arguments: [tx.object(storageId), tx.object(userStorageId), tx.pure.u8(slot)]
833
+ });
834
+ }, 'Pet moved to ranch.');
835
+
836
+ // ── Render ─────────────────────────────────────────────────────────────────
837
+
838
+ if (!isConnected) {
839
+ return (
840
+ <div
841
+ className="min-h-screen flex flex-col items-center justify-center gap-6 p-8"
842
+ style={{ background: 'radial-gradient(ellipse at center, #1a3a1a 0%, #0a1a0a 100%)' }}
843
+ >
844
+ <motion.div
845
+ initial={{ y: -20, opacity: 0 }}
846
+ animate={{ y: 0, opacity: 1 }}
847
+ className="text-center"
848
+ >
849
+ <div className="mb-4 flex justify-center">
850
+ <IconFarm size={80} />
851
+ </div>
852
+ <h1 className="font-pixel text-amber-300 text-2xl mb-2">HARVEST</h1>
853
+ <p className="text-amber-600 text-sm mb-8">Full-Chain Casual Farming on Sui</p>
854
+ <ConnectButton />
855
+ </motion.div>
856
+ </div>
857
+ );
858
+ }
859
+
860
+ // Low balance warning
861
+ const balanceWarning = balance === 0 && (
862
+ <div className="mb-4 bg-red-900/40 border border-red-600/50 rounded-xl px-4 py-3 text-red-300 text-xs font-pixel">
863
+ Your SUI balance is 0. Please get some {network ?? 'localnet'} SUI before making transactions.
864
+ </div>
865
+ );
866
+
867
+ // No UserStorage yet
868
+ if (!userStorageId) {
869
+ return (
870
+ <div
871
+ className="min-h-screen p-4 md:p-6"
872
+ style={{ background: 'radial-gradient(ellipse at top, #1a3a1a 0%, #0a1a0a 100%)' }}
873
+ >
874
+ <div className="flex justify-end mb-6">
875
+ <ConnectButton />
876
+ </div>
877
+ {balanceWarning}
878
+ <div className="flex flex-col items-center justify-center min-h-[60vh] text-center">
879
+ <div className="mb-4 flex justify-center">
880
+ <IconFarm size={60} />
881
+ </div>
882
+ <h2 className="font-pixel text-amber-300 text-lg mb-2">Setup Your Account</h2>
883
+ <p className="text-amber-500 text-sm mb-2">Step 1 of 2 — Create your on-chain storage</p>
884
+ <p className="text-amber-700 text-xs mb-6 max-w-xs">
885
+ This creates your personal UserStorage object on Sui — required before interacting with
886
+ any DApp.
887
+ </p>
888
+ <motion.button
889
+ onClick={handleCreateStorage}
890
+ disabled={isLoading || balance === 0}
891
+ className="bg-blue-700 hover:bg-blue-600 text-white font-pixel px-8 py-3 rounded-xl
892
+ disabled:opacity-50 transition-colors"
893
+ whileHover={{ scale: 1.05 }}
894
+ whileTap={{ scale: 0.95 }}
895
+ >
896
+ {isLoading ? 'Creating...' : 'Create Storage (Step 1)'}
897
+ </motion.button>
898
+ {balance === 0 && (
899
+ <p className="text-red-400 text-xs font-pixel mt-3">Need SUI balance to pay gas</p>
900
+ )}
901
+ </div>
902
+ </div>
903
+ );
904
+ }
905
+
906
+ // Has UserStorage but not registered in game
907
+ if (!state.isRegistered) {
908
+ return (
909
+ <div
910
+ className="min-h-screen p-4 md:p-6"
911
+ style={{ background: 'radial-gradient(ellipse at top, #1a3a1a 0%, #0a1a0a 100%)' }}
912
+ >
913
+ <div className="flex justify-end mb-6">
914
+ <ConnectButton />
915
+ </div>
916
+ {balanceWarning}
917
+ <div className="flex flex-col items-center justify-center min-h-[60vh] text-center">
918
+ <div className="mb-4 flex justify-center">
919
+ <IconSprout size={60} />
920
+ </div>
921
+ <h2 className="font-pixel text-amber-300 text-lg mb-2">Start Your Farm</h2>
922
+ <p className="text-amber-500 text-sm mb-2">Step 2 of 2 — Register in the game world</p>
923
+ <p className="text-amber-700 text-xs mb-6 max-w-xs">
924
+ You will receive 50 gold and your first farm plot.
925
+ <br />
926
+ After registering, activate a Session Key to play without wallet popups.
927
+ </p>
928
+ <motion.button
929
+ onClick={handleRegister}
930
+ disabled={isLoading || balance === 0}
931
+ className="bg-green-700 hover:bg-green-600 text-white font-pixel px-8 py-3 rounded-xl
932
+ disabled:opacity-50 transition-colors"
933
+ whileHover={{ scale: 1.05 }}
934
+ whileTap={{ scale: 0.95 }}
935
+ >
936
+ {isLoading ? 'Registering...' : 'Start Farming (Step 2)'}
937
+ </motion.button>
938
+ {balance === 0 && (
939
+ <p className="text-red-400 text-xs font-pixel mt-3">Need SUI balance to pay gas</p>
940
+ )}
941
+ </div>
942
+ </div>
943
+ );
944
+ }
945
+
946
+ // Main game UI
947
+ return (
948
+ <div
949
+ className="min-h-screen p-4 md:p-6"
950
+ style={{ background: 'radial-gradient(ellipse at top, #1a3a1a 0%, #0a1a0a 100%)' }}
951
+ >
952
+ {/* Header */}
953
+ <div className="flex items-center justify-between mb-4 flex-wrap gap-3">
954
+ <div className="flex items-center gap-2">
955
+ <IconFarm size={28} />
956
+ <h1 className="font-pixel text-amber-300 text-sm">HARVEST</h1>
957
+ </div>
958
+ <div className="flex items-center gap-3">
959
+ {balance > 0 ? (
960
+ <span className="text-xs text-amber-600 font-pixel">{balance.toFixed(3)} SUI</span>
961
+ ) : (
962
+ <span className="text-xs text-red-400 font-pixel">0 SUI — top up needed</span>
963
+ )}
964
+ <ConnectButton />
965
+ </div>
966
+ </div>
967
+
968
+ {balanceWarning}
969
+
970
+ {/* Session key status bar */}
971
+ {state.isRegistered && (
972
+ <div
973
+ className={`mb-4 flex items-center justify-between px-4 py-2 rounded-xl border text-xs font-pixel flex-wrap gap-2
974
+ ${
975
+ sessionActive
976
+ ? 'bg-emerald-900/30 border-emerald-700/40 text-emerald-300'
977
+ : 'bg-amber-900/20 border-amber-700/30 text-amber-500'
978
+ }`}
979
+ >
980
+ {sessionKeypairLoading ? (
981
+ <span className="opacity-60">Loading session key...</span>
982
+ ) : sessionActive ? (
983
+ <>
984
+ <span className="flex items-center gap-2 flex-wrap">
985
+ Session active — {sessionMinutesLeft}m left
986
+ {sessionBalance !== null && (
987
+ <span
988
+ className={`${sessionBalance < 0.01 ? 'text-red-400' : 'text-emerald-400'}`}
989
+ >
990
+ (gas: {sessionBalance.toFixed(4)} SUI)
991
+ </span>
992
+ )}
993
+ {sessionAddress && (
994
+ <span className="flex items-center gap-1 font-mono text-xs text-emerald-400/70">
995
+ <span>
996
+ {sessionAddress.slice(0, 6)}…{sessionAddress.slice(-4)}
997
+ </span>
998
+ <button
999
+ onClick={() => {
1000
+ navigator.clipboard.writeText(sessionAddress);
1001
+ toast.success('Session address copied');
1002
+ }}
1003
+ title="Copy session address"
1004
+ className="hover:text-emerald-300 transition-colors"
1005
+ >
1006
+ <svg
1007
+ xmlns="http://www.w3.org/2000/svg"
1008
+ className="w-3.5 h-3.5"
1009
+ viewBox="0 0 24 24"
1010
+ fill="none"
1011
+ stroke="currentColor"
1012
+ strokeWidth="2"
1013
+ strokeLinecap="round"
1014
+ strokeLinejoin="round"
1015
+ >
1016
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
1017
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
1018
+ </svg>
1019
+ </button>
1020
+ </span>
1021
+ )}
1022
+ </span>
1023
+ <div className="flex items-center gap-2">
1024
+ {sessionBalance !== null && sessionBalance < 0.05 && (
1025
+ <button
1026
+ onClick={() => {
1027
+ if (!userStorageId) return;
1028
+ setIsLoading(true);
1029
+ const tx = buildFundSessionTx(0.1);
1030
+ signAndExecuteTransaction(
1031
+ {
1032
+ transaction: (tx as any).serialize(),
1033
+ chain: `sui:${network ?? 'localnet'}`
1034
+ },
1035
+ {
1036
+ onSuccess: (resp) => {
1037
+ txToast('Topped up 0.1 SUI to session wallet', resp.digest);
1038
+ setTimeout(refresh, 1500);
1039
+ },
1040
+ onError: (e) => toast.error(`Top-up failed: ${e.message}`)
1041
+ }
1042
+ ).finally(() => setIsLoading(false));
1043
+ }}
1044
+ disabled={isLoading}
1045
+ className="bg-amber-700 hover:bg-amber-600 text-white px-2 py-0.5 rounded disabled:opacity-50 transition-colors"
1046
+ >
1047
+ Top up 0.1 SUI
1048
+ </button>
1049
+ )}
1050
+ <button
1051
+ onClick={handleDeactivateSession}
1052
+ className="text-red-400 hover:text-red-300 transition-colors"
1053
+ >
1054
+ Deactivate
1055
+ </button>
1056
+ </div>
1057
+ </>
1058
+ ) : (
1059
+ <>
1060
+ <span>Session inactive — wallet approval required for every action</span>
1061
+ <button
1062
+ onClick={handleActivateSessionDirect}
1063
+ disabled={isLoading || !userStorageId || sessionKeypairLoading}
1064
+ className="ml-4 bg-emerald-700 hover:bg-emerald-600 text-white px-3 py-1 rounded-lg
1065
+ disabled:opacity-50 transition-colors"
1066
+ >
1067
+ Activate Session (1h)
1068
+ </button>
1069
+ </>
1070
+ )}
1071
+ </div>
1072
+ )}
1073
+ <div className="mb-6">
1074
+ <ResourceHUD
1075
+ gold={state.gold}
1076
+ inventory={inventory}
1077
+ seedInventory={seedInventory}
1078
+ plotsOwned={state.plotsOwned}
1079
+ />
1080
+ </div>
1081
+
1082
+ {/* Navigation */}
1083
+ <div className="flex gap-2 mb-6">
1084
+ {(['Farm', 'Market', 'Leaderboard'] as const).map((tab) => (
1085
+ <a
1086
+ key={tab}
1087
+ href={tab === 'Farm' ? '/' : `/${tab.toLowerCase()}`}
1088
+ className="px-4 py-2 text-xs font-pixel rounded-lg bg-amber-900/40 hover:bg-amber-800/40
1089
+ text-amber-400 hover:text-amber-200 transition-colors border border-amber-700/30"
1090
+ >
1091
+ {tab}
1092
+ </a>
1093
+ ))}
1094
+ </div>
1095
+
1096
+ {/* Main layout */}
1097
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
1098
+ <div className="lg:col-span-2 space-y-3">
1099
+ {/* Farm / Ranch view toggle */}
1100
+ <div className="flex gap-1.5">
1101
+ {(['farm', 'ranch'] as const).map((v) => (
1102
+ <button
1103
+ key={v}
1104
+ onClick={() => setMainView(v)}
1105
+ className={`px-4 py-1.5 text-xs font-pixel rounded-lg border transition-colors capitalize
1106
+ ${
1107
+ mainView === v
1108
+ ? 'bg-amber-700 border-amber-500 text-amber-100'
1109
+ : 'bg-amber-900/30 border-amber-700/30 text-amber-500 hover:bg-amber-800/40 hover:text-amber-300'
1110
+ }`}
1111
+ >
1112
+ {v === 'farm' ? 'Farm' : 'Ranch'}
1113
+ </button>
1114
+ ))}
1115
+ </div>
1116
+
1117
+ {mainView === 'farm' && (
1118
+ <FarmLand
1119
+ plots={state.plots}
1120
+ plotsOwned={state.plotsOwned}
1121
+ inventory={seedInventory}
1122
+ now={Date.now()}
1123
+ isLoading={isLoading}
1124
+ onPlant={handlePlant}
1125
+ onHarvest={handleHarvest}
1126
+ />
1127
+ )}
1128
+
1129
+ {mainView === 'ranch' && (
1130
+ <RanchLand
1131
+ inventory={petInv}
1132
+ cropInventory={inventory}
1133
+ isLoading={isLoading || balance === 0}
1134
+ onFeedPet={handleFeedPet}
1135
+ onAssignSlot={handleAssignSlot}
1136
+ onUnassignSlot={handleUnassignSlot}
1137
+ onDismissPet={handleDismissPet}
1138
+ onListPet={handleListPet}
1139
+ onBuySlot={handleBuyPetSlot}
1140
+ />
1141
+ )}
1142
+ </div>
1143
+ <div className="space-y-4">
1144
+ <ShopPanel
1145
+ gold={state.gold}
1146
+ inventory={seedInventory}
1147
+ cropInventory={inventory}
1148
+ plotsOwned={state.plotsOwned}
1149
+ onBuySeeds={handleBuySeeds}
1150
+ onBuyPlot={handleBuyPlot}
1151
+ onSellCrops={handleSellCrops}
1152
+ isLoading={isLoading || balance === 0}
1153
+ />
1154
+ <PetPanel
1155
+ gold={state.gold}
1156
+ inventory={petInv}
1157
+ cropInventory={inventory}
1158
+ isLoading={isLoading || balance === 0}
1159
+ onBuyEgg={handleBuyEgg}
1160
+ onStartHatch={handleStartHatch}
1161
+ onOpenEgg={handleOpenEgg}
1162
+ onFeedPet={handleFeedPet}
1163
+ onBuySlot={handleBuyPetSlot}
1164
+ onDismissPet={handleDismissPet}
1165
+ onListPet={handleListPet}
1166
+ onAssignSlot={handleAssignSlot}
1167
+ onUnassignSlot={handleUnassignSlot}
1168
+ />
1169
+ </div>
1170
+ </div>
1171
+ </div>
1172
+ );
1173
+ }