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

MethodPathPurpose
GET/healthLiveness → {"ok":"mpc-bridge"}
POST/v1/mpc/ai-agent-pubkeyKeygen → { walletId, eddsaPubKey (base64) }. Rate limited 10/min, 100/day per IP. Only for embedded Issue Wallet UIs.
POST/v1/mpc/sign-solana-messageSign a serialized message with the agent key
POST/v1/mpc/execute-willBridge 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

SymptomCauseFix
Transaction signature verification failurewalletId and AI_AGENT_PUBKEY from different issuances, or wrong message bytesRe-copy the pair from one dialog; serialize the message (to_bytes_versioned in Python)
Bridge 400Bad base64, message under 32 bytes, missing walletIdCheck the request body
Bridge 403Agent is past death_timeoutAgent must heartbeat first
Bridge 502 / 504MPC cluster error / 120 s signing timeoutFresh blockhash, rebuild, re-sign with backoff; check /health
Custom error 0x177c (6012)Will not activatedAgent calls create_will once