Skip to content

feat: add swapkit proof of concept as a DEX#1489

Merged
HashEngineering merged 23 commits into
feat/dash-dexfrom
feat/swapkit-poc
Jun 16, 2026
Merged

feat: add swapkit proof of concept as a DEX#1489
HashEngineering merged 23 commits into
feat/dash-dexfrom
feat/swapkit-poc

Conversation

@HashEngineering

@HashEngineering HashEngineering commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Issue being fixed or feature implemented

Related PR's and Dependencies

Screenshots / Videos

How Has This Been Tested?

  • QA (Mobile Team)

Checklist:

  • I have performed a self-review of my own code and added comments where necessary
  • I have added or updated relevant unit/integration/functional/e2e tests

Summary by CodeRabbit

  • New Features

    • Added SwapKit as an alternative cross-chain swap provider alongside Maya
    • Expanded blockchain support: Cardano, NEAR, Solana, Starknet, Sui, TON, TRON, XRP, plus EVM Layer 2s (Base, Optimism, Avalanche, Polygon, BSC, etc.)
    • Users can now switch between swap backends
  • Bug Fixes

    • Improved error handling for low-amount swap scenarios
  • Documentation

    • Added SwapKit protocol and integration guides

@HashEngineering HashEngineering self-assigned this Jun 2, 2026
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR integrates SwapKit as an alternative cross-chain swap backend alongside the existing Maya provider, with support for 20+ new blockchains and 100+ new tokens. It introduces a SwapProvider abstraction, implements complete SwapKit HTTP integration, refactors the existing Maya API to implement the interface, and updates the UI layer to support runtime backend switching.

Changes

SwapKit Backend Integration and Abstraction

Layer / File(s) Summary
SwapProvider abstraction and documentation
integrations/maya/api/SwapProvider.kt, integrations/maya/SWAPKIT_PROTOCOL.md, .claude/agents/update-swapkit-currencies.md
New SwapProvider interface defines backend-agnostic contract for swap operations including pool observation, quoting, swap execution, and pricing. SWAPKIT_PROTOCOL.md documents API endpoints, request/response schemas, and DASH-specific considerations. Agent guide specifies currency sync procedures from SwapKit endpoint discovery.
SwapKit HTTP layer: endpoint, models, auth, and web wrapper
integrations/maya/swapkit/SwapKitEndpoint.kt, integrations/maya/swapkit/model/SwapKitModels.kt, integrations/maya/swapkit/SwapKitAuthInterceptor.kt, integrations/maya/swapkit/SwapKitWebApi.kt
SwapKitEndpoint (Retrofit) exposes /tokens, /swapTo, /v3/quote, /v3/swap, /price methods. SwapKitModels.kt provides complete request/response DTOs. SwapKitAuthInterceptor injects x-api-key header. SwapKitWebApi centralizes error-handling with safe defaults on failures.
SwapKitApiAggregator implementing SwapProvider
integrations/maya/swapkit/SwapKitApiAggregator.kt
Full SwapProvider implementation orchestrating SwapKit calls with 30s pool refresh cadence, USD price caching, fee-in-DASH conversion, and quote-to-Maya-model mapping. getSwapInfo sources DASH address from wallet unspents, calls SwapKit endpoints, and returns populated SwapTradeUIModel. commitSwapTransaction refreshes quotes and delegates transaction sending to blockchain API.
DispatchingSwapProvider for backend selection
integrations/maya/api/DispatchingSwapProvider.kt, integrations/maya/utils/SwapBackend.kt, integrations/maya/utils/MayaConfig.kt
Singleton routing provider selecting between Maya and SwapKit backends from persisted SWAP_BACKEND preference. Supports runtime switching via setBackend() with immediate in-memory update and async persistence. Falls back to MAYA when SwapKit lacks API key.
MayaApiAggregator as SwapProvider
integrations/maya/api/MayaApi.kt
MayaApiAggregator now implements SwapProvider alongside MayaApi for backwards compatibility. Adds getSwapInfo, commitSwapTransaction, getUserAccounts, and applyPoolPrices (computing fiat prices from USDT/USDC stable pools via BigDecimal).
MayaBlockchainApi transaction refactoring
integrations/maya/api/MayaBlockchainApi.kt
Extracts transaction construction/signing into buildAndSendSwapTx method handling MAYAChain output ordering, fee management, and maximum-trade memo outputs. commitSwapTransaction now refetches quotes and delegates to extracted method.
DI wiring and build config
integrations/maya/di/MayaModule.kt, integrations/maya/build.gradle, integrations/maya/proguard-rules.pro
MayaModule provides SwapKitEndpoint with OkHttp auth interceptor and debug logging. Binds DispatchingSwapProvider as singleton SwapProvider. build.gradle loads service.properties and defines SWAPKIT_API_KEY BuildConfig field. proguard-rules.pro preserves Gson models from both maya.model and maya.swapkit.model packages.
Error handling and model updates
integrations/maya/model/SwapQuote.kt, integrations/maya/model/SwapTradeResponse.kt, integrations/maya/model/MayaErrorResponse.kt
SwapQuote timing fields widened from Int to Double. SwapTradeUIModel adds routeName and availableRoutes. Error mapping aligns SwapKit noRoutesFound with existing AMOUNT_TOO_LOW handling.

Multi-Chain Currency and Payment Parser Expansion

Layer / File(s) Summary
New currency types and payment parsers
integrations/maya/payments/MayaCryptoCurrency.kt, integrations/maya/payments/parsers/*
MayaCryptoCurrency adds MayaCacaoCryptoCurrency, 9 EVM L2/sidechain types, 3 BTC-family types, 8 other L1 types (Cardano, Solana, Near, Tron, XRP, Ton, Sui, Starknet), and token wrapper classes with memo aliases. Introduces 15 new address/payment intent parser files implementing chain-specific validation patterns.
Currency catalog and string resources
integrations/maya/payments/MayaCryptoCurrency.kt, integrations/maya/res/values/strings-maya.xml
MayaCurrencyList registers new currencies and expands Ethereum/Arbitrum token catalogs with 20+ tokens. strings-maya.xml adds SwapKit service name and 200+ new cryptocurrency string resources for all new chains.

UI Layer Refactoring to SwapProvider Abstraction

Layer / File(s) Summary
ViewModel refactoring
integrations/maya/ui/MayaViewModel.kt, integrations/maya/ui/MayaConversionPreviewViewModel.kt, integrations/maya/ui/MayaConvertCryptoViewModel.kt, integrations/maya/ui/.../*ViewModel.kt
ViewModels inject SwapProvider instead of direct Maya APIs. MayaViewModel exposes activeSwapBackend, routes pool/inbound-address operations through abstraction, adds isTradingActive(). Others switch quote/swap/account calls from MayaWebApi to SwapProvider.
Fragment and Screen UI updates
integrations/maya/ui/MayaPortalFragment.kt, integrations/maya/ui/MayaPortalScreen.kt, integrations/maya/ui/MayaConversionPreviewFragment.kt, integrations/maya/ui/MayaConvertCryptoFragment.kt, integrations/maya/ui/dialogs/MayaResultDialog.kt
MayaPortalScreen becomes backend-aware, selecting provider name/logo/background from activeSwapBackend. MayaConversionPreviewFragment displays route details. MayaConvertCryptoFragment uses isTradingActive(), switches to vault address, adds showAmountTooLowBanner() for amount errors. MayaResultDialog applies window insets.
Service integration
wallet/src/de/schildbach/wallet/data/BuyAndSellDashServicesModel.kt, wallet/src/de/schildbach/wallet/ui/buy_sell/*
Main wallet Buy/Sell service adds SWAPKIT option with resource-backed icon. BuyAndSellViewModel injects DispatchingSwapProvider and implements setSwapBackend(). BuyAndSellScreen renders SwapKit service card. BuyAndSellIntegrationsFragment calls backend selection before navigation.
UI resources
integrations/maya/res/drawable/ic_swapkit_logo.xml, integrations/maya/res/layout/content_conversion_preview_maya.xml
Adds SwapKit logo drawable and updates conversion preview layout to show order route information.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • dashpay/dash-wallet#1484: Updates to pool price computation (USDT/USDC stable-pool based fiat conversion via BigDecimal) that underpins the new applyPoolPrices logic in this PR.
  • dashpay/dash-wallet#1468: Related Maya integration currency and UI updates in the same integrations/maya directory.

Suggested reviewers

  • abaranouski
  • andreika-baranouski

🐰 SwapKit's Hop

A new provider hops into sight,
SwapKit joins Maya, doubling the might!
From Cardano to Solana's chain so bright,
Twenty new kingdoms now trade in flight.
Backend dispatch makes the choice just right! 🎯

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/swapkit-poc

@HashEngineering HashEngineering changed the title Feat/swapkit poc feat: add swapkit proof of concept as a DEX Jun 2, 2026
@HashEngineering

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConversionPreviewViewModel.kt (1)

119-124: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Transaction metadata is hardcoded to ServiceName.Maya regardless of active backend.

When SwapKit is the selected backend, the committed swap address is still tagged with ServiceName.Maya, mis-attributing the transaction's service. Consider deriving the service name from the active backend so accounting/categorization reflects the actual provider.

#!/bin/bash
# Check whether ServiceName has a SwapKit entry and how markAddressAsync consumers use it.
rg -nP --type=kotlin -C2 '\bServiceName\b' -g '*ServiceName*'
rg -nP --type=kotlin -C3 'markAddressAsync'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConversionPreviewViewModel.kt`
around lines 119 - 124, The code always passes ServiceName.Maya into
transactionMetadataProvider.markAddressAsync which misattributes swaps when
another backend (e.g., SwapKit) is active; change the call to determine the
service from the active swap backend and pass the appropriate ServiceName enum
(e.g., ServiceName.SwapKit when SwapKit is active) — locate the call to
transactionMetadataProvider.markAddressAsync and replace the hardcoded
ServiceName.Maya with a derived value from the active backend provider or the
swap model (e.g., a method or property on swapTradeUIModel or a central backend
selector) and ensure the ServiceName enum includes the corresponding entry for
that backend before calling markAddressAsync.
integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConvertCryptoFragment.kt (1)

138-157: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Preserve halted-state logic for DASH trading; re-check vault address routing (funds risk)

  • isTradingActive() still derives its value from the DASH inbound object: it finds the chain == "DASH" inbound address and returns dashInbound.halted != true, preserving the prior halted-state safety check (lines 138-149).
  • Line 162: the swap recipient changed from dashInbound.address to swapTrade.vaultAddress. Must confirm vaultAddress is the correct destination for the swap transaction; otherwise this could misroute funds.
  • AMOUNT_TOO_LOW inline banner / helper method improvements look good (lines 186-192, 397-402).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConvertCryptoFragment.kt`
around lines 138 - 157, The current change removed or altered the DASH
halted-state safeguard and switched the swap recipient from the DASH inbound
address to swapTrade.vaultAddress; restore/ensure the halted-state check by
keeping the logic in isTradingActive() that finds the inbound with chain ==
"DASH" and returns dashInbound.halted != true (so trading is blocked when
halted), and audit the swap recipient assignment where swapTrade.vaultAddress
replaced dashInbound.address: confirm with product/security whether vaultAddress
is the intended destination for the swap; if not, revert to using
dashInbound.address for the recipient, otherwise add explicit
validation/comments ensuring vaultAddress matches the expected routing for DASH
to avoid misrouting funds.
integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/MayaCryptoCurrency.kt (1)

648-655: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Wrong URI prefix breaks payment intent parsing for ARB.USDC.

Line 652 uses "pepe" as the URI prefix parameter, but this USDC token entry should use "usdc" to match the currency code. This mismatch will cause payment URI parsing to fail for Arbitrum USDC.

🐛 Proposed fix
             MayaEthereumTokenCryptoCurrency(
                 "USDC",
                 "USD Coin",
                 "ARB.USDC-0XAF88D065E77C8CC2239327C5EDB3A432268E5831",
-                EthereumPaymentIntentParser("pepe", "ARB.USDC-0XE5831"),
+                EthereumPaymentIntentParser("usdc", "ARB.USDC-0XE5831"),
                 R.string.cryptocurrency_usdcoin_code,
                 R.string.cryptocurrency_usdcoin_arbitrum_network
             ),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/MayaCryptoCurrency.kt`
around lines 648 - 655, The MayaEthereumTokenCryptoCurrency entry for USDC has
the wrong URI prefix ("pepe") passed into the EthereumPaymentIntentParser which
breaks payment intent parsing; update the constructor call in the USDC token
entry so the EthereumPaymentIntentParser is instantiated with the correct prefix
"usdc" (i.e., replace the "pepe" argument in the
MayaEthereumTokenCryptoCurrency(...) call that creates the ARB.USDC entry) so
the URI prefix matches the currency code and parsing succeeds.
🧹 Nitpick comments (8)
integrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/SwapKitWebApi.kt (1)

79-79: ⚡ Quick win

Consider reusing a single Gson instance.

Creating new Gson() instances in parseErrorBody and parseSwapErrorBody is inefficient. Gson is thread-safe and these parsing methods are called on error paths where performance matters for user experience. Inject or store a single instance at the class level.

♻️ Proposed fix
 open class SwapKitWebApi `@Inject` constructor(
     private val endpoint: SwapKitEndpoint,
-    private val analyticsService: AnalyticsService
+    private val analyticsService: AnalyticsService,
+    private val gson: Gson
 ) {

Then use gson.fromJson(...) instead of Gson().fromJson(...) at lines 79 and 103.

Also applies to: 103-103

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/SwapKitWebApi.kt`
at line 79, Create and reuse a single Gson instance instead of constructing new
ones in parseErrorBody and parseSwapErrorBody: add a class-level gson property
(e.g., in the SwapKitWebApi class) and replace the two occurrences of
Gson().fromJson(...) (used to parse SwapKitQuoteResponse and the swap error)
with gson.fromJson(...); this keeps parsing thread-safe and avoids repeated
instantiation while leaving method signatures and behavior unchanged.
integrations/maya/SWAPKIT_PROTOCOL.md (1)

33-35: 💤 Low value

Add language specifiers to fenced code blocks.

Both code blocks lack language specifiers, which degrades rendering in some Markdown viewers. The first block (line 33) shows a header format example and could use text or http. The second block (line 363) shows an architecture diagram and could use text.

📝 Proposed fixes
-```
+```text
 x-api-key: <YOUR_API_KEY>

```diff
-```
+```text
 SwapKitApi (Interface)
     ↓

Based on learnings from static analysis: markdownlint flagged fenced-code-language violations at these locations.

Also applies to: 363-377

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@integrations/maya/SWAPKIT_PROTOCOL.md` around lines 33 - 35, The fenced code
blocks missing language specifiers should be updated to include a language hint
(e.g., "text" or "http") so Markdown renderers correctly syntax-highlight them:
add a language token after the opening backticks for the block containing
"x-api-key: <YOUR_API_KEY>" and for the block containing the architecture
snippet starting with "SwapKitApi (Interface)". Use "text" for plain
header/diagram blocks or "http" for the API header example as appropriate and
keep the block contents unchanged.
integrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/model/SwapKitModels.kt (3)

37-37: ⚡ Quick win

Consider making identifier nullable instead of defaulting to empty string.

The identifier field is documented as a "Primary key" for API calls, yet it defaults to an empty string when missing from the response. This could mask deserialization failures and lead to runtime errors when the empty string is used in subsequent API calls. A nullable type would force explicit handling of missing identifiers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/model/SwapKitModels.kt`
at line 37, The field declaration currently uses a non-null default (val
identifier: String = "") which can mask missing data; change the property to a
nullable type (val identifier: String?) and remove the empty-string default,
update any code that reads this property (e.g., API call builders,
equality/hash, constructors) to explicitly handle null (throw/return error,
guard before using in requests, or provide a clear fallback), and ensure your
JSON serializer/deserializer mapping for the data class (the class containing
identifier) allows absent keys to produce null rather than injecting an empty
string.

71-71: ⚡ Quick win

Consider making expectedBuyAmount nullable to detect missing quote data.

Defaulting to "0" could mask missing pricing data in quote responses. A null value would make it explicit when the API didn't provide an expected amount, allowing better error handling in the UI.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/model/SwapKitModels.kt`
at line 71, The field expectedBuyAmount currently defaults to "0" which can hide
missing quote data; change its type to nullable (expectedBuyAmount: String? =
null) in the data class (the val expectedBuyAmount declaration) and update any
consumers (parsers, model mappers, UI renderers, and tests) to handle null
explicitly—e.g., treat null as "no quote" and surface an error or empty state;
also ensure JSON deserialization configuration (serializers/adapters) accepts
absent fields as null.

66-66: ⚡ Quick win

Consider making routeId nullable to detect missing route data.

routeId is required for /v3/swap calls (per SWAPKIT_PROTOCOL.md line 209), but defaults to empty string. An empty routeId would cause API failures. Making this field nullable would enable early detection of incomplete quote responses.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/model/SwapKitModels.kt`
at line 66, The routeId property in SwapKitModels.kt is currently declared as a
non-nullable String with a default empty value which masks missing route data;
change the property declaration for routeId to be nullable (e.g., val routeId:
String?) and remove the empty-string default so absent routeId deserializes to
null, then update any construction sites and validation logic (callers that use
routeId and the code that builds /v3/swap requests) to check for null and fail
early with a clear error before making the API call.
integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/SwapProvider.kt (1)

65-68: ⚡ Quick win

Different default values in overloaded methods may cause confusion.

The two getDefaultSwapQuote overloads use different default values (10_0000_0000 vs 1_0000_0000) for the value parameter. This asymmetry could lead to unexpected behavior if a caller assumes consistent defaults. Consider documenting why the values differ or unifying them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/SwapProvider.kt`
around lines 65 - 68, The two overloaded functions getDefaultSwapQuote have
inconsistent default values for the value parameter which can surprise callers;
pick a single canonical default (e.g., DEFAULT_SWAP_VALUE) and use it for both
overloads or explicitly document the reason for the difference. Fix by
introducing a single constant (e.g., DEFAULT_SWAP_VALUE) in the companion/object
visible to SwapProvider, replace both literal defaults in
getDefaultSwapQuote(toAsset: String, value: Long = ...) and
getDefaultSwapQuote(toAsset: String, destinationAddress: String, value: Long =
...) with that constant, and add a brief KDoc on
SwapProvider.getDefaultSwapQuote explaining the chosen default; if you
intentionally need different defaults, change the KDoc of both overloads to
clearly state why they differ instead of keeping asymmetric literals.
integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaViewModel.kt (1)

120-131: 💤 Low value

Observer performs a network read only to log.

This flow calls the suspending exchangeRatesProvider.getExchangeRate(...) and computes rate solely to log.info(...). If it's diagnostic scaffolding, consider removing it; otherwise it adds an extra exchange-rate fetch on every currency change with no functional effect.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaViewModel.kt`
around lines 120 - 131, The onEach block in the walletUIConfig.observe(...) flow
is making an extra suspending call to
exchangeRatesProvider.getExchangeRate(Constants.USD_CURRENCY) purely to compute
and log a derived rate; remove this unnecessary network read or change the flow
to compute the ratio without calling getExchangeRate inside onEach. Either (A)
delete the usdPrice fetch and the log if it was only diagnostic, or (B) compute
both rates via flows (e.g., use combine to observeExchangeRate(...) for the
selected currency and observeExchangeRate(Constants.USD_CURRENCY) once) and then
compute rate from their .rate values so no suspending getExchangeRate call runs
inside onEach; update references to walletUIConfig.observe(...),
exchangeRatesProvider.getExchangeRate, Constants.USD_CURRENCY and
exchangeRate.rate accordingly.
integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/TonPaymentIntentParser.kt (1)

40-68: 🏗️ Heavy lift

Extract the duplicated parse() into a shared base parser.

This parse(input) body is byte-for-byte identical to TronPaymentIntentParser and XrpPaymentIntentParser (only the log strings differ). The three diverge only in the constructor defaults (currency, asset, chain id) and the injected addressParser. Consider a small base class that captures the common URI/address classification flow, leaving subclasses to supply just the AddressParser and defaults. This removes ~30 lines of triplicated logic and ensures future fixes (e.g., URI handling) apply uniformly.

♻️ Sketch of a shared base parser
abstract class SimpleUriAddressPaymentIntentParser(
    currency: String,
    chain: String,
    asset: String,
    shortAsset: String?,
    private val addressParser: AddressParser
) : MayaPaymentIntentParser(currency, chain, asset, shortAsset, null) {
    private val log = LoggerFactory.getLogger(javaClass)

    override suspend fun parse(input: String): PaymentIntent = withContext(Dispatchers.Default) {
        if (input.startsWith("$uriPrefix:") || input.startsWith("${uriPrefix.uppercase()}:")) {
            try {
                return@withContext createPaymentIntent(input.substring(uriPrefix.length + 1))
            } catch (ex: Exception) {
                log.info("got invalid uri: '$input'", ex)
                throw PaymentIntentParserException(ex, ResourceString(R.string.error, listOf(input)))
            }
        } else if (addressParser.exactMatch(input)) {
            try {
                return@withContext createPaymentIntent(input)
            } catch (ex: AddressFormatException) {
                log.info("got invalid address", ex)
                throw PaymentIntentParserException(ex, ResourceString(R.string.error, listOf()))
            }
        }
        log.info("cannot classify '{}' for $chain", input)
        throw PaymentIntentParserException(
            IllegalArgumentException(input),
            ResourceString(R.string.error, listOf(input))
        )
    }
}

TonPaymentIntentParser then becomes a thin subclass passing TonAddressParser() and TON defaults.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/TonPaymentIntentParser.kt`
around lines 40 - 68, The parse() implementation in TonPaymentIntentParser is
identical to TronPaymentIntentParser and XrpPaymentIntentParser; extract the
common logic into a new abstract base class (e.g.,
SimpleUriAddressPaymentIntentParser) that extends MayaPaymentIntentParser and
accepts the AddressParser plus currency/chain/asset defaults, implement the
shared parse() there using uriPrefix, addressParser.exactMatch(),
createPaymentIntent(), and the existing log/error handling, then make
TonPaymentIntentParser/TronPaymentIntentParser/XrpPaymentIntentParser into thin
subclasses that only pass their specific
TonAddressParser/TronAddressParser/XrpAddressParser and default constructor
values. Ensure exception handling (AddressFormatException and generic
Exception), ResourceString usage, and logging calls remain identical to preserve
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@integrations/maya/build.gradle`:
- Around line 29-30: The build is passing
serviceProps.getProperty("SWAPKIT_API_KEY", "\"\"") directly into
buildConfigField("String", "SWAPKIT_API_KEY", swapKitApiKey) which will produce
invalid Java if the property value is unquoted; update the logic around the
swapKitApiKey variable so the string passed to buildConfigField is a properly
quoted Java string literal (e.g., ensure swapKitApiKey is wrapped in double
quotes if it doesn't already start/end with quotes) before calling
buildConfigField, referencing the swapKitApiKey variable and
buildConfigField("String", "SWAPKIT_API_KEY", ...) in
integrations/maya/build.gradle.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/DispatchingSwapProvider.kt`:
- Around line 67-77: The field initializer for activeBackend currently calls
readPersistedBlocking() which uses runBlocking on
config.get(MayaConfig.SWAP_BACKEND) and can block the constructing thread
(risking ANR); instead initialize activeBackend to a safe default
(SwapBackend.MAYA) and remove runBlocking/readPersistedBlocking from the
initializer, then kick off an asynchronous loader in DispatchingSwapProvider
(using the existing persistScope) that calls config.get(MayaConfig.SWAP_BACKEND)
in a coroutine, converts it via SwapBackend.valueOf(...) and effective(...), and
updates the volatile activeBackend when the value arrives; keep persistScope and
effective(...) logic but move blocking work into that coroutine and ensure any
fallback to SwapBackend.MAYA on errors.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/MayaBlockchainApi.kt`:
- Around line 93-230: The buildAndSendSwapTx() flow currently only catches
InsufficientMoneyException and lets other runtime exceptions (from
Address.fromBase58, sendPaymentService.completeTransaction, signTransaction,
sendTransaction, etc.) bubble up; wrap the main try block (the one constructing
SendRequest/tx and calling
sendPaymentService.completeTransaction/signTransaction/sendTransaction) with a
broader catch (catch Exception or Throwable) in buildAndSendSwapTx() and convert
any caught exception into a ResponseResource.Failure that preserves the
exception and message (similar to the InsufficientMoneyException handling), so
callers receive a failure result instead of crashing.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/MayaCryptoCurrency.kt`:
- Around line 323-378: The BCH/LTC/DOGE classes (MayaBitcoinCashCryptoCurrency,
MayaLitecoinCryptoCurrency, MayaDogecoinCryptoCurrency) currently use regex-only
checks in paymentIntentParser and addressParser which miss checksum and
HRP/prefix validation; replace those regex-based validators with proper CashAddr
(BCH) / Bech32 (LTC) / Base58Check (DOGE) parsing and checksum verification
routines (or call existing library helpers) so that paymentIntentParser and
addressParser perform full checksum+network HRP/prefix validation, accept
canonical uppercase/lowercase HRPs where appropriate, and keep legacy Base58
branches limited to the correct prefix sets for each network.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/NearAddressParser.kt`:
- Around line 27-30: The named-account regex in NearAddressParser (the second
constructor argument pattern string) currently requires a dot and thus rejects
single-segment NEAR account IDs; update that pattern to allow either a single
segment or dot-separated segments and ensure each segment starts and ends with
an alphanumeric character (allowing hyphen/underscore only internally) — i.e.,
replace the current "([a-z0-9_-]{2,64}(\\.[a-z0-9_-]{1,64})+)" style pattern
used in the NearAddressParser constructor with one that permits a single segment
and uses segment anchors like "[a-z0-9](?:[a-z0-9_-]{0,62}[a-z0-9])?" for each
segment and "(?:\\.[segment])*" for optional additional segments so
single-segment IDs are accepted and hyphen/underscore are not allowed at segment
boundaries.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/SolanaPaymentIntentParser.kt`:
- Around line 41-69: The parse implementation in SolanaPaymentIntentParser
duplicates logic present in StarknetPaymentIntentParser and
SuiPaymentIntentParser; move the shared parsing flow into
MayaPaymentIntentParser by creating a protected/common parseTemplate method (or
making parse in base call a shared helper) that uses overridable properties:
uriPrefix, addressParser, and createPaymentIntent(address), and centralizes the
try/catch branches that throw PaymentIntentParserException; update
SolanaPaymentIntentParser, StarknetPaymentIntentParser, and
SuiPaymentIntentParser to supply their specific uriPrefix and addressParser and
to call the base helper so only currency-specific values remain in subclasses.
- Around line 42-52: The extracted address from the URI (using uriPrefix and
input) must be validated for non-emptiness before calling createPaymentIntent;
after computing val address = input.substring(uriPrefix.length + 1) in
SolanaPaymentIntentParser.kt, check if address.isBlank() and if so log the
invalid input and throw a PaymentIntentParserException (same shape as the
existing catch with ResourceString(R.string.error, listOf(input))) to reject
inputs like "solana:" or "SOLANA:" early rather than passing an empty address
into createPaymentIntent.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/StarknetAddressParser.kt`:
- Around line 21-22: The Starknet address handling is inconsistent:
StarknetAddressParser currently uses "0x[a-fA-F0-9]{1,64}" while the comment and
felt252 expectation suggest max 63 hex chars, and
MayaStarknetCryptoCurrency.exampleAddress contains 65 hex chars; fix by making
StarknetAddressParser’s regex and comment match the canonical felt252 length
(use 1..63 hex chars or explicitly allow padded 64 only if you also accept
padded values), update StarknetPaymentIntentParser docs to the same wording,
correct MayaStarknetCryptoCurrency.exampleAddress to a valid-length example, and
ensure exactMatch() is used consistently before createPaymentIntent() (or add an
explicit numeric/range check for felt value) so parsing/validation logic is
consistent across StarknetAddressParser, StarknetPaymentIntentParser,
MayaStarknetCryptoCurrency.exampleAddress, exactMatch(), and
createPaymentIntent().

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/StarknetPaymentIntentParser.kt`:
- Around line 38-48: The parser currently extracts the address using uriPrefix
and calls createPaymentIntent without checking for an empty address; update the
block that handles inputs starting with "$uriPrefix:" (and uppercase variant) to
validate that the extracted address (the variable assigned from
input.substring(uriPrefix.length + 1)) is not blank or empty, and if it is, log
an informative message via log.info and throw a PaymentIntentParserException
(using ResourceString(R.string.error, listOf(input))) instead of calling
createPaymentIntent; keep the existing exception handling for other errors so
only empty-address inputs are rejected explicitly before invoking
createPaymentIntent.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/SuiPaymentIntentParser.kt`:
- Around line 38-48: The extracted address from input.substring(uriPrefix.length
+ 1) can be empty (e.g., "sui:"), so before calling createPaymentIntent in
SuiPaymentIntentParser (inside the withContext block where input, uriPrefix are
used) validate that the address is non-empty and not blank; if it is empty throw
a PaymentIntentParserException (using the same ResourceString(R.string.error,
listOf(input))) and log the invalid uri via log.info as the existing catch does,
otherwise proceed to return createPaymentIntent(address).

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/SwapKitApiAggregator.kt`:
- Around line 465-479: The current fee conversion uses targetPerDash for any fee
on the target chain (condition uses chain == targetChain || asset ==
targetAsset), which incorrectly converts gas fees denominated in other tokens;
update the conditional in the when-block that computes dash conversion (the
clause referencing fee.chain, fee.asset, targetPerDash, targetChain,
targetAsset, and amt) so that targetPerDash is applied only when the fee's asset
actually matches the swapRequest target asset (compare fee.asset to targetAsset
after normalizing case); otherwise log and skip conversion (so feeAmount.dash
isn't populated) and let MayaBlockchainApi.buildAndSendSwapTx() avoid sending
wrong DASH amounts.
- Around line 216-230: The code crashes when there are no P2PKH unspents because
map.values.maxOf { it } throws; change the logic to safely handle an empty map
(e.g., use map.entries.maxByOrNull { it.value } or map.values.maxOfOrNull) and
derive address from that nullable result, falling back to the existing
sourceAddress path when null; update the block that computes
maxAddressBalance/address (referencing map,
walletDataProvider.wallet!!.unspents, ScriptPattern, and maxOf) so it no longer
calls maxOf on an empty collection and returns null-safe address instead.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConversionPreviewFragment.kt`:
- Line 215: The use of `@SuppressLint`("SetTextI18n") in
MayaConversionPreviewFragment (affecting
binding.contentOrderReview.orderInfo.text) hides hardcoded English concatenation
like "selected:"/"all:"; remove the debug UI output or replace it with localized
string resources and formatted strings (e.g., order_route_selected and
order_routes_available) and use getString(...)/String.format(...) to set
orderInfo.text, or if it's only for diagnostics, drop the UI changes and log the
route info via Timber/Logger instead.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConvertCryptoFragment.kt`:
- Line 162: The code in MayaConvertCryptoFragment uses Address.fromBase58(null,
swapTrade.vaultAddress) which decodes the vault address without network params
causing inconsistent validation compared to how transactions are built in
MayaBlockchainApi.buildAndSendSwapTx (which uses the network params when
creating VOUT0 to swapTrade.vaultAddress); update the decoder to use the same
network parameters used by the tx builder (pass the appropriate
NetworkParameters instance instead of null) so Address.fromBase58 is validated
consistently with MayaBlockchainApi.buildAndSendSwapTx and continues to accept
the Asgard vault destination with OP_RETURN memo.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaPortalFragment.kt`:
- Line 33: MayaViewModel currently exposes activeSwapBackend as a plain getter;
change it to a StateFlow (or include it inside MayaPortalUIState) so backend
updates are observable, and update MayaPortalFragment to collect that flow with
collectAsStateWithLifecycle() (or use the UI state flow) and pass the resulting
Compose State into MayaPortalScreen so recomposition occurs on backend changes;
refer to MayaViewModel.activeSwapBackend, MayaPortalFragment, MayaPortalScreen,
collectAsStateWithLifecycle(), and MayaPortalUIState when making the changes.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaViewModel.kt`:
- Around line 275-277: The type check in MayaViewModel.isTradingActive() is
wrong: replace the impossible branch "is SwapKitConstants" with the intended
defensive check "is SwapKitApiAggregator" so the when(swapProvider) branch can
actually match a non-dispatching SwapKit backend; keep the body using
inboundAddresses.value.isNotEmpty() unchanged and ensure the branch sits
alongside the existing SwapProvider cases to correctly handle
SwapKitApiAggregator at runtime.

---

Outside diff comments:
In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/MayaCryptoCurrency.kt`:
- Around line 648-655: The MayaEthereumTokenCryptoCurrency entry for USDC has
the wrong URI prefix ("pepe") passed into the EthereumPaymentIntentParser which
breaks payment intent parsing; update the constructor call in the USDC token
entry so the EthereumPaymentIntentParser is instantiated with the correct prefix
"usdc" (i.e., replace the "pepe" argument in the
MayaEthereumTokenCryptoCurrency(...) call that creates the ARB.USDC entry) so
the URI prefix matches the currency code and parsing succeeds.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConversionPreviewViewModel.kt`:
- Around line 119-124: The code always passes ServiceName.Maya into
transactionMetadataProvider.markAddressAsync which misattributes swaps when
another backend (e.g., SwapKit) is active; change the call to determine the
service from the active swap backend and pass the appropriate ServiceName enum
(e.g., ServiceName.SwapKit when SwapKit is active) — locate the call to
transactionMetadataProvider.markAddressAsync and replace the hardcoded
ServiceName.Maya with a derived value from the active backend provider or the
swap model (e.g., a method or property on swapTradeUIModel or a central backend
selector) and ensure the ServiceName enum includes the corresponding entry for
that backend before calling markAddressAsync.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConvertCryptoFragment.kt`:
- Around line 138-157: The current change removed or altered the DASH
halted-state safeguard and switched the swap recipient from the DASH inbound
address to swapTrade.vaultAddress; restore/ensure the halted-state check by
keeping the logic in isTradingActive() that finds the inbound with chain ==
"DASH" and returns dashInbound.halted != true (so trading is blocked when
halted), and audit the swap recipient assignment where swapTrade.vaultAddress
replaced dashInbound.address: confirm with product/security whether vaultAddress
is the intended destination for the swap; if not, revert to using
dashInbound.address for the recipient, otherwise add explicit
validation/comments ensuring vaultAddress matches the expected routing for DASH
to avoid misrouting funds.

---

Nitpick comments:
In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/SwapProvider.kt`:
- Around line 65-68: The two overloaded functions getDefaultSwapQuote have
inconsistent default values for the value parameter which can surprise callers;
pick a single canonical default (e.g., DEFAULT_SWAP_VALUE) and use it for both
overloads or explicitly document the reason for the difference. Fix by
introducing a single constant (e.g., DEFAULT_SWAP_VALUE) in the companion/object
visible to SwapProvider, replace both literal defaults in
getDefaultSwapQuote(toAsset: String, value: Long = ...) and
getDefaultSwapQuote(toAsset: String, destinationAddress: String, value: Long =
...) with that constant, and add a brief KDoc on
SwapProvider.getDefaultSwapQuote explaining the chosen default; if you
intentionally need different defaults, change the KDoc of both overloads to
clearly state why they differ instead of keeping asymmetric literals.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/TonPaymentIntentParser.kt`:
- Around line 40-68: The parse() implementation in TonPaymentIntentParser is
identical to TronPaymentIntentParser and XrpPaymentIntentParser; extract the
common logic into a new abstract base class (e.g.,
SimpleUriAddressPaymentIntentParser) that extends MayaPaymentIntentParser and
accepts the AddressParser plus currency/chain/asset defaults, implement the
shared parse() there using uriPrefix, addressParser.exactMatch(),
createPaymentIntent(), and the existing log/error handling, then make
TonPaymentIntentParser/TronPaymentIntentParser/XrpPaymentIntentParser into thin
subclasses that only pass their specific
TonAddressParser/TronAddressParser/XrpAddressParser and default constructor
values. Ensure exception handling (AddressFormatException and generic
Exception), ResourceString usage, and logging calls remain identical to preserve
behavior.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/model/SwapKitModels.kt`:
- Line 37: The field declaration currently uses a non-null default (val
identifier: String = "") which can mask missing data; change the property to a
nullable type (val identifier: String?) and remove the empty-string default,
update any code that reads this property (e.g., API call builders,
equality/hash, constructors) to explicitly handle null (throw/return error,
guard before using in requests, or provide a clear fallback), and ensure your
JSON serializer/deserializer mapping for the data class (the class containing
identifier) allows absent keys to produce null rather than injecting an empty
string.
- Line 71: The field expectedBuyAmount currently defaults to "0" which can hide
missing quote data; change its type to nullable (expectedBuyAmount: String? =
null) in the data class (the val expectedBuyAmount declaration) and update any
consumers (parsers, model mappers, UI renderers, and tests) to handle null
explicitly—e.g., treat null as "no quote" and surface an error or empty state;
also ensure JSON deserialization configuration (serializers/adapters) accepts
absent fields as null.
- Line 66: The routeId property in SwapKitModels.kt is currently declared as a
non-nullable String with a default empty value which masks missing route data;
change the property declaration for routeId to be nullable (e.g., val routeId:
String?) and remove the empty-string default so absent routeId deserializes to
null, then update any construction sites and validation logic (callers that use
routeId and the code that builds /v3/swap requests) to check for null and fail
early with a clear error before making the API call.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/SwapKitWebApi.kt`:
- Line 79: Create and reuse a single Gson instance instead of constructing new
ones in parseErrorBody and parseSwapErrorBody: add a class-level gson property
(e.g., in the SwapKitWebApi class) and replace the two occurrences of
Gson().fromJson(...) (used to parse SwapKitQuoteResponse and the swap error)
with gson.fromJson(...); this keeps parsing thread-safe and avoids repeated
instantiation while leaving method signatures and behavior unchanged.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaViewModel.kt`:
- Around line 120-131: The onEach block in the walletUIConfig.observe(...) flow
is making an extra suspending call to
exchangeRatesProvider.getExchangeRate(Constants.USD_CURRENCY) purely to compute
and log a derived rate; remove this unnecessary network read or change the flow
to compute the ratio without calling getExchangeRate inside onEach. Either (A)
delete the usdPrice fetch and the log if it was only diagnostic, or (B) compute
both rates via flows (e.g., use combine to observeExchangeRate(...) for the
selected currency and observeExchangeRate(Constants.USD_CURRENCY) once) and then
compute rate from their .rate values so no suspending getExchangeRate call runs
inside onEach; update references to walletUIConfig.observe(...),
exchangeRatesProvider.getExchangeRate, Constants.USD_CURRENCY and
exchangeRate.rate accordingly.

In `@integrations/maya/SWAPKIT_PROTOCOL.md`:
- Around line 33-35: The fenced code blocks missing language specifiers should
be updated to include a language hint (e.g., "text" or "http") so Markdown
renderers correctly syntax-highlight them: add a language token after the
opening backticks for the block containing "x-api-key: <YOUR_API_KEY>" and for
the block containing the architecture snippet starting with "SwapKitApi
(Interface)". Use "text" for plain header/diagram blocks or "http" for the API
header example as appropriate and keep the block contents unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 76cf804b-7847-4187-ac6f-59b4964b4dc2

📥 Commits

Reviewing files that changed from the base of the PR and between 5eb1c5f and 3989c9e.

📒 Files selected for processing (56)
  • .claude/agents/update-swapkit-currencies.md
  • integrations/maya/SWAPKIT_PROTOCOL.md
  • integrations/maya/build.gradle
  • integrations/maya/proguard-rules.pro
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/DispatchingSwapProvider.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/MayaApi.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/MayaBlockchainApi.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/SwapProvider.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/di/MayaModule.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/model/MayaErrorResponse.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/model/SwapQuote.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/model/SwapTradeResponse.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/MayaCryptoCurrency.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/CardanoAddressParser.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/CardanoPaymentIntentParser.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/NearAddressParser.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/NearPaymentIntentParser.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/SimpleBase58PaymentIntentParser.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/SolanaAddressParser.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/SolanaPaymentIntentParser.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/StarknetAddressParser.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/StarknetPaymentIntentParser.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/SuiAddressParser.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/SuiPaymentIntentParser.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/TonAddressParser.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/TonPaymentIntentParser.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/TronAddressParser.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/TronPaymentIntentParser.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/XrpAddressParser.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/XrpPaymentIntentParser.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/SwapKitApiAggregator.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/SwapKitAuthInterceptor.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/SwapKitConstants.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/SwapKitEndpoint.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/SwapKitWebApi.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/model/SwapKitModels.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaAddressInputViewModel.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConversionPreviewFragment.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConversionPreviewViewModel.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConvertCryptoFragment.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConvertCryptoViewModel.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConvertResultViewModel.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaPortalFragment.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaPortalScreen.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaViewModel.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/convert_currency/ConvertViewViewModel.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/dialogs/MayaResultDialog.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/utils/MayaConfig.kt
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/utils/SwapBackend.kt
  • integrations/maya/src/main/res/drawable/ic_swapkit_logo.xml
  • integrations/maya/src/main/res/layout/content_conversion_preview_maya.xml
  • integrations/maya/src/main/res/values/strings-maya.xml
  • wallet/src/de/schildbach/wallet/data/BuyAndSellDashServicesModel.kt
  • wallet/src/de/schildbach/wallet/ui/buy_sell/BuyAndSellIntegrationsFragment.kt
  • wallet/src/de/schildbach/wallet/ui/buy_sell/BuyAndSellScreen.kt
  • wallet/src/de/schildbach/wallet/ui/buy_sell/BuyAndSellViewModel.kt
💤 Files with no reviewable changes (1)
  • integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConvertResultViewModel.kt

Comment thread integrations/maya/build.gradle
Comment on lines +323 to +378
open class MayaBitcoinCashCryptoCurrency : MayaBitcoinCryptoCurrency() {
override val code: String = "BCH"
override val name: String = "Bitcoin Cash"
override val asset: String = "BCH.BCH"
override val exampleAddress: String = "qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"
override val paymentIntentParser: PaymentIntentParser = SimpleBase58PaymentIntentParser(
"BCH",
"bitcoincash",
"BCH.BCH",
"(bitcoincash:)?[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{42,55}|[13][1-9A-HJ-NP-Za-km-z]{25,34}"
)
override val addressParser: AddressParser = AddressParser(
"(bitcoincash:)?[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{42,55}|[13][1-9A-HJ-NP-Za-km-z]{25,34}",
null
)
override val codeId: Int = R.string.cryptocurrency_bch_code
override val nameId: Int = R.string.cryptocurrency_bch_network
}

open class MayaLitecoinCryptoCurrency : MayaBitcoinCryptoCurrency() {
override val code: String = "LTC"
override val name: String = "Litecoin"
override val asset: String = "LTC.LTC"
override val exampleAddress: String = "ltc1qd5wm03t5kcdupjuyq5jffpuacnaqahvfsdu8smf8z0u0pqdqpatqsdrn8h"
override val paymentIntentParser: PaymentIntentParser = SimpleBase58PaymentIntentParser(
"LTC",
"litecoin",
"LTC.LTC",
"(ltc1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38,71})|([LM3][1-9A-HJ-NP-Za-km-z]{26,33})"
)
override val addressParser: AddressParser = AddressParser(
"(ltc1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38,71})|([LM3][1-9A-HJ-NP-Za-km-z]{26,33})",
null
)
override val codeId: Int = R.string.cryptocurrency_ltc_code
override val nameId: Int = R.string.cryptocurrency_ltc_network
}

open class MayaDogecoinCryptoCurrency : MayaBitcoinCryptoCurrency() {
override val code: String = "DOGE"
override val name: String = "Dogecoin"
override val asset: String = "DOGE.DOGE"
override val exampleAddress: String = "DH5yaieqoZN36fDVciNyRueRGvGLR3mr7L"
override val paymentIntentParser: PaymentIntentParser = SimpleBase58PaymentIntentParser(
"DOGE",
"dogecoin",
"DOGE.DOGE",
"[DA9][1-9A-HJ-NP-Za-km-z]{32,33}"
)
override val addressParser: AddressParser = AddressParser(
"[DA9][1-9A-HJ-NP-Za-km-z]{32,33}",
null
)
override val codeId: Int = R.string.cryptocurrency_doge_code
override val nameId: Int = R.string.cryptocurrency_doge_network
}

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify that the address patterns match official specifications

echo "=== Bitcoin Cash Address Format ==="
echo "Pattern: (bitcoincash:)?[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{42,55}|[13][1-9A-HJ-NP-Za-km-z]{25,34}"
echo "Spec: CashAddr (Bech32 variant) and legacy Base58"
echo ""

echo "=== Litecoin Address Format ==="
echo "Pattern: (ltc1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38,71})|([LM3][1-9A-HJ-NP-Za-km-z]{26,33})"
echo "Spec: Bech32 (ltc1...) and legacy Base58 (L/M/3 prefix)"
echo ""

echo "=== Dogecoin Address Format ==="
echo "Pattern: [DA9][1-9A-HJ-NP-Za-km-z]{32,33}"
echo "Spec: Base58 with D/A/9 prefix"
echo ""

echo "Check if example addresses match the patterns:"
echo "BCH example: qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"
echo "LTC example: ltc1qd5wm03t5kcdupjuyq5jffpuacnaqahvfsdu8smf8z0u0pqdqpatqsdrn8h"
echo "DOGE example: DH5yaieqoZN36fDVciNyRueRGvGLR3mr7L"

Repository: dashpay/dash-wallet

Length of output: 764


🌐 Web query:

Bitcoin Cash CashAddr format specification

💡 Result:

The CashAddr format is a Base32-encoded address scheme specifically designed for Bitcoin Cash (BCH) to improve user experience and reduce errors when sharing addresses [1][2][3]. It differs from the legacy Base58Check format and is designed to be easily readable and shareable, including in QR codes [4][1]. Structure A CashAddr string consists of three mandatory components [4][1][2]: 1. Prefix: A human-readable part indicating the network (e.g., bitcoincash for mainnet, bchtest for testnet, bchreg for regtest) [4][5]. 2. Separator: A colon (:) that separates the prefix from the payload [4][1]. 3. Payload: A Base32-encoded string containing a version byte, the address hash (typically P2PKH or P2SH), and a 40-bit checksum [4][1][2]. Technical Specifications Encoding: CashAddr uses a Base32 character set (excluding '1', 'b', 'i', and 'o' to avoid ambiguity) [1][2]. While lowercase is preferred, uppercase is accepted; however, mixed-case strings must be rejected [4][1][5]. Checksum: The format employs a 40-bit BCH (Bose-Chaudhuri-Hocquenghem) code defined over GF(2^5) [4][1]. This provides robust error detection, capable of identifying up to 6 errors in the address and 8 consecutive errors [4][2]. Version Byte: The payload's version byte defines the address type (e.g., 0b000 for P2PKH, 0b001 for P2SH) and includes bits to verify the length of the data [1][2]. Usage Guidelines The prefix is often omitted in certain contexts, but when present, the colon separator must be included [4][5]. Systems should avoid "double prefixing" (e.g., adding bitcoincash: to an already prefixed string) in payment URLs or QR codes [4][5].

Citations:


🌐 Web query:

Litecoin Bech32 address format specification

💡 Result:

Litecoin utilizes the Bech32 address format (as defined in Bitcoin Improvement Proposal 173) for its Native SegWit implementation [1][2][3]. Bech32 is a checksummed base32 address format that replaces the traditional version-byte prefix approach used in Base58Check with a human-readable part (HRP) [4][1]. Key specifications for Litecoin Bech32 addresses include: Human-Readable Part (HRP): Litecoin uses specific HRPs to identify the chain and address type, differentiating them from Bitcoin and other assets [4][2]. - Mainnet SegWit: ltc [4] - Testnet SegWit: tltc [4] - Mainnet MWEB: ltc1mweb [4] - Testnet MWEB: tltc1mweb [4] Structure: Like the standard BIP173 specification, a Bech32 string consists of three parts [1]: 1. The Human-Readable Part (HRP): Such as ltc, followed by the separator 1 [4][1]. 2. The Separator: The character 1 [1]. 3. The Data Part: A base32-encoded witness program, including a checksum, which uses alphanumeric characters excluding 1, b, i, and o [1][5]. Implementation: Litecoin Core introduced full support for Bech32 (Native SegWit) addresses, allowing users to generate and send to these addresses using the -addresstype=bech32 configuration [3]. These addresses are currently the recommended standard for Litecoin transactions due to their improved error detection, lowercase formatting (which reduces transcription errors), and lower transaction fees compared to legacy (P2PKH) and P2SH-wrapped SegWit (M-addresses) [2][6].

Citations:


🌐 Web query:

Dogecoin address format specification

💡 Result:

A Dogecoin address is a base58-encoded string derived from a public key, primarily following the Pay-to-Public-Key-Hash (P2PKH) format [1][2]. The process to generate a standard P2PKH Dogecoin address is as follows [2]: 1. Hash the public key once using the SHA-256 algorithm. 2. Pass the resulting hash into the RIPEMD-160 hash function. 3. Add a network-specific prefix byte to the beginning of the RIPEMD-160 result ('D' for mainnet, which corresponds to a hex value of 0x1E or decimal 30) [2][3]. 4. Create a checksum by performing a double SHA-256 hash on the prefixed string and taking the first 4 bytes. 5. Append the 4-byte checksum to the end of the prefixed string. 6. Encode the final byte sequence using Base58 to produce the human-readable address [2]. Address characteristics: - Standard P2PKH addresses on the Dogecoin mainnet begin with the character 'D' [4][5][6]. - Multisig (P2SH) addresses on the Dogecoin mainnet typically begin with the character 'A' [4][5][7]. - Addresses are case-sensitive and are typically around 33-34 characters in length [8]. Technical constants for the main network include: - Address Header (Prefix): 30 (0x1E) [3] - P2SH Header: 22 (0x16) [3] - Secret Key (WIF) Header: 158 (0x9E) [3]

Citations:


Switch BCH/LTC/DOGE address validation from regex-only to checksum-aware parsing

  • The current regex patterns only enforce allowed characters/lengths; they don’t implement the protocol checksums/structure required by BCH CashAddr (40-bit BCH checksum), LTC Bech32 (BIP173 checksum), or DOGE Base58Check (double-SHA256 checksum), so syntactically valid but checksum-invalid addresses may be accepted (and Bech32/CashAddr uppercase forms may be rejected since the regexes only match lowercase HRPs/prefixes).
  • Replace these regex screenings with the appropriate CashAddr/Bech32/Base58Check validators (or at least validate checksum + network HRPs/prefixes), and ensure the legacy Base58 branches (BCH) and Base58Check branches (DOGE) use the correct prefix sets per network.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/MayaCryptoCurrency.kt`
around lines 323 - 378, The BCH/LTC/DOGE classes (MayaBitcoinCashCryptoCurrency,
MayaLitecoinCryptoCurrency, MayaDogecoinCryptoCurrency) currently use regex-only
checks in paymentIntentParser and addressParser which miss checksum and
HRP/prefix validation; replace those regex-based validators with proper CashAddr
(BCH) / Bech32 (LTC) / Base58Check (DOGE) parsing and checksum verification
routines (or call existing library helpers) so that paymentIntentParser and
addressParser perform full checksum+network HRP/prefix validation, accept
canonical uppercase/lowercase HRPs where appropriate, and keep legacy Base58
branches limited to the correct prefix sets for each network.

Comment on lines +27 to +30
class NearAddressParser : AddressParser(
"([a-f0-9]{64})|([a-z0-9_-]{2,64}(\\.[a-z0-9_-]{1,64})+)",
null
)

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

NEAR Protocol account address format specification

💡 Result:

In the NEAR Protocol, account identifiers (Account IDs) follow specific structural rules and exist in several forms [1][2]. General Rules All NEAR Account IDs must meet these criteria [1][2][3]: - Length: Minimum of 2 characters and maximum of 64 characters [1][2][3]. - Character set: Only lowercase letters (a-z), digits (0-9), and the separators period (.), hyphen (-), and underscore () are permitted [1][2][3]. - Structure: Named accounts consist of parts separated by dots [1][3]. Account ID parts must contain only lowercase alphanumeric symbols separated by hyphens or underscores [3]. Types of Account Addresses 1. Implicit Accounts: These are 64-character hexadecimal strings derived directly from an ED25519 public key [1][4][2]. They do not require registration; holding the private key grants control over the account [1][5]. 2. Named Accounts: These are human-readable addresses (e.g., alice.near) that function similarly to domain names [1][2]. They must be registered via the NEAR network's registrar or parent accounts (e.g.,.near) [2][3]. 3. Ethereum-like Accounts: Identified by a hexadecimal address starting with 0x followed by 40 lowercase hexadecimal characters [1][2][3]. These are typically created when interacting with NEAR applications via Ethereum-compatible wallets [1][2]. 4. Deterministic Accounts: These are identified by an address starting with 0s followed by 40 lowercase hexadecimal characters (42 characters total) [2][3]. Their addresses are derived deterministically based on their initial contract code and state [2]. Validation To validate an Account ID programmatically, the recommended regular expression is [6]: ^(([a-z\d]+[-])[a-z\d]+.)([a-z\d]+[-_])*[a-z\d]+$ (Note: This regex validates the format of named account parts; length checks of 2-64 characters should be performed separately) [6].

Citations:


Fix NEAR account-id regex to allow single-segment IDs
In NearAddressParser.kt lines 27-30, the named-account pattern ([a-z0-9_-]{2,64}(\\.[a-z0-9_-]{1,64})+) requires at least one dot, but NEAR account IDs can be single-part (no dot); the dot-separated prefix is optional in the protocol/Nomicon account-id format. Update the named-account regex accordingly (and preferably tighten segment rules so hyphen/underscore aren’t allowed only at boundaries).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/NearAddressParser.kt`
around lines 27 - 30, The named-account regex in NearAddressParser (the second
constructor argument pattern string) currently requires a dot and thus rejects
single-segment NEAR account IDs; update that pattern to allow either a single
segment or dot-separated segments and ensure each segment starts and ends with
an alphanumeric character (allowing hyphen/underscore only internally) — i.e.,
replace the current "([a-z0-9_-]{2,64}(\\.[a-z0-9_-]{1,64})+)" style pattern
used in the NearAddressParser constructor with one that permits a single segment
and uses segment anchors like "[a-z0-9](?:[a-z0-9_-]{0,62}[a-z0-9])?" for each
segment and "(?:\\.[segment])*" for optional additional segments so
single-segment IDs are accepted and hyphen/underscore are not allowed at segment
boundaries.

Comment on lines +465 to +479
val chain = fee.chain?.uppercase()
val asset = fee.asset?.uppercase()
val inDash = when {
chain == "DASH" || asset?.contains("DASH") == true -> amt
targetPerDash.signum() > 0 &&
(chain == targetChain || asset == targetAsset) ->
amt.divide(targetPerDash, 16, RoundingMode.HALF_UP)
else -> {
// Routing-asset fees (CACAO for Maya, RUNE for Thor liquidity,
// etc.) need their own DASH rate to convert. Log and skip;
// undercounting is preferable to guessing.
log.info(
"swapkit fee skipped: type={} amount={} asset={} chain={}",
fee.type, fee.amount, fee.asset, fee.chain
)

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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don't convert same-chain gas fees with the target token rate.

Lines 467-471 treat any fee on the target chain as if it were denominated in swapRequest.target_maya_asset. That breaks token routes: an ERC-20 quote can return an ETH gas leg while cryptoDashExchangeRate is USDC per DASH. The bad conversion feeds feeAmount.dash, and MayaBlockchainApi.buildAndSendSwapTx() then sends the wrong DASH amount on commit.

Only fees whose fee.asset actually matches target_maya_asset can use that rate. Other fee assets need their own DASH conversion or they should stay excluded.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/SwapKitApiAggregator.kt`
around lines 465 - 479, The current fee conversion uses targetPerDash for any
fee on the target chain (condition uses chain == targetChain || asset ==
targetAsset), which incorrectly converts gas fees denominated in other tokens;
update the conditional in the when-block that computes dash conversion (the
clause referencing fee.chain, fee.asset, targetPerDash, targetChain,
targetAsset, and amt) so that targetPerDash is applied only when the fee's asset
actually matches the swapRequest target asset (compare fee.asset to targetAsset
after normalizing case); otherwise log and skip conversion (so feeAmount.dash
isn't populated) and let MayaBlockchainApi.buildAndSendSwapTx() avoid sending
wrong DASH amounts.

binding.previewOfflineGroup.isVisible = hasInternet
}

@SuppressLint("SetTextI18n")

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove debug code or localize user-facing strings.

The @SuppressLint("SetTextI18n") suppresses a warning about hardcoded English strings ("selected:", "all:"), which is a red flag. This displays internal route debugging information directly to users using raw string concatenation—it's neither localized nor user-friendly.

Options:

  1. If this is temporary debug code: Remove it before merging.
  2. If route info must be shown: Create proper string resources and format professionally:
    <string name="order_route_selected">Route: %s</string>
    <string name="order_routes_available">Available: %s</string>
    Then:
    binding.contentOrderReview.orderInfo.text = buildString {
        appendLine(getString(R.string.order_route_selected, routeName))
        append(getString(R.string.order_routes_available, routes))
    }
  3. If for internal diagnostics only: Use Timber/logging instead of UI display.

Also applies to: 324-330

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@integrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConversionPreviewFragment.kt`
at line 215, The use of `@SuppressLint`("SetTextI18n") in
MayaConversionPreviewFragment (affecting
binding.contentOrderReview.orderInfo.text) hides hardcoded English concatenation
like "selected:"/"all:"; remove the debug UI output or replace it with localized
string resources and formatted strings (e.g., order_route_selected and
order_routes_available) and use getString(...)/String.format(...) to set
orderInfo.text, or if it's only for diagnostics, drop the UI changes and log the
route info via Timber/Logger instead.

var memo: String? = null,
var txid: Sha256Hash = Sha256Hash.ZERO_HASH,
var expectedOutputAmount: BigDecimal = BigDecimal.ZERO
val routeName: String? = "maya-default",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

missing comma; the file does not compile.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

good catch

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@abaranouski this has been fixed

@HashEngineering HashEngineering merged commit bb86dea into feat/dash-dex Jun 16, 2026
1 check passed
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