MPC signing flow
How to integrate MPC Bridge signing into your app
You build the Solana message off-chain, the MPC Bridge returns a 64-byte Ed25519 signature, and you attach it and broadcast. The Bridge at https://bridge.aicw.ai is public and needs no API key — the walletId is the credential, so keep it server-side.
Bridge endpoints
| Method | Path | Purpose |
|---|---|---|
| GET | /health | Liveness → {"ok":"mpc-bridge"} |
| POST | /v1/mpc/ai-agent-pubkey | Keygen → { walletId, eddsaPubKey (base64) }. Rate limited 10/min, 100/day per IP. Only for embedded Issue Wallet UIs. |
| POST | /v1/mpc/sign-solana-message | Sign a serialized message with the agent key |
| POST | /v1/mpc/execute-will | Bridge builds and signs will distribution for a dead agent |
Sign request / response
POST /v1/mpc/sign-solana-message
// request
{
"clientId": "optional",
"walletId": "<MPC_WALLET_ID>",
"messageBytesB64": "<base64 of the serialized message, ≥ 32 bytes>",
"networkCode": "solana-devnet",
"aiAgentPubkey": "<base58, optional — enables the dead-agent check>"
}
// response 200
{ "txId": "uuid", "signatureB64": "<base64 64-byte Ed25519>", "networkCode": "solana-devnet", "walletId": "..." }Serialize the message, not the whole transaction: TypeScript versionedTx.message.serialize(), Python to_bytes_versioned(msg) (not bytes(msg)).
End-to-end flow
MPC Signing Flow
Your App / Agent MPC Bridge Solana RPC │ 1. build MessageV0 │ │ │ 2. POST sign ────────────▶│ threshold Ed25519 │ │ ◀── signatureB64 ─────────│ │ │ 3. attach signature │ │ │ 4. sendRawTransaction ───────────────────────────────▶│ │ ◀────────────────────────────────────── confirmation │
Implementation example (TypeScript)
TypeScript
import {
Connection, PublicKey, TransactionInstruction, TransactionMessage, VersionedTransaction,
} from "@solana/web3.js";
const BRIDGE = process.env.MPC_BRIDGE_URL!; // https://bridge.aicw.ai
const WALLET_ID = process.env.MPC_WALLET_ID!; // server-side only
const AGENT = new PublicKey(process.env.AI_AGENT_PUBKEY!);
const conn = new Connection(process.env.SOLANA_RPC_URL ?? "https://api.devnet.solana.com", "confirmed");
export async function signAndSend(ixs: TransactionInstruction[]): Promise<string> {
const { blockhash } = await conn.getLatestBlockhash("confirmed");
const message = new TransactionMessage({
payerKey: AGENT, recentBlockhash: blockhash, instructions: ixs,
}).compileToV0Message();
const res = await fetch(`${BRIDGE}/v1/mpc/sign-solana-message`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
walletId: WALLET_ID,
messageBytesB64: Buffer.from(message.serialize()).toString("base64"),
networkCode: "solana-devnet",
aiAgentPubkey: AGENT.toBase58(),
}),
});
if (!res.ok) throw new Error(`bridge ${res.status}: ${await res.text()}`);
const { signatureB64 } = (await res.json()) as { signatureB64: string };
const tx = new VersionedTransaction(message);
tx.addSignature(AGENT, Buffer.from(signatureB64, "base64"));
const sig = await conn.sendRawTransaction(tx.serialize(), { preflightCommitment: "confirmed" });
await conn.confirmTransaction(sig, "confirmed");
return sig;
}Implementation example (Python)
Python
import base64, os, requests
from solders.pubkey import Pubkey
from solders.message import MessageV0, to_bytes_versioned
from solders.signature import Signature
from solders.transaction import VersionedTransaction
from solana.rpc.api import Client
BRIDGE = os.environ["MPC_BRIDGE_URL"]
WALLET_ID = os.environ["MPC_WALLET_ID"]
AGENT = Pubkey.from_string(os.environ["AI_AGENT_PUBKEY"])
rpc = Client(os.environ.get("SOLANA_RPC_URL", "https://api.devnet.solana.com"))
def sign_and_send(instructions) -> str:
bh = rpc.get_latest_blockhash().value.blockhash
msg = MessageV0.try_compile(payer=AGENT, instructions=instructions,
address_lookup_table_accounts=[], recent_blockhash=bh)
r = requests.post(f"{BRIDGE}/v1/mpc/sign-solana-message", json={
"walletId": WALLET_ID,
"messageBytesB64": base64.b64encode(to_bytes_versioned(msg)).decode(),
"networkCode": "solana-devnet",
"aiAgentPubkey": str(AGENT),
}, timeout=130)
r.raise_for_status()
sig = Signature.from_bytes(base64.b64decode(r.json()["signatureB64"]))
vtx = VersionedTransaction.populate(msg, [sig])
return str(rpc.send_transaction(vtx).value)Error handling
| Symptom | Cause | Fix |
|---|---|---|
| Transaction signature verification failure | walletId and AI_AGENT_PUBKEY from different issuances, or wrong message bytes | Re-copy the pair from one dialog; serialize the message (to_bytes_versioned in Python) |
| Bridge 400 | Bad base64, message under 32 bytes, missing walletId | Check the request body |
| Bridge 403 | Agent is past death_timeout | Agent must heartbeat first |
| Bridge 502 / 504 | MPC cluster error / 120 s signing timeout | Fresh blockhash, rebuild, re-sign with backoff; check /health |
| Custom error 0x177c (6012) | Will not activated | Agent calls create_will once |