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.
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/package.json +16 -16
- package/templates/101/sui-template/docker-compose.testnet.yml +73 -0
- package/templates/101/sui-template/packages/client/src/app/page.tsx +545 -41
- package/templates/101/sui-template/packages/client/src/app/providers.tsx +12 -4
- package/templates/101/sui-template/packages/contracts/Pub.localnet.toml +4 -4
- package/templates/101/sui-template/packages/contracts/counter.lock.json +20 -0
- package/templates/101/sui-template/packages/contracts/deployment.ts +8 -3
- package/templates/101/sui-template/packages/contracts/dubhe.config.json +59 -13
- package/templates/101/sui-template/packages/contracts/dubhe.config.ts +1 -4
- package/templates/101/sui-template/packages/contracts/metadata.json +1015 -68
- package/templates/101/sui-template/packages/contracts/package.json +7 -4
- package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/dapp_key.move +45 -31
- package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/error.move +8 -0
- package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/genesis.move +30 -13
- package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/init_test.move +19 -14
- package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/resources/counter2.move +198 -0
- package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/resources/counter2withkey.move +90 -0
- package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/resources/value.move +30 -20
- package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/user_storage_init.move +26 -0
- package/templates/101/sui-template/packages/contracts/src/counter/sources/scripts/deploy_hook.move +6 -11
- package/templates/101/sui-template/packages/contracts/src/counter/sources/systems/counter.move +23 -11
- package/templates/101/sui-template/packages/contracts/src/counter/sources/tests/counter.move +15 -14
- package/templates/101/sui-template/packages/contracts/src/dubhe/Move.lock +33 -30
- package/templates/101/sui-template/packages/contracts/src/dubhe/Move.toml +4 -3
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/dapp_key.move +45 -31
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/error.move +170 -0
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/genesis.move +16 -13
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/init_test.move +8 -14
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/core/account_key.move +1 -3
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/core/dapp_service.move +1776 -137
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/core/data_key.move +1 -20
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/core/events.move +864 -25
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/scripts/deploy_hook.move +15 -16
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/scripts/migrate.move +0 -3
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/systems/address_system.move +25 -17
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/systems/dapp_system.move +2802 -343
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/systems/utils.move +1 -1
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/address.move +173 -55
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/dapp_test.move +769 -0
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/fee_test.move +2965 -0
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/integration_test.move +303 -0
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/listing_test.move +1422 -0
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/scene_session_test.move +269 -0
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/session_test.move +624 -0
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/storage_test.move +700 -0
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/typed_object_test.move +177 -0
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/typed_scene_test.move +452 -0
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/tests/user_storage_test.move +160 -0
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/utils/math.move +4 -3
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/utils/type_info.move +2 -2
- package/templates/contract/sui-template/dubhe.config.ts +0 -1
- package/templates/contract/sui-template/package.json +3 -3
- package/templates/contract/sui-template/src/dubhe/Move.lock +33 -30
- package/templates/contract/sui-template/src/dubhe/Move.toml +4 -3
- package/templates/contract/sui-template/src/dubhe/sources/codegen/dapp_key.move +45 -31
- package/templates/contract/sui-template/src/dubhe/sources/codegen/error.move +170 -0
- package/templates/contract/sui-template/src/dubhe/sources/codegen/genesis.move +16 -13
- package/templates/contract/sui-template/src/dubhe/sources/codegen/init_test.move +8 -14
- package/templates/contract/sui-template/src/dubhe/sources/core/account_key.move +1 -3
- package/templates/contract/sui-template/src/dubhe/sources/core/dapp_service.move +1776 -137
- package/templates/contract/sui-template/src/dubhe/sources/core/data_key.move +1 -20
- package/templates/contract/sui-template/src/dubhe/sources/core/events.move +864 -25
- package/templates/contract/sui-template/src/dubhe/sources/scripts/deploy_hook.move +15 -16
- package/templates/contract/sui-template/src/dubhe/sources/scripts/migrate.move +0 -3
- package/templates/contract/sui-template/src/dubhe/sources/systems/address_system.move +25 -17
- package/templates/contract/sui-template/src/dubhe/sources/systems/dapp_system.move +2802 -343
- package/templates/contract/sui-template/src/dubhe/sources/systems/utils.move +1 -1
- package/templates/contract/sui-template/src/dubhe/sources/tests/address.move +173 -55
- package/templates/contract/sui-template/src/dubhe/sources/tests/dapp_test.move +769 -0
- package/templates/contract/sui-template/src/dubhe/sources/tests/fee_test.move +2965 -0
- package/templates/contract/sui-template/src/dubhe/sources/tests/integration_test.move +303 -0
- package/templates/contract/sui-template/src/dubhe/sources/tests/listing_test.move +1422 -0
- package/templates/contract/sui-template/src/dubhe/sources/tests/scene_session_test.move +269 -0
- package/templates/contract/sui-template/src/dubhe/sources/tests/session_test.move +624 -0
- package/templates/contract/sui-template/src/dubhe/sources/tests/storage_test.move +700 -0
- package/templates/contract/sui-template/src/dubhe/sources/tests/typed_object_test.move +177 -0
- package/templates/contract/sui-template/src/dubhe/sources/tests/typed_scene_test.move +452 -0
- package/templates/contract/sui-template/src/dubhe/sources/tests/user_storage_test.move +160 -0
- package/templates/contract/sui-template/src/dubhe/sources/utils/math.move +4 -3
- package/templates/contract/sui-template/src/dubhe/sources/utils/type_info.move +2 -2
- package/templates/contract/sui-template/src/template/Move.toml +12 -0
- package/templates/contract/sui-template/src/template/sources/codegen/dapp_key.move +47 -0
- package/templates/contract/sui-template/src/template/sources/codegen/genesis.move +34 -0
- package/templates/contract/sui-template/src/template/sources/codegen/init_test.move +23 -0
- package/templates/contract/sui-template/src/template/sources/codegen/user_storage_init.move +27 -0
- package/templates/contract/sui-template/src/template/sources/scripts/deploy_hook.move +13 -0
- package/templates/contract/sui-template/src/template/sources/scripts/migrate.move +7 -0
- package/templates/nextjs/sui-card-duel/.gitignore_ +51 -0
- package/templates/nextjs/sui-card-duel/README.md +213 -0
- package/templates/nextjs/sui-card-duel/docker-compose.yml +34 -0
- package/templates/nextjs/sui-card-duel/mprocs.yaml +17 -0
- package/templates/nextjs/sui-card-duel/package.json +43 -0
- package/templates/nextjs/sui-card-duel/packages/client/.gitignore_ +48 -0
- package/templates/nextjs/sui-card-duel/packages/client/.prettierignore +1 -0
- package/templates/nextjs/sui-card-duel/packages/client/.prettierrc.json +8 -0
- package/templates/nextjs/sui-card-duel/packages/client/eslint.config.mjs +22 -0
- package/templates/nextjs/sui-card-duel/packages/client/next.config.mjs +8 -0
- package/templates/nextjs/sui-card-duel/packages/client/package.json +47 -0
- package/templates/nextjs/sui-card-duel/packages/client/postcss.config.mjs +8 -0
- package/templates/nextjs/sui-card-duel/packages/client/src/app/admin/ArenaPanel.tsx +307 -0
- package/templates/nextjs/sui-card-duel/packages/client/src/app/admin/page.tsx +622 -0
- package/templates/nextjs/sui-card-duel/packages/client/src/app/brawl/[id]/page.tsx +559 -0
- package/templates/nextjs/sui-card-duel/packages/client/src/app/collection/page.tsx +202 -0
- package/templates/nextjs/sui-card-duel/packages/client/src/app/components/CardView.tsx +66 -0
- package/templates/nextjs/sui-card-duel/packages/client/src/app/components/GameShell.tsx +294 -0
- package/templates/nextjs/sui-card-duel/packages/client/src/app/components/icons.tsx +101 -0
- package/templates/nextjs/sui-card-duel/packages/client/src/app/duel/[id]/page.tsx +477 -0
- package/templates/nextjs/sui-card-duel/packages/client/src/app/favicon.ico +0 -0
- package/templates/nextjs/sui-card-duel/packages/client/src/app/globals.css +85 -0
- package/templates/nextjs/sui-card-duel/packages/client/src/app/hooks/useGame.ts +313 -0
- package/templates/nextjs/sui-card-duel/packages/client/src/app/layout.tsx +24 -0
- package/templates/nextjs/sui-card-duel/packages/client/src/app/leaderboard/page.tsx +123 -0
- package/templates/nextjs/sui-card-duel/packages/client/src/app/lib/game.ts +77 -0
- package/templates/nextjs/sui-card-duel/packages/client/src/app/lib/scenes.ts +226 -0
- package/templates/nextjs/sui-card-duel/packages/client/src/app/market/page.tsx +418 -0
- package/templates/nextjs/sui-card-duel/packages/client/src/app/page.tsx +427 -0
- package/templates/nextjs/sui-card-duel/packages/client/src/app/providers.tsx +69 -0
- package/templates/nextjs/sui-card-duel/packages/client/tailwind.config.ts +21 -0
- package/templates/nextjs/sui-card-duel/packages/client/tsconfig.json +30 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/.gitignore_ +51 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/.prettierignore +1 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/.prettierrc.json +8 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/Dockerfile +5 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/Pub.localnet.toml +13 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/card_duel.lock.json +66 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/docker-compose.yml +34 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/dubhe.config.ts +150 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/logs/combined.log +31383 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/package.json +49 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/scripts/config.ts +17 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-brawl.ts +270 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-duel.ts +272 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-lobby.ts +177 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-market.ts +219 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/scripts/e2e-session.ts +342 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/scripts/query-dapp-revenue.ts +95 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/scripts/seed.ts +92 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/scripts/withdraw-dapp-revenue.ts +56 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/Move.toml +15 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/dapp_key.move +46 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/enums/card_kind.move +47 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/enums/match_state.move +37 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/enums/rarity.move +37 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/error.move +126 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/genesis.move +33 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/init_test.move +23 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/objects/arena.move +137 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/permits/brawl_permit.move +125 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/permits/duel_permit.move +125 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/battle_state.move +255 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/card.move +282 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/deck.move +64 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/game_config.move +267 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/gold.move +205 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/resources/profile.move +255 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/scenes/brawl.move +313 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/scenes/duel.move +313 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/codegen/user_storage_init.move +26 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/scripts/deploy_hook.move +32 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/scripts/migrate.move +7 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/arena_system.move +57 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/brawl_system.move +559 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/card_system.move +146 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/duel_system.move +515 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/market_system.move +115 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/systems/player_system.move +37 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/tests/brawl_tests.move +385 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/tests/duel_tests.move +656 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/src/card_duel/sources/tests/test_helpers.move +31 -0
- package/templates/nextjs/sui-card-duel/packages/contracts/sui-indexer-schema.graphql +5973 -0
- package/templates/nextjs/sui-card-duel/pnpm-workspace.yaml +2 -0
- package/templates/nextjs/sui-farm/.gitignore_ +51 -0
- package/templates/nextjs/sui-farm/README.md +15 -0
- package/templates/nextjs/sui-farm/UPGRADE_NOTES.md +97 -0
- package/templates/nextjs/sui-farm/docker-compose.yml +34 -0
- package/templates/nextjs/sui-farm/mprocs.yaml +17 -0
- package/templates/nextjs/sui-farm/package.json +43 -0
- package/templates/nextjs/sui-farm/packages/client/.gitignore_ +48 -0
- package/templates/nextjs/sui-farm/packages/client/.prettierignore +1 -0
- package/templates/nextjs/sui-farm/packages/client/.prettierrc.json +8 -0
- package/templates/nextjs/sui-farm/packages/client/eslint.config.mjs +22 -0
- package/templates/nextjs/sui-farm/packages/client/next.config.mjs +8 -0
- package/templates/nextjs/sui-farm/packages/client/package.json +47 -0
- package/templates/nextjs/sui-farm/packages/client/postcss.config.mjs +8 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/admin/page.tsx +618 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/components/FarmLand.tsx +526 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/components/FarmPlotCard.tsx +215 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/components/PetAvatar.tsx +868 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/components/PetPanel.tsx +1360 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/components/PlantModal.tsx +147 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/components/ProxyCard.tsx +684 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/components/RanchLand.tsx +822 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/components/ResourceHUD.tsx +125 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/components/ShopPanel.tsx +232 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/components/icons/GameIcons.tsx +574 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/favicon.ico +0 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/globals.css +423 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/hooks/useWorldPermitId.ts +66 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/layout.tsx +23 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/leaderboard/page.tsx +185 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/lib/crops.ts +97 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/market/page.tsx +763 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/page.tsx +1173 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/providers.tsx +69 -0
- package/templates/nextjs/sui-farm/packages/client/src/app/state/index.tsx +5 -0
- package/templates/nextjs/sui-farm/packages/client/tailwind.config.ts +25 -0
- package/templates/nextjs/sui-farm/packages/client/tsconfig.json +30 -0
- package/templates/nextjs/sui-farm/packages/contracts/.gitignore_ +51 -0
- package/templates/nextjs/sui-farm/packages/contracts/.prettierignore +1 -0
- package/templates/nextjs/sui-farm/packages/contracts/.prettierrc.json +8 -0
- package/templates/nextjs/sui-farm/packages/contracts/Dockerfile +5 -0
- package/templates/nextjs/sui-farm/packages/contracts/Pub.localnet.toml +13 -0
- package/templates/nextjs/sui-farm/packages/contracts/counter.lock.json +14 -0
- package/templates/nextjs/sui-farm/packages/contracts/docker-compose.yml +34 -0
- package/templates/nextjs/sui-farm/packages/contracts/dubhe.config.ts +178 -0
- package/templates/nextjs/sui-farm/packages/contracts/harvest.lock.json +120 -0
- package/templates/nextjs/sui-farm/packages/contracts/package.json +48 -0
- package/templates/nextjs/sui-farm/packages/contracts/scripts/config.ts +17 -0
- package/templates/nextjs/sui-farm/packages/contracts/scripts/query-dapp-revenue.ts +95 -0
- package/templates/nextjs/sui-farm/packages/contracts/scripts/read-permit.ts +72 -0
- package/templates/nextjs/sui-farm/packages/contracts/scripts/seed.ts +85 -0
- package/templates/nextjs/sui-farm/packages/contracts/scripts/withdraw-dapp-revenue.ts +56 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/counter/Move.toml +14 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/codegen/dapp_key.move +31 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/codegen/error.move +10 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/codegen/genesis.move +33 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/codegen/init_test.move +23 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/codegen/resources/counter1.move +64 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/codegen/resources/counter2.move +70 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/codegen/user_storage_init.move +26 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/scripts/deploy_hook.move +8 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/scripts/migrate.move +8 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/systems/counter.move +13 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/counter/sources/tests/counter.move +28 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/Move.toml +14 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/dapp_key.move +46 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/enums/crop_type.move +47 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/enums/egg_type.move +42 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/enums/pet_rarity.move +37 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/enums/pet_species.move +52 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/error.move +110 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/genesis.move +33 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/init_test.move +23 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/permits/world.move +125 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/carrot.move +136 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/carrot_seed.move +80 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/common_egg.move +136 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/corn.move +136 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/corn_seed.move +80 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/crow_charges.move +162 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/crow_damage.move +207 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/farm_plot.move +261 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/gold.move +80 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/pet.move +467 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/pet_config.move +372 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/pet_hatch.move +162 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/pet_slot_index.move +83 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/pet_slots.move +64 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/profile.move +162 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/pumpkin.move +136 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/pumpkin_seed.move +80 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/rare_egg.move +136 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/scarecrow.move +64 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/season_config.move +197 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/season_stats.move +64 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/seasonal_egg.move +136 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/shop_config.move +267 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/trophy.move +245 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/wheat.move +136 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/wheat_seed.move +80 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/resources/world_permit_id.move +64 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/codegen/user_storage_init.move +26 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/scripts/deploy_hook.move +45 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/scripts/migrate.move +7 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/systems/crow_system.move +96 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/systems/farm_system.move +182 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/systems/market_system.move +172 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/systems/pet_system.move +495 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/systems/season_system.move +70 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/systems/shop_system.move +112 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/systems/world_system.move +34 -0
- package/templates/nextjs/sui-farm/packages/contracts/src/harvest/sources/tests/farm_tests.move +1192 -0
- package/templates/nextjs/sui-farm/pnpm-workspace.yaml +2 -0
- package/templates/nextjs/sui-template/packages/client/src/app/components/ProxyCard.tsx +683 -0
- package/templates/nextjs/sui-template/packages/client/src/app/page.tsx +510 -5
- package/templates/nextjs/sui-template/packages/client/src/app/providers.tsx +13 -5
- package/templates/nextjs/sui-template/packages/contracts/Pub.localnet.toml +4 -4
- package/templates/nextjs/sui-template/packages/contracts/counter.lock.json +14 -0
- package/templates/nextjs/sui-template/packages/contracts/deployment.ts +8 -3
- package/templates/nextjs/sui-template/packages/contracts/dubhe.config.json +9 -9
- package/templates/nextjs/sui-template/packages/contracts/dubhe.config.ts +0 -4
- package/templates/nextjs/sui-template/packages/contracts/metadata.json +180 -124
- package/templates/nextjs/sui-template/packages/contracts/package.json +8 -4
- package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/dapp_key.move +45 -31
- package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/error.move +10 -0
- package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/genesis.move +30 -13
- package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/init_test.move +19 -14
- package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/resources/counter1.move +30 -20
- package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/resources/counter2.move +35 -24
- package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/user_storage_init.move +26 -0
- package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/scripts/deploy_hook.move +6 -10
- package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/systems/counter.move +7 -20
- package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/tests/counter.move +14 -27
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/Move.lock +33 -30
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/Move.toml +4 -3
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/dapp_key.move +45 -31
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/error.move +170 -0
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/genesis.move +16 -13
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/init_test.move +8 -14
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/core/account_key.move +1 -3
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/core/dapp_service.move +1776 -137
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/core/data_key.move +1 -20
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/core/events.move +864 -25
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/scripts/deploy_hook.move +15 -16
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/scripts/migrate.move +0 -3
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/systems/address_system.move +25 -17
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/systems/dapp_system.move +2802 -343
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/systems/utils.move +1 -1
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/address.move +173 -55
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/dapp_test.move +769 -0
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/fee_test.move +2965 -0
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/integration_test.move +303 -0
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/listing_test.move +1422 -0
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/scene_session_test.move +269 -0
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/session_test.move +624 -0
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/storage_test.move +700 -0
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/typed_object_test.move +177 -0
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/typed_scene_test.move +452 -0
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/tests/user_storage_test.move +160 -0
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/utils/math.move +4 -3
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/utils/type_info.move +2 -2
- package/templates/101/sui-template/packages/contracts/src/counter/Move.lock +0 -41
- package/templates/101/sui-template/packages/contracts/src/counter/Published.toml +0 -9
- package/templates/101/sui-template/packages/contracts/src/counter/sources/codegen/errors.move +0 -12
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/errors.move +0 -140
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dapp_fee_config.move +0 -186
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dapp_fee_state.move +0 -306
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dapp_metadata.move +0 -396
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dubhe_config.move +0 -246
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/core/dapp_store.move +0 -29
- package/templates/101/sui-template/packages/contracts/src/dubhe/sources/systems/session_system.move +0 -44
- package/templates/contract/sui-template/src/dubhe/sources/codegen/errors.move +0 -140
- package/templates/contract/sui-template/src/dubhe/sources/codegen/resources/dapp_fee_config.move +0 -186
- package/templates/contract/sui-template/src/dubhe/sources/codegen/resources/dapp_fee_state.move +0 -306
- package/templates/contract/sui-template/src/dubhe/sources/codegen/resources/dapp_metadata.move +0 -396
- package/templates/contract/sui-template/src/dubhe/sources/codegen/resources/dubhe_config.move +0 -246
- package/templates/contract/sui-template/src/dubhe/sources/core/dapp_store.move +0 -29
- package/templates/contract/sui-template/src/dubhe/sources/systems/session_system.move +0 -44
- package/templates/nextjs/sui-template/packages/contracts/src/counter/Move.lock +0 -47
- package/templates/nextjs/sui-template/packages/contracts/src/counter/Published.toml +0 -9
- package/templates/nextjs/sui-template/packages/contracts/src/counter/sources/codegen/errors.move +0 -12
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/errors.move +0 -140
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dapp_fee_config.move +0 -186
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dapp_fee_state.move +0 -306
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dapp_metadata.move +0 -396
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/codegen/resources/dubhe_config.move +0 -246
- package/templates/nextjs/sui-template/packages/contracts/src/dubhe/sources/core/dapp_store.move +0 -29
- 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
|
+
}
|