James Bachini

Building A Stellar Web Wallet With Blend

Building On Blend

In this tutorial I’m going to show you how I built a web based wallet that can create Stellar addresses and deposit any funds to Blend Capital.

James On YouTube

Blend is an overcollateralized lending protocol that enables lenders to earn yield on their deposits.

https://www.blend.capital/

The source code for this project is available at: https://github.com/jamesbachini/Blend-Wallet

Setting up the Stellar and Blend SDK’s

The first step is to use webpack to build a bundle.js file for the Stellar and Blend SDK’s. If you are using a framework like React then you can just import them using npm but in this example I’m building them manually.

// webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  mode: 'production',
};

We then import the required module functions and set them to global variables.

import { PoolContract, RequestType } from '@blend-capital/blend-sdk';
import { Keypair, TransactionBuilder, Networks, BASE_FEE, rpc, xdr, nativeToScVal, scValToNative, Contract } from 'stellar-sdk';

window.BlendSdk = { PoolContract, RequestType };
window.StellarSdk = { Keypair, TransactionBuilder, Networks, BASE_FEE, rpc, xdr, nativeToScVal, scValToNative, Contract };

We can then build this by running:

npm install
npx webpack

This will build a bundle in the dist directory which we can work with.


Generating Addresses

We can then use the Stellar SDK to generate a network address.

Generate Stellar Address
const keypair = StellarSdk.Keypair.random();
const wallet = {
  publicKey: keypair.publicKey(),
  secret: keypair.secret()
};

The code snippet above creates a key pair which we can then save somewhere. Note that the secret (private key) is critical from a security perspective so in a production application you should take great care to store this securely.

Tyler @ Stellar has been doing some great work on webauthn passkeys which provide a improved UX for production web wallets. See his post here for more information: https://kalepail.com/blockchain/the-passkey-powered-future-of-web3

We can also fund the account with testnet XLM by calling this URL using fetch.

fetch(`https://friendbot.stellar.org/?addr=${wallet.publicKey}`)

Checking Balances

There are a few ways to check token balances but in this example I’m going to be querying the smart contracts directly using Soroban RPC queries and the balance function within the token smart contract.

Note we can get a full interface for the contract showing functions and input variables using this command with the stellar-cli

stellar contract info interface --network testnet --id CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC
contract interface soroban

We can then run a query from the Stellar SDK using the following code:

const contract = new StellarSdk.Contract(contractId);
let sourceAccount = await rpc.getAccount(wallet.publicKey);
let tx;
try {
  let operationArgs = [StellarSdk.nativeToScVal(wallet.publicKey, { type: "address" })];
  tx = new StellarSdk.TransactionBuilder(sourceAccount, {
    fee: StellarSdk.BASE_FEE,
    networkPassphrase: networkPassphrase,
  })
  .addOperation(contract.call('balance', ...operationArgs))
  .setTimeout(30)
  .build();
  const simulationResult = await rpc.simulateTransaction(tx);
  const decoded = StellarSdk.scValToNative(simulationResult.result?.retval).toString();
  return decoded;
} catch (error) {
  console.error("Error building transaction", error);
}

The native XLM token has a Soroban contract similar to any SEP41 token so this is queried in the same way as USDC etc. One thing to note is that you may need to adjust the decimals as different tokens may use different decimal figures and to display the correct balance you’ll need to adjust it.

You can query any function in any Soroban smart contract for read data using this simulation technique. Because we aren’t changing the on-chain data we don’t need to propagate our tx across the decentralized network. Assuming we trust the RPC node we are connected to it can provide a simulated result.

Simulated Transactions

Deposit & Withdrawals On Blend

The final piece of this puzzle is getting the wallet working so that it can deposit and withdraw funds from Blend. To make this simpler we are using the Blend SDK directly to abstract away some of the complexities.

const sourceKeypair = StellarSdk.Keypair.fromSecret(wallet.secret);
let sourceAccount = await rpc.getAccount(wallet.publicKey);
try {
  const blendPool = new BlendSdk.PoolContract(poolContract);
  const requestType = actionType === 'Deposit'
    ? BlendSdk.RequestType.SupplyCollateral
    : BlendSdk.RequestType.WithdrawCollateral;
  const opXDR = StellarSdk.xdr.Operation.fromXDR(
    blendPool.submit({
      from: wallet.publicKey,
      spender: wallet.publicKey,
      to: wallet.publicKey,
      requests: [{
        amount: parseInt(amount),
        request_type: requestType,
        address: xlmContract,
      }],
    }),
    'base64'
  );
  const tx = new StellarSdk.TransactionBuilder(sourceAccount, {
    fee: StellarSdk.BASE_FEE,
    networkPassphrase: networkPassphrase,
  }).addOperation(opXDR).setTimeout(30).build();
  const preparedTx = await rpc.prepareTransaction(tx);
  preparedTx.sign(sourceKeypair);
  const txResult = await rpc.sendTransaction(preparedTx);
  console.log(txResult)
} catch (error) {
  console.error("Transaction error:", error);
}

The submit function in the Blend contract handles both deposit and withdrawals depending on the request_type enum input.

We create an XDR operation and then sign this in a similar way to before but this time because we are changing state information we need to sign the transaction and pay a fee to execute it on the network.

The withdraw all option also requires to check our collateral balance on Blend to get an up to date figure for how much we can withdraw. We can simulate this using the get_positions external function in the pool contract.

const contract = new StellarSdk.Contract(poolContract);
let sourceAccount = await rpc.getAccount(wallet.publicKey);
let operationArgs = [StellarSdk.nativeToScVal(wallet.publicKey, { type: "address" })];
let tx = new StellarSdk.TransactionBuilder(sourceAccount, {
  fee: StellarSdk.BASE_FEE,
  networkPassphrase: networkPassphrase,
})
.addOperation(contract.call('get_positions', ...operationArgs))
.setTimeout(30)
.build();
const simulationResult = await rpc.simulateTransaction(tx);
const decoded = StellarSdk.scValToNative(simulationResult.result?.retval);
const blendXLMBalance = decoded.collateral[0];
return blendXLMBalance;

The final result is a simple web based wallet that can deposit and withdraw funds from Blend.

Blend web wallet

Get The Blockchain Sector Newsletter, binge the YouTube channel and connect with me on Twitter

The Blockchain Sector newsletter goes out a few times a month when there is breaking news or interesting developments to discuss. All the content I produce is free, if you’d like to help please share this content on social media.

Thank you.

James Bachini

Disclaimer: Not a financial advisor, not financial advice. The content I create is to document my journey and for educational and entertainment purposes only. It is not under any circumstances investment advice. I am not an investment or trading professional and am learning myself while still making plenty of mistakes along the way. Any code published is experimental and not production ready to be used for financial transactions. Do your own research and do not play with funds you do not want to lose.