diff --git a/x/nft/keeper/keeper.go b/x/nft/keeper/keeper.go index 4dde7a7e41e5..8519471cfe91 100644 --- a/x/nft/keeper/keeper.go +++ b/x/nft/keeper/keeper.go @@ -33,3 +33,75 @@ func NewKeeper(env appmodule.Environment, ac: ak.AddressCodec(), } } + +// MsgNewClass creates a new NFT class +func (k Keeper) MsgNewClass(ctx context.Context, msg *nft.MsgNewClass) (*nft.MsgNewClassResponse, error) { + class := nft.Class{ + Id: msg.ClassId, + Name: msg.Name, + Symbol: msg.Symbol, + Description: msg.Description, + Uri: msg.Uri, + UriHash: msg.UriHash, + Data: msg.Data, + } + if err := k.SaveClass(ctx, class); err != nil { + return nil, err + } + return &nft.MsgNewClassResponse{}, nil +} + +// MsgUpdateClass updates an existing NFT class +func (k Keeper) MsgUpdateClass(ctx context.Context, msg *nft.MsgUpdateClass) (*nft.MsgUpdateClassResponse, error) { + class := nft.Class{ + Id: msg.ClassId, + Name: msg.Name, + Symbol: msg.Symbol, + Description: msg.Description, + Uri: msg.Uri, + UriHash: msg.UriHash, + Data: msg.Data, + } + if err := k.UpdateClass(ctx, class); err != nil { + return nil, err + } + return &nft.MsgUpdateClassResponse{}, nil +} + +// MsgMintNFT mints a new NFT +func (k Keeper) MsgMintNFT(ctx context.Context, msg *nft.MsgMintNFT) (*nft.MsgMintNFTResponse, error) { + nft := nft.NFT{ + ClassId: msg.ClassId, + Id: msg.Id, + Uri: msg.Uri, + UriHash: msg.UriHash, + Data: msg.Data, + } + if err := k.Mint(ctx, nft, msg.Receiver); err != nil { + return nil, err + } + return &nft.MsgMintNFTResponse{}, nil +} + +// MsgBurnNFT burns an existing NFT +func (k Keeper) MsgBurnNFT(ctx context.Context, msg *nft.MsgBurnNFT) (*nft.MsgBurnNFTResponse, error) { + if err := k.Burn(ctx, msg.ClassId, msg.Id); err != nil { + return nil, err + } + return &nft.MsgBurnNFTResponse{}, nil +} + +// MsgUpdateNFT updates an existing NFT +func (k Keeper) MsgUpdateNFT(ctx context.Context, msg *nft.MsgUpdateNFT) (*nft.MsgUpdateNFTResponse, error) { + nft := nft.NFT{ + ClassId: msg.ClassId, + Id: msg.Id, + Uri: msg.Uri, + UriHash: msg.UriHash, + Data: msg.Data, + } + if err := k.Update(ctx, nft); err != nil { + return nil, err + } + return &nft.MsgUpdateNFTResponse{}, nil +} diff --git a/x/nft/keeper/keeper_test.go b/x/nft/keeper/keeper_test.go index d907afc1611d..c534524c3373 100644 --- a/x/nft/keeper/keeper_test.go +++ b/x/nft/keeper/keeper_test.go @@ -399,3 +399,171 @@ func (s *TestSuite) TestInitGenesis() { s.Require().True(has) s.Require().EqualValues(expNFT, actNFT) } + +func (s *TestSuite) TestMsgNewClass() { + msg := &nft.MsgNewClass{ + ClassId: testClassID, + Name: testClassName, + Symbol: testClassSymbol, + Description: testClassDescription, + Uri: testClassURI, + UriHash: testClassURIHash, + } + + _, err := s.nftKeeper.MsgNewClass(s.ctx, msg) + s.Require().NoError(err) + + actual, has := s.nftKeeper.GetClass(s.ctx, testClassID) + s.Require().True(has) + s.Require().EqualValues(nft.Class{ + Id: testClassID, + Name: testClassName, + Symbol: testClassSymbol, + Description: testClassDescription, + Uri: testClassURI, + UriHash: testClassURIHash, + }, actual) +} + +func (s *TestSuite) TestMsgUpdateClass() { + class := nft.Class{ + Id: testClassID, + Name: testClassName, + Symbol: testClassSymbol, + Description: testClassDescription, + Uri: testClassURI, + UriHash: testClassURIHash, + } + err := s.nftKeeper.SaveClass(s.ctx, class) + s.Require().NoError(err) + + msg := &nft.MsgUpdateClass{ + ClassId: testClassID, + Name: "Updated Name", + Symbol: "Updated Symbol", + Description: "Updated Description", + Uri: "Updated URI", + UriHash: "Updated URI Hash", + } + + _, err = s.nftKeeper.MsgUpdateClass(s.ctx, msg) + s.Require().NoError(err) + + actual, has := s.nftKeeper.GetClass(s.ctx, testClassID) + s.Require().True(has) + s.Require().EqualValues(nft.Class{ + Id: testClassID, + Name: "Updated Name", + Symbol: "Updated Symbol", + Description: "Updated Description", + Uri: "Updated URI", + UriHash: "Updated URI Hash", + }, actual) +} + +func (s *TestSuite) TestMsgMintNFT() { + class := nft.Class{ + Id: testClassID, + Name: testClassName, + Symbol: testClassSymbol, + Description: testClassDescription, + Uri: testClassURI, + UriHash: testClassURIHash, + } + err := s.nftKeeper.SaveClass(s.ctx, class) + s.Require().NoError(err) + + msg := &nft.MsgMintNFT{ + ClassId: testClassID, + Id: testID, + Uri: testURI, + UriHash: testURIHash, + Receiver: s.encodedAddrs[0], + } + + _, err = s.nftKeeper.MsgMintNFT(s.ctx, msg) + s.Require().NoError(err) + + actual, has := s.nftKeeper.GetNFT(s.ctx, testClassID, testID) + s.Require().True(has) + s.Require().EqualValues(nft.NFT{ + ClassId: testClassID, + Id: testID, + Uri: testURI, + UriHash: testURIHash, + }, actual) +} + +func (s *TestSuite) TestMsgBurnNFT() { + class := nft.Class{ + Id: testClassID, + Name: testClassName, + Symbol: testClassSymbol, + Description: testClassDescription, + Uri: testClassURI, + UriHash: testClassURIHash, + } + err := s.nftKeeper.SaveClass(s.ctx, class) + s.Require().NoError(err) + + nft := nft.NFT{ + ClassId: testClassID, + Id: testID, + Uri: testURI, + UriHash: testURIHash, + } + err = s.nftKeeper.Mint(s.ctx, nft, s.addrs[0]) + s.Require().NoError(err) + + msg := &nft.MsgBurnNFT{ + ClassId: testClassID, + Id: testID, + } + + _, err = s.nftKeeper.MsgBurnNFT(s.ctx, msg) + s.Require().NoError(err) + + _, has := s.nftKeeper.GetNFT(s.ctx, testClassID, testID) + s.Require().False(has) +} + +func (s *TestSuite) TestMsgUpdateNFT() { + class := nft.Class{ + Id: testClassID, + Name: testClassName, + Symbol: testClassSymbol, + Description: testClassDescription, + Uri: testClassURI, + UriHash: testClassURIHash, + } + err := s.nftKeeper.SaveClass(s.ctx, class) + s.Require().NoError(err) + + nft := nft.NFT{ + ClassId: testClassID, + Id: testID, + Uri: testURI, + UriHash: testURIHash, + } + err = s.nftKeeper.Mint(s.ctx, nft, s.addrs[0]) + s.Require().NoError(err) + + msg := &nft.MsgUpdateNFT{ + ClassId: testClassID, + Id: testID, + Uri: "Updated URI", + UriHash: "Updated URI Hash", + } + + _, err = s.nftKeeper.MsgUpdateNFT(s.ctx, msg) + s.Require().NoError(err) + + actual, has := s.nftKeeper.GetNFT(s.ctx, testClassID, testID) + s.Require().True(has) + s.Require().EqualValues(nft.NFT{ + ClassId: testClassID, + Id: testID, + Uri: "Updated URI", + UriHash: "Updated URI Hash", + }, actual) +} diff --git a/x/nft/keeper/msg_server.go b/x/nft/keeper/msg_server.go index ba6721b76468..a76cac854346 100644 --- a/x/nft/keeper/msg_server.go +++ b/x/nft/keeper/msg_server.go @@ -52,3 +52,75 @@ func (k Keeper) Send(ctx context.Context, msg *nft.MsgSend) (*nft.MsgSendRespons return &nft.MsgSendResponse{}, nil } + +// NewClass implements NewClass method of the types.MsgServer. +func (k Keeper) NewClass(ctx context.Context, msg *nft.MsgNewClass) (*nft.MsgNewClassResponse, error) { + class := nft.Class{ + Id: msg.ClassId, + Name: msg.Name, + Symbol: msg.Symbol, + Description: msg.Description, + Uri: msg.Uri, + UriHash: msg.UriHash, + Data: msg.Data, + } + if err := k.SaveClass(ctx, class); err != nil { + return nil, err + } + return &nft.MsgNewClassResponse{}, nil +} + +// UpdateClass implements UpdateClass method of the types.MsgServer. +func (k Keeper) UpdateClass(ctx context.Context, msg *nft.MsgUpdateClass) (*nft.MsgUpdateClassResponse, error) { + class := nft.Class{ + Id: msg.ClassId, + Name: msg.Name, + Symbol: msg.Symbol, + Description: msg.Description, + Uri: msg.Uri, + UriHash: msg.UriHash, + Data: msg.Data, + } + if err := k.UpdateClass(ctx, class); err != nil { + return nil, err + } + return &nft.MsgUpdateClassResponse{}, nil +} + +// MintNFT implements MintNFT method of the types.MsgServer. +func (k Keeper) MintNFT(ctx context.Context, msg *nft.MsgMintNFT) (*nft.MsgMintNFTResponse, error) { + nft := nft.NFT{ + ClassId: msg.ClassId, + Id: msg.Id, + Uri: msg.Uri, + UriHash: msg.UriHash, + Data: msg.Data, + } + if err := k.Mint(ctx, nft, msg.Receiver); err != nil { + return nil, err + } + return &nft.MsgMintNFTResponse{}, nil +} + +// BurnNFT implements BurnNFT method of the types.MsgServer. +func (k Keeper) BurnNFT(ctx context.Context, msg *nft.MsgBurnNFT) (*nft.MsgBurnNFTResponse, error) { + if err := k.Burn(ctx, msg.ClassId, msg.Id); err != nil { + return nil, err + } + return &nft.MsgBurnNFTResponse{}, nil +} + +// UpdateNFT implements UpdateNFT method of the types.MsgServer. +func (k Keeper) UpdateNFT(ctx context.Context, msg *nft.MsgUpdateNFT) (*nft.MsgUpdateNFTResponse, error) { + nft := nft.NFT{ + ClassId: msg.ClassId, + Id: msg.Id, + Uri: msg.Uri, + UriHash: msg.UriHash, + Data: msg.Data, + } + if err := k.Update(ctx, nft); err != nil { + return nil, err + } + return &nft.MsgUpdateNFTResponse{}, nil +} diff --git a/x/nft/keeper/msg_server_test.go b/x/nft/keeper/msg_server_test.go index 5209a7d4010f..a88f01b5cf0a 100644 --- a/x/nft/keeper/msg_server_test.go +++ b/x/nft/keeper/msg_server_test.go @@ -131,3 +131,171 @@ func (s *TestSuite) TestSend() { }) } } + +func (s *TestSuite) TestMsgNewClass() { + msg := &nft.MsgNewClass{ + ClassId: testClassID, + Name: testClassName, + Symbol: testClassSymbol, + Description: testClassDescription, + Uri: testClassURI, + UriHash: testClassURIHash, + } + + _, err := s.nftKeeper.MsgNewClass(s.ctx, msg) + s.Require().NoError(err) + + actual, has := s.nftKeeper.GetClass(s.ctx, testClassID) + s.Require().True(has) + s.Require().EqualValues(nft.Class{ + Id: testClassID, + Name: testClassName, + Symbol: testClassSymbol, + Description: testClassDescription, + Uri: testClassURI, + UriHash: testClassURIHash, + }, actual) +} + +func (s *TestSuite) TestMsgUpdateClass() { + class := nft.Class{ + Id: testClassID, + Name: testClassName, + Symbol: testClassSymbol, + Description: testClassDescription, + Uri: testClassURI, + UriHash: testClassURIHash, + } + err := s.nftKeeper.SaveClass(s.ctx, class) + s.Require().NoError(err) + + msg := &nft.MsgUpdateClass{ + ClassId: testClassID, + Name: "Updated Name", + Symbol: "Updated Symbol", + Description: "Updated Description", + Uri: "Updated URI", + UriHash: "Updated URI Hash", + } + + _, err = s.nftKeeper.MsgUpdateClass(s.ctx, msg) + s.Require().NoError(err) + + actual, has := s.nftKeeper.GetClass(s.ctx, testClassID) + s.Require().True(has) + s.Require().EqualValues(nft.Class{ + Id: testClassID, + Name: "Updated Name", + Symbol: "Updated Symbol", + Description: "Updated Description", + Uri: "Updated URI", + UriHash: "Updated URI Hash", + }, actual) +} + +func (s *TestSuite) TestMsgMintNFT() { + class := nft.Class{ + Id: testClassID, + Name: testClassName, + Symbol: testClassSymbol, + Description: testClassDescription, + Uri: testClassURI, + UriHash: testClassURIHash, + } + err := s.nftKeeper.SaveClass(s.ctx, class) + s.Require().NoError(err) + + msg := &nft.MsgMintNFT{ + ClassId: testClassID, + Id: testID, + Uri: testURI, + UriHash: testURIHash, + Receiver: s.encodedAddrs[0], + } + + _, err = s.nftKeeper.MsgMintNFT(s.ctx, msg) + s.Require().NoError(err) + + actual, has := s.nftKeeper.GetNFT(s.ctx, testClassID, testID) + s.Require().True(has) + s.Require().EqualValues(nft.NFT{ + ClassId: testClassID, + Id: testID, + Uri: testURI, + UriHash: testURIHash, + }, actual) +} + +func (s *TestSuite) TestMsgBurnNFT() { + class := nft.Class{ + Id: testClassID, + Name: testClassName, + Symbol: testClassSymbol, + Description: testClassDescription, + Uri: testClassURI, + UriHash: testClassURIHash, + } + err := s.nftKeeper.SaveClass(s.ctx, class) + s.Require().NoError(err) + + nft := nft.NFT{ + ClassId: testClassID, + Id: testID, + Uri: testURI, + UriHash: testURIHash, + } + err = s.nftKeeper.Mint(s.ctx, nft, s.addrs[0]) + s.Require().NoError(err) + + msg := &nft.MsgBurnNFT{ + ClassId: testClassID, + Id: testID, + } + + _, err = s.nftKeeper.MsgBurnNFT(s.ctx, msg) + s.Require().NoError(err) + + _, has := s.nftKeeper.GetNFT(s.ctx, testClassID, testID) + s.Require().False(has) +} + +func (s *TestSuite) TestMsgUpdateNFT() { + class := nft.Class{ + Id: testClassID, + Name: testClassName, + Symbol: testClassSymbol, + Description: testClassDescription, + Uri: testClassURI, + UriHash: testClassURIHash, + } + err := s.nftKeeper.SaveClass(s.ctx, class) + s.Require().NoError(err) + + nft := nft.NFT{ + ClassId: testClassID, + Id: testID, + Uri: testURI, + UriHash: testURIHash, + } + err = s.nftKeeper.Mint(s.ctx, nft, s.addrs[0]) + s.Require().NoError(err) + + msg := &nft.MsgUpdateNFT{ + ClassId: testClassID, + Id: testID, + Uri: "Updated URI", + UriHash: "Updated URI Hash", + } + + _, err = s.nftKeeper.MsgUpdateNFT(s.ctx, msg) + s.Require().NoError(err) + + actual, has := s.nftKeeper.GetNFT(s.ctx, testClassID, testID) + s.Require().True(has) + s.Require().EqualValues(nft.NFT{ + ClassId: testClassID, + Id: testID, + Uri: "Updated URI", + UriHash: "Updated URI Hash", + }, actual) +} diff --git a/x/nft/proto/cosmos/nft/v1beta1/tx.proto b/x/nft/proto/cosmos/nft/v1beta1/tx.proto index 9eecfdd2d560..46df4067a6a3 100644 --- a/x/nft/proto/cosmos/nft/v1beta1/tx.proto +++ b/x/nft/proto/cosmos/nft/v1beta1/tx.proto @@ -12,6 +12,21 @@ service Msg { // Send defines a method to send a nft from one account to another account. rpc Send(MsgSend) returns (MsgSendResponse); + + // NewClass defines a method to create a new NFT class. + rpc NewClass(MsgNewClass) returns (MsgNewClassResponse); + + // UpdateClass defines a method to update an existing NFT class. + rpc UpdateClass(MsgUpdateClass) returns (MsgUpdateClassResponse); + + // MintNFT defines a method to mint a new NFT. + rpc MintNFT(MsgMintNFT) returns (MsgMintNFTResponse); + + // BurnNFT defines a method to burn an existing NFT. + rpc BurnNFT(MsgBurnNFT) returns (MsgBurnNFTResponse); + + // UpdateNFT defines a method to update an existing NFT. + rpc UpdateNFT(MsgUpdateNFT) returns (MsgUpdateNFTResponse); } // MsgSend represents a message to send a nft from one account to another account. @@ -30,5 +45,142 @@ message MsgSend { // receiver is the receiver address of nft string receiver = 4 [(cosmos_proto.scalar) = "cosmos.AddressString"]; } + // MsgSendResponse defines the Msg/Send response type. -message MsgSendResponse {} \ No newline at end of file +message MsgSendResponse {} + +// MsgNewClass represents a message to create a new NFT class. +message MsgNewClass { + option (cosmos.msg.v1.signer) = "creator"; + + // class_id defines the unique identifier of the nft classification, similar to the contract address of ERC721 + string class_id = 1; + + // name defines the human-readable name of the NFT classification. Optional + string name = 2; + + // symbol is an abbreviated name for nft classification. Optional + string symbol = 3; + + // description is a brief description of nft classification. Optional + string description = 4; + + // uri for the class metadata stored off chain. It can define schema for Class and NFT `Data` attributes. Optional + string uri = 5; + + // uri_hash is a hash of the document pointed by uri. Optional + string uri_hash = 6; + + // data is the app specific metadata of the NFT class. Optional + google.protobuf.Any data = 7; + + // creator is the address of the creator of the NFT class + string creator = 8 [(cosmos_proto.scalar) = "cosmos.AddressString"]; +} + +// MsgNewClassResponse defines the Msg/NewClass response type. +message MsgNewClassResponse {} + +// MsgUpdateClass represents a message to update an existing NFT class. +message MsgUpdateClass { + option (cosmos.msg.v1.signer) = "updater"; + + // class_id defines the unique identifier of the nft classification, similar to the contract address of ERC721 + string class_id = 1; + + // name defines the human-readable name of the NFT classification. Optional + string name = 2; + + // symbol is an abbreviated name for nft classification. Optional + string symbol = 3; + + // description is a brief description of nft classification. Optional + string description = 4; + + // uri for the class metadata stored off chain. It can define schema for Class and NFT `Data` attributes. Optional + string uri = 5; + + // uri_hash is a hash of the document pointed by uri. Optional + string uri_hash = 6; + + // data is the app specific metadata of the NFT class. Optional + google.protobuf.Any data = 7; + + // updater is the address of the updater of the NFT class + string updater = 8 [(cosmos_proto.scalar) = "cosmos.AddressString"]; +} + +// MsgUpdateClassResponse defines the Msg/UpdateClass response type. +message MsgUpdateClassResponse {} + +// MsgMintNFT represents a message to mint a new NFT. +message MsgMintNFT { + option (cosmos.msg.v1.signer) = "minter"; + + // class_id defines the unique identifier of the nft classification, similar to the contract address of ERC721 + string class_id = 1; + + // id defines the unique identification of nft + string id = 2; + + // uri for the NFT metadata stored off chain + string uri = 3; + + // uri_hash is a hash of the document pointed by uri + string uri_hash = 4; + + // data is an app specific data of the NFT. Optional + google.protobuf.Any data = 5; + + // minter is the address of the minter of the NFT + string minter = 6 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // receiver is the receiver address of nft + string receiver = 7 [(cosmos_proto.scalar) = "cosmos.AddressString"]; +} + +// MsgMintNFTResponse defines the Msg/MintNFT response type. +message MsgMintNFTResponse {} + +// MsgBurnNFT represents a message to burn an existing NFT. +message MsgBurnNFT { + option (cosmos.msg.v1.signer) = "burner"; + + // class_id defines the unique identifier of the nft classification, similar to the contract address of ERC721 + string class_id = 1; + + // id defines the unique identification of nft + string id = 2; + + // burner is the address of the burner of the NFT + string burner = 3 [(cosmos_proto.scalar) = "cosmos.AddressString"]; +} + +// MsgBurnNFTResponse defines the Msg/BurnNFT response type. +message MsgBurnNFTResponse {} + +// MsgUpdateNFT represents a message to update an existing NFT. +message MsgUpdateNFT { + option (cosmos.msg.v1.signer) = "updater"; + + // class_id defines the unique identifier of the nft classification, similar to the contract address of ERC721 + string class_id = 1; + + // id defines the unique identification of nft + string id = 2; + + // uri for the NFT metadata stored off chain + string uri = 3; + + // uri_hash is a hash of the document pointed by uri + string uri_hash = 4; + + // data is an app specific data of the NFT. Optional + google.protobuf.Any data = 5; + + // updater is the address of the updater of the NFT + string updater = 6 [(cosmos_proto.scalar) = "cosmos.AddressString"]; +} + +// MsgUpdateNFTResponse defines the Msg/UpdateNFT response type. +message MsgUpdateNFTResponse {}