Docs / Get started / Build on Doge Soft

Developer quickstart

Build on Doge Soft

From zero to a working Dogecoin dApp: detect the wallet, connect, read balance, sign messages and PSBTs, send, inscribe and read the chain.

Live

This page gets you from zero to a working Dogecoin dApp. You don't need an SDK, an API key or your own indexer. The user's wallet injects window.dogesoft into your page, and the public Explorer API answers read questions. Every snippet below uses only methods and endpoints that exist today.

ℹ️

Where it runs: desktop Chrome with the Doge Soft Wallet extension, or any page opened in the Doge Soft mobile app's built-in browser. Both inject the same window.dogesoft object (provider version 1.1.0, Dogecoin mainnet).

1. Detect the wallet

detect.js
function getDogesoft(timeoutMs = 3000) {
  return new Promise((resolve) => {
    if (window.dogesoft) return resolve(window.dogesoft);
    const done = () => resolve(window.dogesoft || null);
    window.addEventListener("dogesoft#initialized", done, { once: true });
    setTimeout(done, timeoutMs);
  });
}

const dogesoft = await getDogesoft();
if (!dogesoft) {
  // No Doge Soft wallet: show an install / "open in the Doge Soft app" hint
}

2. Connect

connect() asks the user to approve your site. Until they do, no address is shared and every other account method rejects with DogeSoft: not connected. An approved site stays connected across reloads.

connect.js
const { accounts, publicKey } = await dogesoft.connect();
const address = accounts[0];        // "D…"

dogesoft.isConnected();                  // true (synchronous)
dogesoft.on("accountsChanged", (accts) => render(accts[0] ?? null));
dogesoft.on("disconnect", () => render(null));

3. Address and balance

account.js
const address = await dogesoft.getAddress();     // "D…" or null
const { balance } = await dogesoft.getBalance(); // spendable DOGE (not koinu), e.g. 42.5

4. Sign a message (log in with Dogecoin)

signMessage() returns { signature }: a base64 compact signature in the standard "Dogecoin Signed Message" format. Put a server-issued nonce in the message so it can't be replayed, and verify it on your server.

login.js (browser)
const { nonce } = await (await fetch("/api/nonce")).json();
const message = `Sign in to My App: ${nonce}`;
const { signature } = await dogesoft.signMessage(message, address);

await fetch("/api/login", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ address, message, signature })
});
verify.js (Node, npm i bitcoinjs-message)
const bitcoinMessage = require("bitcoinjs-message");
const DOGE_PREFIX = "\x19Dogecoin Signed Message:\n";

const ok = bitcoinMessage.verify(message, address, signature, DOGE_PREFIX);
// Also check the nonce is one you issued and hasn't been used yet.

5. Send DOGE, DRC-20 Coins and Doginals

Each call opens the wallet's own confirmation screen. Your page never sees a key, and nothing moves unless the user approves.

send.js
// DOGE: amount is in DOGE, not koinu
const { txid } = await dogesoft.sendDoge({ to: "D…recipient", amount: 4.2 });

// DRC-20 Coins: the wallet creates the transfer inscription and delivers it
await dogesoft.sendDrc20({ tick: "WOOF", amount: "1000", to: "D…recipient" });

// A Doginal the user holds
await dogesoft.sendInscription({ inscriptionId: "<txid>i0", to: "D…recipient" });

sendDoge() resolves with a txid. sendDrc20() and sendInscription() resolve once the send is done; don't depend on a txid in their result. Confirm delivery through the Explorer API (step 8). Details: Sending.

6. Sign a PSBT

For anything custom, such as marketplace listings or multi-party trades, build a PSBT on your server and let the user sign it. Every input the wallet signs needs its full previous transaction (nonWitnessUtxo), and the user always sees a decoded summary of the outputs first.

psbt.js
// psbt: hex or base64 from your backend
const res = await dogesoft.signPsbt(psbt, { finalize: true });
// res.psbtHex: the signed PSBT (base64-encoded, despite the name)
// res.txHex:   raw transaction, present only when every input is now signed

if (res.txHex) {
  // hand it to your server, which broadcasts it (step 8 shows the endpoint)
  await fetch("/api/broadcast", { method: "POST", body: res.txHex });
}

// Or in one step: review, sign every input, broadcast (PSBT as hex)
const { txid } = await dogesoft.pushPsbt(psbtHex);

More, including SIGHASH options for listings: PSBTs.

7. Inscribe

There is no inscribe() method on the provider. Pick the path that fits:

  • DRC-20 transfer or Doginal move: sendDrc20() / sendInscription() above.
  • Custom transaction: build it server-side and have the user sign it (step 6). The inscriber's /prepare endpoint is wallet-only (it powers the Doge Soft wallet's own sends) and is not an integration API. Outside builders inscribe through Origin (requestInscription).
  • From a DogeOS contract: Origin Services inscribes any registered non-DRC-20 action for a fee, delivered to the Dogecoin address you choose.

Whatever the path, the bytes follow the inscription standard. The ref names a record signed with a Dogecoin signmessage. The indexer, reading Dogecoin Core, is the source of truth for the orbit.

8. Read the chain (Explorer API)

The Explorer API is public and needs no key. Call it from your backend: browser requests from other websites aren't enabled by default. If you need browser access from your own domain, ask through the developer access request and list your origins.

explorer.js (Node 18+)
const API = "https://explorer.dogesoft.io/api";
const get = (path) => fetch(API + path).then((r) => r.json());

const addr = "D…";
const bal   = await get(`/wallet/address/${addr}/balance`); // { balance_koinu, balance_doge, … }
const utxos = await get(`/wallet/address/${addr}/utxos`);
const coins = await get(`/wallet/address/${addr}/drc20`);   // { balances: [{ tick, balance, … }] }
const tx    = await get(`/explorer/tx/${txid}`);             // parsed DRC-20 ops + inscriptions
const { blockcount } = await get("/chain/blockcount");

// Broadcast a signed raw transaction
const sent = await fetch(API + "/tx/broadcast", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ hex: rawTxHex })
}).then((r) => r.json());                // { txid, … }

Never treat a txid reported by a browser as proof of payment. Look the transaction up yourself. See Accepting payments and the full Explorer API.

9. Handle errors

Every failure is a rejected Promise with a plain Error. Show the message to the user. If they declined, the message says so.

errors.js
try {
  await dogesoft.sendDoge({ to, amount });
} catch (err) {
  showToast(err.message); // e.g. "Doge Soft: user rejected"
}
💡

Next: provider reference, inscription standard, Origin for reading Dogecoin from DogeOS contracts, and the plugs marketplace.