example

command
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 24, 2023 License: MIT Imports: 13 Imported by: 0

README

KMS Wallet Provider Package Example

This example demonstrates the operations that can be performed using the KMS Wallet Provider package. It showcases the usage of the package with a simple BankAccount smart contract.

BankAccount Smart Contract

The smart contract implementation can be found in the sol/contracts/BankAccount.sol file. Here is the code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

contract BankAccount {
    using ECDSA for bytes32;

    address public accountHolder;
    uint256 public totalBalance;

    constructor() {
        accountHolder = msg.sender;
    }

    function deposit() external payable {
        totalBalance += msg.value;
        // We could use address(this).balance for tracking the balance contract owns,
        // but this is just a sample
    }

    function withdraw(uint256 amount, bytes memory signature) external {
        bytes32 message = keccak256(
            abi.encodePacked(address(this), msg.sender)
        );

        address signer = message.toEthSignedMessageHash().recover(signature);
        require(signer == accountHolder, "Invalid signer");
        // We are checking the signature if the account holder(contract owner) allows msg.sender to withdraw.
        // But this is a dummy logic, do not use it anywhere

        require(totalBalance >= amount, "Insufficient balance");
        totalBalance -= amount;

        (bool sent, ) = msg.sender.call{value: amount}("");
        require(sent, "Withdraw failed");
    }
}

The BankAccount is a simple bank account contract with the following features:

  • The wallet that deploys the contract becomes the account holder.
  • Anyone can deposit funds into the contract.
  • Only wallets authorized by the account holder can perform withdrawals.

In this example, we will create three wallets:

  • Account Holder Wallet: The wallet that deploys the contract and authorizes other wallets for withdrawals.
  • Authorized Wallet: A wallet authorized by the account holder to perform withdrawals.
  • Ordinary (Non-authorized) Wallet: A wallet that will deposit funds into the contract and query the contract's balance.
Step 1: Creating the KMS client and KMS Wallet Provider

First, initialize the KMS client using your AWS access credentials and desired configuration. In this example, we use the eu-central-1 region and provide the AWS access key ID and secret access key.

ctx := context.Background()
chainId := big.NewInt(80001) // means Mumbai Testnet

// Initialize the KMS Client
config := aws.Config{
    Region:      "eu-central-1",
    Credentials: credentials.NewStaticCredentialsProvider("AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", ""),
}

kmsClient := kms.NewFromConfig(config) // or you can use kms.New(...)

// Initialize the wallet provider (with default cache duration)
walletProvider := kmswallet.NewProvider(kmsClient, nil)
Step 2: Creating the Account Holder Wallet

Create the account holder wallet by specifying a custom alias (in this example, "michael") and adding a role tag.

accountHolderAlias := "michael"
accountHolderWallet, err := walletProvider.CreateWallet(ctx, kmswallet.CreateWalletInput{
    Alias:               &accountHolderAlias, // the alias of the key in KMS will be "michael"
    AddWalletAddressTag: true,                // it will add the wallet address into the key tags
    Tags:                map[string]string{"role": "account-holder"},
    // you can set other KMS options
})

if err != nil {
    log.Fatalf

("account holder wallet creation failed, err: %s", err)
}
Step 3: Creating the Authorized Wallet

Create the authorized wallet without specifying a custom alias, but set the IgnoreDefaultWalletAddressAlias flag to false. This will assign the generated wallet address as the alias for the wallet.

authorizedWallet, err := walletProvider.CreateWallet(ctx, kmswallet.CreateWalletInput{
    Alias:                           nil,   // It won't have a custom alias
    IgnoreDefaultWalletAddressAlias: false, // It will set the generated wallet address as the alias
    Tags:                            map[string]string{"role": "authorized"},
})

if err != nil {
    log.Fatalf("authorized wallet creation failed, err: %s", err)
}
Step 4: Creating the Ordinary (Non-authorized) Wallet

Create the ordinary wallet without specifying a custom alias and set the IgnoreDefaultWalletAddressAlias flag to true. This means the wallet will not have any alias.

ordinaryWallet, err := walletProvider.CreateWallet(ctx, kmswallet.CreateWalletInput{
    Alias:                           nil,
    IgnoreDefaultWalletAddressAlias: true, // This key won't have any alias or tags
})

if err != nil {
    log.Fatalf("ordinary wallet creation failed, err: %s", err)
}

Note: Before proceeding to the next steps, you need to load ETH/MATIC into the created wallets to cover gas and deposit fees.

Step 5: Creating the Ethereum RPC Client

Create an Ethereum RPC client to interact with the Ethereum network. In this example, we use the Mumbai Testnet public RPC node.

client, err := ethclient.Dial("https://rpc.ankr.com/polygon_mumbai")
if err != nil {
    log.Fatalf("ethClient initialization failed, err: %s", err)
}

defer client.Close()
Step 6: Creating the Account Holder Wallet Transactor and Deploying the Bank Account Contract

Create the account holder wallet transactor and deploy the BankAccount contract.

accountHolderTransactor, err := walletProvider.GetWalletTransactorByAlias(ctx, accountHolderAlias, chainId)
if err != nil {
    log.Fatalf("account holder transactor creation failed, err: %s", err)
}

contractAddress, tx, bankAccountContract, err := contracts.DeployBankAccount(accountHolderTransactor, client)
if err != nil {
    log.Fatalf("contract deploy failed, err: %s", err)
}

deployReceipt, err := bind.WaitMined(ctx, client, tx)
if err != nil {
    log.Fatalf("contract deploy failed, err: %s", err)
}

if deployReceipt.Status != types.ReceiptStatusSuccessful {
    log.Fatalf("contract deploy transaction failed, status: %d", deployReceipt.Status)
}
Step 7: Creating the Ordinary Wallet Transactor and Making a Deposit

Create the ordinary wallet transactor and deposit funds into the bank account contract. In this example, we deposit 10,000,000 wei MATIC.

ordinaryTransactor, err := walletProvider.GetWalletTransactor(ctx, ordinaryWallet.KeyId, chainId)
if err != nil {
    log.Fatalf("ordinary wallet transactor creation failed, err: %s", err)
}

ordinaryTransactor.Value = big.NewInt(10000000) // ordinary wallet will deposit 10,000,000 wei (MATIC)
depositTx, err := bankAccountContract.Deposit(ordinaryTransactor)
if err != nil {
    log.Fatalf("deposit failed, err: %s", err)
}

depositReceipt, err := bind.WaitMined(ctx,

 client, depositTx)
if err != nil {
    log.Fatalf("deposit failed, err: %s", err)
}

if depositReceipt.Status != types.ReceiptStatusSuccessful {
    log.Fatalf("deposit transaction failed, status: %d", depositReceipt.Status)
}
Step 8: Creating the Account Holder Wallet Caller and Retrieving the Total Balance

Create the account holder wallet caller and retrieve the total balance of the bank account.

accountHolderCaller, err := walletProvider.GetWalletCaller(ctx, accountHolderWallet.KeyId, chainId)
if err != nil {
    log.Fatalf("account holder caller creation failed, err: %s", err)
}

balance, err := bankAccountContract.TotalBalance(accountHolderCaller)
if err != nil {
    log.Fatalf("total balance read failed, err: %s", err)
}
Step 9: Signing the Authorization Message for the Authorized Wallet

Sign the authorization message for the authorized wallet using the account holder wallet.

messageToSign := crypto.Keccak256Hash(
    append(contractAddress.Bytes(), common.HexToAddress(authorizedWallet.Address).Bytes()...),
)

signature, err := walletProvider.SignMessage(ctx, accountHolderWallet.KeyId, messageToSign.Bytes())
if err != nil {
    log.Fatalf("sign message failed, err: %s", err)
}
Step 10: Creating the Authorized Wallet Transactor and Withdrawing Funds

Create the authorized wallet transactor and initiate a withdrawal from the bank account. In this example, we withdraw 50,000 wei MATIC.

authorizedTransactor, err := walletProvider.GetWalletTransactor(ctx, authorizedWallet.KeyId, chainId)
if err != nil {
    log.Fatalf("authorized wallet transactor creation failed, err: %s", err)
}

withdrawAmount := big.NewInt(50000)
withdrawTx, err := bankAccountContract.Withdraw(authorizedTransactor, withdrawAmount, signature)
if err != nil {
    log.Fatalf("withdraw failed, err: %s", err)
}

withdrawReceipt, err := bind.WaitMined(ctx, client, withdrawTx)
if err != nil {
    log.Fatalf("withdraw failed, err: %s", err)
}

if withdrawReceipt.Status != types.ReceiptStatusSuccessful {
    log.Fatalf("withdraw transaction failed, status: %d", withdrawReceipt.Status)
}

You can access the complete code in the example/main.go file.

Please note that in order to execute the code, you need to load ETH/MATIC into the created wallets beforehand to cover gas and deposit fees.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL