Wallets
In this tutorial we'll be reviewing how to create, save, recreate, and use a wallet. When you perform a transaction, the SDK will sign the transaction and send it to the network. This cryptographic seal allows us to detect any modifications to your data. Additionally, it provides traceability to your wallet's activity. This is because all of your transactions will be linked to your wallet.
A wallet is used to create and sign transactions; also, the wallet will have a unique address. The nodes will perform validations of the signature and the wallet's existence in the blockchain for those transactions, so it's a requirement to use valid wallets when creating transactions.
This tutorial assumes that you have an existing blockchain set up with ULedger. For additional information, please visit Getting Started and Import ULedger SDK.
Create a Wallet
Creating a wallet with the SDK involves several steps. To begin, we need to generate a seed phrase, as shown in the code sample below. First, we create an instance of the ULedger Wallet Generator class. Then, we use this instance to call the GetSeedWords method. Take note of the parameters: '24' specifies the length of the word list that will be returned (this number can be any integer between 12 and 24). The second parameter determines the language. The supported languages for this parameter are English ('en'), Spanish ('es'), Italian ('it'), French ('fr'), and Portuguese ('pt'). The method returns an array of strings.
// Create an instance of the ULedgerWalletGenerator class
walletGenerator := modules.ULedgerWalletGenerator{}
// Get a word list from the seed generator
words, err := walletGenerator.GetSeedWords(24, "en")
if err != nil {
fmt.Printf("\n%s\n", err)
}
It's crucial to safeguard your seed phrase; the user is responsible for keeping it secure. If it gets lost, there is no action we can take to retrieve it.
Always maintain a secure offline copy of your seed phrase. With it, you can recover your wallet at any time.
For more information on seed phrases and BIP39:
In the next step, we'll use the word list obtained from the previous code sample to determine our seed phrase and create a wallet. Please note that the array of words provided by the last function call is not yet your seed phrase. You have the option to select a subset of these words, rearrange their order, or use the array as it is. Your seed phrase will only be finalized upon the creation of the wallet. This process will be illustrated in the code sample below. You will first see the function call to generate the wallet. What we need to provide is the array of words (which will become your new seed phrase) and the language specification. The output is a data structure that constitutes the ULedger Wallet.
// Generate Wallet
wallet, err := modules.GenerateWalletFromSeedWords(words, "en")
if err != nil {
fmt.Printf("\n%s\n", err)
}
fmt.Printf("\n\nAddress: %s, ", wallet.GetAddress())
Congratulations! You've created your first wallet with the SDK. In this section, we discussed how to generate a ULedgerWallet object. In the following section, we will explore how serialization and deserialization of wallets allow for storage, such as saving the wallet in the form of a JSON file.
Save a Wallet Locally
Now that we've created our wallet, we will use the SDK to store the wallet in a file. At this step, you have the option to store the wallet in either JSON format or an encrypted format.
Let's concentrate on the serialization process. The data type used to represent a ULedgerWallet during this process is a string, chosen for its ease of handling. To serialize the wallet into a string, we invoke the serialize method and pass the wallet as a parameter. Additionally, there's an optional parameter: a password. If you provide a password, the wallet file will be encrypted, enhancing its security.
If a password is used, the individual utilizing the SDK assumes complete ownership of choosing and managing that password. The SDK does not offer password management features such as storage or recovery. Should the password be lost, the SDK will be unable to deserialize the wallet, thus preventing access to it.
The encryption used behind the password is based on Advanced Encryption Standard and Galois/Counter Mode using AES-256-GCM algorithm, More Details
// Serialize Wallet
walletFactory := modules.ULedgerWalletFactory{}
// Unencrypted
serializedWallet, err := walletFactory.Serialize(wallet, "")
if err != nil {
fmt.Printf("\n%s\n", err)
}
fmt.Printf("\n\nSerialized: %s, ", serializedWallet)
Let's examine the code sample above. First, we create an instance of the wallet factory class. Then we use that class object to call the serialize method. For parameters, we pass in the wallet object that we created in the previous section. Additionally, we pass an empty string to indicate that we want the file in JSON format (unencrypted). If you prefer to encrypt your file, the process is identical, except you would include a real password as a parameter. Below is the code sample for writing the data to a .ukey file.
// Write the file
err = ioutil.WriteFile("wallet-example.ukey", []byte(serializedWallet), 0644)
if err != nil {
fmt.Println("Error writing to file:", err)
return
}
Excellent! You have now saved your wallet file to your local device or server. Next, we'll review how to retrieve that file and use the deserialize method to load your wallet from the .ukey file back into program memory. Please take a look at the code sample below.
// Read the contents of the file
content, err := ioutil.ReadFile(filePath)
if err != nil {
fmt.Println("Error reading file:", err)
return
}
tsWalletEncrypted, err := walletFactory.Deserialize(string(content), "")
if err != nil {
fmt.Printf("\n%s\n", err)
}
fmt.Printf("\n\nDeerialized Encrypted (wallet address): %s ", tsWalletEncrypted.GetAddress())
The first step in this code sample is to read the file from your local device and extract its contents. After obtaining the data, it's passed into the deserialize method. To do this, you'll need the wallet factory class object that we created at the beginning of this section. The parameters for the deserialize method are as follows: first, you pass in the file content represented as a string (in this code sample, we perform the typecasting inline). The second parameter is the password for the wallet file, which is only necessary if your wallet file is encrypted—that is, if you assigned a password when we used the serialize method. The deserialize method returns a ULedger Wallet object.
Great job! In this section, we've used the Wallet Factory class to serialize and store our wallet, as well as to deserialize the wallet and load it into program memory. In the next section, we'll review how to use the wallet for interactions with the blockchain!
Wallet Usage
Before we can use our wallet to create transactions, we need to register it to a blockchain. This is being done in the code sample below. In order to run this block of code successfully, you need to make sure the blockchain id and node url are using actual values instead of placeholder values. The real values are avaliable through your account on the ULedger Portal.
blockchainId := "{{Blockchain-ID}}"
nodeUrl := "{{Node-URL}}"
err = wallet.RegisterWallet(blockchainId, nodeUrl)
if err != nil {
fmt.Printf("\n%s\n", err)
}
Once you have registered your wallet with the blockchain you'll be connecting with, we can create our first transaction. The first step in this process is to create a session with the ULedger network. This can be seen in the code sample below. Please make sure to replace the placeholder values with real values avaliable through your account on the ULedger Portal.
optionsV2 := models.SessionOptions{
AtomicClockUrl: "{{ACS-URL}}",
NodeUrl: "{{Node-URL}}",
NodeId: "{{Node-ID}}",
}
transactionSession := session.NewULedgerTransactionSession(optionsV2, *wallet)
Once we have the transaction session object, we can proceed to building our transaction. In the first part of this code sample we are creating the custom payload of our transaction. For more information on this step, please visit the Transactions page. Basically, this is where you put your custom data.
In the second half of this code sample, we are building the transaction. This is where you specify the blockchain, your payload, the payload type, and to address.
customPayload := map[string]interface{}{
"hello_world": "Hello World",
"foobar": 0,
}
transactionInput := models.ULedgerTransactionInput{
BlockchainId: blockchainId,
Payload: customPayload,
PayloadType: "DATA",
To: "0f9f7f7264f2064820131e1b4d211d3713d147f734a80bffcd946302007",
}
Finally, we have our transaction ready and need to upload it to the blockchain network. This is done in the code sample below. It's very simple, we pass in the transaction input created above and the return type is a minted transaction on the blockchain.
transactionOutput, err := transactionSession.CreateTransaction(&transactionInput)
if err != nil {
fmt.Printf("\n%s\n", err)
}
fmt.Printf("\ntransaction id: %s", transactionOutput.TransactionId)
Great work! You've created the first transaction with your new wallet! Once the block has been minted your should be able to query it on our blockchain explorer.
Recreate Wallet from Seed Phrase
In this section, we'll be reviewing how to recreate a wallet from your seed phrase. Please examine the code sample below. For this to work successfully, you just need to create a string array with your seed phrase. Once that is done, you can pass your seed phrase in to Generate Wallet from Seed method. Additionally, you need to pass in the language being used for your word list. The response type will be a ULedger Wallet object.
words := []string{"gain", "alley", ... , "bean"}
// Generate Wallet
wallet, err := modules.GenerateWalletFromSeedWords(words, "en")
if err != nil {
fmt.Printf("\n%s\n", err)
}
fmt.Printf("\n\nAddress: %s, ", wallet.GetAddress())
🔎 Search Wallet Activity
This tutorial assumes that you have an existing blockchain set up with ULedger. For additional information, please visit Getting Started and Import ULedger SDK.
In this tutorial, we'll be using the BMS to query our wallet activity. This method will return two embedded json objects. One containing transactions that the wallet has sent and the other containing transactions that the wallet has received.
To execute this successfully, first we need to create a session with the bms. This is done in the code sample below. Please make sure to replace the bms url with a real service url. This url can be found through your account on the ULedger portal. For more information on how to create a session, please visit this page.
options := models.BMSSessionOptions{
URL: "{{BMS-URL}}",
}
sessionBms := session.NewULedgerBMSSession(options)
From there, we can use our wallet address to query our activity. this is done in the code sample below. Lets break down the parameters,
Blockchain Id: This is a string representation of your blockchain identifier.
Wallet Address: This is a string representation of your wallet address.
Limit: This value is used to set a pagination limit to your response.
Offset: This value is used to set a pagination offset to your response.
Sort: This value is used to indicate if you would like the response to be sorted by time.
Public: This value is used to indicate if your blockchain is public.
userHistory, err := sessionBms.UserHistory(blockchainId, walletAddress, 10, 0, true, false, true)
if err != nil {
log.Fatal(err)
}
fmt.Printf("User History: %+v\n", userHistory)