-
Notifications
You must be signed in to change notification settings - Fork 70
feat(nft): nft module #296
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
base: main
Are you sure you want to change the base?
Changes from 3 commits
6e4788b
628135e
e834bf6
a658097
ed1caf0
d8a9e7f
9b543d8
466b947
c5ed2f8
dd85dc4
42402ba
4f2e29f
abae283
59b13f5
45df9c0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,12 +2,20 @@ name: ictest E2E | |||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||
| pull_request: | ||||||||||||||||||||||||
| branches: | ||||||||||||||||||||||||
| - main | ||||||||||||||||||||||||
| - master | ||||||||||||||||||||||||
| branches-ignore: | ||||||||||||||||||||||||
| - "mvp/**" | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| push: | ||||||||||||||||||||||||
| tags: | ||||||||||||||||||||||||
| - "**" | ||||||||||||||||||||||||
| branches: | ||||||||||||||||||||||||
| - "main" | ||||||||||||||||||||||||
| - "master" | ||||||||||||||||||||||||
| - main | ||||||||||||||||||||||||
| - master | ||||||||||||||||||||||||
| branches-ignore: | ||||||||||||||||||||||||
| - "mvp/**" | ||||||||||||||||||||||||
|
Comment on lines
+15
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix invalid push filters (branches + branches-ignore conflict). Use negative patterns in branches instead of branches-ignore. push:
tags:
- "**"
branches:
- - main
- - master
- branches-ignore:
- - "mvp/**"
+ - main
+ - master
+ - '!mvp/**'📝 Committable suggestion
Suggested change
🧰 Tools🪛 actionlint (1.7.7)17-17: both "branches" and "branches-ignore" filters cannot be used for the same event "push". note: use '!' to negate patterns (events) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| permissions: | ||||||||||||||||||||||||
| contents: read | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| 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) | ||
| } |
| 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 fmt.Sprintf("nft%x", tmcrypto.AddressHash(bz)) | ||
| } | ||
|
|
||
| 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, denom) { | ||
| 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 | ||
| } |
| 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) | ||
| } | ||
| } |
| 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), | ||
| } | ||
| } |
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.
Fix invalid pull_request filters (branches + branches-ignore are mutually exclusive).
actionlint flags this; GitHub Actions ignores one set unpredictably. Also note: pull_request filters apply to base branch, not head. To exclude PRs from mvp/** heads, drop branches-ignore here and gate jobs with an if condition.
Apply this diff to the trigger:
Then add a job-level guard to skip PRs whose head starts with mvp/ (outside this hunk):
🧰 Tools
🪛 actionlint (1.7.7)
8-8: both "branches" and "branches-ignore" filters cannot be used for the same event "pull_request". note: use '!' to negate patterns
(events)
🤖 Prompt for AI Agents