Ante Handler Token Gates
The Decorator
package app
import (
"fmt"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
tokenizationkeeper "github.com/bitbadges/bitbadgeschain/x/tokenization/keeper"
tokenizationtypes "github.com/bitbadges/bitbadgeschain/x/tokenization/types"
)
// TokenRequirement defines what token must be held for a message type to be allowed.
type TokenRequirement struct {
CollectionId sdkmath.Uint
TokenId sdkmath.Uint
MinBalance sdkmath.Uint
CheckAddress string // If empty, checks the transaction sender
MustHold bool // true = must hold token to proceed; false = must NOT hold (circuit breaker)
ErrorMsg string
}
// ComplianceAnteDecorator gates message types on token ownership.
type ComplianceAnteDecorator struct {
tokenizationKeeper tokenizationkeeper.Keeper
requirements map[string][]TokenRequirement // msg type URL -> requirements
}
func NewComplianceAnteDecorator(
tk tokenizationkeeper.Keeper,
requirements map[string][]TokenRequirement,
) ComplianceAnteDecorator {
return ComplianceAnteDecorator{
tokenizationKeeper: tk,
requirements: requirements,
}
}
func (cad ComplianceAnteDecorator) AnteHandle(
ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler,
) (sdk.Context, error) {
now := sdkmath.NewUint(uint64(ctx.BlockTime().UnixMilli()))
nowRange := []*tokenizationtypes.UintRange{{Start: now, End: now}}
// Get the fee payer (transaction sender)
feeTx, ok := tx.(sdk.FeeTx)
var sender string
if ok {
sender = sdk.AccAddress(feeTx.FeePayer()).String()
}
for _, msg := range tx.GetMsgs() {
msgType := sdk.MsgTypeURL(msg)
reqs, exists := cad.requirements[msgType]
if !exists {
continue
}
for _, req := range reqs {
checkAddr := req.CheckAddress
if checkAddr == "" {
checkAddr = sender
}
if checkAddr == "" {
continue
}
collection, found := cad.tokenizationKeeper.GetCollectionFromStore(ctx, req.CollectionId)
if !found {
if req.MustHold {
return ctx, fmt.Errorf("%s", req.ErrorMsg)
}
continue
}
balanceStore, _, err := cad.tokenizationKeeper.GetBalanceOrApplyDefault(ctx, collection, checkAddr)
if err != nil {
if req.MustHold {
return ctx, fmt.Errorf("%s", req.ErrorMsg)
}
continue
}
tokenIdRange := []*tokenizationtypes.UintRange{{Start: req.TokenId, End: req.TokenId}}
balances, err := tokenizationtypes.GetBalancesForIds(ctx, tokenIdRange, nowRange, balanceStore.Balances)
hasBalance := err == nil && len(balances) > 0 && balances[0].Amount.GTE(req.MinBalance)
if req.MustHold && !hasBalance {
return ctx, fmt.Errorf("%s", req.ErrorMsg)
}
if !req.MustHold && hasBalance {
return ctx, fmt.Errorf("%s", req.ErrorMsg)
}
}
}
return next(ctx, tx, simulate)
}Wiring It Up in app.go
Circuit Breaker (Replacing x/circuit)
KYC-Gated Transfers
Compliant Staking
IBC Transfer Compliance
Governance Participation Gates
Custom Chain Policies
Free Superpowers
Last updated