Monaco Protocol SDK v1.0.50
Two changes need a decision before you upgrade. Registering a sweeper deposit address is now authenticated and takes no identity at all —clientId and userAddress are gone from the request, and the session supplies both — with a new optional depositTarget that can point swept deposits at margin collateral instead of the spot wallet. And funding an isolated risk bucket that holds an open position now commits the money to that position’s stored margin by default, which is the right behavior for an Adjust Margin gesture and the wrong one for a funding loop: those must now pass applyToPositionMargin: false. transferCollateralToMarginAccount is deprecated in the same move, @0xmonaco/contracts gains the deposit-address encoder, and the account channel’s marginRatio documents one more state it can saturate in.
Breaking
Sweeper registration is authenticated, and carries no identity
sdk.sweeper.register (POST /api/v1/sweeper/register; gRPC SweeperService.Register; Rust REST register_deposit_address) now requires a session, and both clientId and userAddress have been removed from the request. The application is the one the session was established under and the credited address is the session’s own, so the call can only ever register the caller’s own address under their own application. The argument is now optional and carries nothing but the target.
400 rather than silently ignored — answering it from the session would hand the caller a deposit address for a different user or application than their own body names. Unauthenticated calls answer 401, and a session whose application is no longer active answers 403. The endpoint also declares 500 for a server-side failure and 503 for any point at which the sweeper could not complete the registration — several unrelated conditions share it. Treat a 503 as an unknown outcome rather than a negative one: the registration is stored before the reply is composed, so a failure reported after that write is indistinguishable from one reported before it, and the status alone never tells you which happened. Re-register rather than assuming either way — registration is idempotent, and the retry answers registered or already_registered according to whether the first attempt had landed. Both are success and both carry the same address, so accept either rather than branching on which one you got.
The per-client (IP-keyed) rate limit is replaced by a service-wide admission limit on new registrations, which answers 429. A per-caller bound handed every fresh session its own allowance; registrations create durable state, so the bound is now global. Re-registering an existing address is never limited — it creates nothing — and a refused call stores nothing, so 429 is safe to retry. The REST body’s retryAfter (and gRPC’s RetryInfo detail) is the limiter’s own remaining wait in seconds, not a fixed window: honor it rather than assuming one.
The new optional depositTarget ("spot", the default, or "margin"; SPOT/MARGIN on the wire) asks for the parent margin account’s collateral, skipping the separate transferCollateralToParentMarginAccount step. The target is part of the address derivation, so "spot" and "margin" return two different deposit addresses for the same application and user, each carrying its own request; a "spot" address is never credited to margin. Every address derived before this change is unaffected — "spot" reproduces the previous bytes exactly. An unrecognized value is rejected (the TypeScript SDK throws before sending; the API answers 400) rather than falling back to spot, which would return an address pointed at a ledger the caller did not ask for.
"margin" is a routing request rather than a guarantee: a deposit the matching engine cannot route to collateral — an unsupported collateral asset, or a margin account that fails validation — is credited to the spot wallet instead, so funds are never stranded. Read the balance back rather than treating the address as proof of where the credit landed. See Vault and Registered Deposit Addresses.
Changed
Funding an isolated risk bucket commits to the position’s margin by default
transferCollateralToRiskBucket (POST /api/v1/margin/risk-buckets/collateral/transfer-in; gRPC MarginAccountsService.TransferCollateralToRiskBucket; the transfer_collateral_to_risk_bucket MCP tool) takes a new optional applyToPositionMargin, and its default is a behavior change.
- Omitted or
true— the human “Adjust Margin” gesture: the amount is committed to the isolated bucket’s sole open position’s stored margin. Effective leverage drops and stays down, initial margin required rises by the amount, and free collateral is unchanged, so new orders cannot spend it. false— the previous behavior: a plain allocation. Free collateral rises and the next order can spend it.
false. Under the default each top-up converts into requirement instead of headroom, and the loop never converges.
A flat isolated bucket has no position to commit to, so both values are a plain allocation there. The cross form has no per-position margin, and the two contracts differ by surface: raw REST and gRPC reject only an explicit true, while the TypeScript request type (never) and the MCP tool schema reject the field however it is set — omit it entirely on a cross transfer rather than passing false. Either behavior improves the liquidation cushion identically — the breach check is bucket-equity based.
The response gains an optional positionMarginCredited reporting what actually happened: true — the credit landed; false — a credit was requested (or defaulted) and degraded to a plain allocation because the bucket’s persisted position row was stale (a close/reopen race), so re-issue the Adjust Margin once the new row lands if the commitment still matters; absent (never null) on plain allocations, parent deposits, and releases. See Margin Accounts and Perps Collateral.
Deprecated
transferCollateralToMarginAccount
transferCollateralToMarginAccount (POST /api/v1/margin/accounts/{margin_account_id}/collateral/transfer-in; gRPC MarginAccountsService.TransferCollateralToMarginAccount) is marked deprecated — a JSDoc @deprecated in TypeScript, deprecated: true in the OpenAPI operation. It overloads two unrelated operations behind one route: with tradingPairId it allocates into that pair’s isolated risk bucket, without it it performs a wallet-debiting deposit to the parent margin account.
Use transferCollateralToRiskBucket for bucket funding — which, as of this release, applies the same position-margin credit — and transferCollateralToParentMarginAccount for wallet-to-margin deposits. The endpoint keeps working and no removal date is scheduled.
Added
The deposit-address encoder is exported from @0xmonaco/contracts
predictSweeperAddress accepts an optional depositTarget ("spot", the default, or "margin"), and encodeDepositApplicationData(clientId, target) plus the DepositTarget type are now exported from @0xmonaco/contracts. Omitting the target reproduces the previous derivation byte-for-byte, so existing calls are unaffected; pass the same target the address was registered with, or the prediction points at the other ledger’s address.
encodeDepositApplicationData is the canonical encoder for a deposit’s on-chain applicationData — the bytes a sweeper address is a hash of, and the counterpart of the backend’s own encoder. @0xmonaco/core consumes it internally rather than keeping a second copy, and @0xmonaco/types re-exports DepositTarget from the same place: the value selects both a direct deposit’s on-chain routing hint and a sweeper address’s derivation, and two definitions could disagree about the bytes an address is derived from. The importable path is @0xmonaco/contracts — core’s package root does not re-export it. DepositTarget itself is untouched: it changed packages, not shape, so its name and its two members are the same and it is still reachable from @0xmonaco/types.
Both predictSweeperAddress and encodeDepositApplicationData throw on a target that is not exactly "spot" or "margin" rather than treating anything unrecognized as spot. The likely mistake is the wire form "MARGIN" copied from the API reference; without the check it would derive a real, fundable spot address while the caller believed it was a margin one, and no server round-trip would reveal it.
Fixed
marginRatio on the account channel saturates on dust equity too
The documented contract of AccountEvent’s marginRatio widens. The maximum-distress sentinel (79228162514264337593543950335) appears whenever a live maintenance requirement makes the ratio impossible to compute or represent — not only for an account still owing one at zero or negative equity, but equally at near-zero positive equity, where the true maintenance / equity exceeds the decimal range. Previously the producer crashed on that state instead of emitting a frame. An account carrying no maintenance requirement is unchanged and still reports 0, whatever its equity: an empty account is not distressed.
No type change, and consumers that already clamp the sentinel for display need no code change: treat any value at or above the liquidation threshold as maximal distress rather than distinguishing the sentinel’s causes. See Account Channel.
Upgrade
1.0.50 too; it runs via npx -y @0xmonaco/mcp-server, so restarting your assistant picks up the new version. Every SDK bump is a patch release. There is no 1.0.49 — that version number was never released.
Two action items. If you call sweeper.register, log in first and drop clientId and userAddress from the body; a partially-migrated body is a 400, not a silent ignore. And if any code path funds an isolated risk bucket to reach a free-collateral level, pass applyToPositionMargin: false — the new default commits the amount to the open position’s margin instead, and a loop that measures free collateral will never converge. Deriving deposit addresses off-chain? Pass the same depositTarget you register with, and import encodeDepositApplicationData from @0xmonaco/contracts.
