Building an App example - Buy me a Coffee¶
We're going to build a very simple application called "Buy me a coffee".
A simple button that when pressed, requests a connection to the Plug wallet and a transfer!
A live demo is available but make sure to replace the url parameters in the address bar with your desired values for testing! If you're clueless, learn more here.
Requirements 🤔¶
The guide assumes you have some basic knowledge of HTML, CSS and Javascript, we'll keep it easy!
It's recommended to read the Getting started guide before you dive into the examples, as it gives you an overview of how Plug works.
Make sure you use a code editor, such as Visual Studio Code or Sublime text, for editing the source-code!
Scaffolding 🏗¶
Just like a real scaffolding in a building construction site, let's create a quick structure for our project, the skeleton for our application!
We're going to write a simple HTML structure for a page, where we'll show the "Buy me a coffee" button.
Start by creating a project directory in your operating system, for example, a directory called Plug-buy-me-coffee
in your home directory, or your favourite project directory.
In the project directory, create a new file named index.html
, with the following content and save it!
<html>
<head>
<title>Buy me a coffee</title>
<!-- App stylesheet (decoration, styles) -->
<!-- App javascript (functionality, logic) -->
</head>
<body>
<!-- App container (button) -->
</body>
</html>
We'll substitute the comments by the actual implementation, ordered by simplicity:
- Application structure (html)
- Custom styles (css)
- Call-to-action (javascript)
- Plug implementation (javascript, Plug API)
With that said, let's get our hands dirty and start coding!
Application structure 🚧¶
Open and edit the file index.html
replacing the <!-- App container (button) -->
comment with our desired application structure, as follows:
<html>
<head>
<title>Buy me a coffee</title>
<!-- App stylesheet (decoration, styles) -->
<!-- App javascript (functionality, logic) -->
</head>
<body>
<div id="app">
<button id="buy-me-coffee">Buy me a coffee</button>
</div>
</body>
</html>
Open the index.html
file in the browser or refresh the page as you go to see the changes!
Custom styles 👄¶
Create a new file named main.css
in the project directory to add custom styles for the app:
- Title colour
- Button size
- etc
Open and edit the file index.html
replacing the <!-- App stylesheet (decoration, styles) -->
comment with a link to our external stylesheet file we have just created, as follows:
<html>
<head>
<title>Buy me a coffee</title>
<link rel="stylesheet" href="main.css">
<!-- App javascript (functionality, logic) -->
</head>
<body>
<div id="app">
<button id="buy-me-coffee">Buy me a coffee</button>
</div>
</body>
</html>
Open end edit the stylesheet file main.css
and add some custom styles.
Let's say that we make the button super colourful like Plug, so copy the following content to the main.css
and save it:
#buy-me-coffee {
border: none;
font-style: normal;
font-weight: bold;
font-size: 16px;
line-height: 24px;
background: linear-gradient(94.95deg, #FFE701 -1.41%, #FA51D3 34.12%,
#10D9ED 70.19%, #52FF53 101.95%);
border-radius: 10px;
color: #fff;
padding: 6px 32px;
cursor: pointer;
transition: opacity 0.3s ease-in, transform 0.3s ease-in-out;
transform: scale(1);
}
#buy-me-coffee:hover {
opacity: 0.94;
transform: scale(1.02);
}
Once you refresh the page, it should look like example below, a colourful call-to-action.
We'll implement the action next!
Feel free to edit main.css
and change the styles to your liking! For example, change the container to center the button verticaly and horizontaly:
#app {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
Call-to-action 🏃🏽♀️¶
We are now going to create our main javascript file where we'll implement the call-to-action
.
Create a file named app.js
in the project directory. This is where we'll put our implementation code to handle the requests via the Plug's application programming interface (API), as described in the Getting started guide.
As an initial placeholder, let's add a "mouse-click" event listener to the call-to-action
that'll show an alert!
// Initialises the application listeners and handlers
function main() {
const button = document.querySelector('#buy-me-coffee');
button.addEventListener("click", onButtonPress);
}
// Button press handler
function onButtonPress() {
alert("Buy me a coffee button was pressed!")
}
// Calls the Main function when the document is ready
document.addEventListener("DOMContentLoaded", main);
Here's a simple breakdown of what it does:
- Awaits for the document to be ready
- Calls the
main
function - Adds a listener to the button
#buy-me-coffee
- When
clicked
calls the functiononButtonPress
Link the app.js
file to the index.html
for this to work!
Open and edit the file index.html
, replacing the <!-- App javascript (functionality, logic) -->
with a link to our javascript file app.js
, as follows:
<html>
<head>
<title>Buy me a coffee</title>
<link rel="stylesheet" href="main.css">
<script type="text/javascript" src="app.js"></script>
</head>
<body>
<div id="app">
<button id="buy-me-coffee">Buy me a coffee</button>
</div>
</body>
</html>
After you save the changes, refresh the page and press the Buy me a coffee
button. You'll get an alert with the message Buy me a coffee button was pressed!
.
We can now proceed and implement the Plug processes in the function body onButtonPress
.
Plug implementation 👷🏻♀️¶
Open the file app.js
in your code editor and edit the onButtonPress
function body, as we want to write the logic to request a transfer of a certain amount via the Plug extension API.
We can break it down in the following steps:
- Detect the Plug extension
- Check balance
- Request to transfer
- On success or error, display a message
Detect the Plug extension 🔎¶
Firstly, we check if the end-user has the Plug extension in the current browser, as documented in the Getting started guide.
Notice that we make the function asynchronous, as we're dealing with a network request, which is resolved at anytime in the future and as such, a response we need to await.
Edit the function body for onButtonPress
and replace the alert
placeholder with:
async function onButtonPress() {
const hasAllowed = await window.ic.plug.requestConnect();
if (hasAllowed) {
console.log('Plug wallet is connected');
} else {
console.log('Plug wallet connection was refused')
}
}
Important
So far, we've been opening the local index.html
file in the web browser for your easiness!
It works because opening html files in a browser, computes what the html file is describing and display the content correctly. But there are browser security restrictions, that prevent us to make browser extension requests from local files.
For this reason, you'll need to serve the project through a local http server!
Http Server 🤖¶
There are plenty of options on how to serve projects locally and you are free to pick whatever suits you best!
If you're clueless, the quickest you can get a http server running in your local machine, is to first install Nodejs - a program that executes javascript outside the browser, that let us run scripts such as a http server
, etc.
You can find the instructions for your operating system here. Once Nodejs is installed and available in your system, install the Http-server package, as an example.
Open your terminal and execute the following:
npm install --global http-server
Once the Http-server package is installed, cd
to the project directory in your terminal and run the following command (the dot represents the current directory):
http-server .
Open the browser that has the Plug extension installed and visit one of the addresses in your http server output:
Starting up http-server, serving .
Available on:
http://127.0.0.1:8080
http://xxx.xxx.x.xx:8080
Let's do a small test and inspect the data in the developer console
.
Open the developer console
in the browser to see the application script outputs, (here's an example for Chrome and Firefox).
Press the Buy me a Coffee
button and the Plug
notification window should pop-up.
Choose one of the options Allow
or Decline
and find the correspondent output in the console: Plug wallet is connected
or Plug wallet connection was refused
.
Call-to-action locking 🔒¶
To complete, ensure that the button is disabled through the Plug wallet request duration, to prevent multiple concurrent requests.
Here's a breakdown of our intent:
- Modify the
onButtonPress
to take a parameterel
- Pass the DOM button element as an argument during runtime
- Reset the
disabled
button property after 5 seconds
We give 5 seconds because we want to provide enough time to the end-user to read the text.
async function onButtonPress(el) {
el.target.disabled = true;
const hasAllowed = await window.ic.plug.requestConnect();
if (hasAllowed) {
console.log('Plug wallet is connected');
} else {
console.log('Plug wallet connection was refused')
}
setTimeout(function () {
el.target.disabled = false;
}, 5000);
}
Feel free to use your own custom values or implementation!
Check balance 💸¶
To keep things easy, let's say that a Coffee is the equivalent of 0.04
ICP or 4000000
(fractional units of ICP tokens, called e8s).
Add the amount at the top of the app.js
file!
const coffeeAmount = 4000000;
You can make the amount more readable by spliting the zero's by groups using an underscore:
const coffeeAmount = 4_000_000;
Finally, we check if the user has enough balance before proceeding to the last step and request the transfer.
We'll also change the button text in each state change to improve the user experience.
async function onButtonPress() {
el.target.disabled = true;
const hasAllowed = await window.ic?.plug?.requestConnect();
if (hasAllowed) {
el.target.textContent = "Plug wallet is connected";
const requestBalanceResponse = await window.ic?.plug?.requestBalance();
const balance = requestBalanceResponse[0]?.value;
if (balance >= coffeeAmount) {
el.target.textContent = "Plug wallet has enough balance";
} else {
el.target.textContent = "Plug wallet doesn't have enough balance";
}
} else {
el.target.textContent = "Plug wallet connection was refused";
}
setTimeout(function () {
el.target.disabled = false;
}, 5000);
}
Important
As described in the Getting started, the asynchronous method requestBalance response data is an array, as such, we pick the first result value that is an object type and get the value of the field name "value" of the object.
If you're not familiar with the question mark in window.ic?.plug?.requestBalance
, don't be worried as that's syntax sugar to help us access nested properties, read more about optional chaining here.
Save the file, refresh the page and press the Buy me a Coffee
button!
You should see the correspondent message to your account balance: Plug wallet has enough balance
or Plug wallet doesn't have enough balance
.
Request to transfer ❓¶
Continue editing the app.js
file.
In the "has enough balance" block, make the requestTransfer
call, that requires us to pass an argument to the function, that is an object with required fields to
and amount
, as described in our Getting started.
const requestTransferArg = {
to: receiverAccountId,
amount: coffeeAmount,
};
const transfer = await window.ic?.plug?.requestTransfer(requestTransferArg);
When the requestTransfer
is called, the Plug
pop-up will show the panel:
As we go, the button text is updated according to the transfer result state. When complete, we reset the Button to have the original text Buy me a coffee
.
async function onButtonPress(el) {
el.target.disabled = true;
const hasAllowed = await window.ic?.plug?.requestConnect();
if (hasAllowed) {
el.target.textContent = "Plug wallet is connected"
const balance = await window.ic?.plug?.requestBalance();
if (balance >= coffeeAmount) {
el.target.textContent = "Plug wallet has enough balance"
const requestTransferArg = {
to: 'xxxxx',
amount: coffeeAmount,
};
const transfer = await window.ic?.plug?.requestTransfer(requestTransferArg);
const transferStatus = transfer?.transactions?.transactions[0]?.status;
if (transferStatus === 'COMPLETED') {
el.target.textContent = `Plug wallet transferred ${coffeeAmount} e8s`;
} else if (transferStatus === 'PENDING') {
el.target.textContent = "Plug wallet is pending.";
} else {
el.target.textContent = "Plug wallet failed to transfer";
}
} else {
el.target.textContent = "Plug wallet doesn't have enough balance";
}
} else {
el.target.textContent = "Plug wallet connection was refused";
}
setTimeout(() => {
el.target.disabled = false;
el.target.textContent = "Buy me a coffee"
}, 5000);
}
Save the changes, refresh the browser and play with it!
If everything's done correctly you should have a working application, that connects to Plug and makes a transfer.
Hope you enjoyed the read this far and got to build a simple application with Plug!
Live demo 🎁¶
A live demo is available but make sure to replace the url parameters in the address bar with your desired values for testing!
If you're clueless, here's an example where the id
and amount
are replaced:
- http://demo.plugwallet.ooo/buy-me-a-coffee?id=xxx&amount=yyy
- http://demo.plugwallet.ooo/buy-me-a-coffee?id=893jk-u41jaz-439xx&amount=2000000
The id should be a valid Principal or Address id, the amount in (e8s) and you'll have to open or refresh the page after the changes!
Project source-code ⚙️¶
The source-code for this guide can be found in our examples.