Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions app/keepers/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (
"github.com/bitsongofficial/go-bitsong/x/fantoken"
fantokenkeeper "github.com/bitsongofficial/go-bitsong/x/fantoken/keeper"
fantokentypes "github.com/bitsongofficial/go-bitsong/x/fantoken/types"
nftkeeper "github.com/bitsongofficial/go-bitsong/x/nft/keeper"
nfttypes "github.com/bitsongofficial/go-bitsong/x/nft/types"
"github.com/bitsongofficial/go-bitsong/x/smart-account/authenticator"
smartaccountkeeper "github.com/bitsongofficial/go-bitsong/x/smart-account/keeper"
smartaccounttypes "github.com/bitsongofficial/go-bitsong/x/smart-account/types"
Expand Down Expand Up @@ -113,6 +115,7 @@ var maccPerms = map[string][]string{
govtypes.ModuleName: {authtypes.Burner},
ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner},
fantokentypes.ModuleName: {authtypes.Minter, authtypes.Burner},
nfttypes.ModuleName: {authtypes.Minter, authtypes.Burner},
wasmtypes.ModuleName: {authtypes.Burner},
protocolpooltypes.ModuleName: nil,
protocolpooltypes.ProtocolPoolEscrowAccount: nil,
Expand Down Expand Up @@ -149,6 +152,7 @@ type AppKeepers struct {
ICQKeeper *icqkeeper.Keeper
EvidenceKeeper evidencekeeper.Keeper
FanTokenKeeper fantokenkeeper.Keeper
NftKeeper nftkeeper.Keeper
WasmKeeper wasmkeeper.Keeper
CadenceKeeper cadencekeeper.Keeper
IBCFeeKeeper ibcfeekeeper.Keeper
Expand Down Expand Up @@ -406,6 +410,14 @@ func NewAppKeepers(
BlockedAddrs(),
)

appKeepers.NftKeeper = nftkeeper.NewKeeper(
appCodec,
runtime.NewKVStoreService(appKeepers.keys[nfttypes.StoreKey]),
appKeepers.AccountKeeper,
appKeepers.BankKeeper,
bApp.Logger(),
)

// Stargate Queries
acceptedStargateQueries := wasmkeeper.AcceptedQueries{
// ibc
Expand Down
2 changes: 2 additions & 0 deletions app/keepers/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keepers

import (
storetypes "cosmossdk.io/store/types"
nfttypes "github.com/bitsongofficial/go-bitsong/x/nft/types"

"cosmossdk.io/x/feegrant"
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
Expand Down Expand Up @@ -57,6 +58,7 @@ func (appKeepers *AppKeepers) GenerateKeys() {
wasmtypes.StoreKey,
icqtypes.StoreKey,
fantokentypes.StoreKey,
nfttypes.StoreKey,
cadencetypes.StoreKey,
smartaccounttypes.StoreKey,
protocolpooltypes.StoreKey,
Expand Down
27 changes: 27 additions & 0 deletions proto/bitsong/nft/v1beta1/nft.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
syntax = "proto3";
package bitsong.nft.v1beta1;

option go_package = "github.com/bitsongofficial/go-bitsong/x/nft/types";

message Collection {
string symbol = 1;
string name = 2;
string description = 3;
string uri = 4;
string creator = 5;
string minter = 6;
uint64 num_tokens = 7;
// bool is_mutable
// update_autority (who can update name, description and uri if is_mutable = true)
}

message Nft {
string name = 1;
string description = 2;
string uri = 3;
// string owner = 5;
// seller_fee_bps
// payment_address
// bool is_mutable
// update_autority (who can update name, description and uri if is_mutable = true)
}
105 changes: 105 additions & 0 deletions x/nft/keeper/collection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package keeper

import (
"fmt"

"cosmossdk.io/math"
"github.com/bitsongofficial/go-bitsong/x/nft/types"
tmcrypto "github.com/cometbft/cometbft/crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)

func (k Keeper) CreateCollection(ctx sdk.Context, creator sdk.AccAddress, coll types.Collection) (denom string, err error) {
denom, err = k.validateCollectionDenom(ctx, creator, coll.Symbol)
if err != nil {
return "", err
}

// TODO: charge fee

metadata := banktypes.Metadata{
DenomUnits: []*banktypes.DenomUnit{{
Denom: denom,
Exponent: 0,
}},
Base: denom,
Name: coll.Name,
Description: coll.Description,
Symbol: coll.Symbol,
Display: coll.Symbol,
URI: coll.Uri,
}

k.bk.SetDenomMetaData(ctx, metadata)

if err := k.setCollection(ctx, denom, coll); err != nil {
return "", err
}

return denom, nil
}

func (k Keeper) GetSupply(ctx sdk.Context, denom string) math.Int {
supply, err := k.Supply.Get(ctx, denom)
if err != nil {
return math.ZeroInt()
}

return supply
}

func (k Keeper) HasSupply(ctx sdk.Context, denom string) bool {
has, err := k.Supply.Has(ctx, denom)
return has && err == nil
}

func (k Keeper) setSupply(ctx sdk.Context, denom string, supply math.Int) error {
return k.Supply.Set(ctx, denom, supply)
}

func (k Keeper) incrementSupply(ctx sdk.Context, denom string) error {
supply := k.GetSupply(ctx, denom)
supply = supply.Add(math.NewInt(1))

return k.setSupply(ctx, denom, supply)
}

func (k Keeper) createCollectionDenom(creator sdk.AccAddress, symbol string) string {
// TODO: if necessary add a salt field

bz := []byte(fmt.Sprintf("%s%s", creator.String(), symbol))
return "nft" + tmcrypto.AddressHash(bz).String()
}

func (k Keeper) validateCollectionDenom(ctx sdk.Context, creator sdk.AccAddress, symbol string) (string, error) {
denom := k.createCollectionDenom(creator, symbol)

if err := sdk.ValidateDenom(denom); err != nil {
return "", err
}

if k.bk.HasSupply(ctx, symbol) {
return "", fmt.Errorf("denom %s already exists", denom)
}

_, exists := k.bk.GetDenomMetaData(ctx, denom)
if exists {
return "", types.ErrCollectionAlreadyExists
}

return denom, nil
}

func (k Keeper) setCollection(ctx sdk.Context, denom string, coll types.Collection) error {
return k.Collections.Set(ctx, denom, coll)
}

func (k Keeper) getCollection(ctx sdk.Context, denom string) (types.Collection, error) {
coll, err := k.Collections.Get(ctx, denom)
if err != nil {
return types.Collection{}, types.ErrCollectionNotFound
}

return coll, nil
}
21 changes: 21 additions & 0 deletions x/nft/keeper/collection_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package keeper

import (
"testing"

"github.com/cometbft/cometbft/crypto/tmhash"
sdk "github.com/cosmos/cosmos-sdk/types"
)

func TestKeeper_createCollectionDenom(t *testing.T) {
creator := sdk.AccAddress(tmhash.SumTruncated([]byte("creator")))
symbol := "MYNFT"

expectedDenom := "nftF1D9FE89CCE1FAD3F83FFCBA6F496EFD30855C42"
k := Keeper{}

denom := k.createCollectionDenom(creator, symbol)
if denom != expectedDenom {
t.Errorf("expected %s, got %s", expectedDenom, denom)
}
}
45 changes: 45 additions & 0 deletions x/nft/keeper/keeper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package keeper

import (
"cosmossdk.io/collections"
"cosmossdk.io/core/address"
"cosmossdk.io/core/store"
"cosmossdk.io/log"
"cosmossdk.io/math"
"github.com/bitsongofficial/go-bitsong/x/nft/types"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
)

type Keeper struct {
cdc codec.BinaryCodec
storeService store.KVStoreService
ac address.Codec
bk types.BankKeeper
logger log.Logger

Schema collections.Schema
Collections collections.Map[string, types.Collection]
Supply collections.Map[string, math.Int]
}

func NewKeeper(cdc codec.BinaryCodec, storeService store.KVStoreService, ak types.AccountKeeper, bk types.BankKeeper, logger log.Logger) Keeper {
if addr := ak.GetModuleAddress(types.ModuleName); addr == nil {
panic("the " + types.ModuleName + " module account has not been set")
}

logger = logger.With(log.ModuleKey, "x/"+types.ModuleName)

sb := collections.NewSchemaBuilder(storeService)

return Keeper{
cdc: cdc,
storeService: storeService,
ac: ak.AddressCodec(),
bk: bk,
logger: logger,
// TODO: fix the store once we add queries
Collections: collections.NewMap(sb, types.CollectionsPrefix, "collections", collections.StringKey, codec.CollValue[types.Collection](cdc)),
Supply: collections.NewMap(sb, types.SupplyPrefix, "supply", collections.StringKey, sdk.IntValue),
}
}
Loading
Loading