Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ Add the maven setting values in `local.properties` file:
```properties
mwsdk.maven.url=https://maven.pkg.github.com/circlefin/modularwallets-android-sdk
mwsdk.maven.username=<GITHUB_USERNAME>
# Fine-grained personal access tokens or classic with package write permission.
# Classic personal access token with `read:packages` scope — GitHub Packages Gradle registry authenticates classic PATs only (fine-grained PATs are not supported here).
# For SSO-enforced orgs, also authorize the PAT for that org — see GitHub Packages auth docs.
mwsdk.maven.password=<GITHUB_PAT>

```
Expand All @@ -33,4 +34,4 @@ Add the dependency:
dependencies {
implementation 'circle.modularwallets:core:version'
}
```
```
1 change: 1 addition & 0 deletions lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ android {
testInstrumentationRunnerArguments += mapOf(
"notPackage" to "com.circle.modularwallets.core.manual"
)
consumerProguardFiles("consumer-rules.pro")
}

buildTypes {
Expand Down
64 changes: 64 additions & 0 deletions lib/consumer-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Consumer ProGuard rules — applied by R8 in the consumer app's build.
#
# These rules ship with the AAR (via consumerProguardFiles in lib/build.gradle.kts)
# and run in addition to whatever rules the consumer app supplies.
#
# Keep narrow targets only. Each block must have a comment explaining why it
# is load-bearing — without that, future maintainers can't tell whether the
# rule is still needed.

# Preserve the JVM Signature attribute. Jackson reads it at runtime to recover
# generic type information for collection-typed properties — notably
# EIP712Message.types : MutableMap<String, MutableList<Entry>>. Without it,
# Jackson sees the setter as raw `Map`/`List`, deserializes the nested type
# entries as LinkedHashMap, and downstream code (StructuredDataEncoder) then
# ClassCastExceptions when iterating them as Entry. InnerClasses and
# EnclosingMethod are paired per R8's own recommendation — Signature can
# reference inner classes, and stripping the other two leaves dangling refs.
# The default proguard-android(-optimize).txt usually keeps these, but
# consumer R8 configurations vary, so we set them explicitly.
-keepattributes Signature, InnerClasses, EnclosingMethod

# EIP-712 typed-data DTOs are deserialized by Jackson via reflection
# (see StructuredDataEncoder.parseJSONMessage). Jackson is brought in
# transitively via web3j; jackson-module-kotlin is NOT registered, so
# Jackson falls back to JavaBean-style binding and needs each property's
# public setter to remain reachable. Without these rules, R8 in consumer
# apps strips the setters as unused (Jackson's reflective access is
# invisible to R8) and verifyingContract / chainId / etc. fail to bind
# with `UnrecognizedPropertyException`.
#
# Scope: constructors + getters + setters only. Data-class extras
# (componentN / copy / equals / hashCode / toString) are not on Jackson's
# binding path and can be shrunk/obfuscated freely.
-keepclassmembers class com.circle.modularwallets.core.models.EIP712Domain {
<init>(...);
public *** get*();
public void set*(***);
}
-keepclassmembers class com.circle.modularwallets.core.models.EIP712Message {
<init>(...);
public *** get*();
public void set*(***);
}
-keepclassmembers class com.circle.modularwallets.core.models.Entry {
<init>(...);
public *** get*();
public void set*(***);
}

# web3j (transitive dependency, used internally by StructuredDataEncoder for
# EIP-712 typed-data hashing). web3j resolves ABI type classes by reflection
# (`Class.forName("org.web3j.abi.datatypes." + name)` in AbiTypes/TypeEncoder),
# so consumer R8 must not rename or strip those datatype classes. Without this
# rule, `hashTypedData(json)` fails at runtime with
# `BaseError: Received an invalid argument for which no constructor exists for the ABI Class …`
# in any minified consumer app. The crypto and utils packages are not reflective
# — SDK code directly imports the entry points (Hash, Sign, Numeric, …) so R8's
# tracer keeps everything reachable through them. No broad `crypto.**` keep
# needed.
-keep class org.web3j.abi.datatypes.** { *; }
-dontwarn org.web3j.**
# web3j drags in SLF4J as a logging facade. Consumer apps that don't ship a
# concrete SLF4J binding would otherwise R8-error on the missing impl class.
-dontwarn org.slf4j.**
24 changes: 24 additions & 0 deletions lib/src/main/java/com/circle/modularwallets/core/chains/Arc.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2025 Circle Internet Group, Inc. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at.
*
* Http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.circle.modularwallets.core.chains

object Arc : Chain() {
override val chainId: Long
get() = 5042L
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ internal val CONTRACT_ADDRESS: Map<String, String> = mapOf(
Token.Arbitrum_ARB.name to "0x912CE59144191C1204E64559FE8253a0e49E6548",
Token.ArbitrumSepolia_USDC.name to "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d",
Token.ArcTestnet_USDC.name to "0x3600000000000000000000000000000000000000",
Token.Arc_USDC.name to "0x3600000000000000000000000000000000000000",
Token.Avalanche_USDC.name to "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
Token.AvalancheFuji_USDC.name to "0x5425890298aed601595a70AB815c96711a31Bc65",
Token.Base_USDC.name to "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
Expand Down Expand Up @@ -1852,4 +1853,4 @@ val CIRCLE_MSCA_6900_V1_EP07_ABI = """
"type": "receive"
}
]
""".trimIndent()
""".trimIndent()
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ import com.circle.modularwallets.core.constants.REPLAY_SAFE_HASH_V1
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

// The Moshi @JsonClass / @Json annotations stay because the generated
// EIP712*JsonAdapter classes are published public API surface — consumer apps
// that use Moshi (without KotlinJsonAdapterFactory) can depend on them.
// However, the SDK's own typed-data parser uses Jackson
// (StructuredDataEncoder.parseJSONMessage), and that path needs consumer-side
// R8 keep rules in lib/consumer-rules.pro.

@JsonClass(generateAdapter = true)
data class EIP712Message @JvmOverloads constructor(
@Json(name = "types") var types: MutableMap<String, MutableList<Entry>>? = null,
Expand All @@ -36,12 +43,18 @@ data class Entry @JvmOverloads constructor(
@Json(name = "type") var type: String? = null,
)

// All five canonical EIP712Domain fields must remain `var` — the SDK's
// Jackson-based typed-data parser binds via JavaBean setters (no
// jackson-module-kotlin on the classpath), so a `val` property has no setter
// and Jackson throws UnrecognizedPropertyException for that field in any
// minified consumer app. The structural invariant is asserted by
// EIP712DomainParseTest.canonicalDomainFieldsExposePublicSetters.
@JsonClass(generateAdapter = true)
data class EIP712Domain @JvmOverloads constructor(
@Json(name = "name") var name: String? = null,
@Json(name = "version") var version: String? = null,
@Json(name = "chainId") var chainId: Long? = null,
@Json(name = "verifyingContract") val verifyingContract: String? = null,
@Json(name = "verifyingContract") var verifyingContract: String? = null,
@Json(name = "salt") var salt: String? = null,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ enum class Token {
Arbitrum_ARB,
ArbitrumSepolia_USDC,
ArcTestnet_USDC,
Arc_USDC,
Avalanche_USDC,
AvalancheFuji_USDC,
Base_USDC,
Expand Down
Loading