Skip to content

fix(swap): correct CACAO 10-decimal scaling in MayaChain provider#594

Merged
gomesalexandre merged 1 commit into
mainfrom
fix/cacao-10-decimals
Jun 5, 2026
Merged

fix(swap): correct CACAO 10-decimal scaling in MayaChain provider#594
gomesalexandre merged 1 commit into
mainfrom
fix/cacao-10-decimals

Conversation

@gomesalexandre

@gomesalexandre gomesalexandre commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

what

MayaChain's native asset CACAO uses 10 decimal places - unlike RUNE/THORChain which uses 8. The Maya /quote/swap API expects CACAO input amounts in 10-dec base units and returns CACAO output amounts in 10-dec base units.

The prior code applied the same 8-decimal "thor-amount" conversion (via toThorChainAmount / fromThorChainAmount) for all Maya assets. This caused two systematic 100x errors when CACAO was involved:

BUG 1 - INPUT: when swapping FROM CACAO, the API received 100x less CACAO than the user intended

  • e.g. user wants to swap 1000 CACAO; API sees 10 CACAO; outputs 10x too little BTC

BUG 2 - OUTPUT: when CACAO is the swap destination, the expected output was inflated 100x

  • e.g. BTC->CACAO quote: API returns 57763399808087 (5776 CACAO at 10-dec); code multiplied by 100 -> 577633 CACAO shown to user

evidence

Verified against live /mayachain/quote/swap 2026-06-01:

Input Amount format Sats BTC output Expected (pool price)
1000 CACAO 10-dec (10000000000000) 172274 sats ~172600 (0.998x - slippage/fees)
1000 CACAO 8-dec (1000000000000, bug path) 16554 sats ~172600 (0.096x - 10x too low)

For CACAO output: API returns 57763399808087 for 0.01 BTC -> CACAO. At 10-dec = 5776 CACAO (matches pool). Prior code's fromThorChainAmount(..., 10) multiplied by 100 -> 577633 CACAO shown (100x inflated).

fix

  • Add isMayaCACAO() helper: returns true when chain == "MayaChain" and address == "" (native asset)
  • For CACAO input: pass the amount directly (10-dec in -> 10-dec API expects, no scaling)
  • For CACAO output: use the raw API value (already in 10-dec native, no scaling)
  • All non-CACAO assets (BTC, ETH, ZEC, DASH, ARB, KUJI) keep the unchanged 8-dec thor-amount path

New mayachain_test.go covers: isMayaCACAO classification, CACAO input pass-through, ETH input still scales to 8-dec, CACAO output pass-through, ETH output still scales from 8-dec.

affected swap paths

BTC->CACAO, ETH->CACAO, ARB->CACAO, ZEC->CACAO, DASH->CACAO, CACAO->BTC, CACAO->ETH, etc.

receipts

no user-visible UI surface - receipt is go build + go vet proof (sdk/swap test binary hits a pre-existing sonic/Go1.26 linker incompatibility in this repo, unrelated to this PR):

$ go build ./sdk/swap/...
(clean - no output)

$ go vet ./sdk/swap/...
(clean - no output)

CI test run covers the new TestMayaCACAODecimalScaling cases. Live API proof above confirms the 10-dec path produces the correct 172274 sats output vs 16554 sats on the bug path.

CACAO (MayaChain's native asset) uses 10 decimal places, not 8 like
RUNE/THORChain. The Maya /quote/swap API expects CACAO input amounts in
10-dec base units and returns CACAO output amounts in 10-dec base units.

The prior code applied the same 8-decimal "thor-amount" conversion for
all Maya assets, causing two systematic errors when CACAO was involved:

  BUG 1 (INPUT): toThorChainAmount() divided CACAO by 10^2 before
  sending to the API. Maya's API received 100x less CACAO than the user
  intended → quotes showed 100x too little swap output.

  BUG 2 (OUTPUT): fromThorChainAmount() multiplied the API's CACAO
  return value by 10^2, treating the 10-dec native value as 8-dec.
  → Quotes showed 100x too many CACAO as expected output for
  non-CACAO→CACAO swaps (e.g. BTC→CACAO, ETH→CACAO).

Verified empirically 2026-06-01: querying 1000 CACAO → BTC with
amount=10000000000000 (10-dec) yields 172274 sats, matching the pool
ratio (1 BTC = 579289 CACAO, ~172600 sats expected). Querying with
amount=1000000000000 (8-dec, bug path) yields 16554 sats — 10x low.

Fix: add isMayaCACAO() helper + skip scaling for CACAO input/output.
All non-CACAO assets (BTC, ETH, ZEC, DASH, …) keep the unchanged 8-dec
thor-amount path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@gomesalexandre, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 33 minutes and 32 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8f8cf4f4-d456-4efb-b313-0dc72772b479

📥 Commits

Reviewing files that changed from the base of the PR and between 43f06c5 and d967993.

📒 Files selected for processing (2)
  • sdk/swap/mayachain.go
  • sdk/swap/mayachain_test.go
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/cacao-10-decimals

Comment @coderabbitai help to get the list of available commands and usage tips.

@gomesalexandre

Copy link
Copy Markdown
Contributor Author

@jpthor heads up - this is a 100x fund-safety bug in the recipes Go SDK affecting every Maya swap quote. CACAO uses 10 decimals (not 8 like RUNE/THORChain) but toThorChainAmount/fromThorChainAmount hardcode 8 decimals → INPUT under-quotes by 100x (user gets ~10x too little) + OUTPUT inflates by 100x (quote looks too good then fails). proof via direct mayanode.mayachain.info API at both 8-dec and 10-dec input. fix isolates the CACAO-decimal path via isMayaCACAO() helper. unit tests added. recipes isn't my normal review scope per the team conventions so flagging you to triage.

@gomesalexandre
gomesalexandre requested a review from NeOMakinG June 2, 2026 08:33

@NeOMakinG NeOMakinG left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Triple-lane review — APPROVE

Verdict: APPROVE. Fix is correct, no in-band change needed.

Lane summary

Lane Status Findings
Claude (correctness + fund-safety) Complete 1 nit (mayaChainDecimals constant currently inert)
GLM glm-4.6 Complete 4 findings — all false-positives after source verification
Codex gpt-5.4 Unavailable (TTY non-interactive + disk transient) Adversarial pass done inline by Claude

Core correctness

  • isMayaCACAO(a Asset) uses a.Chain == "MayaChain" && a.Address == "". Internally consistent with formatAsset() which uses the same Address == "" guard to emit CHAIN.SYMBOL vs CHAIN.SYMBOL-ADDRESS. No asset that passes isMayaCACAO would be routed to the API under a different format.
  • CACAO input path: new(big.Int).Set(req.Amount) — clean copy passed to API. 10-dec in → 10-dec to API. Correct.
  • CACAO output path: expectedOutput = expectedOutputMaya — raw API string returned as-is. No phantom multiply-by-100. Correct.
  • Non-CACAO paths: toThorChainAmount / fromThorChainAmount called identically to pre-fix behavior. Verified thorchain.go:36-66 — simple big.Int divide/multiply with no edge cases.

Adversarial sweep

  • Negative req.AmountmayaAmount.String() emits "-N" → Maya API rejects → fail-closed.
  • Nil req.Amount → pre-existing panic in BOTH old and new paths (no regression).
  • {MayaChain, FOO, ""} false-positive for isMayaCACAOformatAsset emits MAYA.FOO → Maya API rejects unknown asset → no fund movement.

Build / vet

go build ./sdk/swap/... exit 0. go vet ./sdk/swap/... exit 0. Test binary link fails only due to pre-existing bytedance/sonic / Go 1.26 linker incompatibility — documented in PR, unrelated to this change.

GLM triage

All 4 findings false-positive: (1) sentinel fragility — consistent with the repo's formatAsset convention; (2) zero/overflow — big.Int has no overflow, zero → API error (fail-closed); (3) unverified fromThorChainAmount — verified above; (4) no API version guard — out-of-scope.

Non-blocking nit

mayaChainDecimals = 10 at mayachain.go:36 is currently inert (declared but never referenced in arithmetic). Consider wiring it into the test arithmetic (e.g. new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(mayaChainDecimals)), nil) in CACAO_input_no_scaling) so the constant becomes load-bearing and future decimal-change regressions don't silently pass.

@gomesalexandre
gomesalexandre merged commit 1e07672 into main Jun 5, 2026
3 checks passed
@gomesalexandre
gomesalexandre deleted the fix/cacao-10-decimals branch June 5, 2026 06:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants