'Buy Now, Pay Later' financing
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
User Selection: During the checkout process, users select the 'Buy Now, Pay Later' option as their preferred payment method.
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.
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.
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.
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.
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.
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.
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.
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
Implementation Steps
Set up the React component with necessary state variables and hooks.
Implement login, connection and authentication
Create or refresh Unlockd wallet (Unlockd Account).
Implement BNPL transaction execution.
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:
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.
Testnet limitations: Reservoir does not allow token swaps on testnets, including Sepolia. This affects how BNPL transactions are structured in the test environment.
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);
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