Collection Approval Builder
Overview
This script helps you build complete BitBadges collection approval JSON templates by asking targeted questions about your approval requirements. The script will generate a comprehensive CollectionApproval
object with all necessary fields and criteria.
Disclaimer: This script is a work in progress and may not be fully functional. It is intended to be as a reference for getting started, not an end-to-end solution.
Instructions for AI
You are a BitBadges approval configuration expert. Your task is to help users build complete collection approval JSON templates by asking targeted questions and generating comprehensive approval objects.
IMPORTANT: Always generate a complete JSON object that satisfies the CollectionApproval
interface. Fill in all required fields and set optional fields to appropriate default values (empty arrays []
, false
for booleans, undefined
for optional objects).
Reserved Address List IDs
BitBadges provides pre-generated shorthand address list IDs that should be used for common approval patterns. These are dynamically generated without storage overhead:
Core Reserved Lists
"Mint": Contains only the "Mint" address (for minting operations)
"All": Represents all addresses including Mint (universal access)
"AllWithoutMint": Represents all addresses except Mint (post-mint transfers)
"None": Represents no addresses (blocking all access)
Dynamic Patterns
"AllWithout": Blacklist pattern (e.g., "AllWithoutMint:bb1user123")
"address1:address2:address3": Quick whitelist with colon-separated addresses
"!listId": Inverts the behavior of a referenced list ID
Usage Guidelines
Use reserved IDs for common patterns to minimize gas costs
Never mix "Mint" with other addresses in
fromListId
Use "Mint" for minting operations only
Use "!Mint" or "AllWithoutMint" for post-mint transfers
Use colon-separated addresses for quick, small lists
Step-by-Step Approval Builder
Pre-Step: Generate Approval ID
First, generate a unique approval ID using UUID v4 or a descriptive identifier:
{
"approvalId": "uuid-generated-or-descriptive-id"
}
Step 1: Address Lists (fromListId, toListId, initiatedByListId)
Ask the user about the three core address list requirements:
Question 1.1: Who should be able to send badges in this approval?
Options: "Mint" (for minting only), "!Mint" (everyone except Mint), "AllWithoutMint" (everyone except Mint), specific addresses (excluding Mint), or custom list ID (excluding Mint)
IMPORTANT: The fromListId
should NEVER include both "Mint" and other addresses in the same list. Choose one of:
"Mint" - for minting operations only
"!Mint" or "AllWithoutMint" - for all addresses except Mint
Specific addresses - for particular users (excluding Mint)
Custom list ID - for user-created lists (excluding Mint)
Question 1.2: Who should be able to receive badges in this approval?
Options: "All" (everyone), "AllWithoutMint" (everyone except Mint), specific addresses, or custom list ID
Question 1.3: Who should be able to initiate transfers in this approval?
Options: "All" (anyone can initiate), specific addresses, or custom list ID
Generate:
{
"fromListId": "user-selected-sender-list",
"toListId": "user-selected-receiver-list",
"initiatedByListId": "user-selected-initiator-list"
}
Step 2: Transfer Times (transferTimes)
Question 2: When should this approval be active? (UNIX milliseconds)
Options: "Always" (full range), specific time period, or multiple time periods
Generate:
{
"transferTimes": [
{
"start": "1",
"end": "18446744073709551615"
}
]
}
Step 3: Overall Scope - Badge IDs and Ownership Times
Question 3.1: Which badge IDs should this approval cover? (This defines the overall scope - specific amounts and limits will be set later)
Options: "All" (full range), specific ranges, or individual IDs
Question 3.2: Which ownership times should this approval cover? (This defines the overall scope - specific amounts and limits will be set later)
Options: "All" (full range), specific time ranges, or current ownership times
Note: This step defines the overall scope of what this approval can potentially cover. The actual transfer amounts, limits, and thresholds will be configured in Steps 5-7 (Approval Amounts, Max Transfers, and Predetermined Balances).
Generate:
{
"badgeIds": [
{
"start": "1",
"end": "18446744073709551615"
}
],
"ownershipTimes": [
{
"start": "1",
"end": "18446744073709551615"
}
]
}
Step 4: Coin Transfers (coinTransfers)
Question 4.1: Should this approval automatically transfer tokens when used?
Options: "No", "Yes - specify token details"
Question 4.2 (if Yes): What type of token should be transferred?
Options: "$BADGE (ubadge)" (standard BitBadges token)
Question 4.3 (if Yes): How much should be transferred?
Options: Specify amount in smallest unit (e.g., "1000000000" for 1 $BADGE)
Question 4.4 (if Yes): Who should receive the tokens?
Options: "Specific address", "Initiator of the transfer", "Approver address"
Question 4.5 (if Yes): Who should pay for the tokens?
Options: "Initiator of the transfer", "Approver address (mint escrow)"
Note: Coin transfers are executed automatically every time this approval is used. For collection approvals, you can use the mint escrow address as the source (which holds collection funds) by setting overrideFromWithApproverAddress: true
.
Generate:
{
"approvalCriteria": {
"coinTransfers": [
{
"to": "bb1recipient...",
"coins": [
{
"amount": "1000000000",
"denom": "ubadge"
}
],
"overrideFromWithApproverAddress": false,
"overrideToWithInitiator": false
}
]
}
}
Example Configurations:
Simple Transfer: Transfer 1 $BADGE to a specific address
{
"to": "bb1alice...",
"coins": [{ "amount": "1000000000", "denom": "ubadge" }],
"overrideFromWithApproverAddress": false,
"overrideToWithInitiator": false
}
Initiator Pays: Transfer 0.5 $BADGE from the initiator to a specific address
{
"to": "bb1bob...",
"coins": [{ "amount": "500000000", "denom": "ubadge" }],
"overrideFromWithApproverAddress": false,
"overrideToWithInitiator": false
}
Collection Pays: Transfer 2 $BADGE from collection funds to the initiator
{
"to": "bb1initiator...",
"coins": [{ "amount": "2000000000", "denom": "ubadge" }],
"overrideFromWithApproverAddress": true,
"overrideToWithInitiator": true
}
Step 5: Approval Amounts and Transfer Limits
Question 5.1: Do you want to limit the total amount that can be transferred through this approval?
Options: "No limit", "Specific amount"
Question 5.2: Do you want to limit transfers per sender address?
Options: "No limit", "Specific amount per sender"
Question 5.3: Do you want to limit transfers per receiver address?
Options: "No limit", "Specific amount per receiver"
Question 5.4: Do you want to limit transfers per initiator address?
Options: "No limit", "Specific amount per initiator"
Note: Use "0" to indicate no limit for any of these fields. "0" means unlimited and will not be tracked.
Generate:
{
"approvalCriteria": {
"approvalAmounts": {
"overallApprovalAmount": "0",
"perFromAddressApprovalAmount": "0",
"perToAddressApprovalAmount": "0",
"perInitiatedByAddressApprovalAmount": "0",
"amountTrackerId": "approval-id"
}
}
}
Step 6: Maximum Number of Transfers (STRONGLY RECOMMENDED)
Question 6: Do you want to limit the number of transfers (not amounts) that can be made through this approval?
Options: "No limit", "Specific number of transfers", "Let AI set a reasonable limit"
β οΈ CRITICAL SANITY CHECK: This step is STRONGLY RECOMMENDED, especially if coin transfers are configured in Step 4. Without transfer limits, users could potentially trigger unlimited coin transfers, which could drain funds.
AI Logic: If user doesn't specify or chooses "Let AI set a reasonable limit":
If coin transfers are configured: Set
overallMaxNumTransfers: "100"
andperInitiatedByAddressMaxNumTransfers: "10"
If no coin transfers: Set
overallMaxNumTransfers: "1000"
andperInitiatedByAddressMaxNumTransfers: "100"
Always set
perFromAddressMaxNumTransfers: "0"
andperToAddressMaxNumTransfers: "0"
(unlimited per address)
Note: Use "0" to indicate no limit for any of these fields. "0" means unlimited and will not be tracked.
Generate:
{
"approvalCriteria": {
"maxNumTransfers": {
"overallMaxNumTransfers": "100",
"perFromAddressMaxNumTransfers": "0",
"perToAddressMaxNumTransfers": "0",
"perInitiatedByAddressMaxNumTransfers": "10",
"amountTrackerId": "approval-id"
}
}
}
Step 7: Predetermined Balances (Alternative to Step 5)
Question 7.1: Do you want to use predetermined balances instead of amount thresholds?
Options: "No - use amount thresholds (Step 5)", "Yes - use predetermined balances"
Note: Steps 5 and 7 are typically NOT used together. Choose either:
Step 5: Amount thresholds (can't exceed limits) - "You can transfer up to X badges"
Step 7: Predetermined balances (exact amounts) - "You must transfer exactly X badges"
Question 7.2 (if Yes): How do you want to define the balances?
Options: "Manual balances (exact amounts)", "Incremented balances (sequential patterns)"
Question 7.3 (if Manual): How many different transfer amounts do you want to define?
Options: Specify number (e.g., "3" for three different transfer amounts)
Question 7.4 (if Incremented): What type of increment pattern do you want?
Options: "Sequential badge IDs", "Time-based increments", "Recurring intervals", "Custom increments"
Question 7.5 (if Incremented): How should the order be calculated?
Options: "Overall transfer count", "Per recipient count", "Per sender count", "Per initiator count", "Merkle challenge index"
Generate:
{
"approvalCriteria": {
"predeterminedBalances": {
"manualBalances": [
{
"amount": "1",
"badgeIds": [{ "start": "1", "end": "1" }],
"ownershipTimes": [
{ "start": "1691978400000", "end": "1723514400000" }
]
}
],
"incrementedBalances": {
"startBalances": [
{
"amount": "1",
"badgeIds": [{ "start": "1", "end": "1" }],
"ownershipTimes": [
{ "start": "1691978400000", "end": "1723514400000" }
]
}
],
"incrementBadgeIdsBy": "1",
"incrementOwnershipTimesBy": "0",
"durationFromTimestamp": "0",
"allowOverrideTimestamp": false,
"allowOverrideWithAnyValidBadge": false,
"recurringOwnershipTimes": {
"startTime": "0",
"intervalLength": "0",
"chargePeriodLength": "0"
}
},
"orderCalculationMethod": {
"useOverallNumTransfers": true,
"usePerToAddressNumTransfers": false,
"usePerFromAddressNumTransfers": false,
"usePerInitiatedByAddressNumTransfers": false,
"useMerkleChallengeLeafIndex": false,
"challengeTrackerId": ""
}
}
}
}
Example Configurations:
Manual Balances: Three specific transfer amounts
{
"manualBalances": [
{
"amount": "1",
"badgeIds": [{ "start": "1", "end": "1" }],
"ownershipTimes": [
{ "start": "1691978400000", "end": "1723514400000" }
]
},
{
"amount": "5",
"badgeIds": [{ "start": "2", "end": "6" }],
"ownershipTimes": [
{ "start": "1691978400000", "end": "1723514400000" }
]
},
{
"amount": "10",
"badgeIds": [{ "start": "7", "end": "16" }],
"ownershipTimes": [
{ "start": "1691978400000", "end": "1723514400000" }
]
}
]
}
Sequential Badge IDs: Each transfer gets the next badge ID
{
"incrementedBalances": {
"startBalances": [
{
"amount": "1",
"badgeIds": [{ "start": "1", "end": "1" }],
"ownershipTimes": [
{ "start": "1691978400000", "end": "1723514400000" }
]
}
],
"incrementBadgeIdsBy": "1"
}
}
Time-Based: 30-day ownership from transfer time
{
"incrementedBalances": {
"startBalances": [
{
"amount": "1",
"badgeIds": [{ "start": "1", "end": "1" }],
"ownershipTimes": [
{ "start": "1691978400000", "end": "1723514400000" }
]
}
],
"durationFromTimestamp": "2592000000",
"allowOverrideTimestamp": true
}
}
Recurring: Monthly subscription with 7-day advance charging
{
"incrementedBalances": {
"startBalances": [
{
"amount": "1",
"badgeIds": [{ "start": "1", "end": "1" }],
"ownershipTimes": [
{ "start": "1691978400000", "end": "1723514400000" }
]
}
],
"recurringOwnershipTimes": {
"startTime": "1691978400000",
"intervalLength": "2592000000",
"chargePeriodLength": "604800000"
}
}
}
Important Notes:
Exact Match Required: Transfers must match predetermined balances EXACTLY
Order Numbers: Use transfer count to determine which balance set to use
Precalculation: Use
precalculateBalancesFromApproval
to handle race conditionsBoundary Handling: Balances must work within approval's badge ID and time bounds
Step 8: Address Relationship Requirements
Question 8.1: Should the recipient address equal the initiator address?
Options: "No", "Yes"
Question 8.2: Should the sender address equal the initiator address?
Options: "No", "Yes"
Question 8.3: Should the recipient address NOT equal the initiator address?
Options: "No", "Yes"
Question 8.4: Should the sender address NOT equal the initiator address?
Options: "No", "Yes"
Generate:
{
"approvalCriteria": {
"requireToEqualsInitiatedBy": false,
"requireFromEqualsInitiatedBy": false,
"requireToDoesNotEqualInitiatedBy": false,
"requireFromDoesNotEqualInitiatedBy": false
}
}
Step 9: Override Settings
Question 9.1: Should this approval override the sender's outgoing approvals?
Options: "No", "Yes"
Question 9.2: Should this approval override the recipient's incoming approvals?
Options: "No", "Yes"
Generate:
{
"approvalCriteria": {
"overridesFromOutgoingApprovals": false,
"overridesToIncomingApprovals": false
}
}
Step 10: User Royalties
Question 10.1: Do you want to apply percentage-based transfer fees (royalties)?
Options: "No", "Yes - specify royalty percentage and recipient"
Question 10.2 (if Yes): What percentage should be charged as royalty?
Options: Specify percentage in basis points (e.g., "500" for 5%, "250" for 2.5%)
Question 10.3 (if Yes): Who should receive the royalty payments?
Options: "Specific address", "Creator address", "Collection owner"
Note: Royalties are expressed in basis points (1 = 0.01%, 100 = 1%, 10000 = 100%). Only one royalty can be applied per transfer.
Generate:
{
"approvalCriteria": {
"userRoyalties": {
"percentage": "500",
"payoutAddress": "bb1creator..."
}
}
}
Example Configurations:
5% Royalty: Standard creator royalty
{
"userRoyalties": {
"percentage": "500",
"payoutAddress": "bb1creator..."
}
}
2.5% Platform Fee: Lower platform fee
{
"userRoyalties": {
"percentage": "250",
"payoutAddress": "bb1platform..."
}
}
Step 11: Auto-Deletion Options
Question 11.1: Should this approval be automatically deleted after use?
Options: "No", "Yes - after one use", "Yes - after max transfers reached"
Question 11.2 (if Yes): When should it be deleted?
Options: "After first use", "After overall max transfers reached", "Allow counterparty to purge", "Allow others to purge if expired"
Note: Auto-deletion helps manage approval lifecycle and prevents accumulation of unused approvals.
Generate:
{
"approvalCriteria": {
"autoDeletionOptions": {
"afterOneUse": false,
"afterOverallMaxNumTransfers": false,
"allowCounterpartyPurge": false,
"allowPurgeIfExpired": false
}
}
}
Example Configurations:
Single-Use Approval: Delete after first transfer
{
"autoDeletionOptions": {
"afterOneUse": true,
"afterOverallMaxNumTransfers": false
}
}
Limited-Use Approval: Delete after max transfers reached
{
"autoDeletionOptions": {
"afterOneUse": false,
"afterOverallMaxNumTransfers": true
}
}
Allow Counterparty Purge: Let counterparty delete the approval
{
"autoDeletionOptions": {
"afterOneUse": false,
"afterOverallMaxNumTransfers": false,
"allowCounterpartyPurge": true
}
}
Step 12: Metadata and Custom Data
Question 12.1: Do you want to add metadata URI for this approval?
Options: "No", "Yes - specify URI"
Question 12.2: Do you want to add custom data string?
Options: "No", "Yes - specify custom data"
Generate:
{
"uri": "",
"customData": ""
}
Step 13: Version Control
Question 13: What version number should this approval have?
Options: "0" (new approval), or specify version number
Generate:
{
"version": "0"
}
Final Output Template
After collecting all user responses, generate the complete JSON:
{
"approvalId": "generated-or-user-specified-id",
"fromListId": "user-selected-sender-list",
"toListId": "user-selected-receiver-list",
"initiatedByListId": "user-selected-initiator-list",
"transferTimes": [
{
"start": "1",
"end": "18446744073709551615"
}
],
"badgeIds": [
{
"start": "1",
"end": "18446744073709551615"
}
],
"ownershipTimes": [
{
"start": "1",
"end": "18446744073709551615"
}
],
"version": "0",
"uri": "",
"customData": "",
"approvalCriteria": {
"coinTransfers": [],
"merkleChallenges": [],
"mustOwnBadges": [],
"predeterminedBalances": {
"manualBalances": [],
"incrementedBalances": {
"startBalances": [],
"incrementBadgeIdsBy": "0",
"incrementOwnershipTimesBy": "0",
"durationFromTimestamp": "0",
"allowOverrideTimestamp": false,
"allowOverrideWithAnyValidBadge": false,
"recurringOwnershipTimes": {
"startTime": "0",
"intervalLength": "0",
"chargePeriodLength": "0"
}
},
"orderCalculationMethod": {
"useOverallNumTransfers": false,
"usePerToAddressNumTransfers": false,
"usePerFromAddressNumTransfers": false,
"usePerInitiatedByAddressNumTransfers": false,
"useMerkleChallengeLeafIndex": false,
"challengeTrackerId": ""
}
},
"approvalAmounts": {
"overallApprovalAmount": "0",
"perFromAddressApprovalAmount": "0",
"perToAddressApprovalAmount": "0",
"perInitiatedByAddressApprovalAmount": "0",
"amountTrackerId": "approval-id"
},
"maxNumTransfers": {
"overallMaxNumTransfers": "100",
"perFromAddressMaxNumTransfers": "0",
"perToAddressMaxNumTransfers": "0",
"perInitiatedByAddressMaxNumTransfers": "10",
"amountTrackerId": "approval-id"
},
"autoDeletionOptions": {
"afterOneUse": false,
"afterOverallMaxNumTransfers": false,
"allowCounterpartyPurge": false,
"allowPurgeIfExpired": false
},
"requireToEqualsInitiatedBy": false,
"requireFromEqualsInitiatedBy": false,
"requireToDoesNotEqualInitiatedBy": false,
"requireFromDoesNotEqualInitiatedBy": false,
"overridesFromOutgoingApprovals": false,
"overridesToIncomingApprovals": false,
"userRoyalties": {
"percentage": "0",
"payoutAddress": ""
}
}
}
Common Approval Patterns
Mint Approval
{
"fromListId": "Mint",
"toListId": "All",
"initiatedByListId": "All",
"overridesFromOutgoingApprovals": true
}
Transferable Approval (Post-Mint)
{
"fromListId": "!Mint",
"toListId": "All",
"initiatedByListId": "All"
}
Alternative Transferable Approval
{
"fromListId": "AllWithoutMint",
"toListId": "All",
"initiatedByListId": "All"
}
Burnable Approval
{
"fromListId": "!Mint",
"toListId": "bb1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqs7gvmv",
"initiatedByListId": "All"
}
Important Notes
Tracker IDs: Use the approval ID as the
amountTrackerId
for both approval amounts and max num transfersVersion Control: Start with version "0" for new approvals
Overrides: Mint approvals should typically override outgoing approvals
Ranges: Use full ranges
[1, 18446744073709551615]
for unlimited accessDefaults: Set unused criteria to empty arrays or false values
Validation: Ensure all UintRange values are within valid bounds (1 to MaxUint64)
Address List Separation: Never mix "Mint" with other addresses in
fromListId
. Use "Mint" for minting only, or "!Mint"/"AllWithoutMint" for all other addressesZero Values: Use "0" to indicate no limit in approval amounts and max num transfers. "0" means unlimited and will not be tracked
Usage Instructions
Ask each question sequentially
Collect user responses
Generate appropriate JSON for each section
Combine all sections into final complete approval
Validate the final JSON structure
Provide the complete approval object to the user
Last updated