Simplifying Bitcoin DeFi with Chain Abstraction

Learn how to simplify DeFi interactions on DeFi with VaultLayer and Lit.

Simplifying Bitcoin DeFi with Chain Abstraction

Introduction

VaultLayer’s SDK is a chain-abstraction toolkit built on Lit Protocol, designed to streamline and enhance the Bitcoin DeFi experience by providing a unified, user-friendly interface for managing assets. By leveraging Lit Protocol's robust key management network, VaultLayer offers secure and seamless transactions across Bitcoin Layer 1 and L2s.

The Problem: Fragmented UX in Bitcoin DeFi 💔

Today, Bitcoin DeFi holds only 1% of the $95 billion Total Value Locked (TVL), but this is poised for exponential growth, potentially exploding 50x. The TVL on Layer2 solutions is growing at a rate of 100% per month and is only starting. VaultLayer aims to help crypto users capitalize on this growth, offering a streamlined solution for users to stake and earn with ease. 

The current User eXperience (UX) of decentralized finance (DeFi) on Bitcoin is fragmented, complicated, and often overwhelming for users. With over 100+ Layer2 solutions available, the sheer number of wallets and bridging steps required to get started can be daunting: Users must juggle multiple wallets and deal with complex bridging processes before they can actually interact with DeFi protocols and stake their Bitcoin.

This fragmented UX not only causes frustration but also creates barriers to entry, preventing many from fully leveraging the potential of Bitcoin DeFi. This is where VaultLayer comes in, revolutionizing the user experience by providing a seamless, unified platform for all your Bitcoin DeFi needs.

The Solution: Chain-Abstraction

VaultLayer integrates Lit Protocol's technology to address these pain points by offering a streamlined chain-abstraction SDK that simplifies the Bitcoin DeFi experience. 

The concept of Chain-Abstraction tries to simplify interactions across multiple blockchains for users, hiding complexities like key management, gas fees, and transaction processing. This concept is vital for improving user experience and accessibility in the DeFi space.

Unlike other chain-abstraction solutions that derive an Ethereum address from a Bitcoin wallet and rely on Ethereum's ERC-4337 standard for contract wallets, VaultLayer leverages Lit Protocol’s key management network to create an off-chain Bitcoin smart account, simplifying the permission layer. This integration ensures seamless, secure transactions and asset management across Bitcoin L1 and L2. 

With VaultLayer, users can enjoy  💼🚀✨:

  • One Account: Simplify your DeFi journey with a single account that integrates seamlessly with various wallets and Layer2 solutions.
  • One Portfolio: Manage all your assets in one place, whether they are on Bitcoin L1, L2, or even EVM-compatible networks.
  • Any Wallet: Use your preferred wallet without worrying about compatibility issues, as VaultLayer reduces the gap between different technologies and networks.

Supercharging Lit Protocol with Bitcoin support

VaulLayer’s SDK adds 2 main features on top of Lit Protocol: being able to mint Lit Programmable Key Pairs (PKPs) with a Bitcoin wallet, and signing Bitcoin transactions with a Lit Action.

Creating Lit Keys (PKPs) with Bitcoin Wallet

VaultLayer’s SDK provides a ReactJS component that lets users connect any Bitcoin wallet, like Unisat, Xverse, OKX and others, as a new Authentication Method for Lit Keys.

An authentication method is the credential (e.g., wallet address, Google oAuth, or Discord account) linked to a Lit Key (PKP) that controls its key-pair. Only the specific auth method tied to a Lit Key can combine the underlying key shares. You can read more about how authentication works with PKPs here.

Here’s a breakdown of the full process:

  1. First, we’ll define a new Lit AuthMethodType, with a custom authentication Lit Action that will check if the user sends a valid Bitcoin BIP322 signature:
// @ts-nocheck
const { crypto } = require('bitcoin-sdk-js');


/**
*
* Bundles bitcoin-sdk-js package as it's required to sign a message with the Bitcoin wallet which is also decrypted inside the Lit Action.
*
* @jsParam pkpTokenId
* @jsParam network
* @jsParam accessToken - Includes message signed with Bitcoin BIP322 (P2PKH, P2WPKH, P2TR)
*
* @returns { Promise<string> } - Returns true or false if the auth was sucessful
*/


(async () => {
   const LIT_PKP_PERMISSIONS_CONTRACT_ADDRESS = {
       'datil': "0x213Db6E1446928E19588269bEF7dFc9187c4829A",
       'datil-test': "0x60C1ddC8b9e38F730F0e7B70A2F84C1A98A69167",
       'datil-dev': "0xf64638F1eb3b064f5443F7c9e2Dc050ed535D891"
     };
   const BITCOIN_AUTH_METHOD_TYPE = ethers.utils.keccak256(
     ethers.utils.toUtf8Bytes("BITCOIN_BIP322_v0")
   );
   const IS_PERMITTED_AUTH_METHOD_INTERFACE = new ethers.utils.Interface([
     "function isPermittedAuthMethod(uint256 tokenId, uint256 authMethodType, bytes memory id) public view returns (bool)",
   ]);


   console.log("BITCOIN_BIP322 authSig:",accessToken);
   console.log("BITCOIN_BIP322 network:",network);
    try {
     const authSig = JSON.parse(accessToken);
     const permissionsContract = network ? LIT_PKP_PERMISSIONS_CONTRACT_ADDRESS[network] : LIT_PKP_PERMISSIONS_CONTRACT_ADDRESS['datil-dev']
    
     console.log("BITCOIN_BIP322 permissionsContract:", permissionsContract);


     const isValid = await crypto.verifyMessage(
       authSig.signedMessage,
       authSig.sig,
       authSig.address
       );
     if (!isValid) {
       console.log("BITCOIN_BIP322 Invalid Bitcoin BIP322 (P2PKH, P2WPKH, P2TR) signature");
       return Lit.Actions.setResponse({
         response: "false",
         reason: "Invalid Bitcoin BIP322 (P2PKH, P2WPKH, P2TR) signature",
       });
     }
     const expirationTime = authSig.signedMessage.split('Expiration Time: ')[1].split('\n')[0];
     const isRecent = Date.now() / 1000 < new Date(expirationTime);
     if (!isRecent) {
       console.log("Authenticated Bitcoin signature expired");
       return Lit.Actions.setResponse({
         response: "false",
         reason: "Authenticated Bitcoin signature expired",
       });
     }
      // Checking if user's authMethodId is a permitted Auth Method for pkpTokenId
     const authMethodId = ethers.utils.keccak256(
       ethers.utils.toUtf8Bytes(`${authSig.address}:lit`)
     );
    
     const abiEncodedData =
       IS_PERMITTED_AUTH_METHOD_INTERFACE.encodeFunctionData(
         "isPermittedAuthMethod",
         [pkpTokenId, BITCOIN_AUTH_METHOD_TYPE, authMethodId]
       );
     const isPermittedTx = {
       to: permissionsContract,
       data: abiEncodedData,
     };
     const isPermitted = await Lit.Actions.callContract({
       chain: "yellowstone",
       txn: ethers.utils.serializeTransaction(isPermittedTx),
     });
     if (!isPermitted) {
       console.log("BITCOIN_BIP322 Bitcoin address is not authorized to use this PKP");
       return Lit.Actions.setResponse({
         response: "false",
         reason: "Bitcoin address is not authorized to use this PKP",
       });
     }
      return Lit.Actions.setResponse({ response: "true" });
   } catch (error) {
     console.log("BITCOIN_BIP322 Error:",error.message);
     return Lit.Actions.setResponse({
       response: "false",
       reason: `Error: ${error.message}`,
     });
   }
})();
  1. The Lit Action is then compiled with bundled dependencies and uploaded to IPFS, like this: https://ipfs.filebase.io/ipfs/QmdouVTa366pWQHyndMzzerD83imY8GMm8tVAETKMrhLCu
  2. At login time, we create the message to sign (similar to SIWE: Sign In With Ethereum):
// Get expiration or default to 24 hours
 const expiration = process.env.LIT_SESSION_EXPIRATION || new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString();


 const siweMsg = {
   domain: domain,
   statement: 'Sign-in to VaultLayer.xyz - SmartVault',
   uri: domain == 'localhost' ? 'http://localhost:3000' : `https://${domain}`,
   expiration: expiration,
   nonce: litNodeClient.latestBlockhash!,
 };
 const toSign = `${domain} wants you to sign in with your Bitcoin account:\n${address}\n\n${siweMsg.statement}\n\nURI: ${siweMsg.uri}\nNonce: ${siweMsg.nonce}\nExpiration Time: ${siweMsg.expiration}`;


  1. Then we request the user to sign using their Bitcoin wallet:
 const signature = await signMessage(toSign);
 const authSig = {
   sig: signature,
   derivedVia: 'bitcoin.signMessage',
   signedMessage: toSign,
   address: address,
 };
 const authMethod = {
   authMethodType: BITCOIN_AUTH_METHOD_TYPE,
   accessToken: JSON.stringify(authSig),
 };
  1. The resulting AuthMethod object is sent to a Lit Relay server to mint a Lit Key.
  2. The Lit Relay server should validate the Bitcoin signature, and proceed to mint the Lit Key:
import * as bitcoin from 'bitcoin-sdk-js';
import bs58 from "bs58";
import {
 AuthMethodScope,
 AuthMethodType,
} from "@lit-protocol/constants";
import {
 BITCOIN_AUTH_METHOD_TYPE,
 BITCOIN_AUTH_LIT_ACTION_IPFS_CID,
 mintPKPV2,
 getTokenIdFromTransferEvent,
 addPaymentDelegationPayee
} from "../utils/lit-relay";


export default async function handler(req, res) {
 try {
   const params = req.body;
   const {
     authMethodType,
     accessToken
    } = JSON.parse(params);
   if (!authMethodType) throw("Error: missing authMethodType");
   if (!accessToken) throw("Error: missing accessToken");
  
   const authSig = JSON.parse(accessToken);
   const expirationTime = authSig.signedMessage.split('Expiration Time: ')[1].split('\n')[0];


   const isRecent = Date.now() / 1000 < new Date(expirationTime);
   if (!isRecent) {
     throw('Authenticated signature expired');
   }


   let txId;
   if ( authMethodType === BITCOIN_AUTH_METHOD_TYPE) {


     // Verify Bitcoin signature
     const isValid = await bitcoin.crypto.verifyMessage(
       authSig.signedMessage,
       authSig.sig,
       authSig.address
       );
     if (!isValid) {
       throw('Invalid Bitcoin BIP322 (P2PKH, P2WPKH, P2TR) signature');
     }
     const authMethodId = ethers.utils.keccak256(
       ethers.utils.toUtf8Bytes(`${authSig.address}:lit`)
     );


     txId = await mintPKPV2(
       {
         keyType: AuthMethodType.LitAction,
         permittedAuthMethodTypes: [AuthMethodType.LitAction, authMethodType],
         permittedAuthMethodIds: [
           `0x${Buffer.from(bs58.decode(BITCOIN_AUTH_LIT_ACTION_IPFS_CID)).toString("hex")}`,
           authMethodId,
         ],
         permittedAuthMethodPubkeys: ["0x", "0x"],
         permittedAuthMethodScopes: [[AuthMethodScope.SignAnything], [AuthMethodScope.NoPermissions]],
         addPkpEthAddressAsPermittedAddress: true,
         sendPkpToItself: true,
       }
     );


   }  
   console.log(`✅ Minted new PKP, txId:`, txId);


   const pkpInfo = await getTokenIdFromTransferEvent(txId.hash);
   console.log(`ℹ️ PKP Public Key: ${pkpInfo.publicKey}`);
   console.log(`ℹ️ PKP Token ID: ${pkpInfo.tokenId}`);
   console.log(`ℹ️ PKP ETH Address: ${pkpInfo.ethAddress}`);
   const delegation = await addPaymentDelegationPayee({ payeeAddresses: [pkpInfo.ethAddress] });
   console.log(`✅ Payment delegation, txId:`, delegation);
   return res.json(pkpInfo);
 } catch (error) {
 console.error(error);
 res.status(400).json(error);
}
}
  1. The Lit Key is minted with the Bitcoin AuthMethodType pointing to the IPFS CID of the Lit Action created on step 1, the Lit Key is ready!
  2. Now, everytime the user needs to ask Lit to authenticate and sign with the Lit Key, they can replicate the AuthMethod signing and create a session signature:  
controllerSessionSigs = await litNodeClient.getPkpSessionSigs({
           pkpPublicKey: pkp.publicKey,
           litActionIpfsId: BITCOIN_AUTH_LIT_ACTION_IPFS_CID,
           jsParams: {
             accessToken: authMethod.accessToken,
             network: 'datil-dev',
             pkpTokenId: pkp.tokenId,
           },
           resourceAbilityRequests: [
             {
               resource: new LitPKPResource('*'),
               ability: LitAbility.PKPSigning,
             },
             {
               resource: new LitActionResource('*'),
               ability: LitAbility.LitActionExecution,
             },
           ],
       });
  1. For example, to create an Ethereum signing wallet, the Lit nodes would call the Lit Action from IPFS, validate the result, and approve the session to sign:
const pkpWallet = new PKPEthersWallet({
         controllerSessionSigs,
         litNodeClient,
         pkpPubKey: pkp.publicKey,
       });
await pkpWallet!.init();
console.log('connectVaultEthClient pkpWallet:', pkpWallet);

At this point, we started login with a Bitcoin wallet, and ended with a valid Ethereum signer. Here’s a video of the SDK demo in action for this scenario:

0:00
/0:48

Signing Bitcoin Transactions with Lit Actions

A previous article covered in detail how to use Lit to sign bitcoin transactions:  https://spark.litprotocol.com/programming-bitcoin/ . The VaultLayer SDK abstracts the complexity behind that process, making it easy for developers to start working right away on their Bitcoin DeFi applications. Here’s a video demo of an Ethereum wallet sending Bitcoin using the VaultLayer SDK: 

0:00
/0:55

Conclusion

VaultLayer, powered by Lit Protocol, offers a streamlined chain-abstraction SDK that simplifies the process of building applications on Bitcoin Layer 1 and EVM-compatible L2s. 

Resources

Join us on this exciting journey and discover how VaultLayer and Lit Protocol are making Bitcoin DeFi simple, seamless, and accessible for all!