Added method to create Account using the Agorise's faucet, and also making use of Retrofit's suggested ServiceGenerator.
This commit is contained in:
parent
860947b351
commit
78909498c0
9 changed files with 301 additions and 9 deletions
|
@ -72,9 +72,12 @@ dependencies {
|
||||||
implementation "com.jakewharton.rxbinding3:rxbinding:$rx_bindings_version"
|
implementation "com.jakewharton.rxbinding3:rxbinding:$rx_bindings_version"
|
||||||
implementation "com.jakewharton.rxbinding3:rxbinding-material:$rx_bindings_version" // Material Components widgets
|
implementation "com.jakewharton.rxbinding3:rxbinding-material:$rx_bindings_version" // Material Components widgets
|
||||||
implementation "com.jakewharton.rxbinding3:rxbinding-appcompat:$rx_bindings_version" // AndroidX appcompat widgets
|
implementation "com.jakewharton.rxbinding3:rxbinding-appcompat:$rx_bindings_version" // AndroidX appcompat widgets
|
||||||
// Retrofit
|
// Retrofit & OkHttp
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
|
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
|
||||||
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
|
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
|
||||||
|
implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
|
||||||
|
implementation 'com.squareup.okhttp3:okhttp:3.12.0'
|
||||||
|
implementation 'com.squareup.okhttp3:logging-interceptor:3.5.0'
|
||||||
//Firebase
|
//Firebase
|
||||||
implementation 'com.google.firebase:firebase-core:16.0.6'
|
implementation 'com.google.firebase:firebase-core:16.0.6'
|
||||||
implementation 'com.google.firebase:firebase-crash:16.2.1'
|
implementation 'com.google.firebase:firebase-crash:16.2.1'
|
||||||
|
|
|
@ -8,6 +8,7 @@ import android.view.ViewGroup
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import com.jakewharton.rxbinding3.widget.textChanges
|
import com.jakewharton.rxbinding3.widget.textChanges
|
||||||
import cy.agorise.bitsybitshareswallet.R
|
import cy.agorise.bitsybitshareswallet.R
|
||||||
|
import cy.agorise.bitsybitshareswallet.network.FaucetService
|
||||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||||
import cy.agorise.bitsybitshareswallet.utils.containsDigits
|
import cy.agorise.bitsybitshareswallet.utils.containsDigits
|
||||||
import cy.agorise.bitsybitshareswallet.utils.containsVowels
|
import cy.agorise.bitsybitshareswallet.utils.containsVowels
|
||||||
|
@ -21,12 +22,20 @@ import cy.agorise.graphenej.models.JsonRpcResponse
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import kotlinx.android.synthetic.main.fragment_create_account.*
|
import kotlinx.android.synthetic.main.fragment_create_account.*
|
||||||
import org.bitcoinj.core.ECKey
|
import org.bitcoinj.core.ECKey
|
||||||
|
import retrofit2.Callback
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import cy.agorise.bitsybitshareswallet.models.FaucetRequest
|
||||||
|
import cy.agorise.bitsybitshareswallet.models.FaucetResponse
|
||||||
|
import cy.agorise.bitsybitshareswallet.network.ServiceGenerator
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.Response
|
||||||
|
|
||||||
class CreateAccountFragment : ConnectedFragment() {
|
|
||||||
|
class CreateAccountFragment : BaseAccountFragment() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "CreateAccountFragment"
|
private const val TAG = "CreateAccountFragment"
|
||||||
|
@ -34,10 +43,12 @@ class CreateAccountFragment : ConnectedFragment() {
|
||||||
private const val BRAINKEY_FILE = "brainkeydict.txt"
|
private const val BRAINKEY_FILE = "brainkeydict.txt"
|
||||||
private const val MIN_ACCOUNT_NAME_LENGTH = 8
|
private const val MIN_ACCOUNT_NAME_LENGTH = 8
|
||||||
|
|
||||||
private const val RESPONSE_GET_ACCOUNT_BY_NAME = 1
|
// Used when trying to validate that the account name is available
|
||||||
|
private const val RESPONSE_GET_ACCOUNT_BY_NAME_VALIDATION = 1
|
||||||
|
// Used when trying to obtain the info of the newly created account
|
||||||
|
private const val RESPONSE_GET_ACCOUNT_BY_NAME_CREATED = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var mBrainKey: BrainKey
|
|
||||||
private lateinit var mAddress: String
|
private lateinit var mAddress: String
|
||||||
|
|
||||||
/** Variables used to store the validation status of the form fields */
|
/** Variables used to store the validation status of the form fields */
|
||||||
|
@ -87,6 +98,7 @@ class CreateAccountFragment : ConnectedFragment() {
|
||||||
btnCancel.setOnClickListener { findNavController().navigateUp() }
|
btnCancel.setOnClickListener { findNavController().navigateUp() }
|
||||||
|
|
||||||
btnCreate.isEnabled = false
|
btnCreate.isEnabled = false
|
||||||
|
btnCreate.setOnClickListener { createAccount() }
|
||||||
|
|
||||||
// Generating BrainKey
|
// Generating BrainKey
|
||||||
generateKeys()
|
generateKeys()
|
||||||
|
@ -104,7 +116,7 @@ class CreateAccountFragment : ConnectedFragment() {
|
||||||
val id = mNetworkService?.sendMessage(GetAccountByName(accountName), GetAccountByName.REQUIRED_API)
|
val id = mNetworkService?.sendMessage(GetAccountByName(accountName), GetAccountByName.REQUIRED_API)
|
||||||
|
|
||||||
if (id != null)
|
if (id != null)
|
||||||
responseMap[id] = RESPONSE_GET_ACCOUNT_BY_NAME
|
responseMap[id] = RESPONSE_GET_ACCOUNT_BY_NAME_VALIDATION
|
||||||
}
|
}
|
||||||
|
|
||||||
enableDisableCreateButton()
|
enableDisableCreateButton()
|
||||||
|
@ -157,7 +169,8 @@ class CreateAccountFragment : ConnectedFragment() {
|
||||||
if (responseMap.containsKey(response.id)) {
|
if (responseMap.containsKey(response.id)) {
|
||||||
val responseType = responseMap[response.id]
|
val responseType = responseMap[response.id]
|
||||||
when (responseType) {
|
when (responseType) {
|
||||||
RESPONSE_GET_ACCOUNT_BY_NAME -> handleAccountName(response.result)
|
RESPONSE_GET_ACCOUNT_BY_NAME_VALIDATION -> handleAccountNameValidation(response.result)
|
||||||
|
RESPONSE_GET_ACCOUNT_BY_NAME_CREATED -> handleAccountNameCreated(response.result)
|
||||||
}
|
}
|
||||||
responseMap.remove(response.id)
|
responseMap.remove(response.id)
|
||||||
}
|
}
|
||||||
|
@ -171,7 +184,7 @@ class CreateAccountFragment : ConnectedFragment() {
|
||||||
* Handles the response from the NetworkService's GetAccountByName call to decide if the user's suggested
|
* Handles the response from the NetworkService's GetAccountByName call to decide if the user's suggested
|
||||||
* account is available or not.
|
* account is available or not.
|
||||||
*/
|
*/
|
||||||
private fun handleAccountName(result: Any?) {
|
private fun handleAccountNameValidation(result: Any?) {
|
||||||
if (result is AccountProperties) {
|
if (result is AccountProperties) {
|
||||||
tilAccountName.helperText = ""
|
tilAccountName.helperText = ""
|
||||||
tilAccountName.error = getString(R.string.error__account_not_available)
|
tilAccountName.error = getString(R.string.error__account_not_available)
|
||||||
|
@ -185,6 +198,71 @@ class CreateAccountFragment : ConnectedFragment() {
|
||||||
enableDisableCreateButton()
|
enableDisableCreateButton()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the response from the NetworkService's GetAccountByName call and stores the information of the newly
|
||||||
|
* created account if the result is successful, shows a toast error otherwise
|
||||||
|
*/
|
||||||
|
private fun handleAccountNameCreated(result: Any?) {
|
||||||
|
if (result is AccountProperties) {
|
||||||
|
onAccountSelected(result, tietPin.text.toString())
|
||||||
|
} else {
|
||||||
|
context?.toast(getString(R.string.error__created_account_not_found))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the account-creation request to the faucet server.
|
||||||
|
* Only account name and public address is sent here.
|
||||||
|
*/
|
||||||
|
private fun createAccount() {
|
||||||
|
val accountName = tietAccountName.text.toString()
|
||||||
|
val faucetRequest = FaucetRequest(accountName, mAddress, Constants.FAUCET_REFERRER)
|
||||||
|
|
||||||
|
val sg = ServiceGenerator(Constants.FAUCET_URL)
|
||||||
|
val faucetService = sg.getService(FaucetService::class.java)
|
||||||
|
|
||||||
|
val call = faucetService.registerPrivateAccount(faucetRequest)
|
||||||
|
|
||||||
|
// Execute the call asynchronously. Get a positive or negative callback.
|
||||||
|
call.enqueue(object : Callback<FaucetResponse> {
|
||||||
|
override fun onResponse(call: Call<FaucetResponse>, response: Response<FaucetResponse>) {
|
||||||
|
// The network call was a success and we got a response
|
||||||
|
getCreatedAccountInfo(response.body())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<FaucetResponse>, t: Throwable) {
|
||||||
|
// the network call was a failure
|
||||||
|
MaterialDialog(context!!)
|
||||||
|
.title(R.string.title_error)
|
||||||
|
.message(cy.agorise.bitsybitshareswallet.R.string.error__faucet)
|
||||||
|
.negativeButton(android.R.string.ok)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCreatedAccountInfo(faucetResponse: FaucetResponse?) {
|
||||||
|
if (faucetResponse?.account != null) {
|
||||||
|
val id = mNetworkService?.sendMessage(GetAccountByName(faucetResponse.account?.name),
|
||||||
|
GetAccountByName.REQUIRED_API)
|
||||||
|
|
||||||
|
if (id != null)
|
||||||
|
responseMap[id] = RESPONSE_GET_ACCOUNT_BY_NAME_CREATED
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Private account creation failed ")
|
||||||
|
val content = if (faucetResponse?.error?.base?.size ?: 0 > 0) {
|
||||||
|
getString(R.string.error__faucet_template, faucetResponse?.error?.base?.get(0))
|
||||||
|
} else {
|
||||||
|
getString(R.string.error__faucet_template, "None")
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialDialog(context!!)
|
||||||
|
.title(R.string.title_error)
|
||||||
|
.message(text = content)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method that generates a fresh key that will be controlling the newly created account.
|
* Method that generates a fresh key that will be controlling the newly created account.
|
||||||
*/
|
*/
|
||||||
|
@ -197,11 +275,11 @@ class CreateAccountFragment : ConnectedFragment() {
|
||||||
|
|
||||||
val brainKeySuggestion = BrainKey.suggest(dictionary)
|
val brainKeySuggestion = BrainKey.suggest(dictionary)
|
||||||
mBrainKey = BrainKey(brainKeySuggestion, 0)
|
mBrainKey = BrainKey(brainKeySuggestion, 0)
|
||||||
val address = Address(ECKey.fromPublicOnly(mBrainKey.privateKey.pubKey))
|
val address = Address(ECKey.fromPublicOnly(mBrainKey?.privateKey?.pubKey))
|
||||||
Log.d(TAG, "brain key: $brainKeySuggestion")
|
Log.d(TAG, "brain key: $brainKeySuggestion")
|
||||||
Log.d(TAG, "address would be: " + address.toString())
|
Log.d(TAG, "address would be: " + address.toString())
|
||||||
mAddress = address.toString()
|
mAddress = address.toString()
|
||||||
tvBrainKey.text = mBrainKey.brainKey
|
tvBrainKey.text = mBrainKey?.brainKey
|
||||||
|
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.e(TAG, "IOException while trying to generate key. Msg: " + e.message)
|
Log.e(TAG, "IOException while trying to generate key. Msg: " + e.message)
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
package cy.agorise.bitsybitshareswallet.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class used to deserialize a the "account" object contained in the faucet response to the
|
||||||
|
* {@link cy.agorise.bitsybitshareswallet.network.FaucetService#registerPrivateAccount(FaucetRequest)} API call.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class FaucetAccount {
|
||||||
|
public String name;
|
||||||
|
public String owner_key;
|
||||||
|
public String active_key;
|
||||||
|
public String memo_key;
|
||||||
|
public String referrer;
|
||||||
|
public String refcode;
|
||||||
|
|
||||||
|
public FaucetAccount(String accountName, String address, String referrer){
|
||||||
|
this.name = accountName;
|
||||||
|
this.owner_key = address;
|
||||||
|
this.active_key = address;
|
||||||
|
this.memo_key = address;
|
||||||
|
this.refcode = referrer;
|
||||||
|
this.referrer = referrer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOwnerKey() {
|
||||||
|
return owner_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOwnerKey(String owner_key) {
|
||||||
|
this.owner_key = owner_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActiveKey() {
|
||||||
|
return active_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActiveKey(String active_key) {
|
||||||
|
this.active_key = active_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMemoKey() {
|
||||||
|
return memo_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMemoKey(String memo_key) {
|
||||||
|
this.memo_key = memo_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRefcode() {
|
||||||
|
return refcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRefcode(String refcode) {
|
||||||
|
this.refcode = refcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReferrer() {
|
||||||
|
return referrer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReferrer(String referrer) {
|
||||||
|
this.referrer = referrer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package cy.agorise.bitsybitshareswallet.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class used to encapsulate a faucet account creation request
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class FaucetRequest {
|
||||||
|
private FaucetAccount account;
|
||||||
|
|
||||||
|
public FaucetRequest(String accountName, String address, String referrer){
|
||||||
|
account = new FaucetAccount(accountName, address, referrer);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package cy.agorise.bitsybitshareswallet.models;
|
||||||
|
|
||||||
|
public class FaucetResponse {
|
||||||
|
public FaucetAccount account;
|
||||||
|
public Error error;
|
||||||
|
|
||||||
|
public class Error {
|
||||||
|
public String[] base;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package cy.agorise.bitsybitshareswallet.network
|
||||||
|
|
||||||
|
import cy.agorise.bitsybitshareswallet.models.FaucetRequest
|
||||||
|
import cy.agorise.bitsybitshareswallet.models.FaucetResponse
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Headers
|
||||||
|
import retrofit2.http.POST
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to the faucet service. The faucet is used in order to register new BitShares accounts.
|
||||||
|
*/
|
||||||
|
interface FaucetService {
|
||||||
|
|
||||||
|
@GET("/")
|
||||||
|
fun checkStatus(): Call<ResponseBody>
|
||||||
|
|
||||||
|
@Headers("Content-Type: application/json")
|
||||||
|
@POST("/api/v1/accounts")
|
||||||
|
fun registerPrivateAccount(@Body faucetRequest: FaucetRequest): Call<FaucetResponse>
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
package cy.agorise.bitsybitshareswallet.network;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
|
||||||
|
import okhttp3.Interceptor;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor;
|
||||||
|
import retrofit2.Retrofit;
|
||||||
|
import retrofit2.converter.gson.GsonConverterFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class ServiceGenerator{
|
||||||
|
public static String API_BASE_URL;
|
||||||
|
private static HttpLoggingInterceptor logging;
|
||||||
|
private static OkHttpClient.Builder httpClient;
|
||||||
|
private static Retrofit.Builder builder;
|
||||||
|
|
||||||
|
private static HashMap<Class<?>, Object> Services;
|
||||||
|
|
||||||
|
public ServiceGenerator(String apiBaseUrl) {
|
||||||
|
API_BASE_URL= apiBaseUrl;
|
||||||
|
logging = new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||||
|
httpClient = new OkHttpClient.Builder().addInterceptor(logging);
|
||||||
|
builder = new Retrofit.Builder()
|
||||||
|
.baseUrl(API_BASE_URL)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create(getGson()))
|
||||||
|
.addCallAdapterFactory(RxJava2CallAdapterFactory.create());
|
||||||
|
Services = new HashMap<Class<?>, Object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customizes the Gson instance with specific de-serialization logic
|
||||||
|
*/
|
||||||
|
private Gson getGson(){
|
||||||
|
GsonBuilder builder = new GsonBuilder();
|
||||||
|
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCallbackExecutor(Executor executor){
|
||||||
|
builder.callbackExecutor(executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> void setService(Class<T> klass, T thing) {
|
||||||
|
Services.put(klass, thing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T getService(Class<T> serviceClass) {
|
||||||
|
|
||||||
|
T service = serviceClass.cast(Services.get(serviceClass));
|
||||||
|
if (service == null) {
|
||||||
|
service = createService(serviceClass);
|
||||||
|
setService(serviceClass, service);
|
||||||
|
}
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <S> S createService(Class<S> serviceClass) {
|
||||||
|
|
||||||
|
httpClient.interceptors().add(new Interceptor() {
|
||||||
|
@Override
|
||||||
|
public okhttp3.Response intercept(Interceptor.Chain chain) throws IOException {
|
||||||
|
okhttp3.Request original = chain.request();
|
||||||
|
// Request customization: add request headers
|
||||||
|
okhttp3.Request.Builder requestBuilder = original.newBuilder().method(original.method(), original.body());
|
||||||
|
|
||||||
|
okhttp3.Request request = requestBuilder.build();
|
||||||
|
return chain.proceed(request);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
httpClient.readTimeout(5, TimeUnit.MINUTES);
|
||||||
|
httpClient.connectTimeout(5, TimeUnit.MINUTES);
|
||||||
|
OkHttpClient client = httpClient.build();
|
||||||
|
Retrofit retrofit = builder.client(client).build();
|
||||||
|
return retrofit.create(serviceClass);
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,12 @@ object Constants {
|
||||||
/** The minimum required length for a PIN number */
|
/** The minimum required length for a PIN number */
|
||||||
const val MIN_PIN_LENGTH = 6
|
const val MIN_PIN_LENGTH = 6
|
||||||
|
|
||||||
|
/** Name of the account passed to the faucet as the referrer */
|
||||||
|
const val FAUCET_REFERRER = "agorise"
|
||||||
|
|
||||||
|
/** Faucet URL used to create new accounts */
|
||||||
|
const val FAUCET_URL = "https://faucet.palmpay.io"
|
||||||
|
|
||||||
/** The user selected encrypted PIN */
|
/** The user selected encrypted PIN */
|
||||||
const val KEY_ENCRYPTED_PIN = "key_encrypted_pin"
|
const val KEY_ENCRYPTED_PIN = "key_encrypted_pin"
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,10 @@
|
||||||
<string name="text__verifying_account_availability">Verifying account availability…</string>
|
<string name="text__verifying_account_availability">Verifying account availability…</string>
|
||||||
<string name="error__account_not_available">Account not available</string>
|
<string name="error__account_not_available">Account not available</string>
|
||||||
<string name="text__account_is_available">Account is available</string>
|
<string name="text__account_is_available">Account is available</string>
|
||||||
|
<string name="title_error">Error</string>
|
||||||
|
<string name="error__faucet">The server returned an error. This might be due to a limitation purposefully set in place to disallow frequent requests coming from the same IP address in a short time lapse. Please wait 5 minutes and try again, or switch to a different network, for example from wifi to cell.</string>
|
||||||
|
<string name="error__faucet_template">The faucet returned an error. Msg: %1$s</string>
|
||||||
|
<string name="error__created_account_not_found">The app could not retrieve information about the newly created account</string>
|
||||||
|
|
||||||
<!-- Home -->
|
<!-- Home -->
|
||||||
<string name="title_transactions">Transactions</string>
|
<string name="title_transactions">Transactions</string>
|
||||||
|
|
Loading…
Reference in a new issue