Is it possible to fetch by type?

Hi all,

I recently deployed the shared_tic_tac_toe.move example and am builing a FE around it. The game awards the winner of the match with a Trophy object. Is it possible to fetch all objects by type? This way I could create a leaderboard with how many trophies each address has won.

The type looks as follows on the sui explorer:
[packageAddress]::shared_tic_tac_toe::Trophy

However I do not see a type field in the possible arguments within the Sui TS SDK functions. Anyone know if this is possible?

11 Likes

We don’t currently index all objects by type, but if you want to support this kind of functionality, I would recommend using the Events system:

10 Likes

Got it, thanks for the answer.

I do however have a different problem now when trying to emit an event (rust + sui + move is very new to me). This is the current function:

    public entry fun place_mark(game: &mut TicTacToe, row: u8, col: u8, ctx: &mut TxContext) {
        
        assert!(row < 3 && col < 3, EInvalidLocation);
        assert!(game.game_status == IN_PROGRESS, EGameEnded);
        let addr = get_cur_turn_address(game);
        assert!(addr == tx_context::sender(ctx), EInvalidTurn);

        let cell = vector::borrow_mut(vector::borrow_mut(&mut game.gameboard, (row as u64)), (col as u64));
        assert!(*cell == MARK_EMPTY, ECellOccupied);

        *cell = game.cur_turn % 2;
        update_winner(game);
        game.cur_turn = game.cur_turn + 1;

        if (game.game_status != IN_PROGRESS) {
            // Notify the server that the game ended so that it can delete the game.
            let id = object::new(ctx);
            event::emit(GameEndEvent { game_id: object::id(game) });
            event::emit(TrophyWonEvent { id: object::uid_to_inner(&id) });
            if (game.game_status == X_WIN) {
                transfer::transfer(Trophy {id}, game.x_address);
            } else if (game.game_status == O_WIN) {
                transfer::transfer( Trophy {id}, game.o_address);
            };
        }
    }

However I get an error:

Invalid return

tic-tac-toe.move(107, 17): The local variable ‘id’ might still contain a value. The value does not have the ‘drop’ ability and must be consumed before the function returns

object.move(101, 42): The type ‘(sui=0x2)::object::UID’ does not have the ability ‘drop’

object.move(41, 12): To satisfy the constraint, the ‘drop’ ability would need to be added here

Not sure what’s wrong.

3 Likes

The error is saying that you have assigned a value to id and the compiler can’t guarantee that it will be used or disposed of correctly before exiting the function.

This is because it doesn’t know that the only valid values for game.game_status are X_WIN, O_WIN, and IN_PROGRESS, so it’s thinking that if it’s not any of those, you will have created an id and not used it.

To help the compiler out, you can add an else case that aborts if the `game_status isn’t as expected:

const EInvalidGameState: u64 = /* ... */;

struct TrophyEvent has copy, drop {
    trophy_id: ID,
    game: ID,
    winner: address,
}

public entry fun place_mark(game: &mut TicTacToe, row: u8, col: u8, ctx: &mut TxContext) {        
    assert!(row < 3 && col < 3, EInvalidLocation);
    assert!(game.game_status == IN_PROGRESS, EGameEnded);

    let addr = get_cur_turn_address(game);
    assert!(addr == tx_context::sender(ctx), EInvalidTurn);

    let cell = vector::borrow_mut(vector::borrow_mut(&mut game.gameboard, (row as u64)), (col as u64));
    assert!(*cell == MARK_EMPTY, ECellOccupied);

    *cell = game.cur_turn % 2;
    update_winner(game);
    game.cur_turn = game.cur_turn + 1;

    if (game.game_status != IN_PROGRESS) {
        // Notify the server that the game ended so that it can delete the game.
        let trophy = Trophy { id: object::new(ctx) };
        let winner = if (game.game_status == X_WIN) {
            game.x_address
        } else if (game.game_status == O_WIN) {
            game.o_address
        } else {
            abort EInvalidGameState;
        };

        event::emit(GameEndEvent { game_id: object::id(game) });
        event::emit(TrophyWonEvent { 
            trophy_id: object::id(trophy),
            game_id: object::id(game),
            winner,
        });

        transfer::transfer(trophy, winner);
    }
}

I also added some extra fields to your TrophyEvent to help make sense of it (the game it’s from and the winner in that game), because with just the ID of the trophy, you will struggle to get the full picture (the Trophy may get moved, or wrapped, or deleted after you send it to the address).

4 Likes

Got it! I did change it up a little bit, since the solution in the answer above still gave some errors.

    public entry fun place_mark(game: &mut TicTacToe, row: u8, col: u8, ctx: &mut TxContext) {
        assert!(row < 3 && col < 3, EInvalidLocation);
        assert!(game.game_status == IN_PROGRESS, EGameEnded);
        let addr = get_cur_turn_address(game);
        assert!(addr == tx_context::sender(ctx), EInvalidTurn);

        let cell = vector::borrow_mut(vector::borrow_mut(&mut game.gameboard, (row as u64)), (col as u64));
        assert!(*cell == MARK_EMPTY, ECellOccupied);

        *cell = game.cur_turn % 2;
        update_winner(game);
        game.cur_turn = game.cur_turn + 1;

        if (game.game_status != IN_PROGRESS) {
            // Notify the server that the game ended so that it can delete the game.
            event::emit(GameEndEvent { game_id: object::id(game) });
            create_and_send_trophy(game, ctx)
        }
    }

    fun create_and_send_trophy(game: &mut TicTacToe, ctx: &mut TxContext) {
        if(game.game_status != DRAW){
            let trophy = Trophy { id: object::new(ctx) };
            let winner = if(game.game_status == X_WIN) {game.x_address} else {game.o_address};
            event::emit(TrophyEvent { 
                trophy_id: object::uid_to_inner(&trophy.id),
                game: object::id(game),
                winner,
                });
            transfer::transfer(trophy, winner);
        }
    }

Emitted event here:

3 Likes