Skip to content

Commit 4cc2bee

Browse files
kponichterarahimklabermarianasamardzicCornul11ibabalau
authored
Atomic Swap app (Tribler#110)
* Atomic Swap app * Added atomic swap app * Downgraded library versions * Added new activity for the atomic swap app * Added fragments to atomic swap activity with navigation * Extracted wallet stuff to separate, common library * Extracted wallet stuff to separate, common library (ethereum) * GUI for the swap wallet * Fixed improper package names * View model and binding for atomic swap wallet (Bitcoin wallet) * Global Ethereum wallet (TODO: not working GUI yet) * Fixed data binding for Bitcoin and Ethereum wallets * Fixed Ethereum wallet * Display Ethereum instead of Wei * Fixed saving wallet password and keys to shared preferences * Add functionality for creating and claiming btc swaps. * Fix warning * Added TransactionMonitor class * added ipv8 community communication code * renamed some references * added AtomicSwapCommunity and the view for it, fixed the build * Trying to test btcswap * comment out default peer and add local regnet * add callbacks to swap code. * Fixed could not find tx error. turns out I was hashing the hash twice. * Added an alert when trade offered received * Created the wallet holder class * Received an Accept message * FIx some issues with the scripts * Fix test * Cleanup test * minor UI updates * Add swap reclaiming and test for it. * Add eth swap contract and generate wrapper for it. * Create and broadcast a transaction, wait for confirmation and notify the recipient * Fix hashing hash twice. * Fixed using nullable variable * Fix some claim functionality and added test. * BTC and ETH nodes in Docker containers + README * Changed the peer address for regtest * Fixed mistake * Created a callback for recipient's transaction * Handle Complete message * Trade offers fragment * Handle Complete message * Swap fragment (WIP) * Send tx instead txId * fix swap * Added a dialog after the claim transaction gets confirmed * Swap fragment * Replaced transaction hash with transaction * Add fix for web3j not generating wrapper for payable fun. * Start listening to claimed transaction * Added code to extract the secret * Show a dialog when secret gets revealed * Listen for secret revealed transactions * Bind offerId to secret revealed listener * Remove entry from watched addresses * Fix swap contract and tested that claim and reclaim works. * Added logic for Bob to claim transaction * Added some comments * Broadcast trade offer on button click * Code cleanup * Connect UI for creating a swap with the logic * use correct amounts * Auxiliary scripts for adding money to the dev nodes * Get eth swap to work * added trade offers trade item rough design * added missing id and extracted string * fix typos * add logic for remote nodes. * Remove unused ipv8 code * Uncomment ethereum stuff. * Organized code into folders * add trustchain validation,accept * Trade offers fragment cleanup * Added logs to the swap contract. * Refactored trade offer item layout, added item renderer * Trade offers adapter and GUI cleanup * removed alert dialogs * Catch an exception * Add offset to message decoding * Add alert when swap complete * refctored createClaimTx * Check if it is BTC on accepting a trade * Create Trade class * Fix import error * Refactor ethereum swap * Make BitcoinSwap use the Trade class * Added setOnComplete message to Trade class * Refactored the BitcoinSwap class * Delete unused adapter * Change messages and Trade class to account for ethereum. * Fixed some bugs * Make the setters private in the Trade class * Fix app crash. * Fix trade id casting error * Add create swap function for eth. don't deploy the contract every time. * Got eth <-> swap to work. * Fix swap not working * Kotlin linter fixes * Add code for adding/removing offers to trade offers list * Trade offers attachment (Tribler#1) * Commonized trade lists and making trade logic into the activity * Attached trustchain community callbacks to the trade offers adapter * Accepting trade offer and marking it as in progress * Changing trade offer status after completion (not tested yet) * update contract address * Fixing list update * alice is also able to see her own transasction * Fixed list refreshing * Fixed problem with creating proposal block (serializing enums) * only remove trade offer if user was not involved in it * Disabling possibility of accepting the trade by its creator * fix remove trade message * Removing trade offer from the Alice side * Fixed marking trade as completed * Removed pop-ups * add eth config * Remove some dead code * Fix bitcoin tests. Co-authored-by: Konrad Ponichtera <[email protected]> Co-authored-by: Ion Babalau <[email protected]> Co-authored-by: Mariana Samardzic <[email protected]> * Add readme * Update README.md * Update readme * Update readme * Update README.md * Run formatter * Update readme * Updated README of the atomic swap app, removed old IP addresses Co-authored-by: Rahim Klabér <[email protected]> Co-authored-by: Mariana Samardzic <[email protected]> Co-authored-by: Dan Plamadeala <[email protected]> Co-authored-by: Ion Babalau <[email protected]> * Fixed XML layout lint problems in atomic swap app * Code cleanup of the Atomic Swap app * Fix suppressing CheckResult * Code cleanup of the Atomic Swap app (minimum SDK version, commented code removal, exception logging streamlined) Co-authored-by: Rahim Klabér <[email protected]> Co-authored-by: Mariana Samardzic <[email protected]> Co-authored-by: Dan Plamadeala <[email protected]> Co-authored-by: Ion Babalau <[email protected]>
1 parent 9cf6ed3 commit 4cc2bee

File tree

97 files changed

+3711
-29
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+3711
-29
lines changed

Diff for: README.md

+12
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,18 @@ Every time a user opens MusicDAO, they are asked to reload the page in order to
169169
The feature-based models are gossiped along random walks through the network. At each peer they are merged and re-trained on peer's local data. The matrix factorization model seeks to learn a factorization of the user-song matrix. This means that one of the two factors contains only information on how users generally rate each song. This matrix can then be gossiped around the network while a user's personal vector as well as their listening history are kept private.
170170
- [More about federated machine learning using gossiping for music recommendations](gossipML/README.md)
171171

172+
### Atomic Swap
173+
174+
AtomicSwap app allows two users to exchange different cryptocurrencies without the involvement of a third party and without having to trust each other. This is achieved by implementing the Atomic Swap protocol.
175+
176+
User can create trade offers by sending Ipv8 messages indicating that they wish to trade to the Swap community. Others can then accept the offer by sending another Ipv8 message. The swap procedure starts when the initiator receives an accept message for their trade offer.
177+
178+
Below is a video demo that shows the steps to do an atomic swap.
179+
180+
<a href="https://user-images.githubusercontent.com/21971137/164297537-e8b4ff5f-a999-4e6d-b1e8-17135399848e.mp4" title="Swap Demo"><img src="https://user-images.githubusercontent.com/21971137/164298818-a152b7ca-6ebe-4038-a449-e6a246c7f1ab.png" alt="Alternate Text" /></a>
181+
182+
[More about The Atomic Swap app](atomic-swap/README.md)
183+
172184
### Do you want to add your own app?
173185

174186
- [Adding your own app to the TrustChain Super App](doc/AppTutorial.md)

Diff for: app/build.gradle

+5
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ android {
8383
exclude 'META-INF/kotlinx-coroutines-core.kotlin_module'
8484
exclude 'META-INF/DEPENDENCIES'
8585
}
86+
ndkVersion '21.1.6352462'
8687
}
8788

8889
repositories {
@@ -123,9 +124,13 @@ dependencies {
123124
implementation project(':ig-ssi')
124125
implementation project(':liquidity-pool')
125126
implementation project(':valuetransfer')
127+
implementation project(':atomic-swap')
126128
api(project(':common')){
127129
exclude group: 'net.java.dev.jna'
128130
}
131+
api(project(':common-ethereum')){
132+
exclude group: 'net.java.dev.jna'
133+
}
129134
api(project(':currencyii')){
130135
exclude group: 'net.java.dev.jna'
131136
}

Diff for: app/src/main/AndroidManifest.xml

+4
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@
109109
android:name="nl.tudelft.trustchain.liquidity.LiquidityPoolMainActivity"
110110
android:parentActivityName=".ui.dashboard.DashboardActivity" />
111111

112+
<activity
113+
android:name="nl.tudelft.trustchain.atomicswap.AtomicSwapActivity"
114+
android:parentActivityName=".ui.dashboard.DashboardActivity" />
115+
112116
<activity
113117
android:name="nl.tudelft.trustchain.valuetransfer.ValueTransferMainActivity"
114118
android:parentActivityName=".ui.dashboard.DashboardActivity"

Diff for: app/src/main/java/nl/tudelft/trustchain/app/AppDefinition.kt

+7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.annotation.ColorRes
55
import androidx.annotation.DrawableRes
66
import com.example.musicdao.MusicService
77
import nl.tudelft.trustchain.FOC.MainActivityFOC
8+
import nl.tudelft.trustchain.atomicswap.AtomicSwapActivity
89
import nl.tudelft.trustchain.common.R
910
import nl.tudelft.trustchain.explorer.ui.TrustChainExplorerActivity
1011
import nl.tudelft.trustchain.currencyii.CurrencyIIMainActivity
@@ -111,5 +112,11 @@ enum class AppDefinition(
111112
R.color.colorPrimaryValueTransfer,
112113
ValueTransferMainActivity::class.java,
113114
true,
115+
),
116+
ATOMIC_SWAP(
117+
R.drawable.ic_atomic_swap_24dp,
118+
"Atomic Swap",
119+
R.color.blue,
120+
AtomicSwapActivity::class.java
114121
)
115122
}

Diff for: app/src/main/java/nl/tudelft/trustchain/app/TrustChainApplication.kt

+58
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ import nl.tudelft.ipv8.sqldelight.Database
3838
import nl.tudelft.ipv8.util.hexToBytes
3939
import nl.tudelft.ipv8.util.toHex
4040
import nl.tudelft.trustchain.app.service.TrustChainService
41+
import nl.tudelft.trustchain.atomicswap.AtomicSwapCommunity
42+
import nl.tudelft.trustchain.atomicswap.AtomicSwapTrustchainConstants
43+
import nl.tudelft.trustchain.atomicswap.ui.swap.LOG
4144
import nl.tudelft.trustchain.common.DemoCommunity
4245
import nl.tudelft.trustchain.common.MarketCommunity
4346
import nl.tudelft.trustchain.common.bitcoin.WalletService
@@ -79,6 +82,7 @@ class TrustChainApplication : Application() {
7982
createTFTPCommunity(),
8083
createDemoCommunity(),
8184
createWalletCommunity(),
85+
createAtomicSwapCommunity(),
8286
createMarketCommunity(),
8387
createCoinCommunity(),
8488
createVotingCommunity(),
@@ -179,6 +183,52 @@ class TrustChainApplication : Application() {
179183
}
180184
}
181185
)
186+
187+
trustchain.registerTransactionValidator(
188+
AtomicSwapTrustchainConstants.ATOMIC_SWAP_COMPLETED_BLOCK,
189+
object : TransactionValidator {
190+
override fun validate(
191+
block: TrustChainBlock,
192+
database: TrustChainStore
193+
): ValidationResult {
194+
if ((
195+
block.transaction[AtomicSwapTrustchainConstants.TRANSACTION_FROM_COIN] != null &&
196+
block.transaction[AtomicSwapTrustchainConstants.TRANSACTION_TO_COIN] != null &&
197+
block.transaction[AtomicSwapTrustchainConstants.TRANSACTION_FROM_AMOUNT] != null &&
198+
block.transaction[AtomicSwapTrustchainConstants.TRANSACTION_TO_AMOUNT] != null &&
199+
block.transaction[AtomicSwapTrustchainConstants.TRANSACTION_OFFER_ID] != null
200+
) ||
201+
block.isAgreement
202+
) {
203+
return ValidationResult.Valid
204+
} else {
205+
return ValidationResult.Invalid(listOf("Proposal invalid"))
206+
}
207+
}
208+
}
209+
)
210+
211+
trustchain.registerBlockSigner(
212+
AtomicSwapTrustchainConstants.ATOMIC_SWAP_COMPLETED_BLOCK,
213+
object : BlockSigner {
214+
override fun onSignatureRequest(block: TrustChainBlock) {
215+
trustchain.createAgreementBlock(block, mapOf<Any?, Any?>())
216+
Log.d(LOG, "Bob created a trustchain agreement block")
217+
}
218+
}
219+
)
220+
221+
trustchain.addListener(
222+
AtomicSwapTrustchainConstants.ATOMIC_SWAP_COMPLETED_BLOCK,
223+
object : BlockListener {
224+
override fun onBlockReceived(block: TrustChainBlock) {
225+
Log.d(
226+
"AtomicSwap",
227+
"onBlockReceived: ${block.blockId} ${block.transaction}"
228+
)
229+
}
230+
}
231+
)
182232
}
183233

184234
private fun createWalletCommunity(): OverlayConfiguration<AttestationCommunity> {
@@ -193,6 +243,14 @@ class TrustChainApplication : Application() {
193243
)
194244
}
195245

246+
private fun createAtomicSwapCommunity(): OverlayConfiguration<AtomicSwapCommunity> {
247+
val randomWalk = RandomWalk.Factory()
248+
return OverlayConfiguration(
249+
Overlay.Factory(AtomicSwapCommunity::class.java),
250+
listOf(randomWalk)
251+
)
252+
}
253+
196254
private fun createDiscoveryCommunity(): OverlayConfiguration<DiscoveryCommunity> {
197255
val randomWalk = RandomWalk.Factory()
198256
val randomChurn = RandomChurn.Factory()

Diff for: app/src/main/res/xml/network_security_config.xml

+2
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@
55
<domain includeSubdomains="true">134.122.59.107</domain>
66
<!-- This is for RegTest faucet, to claim some starter money -->
77
<domain includeSubdomains="true">131.180.27.224</domain>
8+
<!-- IP address of the host machine when ran from the Android emulator -->
9+
<!-- <domain includeSubdomains="true">10.0.2.2</domain>-->
810
</domain-config>
911
</network-security-config>

Diff for: atomic-swap/README.md

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Atomic Swap app
2+
3+
This app allows for the trading of Ethereum and Bitcoin through atomic swaps.
4+
5+
## Our approach
6+
7+
The Atomic Swap Protocol
8+
Our app currently supports the following atomic swaps:
9+
- Bitcoin <-> Bitcoin
10+
- Ethereum <-> Bitcoin
11+
12+
We achieve atomic swaps by using hash timelocks.
13+
14+
The protocol we use consists of 4 messages; broadcast, accept, initiate and complete. Say we have two users, Alice and Bob. Alice swapping with Bob would look like this:
15+
1. First Alice broadcasts a message indicating the details of the trade she wants to do.
16+
17+
2. Bob receives this message and sends an accept message containing his Bitcoin and Ethereum addresses.
18+
19+
3. Alice receives the accept message,generates a secret and locks funds for Bob in a hash timelock. The funds can be claimed by Bob with the secret. Afterwards, Alice sends an initiate message to Bob containing the transaction or transaction hash of Alice locking funds , the hash of the secret and Alice’s addresses.
20+
21+
4. Bob receives the initiate message and locks funds for Alice in a hash timelock. The funds can be claimed by Alice with the secret or reclaimed by Bob after some time. Bob also starts watching the blockchain for when Alice claims the funds that Bob locked. When Alice claims the funds, the secret is revealed and Bob can claim his funds. Afterwards, Bob sends Alice a complete message containing the transaction or transaction hash of the transaction where Bob locked the funds.
22+
23+
5. Alice receives the complete message and claims the funds Bob locked. Bob realizes this and then claims the funds Alice locked.
24+
25+
<img src="https://user-images.githubusercontent.com/21971137/164295370-bd3a8cdb-21e2-4773-89ee-dbbb038221cc.png" width="280">
26+
27+
### Bitcoin specifics
28+
29+
We use [bitcoinj](https://bitcoinj.org/) to interact with the Bitcoin network.
30+
31+
On the Bitcoin side, the hash timelock is achieved using a single Bitcoin script that contains the logic for either claiming or reclaiming.
32+
When Bitcoin is being swapped and we lock funds in a hash timelock, we send the entire transaction to the counterparty. This is due to the fact that bitcoinj, does not support retrieving transactions by hash if we were not already watching an address involved in that transaction.
33+
34+
### Ethereum specifics
35+
36+
We use [web3j](https://github.com/web3j/web3j) to interact with the Ethereum network.
37+
On the Ethereum side, the hash timelock is achieved using a solidity smart contract.
38+
The contract can be found at `src\main\java\nl\tudelft\trustchain\atomicswap\swap\eth\swap_contract.sol`
39+
40+
### IPv8
41+
42+
We use IPv8 for communication and sending/receiving messages. We have created the AtomicSwap Community for this purpose, which implements callbacks for all the message types.
43+
44+
### TrustChain
45+
46+
Currently, we use TrustChain to store completed transactions between two users. The blocks will have the ATOMIC_SWAP_COMPLETED_BLOCK type, and each block contains the following information about the transaction: offer id, the source and destination coin, the sent and received amount of currency.
47+
48+
## Challenges
49+
50+
- Jvm cryptocurrency libraries are not as well maintained as Javascript libraries. We had some problems getting everything to work with Bitcoinj and Web3j.
51+
- The superapp has many logs, so trying to debug our part was hard in the beginning. Additionally, sometimes the logs would not show up.
52+
- Testing and debugging the code involves running the application on two devices and this setup therefore consumes much time.
53+
54+
## Future work
55+
56+
- Display more detailed status of your trade - maybe be able to expand the trade offer item, and view what is the current progress; we are already doing this in the logs, but not UI.
57+
- TrustScore - crawl the chain of the user and calculate a score based on how many successful transactions that user has; this will be the trust/reliability score for that user.
58+
- Persist swaps to memory to continue after the app is closed.
59+
- Handle different kinds of exceptions e.g. when a user does not have enough money for the swap.
60+
- Implement the reclaiming of the money if something does not go as expected. Currently the money will stay locked and the user has no way of retrieving it.
61+
62+
## Usage instructions
63+
64+
### Preparing the coin nodes
65+
66+
In order for the app to work, it has to be able to connect with Bitcoin and Ethereum (geth) nodes.
67+
These can be deployed with the included Docker Compose file in the `docker` directory in the
68+
project’s root by [following the instructions](../docker/README.md).
69+
70+
Once the nodes are deployed, the addresses have to be configured in the following build config
71+
variables:
72+
73+
* `BITCOIN_DEFAULT_PEER` in the common module’s `build.gradle` file - IP address of the machine with
74+
the Bitcoin node
75+
* `ETH_HTTP_URL` in the common-ethereum module’s `build.gradle` file - URL of the node, complete
76+
with the protocol (HTTP or HTTPS) and the port. In case the HTTPS protocol is used, the
77+
certificate has to be signed by the trusted certificate authority (eg. Let’s Encrypt).
78+
79+
In case the Trustchain app is running on Android emulator, developer can launch Docker Compose
80+
services locally and configure the aforementioned variables to point at the 10.0.2.2 address, on
81+
which the host system is accessible.
82+
83+
Additionally, the IP address(es) have to be added to the `network_security_config.xml`
84+
of the main application module, in order to allow the Trustchain’s application to communicate with
85+
the server.
86+
87+
### Deploying Ethereum Contract
88+
89+
In order for the atomic swaps involving Ethereum to work, the Ethereum contract responsible for this
90+
needs to be deployed. One way this can be done is to use [Remix](https://remix.ethereum.org/) and
91+
copy, add the smart contract file to remix and deploy it by following
92+
this [guide](https://remix-ide.readthedocs.io/en/latest/run.html).
93+
94+
After deploying the contract the build config variables need to be changed in the `build.gradle` of
95+
the atomic swap module:
96+
`ETH_SWAP_CONTRACT` should be changed to the contract address.
97+
`ETH_CHAIN_ID` should be changed to the chain id of the network the contract was deployed at.
98+
99+
### Process of making a swap
100+
101+
A user creates a swap offer in the swap tab and broadcasts it to all users using ipv8.
102+
103+
<img src="https://user-images.githubusercontent.com/21971137/164291922-959cd2b8-a848-4f10-a4bb-4cadf8fbe617.png" width="280">
104+
105+
106+
A user sees all available swap offers in the trade offers tab and can start a swap by clicking on the accept button of the desired swap offer.
107+
108+
<img src="https://user-images.githubusercontent.com/21971137/164292243-63417f62-8cd3-4758-ac29-1cad77ee05f3.png" width="280">
109+
110+
111+
While the atomic swap is in progress, the status of the swap in the swap offers tab will be in progress and when the swap finishes, the status will change to completed.
112+
113+
<img src="https://user-images.githubusercontent.com/21971137/164292369-2191bfd2-5036-4e01-8b68-da0d444f802a.png" width="280"><img src="https://user-images.githubusercontent.com/21971137/164292663-d2b20d5c-c594-4f7e-9375-702bf8350e35.png" width="280">
114+
115+
The balance in the users wallets will change accordingly.
116+
117+
<img src="https://user-images.githubusercontent.com/21971137/164292774-640abb61-cd25-4b26-8a7f-8a9f6c800332.png" width="280"><img src="https://user-images.githubusercontent.com/21971137/164292789-1d064394-87a7-4c62-a22c-602c55128be3.png" width="280">
118+
119+
## Problems
120+
121+
- Currently, there is a random bug where IPV8 fails to decode messages correctly. Because of this
122+
the swap may not work everytime.
123+
- We created some tests, but we removed them since adding them somehow made the IPV8 decode problem
124+
worse.

Diff for: atomic-swap/build.gradle

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
plugins {
2+
id 'com.android.library'
3+
id 'org.jetbrains.kotlin.android'
4+
id 'kotlin-android-extensions'
5+
6+
}
7+
8+
android {
9+
compileSdkVersion 31
10+
buildToolsVersion "29.0.3"
11+
12+
defaultConfig {
13+
minSdkVersion 22
14+
targetSdkVersion 31
15+
16+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17+
consumerProguardFiles "consumer-rules.pro"
18+
19+
buildConfigField "String", "ETH_SWAP_CONTRACT", "\"0x8790bca86ee29530a17a9bf80bd9e6e3503ff4d1\""
20+
buildConfigField "Long", "ETH_CHAIN_ID", "1337L"
21+
}
22+
23+
buildTypes {
24+
release {
25+
minifyEnabled false
26+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27+
}
28+
}
29+
compileOptions {
30+
sourceCompatibility JavaVersion.VERSION_1_8
31+
targetCompatibility JavaVersion.VERSION_1_8
32+
}
33+
kotlinOptions {
34+
jvmTarget = '1.8'
35+
allWarningsAsErrors = true
36+
}
37+
buildFeatures {
38+
viewBinding true
39+
}
40+
testOptions {
41+
unitTests.returnDefaultValues = true
42+
}
43+
}
44+
45+
repositories {
46+
flatDir {
47+
dirs '../common/libs'
48+
}
49+
}
50+
51+
dependencies {
52+
implementation project(":common")
53+
implementation project(":common-bitcoin")
54+
implementation project(":common-ethereum")
55+
implementation project(":ipv8-android")
56+
57+
// TODO: Newer than the rest of apps - should we leave or downgrade
58+
implementation 'androidx.core:core-ktx:1.3.2'
59+
implementation 'androidx.appcompat:appcompat:1.2.0'
60+
implementation 'com.google.android.material:material:1.3.0'
61+
62+
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
63+
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
64+
implementation 'androidx.preference:preference-ktx:1.1.0'
65+
66+
implementation 'com.github.MattSkala:recyclerview-itemadapter:0.4'
67+
68+
testImplementation 'junit:junit:4.12'
69+
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
70+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
71+
}

Diff for: atomic-swap/consumer-rules.pro

Whitespace-only changes.

Diff for: atomic-swap/proguard-rules.pro

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile

0 commit comments

Comments
 (0)