Bridge docs

MeterCall's Bridge is a bidirectional gateway. Smart contracts call real-world APIs. Real-world webhooks turn into on-chain attestations. Every crossing is signed with EIP-712 and verifiable by any EVM contract.

Overview

The bridge has two complementary flows:

  • Chain → Real-life. A contract emits BridgeCall(requestId, apiSlug, params, callback). The bridge listener picks it up, routes to the right HTTP API, and calls fulfill(requestId, result, sig) on the callback contract.
  • Real-life → Chain. A webhook arrives at POST /v1/bridge/webhook/:slug. The bridge verifies it, produces an EIP-712 attestation signed by the hot wallet, and exposes it on /v1/bridge/verify/:requestId. Your contract consumes it via BridgeVerifier.
# quickstart (off-chain)
curl -sX POST https://metercall.ai/v1/bridge/call \
  -H 'content-type: application/json' \
  -d '{"chain":"base","apiSlug":"stripe-replacement","params":{"action":"refund","amount":5000,"customer":"cus_abc"}}'

Architecture

Off-chain components

  • Listener — subscribes to BridgeCall across 30+ chains.
  • Router — maps apiSlug → HTTP connector.
  • Signer — hot wallet, signs EIP-712 attestations.
  • Store — immutable request/result/signature log.

On-chain components

  • BridgeVerifier.sol — verifies EIP-712 signatures.
  • BridgeCallRouter.sol — example contract that emits + consumes.
  • Deployed on Ethereum, Base, Arbitrum, Optimism, Polygon, BSC, Avalanche, Solana (via Wormhole adapter), + 22 more.

Attestation format (EIP-712)

Every bridge result is wrapped in this typed structured-data envelope:

BridgeAttestation(
  bytes32 requestId,
  string  apiSlug,
  bytes32 resultHash,
  uint256 timestamp,
  uint256 chainId
)

The domain separator uses:

name
MeterCallBridge
version
1
chainId
8453 (Base) — portable per deployment
verifyingContract
0x… (deployed BridgeVerifier)

Example payload returned by /v1/bridge/attest:

{
  "eip712": {
    "domain": { "name": "MeterCallBridge", "version": "1", "chainId": 8453 },
    "types": {
      "BridgeAttestation": [
        { "name": "requestId",  "type": "bytes32" },
        { "name": "apiSlug",    "type": "string"  },
        { "name": "resultHash", "type": "bytes32" },
        { "name": "timestamp",  "type": "uint256" },
        { "name": "chainId",    "type": "uint256" }
      ]
    },
    "primaryType": "BridgeAttestation",
    "message": {
      "requestId":  "0x…",
      "apiSlug":    "stripe-replacement",
      "resultHash": "0x…",
      "timestamp":  1745001234,
      "chainId":    8453
    }
  },
  "signature": "0x…65 bytes…"
}

Verifier contract (Solidity sketch)

Ship BridgeVerifier.sol and point it at MeterCall's signer address:

function verifyAttestation(
    bytes32 requestId,
    string  memory apiSlug,
    bytes32 resultHash,
    uint256 timestamp,
    bytes   memory sig
) public view returns (bool) {
    bytes32 structHash = keccak256(abi.encode(
        TYPEHASH, requestId, keccak256(bytes(apiSlug)),
        resultHash, timestamp, block.chainid
    ));
    bytes32 digest = _hashTypedDataV4(structHash);
    return ECDSA.recover(digest, sig) == signer;
}

See the full contract with replay protection and nonce handling in /contracts/BridgeVerifier.sol.

Chain → Real-life flow

  1. Your contract calls router.request(apiSlug, params) → emits BridgeCall.
  2. MeterCall listener picks up the event within ~2s.
  3. Bridge calls the real-world API (Stripe, Twilio, anything from the catalog).
  4. Bridge signs the result EIP-712, calls router.fulfill(requestId, result, sig).
  5. Your contract verifies the signature via BridgeVerifier and uses the result.
// on-chain
bytes32 id = router.request("stripe-replacement", abi.encode("refund", 5000, "cus_abc"));

// ~2s later your fulfill() is called
function fulfill(bytes32 rid, bytes memory result, bytes memory sig) external onlyBridge {
    require(verifier.verifyAttestation(rid, "stripe-replacement",
                                       keccak256(result), block.timestamp, sig),
            "bad attestation");
    // decode result, credit user, whatever
}

Real-life → Chain flow

  1. You register a webhook URL with a real-world provider pointing at POST /v1/bridge/webhook/:slug.
  2. Provider fires an event (charge succeeded, order placed, SMS delivered).
  3. Bridge verifies provider HMAC, normalizes the payload, produces an EIP-712 attestation.
  4. Bridge optionally posts attestation on-chain via BridgeCallRouter.attest(), or you pull it via GET /v1/bridge/verify/:requestId and submit yourself.
  5. Your contract verifies with BridgeVerifier.
# Stripe → MeterCall
POST /v1/bridge/webhook/stripe-replacement HTTP/1.1
X-MeterCall-Signature: t=1745001234,v1=...

{ "type":"charge.succeeded", "data":{ "amount":5000 } }

# MeterCall responds with the signed attestation
{ "attestation_id":"0x…", "eip712":{…}, "signature":"0x…" }

Security model

  • Signer isolation. The bridge hot wallet lives in an HSM-backed signer. Rotated every 30 days.
  • Nonce protection. BridgeVerifier.consumeAttestation tracks used requestIds; replay is impossible.
  • Timestamp freshness. Attestations older than 30 minutes are rejected by default (configurable per contract).
  • Per-slug rate limits. Abuse on a connector is isolated; the whole bridge doesn't degrade.
  • Audit log. Every bridge call is queryable at /attestations.
This is a trust-minimized oracle, not trustless. You trust MeterCall's signer the same way you trust Chainlink's reporter set. We plan a multi-signer committee (M-of-N) in Q3.

Supported chains

Ethereum, Base, Arbitrum, Optimism, Polygon, BSC, Avalanche, zkSync Era, Linea, Scroll, Mantle, Blast, Mode, Gnosis, Celo, Fantom, Moonbeam, Kava, Metis, Boba, Solana (Wormhole adapter), Sui (adapter), Aptos (adapter), NEAR (adapter), Stellar, Cosmos Hub, Osmosis, Injective, Sei, Polkadot.

Live chain status at /l4.

Supported APIs

Every module in the connectors catalog is bridge-ready. Today: 2,866 slugs. Target: 20M. Search, filter, click any card for the EIP-712 snippet.

Rate limits

Free tier
60 bridge calls / hour / address
Builder ($149/mo)
10k / hour
Mainframe ($599/mo)
100k / hour + dedicated signer
Enterprise
unmetered, committee signers

Pricing

Pay per bridge call in PCP (MeterCall credits) or USDC. Chain → Real-life costs more than Real-life → Chain because it includes the downstream API call.

Chain → Real-life
0.002 PCP + API surcharge
Real-life → Chain
0.0005 PCP per attestation
Bulk attestation
discounts over 1M calls / mo

Errors

400 bridge/bad_slug
Unknown apiSlug. Check connectors.
401 bridge/bad_hmac
Webhook HMAC didn't match the provider's secret.
404 bridge/unknown_request
requestId doesn't exist or was evicted (500-entry ring buffer).
409 bridge/replay
Attestation already consumed on-chain.
429 bridge/rate_limited
Back off — see Retry-After.
503 bridge/signer_unavailable
Hot wallet is rotating. Retry in < 5s.