Built on
Developers
Rent back.
Inside your dApp.
Since SIMD-0437 your users’ token accounts hold more rent than Solana asks for. Add UNRENT where they connect their wallet: one scan, two clicks, in your design. Your app keeps 5% of every reclaim.
01 Overview
A few lines.
No backend.
No API key, no server of your own, no dependency. The browser talks to UNRENT, the user’s wallet signs, the SOL lands in the user’s wallet and your share in yours, in the same transactions.
Drop-in
Widget
A card or a one-line banner. Shows only when there is SOL to reclaim. About 6 KB gzipped.
Your UI
Headless client
scan, build, submit and a one-call reclaim, with every transaction checked before signing.
5%
Your share
Of every reclaim in your app, paid on chain with the reclaim itself. Nothing to invoice.
The flow
- 1 wallet connects → free scan: reclaimable SOL, fee, net
- 2 the widget shows the net amount; the user clicks Reclaim
- 3 build → unsigned transactions, checked in the browser
- 4 the user’s wallet signs
- 5 submit → SOL in the user’s wallet, 5% in yours
02 Live preview
Your fonts.
Your colors.
The real widget, on demo numbers. It sits in a shadow root, so your CSS cannot break it; eight CSS variables make it look like your app. The only UNRENT mark is a small “powered by unrent.xyz”.
Theme
Variant
unrent-reclaim {
--unrent-font: system-ui, sans-serif;
--unrent-bg: #16131f;
--unrent-fg: #f4f2ff;
--unrent-muted: #a39fb8;
--unrent-border: #2b2640;
--unrent-accent: #9945ff;
--unrent-accent-fg: #ffffff;
--unrent-radius: 16px;
}03 Quick start
Three steps.
Ten minutes.
01Get your partner code
Open Referral, connect the wallet that should receive your share and sign the message (no transaction, no cost). The code you pick, e.g.
yourdapp, is your partner code. The same page shows your reclaims and earnings.02Add the widget
Script tag: any site, no build step <script src="https://unrent.xyz/embed/v1.js" defer></script> <unrent-reclaim partner="yourdapp"></unrent-reclaim>npm npm install github:unrentsol/unrent-embed#v0.1.0Mount it import { mount } from 'unrent-embed'; const widget = mount('#unrent', { partner: 'yourdapp' });03Hand it the connected wallet
The widget never connects wallets itself; it uses the one your app connected. Give it a signer and update it when the wallet changes. While no wallet is connected, or there is nothing to reclaim, it renders nothing.
import { fromWalletStandard } from 'unrent-embed'; widget.setSigner(fromWalletStandard(wallet, wallet.accounts[0])); // connected widget.setSigner(null); // disconnected // Custom element: set the property instead document.querySelector('unrent-reclaim').signer = signer;
04 Wallet libraries
Any wallet.
One signer.
A signer is an address and a function that signs wire transactions without sending them. Helpers cover the common libraries;
anything else fits createSigner.
import { useEffect, useRef } from 'react';
import { useWallet } from '@solana/wallet-adapter-react';
import { VersionedTransaction } from '@solana/web3.js';
import { fromWalletAdapter, mount, type Widget } from 'unrent-embed';
export function ReclaimRent() {
const wallet = useWallet();
const el = useRef<HTMLDivElement>(null);
const widget = useRef<Widget | null>(null);
useEffect(() => {
widget.current = mount(el.current!, { partner: 'yourdapp' });
return () => widget.current?.destroy();
}, []);
useEffect(() => {
widget.current?.setSigner(
wallet.connected ? fromWalletAdapter(wallet, VersionedTransaction) : null
);
}, [wallet.connected, wallet.publicKey]);
return <div ref={el} />;
}import { useSignTransaction } from '@solana/react';
import { createSigner } from 'unrent-embed';
const signTransaction = useSignTransaction(account, 'solana:mainnet');
const signer = createSigner(account.address, async (txs) => {
const out = [];
for (const transaction of txs) out.push((await signTransaction({ transaction })).signedTransaction);
return out;
});import { getWallets } from '@wallet-standard/app';
import { fromWalletStandard } from 'unrent-embed';
const wallet = getWallets().get().find((w) => w.name === 'Phantom');
const signer = fromWalletStandard(wallet, wallet.accounts[0]);import { createSigner } from 'unrent-embed';
// Uint8Array[] in, signed Uint8Array[] out: unchanged, in order, not sent.
const signer = createSigner(address, (txs) => myWallet.signAll(txs));Vue, Svelte and plain HTML examples are in the repository.
05 Make it yours
Eight variables.
Seven options.
Set the variables on the element or any parent. For finer control: ::part(container), ::part(button), ::part(powered).
- --unrent-font
- Font family
- inherit
- --unrent-bg
- Background
- transparent
- --unrent-fg
- Text
- currentColor
- --unrent-muted
- Secondary text
- 62% of the text color
- --unrent-border
- Hairline border
- 16% of the text color
- --unrent-accent
- Button
- #14f195
- --unrent-accent-fg
- Button text
- #000
- --unrent-radius
- Corner radius
- 12px
mount(element, options)
- partner
- Your partner code from /referral.
- required
- variant
- 'banner': one line for headers and sidebars.
- 'card'
- theme
- The variables above as an object: font, background, foreground, muted, border, accent, accentForeground, radius.
- inherit
- labels
- Replace any text, e.g. for translations (DEFAULT_LABELS).
- English
- hideWhenEmpty
- false also shows “connect a wallet”, “checking…” and “nothing to reclaim”.
- true
- dismissible
- A close button; hides the widget for that wallet until reload.
- true
- onScan · onReclaim · onError
- Callbacks. The custom element fires unrent:scan, unrent:reclaim, unrent:error.
06 Headless client and API
Your own UI.
Same checks.
The widget runs on this client. reclaim() builds, checks every transaction, has the
wallet sign and submits. Base URL https://unrent.xyz/api/v1, open to any origin, all
free.
import { createUnrent } from 'unrent-embed';
const unrent = createUnrent({ partner: 'yourdapp' });
const scan = await unrent.scan(address);
// { reclaimableSol, accounts, fee: { bps, sol }, partner: { bps, sol }, netSol, nextStep }
if (scan.netSol > 0) {
const result = await unrent.reclaim(signer, {
onStatus: (s) => console.log(s) // building → signing → sending
});
// { status: 'confirmed', netSol, transactions: [{ signature, explorer }], leftForNextClaim }
}POST/embed/scan
{ wallet, partner }
- Reclaimable SOL, the fee, your share, the net and the next rent step. Reads the chain only.
POST/embed/build
{ wallet, partner, accounts? }
- Unsigned, simulated v0 transactions (base64) and a claimId. No burns.
POST/reclaim/submit
{ claimId, signed }
- Checks the signed transactions against the build and sends them.
curl -s -X POST https://unrent.xyz/api/v1/embed/scan \
-H 'content-type: application/json' \
-d '{"wallet":"<address>","partner":"yourdapp"}'Errors
- 400
- Invalid wallet, unknown or paused partner code
- 404
- Nothing to reclaim (any more)
- 409
- A claim of this wallet is still on its way: wait a minute
- 422
- The wallet cannot pay the network fee, or a simulation failed
- 429
- Rate limited per visitor IP
- 503
- Busy or paused: retry shortly
Every call, type and error: API reference.
07 Your share
10% fee.
Split in two.
The service fee is 10% of the reclaimed SOL. For a reclaim in your app, 5% of the reclaimed SOL goes to your wallet and 5% to UNRENT. Your users pay exactly what they pay on unrent.xyz.
Example: 1 SOL reclaimed
- User
- 0.90 SOL
- Your app
- 0.05 SOL
- UNRENT
- 0.05 SOL
- Paid on chain
- Your share is a transfer inside the user’s reclaim transactions, so it lands the moment the reclaim does. Nothing to invoice, claim or withdraw.
- Every claim in your app
- Whoever referred a wallet before, a reclaim made in your interface pays you. Your app does not bind wallets: reclaims on unrent.xyz follow the usual referral rules.
- Holder discount
- $UNRNT holders pay a lower fee (tiers). Your share is capped at the fee the wallet pays, so a holder at 5% or less pays it all to you.
- Payout wallet
- It must hold Solana’s minimum balance (about 0.00089 SOL). Until it does, payouts into it would fail the user’s transaction, so they are skipped and the whole fee goes to UNRENT.
08 Security
Checked twice.
Before signing.
UNRENT never holds keys or funds, and neither does your app. On top of the server’s checks, the client parses every transaction in the browser and signs nothing unless all of them are plain reclaims:
- ✓A v0 transaction without address lookup tables, so every account is visible, paid by the connected wallet.
- ✓Token and Token-2022: only WithdrawExcessLamports and CloseAccount, each paying the wallet and authorized by it. No transfers, approvals, burns or authority changes.
- ✓System: only transfers out of the wallet (the fee). Compute Budget is allowed. Any other program is refused.
- ✓All transfers together are at most the fee the build announced.
So even a compromised server could at worst ask for the announced fee, never for tokens or more SOL. The server re-scans on every build, simulates every transaction and accepts only the transactions it built. Details →
09 Good to know
Short list.
Read once.
- Mainnet
- The API serves Solana mainnet.
- No burns
- The embed never touches tokens: it returns excess rent and closes accounts that are already empty. Dust clean-up stays on unrent.xyz.
- Network fee
- The user’s wallet pays Solana’s fee, a few thousandths of a cent per transaction, so it needs a little SOL.
- Large wallets
- One reclaim holds up to 8 transactions, as wallets limit what they sign at once. The widget then offers “Reclaim the rest”.
- Rate limits
- Scans are limited per visitor IP, as on unrent.xyz. The widget scans once per wallet connection.
- CSP
- Allow connect-src https://unrent.xyz, and script-src https://unrent.xyz for the script tag.
- Branding
- Keep “powered by unrent.xyz” visible and the fee in the text: users should know what they sign.
- Open source
- MIT licensed, on GitHub. Issues and pull requests welcome. unrentsol/unrent-embed ↗