---
title: "Integrate Utila with BlindPay"
description: "Fund BlindPay payouts from Utila MPC vaults: sign the wallet-registration challenge and the ERC-20 approve through Utila's service-account API, with async transaction confirmation handled."
date: "2026-08-26"
category: "integrations"
products: ["payouts", "quotes", "webhooks"]
---

Paste this prompt into your coding agent when funds live in Utila-managed MPC wallets and those wallets need to register as the sender and authorize BlindPay payouts on-chain.

## Prompt

```text
You are integrating BlindPay EVM payouts funded from Utila MPC vault wallets into my application. Utila holds the private key shares; my backend calls Utila's REST API to sign the BlindPay registration challenge and the ERC-20 approve, then calls BlindPay to execute the payout.

Before writing code, read these sources and follow them over any prior knowledge:
- https://blindpay.com/docs/llms.txt (read the blockchain wallets page and the EVM payout guide)
- The OpenAPI spec: curl https://api.blindpay.com/doc
- https://docs.utila.io/reference/api-overview
- https://docs.utila.io/reference/authentication
- https://docs.utila.io/reference/service-accounts
- https://docs.utila.io/reference/transactions_initiatetransaction
- https://docs.utila.io/reference/blockchains_listnetworks

Build the flow:
1. Authenticate to Utila as a service account: build a locally-signed RS256 JWT (sub = service account email, aud = "https://api.utila.io/", short expiry) from the uploaded RSA key pair and send it as a Bearer token on every Utila call. Confirm the exact claim set and endpoint against the live auth docs before hardcoding it.
2. Register the wallet: call GET /v1/instances/{instance_id}/customers/{customer_id}/blockchain-wallets/sign-message to get the challenge, sign it by calling Utila's POST /v2/vaults/{vault_id}/transactions:initiate with details.evmPersonalSign (fromAddress, message), poll GetTransaction (or handle the equivalent webhook) until the signing transaction reaches its completed state, extract the signature, and POST it to /v1/instances/{instance_id}/customers/{customer_id}/blockchain-wallets to persist the returned bw_ ID against the customer record.
3. Request a quote: call POST /v1/instances/{instance_id}/quotes with bank_account_id, network, and token, and read back the contract object (address, abi, functionName "approve", blindpayContractAddress, amount, network, expires_at) without hardcoding any contract address.
4. Send the approve: ABI-encode the "approve" call from the quote's contract.abi and amount, then call Utila's POST /v2/vaults/{vault_id}/transactions:initiate with details.evmTransaction (network resolved via ListNetworks for the quote's chain, fromAddress, toAddress = contract.blindpayContractAddress, value "0", data = the encoded call, publish true). Poll GetTransaction (or handle the equivalent webhook) until the transaction reaches CONFIRMED before moving on, and surface Utila's policy-engine states (pending approval, co-signing) as visible statuses in this step since they add latency on top of the quote's expiry window.
5. Execute the payout: call POST /v1/instances/{instance_id}/payouts/evm with the quote_id and sender_wallet_address before the quote's expires_at. If the quote expires while the approve is still confirming, request a fresh quote for the same amount and skip re-sending the approve when the existing on-chain allowance already covers it.
6. Verify the webhook secret and handle payout.new, payout.update, and payout.complete with Svix signature verification (svix-id, svix-timestamp, svix-signature against whsec_) on the raw request body, updating payout state in the database on each event.
7. Run the full flow end to end on a development instance with USDB on a testnet, then repeat it once each with the $666.00 (66600 minor units) sentinel to confirm the failed webhook path and $777.00 (77700 minor units) to confirm the refunded webhook path.

Constraints:
- Keep BlindPay and Utila API keys server-side; never expose either to the client.
- Represent every amount as an integer in minor units end to end; never use floating point for money math.
- Never hardcode the BlindPay contract address, ABI, or Utila network resource name; read them from the quote response and ListNetworks respectively.

Deliverables: the Utila service-account auth helper, the wallet registration flow with the bw_ ID persisted, the quote-approve-execute orchestration with async Utila confirmation and the expiry race handled, Svix-verified webhook handlers, and a passing end-to-end test against a development instance covering the success and both sentinel-amount paths.
```

## How to use

1. Give the agent your Utila service account's key pair location and vault ID, and your BlindPay instance ID.
2. Confirm the Base Sepolia (or your target testnet) network resource name against Utila's ListNetworks before the agent wires the approve call.
3. Watch the quote-expiry-vs-MPC-approval race in testing; Utila's co-signing delay is what usually pushes past the 5-minute window.

## Related docs

- [Blockchain wallets](https://blindpay.com/docs/blockchain-wallets)
- [EVM payouts](https://blindpay.com/docs/payout-evm)
- [Utila API overview](https://docs.utila.io/reference/api-overview)
- [Utila InitiateTransaction](https://docs.utila.io/reference/transactions_initiatetransaction)
