'Buy Now, Pay Later' financing

This guide provides an initial overview of using the Unlockd SDK for the 'Buy Now, Pay Later' functionality. As we're in the early stages of documentation, your feedback is crucial. We encourage developers to explore this use case and help shape these materials.

We're committed to supporting your integration journey. If you need personalized assistance or have suggestions, please reach out to us directly. Your input will help us create more comprehensive and useful documentation for the developer community.

Overview

'Buy Now, Pay Later' (BNPL) financing is a payment solution that allows users to purchase tokenized assets immediately while deferring payment indefinitely (thanks to Unlockd's open-ended loans).

By integrating BNPL functionalities using the Unlockd SDK, developers can provide a seamless and flexible financing option directly at the point of sale, backed by Real-World Assets.

How BNPL Works

  1. User Selection: During the checkout process, users select the 'Buy Now, Pay Later' option as their preferred payment method.

  2. Risk Assessment: Unlockd conducts a real-time appraisal of the user's tokenized assets to determine their value. This appraisal helps in setting an appropriate Loan-to-Value (LTV) ratio for the loan.

  3. Down Payment Selection: Users are required to select a down payment amount, which may sometimes be a minimum required amount based on the value of the asset and the LTV ratio.

  4. Loan Origination: Once the down payment is made, a loan is originated on the Unlockd protocol. The user's tokenized assets are used as collateral to secure the loan.

  5. Instant Payment: The merchant receives the full payment for the asset immediately from Unlockd's liquidity pool, ensuring they do not bear any financial risk.

  6. Open-Ended Loan: The loan remains open-ended, allowing the user to repay at their convenience as long as the health factor of their collateralized asset remains within acceptable limits. There are no fixed repayment schedules.

  7. Collateral Management: The user’s collateralized assets are held securely in their Unlockd Account. These assets remain in the user’s custody and ownership, with Unlockd intervening only to prevent unauthorized transactions during the loan period.

  8. Health Factor Monitoring: Users need to maintain the health factor of their loan, which is continuously monitored. If the health factor drops below a certain threshold, the user may need to add more collateral or repay part of the loan to restore the balance.

  9. Completion: Once the loan is fully repaid, the collateralized assets are released back to the user without any restrictions.

Benefits of BNPL with Unlockd

  • Seamless Integration: Easily incorporate BNPL options into existing checkout flows using the Unlockd SDK.

  • Enhanced Flexibility: Users enjoy the flexibility of deferred payments without immediate financial burden.

  • Improved Cash Flow for Merchants: Merchants receive full payment upfront, improving their cash flow and reducing financial risk.

  • Lower Risk: The use of tokenized assets as collateral reduces the risk for both users and the protocol, enabling better loan terms and interest rates.

How-To Guide

This guide will walk you through the process of implementing a borrowing feature using the Unlockd SDK, allowing users to borrow against RWAs.

Prerequisites

Before you begin, ensure you have the following set up:

  • A React-based project

  • The Unlockd SDK installed (@verislabs/unlockd-sdk)

  • Wagmi library and its dependencies (viem, react-query) for Ethereum interactions. You can Create a Project or Install it manually into your Project.

  • Basic understanding of React hooks and Ethereum transactions

This example is written in TypeScript and uses the wagmi library along with its dependencies. However, it's important to note that wagmi is not required to use the Unlockd SDK. The SDK itself is only dependent on viem.

Developers are free to use their preferred tools and libraries for wallet connection and blockchain interactions. This example serves as one possible implementation approach.

Implementation Steps

  1. Set up the React component with necessary state variables and hooks.

  2. Implement login, connection and authentication

  3. Create or refresh Unlockd wallet (Unlockd Account).

  4. Implement BNPL transaction execution.

  5. Render the user interface with all interactive elements.

Code Overview

Implementing the Buy Now, Pay Later (buyNow in the SDK) functionality in a testnet environment is more complex due to the following reasons:

  1. Two-wallet setup: You need one wallet to mint and list an NFT for sale on https://testnets.reservoir.tools/sepolia, and a second wallet to execute the purchase.

  2. Testnet limitations: Reservoir does not allow token swaps on testnets, including Sepolia. This affects how BNPL transactions are structured in the test environment.

  3. Currency differences: In the testnet implementation, we use WETH. However, on mainnet, USDC is typically used for RWA purchases.

These factors make recreating a full BNPL flow on testnet more challenging. Keep these differences in mind when testing and developing your BNPL integration, as the actual mainnet implementation will differ.

If you need any help, contact us. We actually like being bothered.

1. Set up the React component with necessary state variables and hooks

Initialize the main App component and set up all required state variables using React hooks. This includes states for managing user accounts, selected assets, and BNPL transaction details.

"use client";

import { useState, useEffect } from "react";
import {
    useAccount,
    useConnect,
    useDisconnect,
    useConfig,
    useWriteContract,
} from "wagmi";
import {
    signMessage,
    readContract,
    writeContract,
    waitForTransactionReceipt,
} from "wagmi/actions";
import { sepolia } from "wagmi/chains";
import { injected } from "wagmi/connectors";
import { parseUnits, Address, isAddress } from "viem";
import {
    UnlockdApi,
    Chains,
    UnderlyingsAsset,
    borrow,
    underlyingsAssets,
    createWallet,
    getWallet,
} from "@verislabs/unlockd-sdk";

interface NFTPriceData {
    collection: Address;
    tokenId: string;
    underlyingAsset: Address;
    price?: string;
}

const ALLOWED_COLLECTIONS: Address[] = [
    "0xa6a9acfdd1f64ec324ee936344cdb1457bdbddf0",
    "0x388043e55a388e07a75e9a1412fe2d64e48343a5",
];

// Choose the asset I want
// Validate if possible to buy from reservoir
// If yes: check if the user has a unlockdWallet: if not, create one, if yes: continue.
// Validate the user amount, the selected NFT, and get the buynow signature.
// BuyNow call getCalculations with the signature and the (minAmount,)
// BuyNow

function App() {
    // Variables
    const userAccount = useAccount();
    const { connect, connectors } = useConnect();
    const { disconnect } = useDisconnect();
    const config = useConfig();

    const [unlockdAccount, setUnlockdAccount] = useState<Address>();
    const [token, setToken] = useState<string | null>(null);
    const [selectedUnderlyingAsset, setSelectedUnderlyingAsset] =
        useState<Address>(underlyingsAssets(Chains.Sepolia)[UnderlyingsAsset.USDC]);
    const [selectedUnlockdNFTs, setSelectedUnlockdNFTs] = useState<
        NFTPriceData[]
    >([]);
    const [borrowAmount, setBorrowAmount] = useState<string>("");
    const [collection, setCollection] = useState<string>("");
    const [tokenId, setTokenId] = useState<number>(0);
    const [amount, setAmount] = useState<number>(0);

    const api = new UnlockdApi(Chains.Sepolia);

In this example, all the code is contained within the root component (App) for simplicity. However, this functionality can be implemented in any React component.

The hooks used (useAccount, useDisconnect, etc.) are imported from wagmi, while useState is from React. These reactive variables can be stored and managed using any state management solution you prefer.

It's important to note the initialization of the Unlockd SDK, which is a crucial step:

typescriptCopyconst api = new UnlockdApi(Chains.Sepolia);

This line, found at the end of the component, sets up the Unlockd API instance for interacting with the Sepolia testnet. Make sure to initialize the SDK appropriately in your implementation.

2. Implement login, connection and authentication

Set up functionality authenticating with Unlockd. This step calls the Unlockd SDK for obtaining an authentication token for the user's currently connected address by signing a blockchain message. The auth token is essential for user's interaction with the Unlockd SDK

    // The login should call the unlockd API using the signatureMessage method to get the message to sign.
    // We get the message to sign, and then we trigger it to the user so he can sign it (signMessage).
    // After the user signs the message, we validate it using the validateMessage method, and returning the auth token we will need to interact with the protocol.
    const logIn = async () => {
        if (!userAccount.address) {
            return;
        }
        try {
            const message = await api.signatureMessage(userAccount.address);
            const signature = await signMessage(config, {
                account: userAccount.address,
                message: message.message,
            });
            const authToken = await api.validateMessage(
                userAccount.address,
                signature,
            );
            setToken(authToken.token);
        } catch (error) {
            console.error("Login failed:", error);
        }
    };

3. Create or refresh Unlockd wallet (Unlockd Account)

Implement logic to create a new Unlockd wallet if the user doesn't have one, or refresh an existing one. This step is crucial for managing the user's assets and loans.

    // In order to do a buyNow, you will need to own an unlockd wallet.
    // The assets (nfts) will be locked in your unlockd wallet.
    // If you don't have an unlockd wallet, it will be created.
    const createOrRefreshUnlockdWallet = async () => {
        try {
            let unlockdWallet = await getWallet({ network: Chains.Sepolia });
            if (!unlockdWallet) {
                await createWallet({ network: Chains.Sepolia });
                unlockdWallet = await getWallet({ network: Chains.Sepolia });
            }
            if (unlockdWallet) {
                setUnlockdAccount(unlockdWallet);
            }
        } catch (error) {
            console.error("Error with Unlockd wallet:", error);
        }
    };

4. Implement BNPL transaction execution

Set up the core BNPL functionality using the Unlockd SDK. This involves getting the buy now signature, calculating the loan terms, and executing the BNPL transaction.

    const handleBuyNow = async () => {
        if (selectedUnlockdNFTs.length === 0 || !token || !unlockdAccount) {
            return;
        }

        try {
            const nftsArray = selectedUnlockdNFTs.map((nft) => ({
                collection: nft.collection,
                tokenId: nft.tokenId,
            }));

            const params = {
                nfts: nftsArray,
                underlyingAsset: selectedUnderlyingAsset,
            };

            const signature = await api.borrowSignature(token, params);

            await borrow(BigInt(parseUnits(borrowAmount, 6)), nftsArray, signature, {
                network: Chains.Sepolia,
            });
        } catch (error) {
            console.error("Borrow failed:", error);
        }
    };

    useEffect(() => {
        if (token) {
            createOrRefreshUnlockdWallet();
        }
    }, [token]);

    const isAuth = userAccount.isConnected && !!token;

5. Render the user interface with all interactive elements

Create and render the user interface components that tie all the functionality together. This includes elements for wallet connection, asset selection, and initiating the BNPL transaction.

    return (
        <div>
            <h1>Unlockd Borrow Example</h1>

            {/* Connect wallet */}
            {(() => {
                if (!userAccount.isConnected) {
                    return (
                        <div>
                            <button onClick={() => connect({ connector: injected() })}>
                                Connect
                            </button>
                        </div>
                    );
                }

                return (
                    <div>
                        <p>Connected to {userAccount.address}</p>
                        <button onClick={() => disconnect()}>Disconnect</button>
                    </div>
                );
            })()}

            {/* Check current network */}
            {(() => {
                if (userAccount.isConnected && userAccount.chainId !== sepolia.id) {
                    return (
                        <div>
                            <p>Please switch to Sepolia network</p>
                        </div>
                    );
                }
                return null;
            })()}

            {/* Unlockd Log In */}
            {(() => {
                if (userAccount.isConnected && !isAuth) {
                    return (
                        <div>
                            <button onClick={logIn}>Log In to Unlockd</button>
                        </div>
                    );
                }
                return null;
            })()}

            {/* Select asset */}
            {(() => {
                if (isAuth) {
                    return (
                        <select
                            value={selectedUnderlyingAsset}
                            onChange={(e) =>
                                setSelectedUnderlyingAsset(e.target.value as Address)
                            }
                        >
                            {Object.entries(UnderlyingsAsset).map(([key, value]) => (
                                <option
                                    key={key}
                                    value={underlyingsAssets(Chains.Sepolia)[value]}
                                >
                                    {key}
                                </option>
                            ))}
                        </select>
                    );
                }
            })()}

            {/* Create & display Unlockd wallet and the supported collections */}
            {(() => {
                if (isAuth) {
                    return (
                        <>
                            <h2>Unlockd Wallet</h2>
                            <p>Unlockd Wallet: {unlockdAccount || "Not created"}</p>
                            <button onClick={createOrRefreshUnlockdWallet}>
                                Create/Refresh Unlockd Wallet
                            </button>
                            {unlockdAccount && (
                                <>
                                    <h3>NFT to buyNow</h3>
                                    <div>
                                        <input
                                            type="text"
                                            value={collection}
                                            onChange={(e) => setCollection(e.target.value)}
                                            placeholder="Collection"
                                        />
                                    </div>
                                    <div>
                                        <input
                                            type="number"
                                            value={tokenId}
                                            onChange={(e) => setTokenId(parseInt(e.target.value))}
                                        />
                                    </div>
                                    <div>
                                        <input
                                            type="number"
                                            value={amount}
                                            onChange={(e) => setAmount(parseInt(e.target.value))}
                                        />
                                    </div>
                                    <div>
                                        <button onClick={handleBuyNow}>Buy Now</button>
                                    </div>
                                </>
                            )}
                        </>
                    );
                }
            })()}
        </div>
    );
}

export default App;

Last updated