Mint, send and redeem DigiDollar
Mint, send and redeem DigiDollar
Reading DigiDollar is arithmetic. Writing it is arithmetic with consequences, and the first consequence is a units convention that costs real money: in the DigiDollar RPCs a bare integer is cents, and only a decimal point makes it dollars. mintdigidollar 500 does not mint five hundred dollars. It mints $5.00, opens a real position, and locks real DGB collateral for the tier you named — and the way you find out is by reading a position back and seeing two decimal places where you expected none. This guide covers the write side: the preconditions that must hold before a mint is even possible, the dry runs that price a position without spending anything, the one fee rule that two published sources get wrong, why a DigiDollar transaction can sit unconfirmed while ordinary DGB transactions sail past it, and why redemption is all-or-nothing.
Who this is for. You are building a wallet, a treasury tool or a service that has to write DigiDollar transactions against your own node — open a position, pay someone in $DD, close a position out — rather than call a hosted API.
What you will build. A precondition check that refuses to mint when the network says no, a tier choice you can defend, a priced position derived from the node's own consensus math, a mint whose amount is unambiguous, a send that does not strand itself on a fee argument, and a redemption you simulated before you signed anything.
Before you start
- DigiByte Core v9.26.5 or later, built with the DigiDollar softfork, running with
digidollar=1andtxindex=1, and a loaded wallet. That wallet needs spendable DGB for two separate reasons: collateral for the position, and the miner fee — DigiDollar transaction fees are paid in DGB, never in $DD. - The transport is the read guide's. Config keys, the loopback endpoint, HTTP basic auth, the worked
curl, and the/wallet/<name>path rule are all in Read DigiDollar state, and are not repeated here. Carry one thing forward: everything in this guide that touches your wallet's coins, addresses or positions is wallet-scoped and needs that path.getdigidollarstatsandestimatecollateralanswer a barePOST /— but scopeestimatecollateralto a wallet anyway when you have one, because its response carries wallet-relative fields (wallet_collateral_dgb) alongside the protocol-levelrequired_dgb, and unscoped you only get the latter. - Units, address decoding, transaction anatomy and balances are the read guide's too. This guide assumes you can already decode a
DD…address, read the17476OP_RETURN magic and itstype_id, and normalize cents and micro-USD. Where a figure here needs a unit, it is named inline; the full table lives there. - Two amounts, two conventions, in the same session. Collateral and fees are DGB, the ordinary way. DigiDollar amounts are cents.
Trap: In the DigiDollar RPCs a bare integer is cents; a value carrying a decimal point is dollars.
mintdigidollar 500 4mints $5.00;mintdigidollar 500.00 4mints $500.getdigidollarbalanceandlistdigidollartxsreport in cents on the way back out, so a round-trip through a naive client — integer in, integer out — is self-consistent and silently 100× off. Pick one representation at your edge, convert once, and never let a user-facing "amount" field reach an RPC without passing through it.
The bounds, in the same units. A single mint is $100 minimum and $100,000 maximum — 10000 to 10000000 cents. No RPC reports those bounds. getdigidollarstats does not carry them and neither does anything else on the DigiDollar surface, so they are consensus constants you have to hold in your own code while the node remains the only thing that actually enforces them. DigiScope holds them as a literal for exactly that reason — and, because the object around them is assembled entirely from live RPC results, refuses to answer at all rather than serve invented limits when the node cannot be reached:
from backend/src/controllers/digidollar.js
const TIER_LOCK_LABELS = ['1 hour', '30 days', '90 days', '180 days', '1 year', '2 years', '3 years', '5 years', '7 years', '10 years'];
const MINT_PARAMS_TTL_MS = 60_000;
const MINT_PROBE_CENTS = 10000; // $100 — protocol minimum, valid for every tier
// …
/** Pure assembler — exported for tests. estimates = one estimatecollateral
* result per tier 0..9; stats = raw getdigidollarstats (or null). */
export function buildMintParams(estimates, stats) {
const first = estimates[0] || {};
const isStale = !stats || stats.oracle_available !== true;
const marginPct = Number.isFinite(first.collateral_safety_margin_dgb) && first.required_dgb > 0
? (first.collateral_safety_margin_dgb / first.required_dgb) * 100
: null;
// …
system_health: stats?.system_collateral_ratio ?? first.system_health ?? null,
mint_bounds_cents: { min: 10000, max: 10000000 },
wallet_safety_margin_pct: marginPct,
// …
const estimates = await Promise.all(
Array.from({ length: 10 }, (_, t) => rpcClient.call('estimatecollateral', [MINT_PROBE_CENTS, t]))
);
The preconditions. Read getdigidollarstats before you build anything. Two fields decide whether a mint is possible at all:
minting_restricted_reasonmust benone. Anything else is the network telling you why it is refusing, and the answer is to surface that string, not to retry.oracle_availablemust be true. Collateral is priced against the oracle feed; without a usable price there is no ratio to satisfy, and a mint attempted on a stale price should fail before a transaction is built, not after.
Those two are network-wide gates, and they are not the same thing as your own position's collateral ratio. Keep them apart in your head and in your code — Step 2 explains why.
Step 1 — Choose a lock tier
A DigiDollar position is minted into exactly one of ten lock tiers, chosen at mint time and fixed for the life of the position. Tier 0 is one hour; tier 9 is ten years. The trade is the whole design: a longer lock demands less collateral per dollar minted, because the protocol is being asked to carry less price risk per unit of time it cannot be unwound.
from backend/src/services/digidollar-position-indexer.js
* Lock ladder. CANONICAL SOURCE is consensus, via `digibyte-cli help mintdigidollar`:
* lock_tier 0-9 (0=1h, 1=30d, 2=90d, 3=180d, 4=1y, 5=2y, 6=3y, 7=5y, 8=7y, 9=10y)
// …
* ⚠️ TIER 0 IS A REAL 1-HOUR LOCK — 240 blocks — not a "test" tier. It is a
* first-class protocol tier and collateral in it is genuinely locked; someone who
* mints tier 0 and never redeems stays locked exactly like any other tier.
// …
* TIER_BLOCKS is the accurate ladder. TIER_DAYS is day-denominated for the
* `lock_days` INTEGER column, so tier 0 necessarily rounds to 0 there — that 0
* is a storage artifact and must NEVER be read as "no lock". Label from the tier,
* never from lock_days.
// …
export const TIER_BLOCKS = Object.freeze({
0: 240, 1: 172800, 2: 518400, 3: 1036800, 4: 2102400,
5: 4204800, 6: 6307200, 7: 10512000, 8: 14716800, 9: 21024000,
});
Two things in that ladder deserve saying in prose, because both have already been got wrong in shipped software.
Tier 0 is a real lock. Two hundred and forty blocks at roughly fifteen seconds each is one hour, and during that hour the collateral behind a tier-0 position is locked exactly as hard as the collateral behind a ten-year one. It is not a sandbox, not a dry run, and not a "test tier" — it is simply the tier that pays for its short horizon with extreme overcollateralization. A UI that labels it "not a real lock" is telling a user their DGB is available when it is not.
Label from the tier, never from a day count. A day-denominated column rounds tier 0 to 0, and a 0 in a lock_days field is indistinguishable from "no lock" to any code that reads it later. Carry the tier index; derive the label from the tier.
The other end of the ladder matters for a different reason: unlock_height is when collateral becomes eligible for release, not when it is released. Redemption is voluntary. A matured position can sit unredeemed indefinitely, and a great many do. If you are drawing a forward curve of "DGB coming back", it is a ceiling on what could be released, never a schedule of what will be.
Step 2 — Price it with estimatecollateral (a dry run)
estimatecollateral dd_amount lock_tier ( oracle_price_micro_usd ) answers "how much DGB would I have to lock to mint this much, in this tier, right now". It builds nothing, signs nothing and spends nothing — it is the consensus mint math, run as a question. Use it liberally. The optional third argument lets you re-run the same math against a hypothetical price, which is how you model a position before the price you are worried about actually arrives.
The important property is that the ratio is amount-independent: doubling the dollars doubles the required collateral. That is what lets a client probe the ladder once with a fixed amount and reuse the answer for every amount the user might type. DigiScope probes all ten tiers with a fixed MINT_PROBE_CENTS = 10000 ($100) and derives its safety margin from the node's own collateral_safety_margin_dgb, so the ratios are never hardcoded on the client — the numbers a consensus change would actually move arrive with each probe. That is the property worth copying, and it is not the same as "no constants": the mint bounds in the same object are a literal, for the reason given in Before you start. See the excerpt there, whose Promise.all is precisely these ten probes.
Two gates, and they are not the same gate. Conflating them is the most common way to build a mint UI that lies:
- Per-mint, yours. Your position's collateral must satisfy your chosen tier's required ratio. The consensus expression is in
src/digidollar/validation.cpp:1465—DD amount = (collateral * oracle_price * 100) / (collateral_ratio * COIN)— and it is per-position, per-tier, evaluated against the oracle price at mint time.estimatecollateralis that formula, rearranged for the question you are actually asking. - Network-wide, everybody's. All minting halts while ERR — the Emergency Redemption Ratio — is active, and ERR activates when system collateralization falls below 100% (
validation.cpp:3026-3030, and the test itself at1038-1039:bool errActive = systemCollateral < 100;). This gate has nothing to do with your tier or your collateral. You can be comfortably over-collateralized and still be unable to mint.
system_collateral_ratio from getdigidollarstats is reported state, not the per-mint gate — it is how you observe the second condition, not how you satisfy the first. And the reason the node can be trusted about either is that every full node recomputes the collateral behind every $DD on every block; the ratio is not a number somebody publishes, it is a number everybody derives.
Trap: Do not build a "will this mint succeed?" check out of
system_collateral_ratioalone, and do not build one out of your own ratio alone. Readminting_restricted_reason— the node has already collapsed every network-wide reason into that one field, including ERR. Treat anything other thannoneas a refusal to be displayed verbatim, and re-check it immediately before you build the transaction rather than at page load.
Step 3 — Mint
digibyte-cli -rpcwallet=<wallet> mintdigidollar 10000 4
That is $100.00 — 10000 cents — into tier 4, the one-year lock. The node selects collateral from the wallet, builds the type-1 transaction (collateral at vout 0, a zero-value taproot output carrying the $DD, and the OP_RETURN payload the read guide decodes), signs it and broadcasts it. What comes back is a txid; what you want next is listdigidollarpositions, because the position id is how everything downstream refers to what you just created.
Then leave the fee alone.
Trap — the fee rule, stated once.
MIN_DD_TX_FEE = 10000000(0.1 DGB) lives atsrc/digidollar/txbuilder.cpp:37in DigiByte Core and is applied at line 371 asstd::max(ESTIMATED_TX_VSIZE * params.feeRate / 1000, MIN_DD_TX_FEE). That file is the wallet transaction builder, so 0.1 DGB is a builder floor, not a consensus rule:src/consensus/digidollar_transaction_validation.cppcontains no fee floor at all, only conservation (inputs == outputs + fee). A DD transaction paying less is not consensus-invalid — Core's own builder simply declines to construct one. 35,000,000 sat/kB is not a floor either; it is the default rate, picked so a ~300 vB transaction clears that floor (txbuilder.cpp:150-154: "For 0.1 DGB min fee on ~300 vB tx: need ~33,333,333 sat/kB (we use 35M)"), andValidateFeeRateaccepts the whole band 100,000 – 100,000,000 sat/kB. The unit is sat/kB, even thoughtxbuilder.hcommentsfeeRateas "sat/vB" at lines 46, 59 and 82 —CalculateFeecomputes(vsize * feeRate) / 1000andValidateFeeRate's own comment at line 149 says sat/kB. Core's header is mislabelled; the arithmetic is not. So: omitfee_rateand let the builder pick. On Core ≤ 9.26.4 a plausible-looking100or1000is honoured and is roughly 350,000× too low, and the transaction is rejected; on 9.26.5+ the argument is deprecated and ignored. There is no version of this where supplying it helps.
That trap is worth one more sentence because of where it doesn't come from. Published sources — including comments inside the open-source DigiByte Android wallet — describe the 0.1 DGB figure as a "consensus floor" while citing Core's builder in the same breath. The number is right and the category is wrong, and the category is what determines whether a third-party builder that pays less is producing invalid transactions (it is not) or transactions Core's wallet would never have produced (it is). Read digidollar_transaction_validation.cpp before you repeat anyone on this, including this guide.
Step 4 — Why confirmation arrives in bursts
Your mint is broadcast. Ordinary DGB transactions from the same wallet confirm in a block or two. Yours sits there for an hour. Nothing is wrong.
DigiDollar is its own consensus layer, and a miner needs DD-aware node software to validate a DD transaction and include it in a block template. Taproot capability alone is not sufficient — a node can relay the transaction shape perfectly well and still decline to build a block containing it, because it cannot evaluate the rules that make it valid. Until most pools upgrade, DD transactions accumulate in DD-aware mempools and confirm whenever a DD-aware pool happens to find a block. The result is bursty: long quiet stretches, then several DD transactions confirming close together.
This is the same shape SegWit had in its early months, where non-upgraded pools simply omitted the new transaction type from their templates, and it resolves the same way — by adoption, not by intervention. The practical consequences for anything you build:
- Do not treat elapsed time as failure. A DD transaction that has not confirmed in an hour is not stuck, not underpaid, and not eligible for replacement. Bumping the fee does not buy you a miner that can validate it.
- Do not compare against a DGB fee estimator. The confirmation delay is not a fee-market signal, so a "your fee is too low" warning derived from ordinary mempool statistics is simply wrong here.
- Say so in the UI. "Waiting for a DigiDollar-aware miner" is honest and calming; a spinner that has looked identical for fifty minutes is neither.
Step 5 — Addresses and sending
getdigidollaraddress ( "label" ) returns a fresh DigiDollar receive address from your wallet; the optional label is the wallet's, not the chain's. validateddaddress "address" checks one.
Trap: The two address validators mutually reject. Generic
validateaddressrejects aDD…address, andvalidateddaddressrejects an ordinary DGB address. Neither returns a soft "not mine" — each says the input is invalid for it. So a client that runs one validator over a mixed address book will mark half of it broken, and a form that validates a paste withvalidateaddressbefore handing it tosenddigidollarwill refuse every correct address a user can give it. Route by address family first, then validate with the matching call.
Sending is one call:
digibyte-cli -rpcwallet=<wallet> senddigidollar "<dd_address>" 2500
senddigidollar "address" amount ( "comment" fee_rate [inputs] ) — and 2500 there is $25.00, because the amount is cents like everywhere else. The optional inputs array is for callers who need to control coin selection; the fee_rate argument carries exactly the caveat from Step 3, which is to leave it out.
The wallet needs DGB as well as $DD. Fees are paid in DGB. A wallet holding a large $DD balance and no spendable DGB cannot send $DD at all, and the error it gets back is about funds, which reads — wrongly — as "you don't have the dollars". Check both balances before you offer a send button, and say which one is missing.
Trap: Immediately after a send, a DD balance read can come back empty for a moment while the wallet catches up with its own change. It is transient, and it is not a lost balance. The dangerous reaction is automated: a client that reads zero and retries the send, or that reads zero and reports the funds gone, does real damage from a reading that would have corrected itself. Re-read after the transaction appears in
listdigidollartxs, and never fire a second write off a balance read taken seconds after the first.
Step 6 — Redeem
A position cannot be redeemed before it matures. can_redeem stays false until the chain height passes that position's unlock_height; listdigidollarpositions reports both, along with a blocks_remaining countdown. Mint into tier 4 in Step 3 and there is nothing to redeem for a year, and no argument to redeemdigidollar shortens that. Note that this is the one failure the all-or-nothing rule below does not cover: when a partial redeem is rejected the recovery is to redeem the whole position, but when the position is immature the whole position is locked too, and the only move is to wait.
Then start with the dry run. Always.
digibyte-cli -rpcwallet=<wallet> getredemptioninfo "<position_id>"
getredemptioninfo "position_id" ( dd_amount ) tells you what a redemption would do before you attempt it — what it would consume, what it would release, and whether it can happen at all. It is the redemption equivalent of estimatecollateral, it costs nothing, and it is the single call that turns the trap below from an error message into a decision.
Then, when the answer is what you wanted:
digibyte-cli -rpcwallet=<wallet> redeemdigidollar "<position_id>" <dd_cents>
redeemdigidollar "position_id" dd_amount ( "redemption_address" fee_rate ). The optional address is where the released collateral lands; the fee argument carries the Step 3 caveat.
Trap: Redemption is all-or-nothing. A partial redemption against a position — asking for $200 back out of a $500 position — fails with error
-8. There is no partial-redeem path to find, no flag that enables one, and no sequence of smaller calls that adds up to one. A failed redemption spends nothing: the collateral stays locked and the position is unchanged, so the recovery is to redeem the whole position, not to retry with a smaller number. If your product needs partial exits, the shape that works is several smaller positions minted separately, closed one at a time — decided at mint time, because it cannot be decided later.
Positions are enumerated with listdigidollarpositions ( active_only tier_filter min_amount count skip ). There is no getdigidollarposition singular — it answers -32601 Method not found, and reaching for it is the most common wrong turn on this surface. Filter the list.
Closure is the last thing to get right, and it is not marked. A redemption transaction does not name the position it closes; the link is structural, and the only reliable signal is that the mint's vout 0 — the collateral output — has been spent:
from backend/src/services/digidollar-position-indexer.js
* ⚠️ THE CLOSURE RULE: a position is closed if and only if its COLLATERAL
* output (vout 0) is spent.
// …
export function closedPositionsFromRedeem(redeemTx, mintTxidSet) {
const ins = Array.isArray(redeemTx?.vin) ? redeemTx.vin : [];
const set = mintTxidSet instanceof Set ? mintTxidSet : new Set(mintTxidSet ?? []);
const closed = new Set();
for (const vi of ins) {
if (Number(vi?.vout) !== 0) continue; // vout 0 ONLY — collateral, not the DD token
const txid = vi?.txid;
if (txid && set.has(txid)) closed.add(txid);
}
return [...closed];
The vout 0 restriction is load-bearing, and DigiScope learned it from chain rather than from a spec. $DD tokens are fungible: a redemption can be paid with $DD that some other position minted, so the redeeming transaction may well spend a vout 1 belonging to a position whose collateral is still locked. Treat a vout 1 spend as closure and you delete a live position from your own accounting — its collateral vanishes from your totals and from every forward bucket, while remaining perfectly locked on chain. One redemption may also close several positions at once, which is why the function returns a list.
Verify it
Nothing below requires a real mint. estimatecollateral and getredemptioninfo are dry runs, the node will decode DigiDollar transactions for you, and the two validators can be exercised on addresses you do not own.
Read the two preconditions with your own eyes, and confirm your client refuses to offer a mint when either says no:
digibyte-cli getdigidollarstatsExpected:
minting_restricted_reasonandoracle_available. Point your client at a captured copy withminting_restricted_reasonset to anything other thannoneand confirm the mint path is closed and the reason is displayed verbatim, not replaced with a generic error.Prove the cents convention without spending anything. Price the same tier at two amounts that differ by exactly 100×:
digibyte-cli estimatecollateral 10000 4 digibyte-cli estimatecollateral 1000000 4Expected: the second
required_dgbis one hundred times the first.10000is $100 and1000000is $10,000 — the ratio is amount-independent, so the collateral scales linearly with the cents you passed. If your client's "amount" field produces the same estimate for500and500.00, it is not converting and it will one day mint a hundredth of what a user asked for.Walk the whole ladder in one pass and confirm the trade is real:
for t in 0 1 2 3 4 5 6 7 8 9; do digibyte-cli estimatecollateral 10000 $t; doneExpected: ten answers for the same $100, with required collateral falling as the tier rises. Confirm your UI labels tier 0 as a one-hour lock — not a test, not "no lock" — and that it derives that label from the tier index rather than from any day count.
Confirm the two validators mutually reject, so your address routing never depends on one of them being lenient:
digibyte-cli validateddaddress <dd_address> digibyte-cli validateaddress <dd_address> digibyte-cli validateddaddress <dgb_address>Expected: the first succeeds; the second and third do not. Then confirm your form routes by address family before validating.
Let the node decode a DigiDollar transaction for you. With
txindex=1this works on any DD transaction on chain, not just your own:digibyte-cli getrawtransaction <txid> 2 | python3 -c 'import json,sys; t=json.load(sys.stdin); print(t.get("digidollar"))'Expected: a
digidollarobject whosetype_idis1(mint),2(transfer) or3(redeem).Enumerate positions and dry-run a redemption. An empty list is a valid answer and your client must render it as one:
digibyte-cli -rpcwallet=<wallet> listdigidollarpositions digibyte-cli -rpcwallet=<wallet> getredemptioninfo "<position_id>"Expected: a list (possibly empty), and — for any position you do hold — a redemption preview you never had to sign for. Confirm
getdigidollarpositionis not what you reached for; it does not exist.Confirm your closure detection reads
vout 0. Take any redeem transaction and check which input index it spends against the mint you are tracking. Avout 1spend is a $DD payment, not a closure, and code that treats it as one will quietly write off collateral that is still locked.
Check 5 above is the whole of DigiScope's on-chain proof that a pasted txid is a real DigiDollar transaction. It does not re-implement the decode — it asks the node, and then it distinguishes the three ways the answer can be no:
from backend/src/services/quest-service.js
let tx;
try { tx = await rpcClient.getRawTransaction(txid, 2); }
catch { unclaim(); return { error: 'We couldn\'t find that transaction on-chain yet. Wait for it to confirm, then try again.' }; }
if (!tx || (tx.confirmations ?? 0) < 1) { unclaim(); return { error: 'That transaction isn\'t confirmed yet. Wait for one confirmation and try again.' }; }
const typeId = tx.digidollar?.type_id;
if (![1, 2, 3].includes(typeId)) { unclaim(); return { error: 'That\'s a valid DigiByte transaction, but not a DigiDollar mint, transfer, or redeem.' }; }
Not-found, then unconfirmed, then not-DigiDollar: three different messages, because they are three different things for the reader to do next. A single "invalid transaction" would send someone hunting for a typo when all they had to do was wait — and, given Step 4, waiting is the likeliest correct answer.
Check: Try to make your own client mint the wrong amount. Type
500into whatever field a user types into and follow the value all the way to the RPC boundary. If what arrives is500, you are about to mint $5.00 when the user meant $500 — and the position that results will be perfectly valid, perfectly confirmed, and perfectly wrong. Then do the same for a send.
Traps
- A bare integer is cents; a decimal point makes it dollars.
mintdigidollar 500is $5.00, not $500.getdigidollarbalanceandlistdigidollartxsreport cents on the way back, so an unconverted client is self-consistently 100× off. - Per-mint bounds are $100 – $100,000, i.e.
10000–10000000cents. No RPC reports them, so every client carries them as constants and only the node enforces them — surface its rejection verbatim rather than inventing your own message, and re-check the constants against consensus when you upgrade. - Two independent gates. Your position must satisfy your tier's ratio (
validation.cpp:1465); separately, all minting halts while ERR is active, which is when system collateralization falls below 100% (validation.cpp:3026-3030,1038-1039).minting_restricted_reasonis where the node reports the second. system_collateral_ratiois reported state, not the per-mint gate. It tells you about the network, not about whether your mint clears.- Tier 0 is a real one-hour lock (240 blocks). Not a test tier. Collateral in it is locked exactly like any other tier's.
- Label the tier from the tier. A day-denominated field rounds tier 0 to
0, which reads identically to "no lock". unlock_heightmeans eligible, not released. Redemption is voluntary; matured positions sit unredeemed indefinitely. A forward curve is a ceiling, never a schedule.- 0.1 DGB is a builder floor, not a consensus rule. It lives in
txbuilder.cpp:37;digidollar_transaction_validation.cpphas no fee floor, only conservation. Do not repeat "consensus floor", however authoritative the source. - 35,000,000 sat/kB is a default rate, not a minimum. The accepted band is 100,000 – 100,000,000 sat/kB, and the unit is sat/kB despite
txbuilder.hcommenting "sat/vB" at lines 46, 59 and 82. - Omit
fee_rate. On Core ≤ 9.26.4 a plausible100or1000is honoured and ~350,000× too low; on 9.26.5+ the argument is deprecated and ignored. - Fees are paid in DGB, not $DD. A wallet with plenty of $DD and no spendable DGB cannot send.
- Slow confirmation is expected, not stuck. A miner needs DD-aware software to include a DD transaction; taproot capability alone is not enough. Do not bump fees, do not warn about the fee market, and do not time out.
validateaddressandvalidateddaddressmutually reject. Route by address family before you validate.- A DD balance can read empty transiently right after a send. Never retry a write, or declare funds lost, off that reading.
- An immature position cannot be redeemed at all.
can_redeemisfalseuntil chain height passesunlock_height. No argument shortens the lock, and "redeem the whole position" is not a recovery when the whole position is still locked — check maturity before you diagnose anything else. - Redemption is all-or-nothing; a partial redeem fails with error
-8. A failed redemption spends nothing. Plan partial exits as several smaller positions at mint time. getredemptioninfobeforeredeemdigidollar, every time. It is free and it is the only preview you get.- There is no
getdigidollarposition(singular). It answers-32601. Uselistdigidollarpositions. - A position is closed only when its mint's
vout 0is spent. $DD tokens are fungible, so a redeem may spend avout 1belonging to a position that is still fully locked.
Where DigiScope's implementation differs from the ideal
This is the one guide in the set where DigiScope's own code is not the worked example for the main action, and the honest reason is simple: DigiScope has no mint path. No backend code calls mintdigidollar, senddigidollar or redeemdigidollar — every reference to those methods in this repository is a comment or a test citing digibyte-cli help mintdigidollar as the canonical source for the lock ladder. The site's MintCalculator is exactly what its name says: its only network call is a read-only mint-params fetch built on estimatecollateral. There is no submit button, no signing, and no custody of anyone's position.
So what you are getting here is protocol instruction, not an implementation to copy. That is a real limitation and worth naming: the read guide could hand you DigiScope's decoder and let you diff your own against it, and this one cannot do the equivalent for a mint. The DigiScope code it can point at sits on either side of the write — buildMintParams, which turns the node's own estimatecollateral math into a client-safe shape, and verifyDigiDollarTx, which proves a txid really is a DigiDollar transaction by asking the node rather than re-deriving it. Between those two, you are on the RPCs directly.
The rest:
- The position indexer is DigiScope's, and it is a reader.
closedPositionsFromRedeemabove exists because DigiScope watches other people's positions to answer "how much DGB is locked, by term" — a question the node's own RPCs cannot answer, sincegetdigidollarstatsreturns totals andlistdigidollarpositionsis wallet-scoped. It is a good model for tracking positions and no model at all for creating them. - Nothing on the site holds a user's DigiDollar keys. If the operator mints, they do it by hand from a node wallet on the server. That is an operational act, not a product surface, and it should not be read as a pattern.
estimatecollateralis probed with a fixed $100 and cached for 60 seconds. That is a cost decision about a public endpoint, not a protocol property; a client that needs the ratio at the instant of signing should probe for itself.- Almost every protocol number on the DigiScope side is read from the node — the collateral ratios, the DCA multipliers, the safety margin — deliberately, so a consensus change arrives without a deploy. There are two hardcoded exceptions, and both are visible in the excerpts above. The mint bounds are a literal in
digidollar.js:305, because no RPC reports them; the tier ladder is a frozen table because it is structural rather than parametric. Both are annotated with their canonical source, which is the most that can be done for a number the node will not hand you — but neither would notice a consensus change on its own, and that is the cost.
Previous: the read side, in Read DigiDollar state.