mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 03:38:23 +00:00
These were the last two reflective JSON bindings in the app. Jackson's hand-written
NWC deserializers dispatched to ~35 concrete classes with treeToValue(), and CLINK
went through readValue<T>() with no registered deserializer at all — so the field
names of 106 classes were load-bearing under R8 and cost two package keeps.
OptimizedJsonMapper.jvmAndroid now routes Request/Response/Notification and the
seven CLINK types at the hand-written kotlinx serializers that already existed in
commonMain and already were the production path on iOS. Nothing new had to be
written to replace Jackson; the Jackson (de)serializers for these types, and
OmitNullsMixin, are deleted. Everything else stays on Jackson, which is faster on
the event hot path and non-reflective there.
seeds under nip47WalletConnect.rpc 851 -> 19
seeds under experimental.clink 793 -> 9
DEX 31.11 MB -> 30.96 MB
reflection contract 31 checks -> 24
PayInvoiceParams is now gone from the DEX entirely: R8 merged it away, which the
keep rule had been forbidding. It is not deleted (usage.txt lists only members),
the request serializer survives, and the wire-shape tests pass.
**Forgiving parsing.** The serializers read every field through new helpers in
nip01Core.kotlinSerialization.LenientJson, replacing 176 raw `.jsonPrimitive` /
`.jsonObject` accesses that threw on the wrong shape. The rule now: a field that is
missing, null, or the wrong type reads as null and the rest of the message still
parses. That is deliberately more forgiving than Jackson was — it ignored unknown
properties but still raised MismatchedInputException when a declared String arrived
as an object, losing a settled payment's preimage over one bad neighbouring field.
Covered by 22 new tests across NWC and CLINK: unknown result_types, unknown extra
fields, integers quoted as strings, booleans as 1/0, explicit nulls, a wrong-shaped
field costing only itself, a broken entry in a list leaving the good ones, and a
lone value where a list belongs (Jackson's ACCEPT_SINGLE_VALUE_AS_ARRAY, preserved).
An unrecognised `result_type` no longer throws. NIP-47 grows and the wallet is
somebody else's software, so those arrive as the new NwcUnknownResponse carrying
their result intact. It is deliberately not an IErrorResponseLike: an unknown
answer is not a refusal. Response is abstract, not sealed, so no consumer breaks.
An unknown *request* method still throws — Request is sealed, we are a client, and
that one is left for a separate decision.
Two things found on the way:
- toAnyValue() tried Double before Long, so a metadata integer round-tripped 42 as
42.0 and changed the bytes of a field we only carry. Fixed the precedence.
- toAnyValue/toAnyMap lived under nip47 while CLINK imported them across packages;
moved beside their inverse anyToJsonElement in nip01Core.
Verified: full ./gradlew test green, R8 release build green, 24/24 contract checks,
and the NWC/CLINK classes confirmed renamed in the shipped DEX. The NWC payment and
CLINK offer paths are the ones to exercise on a device — R8 can optimize these
classes for the first time, and no unit test runs against the minified APK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DEoxktEZyTrAS33vVZBiwm
40 lines
1.9 KiB
Prolog
40 lines
1.9 KiB
Prolog
# =============================================================================
|
|
# Keep rules Quartz contributes to every app that consumes it (wired through
|
|
# `optimization.consumerKeepRules` in build.gradle.kts, so these end up merged
|
|
# into the consumer's own R8 configuration).
|
|
#
|
|
# Scope them tightly. A rule here applies to the CONSUMER's whole program, so
|
|
# the `-keepnames class ** { *; }` that used to sit in this file pinned every
|
|
# name in every app that depends on Quartz — ours included — and blocked R8
|
|
# from optimizing any member anywhere (`-keepnames` is `-keep,allowshrinking`).
|
|
# Only list what breaks at runtime if the name changes.
|
|
# =============================================================================
|
|
|
|
-keepdirectories libs
|
|
|
|
# secp256k1's JNI layer resolves these from native code.
|
|
-keep class fr.acinq.secp256k1.** { *; }
|
|
|
|
# libscrypt
|
|
-keep class com.lambdaworks.codec.** { *; }
|
|
-keep class com.lambdaworks.crypto.** { *; }
|
|
-keep class com.lambdaworks.jni.** { *; }
|
|
|
|
# No Jackson keeps. Every wire format Quartz speaks is now handled by a
|
|
# hand-written serializer that names its fields as string literals: the
|
|
# StdSerializer/StdDeserializer pairs registered on JacksonMapper (Event, Filter,
|
|
# Message, Command, Rumor, EventTemplate, TagArray, the NIP-46 Bunker messages),
|
|
# JsonMapperNip55 (IntentResult, Permission), and the kotlinx serializers that
|
|
# NIP-47 and CLINK route through on every target. None of it reads a Kotlin
|
|
# constructor parameter name at runtime, so none of it has to survive R8.
|
|
|
|
# Quartz serialises enums by name (Jackson writes/reads Enum.name, and
|
|
# Enum.valueOf resolves that string against the static field name), so the
|
|
# constants of its own enums have to keep their names. Consumers that persist
|
|
# their OWN enums by name need the equivalent rule in their own configuration.
|
|
-keepclassmembers enum com.vitorpamplona.quartz.** {
|
|
<fields>;
|
|
public static **[] values();
|
|
public static ** valueOf(java.lang.String);
|
|
}
|