Skip to content
Merged
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
24 changes: 24 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// swift-tools-version:5.9
import PackageDescription

let packageName = "SharedCore"

let package = Package(
name: packageName,
platforms: [
.iOS(.v15)
],
products: [
.library(
name: packageName,
targets: [packageName]
),
],
targets: [
.binaryTarget(
name: packageName,
path: "./kmp/shared-core/build/XCFrameworks/debug/\(packageName).xcframework"
)
,
]
)
3 changes: 3 additions & 0 deletions apps/flipcash/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ androidComponents {

dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
// KMP shared core — consumed transitively via :libs:encryption:base58 but declared
// explicitly so the app's own code can reference com.flipcash.shared.* directly.
implementation(project(":kmp:shared-core"))
implementation(project(":services:flipcash-compose"))

implementation(project(":apps:flipcash:core"))
Expand Down
4 changes: 4 additions & 0 deletions build-logic/convention/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,9 @@ gradlePlugin {
id = "flipcash.android.ed25519.shadow"
implementationClass = "AndroidEd25519ShadowConventionPlugin"
}
register("kmpLibrary") {
id = "flipcash.kmp.library"
implementationClass = "KmpLibraryConventionPlugin"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import org.gradle.api.Plugin
import org.gradle.api.Project

/**
* Convention plugin for KMP library modules.
*
* Applies:
* - `org.jetbrains.kotlin.multiplatform`
* - `com.android.kotlin.multiplatform.library`
*
* After applying this plugin the `kotlin {}` DSL block is available for configuring targets,
* source sets, and dependencies.
*
* Usage in a module's `build.gradle.kts`:
* ```
* plugins {
* alias(libs.plugins.flipcash.kmp.library)
* }
* ```
*/
class KmpLibraryConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("org.jetbrains.kotlin.multiplatform")
apply("com.android.kotlin.multiplatform.library")
}
}
}
}
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ plugins {
alias(libs.plugins.androidx.room) apply false
alias(libs.plugins.screenshot) apply false
alias(libs.plugins.kover)
// KMP modules: shared-core umbrella + libs/* pilots
alias(libs.plugins.kotlin.multiplatform) apply false
alias(libs.plugins.android.kotlin.multiplatform.library) apply false
alias(libs.plugins.kmmbridge) apply false
}

// NOTE: The per-project `allprojects { }` configuration that used to live here
Expand Down
2 changes: 2 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ android.enableR8.fullMode=true

# Enabled parallel sync for Gradle 9.4+
org.gradle.tooling.parallel=true

kotlin.native.ignoreDisabledTargets=true
21 changes: 13 additions & 8 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ android-targetSdk = "37"
android-java = "21"

kotlin = "2.4.10"
ksp = "2.3.11"
ksp = "2.3.10"
kotlinx-coroutines = "1.11.0"
kotlinx-serialization = "1.11.0"
kotlinx-datetime = "0.8.0"

kover = "0.9.9"
agp = "9.3.1"
kmmbridge = "1.2.1"
google-services = "4.5.0"

androidx-appcompat = "1.7.1"
Expand Down Expand Up @@ -48,26 +49,26 @@ hilt-jetpack = "1.4.0"
okhttp = "5.4.0"
retrofit = "3.0.0"
kin-sdk = "2.1.2"
grpc-android = "1.83.1"
grpc-android = "1.83.0"
slf4j = "1.7.36"
firebase-bom = "34.17.0"
firebase-bom = "34.16.0"
play-service-ml-barcode = "18.3.1"
play-services-auth-api-phone = "18.3.1"
google-play-billing = "9.1.0"
google-play-updates = "2.1.0"

grpc = "1.83.1"
grpc-okhttp = "1.83.1"
grpc = "1.83.0"
grpc-okhttp = "1.83.0"
grpc-kotlin = "1.5.0"
protobuf = "4.35.1"
protobuf-plugin = "0.10.0"
protovalidate-kt = "0.1.1"

lib-phone-number-port = "9.0.36"
lib-phone-number-google = "9.0.36"
lib-phone-number-port = "9.0.34"
lib-phone-number-google = "9.0.35"
zxing = "3.5.4"

androidx-benchmark-macro = "1.5.0-beta01"
androidx-benchmark-macro = "1.5.0-alpha07"
androidx-profileinstaller = "1.4.1"
androidx-test-runner = "1.7.0"
junit = "4.13.2"
Expand Down Expand Up @@ -325,6 +326,7 @@ flipcash-android-library = { id = "flipcash.android.library" }
flipcash-android-library-compose = { id = "flipcash.android.library.compose" }
flipcash-android-feature = { id = "flipcash.android.feature" }
flipcash-android-ed25519-shadow = { id = "flipcash.android.ed25519.shadow" }
flipcash-kmp-library = { id = "flipcash.kmp.library" }
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
Expand All @@ -344,3 +346,6 @@ androidx-baselineprofile = { id = "androidx.baselineprofile", version.ref = "and
screenshot = { id = "com.android.compose.screenshot", version.ref = "screenshot" }
kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
protobuf-validate = { id = "dev.bmcreations.protovalidate", version.ref = "protovalidate-kt" }
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
android-kotlin-multiplatform-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }
kmmbridge = { id = "co.touchlab.kmmbridge.github", version.ref = "kmmbridge" }
41 changes: 41 additions & 0 deletions kmp/shared-core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
plugins {
kotlin("multiplatform")
id("com.android.kotlin.multiplatform.library")
id("co.touchlab.kmmbridge.github") version "1.2.1"
`maven-publish`
}

group = "com.flipcash"
version = "0.1.0"

kotlin {
android {
namespace = "com.flipcash.shared"
compileSdk = 37
minSdk = 29
}

val appleTargets = listOf(iosArm64(), iosSimulatorArm64(), iosX64())
appleTargets.forEach {
it.binaries.framework {
baseName = "SharedCore"
isStatic = true
export(project(":libs:encryption:base58"))
}
}

sourceSets {
commonMain {
dependencies {
api(project(":libs:encryption:base58"))
}
}
}
}

kmmbridge {
gitHubReleaseArtifacts()
spm(swiftToolVersion = "5.9") {
iOS { v("15") }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.flipcash.shared

/**
* Marker for the cross-platform shared core. Gives iOS a symbol to verify the
* XCFramework is linked (`SharedCore.version`) and a home for the shared surface
* as it grows across milestones.
*/
object SharedCore {
const val version: String = "0.1.0"
}
34 changes: 27 additions & 7 deletions libs/encryption/base58/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
plugins {
alias(libs.plugins.flipcash.android.library)
kotlin("multiplatform")
id("com.android.kotlin.multiplatform.library")
}

android {
namespace = "${Gradle.codeNamespace}.encryption.base58"
}
kotlin {
android {
namespace = "com.getcode.encryption.base58"
compileSdk = 37
minSdk = 29
withHostTest {}
}

iosArm64()
iosSimulatorArm64()
iosX64()

dependencies {
testImplementation(kotlin("test"))
testImplementation(libs.kotlinx.serialization.json) // parse cross-platform test-vector fixtures
sourceSets {
commonMain {
// Pure Kotlin -- no external dependencies needed.
}
androidMain {
// MessageDigest + BigInteger -- JDK only; no extra Gradle deps.
}
commonTest {
dependencies {
implementation(kotlin("test"))
implementation(libs.kotlinx.serialization.json)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.getcode.vendor

actual fun readTestResource(name: String): String =
checkNotNull(Thread.currentThread().contextClassLoader?.getResourceAsStream(name)) {
"Resource '$name' not found on classpath"
}.bufferedReader().use { it.readText() }
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.getcode.vendor

import java.math.BigInteger
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException

/**
* JVM-only helpers for Base58. These use [MessageDigest] and [BigInteger] which are not
* available on Kotlin/Native, so they live in `androidMain` rather than `commonMain`.
*
* All are extension functions / top-level functions on `Base58` so existing callers that
* call `Base58.encodeChecked(...)` etc. keep compiling unchanged.
*/

/** Encodes version + payload with a 4-byte SHA256d checksum appended. */
fun Base58.encodeChecked(version: Int, payload: ByteArray): String {
require(version in 0..255) { "Version not in range." }
val addressBytes = ByteArray(1 + payload.size + 4)
addressBytes[0] = version.toByte()
payload.copyInto(addressBytes, 1)
val checksum = hashTwice(addressBytes, 0, payload.size + 1)
checksum.copyInto(addressBytes, payload.size + 1, 0, 4)
return encode(addressBytes)
}

/** Decodes a base58-encoded string and verifies the 4-byte checksum. */
@Throws(Base58.AddressFormatException::class)
fun Base58.decodeChecked(input: String): ByteArray {
val decoded = decode(input)
if (decoded.size < 4) throw Base58.AddressFormatException.InvalidDataLength("Input too short: ${decoded.size}")
val data = decoded.copyOfRange(0, decoded.size - 4)
val checksum = decoded.copyOfRange(decoded.size - 4, decoded.size)
val actualChecksum = hashTwice(data).copyOfRange(0, 4)
if (!checksum.contentEquals(actualChecksum)) throw Base58.AddressFormatException.InvalidChecksum()
return data
}

/** Decodes a base58 string to a [BigInteger]. */
@Throws(Base58.AddressFormatException::class)
fun Base58.decodeToBigInteger(input: String): BigInteger = BigInteger(1, decode(input))

/**
* Computes SHA-256(SHA-256(input[offset..<offset+length])).
*/
@JvmOverloads
fun Base58.hashTwice(input: ByteArray, offset: Int = 0, length: Int = input.size): ByteArray {
val digest = try {
MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw RuntimeException(e)
}
digest.update(input, offset, length)
return digest.digest(digest.digest())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.getcode.vendor

actual fun readTestResource(name: String): String =
checkNotNull(Thread.currentThread().contextClassLoader?.getResourceAsStream(name)) {
"Resource '$name' not found on classpath"
}.bufferedReader().use { it.readText() }
Loading
Loading