feat: add swapkit proof of concept as a DEX#1489
Conversation
📝 WalkthroughWalkthroughThis 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 ChangesSwapKit Backend Integration and Abstraction
Multi-Chain Currency and Payment Parser Expansion
UI Layer Refactoring to SwapProvider Abstraction
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
…feat/swapkit-poc
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
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 winTransaction metadata is hardcoded to
ServiceName.Mayaregardless 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 winPreserve 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 thechain == "DASH"inbound address and returnsdashInbound.halted != true, preserving the prior halted-state safety check (lines 138-149).- Line 162: the swap recipient changed from
dashInbound.addresstoswapTrade.vaultAddress. Must confirmvaultAddressis 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 winCritical: 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 winConsider reusing a single Gson instance.
Creating new
Gson()instances inparseErrorBodyandparseSwapErrorBodyis 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 ofGson().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 valueAdd 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
textorhttp. The second block (line 363) shows an architecture diagram and could usetext.📝 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 winConsider making
identifiernullable instead of defaulting to empty string.The
identifierfield 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 winConsider making
expectedBuyAmountnullable 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 winConsider making
routeIdnullable to detect missing route data.
routeIdis required for/v3/swapcalls (per SWAPKIT_PROTOCOL.md line 209), but defaults to empty string. An emptyrouteIdwould 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 winDifferent default values in overloaded methods may cause confusion.
The two
getDefaultSwapQuoteoverloads use different default values (10_0000_0000 vs 1_0000_0000) for thevalueparameter. 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 valueObserver performs a network read only to log.
This flow calls the suspending
exchangeRatesProvider.getExchangeRate(...)and computesratesolely tolog.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 liftExtract the duplicated
parse()into a shared base parser.This
parse(input)body is byte-for-byte identical toTronPaymentIntentParserandXrpPaymentIntentParser(only the log strings differ). The three diverge only in the constructor defaults (currency,asset, chain id) and the injectedaddressParser. Consider a small base class that captures the common URI/address classification flow, leaving subclasses to supply just theAddressParserand 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)) ) } }
TonPaymentIntentParserthen becomes a thin subclass passingTonAddressParser()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
📒 Files selected for processing (56)
.claude/agents/update-swapkit-currencies.mdintegrations/maya/SWAPKIT_PROTOCOL.mdintegrations/maya/build.gradleintegrations/maya/proguard-rules.prointegrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/DispatchingSwapProvider.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/MayaApi.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/MayaBlockchainApi.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/api/SwapProvider.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/di/MayaModule.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/model/MayaErrorResponse.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/model/SwapQuote.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/model/SwapTradeResponse.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/MayaCryptoCurrency.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/CardanoAddressParser.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/CardanoPaymentIntentParser.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/NearAddressParser.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/NearPaymentIntentParser.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/SimpleBase58PaymentIntentParser.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/SolanaAddressParser.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/SolanaPaymentIntentParser.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/StarknetAddressParser.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/StarknetPaymentIntentParser.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/SuiAddressParser.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/SuiPaymentIntentParser.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/TonAddressParser.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/TonPaymentIntentParser.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/TronAddressParser.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/TronPaymentIntentParser.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/XrpAddressParser.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/payments/parsers/XrpPaymentIntentParser.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/SwapKitApiAggregator.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/SwapKitAuthInterceptor.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/SwapKitConstants.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/SwapKitEndpoint.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/SwapKitWebApi.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/swapkit/model/SwapKitModels.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaAddressInputViewModel.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConversionPreviewFragment.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConversionPreviewViewModel.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConvertCryptoFragment.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConvertCryptoViewModel.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaConvertResultViewModel.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaPortalFragment.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaPortalScreen.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/MayaViewModel.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/convert_currency/ConvertViewViewModel.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/ui/dialogs/MayaResultDialog.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/utils/MayaConfig.ktintegrations/maya/src/main/java/org/dash/wallet/integrations/maya/utils/SwapBackend.ktintegrations/maya/src/main/res/drawable/ic_swapkit_logo.xmlintegrations/maya/src/main/res/layout/content_conversion_preview_maya.xmlintegrations/maya/src/main/res/values/strings-maya.xmlwallet/src/de/schildbach/wallet/data/BuyAndSellDashServicesModel.ktwallet/src/de/schildbach/wallet/ui/buy_sell/BuyAndSellIntegrationsFragment.ktwallet/src/de/schildbach/wallet/ui/buy_sell/BuyAndSellScreen.ktwallet/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
| 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 | ||
| } |
There was a problem hiding this comment.
🧩 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:
- 1: https://documentation.cash/protocol/blockchain/encoding/cashaddr.html
- 2: https://reference.cash/protocol/blockchain/encoding/cashaddr
- 3: https://support.token.im/hc/en-us/articles/900000072963-What-is-the-Bitcoin-Cash-BCH-CashAddr-Format
- 4: https://github.com/bitcoincashorg/bitcoincash.org/blob/3e2e6da8c38dab7ba12149d327bc4b259aaad684/spec/cashaddr.md
- 5: https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md
🌐 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:
- 1: https://en.bitcoin.it/wiki/BIP_0173
- 2: https://litecoin.watch/articles/litecoin-address-formats-explained-legacy-segwit-bech32-and-mweb
- 3: https://github.com/litecoin-project/litecoin/blob/3567effe2049771a2201ae62f6edb06bf27634d2/doc/release-notes-litecoin.md
- 4: https://litecoin.com/learning-center/addresses-prefixes
- 5: https://github.com/bitcoin/bips/blob/8d3b41936f061fb1e86917f9f6f33182a277b42c/bip-0173.mediawiki
- 6: https://stealthex.io/blog/what-does-a-litecoin-address-look-like-ltc-address-examples/
🌐 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:
- 1: https://github.com/dogecoinfoundation/libdogecoin/blob/0.1.5-dev/doc/address.md
- 2: https://lib.dogecoin.org/docs/address
- 3: https://github.com/dogecoin/libdohj/blob/master/core/src/main/java/org/libdohj/params/DogecoinMainNetParams.java
- 4: https://support.ledger.com/article/115005174025-zd
- 5: https://coin.space/dogecoin-address-example/
- 6: https://docs.getblock.io/api-reference/dogecoin-doge
- 7: https://www.reddit.com/r/dogecoindev/comments/w4vicr/dogecoin_addresses_start_with_a_d/
- 8: https://help.emma-app.com/en/article/what-is-my-dogecoin-address-fg6mk3/
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.
| class NearAddressParser : AddressParser( | ||
| "([a-f0-9]{64})|([a-z0-9_-]{2,64}(\\.[a-z0-9_-]{1,64})+)", | ||
| null | ||
| ) |
There was a problem hiding this comment.
🧩 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:
- 1: https://docs.near.org/protocol/account-id
- 2: https://docs.near.org/protocol/accounts-contracts/account-id
- 3: https://nomicon.io/DataStructures/Account.html
- 4: https://near.github.io/nearcore/DataStructures/Account.html
- 5: https://www.mintlify.com/near/docs/protocol/account-id
- 6: https://stackoverflow.com/questions/72537015/regex-for-validating-account-names-for-near-protocol
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.
| 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 | ||
| ) |
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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:
- If this is temporary debug code: Remove it before merging.
- If route info must be shown: Create proper string resources and format professionally:
Then:
<string name="order_route_selected">Route: %s</string> <string name="order_routes_available">Available: %s</string>
binding.contentOrderReview.orderInfo.text = buildString { appendLine(getString(R.string.order_route_selected, routeName)) append(getString(R.string.order_routes_available, routes)) }
- 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", |
There was a problem hiding this comment.
missing comma; the file does not compile.
There was a problem hiding this comment.
good catch
Issue being fixed or feature implemented
Related PR's and Dependencies
Screenshots / Videos
How Has This Been Tested?
Checklist:
Summary by CodeRabbit
New Features
Bug Fixes
Documentation