DogecoinVM API reference

The DogecoinVM site at https://metaldoge.com serves two APIs, the same ones its web wallet and the Mac app use:

Alpha. DogecoinVM and its bridge are alpha software, not yet audited, and capped. Read the alpha terms before moving real DOGE. The current caps are always in GET /api/info.

Keys never go to any server. Nothing in this API takes a private key. Make keys and sign transactions on the user's own device (see Build a wallet in 5 steps and Security).

Conventions

In the examples, D5EQRCnPMXmRvUoZwC7gu7fYspean3PQ9a is a made-up address and the transaction IDs are placeholders. The response shapes are real.

Limits

What Limit
Explorer-style endpoints (/api/address, /api/tx, /api/block, /api/blocks, /api/doge/import, /api/doge/rawtx) 8 at a time across all users; a request waiting over 10 s gets 503 busy; try again shortly
New deposit addresses (POST /api/deposit-address) and new Dogecoin addresses (POST /api/doge/watch) 30 per hour per IP (registering one already known is free)
/api/events streams 5,000 open at once
Request bodies 1 MiB

This is one public site, not a hosted API service: there are no API keys or uptime guarantees. An app with real traffic should run its own node (RUN-A-NODE.md) and use its JSON-RPC.


Network and bridge

GET /api/info

The network's parameters and the bridge's current policy. Read it at start-up and check chainID and dogecoinvmNetwork before signing anything.

curl https://metaldoge.com/api/info
{
  "chainID": "2hFCfzdMmfXBxYgvvdL7BYiJAxdejyn4AksMYUM2eM5gN7Xrjy",
  "dogecoinvmNetwork": "dogecoinvm",
  "dogecoinNetwork": "mainnet",
  "dogecoinvmVersions": { "p2pkh": 30, "p2sh": 22, "wif": 158 },
  "dogecoinVersions": { "p2pkh": 30, "p2sh": 22, "wif": 158 },
  "pegAddress": "AAvNfukpAa4iTcRJPetxuxX8XxbC5gFqUM",
  "reserveAddress": "AAvNfukpAa4iTcRJPetxuxX8XxbC5gFqUM",
  "signers": { "required": 2, "publicKeys": ["0212a0…", "…", "…"] },
  "depositConfirmations": 20,
  "confirmationTiers": [
    { "upTo": "1.00000000", "confirmations": 1 },
    { "upTo": "10.00000000", "confirmations": 6 },
    { "upTo": "50.00000000", "confirmations": 12 }
  ],
  "vmFee": "0.01000000",
  "dogeFee": "0.10000000",
  "minDeposit": "1.00000000",
  "maxDeposit": "100.00000000",
  "minPegOut": "2.00000000",
  "maxCirculating": "4200.00000000",
  "faucet": { "enabled": false },
  "dogeWallet": true
}
Field Meaning
chainID DogecoinVM's blockchain ID on Metal. Pin it in your app.
*Versions Base58 version bytes. Identical on both chains: that's why one key has one address on both.
pegAddress The bridge's multisig on Dogecoin (the shared deposit address; see deposits).
reserveAddress The bridge's reserve on DogecoinVM. Paying DOGE into it (with a tag) is how you withdraw. It is the same script as pegAddress, so the same string.
signers The bridge's m-of-n signer set (public keys).
confirmationTiers, depositConfirmations Dogecoin confirmations a deposit needs before it is credited: by amount, up to each upTo; above the last tier, depositConfirmations.
vmFee What the bridge deducts from a deposit when it credits it on DogecoinVM.
dogeFee What the bridge deducts from a withdrawal for the Dogecoin payout.
minDeposit / maxDeposit Per-deposit limits. A deposit outside them isn't credited; it's held for a refund.
minPegOut The smallest withdrawal.
maxCirculating The alpha cap on all DOGE on DogecoinVM. A deposit that would exceed it waits.
faucet Test networks only; disabled on mainnet.
dogeWallet Whether the /api/doge/* endpoints are served.

GET /api/status

Both chains' heights, Dogecoin sync, the peg audit (proof of reserves), measured finality, and whether the bridge is paused. Updated every 15 s.

curl https://metaldoge.com/api/status
{
  "dogecoinvmHeight": 51,
  "dogecoinHeight": 6400367,
  "dogecoinSync": { "headers": 6400367, "progress": 0.9999997, "syncing": false, "available": true },
  "dogecoinBlockTime": 1791065270,
  "dogecoinSupply": { "amount": "156175782865", "height": 6400328 },
  "finality": { "payments": 21, "medianMs": 196, "p90Ms": 268, "latestMs": 97 },
  "audit": {
    "solvent": true,
    "locked": "46.61000000",
    "circulating": "46.61000000",
    "pendingPegIns": "0.00000000",
    "pendingPegOuts": "0.00000000",
    "surplus": "0.00000000",
    "unclaimedOnDogecoin": "0.00000000",
    "unclaimedOnDogecoinVM": "0.00000000"
  },
  "updated": "2026-10-03T22:08:11Z"
}

GET /api/health

Health checks, for uptime monitors: HTTP 200 when everything passes (including "status": "degraded", a deliberate pause or a Dogecoin node catching up), 503 when something is down.

{
  "ok": true,
  "status": "ok",
  "checks": [
    { "name": "pause", "ok": true, "detail": "not paused" },
    { "name": "peg", "ok": true, "detail": "locked 46.61000000, circulating 46.61000000, …" }
  ],
  "updated": "2026-10-03T22:08:11Z"
}

GET /api/reserves

Every output the bridge holds on Dogecoin, for checking the proof of reserves yourself against any Dogecoin explorer.

{
  "lockedOnDogecoin": "46.61000000",
  "circulating": "46.61000000",
  "pegAddress": "AAvNfukpAa4iTcRJPetxuxX8XxbC5gFqUM",
  "reserveAddress": "AAvNfukpAa4iTcRJPetxuxX8XxbC5gFqUM",
  "reserveCreated": "9000000000.00000000",
  "reserveHeld": "8999999953.39000000",
  "personalDepositCount": 11,
  "dogecoinOutputs": [
    { "address": "9rctRHEpjCvZmVJqwSaGNTzCqUWKS9MmYB", "amount": "1.00000000",
      "confirmations": 11580, "txid": "709b55bd…201b", "vout": 0 }
  ]
}

reserveCreated is the reserve the chain created at genesis (it backs nothing by itself); reserveCreated − reserveHeld is what circulates.

GET /api/activity

Recent bridge activity: deposits and withdrawals, newest first (up to 20).

[
  { "type": "withdrawal", "status": "paid", "amount": "2.00000000", "pays": "1.90000000",
    "to": "D5EQRCnPMXmRvUoZwC7gu7fYspean3PQ9a",
    "dogecoinvmTxid": "27ca64c0…a1e3", "dogecoinTxid": "1f3cb18e…b7a9", "time": 1790899406 }
]

DogecoinVM: addresses, transactions, blocks

GET /api/address/{address}

A DogecoinVM address's balance, unspent outputs and last 50 transactions.

curl https://metaldoge.com/api/address/D5EQRCnPMXmRvUoZwC7gu7fYspean3PQ9a
{
  "address": "D5EQRCnPMXmRvUoZwC7gu7fYspean3PQ9a",
  "confirmed": "5.65601300",
  "pending": "0.00000000",
  "utxos": [
    { "txid": "709b55bd…201b", "vout": 0, "value": "99000000",
      "script": "76a914…88ac", "confirmations": 3 }
  ],
  "history": [
    { "txid": "709b55bd…201b", "confirmations": 3, "net": "0.99000000", "time": 1790899408 }
  ]
}

GET /api/tx/{txid}

A DogecoinVM transaction, decoded and described.

{
  "txid": "27ca64c0…a1e3",
  "confirmations": 1,
  "blockHash": "5af2392a…ee73",
  "time": 1790899408,
  "kind": "credit",
  "label": "Credit for a deposit on Dogecoin, released from the peg reserve",
  "inputs": [ { "address": "AAvNfukpAa4iTcRJPetxuxX8XxbC5gFqUM", "value": "8999999935.06000000", "note": "peg reserve" } ],
  "outputs": [
    { "address": "D5EQRCnPMXmRvUoZwC7gu7fYspean3PQ9a", "value": "0.99000000" },
    { "address": "AAvNfukpAa4iTcRJPetxuxX8XxbC5gFqUM", "value": "8999999934.06000000", "note": "peg reserve" },
    { "value": "0.00000000", "note": "bridge message" }
  ],
  "fee": "0.01000000",
  "dogecoinTxid": "959f7afe…b68b"
}

kind is one of transfer, credit (a bridge deposit credited), withdrawal (a payment into the reserve), reserve, reward. dogecoinTxid links a bridge transaction to its Dogecoin side.

GET /api/rawtx/{txid}

A DogecoinVM transaction's raw hex: {"hex": "0100…"}. 404 if the chain doesn't have it.

POST /api/tx

Broadcast a signed DogecoinVM transaction.

curl -X POST https://metaldoge.com/api/tx -H 'content-type: application/json' \
  -d '{"hex":"0100000001…"}'
{ "txid": "709b55bd…201b" }

GET /api/blocks?before={height}

The 15 newest blocks (or the 15 below before): {"tip": 51, "blocks": [{height, hash, time, txCount, previousHash}, …]}. DogecoinVM makes a block only when there are transactions, so heights grow slowly and a block can be minutes or days after the last.

GET /api/block/{height-or-hash}

One block with its transactions (up to 50 described): {"block": {height, hash, time, txCount, previousHash, txids}, "transactions": [tx, …], "txCount": n}.

GET /api/events

A Server-Sent Events stream that says when a chain has a new block, so wallets refresh at once instead of polling. It carries no account data.

event: block
data: {"chain":"dogecoinvm","height":52}

event: block
data: {"chain":"dogecoin","height":6400368}

On connect, the latest height of each chain is sent first.


The bridge: deposits and withdrawals

POST /api/deposit-address

Gives the personal Dogecoin deposit address for a DogecoinVM address. DOGE sent to it on Dogecoin is credited to that DogecoinVM address (less vmFee) once it has enough confirmations. Each DogecoinVM address has one deposit address, and it never changes; registering it again returns the same one.

curl -X POST https://metaldoge.com/api/deposit-address -H 'content-type: application/json' \
  -d '{"address":"D5EQRCnPMXmRvUoZwC7gu7fYspean3PQ9a"}'
{
  "depositAddress": "9rctRHEpjCvZmVJqwSaGNTzCqUWKS9MmYB",
  "creditTo": "D5EQRCnPMXmRvUoZwC7gu7fYspean3PQ9a",
  "redeemScript": "15000101…0101755221…53ae"
}

Check it yourself; don't just trust the server. The deposit address is the P2SH of redeemScript, which is

OP_DATA_21 <type> <hash160 of the DogecoinVM address>  OP_DROP  <the bridge's m-of-n multisig>

where type is 0x00 for P2PKH and 0x01 for P2SH, and the multisig is OP_m <signers.publicKeys…> OP_n OP_CHECKMULTISIG from /api/info. A wallet should rebuild the script from /api/info and the user's own address, and refuse to show a deposit address that doesn't match.

Rules (alpha; read the current values from /api/info):

Advanced: instead of a personal address, a deposit can pay pegAddress with an OP_RETURN of DVMD <type> <hash160> (25 bytes) naming the DogecoinVM address. A payment to pegAddress without that tag has no destination and is held.

GET /api/deposits/{dogecoinvm-address}

Deposits made for a DogecoinVM address, and where each one is.

[
  { "txid": "959f7afe…b68b", "vout": 0, "amount": "1.00000000",
    "confirmations": 1, "required": 1, "status": "credited",
    "creditTxid": "27ca64c0…a1e3", "credited": "0.99000000" },
  { "txid": "1f3cb18e…b7a9", "vout": 1, "amount": "150.00000000",
    "confirmations": 40, "required": 20, "status": "held",
    "reason": "above the maximum deposit" }
]

status is confirming, crediting (enough confirmations; credited within a few seconds), waiting_for_capacity, credited, held (with a reason) or refunded (with refundTxid).

Withdrawing: a payment into the reserve

A withdrawal is an ordinary DogecoinVM transaction that pays DOGE into reserveAddress and names the Dogecoin address to pay, in its only OP_RETURN output:

Output Script
Payment the amount (at least minPegOut) to reserveAddress (P2SH)
Tag OP_RETURN pushing 25 bytes: "DVMO" (ASCII) ‖ type (1 byte: 0x00 P2PKH, 0x01 P2SH) ‖ the 20-byte hash160 of the Dogecoin destination address
Change back to your own address, as usual

The bridge pays the Dogecoin address amount − dogeFee. Rules:

Example tag for withdrawing to the made-up D5R2B6TsTmVihS3WLQMwbpfSsCV9WeqE4v (hash160 0303…03):

6a 19 44564d4f 00 0303030303030303030303030303030303030303
OP_RETURN, push 25: "DVMO", P2PKH, hash160

GET /api/pegout/{dogecoinvm-txid}

A withdrawal's progress, by the txid of the DogecoinVM payment.

{ "status": "paid", "amount": "2.00000000", "pays": "1.90000000",
  "to": "D5R2B6TsTmVihS3WLQMwbpfSsCV9WeqE4v",
  "paymentTxid": "1f3cb18e…b7a9", "paymentConfirmations": 4 }

status is pending (being paid), paid (with the Dogecoin paymentTxid and its confirmations), or unknown (not final on DogecoinVM yet, or not a valid withdrawal).


The Dogecoin side of a wallet

The site also serves a user's Dogecoin (mainnet) balance, so a light wallet needs no Dogecoin node. Addresses must be registered first.

POST /api/doge/watch

Start indexing a Dogecoin address: {"address": "D…"} → {"address": "D…", "watchedFrom": 6400000}. Payments are found from watchedFrom onwards; import older ones with /api/doge/import.

GET /api/doge/address/{address}

{
  "address": "D5EQRCnPMXmRvUoZwC7gu7fYspean3PQ9a",
  "confirmed": "5.00000000",
  "pending": "0.00000000",
  "utxos": [ { "txid": "1f3cb18e…b7a9", "vout": 0, "value": "500000000",
               "script": "76a914…88ac", "confirmations": 6 } ],
  "history": [ { "txid": "1f3cb18e…b7a9", "net": "5.00000000", "confirmations": 6, "time": 1791008000 } ],
  "watchedFrom": 6400000,
  "indexedTo": 6400367
}

404 address is not registered until /api/doge/watch; 503 while the site's Dogecoin node is catching up.

POST /api/doge/import

Add a payment made before the address was registered: {"address": "D…", "txid": "…"} → {"imported": 1}.

GET /api/doge/rawtx/{txid} and POST /api/doge/tx

The Dogecoin equivalents of /api/rawtx and /api/tx: fetch a Dogecoin transaction's hex (to check what you spend), and broadcast a signed Dogecoin transaction ({"hex": …} → {"txid": …}, same 400/502 rules).


JSON-RPC: /rpc

The DogecoinVM node's own JSON-RPC (btcd, Bitcoin Core style), through a limited login whose username and password are both public:

curl -s -u public:public https://metaldoge.com/rpc -H 'content-type: application/json' \
  -d '{"jsonrpc":"1.0","id":1,"method":"getblockcount","params":[]}'
{"result": 51, "error": null, "id": 1}

The limited login may call: getbestblock, getbestblockhash, getblock, getblockcount, getblockhash, getblockheader, getchaintips, getheaders, getcfilter, getcfilterheader, getinfo, getrawmempool, getrawtransaction, gettxout, searchrawtransactions, decoderawtransaction, decodescript, createrawtransaction, estimatefee, validateaddress, verifymessage, getcurrentnet, getdifficulty, getnettotals, uptime, version and help (and notifyblocks over the WebSocket). Wallet, node-administration and broadcast methods are refused: to broadcast, use POST /api/tx.

searchrawtransactions (the node keeps an address index) returns at most 500 transactions per call; read longer histories in pages with skip.


Build a wallet in 5 steps

  1. Make the key on the device. Use any Dogecoin library (for example libdogecoin, or a Bitcoin library with Dogecoin's mainnet parameters: P2PKH 30, P2SH 22, WIF 158, BIP44 coin type 3). A compressed secp256k1 key gives one D… address, valid on both Dogecoin and DogecoinVM. Back the key up; nobody can recover it.

  2. Check the network. GET /api/info: refuse to continue unless chainID is 2hFCfzdMmfXBxYgvvdL7BYiJAxdejyn4AksMYUM2eM5gN7Xrjy and dogecoinvmNetwork is dogecoinvm.

  3. Read the balance. GET /api/address/{address} for DogecoinVM (and /api/doge/watch + /api/doge/address for Dogecoin). For each UTXO you will spend, GET /api/rawtx/{txid}, check the hex hashes to txid, and take the value and script from that output.

  4. Build and sign a standard legacy transaction: version 1, P2PKH inputs signed with SIGHASH_ALL, low-S DER signatures, compressed public keys. DogecoinVM's relay rules are Dogecoin Core 1.14's:

    • fee at least 0.001 DOGE per kB (100 koinu per byte) of the signed size;
    • each spendable output below 0.01 DOGE adds 0.01 DOGE to the required fee; an output below 0.001 DOGE is non-standard;
    • at most one OP_RETURN; no SegWit.

    (vmFee and dogeFee in /api/info are the bridge's charges on credits and payouts, not network fees.)

  5. Broadcast with POST /api/tx. Final in under a second; /api/events tells you when the block lands.

Bridge in: POST /api/deposit-address with the user's DogecoinVM address, check the redeem script as above, and show the deposit address (it's a Dogecoin address: say so). Track it with /api/deposits/{address}. Bridge out: pay at least minPegOut to reserveAddress with the DVMO tag naming the Dogecoin address; track it with /api/pegout/{txid}.

Security