Explanations

About Resolution Proofs


General

What are resolution proofs

Resolution proofs (also called "resolved proofs") are proofs created by the ledger during intent processing that serve as routing instructions for the two-phase commit (2PC) protocol. They are added to an intent after ledger adds pending proof, and act as the foundation for all subsequent 2PC coordination and intent processing. They are derived from claims present in the intent, and express how those claims are going to be executed and with which consequences, namely:

  • They determine which external systems (bridges), if any, will participate in executing each part of an intent and establish the ledger's commitment to coordinate the distributed transaction.
  • They determine which wallet(s) will receive and lose balance, of which symbol and of which amount

Claim types destroy and issue currently do not contact bridges. Resolved proofs will still contain references to bridge(s) assigned to the wallet from the claim(s).

Unlike participant proofs (prepared, committed, failed, aborted) which are created by bridges responding to ledger requests, resolution proofs are always created by the ledger itself using its own signing key.

How to find them

Resolution proofs are proofs in the intent, identifiable by having the value of custom.status as resolved, and being signed by ledger's public key.

Why they matter

  • Immutable source of truth: They create an auditable record of the ledger's routing decisions for the intent. These are the source of truth for:
    • how much balance(s) should be moved from which wallet to which wallet's exact handle (no need to guess what the wallet address resolves into)
    • which bridges the ledger will communicate with as participants
  • Entry tracking: Each resolution proof contains a unique entry handle (starting with deb_ or cre_) that is used throughout the 2PC lifecycle.
  • Balance reservation: Resolution proofs with schema debit contain the information (wallet, amount, symbol) that the ledger uses to create balance reservations when it prepares internally (as a 2PC participant).

When they are created

Resolution proofs are created during the pending phase of intent processing, specifically:

  1. Intent is created and validated (aspects execute)
  2. Intent enters pending status
  3. Ledger analyzes claims and determines routing
  4. Resolution proofs are created for each debit/credit operation
  5. DTC initiates communication with participant bridges, and processes intent

Resolution proofs are created before any bridge is contacted. They represent the ledger's plan for executing the intent, not the result of bridge responses.

Resolution proof structure

Resolution proofs follow the standard ledger proof structure with specific custom fields:

Example resolution proof:

{
  "method": "ed25519-v2",
  "digest": "...",
  "result": "...",
  "public": "<ledger-public-key>",
  "custom": {
    "status": "resolved",
    "handle": "deb_xyz123",           // Unique entry identifier
    "schema": "debit",                // Operation type: "debit" or "credit"
    "wallet": "wallet_alice",         // Target wallet for the operation
    "bridge": "bridge_bank_a",        // Bridge that must handle this operation
    "symbol": "usd",                  // Symbol being transferred
    "amount": 1000,                   // Amount for this operation
    "inputs": [0],                    // Claim indices that contribute to this entry
    "moment": "2025-01-15T10:30:00.000Z"
  }
}

Key fields

FieldTypeDescription
publicstringAlways the ledger's public key (identifies this as a ledger-created proof)
custom.statusstringAlways "resolved" for resolution proofs
custom.handlestringUnique resolution proof identifier, prefixed with deb_ (debit) or cre_ (credit)
custom.schemastringOperation type: "debit" or "credit"
custom.walletstringHandle of the wallet to be debited or credited (exact wallet, no more resolution involved)
custom.bridgestringHandle of the bridge responsible for this operation (optional)
custom.symbolstringSymbol (currency/token) being transferred
custom.amountnumberAmount for this specific resolution proof
custom.inputsnumber[]Array of claim indices that contribute to this resolution proof

Examples

Example 1: Simple transfer

Intent: Transfer 100 USD from Alice to Bob

Claims:

[
  {
    "action": "transfer",
    "source": { "handle": "wallet_alice" },
    "target": { "handle": "wallet_bob" },
    "symbol": { "handle": "usd" },
    "amount": 10000 // extra 00 because of `symbol` factor
  }
]

Resolution proofs created:

[
  // Debit resolution proof for Alice
  {
    "method": "ed25519-v2",
    "digest": "...",
    "result": "...",
    "public": "<ledger-public-key>",
    "custom": {
      "status": "resolved",
      "handle": "deb_abc123",
      "schema": "debit",
      "wallet": "wallet_alice",
      "bridge": "bridge_bank_a",
      "symbol": "usd",
      "amount": 10000,
      "inputs": [0]
    }
  },
  // Credit resolution proof for Bob
  {
    "method": "ed25519-v2",
    "digest": "...",
    "result": "...",
    "public": "<ledger-public-key>",
    "custom": {
      "status": "resolved",
      "handle": "cre_xyz789",
      "schema": "credit",
      "wallet": "wallet_bob",
      "bridge": "bridge_bank_b",
      "symbol": "usd",
      "amount": 10000,
      "inputs": [0]
    }
  }
]

Example 2: Multi-claim intent

Intent: Exchange 100 USD for 85 EUR with 2 USD fee

Claims:

[
  {
    "action": "transfer",
    "source": { "handle": "wallet_alice" },
    "target": { "handle": "wallet_exchange" },
    "symbol": { "handle": "usd" },
    "amount": 10000
  },
  {
    "action": "transfer",
    "source": { "handle": "wallet_exchange" },
    "target": { "handle": "wallet_alice" },
    "symbol": { "handle": "eur" },
    "amount": 8500
  },
  {
    "action": "transfer",
    "source": { "handle": "wallet_exchange" },
    "target": { "handle": "wallet_fee_collector" },
    "symbol": { "handle": "usd" },
    "amount": 200
  }
]

Resolution proofs created: 6 total (one debit + one credit per claim)

[
  // Claim 0: Debit Alice 100 USD
  { "custom": { "handle": "deb_001", "wallet": "wallet_alice", "amount": 10000, "symbol": "usd", "inputs": [0] } },
  // Claim 0: Credit Exchange 100 USD
  { "custom": { "handle": "cre_001", "wallet": "wallet_exchange", "amount": 10000, "symbol": "usd", "inputs": [0] } },

  // Claim 1: Debit Exchange 85 EUR
  { "custom": { "handle": "deb_002", "wallet": "wallet_exchange", "amount": 8500, "symbol": "eur", "inputs": [1] } },
  // Claim 1: Credit Alice 85 EUR
  { "custom": { "handle": "cre_002", "wallet": "wallet_alice", "amount": 8500, "symbol": "eur", "inputs": [1] } },

  // Claim 2: Debit Exchange 2 USD fee
  { "custom": { "handle": "deb_003", "wallet": "wallet_exchange", "amount": 200, "symbol": "usd", "inputs": [2] } },
  // Claim 2: Credit Fee Collector 2 USD
  { "custom": { "handle": "cre_003", "wallet": "wallet_fee_collector", "amount": 200, "symbol": "usd", "inputs": [2] } }
]

Example 3: Issue claim

Intent: Issue 1000 USD to merchant wallet

Claims:

[
  {
    "action": "issue",
    "target": { "handle": "wallet_merchant" },
    "symbol": { "handle": "usd" },
    "amount": 100000
  }
]

Resolution proofs created: 1 (credit only, no debit for issue actions)

[
  {
    "method": "ed25519-v2",
    "digest": "...",
    "result": "...",
    "public": "<ledger-public-key>",
    "custom": {
      "status": "resolved",
      "handle": "cre_abc123",
      "schema": "credit",
      "wallet": "wallet_merchant",
      "bridge": "bridge_merchant_system",
      "symbol": "usd",
      "amount": 100000,
      "inputs": [0]
    }
  }
]

How resolution proofs work

The process of creating resolution proofs involves several steps:

1. Claim analysis

The ledger analyzes the intent's claims to determine which operations need resolution:

  • Transfer claims → Create both debit and credit entries (2 resolution proofs)
  • Issue claims → Create credit resolution proof only (1 resolution proof)
  • Destroy claims → Create debit resolution proof only (1 resolution proof)

2. Wallet routing

The ledger follows wallet routing rules (if configured) to determine the final wallets that will be debited/credited and which bridges are assigned to those wallets. See About Wallets for details on routing mechanics.

Wallet routing can create chains where the actual debited/credited wallet differs from the wallet specified in the claim. Resolution proofs always reference the final routed wallet, not the original claim wallet.

3. Bridge entry creation

For each routed wallet operation, the ledger creates a bridge entry with:

  • A unique handle (e.g., deb_abc123 or cre_xyz789), of same value as corresponding resolution proof's handle
  • The operation schema (debit or credit)
  • The final routed wallet
  • The bridge assigned to that wallet (if any)
  • The symbol and amount
  • The claim indices that contribute to this bridge entry

4. Proof signing and attachment

Each bridge entry is used to generate and sign a proof, which is then appended to the intent's meta.proofs array.

Relationship to 2PC requests

Resolution proofs directly determine which prepare/commit/abort requests are sent to which bridge(s):

  1. Debit resolution proofs → Determine which bridge receives debit prepare/commit/abort requests
  2. Credit resolution proofs → Determine which bridge receives credit prepare/commit/abort requests

The bridge entry handle in prepare requests (e.g., deb_abc123) comes directly from the corresponding resolution proof's handle. Bridges must respond with participant proofs using this same handle to link their response to the correct resolution proof.

For complete details on 2PC request/response structures, see About Bridges and About Intents.

Resolution proof lifecycle

1. Intent created

2. Resolution proofs created by ledger
   - Signed with ledger's key, their `custom.status` is `resolved`
   - Added to intent.meta.proofs

3. Processing starts, using resolution proofs as source of truth

Resolution proofs remain in intent.meta.proofs throughout the entire intent lifecycle. They are never removed or modified, providing a permanent record of the original routing decisions.

Interaction with other concepts in system

Resolution proofs and balance reservations

Resolution proofs with schema: "debit" contain the wallet, amount, and symbol information that the ledger uses to create balance reservations when it prepares internally as a 2PC participant.

See About Balance Reservations for complete details on how reservations work throughout the 2PC lifecycle.

Wallet routing and resolution

Resolution proofs reflect the final routed wallets, not necessarily the wallets specified in claims. This is important to understand if you've configured wallet routing rules.

Scenario: Alice's wallet has an outgoing route to Bob's wallet

Claim:

{
  "action": "transfer",
  "source": { "handle": "wallet_alice" },
  "target": { "handle": "wallet_charlie" },
  "symbol": { "handle": "usd" },
  "amount": 10000
}

If wallet_alice has route: { "action": "debit", "target": "wallet_bob" }

Resolution proof reflects the routed wallet:

{
  "custom": {
    "status": "resolved",
    "handle": "deb_xyz",
    "schema": "debit",
    "wallet": "wallet_bob",        // Routed wallet, not wallet_alice
    "bridge": "bridge_bank_b",     // Bridge assigned to wallet_bob
    "amount": 10000,
    "inputs": [0]
  }
}

See About Wallets for complete details on routing mechanics, depth limits, and cycle detection.

Troubleshooting

Debugging with resolution proofs

Resolution proofs are valuable for debugging intent processing issues:

Check if resolution occurred

Using the API:

GET /v2/intents/:handle

# Check for proofs with custom.status === "resolved"

Using the CLI:

minka intent show <handle> -v

# Look for proofs with status "resolved" signed by ledger

Identify which bridges are involved

From the intent response, filter resolution proofs and extract the custom.bridge field:

// Example: Extract bridges from resolution proofs
{
  "meta": {
    "proofs": [
      { "custom": { "status": "resolved", "bridge": "bridge_bank_a" } },
      { "custom": { "status": "resolved", "bridge": "bridge_bank_b" } },
      { "custom": { "status": "resolved", "bridge": "bridge_bank_a" } }
    ]
  }
}

// Unique bridges involved: bridge_bank_a, bridge_bank_b

Match resolution proofs with participant responses

For each resolution proof, check if a participant proof with the same custom.handle exists:

// Resolution proof
{ "custom": { "status": "resolved", "handle": "deb_abc123", "bridge": "bridge_x" } }

// Expected participant prepared proof (same handle)
{ "custom": { "status": "prepared", "handle": "deb_abc123" } }

// If missing → bridge_x has not responded to prepare request

Detect missing responses

Compare resolution proof handles with participant proof handles to find missing responses:

Resolution proofs: [deb_001, cre_001, deb_002]
Participant proofs: [deb_001, cre_001]
Missing: deb_002 → Check which bridge was assigned deb_002

Common issues

Issue: Intent stuck in pending, no resolution proofs

Symptom: Intent remains in pending status, no proofs with status: "resolved" exist

Possible causes:

  • Intent has no resolvable claims (only limit claims or unsupported actions)
  • Routing failed (routed wallet doesn't exist or routing cycle detected)
  • Ledger signer not configured (contact support to report a bug)

What to check:

  • Verify all wallet handles in claims exist
  • Check if wallets have routing rules that might cause issues
  • Ensure claims use supported actions: transfer, issue, destroy

Issue: Wrong bridge assigned in resolution proof

Symptom: Resolution proof shows unexpected bridge

Possible causes:

  • Wallet routing redirected to a different wallet with different bridge
  • Wallet's bridge field was updated after you created the intent

What to check:

# Check wallet's current bridge assignment
GET /v2/wallets/:handle

# Response shows bridge field
{
  "data": {
    "handle": "wallet_alice",
    "bridge": "bridge_bank_a"  // Current bridge assignment
  }
}

# Check wallet routes
{
  "data": {
    "routes": [
      { "action": "debit", "target": "wallet_bob" }  // May redirect to different bridge
    ]
  }
}

Issue: Bridge not receiving prepare requests

Symptom: Resolution proof exists with your bridge handle, but you're not receiving prepare requests

Possible causes:

  • Bridge endpoint (config.server) misconfigured in bridge record
  • Network connectivity issues between ledger and your Bridge
  • Bridge not assigned to resolved wallet

What to check:

# Verify bridge configuration
GET /v2/bridges/:bridgeHandle (or `minka bridge show :bridgeHandle`)

# Check config.server URL is correct
{
  "data": {
    "handle": "your_bridge",
    "schema": "rest",
    "config": {
      "server": "https://your-bridge.example.com/v2"  // Verify this URL
    }
  }
}
# Verify bridge assigned to wallet from resolution proof
GET /v2/wallets/:walletHandle (or `minka wallet show :walletHandle`)

# Check bridge handle is correct
{
  "data": {
    "handle": "your_wallet",
    "bridge": "your_bridge",
  }
}
  • Test your bridge /status endpoint is accessible
  • Check your bridge logs for incoming requests
  • Verify firewall rules allow ledger to reach your bridge

Bridge implementers: The prepare request entry includes the full intent with resolution proofs in intent.meta.proofs. When responding with participant proofs, you must use the same handle from the entry. For complete bridge implementation details, see About Bridges and About Intents.