diff --git a/build.gradle b/build.gradle index 776f2b6..f302a38 100644 --- a/build.gradle +++ b/build.gradle @@ -1,31 +1,7 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - -buildscript { - ext.kotlin_version = '1.7.10' - repositories { - google() - mavenCentral() - - } - dependencies { - classpath 'com.android.tools.build:gradle:7.2.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' - classpath 'com.google.dagger:hilt-android-gradle-plugin:2.38' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -allprojects { - repositories { - google() - mavenCentral() - maven { url "https://jitpack.io" } - } -} - -task clean(type: Delete) { - delete rootProject.buildDir +plugins { + id 'com.android.application' version '8.2.1' apply false + id 'org.jetbrains.kotlin.android' version '1.9.0' apply false + id 'com.google.dagger.hilt.android' version '2.48.1' apply false + id("com.google.firebase.crashlytics") version "2.9.9" apply false + id 'com.google.devtools.ksp' version '1.9.0-1.0.13' apply false } diff --git a/demo/.gitignore b/demo/.gitignore index 42afabf..5064f87 100644 --- a/demo/.gitignore +++ b/demo/.gitignore @@ -1 +1,79 @@ -/build \ No newline at end of file +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/runConfigurations.xml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +.idea/jarRepositories.xml +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + +# Android Profiling +*.hprof diff --git a/demo/src/main/java/ramp/network/demo/MainActivity.kt b/demo/src/main/java/ramp/network/demo/MainActivity.kt index 2e48e9f..2a3220a 100644 --- a/demo/src/main/java/ramp/network/demo/MainActivity.kt +++ b/demo/src/main/java/ramp/network/demo/MainActivity.kt @@ -34,15 +34,14 @@ class MainActivity : AppCompatActivity() { val config = Config( hostLogoUrl = "https://ramp.network/assets/images/Logo.svg", hostAppName = "My App", - url = "https://app.dev.ramp-network.org", - hostApiKey = "3qncr4yvxfpro6endeaeu6npkh8qc23e9uadtazq", + url = "https://app.demo.ramp.network", + hostApiKey = "udr4csh6vje38gvndow6p3f32j3zq3jbkfgrsn22", + userAddress = "0x81A4362705891615d7395135d5591BaaC9f21d3D", + swapAsset = "GOERLI_ETH", enabledFlows = setOf(Flow.OFFRAMP, Flow.ONRAMP) ) // 4. Implement callbacks val callback = object : RampCallback { - override fun onPurchaseFailed() { - Log.d("MainActivity", "onPurchaseFailed") - } override fun onPurchaseCreated( purchase: Purchase, diff --git a/gradle.properties b/gradle.properties index 376270d..952185b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,4 +18,5 @@ android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official \ No newline at end of file +kotlin.code.style=official +android.defaults.buildfeatures.buildconfig=true diff --git a/rampsdk/.gitignore b/rampsdk/.gitignore index 796b96d..5064f87 100644 --- a/rampsdk/.gitignore +++ b/rampsdk/.gitignore @@ -1 +1,79 @@ -/build +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/runConfigurations.xml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +.idea/jarRepositories.xml +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + +# Android Profiling +*.hprof diff --git a/rampsdk/build.gradle b/rampsdk/build.gradle index 0e1244e..b098613 100644 --- a/rampsdk/build.gradle +++ b/rampsdk/build.gradle @@ -2,21 +2,15 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'kotlin-parcelize' - id 'kotlin-kapt' + id 'com.google.devtools.ksp' id 'maven-publish' } group = 'com.github.RampNetwork' -repositories { - mavenCentral() - google() - maven { url "https://jitpack.io" } -} - android { - compileSdkVersion 32 - buildToolsVersion "30.0.3" + compileSdk 32 + buildToolsVersion "34.0.0" defaultConfig { @@ -27,6 +21,13 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles 'consumer-rules.pro' buildConfigField 'String', 'VERSION', "\"${defaultConfig.versionName}\"" + + namespace "network.ramp.sdk" + + } + + kotlinOptions { + jvmTarget = "1.8" } buildTypes { @@ -37,6 +38,7 @@ android { } buildFeatures { + buildConfig true viewBinding true } @@ -50,20 +52,16 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10" implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'androidx.core:core-ktx:1.7.0' - implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-reflect:1.7.10" - implementation 'com.squareup.moshi:moshi:1.14.0' - implementation 'com.squareup.moshi:moshi-adapters:1.14.0' - implementation 'com.squareup.moshi:moshi-kotlin:1.14.0' + implementation("com.squareup.moshi:moshi-kotlin:1.15.0") + implementation 'com.squareup.moshi:moshi-adapters:1.15.0' - kapt("com.squareup.moshi:moshi-kotlin-codegen:1.14.0") + ksp("com.squareup.moshi:moshi-kotlin-codegen:1.15.0") - def dagger_version = "2.40.4" - implementation "com.google.dagger:dagger:$dagger_version" - kapt "com.google.dagger:dagger-compiler:$dagger_version" implementation 'com.jakewharton.timber:timber:4.7.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1' @@ -76,16 +74,3 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' } - -afterEvaluate { - publishing { - publications { - release(MavenPublication) { - from components.release - groupId = 'com.github.RampNetwork' - artifactId = 'ramp-sdk-android' - version = '3.0.4' - } - } - } -} diff --git a/rampsdk/src/main/AndroidManifest.xml b/rampsdk/src/main/AndroidManifest.xml index c4ea92b..5d8c4b6 100644 --- a/rampsdk/src/main/AndroidManifest.xml +++ b/rampsdk/src/main/AndroidManifest.xml @@ -1,13 +1,11 @@ + xmlns:tools="http://schemas.android.com/tools"> diff --git a/rampsdk/src/main/java/network/ramp/sdk/events/model/Events.kt b/rampsdk/src/main/java/network/ramp/sdk/events/model/Events.kt index 5ff68b4..6b7586b 100644 --- a/rampsdk/src/main/java/network/ramp/sdk/events/model/Events.kt +++ b/rampsdk/src/main/java/network/ramp/sdk/events/model/Events.kt @@ -9,6 +9,8 @@ sealed class Event(val type: EventType) { } } +@JsonClass(generateAdapter = true) +data class Close(var payload: WidgetClosePayload? = null) : Event(EventType.CLOSE) // TO_VERIFY Not in documentation but in received events @JsonClass(generateAdapter = true) data class WidgetClose(var payload: WidgetClosePayload? = null) : Event(EventType.WIDGET_CLOSE) @@ -48,6 +50,7 @@ internal data class OfframpSaleCreated(var payload: OfframpSaleCreatedPayload) : @JsonClass(generateAdapter = false) enum class EventType { + CLOSE, WIDGET_CLOSE, OPEN_LINK, WIDGET_CONFIG_DONE, diff --git a/rampsdk/src/main/java/network/ramp/sdk/events/model/Payloads.kt b/rampsdk/src/main/java/network/ramp/sdk/events/model/Payloads.kt index daee3b1..287948d 100644 --- a/rampsdk/src/main/java/network/ramp/sdk/events/model/Payloads.kt +++ b/rampsdk/src/main/java/network/ramp/sdk/events/model/Payloads.kt @@ -48,15 +48,9 @@ data class OfframpSaleCreatedPayload( data class Purchase( val id: String, val endTime: String, // purchase validity time, ISO date-time string - @Deprecated("This parameter will be removed in future release") - val escrowAddress: String? = null, // filled only for escrow-backend purchases val cryptoAmount: String, // number-string, in wei or token units val fiatCurrency: String, // three-letter currency code - val fiatValue: Long, // total value the user pays for the purchase, in fiatCurrency - @Deprecated("This parameter will be removed in future") - val assetExchangeRateEur: Double, - @Deprecated("This parameter will be removed in future") - val fiatExchangeRateEur: Long, + val fiatValue: Double, // total value the user pays for the purchase, in fiatCurrency val baseRampFee: Double, // base Ramp fee before any modifications, in fiatCurrency val networkFee: Double, // network fee for transferring the purchased asset, in fiatCurrency val appliedFee: Double, // final fee the user pays (included in fiatValue), in fiatCurrency @@ -65,11 +59,9 @@ data class Purchase( val asset: Asset, // description of the purchased asset (address, symbol, name, decimals) val receiverAddress: String, // blockchain address of the buyer val assetExchangeRate: Double,// price of 1 whole token of purchased asset, in fiatCurrency - @Deprecated("This parameter will be removed in future") - val purchaseViewToken: String, val status: String, // purchase status val paymentMethodType: String, // type of payment method used to pay for the swap: 'MANUAL_BANK_TRANSFER' | 'AUTO_BANK_TRANSFER' | 'CARD_PAYMENT' | 'APPLE_PAY' - val finalTxHash: String? = null // hash of the crypto transfer blockchain transaction, filled once available + val finalTxHash: String? = null // TO_VERIFY() not in recived json ) @JsonClass(generateAdapter = true) @@ -97,8 +89,10 @@ data class Fiat( data class Asset( val address: String? = null, // 0x-prefixed address for ERC-20 tokens, `null` for ETH val symbol: String, // asset symbol, for example `ETH`, `DAI`, `USDC` + val apiV3Symbol: String, // TO VERIFY new val name: String, val decimals: Long, // token decimals, e.g. 18 for ETH/DAI, 6 for USDC val type: String, // asset type & network, e.g. `ETH`, `ERC20`, `MATIC_ERC20` - val chain: String // asset chain, for example `ETH`, `BSC`, `POLKADOT` + val chain: String, // asset chain, for example `ETH`, `BSC`, `POLKADOT` + val apiV3Type: String // TO_VERIFY() new ) : Parcelable \ No newline at end of file diff --git a/rampsdk/src/main/java/network/ramp/sdk/facade/Config.kt b/rampsdk/src/main/java/network/ramp/sdk/facade/Config.kt index 0c2c071..7eb3dae 100644 --- a/rampsdk/src/main/java/network/ramp/sdk/facade/Config.kt +++ b/rampsdk/src/main/java/network/ramp/sdk/facade/Config.kt @@ -109,7 +109,7 @@ data class Config( var useSendCryptoCallback: Boolean? = null, - var useSendCryptoCallbackVersion: Int? = SEND_CRYPTO_CALLBACK_VERSION + var useSendCryptoCallbackVersion: Int? = SEND_CRYPTO_CALLBACK_VERSION // TO_VERIFY() Not in the documentation. Should it stay? ) : Parcelable { diff --git a/rampsdk/src/main/java/network/ramp/sdk/facade/RampCallback.kt b/rampsdk/src/main/java/network/ramp/sdk/facade/RampCallback.kt index f3ec83c..eb24fc0 100644 --- a/rampsdk/src/main/java/network/ramp/sdk/facade/RampCallback.kt +++ b/rampsdk/src/main/java/network/ramp/sdk/facade/RampCallback.kt @@ -5,10 +5,6 @@ import network.ramp.sdk.events.model.Purchase import network.ramp.sdk.events.model.Asset interface RampCallback { - - @Deprecated(message = "This method is deprecated and will be removed in future versions.") - fun onPurchaseFailed() - fun onPurchaseCreated(purchase: Purchase, purchaseViewToken: String, apiUrl: String) fun onOfframpSaleCreated( diff --git a/rampsdk/src/main/java/network/ramp/sdk/facade/RampSDK.kt b/rampsdk/src/main/java/network/ramp/sdk/facade/RampSDK.kt index 5e2ae72..5bc45cf 100644 --- a/rampsdk/src/main/java/network/ramp/sdk/facade/RampSDK.kt +++ b/rampsdk/src/main/java/network/ramp/sdk/facade/RampSDK.kt @@ -12,6 +12,7 @@ import network.ramp.sdk.events.EventBus import network.ramp.sdk.events.model.* import network.ramp.sdk.ui.activity.RampWidgetActivity import timber.log.Timber +import java.net.URL class RampSDK { @@ -28,9 +29,7 @@ class RampSDK { fun startTransaction( activity: Activity, config: Config, - callback: RampCallback, - //@DEPRECATED to remove in next major release - url: String? = null + callback: RampCallback ) { Timber.d("RAMP SDK version - ${BuildConfig.VERSION}") release() @@ -42,6 +41,21 @@ class RampSDK { activity.startActivity(intent) } + fun startTransactionWithUrl( + activity: Activity, + callback: RampCallback, + url: String = "" + ) { + Timber.d("RAMP SDK version - ${BuildConfig.VERSION}") + release() + this.callback = callback + val intent = Intent(activity, RampWidgetActivity::class.java) + intent.putExtra( + URL_EXTRA, url + ) + activity.startActivity(intent) + } + fun onOfframpCryptoSent(txHash: String? = null, error: String? = null) { scope.launch { EventBus.invokeEvent(SendCryptoResult(SendCryptoResultPayload(txHash, error))) @@ -63,9 +77,6 @@ class RampSDK { payload.apiUrl ) } - EventType.PURCHASE_FAILED -> { - callback?.onPurchaseFailed() - } EventType.OFFRAMP_SALE_CREATED -> { val payload = (it as OfframpSaleCreated).payload @@ -103,5 +114,6 @@ class RampSDK { companion object { internal const val CONFIG_EXTRA = "config" + internal const val URL_EXTRA = "url" } } \ No newline at end of file diff --git a/rampsdk/src/main/java/network/ramp/sdk/ui/activity/RampPresenter.kt b/rampsdk/src/main/java/network/ramp/sdk/ui/activity/RampPresenter.kt index fd6e7d0..1ab91cc 100644 --- a/rampsdk/src/main/java/network/ramp/sdk/ui/activity/RampPresenter.kt +++ b/rampsdk/src/main/java/network/ramp/sdk/ui/activity/RampPresenter.kt @@ -26,6 +26,7 @@ internal class RampPresenter( .add( PolymorphicJsonAdapterFactory.of(Event::class.java, LABEL_KEY_TYPE) .withSubtype(OpenLink::class.java, EventType.OPEN_LINK.name) + .withSubtype(Close::class.java, EventType.CLOSE.name) .withSubtype(WidgetClose::class.java, EventType.WIDGET_CLOSE.name) .withSubtype(PurchasedFailed::class.java, EventType.PURCHASE_FAILED.name) .withSubtype(PurchasedCreated::class.java, EventType.PURCHASE_CREATED.name) @@ -68,15 +69,18 @@ internal class RampPresenter( .fromJson(json) when (event?.type) { + EventType.CLOSE -> { + (event as? Close)?.payload?.let { + showDialogOrClose(it.showAlert) + } + } + EventType.WIDGET_CLOSE -> { (event as? WidgetClose)?.payload?.let { - if (it.showAlert) - view.showDialog() - else { - view.close() - } + showDialogOrClose(it.showAlert) } } + EventType.OPEN_LINK -> { (event as? OpenLink)?.payload?.let { Timber.d("onOpenUrl ${it.linkType} ${it.url} ") @@ -117,6 +121,7 @@ internal class RampPresenter( EventType.WIDGET_CONFIG_DONE -> { configDone = true } + else -> Timber.w("Unhandled event $json") } } @@ -164,6 +169,14 @@ internal class RampPresenter( fun isUrlSafe(url: String): Boolean = UrlSafeChecker.isUrlSafe(url) + private fun showDialogOrClose(showAlert: Boolean) { + if (showAlert) + view.showDialog() + else { + view.close() + } + } + fun postMessage(event: T) { val eventJson = moshi .adapter(Event::class.java) diff --git a/rampsdk/src/main/java/network/ramp/sdk/ui/activity/RampWidgetActivity.kt b/rampsdk/src/main/java/network/ramp/sdk/ui/activity/RampWidgetActivity.kt index d40f634..e894a36 100644 --- a/rampsdk/src/main/java/network/ramp/sdk/ui/activity/RampWidgetActivity.kt +++ b/rampsdk/src/main/java/network/ramp/sdk/ui/activity/RampWidgetActivity.kt @@ -1,7 +1,6 @@ package network.ramp.sdk.ui.activity -import android.app.Activity import android.content.Intent import android.content.pm.PackageManager import android.net.Uri @@ -21,6 +20,7 @@ import network.ramp.sdk.facade.Config import network.ramp.sdk.facade.RampSDK.Companion.CONFIG_EXTRA import timber.log.Timber import network.ramp.sdk.events.model.* +import network.ramp.sdk.facade.RampSDK.Companion.URL_EXTRA import network.ramp.sdk.ui.webview.RampWidgetWebViewChromeClient.Companion.CAMERA_PERMISSION_REQUEST import network.ramp.sdk.ui.webview.RampWidgetWebViewClient @@ -45,7 +45,9 @@ internal class RampWidgetActivity : AppCompatActivity(), Contract.View { private lateinit var binding: WidgetActivityBinding - private lateinit var config: Config + private var config: Config? = null + + private var url: String? = "" private val jsInterface = RampSdkJsInterface( onPostMessage = { @@ -65,23 +67,36 @@ internal class RampWidgetActivity : AppCompatActivity(), Contract.View { jsInterface = jsInterface, fileChooserLauncher = fileChooserLauncher ) { filePathCallback = it } + intent.extras?.getParcelable(CONFIG_EXTRA)?.let { config = it - } ?: returnOnError("Config object cannot be null") + } + + url = intent.extras?.getString(URL_EXTRA) if (savedInstanceState == null) { - Timber.d(rampPresenter.buildUrl(config)) - securityCheck(intent)?.let { - binding.webView.loadUrl(it) - } ?: close() + config?.let { + Timber.d(rampPresenter.buildUrl(it)) + } + + val safeUrl = securityCheck(intent, config) + + when { + !url.isNullOrBlank() -> binding.webView.loadUrl(url!!) + + safeUrl != null -> binding.webView.loadUrl(safeUrl) + + else -> { + returnOnError("UNAUTHORIZED CALL") + } + } } } - private fun securityCheck(intent: Intent): String? = - if (isInternalIntent(intent) && rampPresenter.isUrlSafe(config.url)) + private fun securityCheck(intent: Intent, config: Config?): String? = + if (config != null && isInternalIntent(intent) && rampPresenter.isUrlSafe(config.url)) rampPresenter.buildUrl(config) else { - Timber.e("SECURITY ALERT - UNAUTHORIZED CALL") null } @@ -147,7 +162,6 @@ internal class RampWidgetActivity : AppCompatActivity(), Contract.View { if (grantResults[i] == PackageManager.PERMISSION_GRANTED) Timber.d("PERMISSION GRANTED ${permissions[i]}") } - } } diff --git a/settings.gradle b/settings.gradle index d4ae00d..67b26d2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,27 @@ -include ':rampsdk' +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven { url 'https://jitpack.io' } + } +} + rootProject.name='Ramp SDK' +include ':rampsdk' include ':demo' + + +rootProject.repositories { + google() + mavenCentral() + maven { url 'https://jitpack.io' } +} \ No newline at end of file