Manage Your Tokens on the Ethereum Network with Samsung Blockchain Platform SDK

Samiul Hossain

Engineer, Samsung Developer Program

Here at Samsung, we want to stay on top of blockchain technology, which has the potential to revolutionize many aspects of our lives.

Cryptocurrency is a trillion dollar market and one of its biggest players is Ethereum, a blockchain technology that allows its users to create decentralized applications (DApps) powered by its own currency, ETH. The Samsung Blockchain Platform (SBP) SDK provides Android developers with a convenient platform and full set of features for developing DApps that interact with the Ethereum network.

Transactions are the heart of any blockchain-based system. In Ethereum, transactions can be initiated by either an account or a smart contract. The SBP SDK supports ETH transactions, ERC-20 token transactions, and ERC-721 token transactions.

This article describes a sample application that demonstrates how to use the SBP SDK to store and transfer ERC-20 tokens, a primary transaction method on the Ethereum network. You can follow along with the demonstration by downloading the sample application.

The sample application connects to the hardware wallet on the device and retrieves your account details. You can then add a token to the account and create a transaction. Each of these actions has been implemented with a corresponding button to help you understand the sequence of steps and visualize the result.

Sample application

Figure 1: Sample application

Prerequisites

To implement SBP SDK features in an Android application, you must integrate the SDK by downloading the AAR file and adding its required dependencies.

Retrieving account details

To retrieve your account details:

  1. Configure and create an instance of the SBlockchain class, which implements the SBP SDK.

    In the sample application, to call the OnClickObjectCreate() method and create an instance of the SBlockchain class, select Create object.

    When the instance is successfully created, the message "Object has been created" is displayed.

    SBlockchain class instance created

    Figure 2: "SBlockchain" class instance created

  2. Connect to the hardware wallet.

    Hardware wallets are special devices that provide secure storage for keys. The SBP SDK supports Ledger Nano S, Ledger Nano X, and Samsung Blockchain Keystore wallets. The sample application implements support for the Samsung Blockchain Keystore, which is a hardware wallet preloaded on selected Galaxy devices.

    The SBlockchain class allows you to access the hardwareWalletManager class instance, which enables connecting to the hardware wallet.

    Hardware wallet connected

    Figure 3: Hardware wallet connected

  3. Restore the account information.

    SBP SDK manages each blockchain address as an account. The account contains the information required to sign a transaction, including the public and private key pair. You can both generate accounts for and restore your accounts from the Ethereum network.

    Once you have restored your accounts, they are saved and their details can be retrieved requiring a network operation. You only need to restore the accounts again when you change wallets.

    Account address successfully retrieved

    Figure 4: Account address successfully retrieved

Sending tokens

To send an ERC-20 token:

  1. Add a token to the account.

    In the sample application, to add a token to the account, enter the contract address in the "Contract Address" field and select Add token.

    Token added

    Figure 5: Token added

    The following code snippet illustrates how the sample application implements adding a token.

    public void onClickAddTokenAccount(View view){
            if (TextUtils.isEmpty(tokenAddressEditText.getText().toString()))
            {
                Toast.makeText(MainActivity.this, "Please add a token address", Toast.LENGTH_SHORT).show();
                return;
            }
            addedTokenAddress = tokenAddressEditText.getText().toString();
            boolean check = checkExistingTokenAccount();
            if(check){
                Toast.makeText(MainActivity.this, "Token already added", Toast.LENGTH_SHORT).show();
                getTokenBalanceButton.setEnabled(true);
                return;
            }
            ethereumService.addTokenAddress((EthereumAccount) mFirstAccount, addedTokenAddress).setCallback(new ListenableFutureTask.Callback<EthereumAccount>() {
                @Override
                public void onSuccess(final EthereumAccount account) {
                    Log.d(LOG_TAG, "onSuccess: addTokenAccount " + account);
                    runOnUiThread(()->{
                        getTokenBalanceButton.setEnabled(true);
                    });
                    getAccount();
                }
    
                @Override
                public void onFailure(
                        @NonNull final ExecutionException exception) {
                    Log.e(LOG_TAG, "onFailure: addTokenAccount " + exception);
                }
    
                @Override
                public void onCancelled(@NotNull InterruptedException exception) {
                    Log.e(LOG_TAG, "onFailure: addTokenAccount " + exception);
                }
            });
    
        }
    
        // Check whether the token is already added to the account
        private boolean checkExistingTokenAccount(){
            for (Account currentAccount : accountList) {
                if (currentAccount.getAddress().equals(mFirstAccount.getAddress())) {
                    if (addedTokenAddress.equals(((EthereumAccount) currentAccount).getTokenAddress())) {
                        return true;
                    }
                }
            }
            return false;
        }
    
  2. Retrieve the token balance.

    To ensure you have sufficient balance to make a transaction, you can check the token balance.

    In the sample application, select Get token balance. This is a network operation and can take some time. The retrieved balance is displayed below the "Get token balance" button.

    Token balance retrieved

    Figure 6: Token balance retrieved

    The following code snippet illustrates how to perform this task in two steps: First, retrieve the account associated with the token address with the getTokenAccount() method. Next, pass the account into the getTokenBalance() method of the SDK to retrieve the token balance.

    private EthereumAccount getTokenAccount(){
            EthereumAccount ethereumAccount = null;
            for (Account currentAccount : accountList) {
                if (currentAccount.getAddress().equals(mFirstAccount.getAddress())) {
                    if (addedTokenAddress.equals(((EthereumAccount) currentAccount).getTokenAddress())) {
                        ethereumAccount = (EthereumAccount) currentAccount;
                    }
                }
            }
            return ethereumAccount;
        }
    
        public void onClickGetTokenbalance(View view){
            mTokenAccount = getTokenAccount();
            if(mTokenAccount == null){
                Log.e(LOG_TAG, "Token Account is null");
            }
            ethereumService.getTokenBalance(mTokenAccount).setCallback(new ListenableFutureTask.Callback<BigInteger>() {
                @Override
                public void onSuccess(BigInteger tokenBalance) {
                    runOnUiThread(()->{
                        tokenBalanceText.setEnabled(true);
                        gasPriceButton.setEnabled(true);
                        tokenBalanceText.setText(tokenBalance.toString());
                    });
                }
    
                @Override
                public void onFailure(@NonNull ExecutionException e) {
                    Log.e(LOG_TAG, "Fetching balance is failed.");
                    Log.e(LOG_TAG, "" + e.getMessage());
                }
    
                @Override
                public void onCancelled(@NonNull InterruptedException e) {
                    Log.e(LOG_TAG, "Fetching balance is canceled.");
                }
            });
        }
    
  3. Retrieve the gas fee.

    Network operations on the Ethereum network require gas, which is a unit that measures the effort required by the network to complete the operation. To perform a transaction, you must retrieve the gas fee and the gas limit of the transaction. The gas fee is determined by how quickly you want the transaction to be performed.

    In the sample application, enter the destination address and amount for the transaction, select the desired transaction speed, then select Gas price.

    Gas fee retrieved

    Figure 7: Gas fee retrieved

    The following code snippet illustrates how to retrieve the gas fee.

    public void OnClickGasPrice(View view) {
    
            ethereumService.getFeeInfo(EthereumTransactionType.EIP1559).setCallback(new ListenableFutureTask.Callback<FeeInfo>() {
                @Override
                public void onSuccess(FeeInfo feeInfo) {
                    mEthereumFeeInfo = (Eip1559FeeInfo) feeInfo;;
                    Log.i(LOG_TAG, "Fee info is fetched successfully.");
                    runOnUiThread(() -> {
                                gasLimitButton.setEnabled(true);
                                Toast.makeText(getApplicationContext(), "Fee info is fetched successfully.", Toast.LENGTH_SHORT).show();
                            }
                    );
                }
    
                @Override
                public void onFailure(@NotNull ExecutionException e) {
                    Log.e(LOG_TAG, "Fetching Fee info is failed.");
                    Log.e(LOG_TAG, "" + e.getMessage());
                }
    
                @Override
                public void onCancelled(@NotNull InterruptedException e) {
                    Log.e(LOG_TAG, "Fetching Fee info is canceled.");
                }
            });
        }
    
  4. Retrieve the gas limit.

    The gas limit represents the maximum amount of work that may be required for the transaction.

    In the sample application, to retrieve the gas limit, select Gas limit. The limit is calculated and displayed next to the "Gas limit" button.

    Gas limit retrieved

    Figure 8: Gas limit retrieved

    The following code snippet illustrates how to retrieve the gas limit.

        public void onClickGasLimit(View view) {
    
            String toAddress = toAddressEditText.getText().toString();
            String amount = sendAmountEditText.getText().toString();
    
            if (toAddress.isEmpty() || amount.isEmpty()) {
                Toast.makeText(getApplicationContext(), "Fill Send Address and Amount Field", Toast.LENGTH_SHORT).show();
            } else if(!ethereumService.isValidAddress(toAddress)){
                Toast.makeText(getApplicationContext(), "Invalid Address.", Toast.LENGTH_SHORT).show();
            } else {
                BigDecimal sendAmount = new BigDecimal(amount);
                BigInteger convertedSendAmount = EthereumUtils.convertEthToWei(sendAmount);
    
                List<Type> inParams = Arrays.asList(new Address(toAddress), new Uint(convertedSendAmount));
                List outParams = Arrays.asList(new TypeReference<Bool>() {});
                String encodedFunction = FunctionEncoder.encode(new Function("transfer", inParams, outParams));
    
                ethereumService.estimateGasLimit((EthereumAccount) mFirstAccount, toAddress, null, encodedFunction).setCallback(new ListenableFutureTask.Callback<BigInteger>() {
                    @Override
                    public void onSuccess(BigInteger bigInteger) {
                        mGasLimit = bigInteger;
                        Log.i(LOG_TAG, "Gas limit is fetched successfully.");
                        Log.i(LOG_TAG, "Gas limit is:" + bigInteger.toString());
                        runOnUiThread(() -> {
                            sendButton.setEnabled(true);
                            gasLimitText.setText(bigInteger.toString());
                        });
                    }
    
                    @Override
                    public void onFailure(@NotNull ExecutionException e) {
                        Log.e(LOG_TAG, "Fetching Gas limit has failed.");
                        Log.e(LOG_TAG, "" + e.getMessage());
                    }
    
                    @Override
                    public void onCancelled(@NotNull InterruptedException e) {
                        Log.e(LOG_TAG, "Fetching Gas limit has been canceled.");
                    }
                });
            }
    
        }
    
  5. Send the tokens.

    In the sample application, to send tokens using your available balance, select Send.

    Transaction created

    Figure 9: Transaction created

    The following code snippet illustrates how to create a transaction.

    public void onClickSendToken(View view) {
    
            if (TextUtils.isEmpty(toAddressEditText.getText().toString()))
            {
                Toast.makeText(MainActivity.this, "to Address field must be filled!", Toast.LENGTH_SHORT).show();
                return;
            }
    
            if (TextUtils.isEmpty(sendAmountEditText.getText().toString()))
            {
                Toast.makeText(MainActivity.this, "send amount field must be filled!", Toast.LENGTH_SHORT).show();
                return;
            }
    
            String toAddress = toAddressEditText.getText().toString();
            BigInteger sendAmount = new BigInteger(sendAmountEditText.getText().toString());
    
            // Retrieve the gas price
            int transactionSpeedID = transactionSpeedGroup.getCheckedRadioButtonId();
            if (transactionSpeedID == -1) {
                Toast.makeText(getApplicationContext(), "Select a Transaction Speed.", Toast.LENGTH_SHORT).show();
            } else {
                switch (transactionSpeedID) {
                    case R.id.transaction_speed_slow:
                        mGasPrice = mEthereumFeeInfo.getSlowPriorityFee();
                        Log.d(LOG_TAG, "GasPrice: " + mGasPrice);
                        break;
                    case R.id.transaction_speed_normal:
                        mGasPrice = mEthereumFeeInfo.getNormalPriorityFee();
                        Log.d(LOG_TAG, "GasPrice: " + mGasPrice);
                        break;
                    case R.id.transaction_speed_fast:
                        mGasPrice = mEthereumFeeInfo.getFastPriorityFee();
                        Log.d(LOG_TAG, "GasPrice: " + mGasPrice);
                        break;
                }
            }
    
            if(transactionSpeedID != -1) {
                try {
                    ethereumService.sendTokenTransaction(
                            mHardwareWallet,
                            (EthereumAccount) mTokenAccount,
                            toAddress,
                            addedTokenAddress,
                            mGasPrice,
                            mEthereumFeeInfo.getEstimatedBaseFee().add(mGasPrice),
                            mGasLimit,
                            sendAmount,
                            null
                        ).setCallback(new ListenableFutureTask.Callback<TransactionResult>() {
                            @Override
                            public void onSuccess(TransactionResult transactionResult) {
                                Log.d(LOG_TAG, "Transaction Hash: " + transactionResult.getHash());
                                runOnUiThread(() ->
                                        Toast.makeText(getApplicationContext(), "Transaction Hash: " + transactionResult.getHash(), Toast.LENGTH_SHORT).show()
                                );
                            }
    
                            @Override
                            public void onFailure(@NotNull ExecutionException e) {
                                Log.e(LOG_TAG, "Transaction failed.");
                                Log.e(LOG_TAG, "" + e.getMessage());
                            }
    
                            @Override
                            public void onCancelled(@NotNull InterruptedException e) {
                                Log.e(LOG_TAG, "Transaction canceled.");
                            }
                        });
                } catch (AvailabilityException e) {
                    Log.e(LOG_TAG, "Error in sending: " + e);
                }
            }
    
        }
    

Conclusion

Token transactions are a vital part of the Ethereum ecosystem, and the SBP SDK enables you to create wallet applications that manage ERC-20 tokens and transactions.

Share your thoughts with us and the global blockchain developer community on the Samsung Blockchain Developer Forum.

Learn more about SBP SDK and its features in our recent blogs:

Additional resources on the Samsung Developers site

The Samsung Developers site has many resources for developers looking to build for and integrate with Samsung devices and services. Stay in touch with the latest news by creating a free account and subscribing to our monthly newsletter. Visit the Galaxy Store Games page for information on bringing your game to Galaxy Store and visit the Marketing Resources page for information on promoting and distributing your Android apps. Finally, our Developer Forum is an excellent way to stay up-to-date on all things related to the Galaxy ecosystem.