Bitcoin Calculator
- abhyash
- Oct 31, 2022
- 5 min read
Updated: Nov 5, 2022
V1 Prepared By: Abhyash Timsina - 31 October 2022
Motivation: Showcase a demo of Lightning Web Component communicating with APEX to do a REST callout to Bitpay.com, get all currency codes from JSON as well as doing real time calculation – specify a value and currency code and get the currency value. Note - This is a personal project and not intended for financial gain.
My Technical Components

Screenshots of LWC


Source Code for bitcoinLWC (with annotation starting with //)
Html
<!--
- Created by Abhyash Timsina on 31/10/2022.
-->
<template>
<lightning-card title="Bitcoin Value Comparison">
<div style="text-align: right; padding-right: 20px"> Rate: {rateValue} </div>
<div class="slds-p-around_medium lgc-bg">
// Lightning combobox dropdown that displays variables from Javascript for currency codes
<lightning-combobox
name="comparisonCurrencyCodeValue"
label="Comparison Currency Code"
value={comparisonCurrencyCodeValue}
placeholder="Select Currency Code"
options={countryCodeOptions}
onchange={handleComparisonCurrencyChange}
></lightning-combobox>
</div>
<div class="slds-p-around_medium lgc-bg">
// Lightning Input that allows user to input the Bitcoin Value
<lightning-input type="number" name="bitcoinValue" label="Bitcoin Value" value={bitcoinValue} step="0.01" onchange={handleBitcoinValueChange}></lightning-input>
</div>
<div class="slds-p-around_medium lgc-bg">
// Lightning Input that allows user to input the Currency Value
<lightning-input type="number" name="currencyValue" label="Currency Value" value={currencyValue} formatter="currency" step="0.01" onchange={handleCurrencyValueChange}></lightning-input>
</div>
// Create Record button that creates a record on Bitcoin Request object of above 3 values
<div style="text-align: right; padding-right: 20px"> <lightning-button variant="brand" label="Create Record" title="Create Record" onclick={createRecord} class="slds-m-left_x-small"></lightning-button> </div>
</lightning-card>
</template>
JavaScript
/**
* Created by Abhyash Timsina on 31/10/2022.
*/
// uses track and wire decorators for this example
import {LightningElement, track, wire} from 'lwc';
// imports both methods from apex
import getRateForCurrencyCode from'@salesforce/apex/BitcoinService.getRateForCurrencyCode';
import getCountryCodes from'@salesforce/apex/BitcoinService.getCountryCodes';
// uses createRecord method to create record from lwc
import { createRecord } from 'lightning/uiRecordApi';
let i=0;
export default class BitcoinLwc extends LightningElement {
// set tracking variables. default currency code is Australian Dollars. Some variables are lists
@track comparisonCurrencyCodeValue = 'AUD';
@track bitcoinValue = 1.00;
@track currencyValue;
@track countryCodeList = [];
@track rateValue;
@track error;
@track countryCodeOptions = [];
// Lifecycle hook that starts when this component is inserted into DOM. In simplest terms - when page is refreshed and loaded
connectedCallback() {
// uses Apex method to get the latest rate and store it in rateValue
getRateForCurrencyCode({comparisonCurrencyCodeValue: this.comparisonCurrencyCodeValue})
.then(result => {
this.rateValue = result;
this.currencyValue = result;
})
.catch(error => {
this.error = error;
});
// apex method to retrieve all country codes and store into countryCodeOptions
getCountryCodes()
.then(result => {
this.countryCodeList = result;
for(i=0; i<result.length; i++) {
this.countryCodeOptions = [...this.countryCodeOptions ,{value: result[i] , label: result[i]} ];
}
})
.catch(error => {
this.error = error;
});
}
// function to create a record when the button is pressed
createRecord(){
// Creating mapping of fields of Bitcoin Request with values
var fields = {'Bitcoin_Value__c' : this.bitcoinValue, 'Rate__c' : this.rateValue, 'Currency_Code__c' : this.comparisonCurrencyCodeValue, 'Currency_Value__c' : this.currencyValue};
// Record details to pass to create method with api name of Bitcoin Request Object.
var objRecordInput = {'apiName' : 'Bitcoin_Request__c', fields};
// Lightning Data Service method to create record
createRecord(objRecordInput).then(response => {
// Present alert to user
alert('Bitcoin Request Record created with Id: ' +response.id);
}).catch(error => {
alert('Error: ' +JSON.stringify(error));
});
}
// Event handler that tracks when a currency code is changed
handleComparisonCurrencyChange(event) {
this.comparisonCurrencyCodeValue = event.detail.value;
// Each time a new currency code is selected, it uses APEX method to gather the newest rate
getRateForCurrencyCode({comparisonCurrencyCodeValue: event.detail.value})
// Then store the result values into the respective fields in the JS & HTML
.then(result => {
this.rateValue = result;
this.bitcoinValue = (this.currencyValue / result);
this.currencyValue = (result * this.bitcoinValue);
})
.catch(error => {
this.error = error;
});
}
// Event handler to handle user input changes in Bitcoin value. Does real time calculation
handleBitcoinValueChange(event) {
this.currencyValue = (this.rateValue * event.detail.value);
}
// Event handler to handle user input changes in Currency Value. Does real time calculation
handleCurrencyValueChange(event) {
this.bitcoinValue = (event.detail.value / this.rateValue);
}
}
Metadata
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>56.0</apiVersion>
<description>Bitcoin Lwc</description>
<isExposed>true</isExposed>
<masterLabel>Bitcoin Lwc</masterLabel>
// Allows LWC to be visible in AppPage, RecordPage and HomePage
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle>
Source Code for BitcoinService (with annotation starting with //)
/**
* Created by Abhyash Timsina on 31/10/2022.
*/
public class BitcoinService {
// Wrapper class to store data for Currency Code, Currency Name and Currency Rate - the variable names are used to match the API JSON
public class BitpayList
{
public String code;
public String name;
public Decimal rate;
}
// Method to get country codes. AuraEnabled is required for LWC to communicate with APEX
@AuraEnabled(Cacheable=true)
public static List<String> getCountryCodes() {
//Create an empty list of country codes
List<String> countryCodesList = new List<String>();
//Create an empty list of wrapper class BitpayList
List<BitpayList> response = new List<BitpayList>();
// Do a REST callout to bitpay website using GET
try {
HttpRequest req = new HttpRequest();
HttpResponse res = new HttpResponse();
Http http = new Http();
req.setHeader('Content-Type', 'application/json');
req.setHeader('X-Accept-Version', '2.0.0');
req.setEndpoint('https://bitpay.com/api/rates/');
req.setMethod('GET');
res = http.send(req);
// Capture the response values into the wrapper class values and deserialize the values
response = (List<BitpayList>) JSON.deserialize(res.getBody(), List<BitpayList>.class);
System.debug(response);
System.debug('Str:' + res.getBody());
// For all the returned values, map it with each country code
for (BitpayList bpl : response) {
countryCodesList.add(bpl.code);
}
} catch (Exception e) {
System.debug('Error:' + e.getMessage() + 'LN:' + e.getLineNumber());
}
//Return the value
return countryCodesList;
}
// Method to get rate for the provided currency code
@AuraEnabled(Cacheable=true)
public static Decimal getRateForCurrencyCode( String comparisonCurrencyCodeValue ) {
Decimal rateValue;
// The same REST callout as above
List<BitpayList> response = new List<BitpayList>();
try {
HttpRequest req = new HttpRequest();
HttpResponse res = new HttpResponse();
Http http = new Http();
req.setHeader('Content-Type', 'application/json');
req.setHeader('X-Accept-Version', '2.0.0');
req.setEndpoint('https://bitpay.com/api/rates/');
req.setMethod('GET');
res = http.send(req);
response = (List<BitpayList>) JSON.deserialize(res.getBody(), List<BitpayList>.class);
System.debug(response);
System.debug('Str:' + res.getBody());
// Has a IF statement to pass the rate to the selected country code (remember this method is called upon event change)
for (BitpayList bpl : response) {
if (bpl.code == comparisonCurrencyCodeValue) {
rateValue = bpl.rate;
}
}
} catch (Exception e) {
System.debug('Error:' + e.getMessage() + 'LN:' + e.getLineNumber());
}
//Return the value
return rateValue;
}
}
Source Code for BitcoinServiceTest (with annotation starting with //)
/**
* Created by Abhyash Timsina on 31/10/2022.
*/
@IsTest
private class BitcoinServiceTest {
@IsTest static void getBitcoinRateTest() {
// Set headers so it imitates a real REST callout
System.Test.startTest();
Map<String,String> headers = new Map<String, String>();
headers.put('Content-Type','application/json');
headers.put('X-Accept-Version','2.0.0');
// Uses mock class to set expected values from the mock response
System.Test.setMock(HttpCalloutMock.class, new BitcoinServiceMock(200,'Success','[{"code":"AUD","name":"Australian Dollar","rate":83743.75},{"code":"CNY","name":"Chinese Yuan","rate":396272.55},{"code":"CHF","name":"Swiss Franc","rate":56491.49},{"code":"SEK","name":"Swedish Krona","rate":544288.77},{"code":"NZD","name":"New Zealand Dollar","rate":87039.69}]',headers));
Decimal rate =
// Call the BitcoinService apex class for the test with country code AUD as parameter
BitcoinService.getRateForCurrencyCode('AUD');
System.Test.stopTest();
// Assert that the rate is accurate
System.assertEquals(83743.75, rate);
}
@IsTest static void getCountryCodesTest() {
System.Test.startTest();
Map<String,String> headers = new Map<String, String>();
headers.put('Content-Type','application/json');
headers.put('X-Accept-Version','2.0.0');
// Same as above, set mock expected returns
System.Test.setMock(HttpCalloutMock.class, new BitcoinServiceMock(200,'Success','[{"code":"AUD","name":"Australian Dollar","rate":83743.75},{"code":"CNY","name":"Chinese Yuan","rate":396272.55},{"code":"CHF","name":"Swiss Franc","rate":56491.49},{"code":"SEK","name":"Swedish Krona","rate":544288.77},{"code":"NZD","name":"New Zealand Dollar","rate":87039.69}]',headers));
List <String> countryCodeList = BitcoinService.getCountryCodes();
System.Test.stopTest();
// Assert that the response back is a list of 5 as per the mock
System.assert(countryCodeList.size()==5);
}
}
Source Code for BitcoinServiceMock (with annotation starting with //)
/**
* Created by Abhyash Timsina on 31/10/2022.
*/
// Note that his implements HttpCalloutMock - cannot be used as a standard apex class
public with sharing class BitcoinServiceMock implements HttpCalloutMock{
// Same variable values from Bitpay REST JSON
protected Integer code;
protected String status;
protected String body;
// Variable to store the header data from REST
protected Map<String , String> responseHeaders;
// Mock Method that accepts passed from APEX header variables
public BitcoinServiceMock(Integer code, String status, String body, Map<String,String> responseHeaders) {
this.code = code;
this.status = status;
this.body = body;
this.responseHeaders = responseHeaders;
}
public HttpResponse respond(HttpRequest req) {
// Create Fake Response
HttpResponse res = new HttpResponse();
for (String key : this.responseHeaders.keySet()) {
res.setHeader(key, this.responseHeaders.get(key));
}
Pass the header values to each of the variables back to TEST class
res.setBody(this.body);
res.setStatusCode(this.code);
res.setStatus(this.status);
return res;
}
}
Package Link:
If you need help in installing and getting this working – please contact me on LinkedIn
Comments