AWS Database Blog

How to build a crypto wallet application using Amazon Managed Blockchain Access and Query

Building a cryptocurrency wallet requires the operation of a multitude of blockchain-specific components that enable critical functionality for the wallet. This includes but is not limited to provisioning and managing blockchain nodes, developing secure private key management solutions, building performant transaction management modules, and working with complex blockchain data pipelines. These components require complex security and infrastructure management tasks, and with the critical task of securing cryptocurrency, a wallet application must be built with care.

Amazon Managed Blockchain (AMB) provides powerful abstractions for some of these core blockchain components, which allow developers to offload the undifferentiated heavy lifting in favor of application-specific tasks, such as building robust key management for their cryptocurrency wallet application. AMB Access and AMB Query provide developers with access to multiple public blockchains such as Bitcoin and Ethereum, enabling you to build transaction workflows, retrieve current and historical blockchain data, and more. In tandem with other AWS services, Managed Blockchain provides the foundations for a cryptocurrency wallet solution.

In this post, we share an example implementation of a wallet application that takes advantage of AWS services and the unique features of Managed Blockchain.

AMB Access and Query

Managed Blockchain significantly streamlines the operational aspects of blockchain applications, providing essential infrastructure support for major networks like Bitcoin and Ethereum. AMB Access offers a fully managed gateway to blockchain nodes, simplifying the process of connecting to and interacting with different blockchain networks. Additionally, AMB Query enhances the efficiency of blockchain data handling. It provides indexed blockchain data, enabling complex blockchain queries of both current and historical blockchain data to be translated into simple API calls. This unified approach across different protocols reduces the complexity and time required for blockchain data management, enhancing overall efficiency and lowering costs, risks, and operational efforts.

Building a cryptocurrency wallet application

There are two critical considerations in implementing a wallet.

The first is key management. AWS CloudHSM and AWS Key Management Service (AWS KMS) are secure key management services used to securely store and access private keys in blockchain wallets. CloudHSM provides a cloud-based hardware security module that is FIPS 140-2 Level 3 certified, and only users can access keys. Users can generate their own private keys and store them securely within CloudHSM, and are protected against system failures in multiple availability zones. AWS KMS securely manages keys in a cloud environment and supports secure storage and access to private keys.

The next consideration is transaction management. Wallets add cryptographic signatures to transactions using the user’s private key to create new transactions and send them securely to the blockchain network. This signature ensures that the transaction is valid and has not been tampered with, and allows transactions to be processed on the blockchain network. Users secure private keys and sign transactions so they can be recorded securely on the blockchain. Additionally, user transaction records and their status must be able to be queried from the blockchain network. For this function, wallets communicate with blockchain nodes to accurately provide transaction progress, number of confirmations, and whether blocks are included. Users can monitor their transactions in real time and take necessary actions.

For more information about the key management and transaction creation processes, refer to AWS Nitro Enclaves for secure blockchain key management: Part 1.

Simplified wallet application development with AMB Access and AMB Query

If you implement your own transaction manager without using AMB Access and AMB Query, you must create an event listener for callbacks after sending transactions based on Amazon Elastic Compute Cloud (Amazon EC2) and AWS Fargate. This manual transaction management module uses various AWS services, such as Amazon Kinesis, Amazon Simple Storage Service (Amazon S3), Amazon DynamoDB, and Amazon Aurora, to track when transactions are submitted, finalized, and more. The following diagram illustrates a sample architecture.

However, you can simplify the implementation of this capability by using the convenient API-based query functions in AMB Access and AMB Query, which allow developers to broadcast transactions to the network of their choosing (AMB Access) and then query data pertaining to that transaction’s status (AMB Query).

The following diagram illustrates the architecture for sending transactions from wallets via AMB Access and querying transactions using AMB Query.

The architecture follows these general steps to perform transaction monitoring:

  1. When you call a backend API, Amazon API Gateway and AWS Lambda perform authentication and authorization.
  2. After authentication, Lambda uses AMB Query to call an API to request the desired blockchain data.
  3. AMB Query retrieves blockchain data and returns results, and the Lambda function processes the AMB Query results into the desired form of data, and then responds to the user.
  4. If you’re sending cryptocurrencies or tokens owned by other users, you can use AMB Access to create transactions and send transactions. For more information, see How to sign Ethereum EIP-1559 transactions using AWS KMS.

Wallet service flow overview

The following figure shows a sample application interface outlining common functionalities for a cryptocurrency wallet.

It offers the following features:

  • Balance – Check total wallet assets
  • Transaction list – View transaction history
  • Transaction details – Explore specific transaction information
  • Token list – Access owned tokens, especially NFTs

With AMB Query, developers can populate these wallet user interface elements with a series of simple API calls.

Balance

AMB Query can check balances in two ways: GetTokenBalance and BatchGetTokenBalance. It also supports querying the balance of cryptocurrencies owned in multiple programing languages. The difference is that getTokenBalance checks balances held by Externally Owned Accounts (EOA) or Contract Address (CA), whereas batchGetTokenBalance can check balances for multiple addresses and contract addresses. If the user is using multiple mainnets, you can check the balance at once with batchGetTokenBalance.

Let’s check a balance using Python code with GetTokenBalance. In this example, we used a famous Bitcoin address. Of course, you could use one of your own addresses for testing.

$ python
>>> import boto3
>>> query = boto3.client("managedblockchain-query")
>>> query.get_token_balance(tokenIdentifier={"network":"BITCOIN_MAINNET", "tokenId":"btc"}, ownerIdentifier={"address":"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"})
{
    'ResponseMetadata': {
        'HTTPStatusCode': 200, 
        'HTTPHeaders': 
        ...
    }, 
    'ownerIdentifier': {
        'address': '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'
    }, 
    'tokenIdentifier': {
        'network': 'BITCOIN_MAINNET', 
        'tokenId': 'btc'
    }, 
    'balance': '72.66900535', 
    'atBlockchainInstant': {
        'time': datetime.datetime(2023, 8, 2, 22, 15, 12, tzinfo=tzlocal())
    }, 
    'lastUpdatedTime': {
        'time': datetime.datetime(2023, 8, 1, 19, 1, 26, tzinfo=tzlocal())
    }
}

Transaction list

From the results of the initial search on ListTokenBalance, we can infer that many transactions are sent to this specific address, so we only search for the first two transactions with ListTransactions. To retrieve additional pages of results, you can use the nextToken included in the response to make a subsequent call for the next page of data. Note that the absence of nextToken in the response illustrates that you have reached the end of available data for a given query, and no further pages remain to be retrieved.

>>> query.list_transactions(address="1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", network="BITCOIN_MAINNET", maxResults=2)
{
    'ResponseMetadata':{
        'HTTPStatusCode':200,
        'HTTPHeaders':{ 
          ...
    },
    'transactions':[
        {
            'transactionHash':'4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b',
            'network':'BITCOIN_MAINNET',
            'transactionTimestamp':datetime.datetime(2009, 1, 4, 3, 15, 5, "tzinfo=tzlocal())'
        },
        {
            'transactionHash':'3387418aaddb4927209c5032f515aa442a6587d6e54677f08a03b8fa7789e688',
            'network':'BITCOIN_MAINNET',
            'transactionTimestamp':datetime.datetime(2011, 5, 14, 4, 42, 15, "tzinfo=tzlocal())'
        }
    ],
    'nextToken': 'XXXXX'
}

Transaction details

To view the details of Bitcoin’s first transaction, you can use GetTransaction:

>>> query.get_transaction(network="BITCOIN_MAINNET", transactionHash="4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")
{
    'ResponseMetadata': {
        'HTTPStatusCode': 200,
        ...
    },
    'transaction': {
        'network': 'BITCOIN_MAINNET', 
        'blockHash': '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f', 
        'transactionHash': '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b', 
        'blockNumber': '0', 
        'transactionTimestamp': datetime.datetime(2009, 1, 4, 3, 15, 5, tzinfo=tzlocal()), 
        'transactionIndex': 0, 
        'numberOfTransactions': 1, 
        'status': 'FINAL', 
        'transactionFee': '0', 
        'transactionId': '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b'
    }
}

Transaction 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b is in the block number 0 (Genesis Block), and the status is FINAL. The number of transactions included in the block is 1. If you want to see more detailed transaction information, you can use ListTransactionEvents:

>>> query.list_transaction_events(network="BITCOIN_MAINNET", transactionHash="4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")
{
    'ResponseMetadata': {
        'HTTPStatusCode': 200, 
        ...
    'events': [
        {
            'network': 'BITCOIN_MAINNET', 
            'transactionHash': '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b', 
            'eventType': 'BITCOIN_VOUT', 
            'to': '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', 
            'value': '50', 
            'tokenId': 'btc', 
            'transactionId': '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b', 
            'voutIndex': 0
        }
    ]
}

The first transaction was a BITCOIN_VOUT type, and 50 BTC was paid to the address. In fact, this transaction is the first transaction in a block and corresponds to a coinbase transaction that occurred as a reward for block mining in Bitcoin. BITCOIN_VIN and BITCOIN_VOUT type events can also be observed when sending and receiving BTC between normal addresses. In Ethereum, these event types differ in responses from AMB Query, as illustrated in the next example.

Token list

ListTokenBalances queries the owner’s list of Ethereum tokens (ERC20, ERC721, ERC1155) and its balances:

>>> arguments = { 'tokenFilter' : {'network': 'ETHEREUM_MAINNET'}, 'ownerFilter' : {'address': '0xab5801a7d398351b8be11c439e05c5b3259aec9b'}, 'maxResults' : 3 }
>>> query.list_token_balances(**arguments)
{
    'ResponseMetadata': {
     'HTTPStatusCode': 200,  
        ...
    'tokenBalances': [
        {
              'ownerIdentifier': {'address': '0xab5801a7d398351b8be11c439e05c5b3259aec9b'}, 
              'tokenIdentifier': {'network': 'ETHEREUM_MAINNET', 'tokenId': 'eth'}, 
              'balance': '24185900769032875808', 
              'atBlockchainInstant': {'time': datetime.datetime(...)}, 
              'lastUpdatedTime': {'time': datetime.datetime(...)}
         },
         {
              'ownerIdentifier': {'address': '0xab5801a7d398351b8be11c439e05c5b3259aec9b'}, 
              'tokenIdentifier': {
                   'network': 'ETHEREUM_MAINNET', 
                   'contractAddress': '0x00000000051b48047be6dc0ada6de5c3de86a588'}, 
              'balance': '99000000000000000000', 
              'atBlockchainInstant': {'time': datetime.datetime(...)}, 
              'lastUpdatedTime': {'time': datetime.datetime(...)}
         },
         {
              'ownerIdentifier': {'address': '0xab5801a7d398351b8be11c439e05c5b3259aec9b'}, 
              'tokenIdentifier': {
                     'network': 'ETHEREUM_MAINNET', 
                     'contractAddress': '0x0000000035f26e72b70552b92bf7e02f67a90549'}, 
              'balance': '20000000000000000000000000', 
              'atBlockchainInstant': {'time': datetime.datetime(...)}, 
              'lastUpdatedTime': {'time': datetime.datetime(...)}
          }
    ],
    'nextToken': 'XXXXX'
}

The response’s contranctAddress field is the address of the smart contract owned by the requester. It also includes the balance of the smart contract. For pagination, the API also supports the maxResult feature, so the requester can use nextToken to get the next results.

You can find the code samples for this sample wallet in the GitHub repository.

Conclusion

In this post, we described how AMB Access and AMB Query allow you to quickly create a cryptocurrency wallet by abstracting the complexity of interacting with public blockchains and their data. In addition to wallets, these services can also be used for various Web3 applications such as NFTs, gaming, DeFi, DAOs, and more.

Creating and operating blockchain nodes yourself and indexing data from those nodes requires a lot of effort. However, by using AMB Query and AMB Access as managed services, you can implement blockchain applications with ease. To get started with Managed Blockchain, contact the Managed Blockchain team for a free consultation.


About the Authors

Hye Young Park is a Principal Solutions Architect at AWS. She helps align Amazon Managed Blockchain product initiatives with customer needs in the rapidly evolving and maturing blockchain industry for both private and public blockchains. She has worked in the IT industry for over 20 years, and has held various technical management positions, covering search engine, messaging, bigdata, and app modernization. Before joining AWS, she has experience in search engine, messaging, and big data at Yahoo, Samsung Electronics, and SK Telecom as project manager.

Hun Kim is a Solutions Architect at AWS. He is devoted to helping financial service industry customers’ application modernization and advises on how they can successfully achieve their business goals by constructing an architecture suitable for the cloud. He had worked with mobile platform, backend server, and blockchain as a software engineer over 15 years before joining AWS, and is interested in event-driven architecture and the blockchain industry. In his free time, he likes to put his feet up while listening to classical music.

MinPyo Hong is a Technical Account Manager at AWS. He suports various customers, including finance, airline, and manufacture industries. He is interested in blockchain, security, and AI/ML technical domains. He loves to learn from the customer side and improve their experiences, and drive their value through the proactive services of the AWS Enterprise Support Program. Before joining AWS, he worked for over 15 years as a software developer, IT planning manager, blockchain developer, and blockchain project manager.

Sanghee Lee is a Solutions Architect at AWS. She is currently supporting various customers to start and develop SaaS services and products, helping customers get used to SaaS concepts and business. She loves developing cool and fancy projects. Before joining AWS, she had worked in the financial industry as a developer and data engineer. She is interested in scuba diving and has seen various species under the sea.