Mint and Distribute
Mint BitBadges tokens to yourself or to the public, charge for mints, pay out from escrow, and control circulating supply.
Set SIGNER to the wallet address authorized to initiate the mint, never Mint. Browser deployment requires this binding even when reading a saved transaction.
At the end your collection has the mint approvals it needs, tokens are in wallets, and the supply is as fixed or as open as you decided.
Minting is a transfer from the reserved Mint address, allowed by a collection approval; see Minting and Supply. For code-, allowlist-, or social-gated distribution, use Distribute with Claims instead.
Rules that apply to every mint approval:
fromListId: "Mint".overridesFromOutgoingApprovals: true. The Mint address has no outgoing approvals to check. Without a matching or overridden outgoing check, the transfer is rejected.- A passing incoming check: for example
autoApproveAllIncomingTransfers: trueindefaultBalances, a matching incoming approval, self-initiated incoming auto-approval, or an explicit collection-level override. predeterminedBalancesandapprovalAmountsare incompatible; use one or the other.orderCalculationMethodmust have exactly one method set totruewhenpredeterminedBalancesis used (defaultuseOverallNumTransfers).amountTrackerIdis required whenmaxNumTransfersorapprovalAmountsis set.- All numbers are strings.
1. Choose a Mint Pattern
| Pattern | initiatedByListId | Distinctive criteria |
|---|---|---|
| Creator-only mint | your address | none; you mint any amount later |
| Public mint, sequential IDs | "All" | predeterminedBalances.incrementedBalances with incrementTokenIdsBy: "1" |
| Paid mint | "All" | coinTransfers with both override flags false |
| Free mint with payout | "All" | coinTransfers with both override flags true, funded via mintEscrowCoinsToTransfer |
| Capped mint | any | maxNumTransfers or approvalAmounts with an amountTrackerId |
| One-shot approval | any | autoDeletionOptions.afterOneUse: true |
Ask your agent. With the MCP builder tools installed, paste one of these:
Add a public mint to collection 1 that charges 5 BADGE per mint, one per address, capped at 1,000 mints, and give me the review link.Build a transfer that mints token 1 of collection 1 to bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue through the manager-mint approval, validate it, and give me the review link.Creator-Only Mint
import { UintRangeArray } from 'bitbadges';
const myAddress = 'bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d';
const mintApproval = {
fromListId: 'Mint', // From the mint address
toListId: 'All', // To any address
initiatedByListId: myAddress, // Only you can initiate
transferTimes: UintRangeArray.FullRanges(),
tokenIds: UintRangeArray.FullRanges(), // All token IDs
ownershipTimes: UintRangeArray.FullRanges(),
approvalId: 'mint-approval',
version: 0n,
approvalCriteria: {
// No restrictions: you can mint unlimited amounts
...EmptyApprovalCriteria,
overridesFromOutgoingApprovals: true, // Required for the Mint address
},
};
const collection = {
...BaseCollectionDetails,
collectionApprovals: [mintApproval, transferableApproval],
};EmptyApprovalCriteria and transferableApproval are the no-restrictions template and the post-mint approval in Set Transferability. BaseCollectionDetails is from Create a Collection.
Paid Mint
{
"approvalCriteria": {
"coinTransfers": [{
"to": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d",
"coins": [{ "denom": "ubadge", "amount": "5000000000" }],
"overrideFromWithApproverAddress": false,
"overrideToWithInitiator": false
}]
}
}Both override flags are false for a standard payment: the initiator pays, and to (the creator or approver) receives. See Coin Transfers.
Sequential Token IDs
{
"approvalCriteria": {
"predeterminedBalances": {
"incrementedBalances": {
"startBalances": [{
"amount": "1",
"tokenIds": [{ "start": "1", "end": "1" }],
"ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }]
}],
"incrementTokenIdsBy": "1",
"recurringOwnershipTimes": { "startTime": "0", "intervalLength": "0", "chargePeriodLength": "0" }
},
"orderCalculationMethod": {
"useOverallNumTransfers": true
}
}
}
}{
"approvalCriteria": {
"predeterminedBalances": {
"incrementedBalances": {
"startBalances": [{
"amount": "1",
"tokenIds": [{ "start": "1", "end": "1" }],
"ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }]
}],
"incrementTokenIdsBy": "1",
"incrementOwnershipTimesBy": "0",
"durationFromTimestamp": "0",
"allowOverrideTimestamp": false,
"recurringOwnershipTimes": { "startTime": "0", "intervalLength": "0", "chargePeriodLength": "0" },
"allowOverrideWithAnyValidToken": false,
"allowAmountScaling": false,
"maxScalingMultiplier": "0"
},
"orderCalculationMethod": {
"useOverallNumTransfers": true,
"usePerToAddressNumTransfers": false,
"usePerFromAddressNumTransfers": false,
"usePerInitiatedByAddressNumTransfers": false,
"useMerkleChallengeLeafIndex": false,
"challengeTrackerId": ""
},
"manualBalances": []
}
}
}For one-time or fixed-use approvals, prefer incrementedBalances with zero increments (incrementTokenIdsBy: "0", incrementOwnershipTimesBy: "0") over maxNumTransfers alone. The BitBadges site detects predeterminedBalances and shows users the exact tokens they will receive. Avoid manualBalances. See Predetermined Balances.
Transfer Limits
{
"approvalCriteria": {
"maxNumTransfers": {
"overallMaxNumTransfers": "100",
"perInitiatedByAddressMaxNumTransfers": "1",
"perToAddressMaxNumTransfers": "0",
"perFromAddressMaxNumTransfers": "0",
"amountTrackerId": "mint-tracker",
"resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
}
}
}"0" means unlimited. See Approval Trackers.
Auto-Deletion
{
"approvalCriteria": {
"autoDeletionOptions": {
"afterOneUse": true,
"afterOverallMaxNumTransfers": false,
"allowCounterpartyPurge": false,
"allowPurgeIfExpired": false
}
}
}See Auto-Deletion.
Free Mint with a Payout from Escrow
The mint escrow address is a reserved address derived from the collection ID. It holds native coins and has no private key; only collection approvals can move funds out of it. Fund it at creation with mintEscrowCoinsToTransfer (the address depends on the collection ID, so genesis is the convenient moment) or top it up later. This complete MsgCreateCollection funds the escrow with 10,000 BADGE, pays each minter 1 BADGE, and caps the mint at one token per address and ten in total:
{
"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": "100" }],
"ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
"approvalId": "free-mint",
"approvalCriteria": {
"predeterminedBalances": {
"incrementedBalances": {
"startBalances": [
{
"amount": "1",
"ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
"tokenIds": [{ "start": "1", "end": "1" }]
}
],
"incrementTokenIdsBy": "1",
"recurringOwnershipTimes": { "startTime": "0", "intervalLength": "0", "chargePeriodLength": "0" }
},
"orderCalculationMethod": {
"useOverallNumTransfers": true
}
},
"approvalAmounts": {
"resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
},
"maxNumTransfers": {
"overallMaxNumTransfers": "10",
"perInitiatedByAddressMaxNumTransfers": "1",
"amountTrackerId": "free-mint",
"resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
},
"coinTransfers": [
{
"coins": [{ "denom": "ubadge", "amount": "1000000000" }],
"overrideFromWithApproverAddress": true,
"overrideToWithInitiator": true
}
],
"overridesFromOutgoingApprovals": true,
"userApprovalSettings": {
"userRoyalties": { "percentage": "0", "payoutAddress": "" }
}
}
}
],
"standards": ["NFTs"],
"mintEscrowCoinsToTransfer": [{ "denom": "ubadge", "amount": "10000000000" }],
"invariants": {
"cosmosCoinBackedPath": { "conversion": { "sideA": { "amount": "0", "denom": "" }, "sideB": [] } }
}
}{
"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": "100" }],
"ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
"uri": "",
"customData": "",
"approvalId": "free-mint",
"approvalCriteria": {
"merkleChallenges": [],
"predeterminedBalances": {
"manualBalances": [],
"incrementedBalances": {
"startBalances": [
{
"amount": "1",
"ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
"tokenIds": [{ "start": "1", "end": "1" }]
}
],
"incrementTokenIdsBy": "1",
"incrementOwnershipTimesBy": "0",
"durationFromTimestamp": "0",
"allowOverrideTimestamp": false,
"recurringOwnershipTimes": { "startTime": "0", "intervalLength": "0", "chargePeriodLength": "0" },
"allowOverrideWithAnyValidToken": false,
"allowAmountScaling": false,
"maxScalingMultiplier": "0"
},
"orderCalculationMethod": {
"useOverallNumTransfers": true,
"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": "10",
"perToAddressMaxNumTransfers": "0",
"perFromAddressMaxNumTransfers": "0",
"perInitiatedByAddressMaxNumTransfers": "1",
"amountTrackerId": "free-mint",
"resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
},
"coinTransfers": [
{
"to": "",
"coins": [{ "denom": "ubadge", "amount": "1000000000" }],
"overrideFromWithApproverAddress": true,
"overrideToWithInitiator": true
}
],
"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": [{ "denom": "ubadge", "amount": "10000000000" }],
"cosmosCoinWrapperPathsToAdd": [],
"invariants": {
"noCustomOwnershipTimes": false,
"maxSupplyPerId": "0",
"cosmosCoinBackedPath": { "conversion": { "sideA": { "amount": "0", "denom": "" }, "sideB": [] } },
"noForcefulPostMintTransfers": false,
"disablePoolCreation": false,
"evmQueryChallenges": []
},
"aliasPathsToAdd": []
}overrideFromWithApproverAddress: truemakes the mint escrow the payer.overrideToWithInitiator: truepays whoever initiated the mint, ignoringto.
Complete Example: Public Paid Mint with Sequential IDs and Caps
One complete CollectionApproval for collectionApprovals:
{
"fromListId": "Mint",
"toListId": "All",
"initiatedByListId": "All",
"transferTimes": [{ "start": "1", "end": "18446744073709551615" }],
"tokenIds": [{ "start": "1", "end": "18446744073709551615" }],
"ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
"uri": "",
"approvalId": "public-mint-5-badge",
"approvalCriteria": {
"predeterminedBalances": {
"incrementedBalances": {
"startBalances": [
{
"amount": "1",
"ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
"tokenIds": [{ "start": "1", "end": "1" }]
}
],
"incrementTokenIdsBy": "1",
"recurringOwnershipTimes": { "startTime": "0", "intervalLength": "0", "chargePeriodLength": "0" }
},
"orderCalculationMethod": {
"useOverallNumTransfers": true
}
},
"approvalAmounts": {
"resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
},
"maxNumTransfers": {
"overallMaxNumTransfers": "1000",
"perInitiatedByAddressMaxNumTransfers": "1",
"amountTrackerId": "public-mint-tracker",
"resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
},
"coinTransfers": [
{
"to": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d",
"coins": [{ "denom": "ubadge", "amount": "5000000000" }]
}
],
"overridesFromOutgoingApprovals": true,
"userApprovalSettings": {
"userRoyalties": { "percentage": "0", "payoutAddress": "" }
}
}
}{
"fromListId": "Mint",
"toListId": "All",
"initiatedByListId": "All",
"transferTimes": [{ "start": "1", "end": "18446744073709551615" }],
"tokenIds": [{ "start": "1", "end": "18446744073709551615" }],
"ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
"uri": "",
"customData": "",
"approvalId": "public-mint-5-badge",
"approvalCriteria": {
"merkleChallenges": [],
"predeterminedBalances": {
"manualBalances": [],
"incrementedBalances": {
"startBalances": [
{
"amount": "1",
"ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
"tokenIds": [{ "start": "1", "end": "1" }]
}
],
"incrementTokenIdsBy": "1",
"incrementOwnershipTimesBy": "0",
"durationFromTimestamp": "0",
"allowOverrideTimestamp": false,
"recurringOwnershipTimes": { "startTime": "0", "intervalLength": "0", "chargePeriodLength": "0" },
"allowOverrideWithAnyValidToken": false,
"allowAmountScaling": false,
"maxScalingMultiplier": "0"
},
"orderCalculationMethod": {
"useOverallNumTransfers": true,
"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": "1000",
"perToAddressMaxNumTransfers": "0",
"perFromAddressMaxNumTransfers": "0",
"perInitiatedByAddressMaxNumTransfers": "1",
"amountTrackerId": "public-mint-tracker",
"resetTimeIntervals": { "startTime": "0", "intervalLength": "0" }
},
"coinTransfers": [
{
"to": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d",
"coins": [{ "denom": "ubadge", "amount": "5000000000" }],
"overrideFromWithApproverAddress": false,
"overrideToWithInitiator": false
}
],
"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"
}To add a mint approval after creation, the collection's canUpdateCollectionApprovals permission must not be frozen for Mint, and you send the approval in a separate MsgUniversalUpdateCollection. See Lock Permissions.
2. Mint at Creation Time
One transaction can carry the MsgUniversalUpdateCollection that creates the collection plus one or more MsgTransferTokens. Every transfer uses collectionId: "0", which refers to the collection created by the first message in the same transaction.
{
"typeUrl": "/tokenization.MsgTransferTokens",
"value": {
"creator": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d",
"transfers": [
{
"from": "Mint",
"toAddresses": ["bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue"],
"balances": [
{
"amount": "1",
"ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
"tokenIds": [{ "start": "1", "end": "1" }]
}
],
"precalculateBalancesFromApproval": {
"precalculationOptions": { "overrideTimestamp": "0", "tokenIdsOverride": [], "scalingMultiplier": "0" }
},
"prioritizedApprovals": [
{
"approvalId": "manager-mint",
"approvalLevel": "collection"
}
]
}
]
}
}{
"typeUrl": "/tokenization.MsgTransferTokens",
"value": {
"creator": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d",
"collectionId": "0",
"transfers": [
{
"from": "Mint",
"toAddresses": ["bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue"],
"balances": [
{
"amount": "1",
"ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
"tokenIds": [{ "start": "1", "end": "1" }]
}
],
"precalculateBalancesFromApproval": {
"approvalId": "",
"approvalLevel": "",
"approverAddress": "",
"version": "0",
"precalculationOptions": { "overrideTimestamp": "0", "tokenIdsOverride": [], "scalingMultiplier": "0" }
},
"merkleProofs": [],
"ethSignatureProofs": [],
"memo": "",
"prioritizedApprovals": [
{
"approvalId": "manager-mint",
"approvalLevel": "collection",
"approverAddress": "",
"version": "0"
}
],
"onlyCheckPrioritizedCollectionApprovals": false,
"onlyCheckPrioritizedIncomingApprovals": false,
"onlyCheckPrioritizedOutgoingApprovals": false
}
]
}
}Use this when you want tokens in wallets right after creation: minting to yourself or others, an initial allocation, or a manager-only collection where the manager holds everything. Do not add transfer messages to public-mint, subscription, or smart token collections; those mint later through their approvals.
Rules:
prioritizedApprovalsmust be present, even as[]. MatchapprovalIdto one of the collection'scollectionApprovals.from: "Mint"mints new tokens. A regularbb1address makes a peer-to-peer transfer.- The signer (
creator) is the initiator, so the collection needs an approval that allows this address asinitiatedBy. - All numbers are strings.
For expiring tokens, set ownershipTimes on the balance to a window in milliseconds since the epoch. This one lasts five minutes from 1788739200000 (2026-09-06T00:00:00Z), the current timestamp plus 5 * 60 * 1000:
{
"amount": "1",
"tokenIds": [{ "start": "1", "end": "1" }],
"ownershipTimes": [{ "start": "1788739200000", "end": "1788739500000" }]
}MCP builder tool sessions edit these messages with patch operations: add_transfer ({ op: "add_transfer", transfer: { transfers: [...] } }) appends a MsgTransferTokens, remove_transfer ({ op: "remove_transfer", index: 0 }) removes one by 0-based index among the transfer messages, and update_transfer ({ op: "update_transfer", index: 0, changes: {...} }) deep-merges changes. See MCP tools.
3. Mint After Creation
bb CLI
bb build transfer fetches the collection, the sender's outgoing approvals, and the recipient's incoming approvals, then walks you through the choices. It needs BITBADGES_API_KEY (env var or bb settings set apiKey ...). Create a key at bitbadges.io/developer.
# Interactive walkthrough: prompts for everything
bb build transfer
# Flag-driven: still prompts for the approval-selection step
bb build transfer --collection-id 1 --from Mint --to bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue --amount 5
# Fully non-interactive: no prioritized approvals (chain matches), no
# precalculation, default amount=1, default tokenIds=all valid
bb build transfer --yes --collection-id 1 --from Mint --to bb1py4mfpg6uf59qkyzg0nmau322c5873eeysp5ue | bb deploy --browser --expected-address "$SIGNER"| Flag | Required | Description |
|---|---|---|
--collection-id <id> | No | Collection ID (prompts if omitted) |
--from <address> | No | Sender: a bb1 or 0x address, or Mint for minting (prompts if omitted) |
--to <address> | No | Recipient (cannot be Mint; prompts if omitted) |
--amount <n> | No | Per-recipient amount when not precalculated (default: prompt; 1 with --yes) |
--token-ids <spec> | No | 1-5, 1,3,5, or all (default: prompt; all with --yes) |
-y, --yes | No | Skip every prompt. For scripts and CI |
The walkthrough lists approvals grouped by level (collection, outgoing, incoming) with tags predetermined, payment, must-own, and backed, then asks:
- Which approvals to set as
prioritizedApprovals(comma-separated indices, blank to skip). - For each level with a pick, whether to set
onlyCheckPrioritized<Level>Approvals: true. - If a picked approval has
predeterminedBalances, whether to delegate balance computation withprecalculateBalancesFromApproval(and an optionalscalingMultiplierto consume N predetermined steps in one transaction). - If not precalculated, the per-recipient
amountandtokenIds.
If a picked approval requires a coin payment or prerequisite token ownership, the walkthrough prints a "Heads up" line before emitting. The output flows through the same pipeline as the collection builders, so --simulate, --explain, and --browser behave identically. --burner is create-only and refuses transfers.
Raw JSON: Explicit Balances
Mint token ID 1 of collection 1 to the creator, naming the collection approval to use. onlyCheckPrioritizedCollectionApprovals: true skips auto-scanning of other collection approvals; the user-level approvals still auto-scan.
{
"creator": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d",
"collectionId": "1",
"transfers": [
{
"from": "Mint",
"toAddresses": ["bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d"],
"balances": [
{
"amount": "1",
"ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
"tokenIds": [{ "start": "1", "end": "1" }]
}
],
"precalculateBalancesFromApproval": {
"precalculationOptions": { "overrideTimestamp": "0", "tokenIdsOverride": [], "scalingMultiplier": "0" }
},
"prioritizedApprovals": [
{
"approvalId": "manager-mint",
"approvalLevel": "collection"
}
],
"onlyCheckPrioritizedCollectionApprovals": true
}
]
}{
"creator": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d",
"collectionId": "1",
"transfers": [
{
"from": "Mint",
"toAddresses": ["bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d"],
"balances": [
{
"amount": "1",
"ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
"tokenIds": [{ "start": "1", "end": "1" }]
}
],
"precalculateBalancesFromApproval": {
"approvalId": "",
"approvalLevel": "",
"approverAddress": "",
"version": "0",
"precalculationOptions": { "overrideTimestamp": "0", "tokenIdsOverride": [], "scalingMultiplier": "0" }
},
"merkleProofs": [],
"ethSignatureProofs": [],
"memo": "",
"prioritizedApprovals": [
{
"approvalId": "manager-mint",
"approvalLevel": "collection",
"approverAddress": "",
"version": "0"
}
],
"onlyCheckPrioritizedCollectionApprovals": true,
"onlyCheckPrioritizedIncomingApprovals": false,
"onlyCheckPrioritizedOutgoingApprovals": false
}
]
}Raw JSON: Precalculated Balances
When the approval has predeterminedBalances, leave balances empty and let the chain compute them from the approval. Only the named approval is checked; no other approval is scanned. This is how approvals with side effects (Merkle challenges, ETH signature challenges, payments) are used deliberately, and it shows the approval version being pinned.
{
"creator": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d",
"collectionId": "1",
"transfers": [
{
"from": "Mint",
"toAddresses": ["bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d"],
"precalculateBalancesFromApproval": {
"approvalId": "manager-mint",
"approvalLevel": "collection",
"precalculationOptions": { "overrideTimestamp": "0", "tokenIdsOverride": [], "scalingMultiplier": "0" }
},
"prioritizedApprovals": [
{
"approvalId": "manager-mint",
"approvalLevel": "collection"
}
],
"onlyCheckPrioritizedCollectionApprovals": true
}
]
}{
"creator": "bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d",
"collectionId": "1",
"transfers": [
{
"from": "Mint",
"toAddresses": ["bb1p0rrel3365scadq5k9pv0x0zp9j22js6dnw70d"],
"balances": [],
"precalculateBalancesFromApproval": {
"approvalId": "manager-mint",
"approvalLevel": "collection",
"approverAddress": "",
"version": "0",
"precalculationOptions": { "overrideTimestamp": "0", "tokenIdsOverride": [], "scalingMultiplier": "0" }
},
"merkleProofs": [],
"ethSignatureProofs": [],
"memo": "",
"prioritizedApprovals": [
{
"approvalId": "manager-mint",
"approvalLevel": "collection",
"approverAddress": "",
"version": "0"
}
],
"onlyCheckPrioritizedCollectionApprovals": true,
"onlyCheckPrioritizedIncomingApprovals": false,
"onlyCheckPrioritizedOutgoingApprovals": false
}
]
}precalculationOptions.overrideTimestamp: "0"uses the current time. It applies only if the approval hasallowOverrideTimestamp: true.precalculationOptions.tokenIdsOverride: []uses the approval's own token IDs. It applies only if the approval hasallowOverrideWithAnyValidToken: true.precalculationOptions.scalingMultiplier: "0"consumes one predetermined step. A larger value consumes that many steps in one transaction and applies only if the approval hasallowAmountScaling: true.
| Explicit balances | Precalculated | |
|---|---|---|
| Balance specification | manual amounts | computed from the approval |
| Approval scanning | auto-scan for unlisted levels | only the named approval |
| Fits | fixed amounts you control | approvals whose criteria decide amounts and IDs |
See Prioritized Approvals and MsgTransferTokens.
TypeScript SDK
import { BitBadgesSigningClient, GenericCosmosAdapter, MsgTransferTokens, UintRangeArray } from 'bitbadges';
const adapter = await GenericCosmosAdapter.fromMnemonic(process.env.MNEMONIC!, 'bitbadges-1');
const client = new BitBadgesSigningClient({ adapter, network: 'mainnet' });
const myAddress = client.address;
const transfers = [
{
from: 'Mint', // From mint address
toAddresses: [myAddress], // To your address
balances: [
{
tokenIds: [{ start: 1n, end: 100n }],
ownershipTimes: UintRangeArray.FullRanges(),
amount: 100n,
},
],
prioritizedApprovals: [{ approvalId: 'mint-approval', approvalLevel: 'collection', approverAddress: '', version: 0n }],
onlyCheckPrioritizedCollectionApprovals: true,
onlyCheckPrioritizedIncomingApprovals: false,
onlyCheckPrioritizedOutgoingApprovals: false,
merkleProofs: [],
ethSignatureProofs: [],
memo: '',
},
];
const msg = new MsgTransferTokens({ creator: myAddress, collectionId: '1', transfers });
const result = await client.signAndBroadcast([msg]);
console.log(result.success ? result.txHash : result.error);4. Define and Lock Circulating Supply
Supply on BitBadges is not a fixed number. It is whatever the current mint approvals allow, plus whatever new mint approvals the manager can still create. If the manager can add or edit a Mint approval, they can raise supply by whatever that approval allows. The canUpdateCollectionApprovals permission is what makes supply final.
const FullTimeRanges = [
{
start: '1',
end: '18446744073709551615',
},
];Each block below is the canUpdateCollectionApprovals value to set inside collectionPermissions; the other ten permission arrays stay as in Create a Collection.
Lock supply forever (fixed cap). Every existing Mint approval stays as it is and no new one can be added:
const canUpdateCollectionApprovals = [
{
fromListId: 'Mint', // Target all mint approvals
toListId: 'All',
initiatedByListId: 'All',
transferTimes: FullTimeRanges,
tokenIds: FullTimeRanges,
ownershipTimes: FullTimeRanges,
approvalId: 'All',
permanentlyPermittedTimes: [],
permanentlyForbiddenTimes: FullTimeRanges, // Cannot update mint approvals
},
];Controlled supply. Only the initial-mint approval is locked; the manager can add new ones:
const canUpdateCollectionApprovals = [
{
fromListId: 'Mint',
toListId: 'All',
initiatedByListId: 'All',
transferTimes: FullTimeRanges,
tokenIds: FullTimeRanges,
ownershipTimes: FullTimeRanges,
approvalId: 'initial-mint', // Only lock initial mint approval
permanentlyPermittedTimes: [],
permanentlyForbiddenTimes: FullTimeRanges,
},
];Dynamic supply. The manager can always change mint approvals:
const canUpdateCollectionApprovals = []; // Soft-enabled, like canAddMoreAliasPaths and canAddMoreCosmosCoinWrapperPathsLock specific token IDs. Mint approvals for tokens 1 to 100 are final; the manager can still add Mint approvals for other IDs, and post-mint approvals for these:
const canUpdateCollectionApprovals = [
{
fromListId: 'Mint',
toListId: 'All',
initiatedByListId: 'All',
transferTimes: FullTimeRanges,
tokenIds: [
{
start: '1',
end: '100',
},
],
ownershipTimes: FullTimeRanges,
approvalId: 'All',
permanentlyPermittedTimes: [],
permanentlyForbiddenTimes: FullTimeRanges,
},
];More locking patterns are in Lock Permissions.
Common Mistakes
- Numbers instead of strings (
"1000", not1000). - Missing
overridesFromOutgoingApprovals: trueon a Mint approval. - No matching incoming approval, auto-approve flag, or collection-level incoming override for the recipient.
- Missing
prioritizedApprovalsinMsgTransferTokens. The field must be present, even as[]. - Combining
predeterminedBalanceswithapprovalAmounts. - More than one
trueinorderCalculationMethod. - Coin transfer override flags set
truefor a standard payment (they are for escrow payouts only).