Building on Libre — Let’s make the world smile [Part 1]
Libre gives you the freedom to transact, save, and trade anywhere in the world without middlemen (or banks). It is the first blockchain focused on making the two most popular cryptocurrencies in the world — Bitcoin and Tether — programmable and easy to use.
You can learn more about Libre Chain in this article. Today we will walk through building simple application on top of Libre Chain. Before diving into development, I feel we need to cover few technical details:
- Libre blockchain is running on Antelope.io software, with customised system contracts. You can find more details on Libre GitBook
- Unlike many other chains, Libre doesn’t require users to own native tokens for paying gas fees. Instead, it uses resources — RAM, CPU and NET. On Libre Blockchain, every new user gets enough resources for interacting with dApps without taking any action.
- You can interact with Libre Blockchain using EOSJS , very well documented JS library for EOSIO. These are public endpoints which you can use for your dApp development, but if you don’t want to be dependent on one single producer’s endpoints, you can simply use lb.libre.org
4. For cross-chain interoperability Libre uses PNetwork. You can read more about PNetwork and Libre here. As a developer, what you need to know is this:
- Bitcoin on Libre is issued by btc.ptokens contract, token symbol is PBTC, precision — 9.
- USDT on Libre is issued by usdt.ptokens contract, token symbol is PUSDT, precision — 9.
5. Libre website has a nice Development section, where you can find everything you need for building dApp — from Libre-Link — fast and secure signature provider, to smart contract development tools
6. Check out Bitcoin Libre app — that’s the first app built on top of Libre Chain — you can create account and bring in some BTC within seconds using Lightning Network.
Okay, we have all the resources and we know the basics. Let’s start building.
First of all, let’s define the task — and since this is a walkthrough, let’s build something very simple and fun — no tokenomics involved. After thinking about today’s task, I came up with this simple idea:
I want to build an app, where users will be able to login every day using Libre account, smile to web-camera and we’ll give them 1 sats.
How are we going to build it:
- We’ll build a React app and use Libre-Link for authentication
- We’ll use face-api.js for detecting emotions
- We’ll use NestJS backend for recording user activity and sending SATS
- I will create new account on Libre Blockchain —
justsmile
and send few thousand SATS. That will be our ‘bank’ which will cover user rewards.
Let’s start with account creation:
Libre has a publicly accessible website https://accounts.libre.org where you can create accounts. You’ll need a public key — and don’t worry, here you can find tons of options for getting one
I prefer using cleos, so I’ll just go to my terminal and type
> cleos create key --to-console
And I got my keys (I’ve hidden the private key, of course)
Private key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Public key: EOS8PgBtxm21Bnshy7LhBNJTMUEcQRrupnRev8v4YcvcSnrWr8gp8
I headed to account creator, and after filling 3 fields and picking squirrels on a tree (funny captcha — made me smile), my account was ready
Time to move some funds to the account — I have my primary account imported in Anchor wallet, and I sent 10k SATs to justsmile
— it took more time to write down this step, then the transaction itself. We are ready to fund 10 000 smiles 💛
Let’s start with React app — standard setup, nothing fancy
> npx create-react-app libre-smile-webapp --template typescript
and add our main dependencies — Libre-Link, Libre-Link-Browser-Transport and Libre-Signing-Request for authentication and face-api.js — for face detection
> yarn add @libre-chain/libre-link face-api.js
and let’s add MUI — we want our app to be beautiful
yarn add @mui/material @emotion/react @emotion/styled
Let’s move on to the functionality. First of all, we need to let our users log in. I will move everything related to Libre Blockchain connection to LibreClient class:
import LibreLink, {
Link,
LinkSession,
LinkTransport,
} from "@libre-chain/libre-link";
import LibreLinkBrowserTransport from "@libre-chain/libre-link-browser-transport";
import moment from "moment";
export interface User {
actor: string;
permission: string;
}
export interface WalletResponse {
user: User | null;
error: string;
}
class LibreClient {
appName = `Libre Smile`;
session: LinkSession | null;
link: Link | null;
transport: LinkTransport | null;
nodeUrl = `http://lb.libre.org`;
chainId = `38b1d7815474d0c60683ecbea321d723e83f5da6ae5f1c1f9fecc69d9ba96465`;
constructor() {
this.session = null;
this.transport = new LibreLinkBrowserTransport();;
this.link = new LibreLink({
transport: this.transport,
chains: [
{
chainId: String(this.chainId),
nodeUrl: String(this.nodeUrl),
},
],
scheme: 'libre',
});
}
login = async (): Promise<WalletResponse | undefined> => {
try {
this.link = new LibreLink({
transport: new LibreLinkBrowserTransport(),
chains: [
{
chainId: String(this.chainId),
nodeUrl: String(this.nodeUrl),
},
],
scheme: 'libre',
});
const identity = await this.link.login(this.appName);
this.session = identity.session;
console.log({
actor: identity.session.auth.actor.toString(),
permission: identity.session.auth.permission.toString(),
expiration: moment().add(20, "minutes"),
});
localStorage.setItem(
"user-auth",
JSON.stringify({
actor: identity.session.auth.actor.toString(),
permission: identity.session.auth.permission.toString(),
expiration: moment().add(20, "minutes"),
})
);
return {
user: {
actor: identity.session.auth.actor.toString(),
permission: identity.session.auth.permission.toString(),
},
error: "",
};
} catch (e: any) {
return {
user: null,
error: e.message || "An error has occurred while logging in",
};
}
};
}
export default new LibreClient();
By the way, once you install @libre-chain/libre-link
package, you’ll immediately remember that Webpack 5 removed polyfills for core NodeJS modules, so you’ll need to rewire your React app (use react-app-rewired with crypto-browserify and stream-browserify fallbacks 😜)
Once you get used to Anchor-Link / Libre-Link, it’s pretty much straightforward — we are setting up new link, requesting login and after successful sign-in, we just set current user’s identity information in local storage.
Up to this point, we’ve achieved this result:
and when I scan this QR code in my Bitcoin Libre app, this beautiful modal disappears and I get the following in my browser console:
So — all good, login works. In many cases this is the most important part of the project 😆
Now, let’s reflect our login state in our web-app and move to backend.
What I’m going to do now is unacceptable (yes, I’m going to put everything in App.tsx) but since this is a demo, and I want to showcase the functionality and make the world smile at the same time — you must forgive me.
Let’s move to our App.tsx, which looks like this at this point:
<ThemeProvider theme={darkTheme}>
<CssBaseline />
<Box sx={{ flexGrow: 1 }}>
<AppBar position="static">
<Toolbar>
<Grid component="div" sx={{ flexGrow: 1 }}>
<img src={libreLogo} style={styles.logo} alt="Libre Logo" />
</Grid>
<Button color="inherit" onClick={() => LibreClient.login()}>Connect</Button>
</Toolbar>
</AppBar>
</Box>
<main>
</main>
</ThemeProvider>
What we need to do now — let’s reflect login result in our UI — once user logs in we can say hello instead of displaying Connect button. Also, we’ll need to show something on the page.
After few tweaks, our App component looks like this:
function App() {
const [user, setUser] = useState<User>();
const login = async() => {
const loginResult = await LibreClient.login();
if (loginResult) {
const {user} = loginResult;
setUser(user!);
}
}
return (
<ThemeProvider theme={darkTheme}>
<CssBaseline />
<Box sx={{ flexGrow: 1 }}>
<AppBar position="static">
<Toolbar>
<Grid component="div" sx={{ flexGrow: 1 }}>
<img src={libreLogo} style={styles.logo} alt="Libre Logo" />
</Grid>
{
!user && <Button color="inherit" onClick={login}>Connect</Button>
}
{
user && <Button color="primary">Welcome, {user.actor}</Button>
}
</Toolbar>
</AppBar>
</Box>
<main>
<Box>
{
!user && (
<Box
display="flex"
justifyContent="center"
alignItems="center"
minHeight="80vh"
>
Please connect your wallet
</Box>
)
}
{
user && (
<FaceDetector user={user.actor} />
)
}
</Box>
</main>
</ThemeProvider>
);
}
I’ve put together FaceDetector component, which uses face-api.js to detect smile on user’s face and once it does — it pings backend to pay user. I won’t dive into details of expression detector — there are plenty of examples / walkthroughs (this one was the most useful) and it’s not goal of this article.
Let’s summarise what we did — we successfully implemented Libre Login using libre-link and we are able to identify user on the frontend.
It’s time to move to backend — we need to create single endpoint which will check users’ eligibility to receive SATS and will transfer SATS, if everything is fine. For now we’ll just receive username and send 1 SATS — I won’t include any additional checks for now.
Let’s create backend 😍
I’ll use NestJS —my favourite NodeJS framework — this one endpoint doesn’t require more than one script, it will get quite messy in the future, so let’s make things right from the beginning
> nest new libre-smile-api
and add all necessary dependencies — @nestjs/config, eosjs and node-fetch@2.
What we’ll do — we’ll initialize JsonRPC and EOSJS API, and write method which transfers SATS from justsmile
account to user
Init JsonRPC & API:
export class AppService {
private _rpc;
private _nodeUrl = `https://lb.libre.org`;
private _bankPrivateKey;
private _bankName;
private _api;
constructor(private _configService: ConfigService) {
this._rpc = new JsonRpc(this._nodeUrl, { fetch });
this._bankPrivateKey = this._configService.get('BANK_PRIVATE_KEY');
this._bankName = this._configService.get('BANK_NAME');
const signatureProvider = new JsSignatureProvider([this._bankPrivateKey]);
this._api = new Api({
rpc: this._rpc,
signatureProvider,
textDecoder: new TextDecoder(),
textEncoder: new TextEncoder(),
});
}
}
We pick up BANK_PRIVATE_KEY from .env, we init JsonRPC, tell API to use that RPC and sign transactions with our private key.
Let’s write second method which transfers PBTC:
async transfer(to: string, sats: number) {
const amountInBTC = sats / 10 ** 8;
console.log(`Transferring ${amountInBTC} PBTC to ${to}`);
const actions = [
{
account: 'btc.ptokens',
name: 'transfer',
authorization: [
{
actor: this._bankName,
permission: 'owner',
},
],
data: {
from: this._bankName,
to,
quantity: `${amountInBTC.toFixed(9)} PBTC`,
memo: 'Thanks for smiling',
},
},
];
console.log(actions);
const transaction = await this._api.transact(
{
actions,
},
{
blocksBehind: 3,
expireSeconds: 30,
},
);
return transaction;
}
We convert SATS to BTC (divide it by 10⁸) and construct action, which in plain Engish sounds like this:
Call transfer method in btc.ptokens contract with following data: from should be our BANK_NAME, to should be whatever I pass to this function, memo should be ‘Thanks for smiling’ and quantity should be 0.000000010 PBTC (if you’re confused by ‘10’ in the end, remember, we have precision set to 9 for PBTC).
Best regards, BANK_NAME (this is set in authorization part)
EOSJS signs this transaction with our private key and broadcasts it to the network. Here is test transaction which I made when I was testing the method.
Let’s finalise this iteration of our evil plan to make the world smile — all we need to do is add one endpoint which will call this transfer method and that’s it 🚀
Here is the outcome:
and I received 1 SAT 😍
Here are both Frontend and Backend repos:
Libre Smile WebApp: https://github.com/LevanIlashvili/libre-smile-webapp
Libre Smile API: https://github.com/LevanIlashvili/libre-smile-api
and here is live version: https://smile.libre.rocks/
In Part 2 we’ll create smart contract on Libre Chain and add few things (like — signature to verify smile 😊 and prevent bad people from calling API endpoint with scripts)
Thanks for reading this article and think what you can build on Libre 🚀