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_tokenis 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,
]);
}