custom-2fa
Two-factor authentication for transfers using a secondary approval address
Custom 2FA
Instruction text for agents that use the Custom 2FA skill, loaded by bb dev skills custom-2fa and the MCP get_skill_instructions tool.
Try it
Paste this into Claude Code, Codex, or Cursor with the BitBadges MCP server wired. The agent loads the skill, builds, verifies, and hands back a link to review and sign.
Load the custom-2fa skill and build me one: two-factor authentication for transfers using a secondary approval address. Use the session tools, run validate, review, and simulate in parallel, fix anything critical, then call get_review_url and give me the link.From a shell, bb dev skills custom-2fa prints the same instructions.
Summary
Required standards: ["Custom-2FA"]
- autoDeletionOptions.allowPurgeIfExpired: must be true
- Approval name must contain "Custom 2FA"
- Use time-dependent ownershipTimes in MsgTransferTokens (not forever)
- Calculate timestamps: current time + expiration duration (milliseconds since epoch)
- Tokens automatically expire and can be purged after expiration
Instructions
Custom-2FA Configuration
When creating a Custom-2FA collection, follow these requirements:
Preferred path: preset (one short tool call)
The mint approval is canonical, use custom-2fa.mint:
add_preset_approval({ presetId: "custom-2fa.mint", params: { managerAddress: "bb1..." } })The actual token expiration is set per-mint via the MsgTransferTokens ownershipTimes window (e.g. now → now + 5601000 ms). The approval itself just enables the Mint path with auto-purge.
Required Structure
-
Standards: must include "Custom-2FA"
- "standards": ["Custom-2FA"]
-
Approval Requirements:
- autoDeletionOptions.allowPurgeIfExpired: must be true
- This allows expired tokens to be automatically purged
- Approval name must contain "Custom 2FA"
-
Time-Dependent Ownership: Use time-dependent ownershipTimes in MsgTransferTokens
- Calculate timestamps: current time + expiration duration
- Example: 5 minutes = Date.now() + (5 * 60 * 1000)
Complete Example
{
"messages": [
{
"typeUrl": "/tokenization.MsgUniversalUpdateCollection",
"value": {
"standards": ["Custom-2FA"],
"collectionApprovals": [{
"fromListId": "Mint",
"toListId": "All",
"initiatedByListId": "bb1manager...",
"approvalId": "2fa-mint",
"tokenIds": [{ "start": "1", "end": "18446744073709551615" }],
"transferTimes": [{ "start": "1", "end": "18446744073709551615" }],
"ownershipTimes": [{ "start": "1", "end": "18446744073709551615" }],
"approvalCriteria": {
"overridesFromOutgoingApprovals": true,
"autoDeletionOptions": { "allowPurgeIfExpired": true }
}
}]
}
},
{
"typeUrl": "/tokenization.MsgTransferTokens",
"value": {
"collectionId": "0",
"transfers": [{
"from": "Mint",
"toAddresses": ["bb1recipient..."],
"balances": [{
"amount": "1",
"tokenIds": [{ "start": "1", "end": "1" }],
"ownershipTimes": [{ "start": "1706000000000", "end": "1706000300000" }]
}]
}]
}
}
]
}2FA-Specific Gotchas
- Must set allowPurgeIfExpired: true
- Use time-dependent ownershipTimes in transfers (not forever)
- Calculate expiration timestamps correctly (milliseconds since epoch)
- Tokens automatically expire and can be purged after expiration