-
Notifications
You must be signed in to change notification settings - Fork 2
Frontend v2 on Solana — new pet instructions, VRF recovery, and UI overhaul #60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
48 commits
Select commit
Hold shift + click to select a range
ba81082
chore: update solana program id to fresh address
tydreamer 3234068
feat: map v2 pet fields from PetAccount in mapSolanaPet
tydreamer 271bdaf
feat: resolve parent2Owner for cross-owner (married) breeding
tydreamer 25e92f7
feat: parse BattleResolved event and return firstWins from settle tx
tydreamer 3e634a0
feat: resolve explorer URL from connection rpcEndpoint
tydreamer 8a0d5f1
feat: expose awaiting-vrf lifecycle phase during oracle wait
tydreamer 4370202
feat: add pending VRF recovery notices for battle and breed
tydreamer a7a0875
feat: add openToChallenges field, toggle action, and UI
tydreamer b6015f3
feat: add cancel_battle and cancel_breed support
tydreamer d153e9c
feat: add sync_metadata instruction support
tydreamer ace2352
feat: add stud fee balance reader and withdraw_stud_fees action
tydreamer 98626a1
feat: mainnet readiness — cluster-aware VRF timing
tydreamer 467a01d
test: add tests for new Solana hooks and update adapter mock
tydreamer 1d728b5
fix: wire VITE_CRYPTOPETS_IDL_ADDRESS into Program.fetchIdl
tydreamer 7808f8d
fix: split IDL fetch from Program instantiation to prevent re-fetch o…
tydreamer 31bfc8f
fix: restore idlAddress fallback in useProgram IDL fetch
tydreamer 54fa578
fix: pass programId to fetchIdl so the on-chain IDL resolves
tydreamer 9e43987
chore: add initialize script for fresh program deployments
tydreamer e772e71
fix(solana): drop update_authority from mpl-core asset create CPI
tydreamer 559c1ad
feat(solana): add transfer_pet instruction and wire pet sending
tydreamer 22378c0
feat: drop backend sign-in requirement for first pet
tydreamer 1f7fc08
feat: allow custom devnet RPC via VITE_SOLANA_DEVNET_RPC_URL
tydreamer f332333
fix: make interactions panel body scroll in all layouts
tydreamer acfcf52
feat: two-lane matchmaking layout for fighter/opponent pickers
tydreamer 7fce4b2
feat: flank the VS arena with fighter/opponent lanes
tydreamer af4e50b
fix: widen the standalone battle stage to use the full row
tydreamer dd96f76
fix: stretch the battle stage to fill the panel height
tydreamer 693a31f
ui: redesign arena card with card shell, grid layout, and directional…
tydreamer 8fc38e7
fix: auto-select married Solana pet and show spouse indicator in bree…
tydreamer 541661d
test: update breed panel tests for married-pet auto-select and spouse…
tydreamer 17c5eec
test: fix pre-existing CI failures in battle-setup and level-up tests
tydreamer f76e041
chore: prune unused deps and tidy marriage/account-dropdown
tydreamer 2a23ab9
refactor: move formatExpiry to shared/utils/common/time
tydreamer 490cde4
refactor: split marriage panel into focused part components
tydreamer bb49df9
chore(frontend): add prettier + editorconfig and reformat src
tydreamer 5b9facc
refactor(frontend): colocate interaction panel CSS
tydreamer 20bbb96
refactor: extract shared useSpousePet hook
tydreamer d38654b
refactor: decompose breed panel into parts
tydreamer 8a88e84
refactor: extract usePetCooldowns hook from pet-gallery
tydreamer 6265b7e
feat: extend NeonButton palette for app-wide adoption
tydreamer fa654d3
refactor: render auth action buttons via NeonButton
tydreamer 7ff47cb
refactor: migrate dashboard hub buttons to NeonButton
tydreamer 9689b54
fix(shared): add missing chainId deps to settle callbacks
tydreamer d9147ea
refactor: migrate remaining action buttons to NeonButton
tydreamer 45d8bde
refactor: migrate send-pet modal to NeonModal
tydreamer 8f352eb
refactor(frontend): migrate create-pet modal to NeonModal
tydreamer 43f6726
refactor(frontend): migrate marriage confirm dialog to NeonModal
tydreamer ad01d38
chore: tighten no-explicit-any rule and drop dead CSS
tydreamer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 85 additions & 0 deletions
85
contracts/solana/cryptopets/programs/cryptopets/src/instructions/pet/transfer_pet.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| use anchor_lang::prelude::*; | ||
| use mpl_core::instructions::TransferV1CpiBuilder; | ||
|
|
||
| use crate::{ | ||
| errors::ErrorCode, | ||
| state::{GlobalState, PetAccount}, | ||
| utils::metadata::core_asset_owner, | ||
| }; | ||
|
|
||
| /// Transfer a pet to another wallet. Pets are Metaplex Core assets and the asset is the | ||
| /// source of truth for ownership (plan §2.3/v2.1 Phase A), so this CPIs mpl-core | ||
| /// `TransferV1` to move the asset and then updates the denormalized `pet.owner` snapshot so | ||
| /// owner-filtered queries (the gallery's `getProgramAccounts` memcmp on `owner`) keep | ||
| /// finding the pet under its new owner. | ||
| /// | ||
| /// A married pet is refused: the spouse's `PetAccount` cross-references this pet (and its | ||
| /// `marriage_owner_snapshot` drives cross-owner breeding), so it must be divorced first. | ||
| pub fn handler(ctx: Context<TransferPet>) -> Result<()> { | ||
| require!(!ctx.accounts.global_state.paused, ErrorCode::Paused); | ||
|
|
||
| // Only the current (live) asset owner may transfer. mpl-core re-checks this in the CPI; | ||
| // we assert it up front for a clean error and to gate the `pet.owner` write below. | ||
| require_keys_eq!( | ||
| core_asset_owner(&ctx.accounts.pet_asset.to_account_info())?, | ||
| ctx.accounts.owner.key(), | ||
| ErrorCode::Unauthorized | ||
| ); | ||
|
|
||
| require!( | ||
| ctx.accounts.pet.spouse_id == 0, | ||
| ErrorCode::CannotTransferMarriedPet | ||
| ); | ||
|
|
||
| // mpl-core CPI: move the Core asset to `new_owner`. The asset's owner is the transfer | ||
| // authority and signs the outer transaction, so this is `invoke()` (no PDA seeds). The | ||
| // collection must be supplied for collection-scoped assets; it is not mutated here. | ||
| TransferV1CpiBuilder::new(&ctx.accounts.mpl_core_program.to_account_info()) | ||
| .asset(&ctx.accounts.pet_asset.to_account_info()) | ||
| .collection(Some(&ctx.accounts.collection.to_account_info())) | ||
| .payer(&ctx.accounts.owner.to_account_info()) | ||
| .authority(Some(&ctx.accounts.owner.to_account_info())) | ||
| .new_owner(&ctx.accounts.new_owner.to_account_info()) | ||
| .system_program(Some(&ctx.accounts.system_program.to_account_info())) | ||
| .invoke()?; | ||
|
|
||
| // Keep the cached owner in sync with the asset's new owner. | ||
| ctx.accounts.pet.owner = ctx.accounts.new_owner.key(); | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| #[derive(Accounts)] | ||
| pub struct TransferPet<'info> { | ||
| #[account(seeds = [GlobalState::SEED], bump = global_state.bump)] | ||
| pub global_state: Account<'info, GlobalState>, | ||
|
|
||
| /// CHECK: this pet's Metaplex Core asset account; PDA seed for `pet` and source of | ||
| /// truth for ownership (plan §2.3/v2.1 Phase A). Mutated by the `TransferV1` CPI. | ||
| #[account(mut, owner = mpl_core::ID)] | ||
| pub pet_asset: UncheckedAccount<'info>, | ||
|
|
||
| #[account( | ||
| mut, | ||
| seeds = [PetAccount::SEED, pet_asset.key().as_ref()], | ||
| bump = pet.bump, | ||
| )] | ||
| pub pet: Account<'info, PetAccount>, | ||
|
|
||
| /// CHECK: the "CryptoPets" collection account (`global_state.collection`); supplied to | ||
| /// the `TransferV1` CPI to validate collection membership. Not mutated. | ||
| #[account(address = global_state.collection)] | ||
| pub collection: UncheckedAccount<'info>, | ||
|
|
||
| /// CHECK: recipient wallet that receives the Core asset. Any pubkey is valid. | ||
| pub new_owner: UncheckedAccount<'info>, | ||
|
|
||
| #[account(mut)] | ||
| pub owner: Signer<'info>, | ||
|
|
||
| /// CHECK: address-constrained to the Metaplex Core program; invoked via CPI. | ||
| #[account(address = mpl_core::ID)] | ||
| pub mpl_core_program: UncheckedAccount<'info>, | ||
|
|
||
| pub system_program: Program<'info, System>, | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| #!/usr/bin/env tsx | ||
| // | ||
| // One-time on-chain setup for a freshly deployed `cryptopets` program: runs the | ||
| // `initialize` instruction, which creates the `global-state` PDA (admin + fee | ||
| // config + next_pet_id) and the Metaplex Core collection that every pet is | ||
| // minted into. Without this, mint/breed/battle and pet loading have nothing to | ||
| // read or write, so the frontend shows an empty list and create fails. | ||
| // | ||
| // Idempotent: if `global-state` already exists it prints the current config and | ||
| // exits without sending a transaction. | ||
| // | ||
| // Usage (devnet): | ||
| // ANCHOR_PROVIDER_URL=https://api.devnet.solana.com \ | ||
| // ANCHOR_WALLET=$HOME/.config/solana/id.json \ | ||
| // pnpm exec tsx scripts/initialize.ts | ||
| // | ||
| // The wallet must be the intended program admin and hold a little devnet SOL. | ||
|
|
||
| import * as anchor from "@coral-xyz/anchor"; | ||
| import { globalStatePda } from "../tests/utils"; | ||
|
|
||
| // Defaults to the program id pinned in Anchor.toml ([programs.devnet]); override | ||
| // with PROGRAM_ID=... if you deploy under a different key. | ||
| const PROGRAM_ID = new anchor.web3.PublicKey( | ||
| process.env.PROGRAM_ID ?? "88HGagCw4i3BTMHEpdQy3YLeHrkTSKgXmvq66HJXKM7k", | ||
| ); | ||
|
|
||
| // mpl-core program (stable across clusters). Required by the `initialize` CPI | ||
| // that creates the collection. | ||
| const MPL_CORE_PROGRAM_ID = new anchor.web3.PublicKey( | ||
| "CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d", | ||
| ); | ||
|
|
||
| // Initial level-up fee in lamports; matches the test default. Tune via | ||
| // LEVEL_UP_FEE_LAMPORTS=... or adjust later with the `set_level_up_fee_lamports` | ||
| // admin instruction. | ||
| const LEVEL_UP_FEE_LAMPORTS = new anchor.BN( | ||
| process.env.LEVEL_UP_FEE_LAMPORTS ?? 1_000_000, | ||
| ); | ||
|
|
||
| async function main() { | ||
| const provider = anchor.AnchorProvider.env(); | ||
| anchor.setProvider(provider); | ||
| const wallet = provider.wallet as anchor.Wallet; | ||
|
|
||
| const idl = await anchor.Program.fetchIdl(PROGRAM_ID, provider); | ||
| if (!idl) { | ||
| throw new Error( | ||
| `IDL not found on-chain for ${PROGRAM_ID.toBase58()}. Deploy the program and run \`anchor idl init\` first.`, | ||
| ); | ||
| } | ||
| const program = new anchor.Program(idl, provider); | ||
|
|
||
| const [globalState] = globalStatePda(PROGRAM_ID); | ||
|
|
||
| console.log("program :", PROGRAM_ID.toBase58()); | ||
| console.log("admin :", wallet.publicKey.toBase58()); | ||
| console.log("cluster :", provider.connection.rpcEndpoint); | ||
| console.log("globalState:", globalState.toBase58()); | ||
|
|
||
| const existing = await provider.connection.getAccountInfo(globalState); | ||
| if (existing) { | ||
| const gs = await (program.account as any).globalState.fetch(globalState); | ||
| console.log("\n✅ Already initialized — nothing to do."); | ||
| console.log(" collection :", gs.collection.toBase58()); | ||
| console.log(" admin :", gs.admin.toBase58()); | ||
| console.log(" nextPetId :", gs.nextPetId); | ||
| console.log(" paused :", gs.paused); | ||
| return; | ||
| } | ||
|
|
||
| const collection = anchor.web3.Keypair.generate(); | ||
| console.log("\nInitializing… new collection:", collection.publicKey.toBase58()); | ||
|
|
||
| const sig = await program.methods | ||
| .initialize(LEVEL_UP_FEE_LAMPORTS) | ||
| .accounts({ | ||
| globalState, | ||
| collection: collection.publicKey, | ||
| admin: wallet.publicKey, | ||
| mplCoreProgram: MPL_CORE_PROGRAM_ID, | ||
| }) | ||
| .signers([collection]) | ||
| .rpc(); | ||
|
|
||
| console.log("\n✅ Initialized."); | ||
| console.log(" tx :", sig); | ||
| console.log(" collection :", collection.publicKey.toBase58()); | ||
| console.log( | ||
| "\nSave the collection address — it lives in global-state and is used by", | ||
| "every mint/sync. No env change is needed; the frontend reads it on-chain.", | ||
| ); | ||
| } | ||
|
|
||
| main().catch((err) => { | ||
| console.error("\n❌ initialize failed:", err instanceof Error ? err.message : err); | ||
| process.exit(1); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tydreamer it looks weird to have this here :)
can you please remove the value?
so the actual value will be in .env.staging and .env.production