Supporting Multiple Standards

To support multiple token standards, we recommend using a SendManager module with alias denom routing. It automatically routes transactions to the appropriate handler based on denomination prefixes.

For complete implementation details, see the source code. We recommend simply copying / importing the implementation and using it directly if all you need is x/badges and x/bank.

It is intended to be a drop-in replacement for the BankKeeper interface. Simply replace anywhere you call k.bankKeeper.SendCoins() with k.sendManagerKeeper.SendCoinsWithAliasRouting() (or whatever other functions you need) and get support for multiple token standards at once via dynamic routing.

type SendManagerKeeper interface {
	SendCoinWithAliasRouting(ctx sdk.Context, fromAddressAcc sdk.AccAddress, toAddressAcc sdk.AccAddress, coin *sdk.Coin) error
	SendCoinsWithAliasRouting(ctx sdk.Context, fromAddressAcc sdk.AccAddress, toAddressAcc sdk.AccAddress, coins sdk.Coins) error
	FundCommunityPoolWithAliasRouting(ctx sdk.Context, fromAddressAcc sdk.AccAddress, coins sdk.Coins) error
	SpendFromCommunityPoolWithAliasRouting(ctx sdk.Context, toAddressAcc sdk.AccAddress, coins sdk.Coins) error
	SendCoinsFromModuleToAccountWithAliasRouting(ctx sdk.Context, moduleName string, toAddressAcc sdk.AccAddress, coins sdk.Coins) error
	SendCoinsFromAccountToModuleWithAliasRouting(ctx sdk.Context, fromAddressAcc sdk.AccAddress, moduleName string, coins sdk.Coins) error
	GetBalanceWithAliasRouting(ctx sdk.Context, address sdk.AccAddress, denom string) (sdk.Coin, error)
}

How It Works

Pattern Matching

SendManager uses prefix-based pattern matching to route denoms. It checks each registered prefix against the denom and routes to the first matching handler:

// Example: denom = "badgeslp:1:utoken"
func (k Keeper) getRouterForDenom(denom string) (types.AliasDenomRouter, bool) {
    // Check registered prefixes in order (e.g., ["badgeslp:", "badges:"])
    for _, prefix := range k.registeredPrefixes {
        // Does "badgeslp:1:utoken" start with "badgeslp:"? Yes!
        if strings.HasPrefix(denom, prefix) {
            // Return the router registered for this prefix
            return k.prefixToRouter[prefix], true
        }
    }
    // No prefix matched - will use bank keeper for standard coins
    return nil, false
}

Examples:

  • badgeslp:1:utoken β†’ matches badgeslp: prefix β†’ routes to x/badges

  • uatom β†’ no prefix match β†’ routes to x/bank (standard Cosmos SDK coin)

Alias Denoms

BitBadges format: badgeslp:COLLECTION_ID:denom (e.g., badgeslp:1:utoken). The integer amount converts to Balances[] via collection's cosmosCoinWrapperPaths field (where the conversion rate is defined). Note that this process has no wrapping involved. The conversion is simply an alias for the full Balances[] field for compatibility with other environments.

Auto-Scan Mode

All sends through SendManager use auto-scan mode (no prioritized approvals). The underlying transfer is executed via MsgTransferTokens:

User-Level Approvals

SendManager does not manage user-level approvals automatically. If they need to be handled, you must set them as needed elsewhere before / after calling SendManager. Note: All x/badges transfers require approvals to be satisfied on the collection, sender, and recipient level, where applicable.

This may be especially important for module addresses or special non-user addresses. They automatically inherit the defaults for the collection.

Example: Setting approvals before sending (from FundCommunityPoolViaAliasDenom)

For example, the community pool address (depending on the defaults) may not accept incoming approvals by default which is needed to transfer tokens to it in the x/badges module.

Routing Flow

Usage

Last updated