# custom-2fa

<!-- generated by site/scripts/gen-skills.ts; do not hand-edit -->

# 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.

```text
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`:

```ts
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 + 5*60*1000 ms). The approval itself just enables the Mint path with auto-purge.

### Required Structure

1. **Standards**: must include "Custom-2FA"
   - "standards": ["Custom-2FA"]

2. **Approval Requirements**:
   - autoDeletionOptions.allowPurgeIfExpired: must be true
   - This allows expired tokens to be automatically purged
   - Approval name must contain "Custom 2FA"

3. **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

```json
{
  "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
