Resolve Protocol
Resolve Protocol

Server-side address resolution.

Keep your payment address off the page. The wallet calls your endpoint at payment time — the address never appears in source.

How it works

Instead of putting your MobileCoin address in client-side code, pass a resolveUrl and intentToken. The wallet calls your server to fetch the address at payment time.

JavaScript
const result = await window.fire.requestPayment({
  resolveUrl: 'https://yoursite.com/api/resolve-payment',
  intentToken: 'your-server-generated-token',
  amount: '5000000',
  tokenId: 1,
  memo: 'Order #42',
  merchantRef: 'order-42'
});

Your resolve endpoint

Request (from wallet)

HTTP
POST https://yoursite.com/api/resolve-payment
Content-Type: application/json

{
  "intent_token": "your-server-generated-token",
  "merchant_ref": "order-42",
  "amount_micro": "5000000"
}

Response

JSON
{
  "success": true,
  "recipientB58": "3Fhv7xYourMobileCoinAddress...",
  "amount_micro": "5000000",
  "merchant_ref": "order-42",
  "memo": "Order #42"
}

You must echo back amount_micro and merchant_ref exactly as received. The wallet verifies these match the original request.


Endpoint requirements

  • HTTPS only (port 443)
  • Domain name (no IP addresses)
  • No cookies — the intent_token is your sole auth mechanism
  • Handle OPTIONS preflight for CORS
  • Tokens should expire (recommended: 30 minutes max)

HTTP status codes

Status Wallet Behavior
200 Proceeds with payment
400 Fails immediately
403 Fails immediately (invalid/expired token)
429 Retries once (respects Retry-After)
5xx Retries once after 2s

Error response

JSON
{
  "success": false,
  "error": "Token expired"
}

HMAC signing (optional)

Sign your resolve response with HMAC-SHA256 for high-value payments.

Format
signature = HMAC-SHA256(shared_secret, recipientB58 + amount_micro + merchant_ref)

Include the hex-encoded result as signature in your response. If present, the wallet verifies it before proceeding.


Example server (Node.js)

Node.js
import crypto from 'crypto';

const SECRET = process.env.INTENT_SECRET;
const WALLET_ADDRESS = process.env.WALLET_ADDRESS;

app.post('/api/resolve-payment', (req, res) => {
  const { intent_token, merchant_ref, amount_micro } = req.body;

  if (!validateToken(intent_token, merchant_ref)) {
    return res.status(403).json({ success: false, error: 'Invalid token' });
  }

  res.json({
    success: true,
    recipientB58: WALLET_ADDRESS,
    amount_micro,
    merchant_ref,
    memo: `Payment ${merchant_ref}`,
  });
});

function validateToken(token, merchantRef) {
  const [timestamp, sig] = token.split(':');
  const expected = crypto
    .createHmac('sha256', SECRET)
    .update(timestamp + merchantRef)
    .digest('hex');

  const valid = crypto.timingSafeEqual(
    Buffer.from(sig, 'hex'),
    Buffer.from(expected, 'hex')
  );

  const age = Date.now() - parseInt(timestamp);
  return valid && age < 30 * 60 * 1000;
}

Example server (PHP / WordPress)

PHP / WordPress
add_action('rest_api_init', function () {
  register_rest_route('fire-pay/v1', '/intent/resolve', [
    'methods' => 'POST',
    'callback' => 'fire_pay_resolve_intent',
    'permission_callback' => '__return_true',
  ]);
});

function fire_pay_resolve_intent($request) {
  $token = $request->get_param('intent_token');
  $merchant_ref = $request->get_param('merchant_ref');
  $amount_micro = $request->get_param('amount_micro');

  $order = fire_pay_validate_intent_token($token, $merchant_ref);
  if (!$order) {
    return new WP_REST_Response(
      ['success' => false, 'error' => 'Invalid token'], 403
    );
  }

  return new WP_REST_Response([
    'success' => true,
    'recipientB58' => get_option('fire_pay_wallet_address'),
    'amount_micro' => $amount_micro,
    'merchant_ref' => $merchant_ref,
    'memo' => 'Order ' . $merchant_ref,
  ]);
}