Send to an Anchor

Resolve a phone number or alias to get account details, then create a payment intent — the two-step pattern for paying someone you know by identifier, not by account address.

Audience: Developers, Integration Engineers · Read time: 3 min

Sometimes you know the recipient by their phone number, email, or alias — not by their bank account address. In these cases, you resolve the anchor first to get the account details, then create the intent with the resolved information. This two-step pattern is common for loan disbursements, refunds to mobile wallets, and payroll to phone-linked accounts.

What you're building

In this guide you'll resolve a tel:+573001234567 anchor to find the recipient's wallet address, then create an intent to disburse a loan from the merchant's treasury to the resolved account. The same pattern works for any scenario where you have a human-readable identifier but need a wallet address before you can send.

Prerequisites

  • A connection to a ledger — see How to Connect to a Ledger
  • A funded source wallet (treasury or operating account)
  • The recipient's anchor identifier (phone number, email, or alias)

Send the payment

Resolve the anchor

Before creating the intent, look up the anchor to find the wallet address it points to. Submit the identifier URL-encoded — tel:+573001234567 becomes tel%3A%2B573001234567 in the path.

minka anchor read "tel:+573001234567"
curl "https://{ledger-host}/v2/anchors/tel%3A%2B573001234567" \
  -H "Authorization: Bearer {your-token}" \
  -H "x-ledger: {your-ledger}"
GET /v2/anchors/tel%3A%2B573001234567
// SDK setup: see How to Connect to a Ledger
// /moving-money/connect-to-ledger

// The SDK handles URL encoding automatically — pass the handle as-is
const { anchor } = await sdk.anchor.read('tel:+573001234567')

// The resolved wallet address is in anchor.data.target
const targetWallet = anchor.data.target
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://{ledger-host}/v2/anchors/tel%3A%2B573001234567"))
    .header("Authorization", "Bearer {your-token}")
    .header("x-ledger", "{your-ledger}")
    .GET()
    .build();

HttpResponse<String> response = client.send(
    request, HttpResponse.BodyHandlers.ofString());
using System.Net.Http;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer {your-token}");
client.DefaultRequestHeaders.Add("x-ledger", "{your-ledger}");

var response = await client.GetAsync(
    "https://{ledger-host}/v2/anchors/tel%3A%2B573001234567");
var body = await response.Content.ReadAsStringAsync();

The curl examples use a Bearer token for authentication. In production, every mutation also requires a cryptographic signature — see Sign Payments for the full signing flow. The TypeScript SDK handles signing automatically.

The response includes the wallet address and any custom fields registered with the anchor:

{
  "data": {
    "wallet": "tel:+573001234567",
    "target": "svgs:12345@recipient-bank.co",
    "schema": "account",
    "symbol": "cop",
    "custom": {
      "name": "Carlos Mendoza",
      "documentType": "cc",
      "documentNumber": "1023456789"
    }
  }
}

The target field is the wallet address you'll use in the next step. The anchor maps the phone number to a specific account — in this case, Carlos's savings account at recipient-bank.co.

Create the intent

Use the resolved wallet address from target as the intent's target. The rest of the intent follows the same structure as any push payment.

minka intent create

The CLI walks you through each field:

PromptWhat to enterExample
HandleA unique payment referenceloan-disbursement-001
SourceThe funding accounttreasury@bank.co
TargetThe resolved wallet addresssvgs:12345@recipient-bank.co
AmountAmount in smallest currency unit50000000 (500,000.00 COP)
SymbolCurrency handlecop
CommitSettlement modeauto

minka intent create submits a real payment. Use a test ledger for experimentation.

curl -X POST "https://{ledger-host}/v2/intents" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer {your-token}" \
  -H "x-ledger: {your-ledger}" \
  -d '{
    "data": {
      "handle": "loan-disbursement-001",
      "claims": [
        {
          "action": "transfer",
          "amount": "50000000",
          "symbol": { "handle": "cop" },
          "source": { "handle": "treasury@bank.co" },
          "target": { "handle": "svgs:12345@recipient-bank.co" }
        }
      ],
      "config": { "commit": "auto" }
    }
  }'
POST /v2/intents
// SDK setup: see How to Connect to a Ledger
// /moving-money/connect-to-ledger
const { response } = await sdk.intent
  .init()
  .data({
    handle: 'loan-disbursement-001',
    claims: [
      {
        action: 'transfer',
        amount: '50000000',
        symbol: { handle: 'cop' },
        source: { handle: 'treasury@bank.co' },
        target: { handle: anchor.data.target },
      },
    ],
    config: { commit: 'auto' },
  })
  .hash()
  .sign([{ keyPair }])
  .send()
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

HttpClient client = HttpClient.newHttpClient();

String json = """
    {
      "data": {
        "handle": "loan-disbursement-001",
        "claims": [
          {
            "action": "transfer",
            "amount": "50000000",
            "symbol": { "handle": "cop" },
            "source": { "handle": "treasury@bank.co" },
            "target": { "handle": "svgs:12345@recipient-bank.co" }
          }
        ],
        "config": { "commit": "auto" }
      }
    }
    """;

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://{ledger-host}/v2/intents"))
    .header("Content-Type", "application/json")
    .header("Authorization", "Bearer {your-token}")
    .header("x-ledger", "{your-ledger}")
    .POST(HttpRequest.BodyPublishers.ofString(json))
    .build();

HttpResponse<String> response = client.send(
    request, HttpResponse.BodyHandlers.ofString());
using System.Net.Http;
using System.Text;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer {your-token}");
client.DefaultRequestHeaders.Add("x-ledger", "{your-ledger}");

var json = """
    {
      "data": {
        "handle": "loan-disbursement-001",
        "claims": [
          {
            "action": "transfer",
            "amount": "50000000",
            "symbol": { "handle": "cop" },
            "source": { "handle": "treasury@bank.co" },
            "target": { "handle": "svgs:12345@recipient-bank.co" }
          }
        ],
        "config": { "commit": "auto" }
      }
    }
    """;

var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(
    "https://{ledger-host}/v2/intents", content);
var body = await response.Content.ReadAsStringAsync();

The source is where the funds come from — a treasury wallet or disbursement pool. The target is the wallet address returned by the anchor resolution. The claims array holds the transfer instruction. Setting config.commit to "auto" tells the ledger to settle immediately once participants confirm.

Check the result

minka intent read loan-disbursement-001
curl "https://{ledger-host}/v2/intents/loan-disbursement-001" \
  -H "Authorization: Bearer {your-token}" \
  -H "x-ledger: {your-ledger}"
// SDK setup: see How to Connect to a Ledger
// /moving-money/connect-to-ledger
const { intent } = await sdk.intent.read('loan-disbursement-001')
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://{ledger-host}/v2/intents/loan-disbursement-001"))
    .header("Authorization", "Bearer {your-token}")
    .header("x-ledger", "{your-ledger}")
    .GET()
    .build();

HttpResponse<String> response = client.send(
    request, HttpResponse.BodyHandlers.ofString());
using System.Net.Http;

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer {your-token}");
client.DefaultRequestHeaders.Add("x-ledger", "{your-ledger}");

var response = await client.GetAsync(
    "https://{ledger-host}/v2/intents/loan-disbursement-001");
var body = await response.Content.ReadAsStringAsync();

A completed status means the funds have settled — the treasury was debited and Carlos's savings account was credited atomically. If the status is rejected, the response includes the reason from the participant that couldn't complete.

{
  "data": {
    "handle": "loan-disbursement-001",
    "claims": [
      {
        "action": "transfer",
        "amount": "50000000",
        "symbol": { "handle": "cop" },
        "source": { "handle": "treasury@bank.co" },
        "target": { "handle": "svgs:12345@recipient-bank.co" }
      }
    ]
  },
  "meta": {
    "status": "completed"
  }
}

Why resolve at payment time? The anchor might resolve to a different account over time — a user can change which bank account their phone number points to. Always resolve the anchor at the moment of payment to get the current mapping, not a cached one from an earlier lookup.

Common patterns

The same two-step flow works for any scenario where you have an identifier but need a wallet address. Only the anchor type and accounts change.

PatternAnchorSourceUse case
Loan disbursementtel:+573001234567treasury@bank.coPay out a loan to a phone-linked account
Email refundemail:customer@mail.coops:returns@merchant.coRefund to customer's registered email
Alias payrollemp:12345@company.coops:payroll@company.coMonthly salary to employee alias

What you learned

  • How to resolve an anchor identifier to a wallet address before creating an intent
  • Why anchor resolution should happen at payment time, not cached in advance
  • How the two-step pattern — resolve then pay — works for any human-readable identifier

Common errors

ErrorCauseFix
anchor-not-foundNo anchor is registered for the given identifierVerify the anchor handle and that it has been registered on this ledger
anchor-expiredThe anchor's TTL has elapsed and it is no longer validCreate a new anchor — expired anchors cannot be reactivated
target-wallet-inactiveThe wallet that the anchor resolves to has been deactivatedContact the wallet owner or check the wallet status

Next steps

On this page