Cosmos Coin Wrapper Paths
Wrapper paths burn native tokens into a generated badges:COLLECTION_ID:denom x/bank coin and back. Fields, conversion rates, {id} denoms, approvals.
A wrapper path gives a collection a generated x/bank denom, badges:<collectionId>:<denom>, that is IBC-compatible. Sending tokens to the path's wrapper address burns them and mints the coin. Sending the coin back burns the coin and mints the tokens. The denom is new and generated; it is not an existing IBC denom (for that, see Backed Minting).
Use cases:
- Keep tokens native for time-based logic, then convert them to plain Cosmos coins later.
- Reach chains and services that only understand x/bank coins (Osmosis, Juno, and others).
Wrapper addresses have no private key. Collection approvals must override the wrapper address's user-level approvals where needed, and every approval used for a wrap or unwrap must set allowSpecialWrapping: true in approvalCriteria. See Special Address Flags.
Shape
A collection with one wrapper path and one alias path:
A complete MsgCreateCollection with both paths open:
{
"creator": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d",
"defaultBalances": {
"autoApproveSelfInitiatedOutgoingTransfers": true,
"autoApproveSelfInitiatedIncomingTransfers": true,
"autoApproveAllIncomingTransfers": true
},
"validTokenIds": [
{ "start": "1", "end": "100" }
],
"manager": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d",
"collectionMetadata": {
"uri": "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/collection.json"
},
"tokenMetadata": [
{
"uri": "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/{id}.json",
"tokenIds": [
{ "start": "1", "end": "100" }
]
}
],
"collectionApprovals": [
{
"fromListId": "Mint",
"toListId": "All",
"initiatedByListId": "All",
"transferTimes": [
{ "start": "1", "end": "18446744073709551615" }
],
"tokenIds": [
{ "start": "1", "end": "18446744073709551615" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
],
"approvalId": "mint",
"approvalCriteria": {
"approvalAmounts": {
"resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
},
"maxNumTransfers": {
"resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
},
"overridesFromOutgoingApprovals": true,
"userApprovalSettings": {
"userRoyalties": { "percentage": "0", "payoutAddress": "" }
}
}
}
],
"standards": [
"NFTs"
],
"cosmosCoinWrapperPathsToAdd": [
{
"denom": "utoken",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "100" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "TOKEN",
"denomUnits": [
{
"decimals": "6",
"symbol": "TOKEN",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"metadata": { "uri": "", "customData": "" }
}
],
"invariants": {
"cosmosCoinBackedPath": null
},
"aliasPathsToAdd": [
{
"denom": "utoken-alias",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "100" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "ALIAS",
"denomUnits": [
{
"decimals": "6",
"symbol": "ALIAS",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"metadata": { "uri": "", "customData": "" }
}
]
}{
"creator": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d",
"defaultBalances": {
"balances": [],
"outgoingApprovals": [],
"incomingApprovals": [],
"autoApproveSelfInitiatedOutgoingTransfers": true,
"autoApproveSelfInitiatedIncomingTransfers": true,
"autoApproveAllIncomingTransfers": true,
"userPermissions": {
"canUpdateOutgoingApprovals": [],
"canUpdateIncomingApprovals": [],
"canUpdateAutoApproveSelfInitiatedOutgoingTransfers": [],
"canUpdateAutoApproveSelfInitiatedIncomingTransfers": [],
"canUpdateAutoApproveAllIncomingTransfers": []
}
},
"validTokenIds": [
{ "start": "1", "end": "100" }
],
"collectionPermissions": {
"canDeleteCollection": [],
"canArchiveCollection": [],
"canUpdateStandards": [],
"canUpdateCustomData": [],
"canUpdateManager": [],
"canUpdateCollectionMetadata": [],
"canUpdateValidTokenIds": [],
"canUpdateTokenMetadata": [],
"canUpdateCollectionApprovals": [],
"canAddMoreAliasPaths": [],
"canAddMoreCosmosCoinWrapperPaths": []
},
"manager": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d",
"collectionMetadata": {
"uri": "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/collection.json",
"customData": ""
},
"tokenMetadata": [
{
"uri": "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/{id}.json",
"customData": "",
"tokenIds": [
{ "start": "1", "end": "100" }
]
}
],
"customData": "",
"collectionApprovals": [
{
"fromListId": "Mint",
"toListId": "All",
"initiatedByListId": "All",
"transferTimes": [
{ "start": "1", "end": "18446744073709551615" }
],
"tokenIds": [
{ "start": "1", "end": "18446744073709551615" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
],
"uri": "",
"customData": "",
"approvalId": "mint",
"approvalCriteria": {
"merkleChallenges": [],
"predeterminedBalances": {
"manualBalances": [],
"incrementedBalances": {
"startBalances": [],
"incrementTokenIdsBy": "0",
"incrementOwnershipTimesBy": "0",
"durationFromTimestamp": "0",
"allowOverrideTimestamp": false,
"recurringOwnershipTimes": {
"startTime": "0",
"intervalLength": "0",
"chargePeriodLength": "0"
},
"allowOverrideWithAnyValidToken": false,
"allowAmountScaling": false,
"maxScalingMultiplier": "0"
},
"orderCalculationMethod": {
"useOverallNumTransfers": false,
"usePerToAddressNumTransfers": false,
"usePerFromAddressNumTransfers": false,
"usePerInitiatedByAddressNumTransfers": false,
"useMerkleChallengeLeafIndex": false,
"challengeTrackerId": ""
}
},
"approvalAmounts": {
"overallApprovalAmount": "0",
"perToAddressApprovalAmount": "0",
"perFromAddressApprovalAmount": "0",
"perInitiatedByAddressApprovalAmount": "0",
"amountTrackerId": "",
"resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
},
"maxNumTransfers": {
"overallMaxNumTransfers": "0",
"perToAddressMaxNumTransfers": "0",
"perFromAddressMaxNumTransfers": "0",
"perInitiatedByAddressMaxNumTransfers": "0",
"amountTrackerId": "",
"resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
},
"coinTransfers": [],
"requireToEqualsInitiatedBy": false,
"requireFromEqualsInitiatedBy": false,
"requireToDoesNotEqualInitiatedBy": false,
"requireFromDoesNotEqualInitiatedBy": false,
"overridesFromOutgoingApprovals": true,
"overridesToIncomingApprovals": false,
"autoDeletionOptions": {
"afterOneUse": false,
"afterOverallMaxNumTransfers": false,
"allowCounterpartyPurge": false,
"allowPurgeIfExpired": false
},
"mustOwnTokens": [],
"dynamicStoreChallenges": [],
"ethSignatureChallenges": [],
"senderChecks": {
"mustBeEvmContract": false,
"mustNotBeEvmContract": false,
"mustBeLiquidityPool": false,
"mustNotBeLiquidityPool": false
},
"recipientChecks": {
"mustBeEvmContract": false,
"mustNotBeEvmContract": false,
"mustBeLiquidityPool": false,
"mustNotBeLiquidityPool": false
},
"initiatorChecks": {
"mustBeEvmContract": false,
"mustNotBeEvmContract": false,
"mustBeLiquidityPool": false,
"mustNotBeLiquidityPool": false
},
"altTimeChecks": {
"offlineHours": [],
"offlineDays": [],
"offlineMonths": [],
"offlineDaysOfMonth": [],
"offlineWeeksOfYear": [],
"timezoneOffsetMinutes": "0",
"timezoneOffsetNegative": false
},
"mustPrioritize": false,
"votingChallenges": [],
"allowBackedMinting": false,
"allowSpecialWrapping": false,
"evmQueryChallenges": [],
"userApprovalSettings": {
"allowedDenoms": [],
"disableUserCoinTransfers": false,
"userRoyalties": { "percentage": "0", "payoutAddress": "" }
}
},
"version": "0"
}
],
"standards": [
"NFTs"
],
"isArchived": false,
"mintEscrowCoinsToTransfer": [],
"cosmosCoinWrapperPathsToAdd": [
{
"denom": "utoken",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "100" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "TOKEN",
"denomUnits": [
{
"decimals": "6",
"symbol": "TOKEN",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"allowOverrideWithAnyValidToken": false,
"metadata": { "uri": "", "customData": "" }
}
],
"invariants": {
"noCustomOwnershipTimes": false,
"maxSupplyPerId": "0",
"cosmosCoinBackedPath": null,
"noForcefulPostMintTransfers": false,
"disablePoolCreation": false,
"evmQueryChallenges": []
},
"aliasPathsToAdd": [
{
"denom": "utoken-alias",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "100" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "ALIAS",
"denomUnits": [
{
"decimals": "6",
"symbol": "ALIAS",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"metadata": { "uri": "", "customData": "" }
}
]
}| Field | Type | Required | Description |
|---|---|---|---|
denom | string | yes | Base denom. Full x/bank denom becomes badges:<collectionId>:<denom>. May contain {id}. |
conversion | ConversionWithoutDenom | yes | sideA.amount wrapped units = sideB[] token balances |
symbol | string | yes | On-chain symbol used for identification. May contain {id}. |
denomUnits | DenomUnit[] | no | Display units with decimals, symbol, isDefaultDisplay, optional metadata |
allowOverrideWithAnyValidToken | bool | no | Accept any single valid token ID and override sideB[].tokenIds at transfer time |
metadata | PathMetadata | no | uri and customData |
address | string | derived | The wrapper address, generated from denom. Not present on alias paths. |
Ask your agent:
Add a wrapper path to collection 1 with denom utoken and symbol TOKEN, plus the wrap and unwrap approvals it needs.The MCP builder tools (add_cosmos_wrapper_path, generate_wrapper_address, add_approval) produce the objects on this page.
Wrapper Paths Versus Alias Paths
The chain keeps two separate path types.
Cosmos coin wrapper paths do real wrapping:
- Purpose: convert tokens to native Cosmos SDK coins and back.
- Behavior: tokens burn when wrapping and coins mint; coins burn when unwrapping and tokens mint.
- Use case: IBC transfers and Cosmos ecosystem compatibility.
- Storage: the
cosmosCoinWrapperPathsarray. - Extra fields:
address(the wrapper address) andallowOverrideWithAnyValidToken.
{
"denom": "utoken",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "100" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "TOKEN",
"denomUnits": [
{
"decimals": "6",
"symbol": "TOKEN",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"metadata": { "uri": "", "customData": "" }
}{
"denom": "utoken",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "100" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "TOKEN",
"denomUnits": [
{
"decimals": "6",
"symbol": "TOKEN",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"allowOverrideWithAnyValidToken": false,
"metadata": { "uri": "", "customData": "" }
}Alias paths do no wrapping:
- Purpose: alias denom support (
badgeslp:COLLECTION_ID:denom). - Behavior: no mint or burn; the alias is informational.
- Use case: liquidity pools and DeFi code that expects
sdk.Coin. - Storage: the
aliasPathsarray, separate from wrapper paths. - No
addressand noallowOverrideWithAnyValidTokenfields.
{
"denom": "utoken",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "100" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "TOKEN",
"denomUnits": [
{
"decimals": "6",
"symbol": "TOKEN",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"metadata": { "uri": "", "customData": "" }
}See Alias Denoms for the alias side.
Wrapper Address Generation
The wrapper address derives from the base denom only, not from the full badges:collectionId:denom string.
import { generateAliasAddressForDenom } from 'bitbadges';
const denom = 'utoken';
const wrapperAddress = generateAliasAddressForDenom(denom);
console.log('Wrapper Address:', wrapperAddress);Conversion Structure
Wrapper paths and alias paths both use ConversionWithoutDenom. The denom is stored at the path level, which is why the type carries "WithoutDenom".
{
"denom": "utoken",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "100" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "TOKEN",
"denomUnits": [
{
"decimals": "6",
"symbol": "TOKEN",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"metadata": { "uri": "", "customData": "" }
}{
"denom": "utoken",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "100" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "TOKEN",
"denomUnits": [
{
"decimals": "6",
"symbol": "TOKEN",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"allowOverrideWithAnyValidToken": false,
"metadata": { "uri": "", "customData": "" }
}sideA.amountis the number of wrapped or alias units. It is required and cannot be"0"or nil.sideBis theBalance[]that takes part in the conversion.- Rate:
sideA.amountwrapped units =sideB[]tokens.
With sideA.amount = "1" and sideB = [{ amount: 1n, ... }], one wrapped coin equals one token (1:1). With sideA.amount = "100" and the same sideB, 100 wrapped coins equal one token (100:1).
Configuration Fields
Denom
The full Cosmos denom is badges:collectionId:denom. badges: is the wrapper prefix; badgeslp: is the alias prefix.
{
"denom": "utoken",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "100" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "TOKEN",
"denomUnits": [
{
"decimals": "6",
"symbol": "TOKEN",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"metadata": { "uri": "", "customData": "" }
}{
"denom": "utoken",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "100" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "TOKEN",
"denomUnits": [
{
"decimals": "6",
"symbol": "TOKEN",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"allowOverrideWithAnyValidToken": false,
"metadata": { "uri": "", "customData": "" }
}Conversion
{
"denom": "utoken",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "100" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "TOKEN",
"denomUnits": [
{
"decimals": "6",
"symbol": "TOKEN",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"metadata": { "uri": "", "customData": "" }
}{
"denom": "utoken",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "100" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "TOKEN",
"denomUnits": [
{
"decimals": "6",
"symbol": "TOKEN",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"allowOverrideWithAnyValidToken": false,
"metadata": { "uri": "", "customData": "" }
}Rate: conversion.sideA.amount wrapped coin = conversion.sideB[] tokens.
Denom Units
Several display units can describe the same base unit.
{
"denom": "utoken",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "100" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "TOKEN",
"denomUnits": [
{
"decimals": "3",
"symbol": "mtoken",
"metadata": { "uri": "", "customData": "" }
},
{
"decimals": "6",
"symbol": "TOKEN",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"metadata": { "uri": "", "customData": "" }
}{
"denom": "utoken",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "100" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "TOKEN",
"denomUnits": [
{
"decimals": "3",
"symbol": "mtoken",
"isDefaultDisplay": false,
"metadata": { "uri": "", "customData": "" }
},
{
"decimals": "6",
"symbol": "TOKEN",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"allowOverrideWithAnyValidToken": false,
"metadata": { "uri": "", "customData": "" }
}utokenis the base unit (0 decimals).mtokenis 1,000utoken(3 decimals).TOKENis 1,000,000utoken(6 decimals, default display).
Each DenomUnit carries an optional metadata field of type PathMetadata.
Allow Override with Any Valid Token
When true, the wrapper accepts any single token ID inside the collection's validTokenIds.
{
"denom": "utoken",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "1" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "TOKEN",
"denomUnits": [
{
"decimals": "6",
"symbol": "TOKEN",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"allowOverrideWithAnyValidToken": true,
"metadata": { "uri": "", "customData": "" }
}- A user transfers token ID 5 to the wrapper.
- The chain checks that token ID 5 is in
validTokenIds. - The chain replaces
conversion.sideB[].tokenIdswith[{ start: 5n, end: 5n }]for this transfer and ignores the stored values. - The conversion proceeds with token ID 5.
{id} Placeholder
{id} in denom or symbol is replaced by the actual token ID.
{
"denom": "utoken{id}",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "1" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "TOKEN:{id}",
"denomUnits": [
{
"decimals": "6",
"symbol": "TOKEN",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"allowOverrideWithAnyValidToken": true,
"metadata": { "uri": "", "customData": "" }
}Transferring token ID 5 produces the denom utoken5.
Metadata
{
"denom": "utoken",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "100" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "TOKEN",
"denomUnits": [
{
"decimals": "6",
"symbol": "TOKEN",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"metadata": {
"uri": "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/path.json",
"customData": "{\"key\": \"value\"}"
}
}{
"denom": "utoken",
"conversion": {
"sideA": { "amount": "1" },
"sideB": [
{
"amount": "1",
"tokenIds": [
{ "start": "1", "end": "100" }
],
"ownershipTimes": [
{ "start": "1", "end": "18446744073709551615" }
]
}
]
},
"symbol": "TOKEN",
"denomUnits": [
{
"decimals": "6",
"symbol": "TOKEN",
"isDefaultDisplay": true,
"metadata": { "uri": "", "customData": "" }
}
],
"allowOverrideWithAnyValidToken": false,
"metadata": {
"uri": "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/path.json",
"customData": "{\"key\": \"value\"}"
}
}The hosted JSON is usually { name, image, description }; the image is the main use. The on-chain symbol identifies the path, not the metadata name. Metadata is optional on the path and on each DenomUnit.
Transferability Requirements
A wrapper address follows the same approval rules as any other address. You can gate by user, rate-limit, or apply any criteria.
// Example: Rate-limited wrapping
const collectionApprovals = [
{
fromListId: 'AllWithoutMint',
toListId: wrapperAddress,
initiatedByListId: 'All',
transferTimes: [{ start: 1n, end: 18446744073709551615n }],
tokenIds: [{ start: 1n, end: 100n }],
ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
approvalId: 'wrap-approval',
version: 0n,
approvalCriteria: {
allowSpecialWrapping: true, // Required for wrapper path operations
mustPrioritize: true, // Chain-enforced: required for allowSpecialWrapping
maxNumTransfers: {
overallMaxNumTransfers: 0n,
perToAddressMaxNumTransfers: 0n,
perFromAddressMaxNumTransfers: 0n,
perInitiatedByAddressMaxNumTransfers: 10n, // 10 wraps per day
amountTrackerId: 'wrap-daily',
resetTimeIntervals: { startTime: 1788739200000n, intervalLength: 86400000n },
},
},
},
{
fromListId: wrapperAddress,
toListId: 'All',
initiatedByListId: 'All',
transferTimes: [{ start: 1n, end: 18446744073709551615n }],
tokenIds: [{ start: 1n, end: 100n }],
ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
approvalId: 'unwrap-approval',
version: 0n,
approvalCriteria: {
allowSpecialWrapping: true, // Required for wrapper path operations
mustPrioritize: true, // Chain-enforced: required for allowSpecialWrapping
// Override wrapper's outgoing approvals (wrapper is the sender for unwrapping)
overridesFromOutgoingApprovals: true,
},
},
];Conversion Process
Token to Coin (Wrapping)
- The user transfers tokens to the wrapper address.
- The chain processes the denom (replaces
{id}, validates the override if enabled). - The chain burns the tokens from the user's balance.
- The chain mints the equivalent native coins.
- The coins are credited to the user's account.
// Wrapping tokens
// Wrapping/unwrapping requires prioritized approvals (not compatible with auto-scan mode)
const wrapTokens: MsgTransferTokens = {
creator: 'bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue',
collectionId: '1',
transfers: [
{
from: 'bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue',
toAddresses: [wrapperAddress],
balances: [
{
amount: 10n,
tokenIds: [{ start: 1n, end: 100n }],
ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
},
],
prioritizedApprovals: [
{
approvalId: 'wrap-approval',
approvalLevel: 'collection',
approverAddress: '',
version: 0n,
},
],
onlyCheckPrioritizedCollectionApprovals: true,
},
],
};
// Result: User receives 10 badges:1:utoken coins (based on conversion.sideA.amount = 1)
// 10 tokens are burned (based on conversion.sideB balances)Coin to Token (Unwrapping)
Unwrapping also uses MsgTransferTokens. The user initiates a transfer on behalf of the wrapper address.
- The user submits
MsgTransferTokenswith the wrapper address asfrom. - The chain processes the denom (replaces
{id}, validates the override if enabled). - The chain burns the native coins from the wrapper address.
- The chain mints the equivalent tokens.
- The tokens are credited to the user's balance.
// Unwrapping coins
// Wrapping/unwrapping requires prioritized approvals (not compatible with auto-scan mode)
// You initiate a transfer on behalf of the wrapper address
const unwrapCoins: MsgTransferTokens = {
creator: 'bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue',
collectionId: '1',
transfers: [
{
from: wrapperAddress, // Transfer from wrapper address
toAddresses: ['bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue'], // To user
balances: [
{
amount: 10n,
tokenIds: [{ start: 1n, end: 100n }],
ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
},
],
prioritizedApprovals: [
{
approvalId: 'unwrap-approval',
approvalLevel: 'collection',
approverAddress: '',
version: 0n,
},
],
onlyCheckPrioritizedCollectionApprovals: true,
},
],
};
// Result: User receives 10 tokens (based on conversion.sideB balances)
// 10 badges:1:utoken coins are burned from wrapper address (based on conversion.sideA.amount = 1)Use Cases
IBC Transfers
Wrap, then send the x/bank coin over ICS-20.
// Wrap tokens for IBC transfer
// Requires prioritized approvals
// The conversion rate is defined in the wrapper path's conversion field
const wrapForIBC: MsgTransferTokens = {
creator: 'bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue',
collectionId: '1',
transfers: [
{
from: 'bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue',
toAddresses: [wrapperAddress],
balances: [
{
amount: 100n,
tokenIds: [{ start: 1n, end: 100n }],
ownershipTimes: [{ start: 1n, end: 18446744073709551615n }],
},
],
prioritizedApprovals: [
{
approvalId: 'wrap-approval',
approvalLevel: 'collection',
approverAddress: '',
version: 0n,
},
],
onlyCheckPrioritizedCollectionApprovals: true,
},
],
};
// Transfer wrapped coins via IBC
const ibcTransfer = {
sourcePort: 'transfer',
sourceChannel: 'channel-0',
token: {
denom: 'badges:1:utoken',
amount: '100',
},
sender: 'bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue',
receiver: 'cosmos1py4mfpg6uf59qkyzg0nmau322c5873ee8df8qg',
};DeFi Integration
// Add wrapped tokens to liquidity pool
const addLiquidity = {
poolId: '1',
sender: 'bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue',
tokenInMaxs: [
{
denom: 'badges:1:utoken',
amount: '1000000',
},
{
denom: 'uatom',
amount: '500000',
},
],
};Permission Control
The canAddMoreCosmosCoinWrapperPaths collection permission controls when the manager may add wrapper paths. It is an ActionPermission with time-based controls.
- Empty or nil means adding paths is allowed (neutral state).
- Collections migrated from v21 have empty permissions, so adding paths is allowed by default.
Allow at all times:
// Empty = allowed by default
const collectionPermissions: CollectionPermissions<bigint> = {
canDeleteCollection: [],
canArchiveCollection: [],
canUpdateStandards: [],
canUpdateCustomData: [],
canUpdateManager: [],
canUpdateCollectionMetadata: [],
canUpdateValidTokenIds: [],
canUpdateTokenMetadata: [],
canUpdateCollectionApprovals: [],
canAddMoreAliasPaths: [],
canAddMoreCosmosCoinWrapperPaths: [],
};Explicitly permit forever:
const collectionPermissions: CollectionPermissions<bigint> = {
canDeleteCollection: [],
canArchiveCollection: [],
canUpdateStandards: [],
canUpdateCustomData: [],
canUpdateManager: [],
canUpdateCollectionMetadata: [],
canUpdateValidTokenIds: [],
canUpdateTokenMetadata: [],
canUpdateCollectionApprovals: [],
canAddMoreAliasPaths: [],
canAddMoreCosmosCoinWrapperPaths: [
{
permanentlyPermittedTimes: [
{ start: 1n, end: 18446744073709551615n },
],
permanentlyForbiddenTimes: [],
},
],
};Lock forever:
const collectionPermissions: CollectionPermissions<bigint> = {
canDeleteCollection: [],
canArchiveCollection: [],
canUpdateStandards: [],
canUpdateCustomData: [],
canUpdateManager: [],
canUpdateCollectionMetadata: [],
canUpdateValidTokenIds: [],
canUpdateTokenMetadata: [],
canUpdateCollectionApprovals: [],
canAddMoreAliasPaths: [],
canAddMoreCosmosCoinWrapperPaths: [
{
permanentlyPermittedTimes: [],
permanentlyForbiddenTimes: [
{ start: 1n, end: 18446744073709551615n },
],
},
],
};Allow only during a window:
const collectionPermissions: CollectionPermissions<bigint> = {
canDeleteCollection: [],
canArchiveCollection: [],
canUpdateStandards: [],
canUpdateCustomData: [],
canUpdateManager: [],
canUpdateCollectionMetadata: [],
canUpdateValidTokenIds: [],
canUpdateTokenMetadata: [],
canUpdateCollectionApprovals: [],
canAddMoreAliasPaths: [],
canAddMoreCosmosCoinWrapperPaths: [
{
permanentlyPermittedTimes: [
{ start: 1704067200000n, end: 1735689600000n },
],
permanentlyForbiddenTimes: [],
},
],
};When MsgUniversalUpdateCollection carries cosmosCoinWrapperPathsToAdd, the chain checks canAddMoreCosmosCoinWrapperPaths before it processes the paths. A failed check rejects the transaction. The check happens before the paths are added, but the permission itself can still be updated at the end of the same transaction when updateCollectionPermissions is true. Paths can be added but never edited.
Differences from Backed Paths
| Feature | Wrapper path | Backed path |
|---|---|---|
| Minting | Mints and burns a new denom | No mint or burn; uses an existing IBC denom |
| Denom source | Generated | Existing IBC denom |
| Configuration | Paths can be added, never edited | Collection invariant, set once |
| Mint address | Enabled | Disabled |